diff --git a/DOCUMENT.md b/DOCUMENT.md
index bbaba03..a22c2ce 100644
--- a/DOCUMENT.md
+++ b/DOCUMENT.md
@@ -117,8 +117,6 @@ User {
bot: false,
system: false,
flags: UserFlagsBitField { bitfield: 256 },
- friend: false,
- blocked: false,
note: null,
connectedAccounts: [],
premiumSince: 1623357181151,
@@ -156,6 +154,40 @@ Guild {}
```
+## Group DM
+
+Click to show
+
+Code:
+```js
+/* Create */
+const memberAdd = [
+ client.users.cache.get('id1'),
+ client.users.cache.get('id2'),
+ ...
+ client.users.cache.get('id9')
+]
+// Max member add to Group: 9, Min: 2
+await client.channels.createGroupDM(memberAdd);
+/* Edit */
+const groupDM = client.channels.cache.get('id');
+await groupDM.setName('New Name');
+await groupDM.setIcon('iconURL');
+await groupDM.getInvite();
+await groupDM.fetchInvite();
+await groupDM.removeInvite(invite);
+await groupDM.addMember(user);
+await groupDM.removeMember(user);
+/* Text Channel not Bulk delete */
+await groupDM.send('Hello World');
+await groupDM.delete(); // Leave
+```
+Response
+```js
+Guild {}
+```
+
+
## Custom Status and RPC
diff --git a/package.json b/package.json
index 69731fb..6a1135d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "discord.js-selfbot-v13",
- "version": "1.2.6",
+ "version": "1.2.7",
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
"main": "./src/index.js",
"types": "./typings/index.d.ts",
@@ -47,6 +47,7 @@
"@types/ws": "^8.5.2",
"axios": "^0.26.1",
"bignumber.js": "^9.0.2",
+ "bufferutil": "^4.0.6",
"chalk": "^4.1.2",
"discord-api-types": "^0.27.3",
"discord-bettermarkdown": "^1.1.0",
@@ -59,6 +60,7 @@
"node-fetch": "^2.6.1",
"string-similarity": "^4.0.4",
"undici": "^4.15.0",
+ "utf-8-validate": "^5.0.9",
"ws": "^8.5.0"
},
"engines": {
diff --git a/src/errors/Messages.js b/src/errors/Messages.js
index 43c91f0..625b891 100644
--- a/src/errors/Messages.js
+++ b/src/errors/Messages.js
@@ -56,7 +56,7 @@ const Messages = {
EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.',
EMBED_DESCRIPTION: 'MessageEmbed description must be a string.',
EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.',
- /* add */
+ /* add */
EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.',
BUTTON_LABEL: 'MessageButton label must be a string',
@@ -152,6 +152,10 @@ const Messages = {
INVITE_NOT_FOUND: 'Could not find the requested invite.',
+ NOT_OWNER_GROUP_DM_CHANNEL: "You can't do this action [Missing Permission]",
+ USER_ALREADY_IN_GROUP_DM_CHANNEL: 'User is already in the channel.',
+ USER_NOT_IN_GROUP_DM_CHANNEL: 'User is not in the channel.',
+
DELETE_GROUP_DM_CHANNEL:
"Bots don't have access to Group DM Channels and cannot delete them",
FETCH_GROUP_DM_CHANNEL:
@@ -200,7 +204,8 @@ const Messages = {
APPLICATION_ID_INVALID: "The application isn't BOT",
INVALID_NITRO: 'Invalid Nitro Code',
MESSAGE_ID_NOT_FOUND: 'Message ID not found',
- MESSAGE_EMBED_LINK_LENGTH: 'Message content with embed link length is too long',
+ MESSAGE_EMBED_LINK_LENGTH:
+ 'Message content with embed link length is too long',
};
for (const [name, message] of Object.entries(Messages)) register(name, message);
diff --git a/src/managers/ChannelManager.js b/src/managers/ChannelManager.js
index 06b25dc..456cc32 100644
--- a/src/managers/ChannelManager.js
+++ b/src/managers/ChannelManager.js
@@ -4,6 +4,8 @@ const process = require('node:process');
const CachedManager = require('./CachedManager');
const { Channel } = require('../structures/Channel');
const { Events, ThreadChannelTypes } = require('../util/Constants');
+const User = require('../structures/User');
+const PartialGroupDMChannel = require('../structures/PartialGroupDMChannel');
let cacheWarningEmitted = false;
@@ -113,8 +115,27 @@ class ChannelManager extends CachedManager {
}
const data = await this.client.api.channels(id).get();
+ // delete in cache
+ this._remove(id);
return this._add(data, null, { cache, allowUnknownGuild });
}
+ // Create Group DM
+ /**
+ * Create Group DM
+ * @param {Array} recipients Array of recipients
+ * @returns {PartialGroupDMChannel} Channel
+ */
+ async createGroupDM(recipients) {
+ // Check
+ if (!recipients || !Array.isArray(recipients)) throw new Error('No recipients || Invalid Type (Array)');
+ recipients = recipients.filter(r => r instanceof User && r.id && r.friend);
+ console.log(recipients);
+ if (recipients.length < 2 || recipients.length > 9) throw new Error('Invalid Users length (2 - 9)');
+ const data = await this.client.api.users['@me'].channels.post({
+ data: { recipients: recipients.map((r) => r.id) },
+ });
+ return this._add(data, null, { cache: true, allowUnknownGuild: true });
+ }
}
module.exports = ChannelManager;
diff --git a/src/managers/DMGroupManager.js b/src/managers/DMGroupManager.js
deleted file mode 100644
index 2a12c44..00000000
--- a/src/managers/DMGroupManager.js
+++ /dev/null
@@ -1 +0,0 @@
-// Todo: create, add, remove, update
\ No newline at end of file
diff --git a/src/structures/PartialGroupDMChannel.js b/src/structures/PartialGroupDMChannel.js
index 715d0b6..b774cee 100644
--- a/src/structures/PartialGroupDMChannel.js
+++ b/src/structures/PartialGroupDMChannel.js
@@ -2,6 +2,14 @@
const { Channel } = require('./Channel');
const { Error } = require('../errors');
+const { Collection } = require('discord.js');
+const { Message } = require('./Message');
+const MessageManager = require('../managers/MessageManager');
+const User = require('./User');
+const DataResolver = require('../util/DataResolver');
+const TextBasedChannel = require('./interfaces/TextBasedChannel');
+const Invite = require('./Invite');
+
/**
* Represents a Partial Group DM Channel on Discord.
@@ -10,7 +18,6 @@ const { Error } = require('../errors');
class PartialGroupDMChannel extends Channel {
constructor(client, data) {
super(client, data);
-
/**
* The name of this Group DM Channel
* @type {?string}
@@ -33,7 +40,84 @@ class PartialGroupDMChannel extends Channel {
* The recipients of this Group DM Channel.
* @type {PartialRecipient[]}
*/
- this.recipients = data.recipients;
+ this.recipients = new Collection();
+
+ /**
+ * Messages data
+ * @type {Collection}
+ */
+ this.messages = new MessageManager(this);
+
+ /**
+ * Last Message ID
+ * @type {?snowflake}
+ */
+ this.lastMessageId = null;
+
+ /**
+ * Last Pin Timestamp
+ * @type {UnixTimestamp}
+ */
+ this.lastPinTimestamp = null;
+
+ /**
+ * The owner of this Group DM Channel
+ * @type {?User}
+ * @readonly
+ */
+ this.owner = client.users.cache.get(data.owner_id);
+ this.ownerId = data.owner_id;
+
+ /**
+ * Invites fetch
+ */
+ this.invites = new Collection();
+
+ this._setup(client, data);
+ }
+
+ /**
+ *
+ * @param {Discord.Client} client
+ * @param {object} data
+ * @private
+ */
+ _setup(client, data) {
+ if ('recipients' in data) {
+ Promise.all(
+ data.recipients.map((recipient) => {
+ this.recipients.set(
+ recipient.id,
+ client.users.cache.get(data.owner_id) || recipient,
+ );
+ }),
+ );
+ }
+ if ('last_pin_timestamp' in data) {
+ const date = new Date(data.last_pin_timestamp);
+ this.lastPinTimestamp = date.getTime();
+ }
+ if ('last_message_id' in data) {
+ this.lastMessageId = data.last_message_id;
+ }
+ }
+
+ /**
+ *
+ * @param {Object} data name, icon
+ * @returns
+ * @private
+ */
+ async edit(data) {
+ const _data = {};
+ if ('name' in data) _data.name = data.name?.trim() ?? null;
+ if (typeof data.icon !== 'undefined')
+ _data.icon = await DataResolver.resolveImage(data.icon);
+ const newData = await this.client.api.channels(this.id).patch({
+ data: _data,
+ });
+
+ return this.client.actions.ChannelUpdate.handle(newData).updated;
}
/**
@@ -42,16 +126,85 @@ class PartialGroupDMChannel extends Channel {
* @returns {?string}
*/
iconURL({ format, size } = {}) {
- return this.icon && this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size);
+ return (
+ this.icon &&
+ this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size)
+ );
}
- delete() {
- return Promise.reject(new Error('DELETE_GROUP_DM_CHANNEL'));
+ async addMember(user) {
+ if (this.ownerId !== this.client.user.id)
+ return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
+ if (!user instanceof User)
+ return Promise.reject(
+ new TypeError('User is not an instance of Discord.User'),
+ );
+ if (this.recipients.get(user.id)) return Promise.reject(new Error('USER_ALREADY_IN_GROUP_DM_CHANNEL'));
+ //
+ await this.client.api.channels[this.id].recipients[user.id].put();
+ this.recipients.set(user.id, user);
+ return this;
}
- fetch() {
- return Promise.reject(new Error('FETCH_GROUP_DM_CHANNEL'));
+ async removeMember(user) {
+ if (this.ownerId !== this.client.user.id)
+ return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
+ if (!user instanceof User)
+ return Promise.reject(
+ new TypeError('User is not an instance of Discord.User'),
+ );
+ if (!this.recipients.get(user.id)) return Promise.reject(new Error('USER_NOT_IN_GROUP_DM_CHANNEL'));
+ await this.client.api.channels[this.id].recipients[user.id].delete();
+ this.recipients.delete(user.id);
+ return this;
}
+
+ setName(name) {
+ return this.edit({ name });
+ }
+
+ setIcon(icon) {
+ return this.edit({ icon });
+ }
+
+ async getInvite() {
+ const inviteCode = await this.client.api.channels(this.id).invites.post({
+ data: {
+ max_age: 86400,
+ },
+ });
+ const invite = new Invite(this.client, inviteCode);
+ this.invites.set(invite.code, invite);
+ return invite;
+ }
+
+ async fetchInvite(force = false) {
+ if (this.ownerId !== this.client.user.id)
+ return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
+ if (!force && this.invites.size) return this.invites;
+ const invites = await this.client.api.channels(this.id).invites.get();
+ await Promise.all(invites.map(invite => this.invites.set(invite.code, new Invite(this.client, invite))));
+ return this.invites;
+ }
+
+ async removeInvite(invite) {
+ if (this.ownerId !== this.client.user.id)
+ return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
+ if (!invite instanceof Invite)
+ return Promise.reject(new TypeError('Invite is not an instance of Discord.Invite'));
+ await this.client.api.channels(this.id).invites[invite.code].delete();
+ this.invites.delete(invite.code);
+ return this;
+ }
+
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ /* eslint-disable no-empty-function */
+ get lastMessage() { }
+ get lastPinAt() { }
+ send() { }
+ sendTyping() { }
}
+TextBasedChannel.applyToClass(PartialGroupDMChannel, false);
+
module.exports = PartialGroupDMChannel;
diff --git a/src/structures/User.js b/src/structures/User.js
index d2edde0..5c634e0 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -14,121 +14,135 @@ const ApplicationCommandManager = require('../managers/ApplicationCommandManager
* @extends {Base}
*/
class User extends Base {
- constructor(client, data) {
- super(client);
+ constructor(client, data) {
+ super(client);
- /**
- * The user's id
- * @type {Snowflake}
- */
- this.id = data.id;
+ /**
+ * The user's id
+ * @type {Snowflake}
+ */
+ this.id = data.id;
- this.bot = null;
+ this.bot = null;
- this.system = null;
+ this.system = null;
- this.flags = null;
-
- this.friend = client.friends.cache.has(this.id);
-
- this.blocked = client.blocked.cache.has(this.id);
+ this.flags = null;
// Code written by https://github.com/aiko-chan-ai
this.connectedAccounts = [];
this.premiumSince = null;
this.premiumGuildSince = null;
this.mutualGuilds = new Collection();
- this.applications = null;
- this.note = null;
- this._patch(data);
- }
+ this.applications = null;
+ this.note = null;
+ this._patch(data);
+ }
- _patch(data) {
- if ('username' in data) {
- /**
- * The username of the user
- * @type {?string}
- */
- this.username = data.username;
- } else {
- this.username ??= null;
- }
+ _patch(data) {
+ if ('username' in data) {
+ /**
+ * The username of the user
+ * @type {?string}
+ */
+ this.username = data.username;
+ } else {
+ this.username ??= null;
+ }
- if ('bot' in data) {
- /**
- * Whether or not the user is a bot
- * @type {?boolean}
- */
- this.bot = Boolean(data.bot);
- if (this.bot == true) {
- this.applications = new ApplicationCommandManager(this.client, undefined, this);
- }
- } else if (!this.partial && typeof this.bot !== 'boolean') {
- this.bot = false;
- }
+ if ('bot' in data) {
+ /**
+ * Whether or not the user is a bot
+ * @type {?boolean}
+ */
+ this.bot = Boolean(data.bot);
+ if (this.bot == true) {
+ this.applications = new ApplicationCommandManager(
+ this.client,
+ undefined,
+ this,
+ );
+ }
+ } else if (!this.partial && typeof this.bot !== 'boolean') {
+ this.bot = false;
+ }
- if ('discriminator' in data) {
- /**
- * A discriminator based on username for the user
- * @type {?string}
- */
- this.discriminator = data.discriminator;
- } else {
- this.discriminator ??= null;
- }
+ if ('discriminator' in data) {
+ /**
+ * A discriminator based on username for the user
+ * @type {?string}
+ */
+ this.discriminator = data.discriminator;
+ } else {
+ this.discriminator ??= null;
+ }
- if ('avatar' in data) {
- /**
- * The user avatar's hash
- * @type {?string}
- */
- this.avatar = data.avatar;
- } else {
- this.avatar ??= null;
- }
+ if ('avatar' in data) {
+ /**
+ * The user avatar's hash
+ * @type {?string}
+ */
+ this.avatar = data.avatar;
+ } else {
+ this.avatar ??= null;
+ }
- if ('banner' in data) {
- /**
- * The user banner's hash
- * The user must be force fetched for this property to be present or be updated
- * @type {?string}
- */
- this.banner = data.banner;
- } else if (this.banner !== null) {
- this.banner ??= undefined;
- }
+ if ('banner' in data) {
+ /**
+ * The user banner's hash
+ * The user must be force fetched for this property to be present or be updated
+ * @type {?string}
+ */
+ this.banner = data.banner;
+ } else if (this.banner !== null) {
+ this.banner ??= undefined;
+ }
- if ('accent_color' in data) {
- /**
- * The base 10 accent color of the user's banner
- * The user must be force fetched for this property to be present or be updated
- * @type {?number}
- */
- this.accentColor = data.accent_color;
- } else if (this.accentColor !== null) {
- this.accentColor ??= undefined;
- }
+ if ('accent_color' in data) {
+ /**
+ * The base 10 accent color of the user's banner
+ * The user must be force fetched for this property to be present or be updated
+ * @type {?number}
+ */
+ this.accentColor = data.accent_color;
+ } else if (this.accentColor !== null) {
+ this.accentColor ??= undefined;
+ }
- if ('system' in data) {
- /**
- * Whether the user is an Official Discord System user (part of the urgent message system)
- * @type {?boolean}
- */
- this.system = Boolean(data.system);
- } else if (!this.partial && typeof this.system !== 'boolean') {
- this.system = false;
- }
+ if ('system' in data) {
+ /**
+ * Whether the user is an Official Discord System user (part of the urgent message system)
+ * @type {?boolean}
+ */
+ this.system = Boolean(data.system);
+ } else if (!this.partial && typeof this.system !== 'boolean') {
+ this.system = false;
+ }
- if ('public_flags' in data) {
- /**
- * The flags for this user
- * @type {?UserFlags}
- */
- this.flags = new UserFlags(data.public_flags);
- }
- }
+ if ('public_flags' in data) {
+ /**
+ * The flags for this user
+ * @type {?UserFlags}
+ */
+ this.flags = new UserFlags(data.public_flags);
+ }
+ }
+
+ /**
+ * Friend ?
+ * @readonly
+ */
+ get friend() {
+ return this.client.friends.cache.has(this.id);
+ }
+ /**
+ * Blocked ?
+ * @readonly
+ */
+ get blocked() {
+ return this.client.blocked.cache.has(this.id);
+ }
-
// Code written by https://github.com/aiko-chan-ai
_ProfilePatch(data) {
if (!data) return;
@@ -223,221 +237,233 @@ class User extends Base {
.relationships[this.id].delete.then((_) => _);
}
+ /**
+ * Whether this User is a partial
+ * @type {boolean}
+ * @readonly
+ */
+ get partial() {
+ return typeof this.username !== 'string';
+ }
- /**
- * Whether this User is a partial
- * @type {boolean}
- * @readonly
- */
- get partial() {
- return typeof this.username !== 'string';
- }
+ /**
+ * The timestamp the user was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return SnowflakeUtil.timestampFrom(this.id);
+ }
- /**
- * The timestamp the user was created at
- * @type {number}
- * @readonly
- */
- get createdTimestamp() {
- return SnowflakeUtil.timestampFrom(this.id);
- }
+ /**
+ * The time the user was created at
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
- /**
- * The time the user was created at
- * @type {Date}
- * @readonly
- */
- get createdAt() {
- return new Date(this.createdTimestamp);
- }
+ /**
+ * A link to the user's avatar.
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
+ * @returns {?string}
+ */
+ avatarURL({ format, size, dynamic } = {}) {
+ if (!this.avatar) return null;
+ return this.client.rest.cdn.Avatar(
+ this.id,
+ this.avatar,
+ format,
+ size,
+ dynamic,
+ );
+ }
- /**
- * A link to the user's avatar.
- * @param {ImageURLOptions} [options={}] Options for the Image URL
- * @returns {?string}
- */
- avatarURL({ format, size, dynamic } = {}) {
- if (!this.avatar) return null;
- return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic);
- }
+ /**
+ * A link to the user's default avatar
+ * @type {string}
+ * @readonly
+ */
+ get defaultAvatarURL() {
+ return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5);
+ }
- /**
- * A link to the user's default avatar
- * @type {string}
- * @readonly
- */
- get defaultAvatarURL() {
- return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5);
- }
+ /**
+ * A link to the user's avatar if they have one.
+ * Otherwise a link to their default avatar will be returned.
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
+ * @returns {string}
+ */
+ displayAvatarURL(options) {
+ return this.avatarURL(options) ?? this.defaultAvatarURL;
+ }
- /**
- * A link to the user's avatar if they have one.
- * Otherwise a link to their default avatar will be returned.
- * @param {ImageURLOptions} [options={}] Options for the Image URL
- * @returns {string}
- */
- displayAvatarURL(options) {
- return this.avatarURL(options) ?? this.defaultAvatarURL;
- }
+ /**
+ * The hexadecimal version of the user accent color, with a leading hash
+ * The user must be force fetched for this property to be present
+ * @type {?string}
+ * @readonly
+ */
+ get hexAccentColor() {
+ if (typeof this.accentColor !== 'number') return this.accentColor;
+ return `#${this.accentColor.toString(16).padStart(6, '0')}`;
+ }
- /**
- * The hexadecimal version of the user accent color, with a leading hash
- * The user must be force fetched for this property to be present
- * @type {?string}
- * @readonly
- */
- get hexAccentColor() {
- if (typeof this.accentColor !== 'number') return this.accentColor;
- return `#${this.accentColor.toString(16).padStart(6, '0')}`;
- }
+ /**
+ * A link to the user's banner.
+ * This method will throw an error if called before the user is force fetched.
+ * See {@link User#banner} for more info
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
+ * @returns {?string}
+ */
+ bannerURL({ format, size, dynamic } = {}) {
+ if (typeof this.banner === 'undefined')
+ throw new Error('USER_BANNER_NOT_FETCHED');
+ if (!this.banner) return null;
+ return this.client.rest.cdn.Banner(
+ this.id,
+ this.banner,
+ format,
+ size,
+ dynamic,
+ );
+ }
- /**
- * A link to the user's banner.
- * This method will throw an error if called before the user is force fetched.
- * See {@link User#banner} for more info
- * @param {ImageURLOptions} [options={}] Options for the Image URL
- * @returns {?string}
- */
- bannerURL({ format, size, dynamic } = {}) {
- if (typeof this.banner === 'undefined') throw new Error('USER_BANNER_NOT_FETCHED');
- if (!this.banner) return null;
- return this.client.rest.cdn.Banner(this.id, this.banner, format, size, dynamic);
- }
+ /**
+ * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
+ * @type {?string}
+ * @readonly
+ */
+ get tag() {
+ return typeof this.username === 'string'
+ ? `${this.username}#${this.discriminator}`
+ : null;
+ }
- /**
- * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
- * @type {?string}
- * @readonly
- */
- get tag() {
- return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null;
- }
+ /**
+ * The DM between the client's user and this user
+ * @type {?DMChannel}
+ * @readonly
+ */
+ get dmChannel() {
+ return this.client.users.dmChannel(this.id);
+ }
- /**
- * The DM between the client's user and this user
- * @type {?DMChannel}
- * @readonly
- */
- get dmChannel() {
- return this.client.users.dmChannel(this.id);
- }
+ /**
+ * Creates a DM channel between the client and the user.
+ * @param {boolean} [force=false] Whether to skip the cache check and request the API
+ * @returns {Promise}
+ */
+ createDM(force = false) {
+ return this.client.users.createDM(this.id, force);
+ }
- /**
- * Creates a DM channel between the client and the user.
- * @param {boolean} [force=false] Whether to skip the cache check and request the API
- * @returns {Promise}
- */
- createDM(force = false) {
- return this.client.users.createDM(this.id, force);
- }
+ /**
+ * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
+ * @returns {Promise}
+ */
+ deleteDM() {
+ return this.client.users.deleteDM(this.id);
+ }
- /**
- * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
- * @returns {Promise}
- */
- deleteDM() {
- return this.client.users.deleteDM(this.id);
- }
+ /**
+ * Checks if the user is equal to another.
+ * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
+ * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
+ * @param {User} user User to compare with
+ * @returns {boolean}
+ */
+ equals(user) {
+ return (
+ user &&
+ this.id === user.id &&
+ this.username === user.username &&
+ this.discriminator === user.discriminator &&
+ this.avatar === user.avatar &&
+ this.flags?.bitfield === user.flags?.bitfield &&
+ this.banner === user.banner &&
+ this.accentColor === user.accentColor
+ );
+ }
- /**
- * Checks if the user is equal to another.
- * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
- * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
- * @param {User} user User to compare with
- * @returns {boolean}
- */
- equals(user) {
- return (
- user &&
- this.id === user.id &&
- this.username === user.username &&
- this.discriminator === user.discriminator &&
- this.avatar === user.avatar &&
- this.flags?.bitfield === user.flags?.bitfield &&
- this.banner === user.banner &&
- this.accentColor === user.accentColor
- );
- }
+ /**
+ * Compares the user with an API user object
+ * @param {APIUser} user The API user object to compare
+ * @returns {boolean}
+ * @private
+ */
+ _equals(user) {
+ return (
+ user &&
+ this.id === user.id &&
+ this.username === user.username &&
+ this.discriminator === user.discriminator &&
+ this.avatar === user.avatar &&
+ this.flags?.bitfield === user.public_flags &&
+ ('banner' in user ? this.banner === user.banner : true) &&
+ ('accent_color' in user ? this.accentColor === user.accent_color : true)
+ );
+ }
- /**
- * Compares the user with an API user object
- * @param {APIUser} user The API user object to compare
- * @returns {boolean}
- * @private
- */
- _equals(user) {
- return (
- user &&
- this.id === user.id &&
- this.username === user.username &&
- this.discriminator === user.discriminator &&
- this.avatar === user.avatar &&
- this.flags?.bitfield === user.public_flags &&
- ('banner' in user ? this.banner === user.banner : true) &&
- ('accent_color' in user ? this.accentColor === user.accent_color : true)
- );
- }
+ /**
+ * Fetches this user's flags.
+ * @param {boolean} [force=false] Whether to skip the cache check and request the API
+ * @returns {Promise}
+ */
+ fetchFlags(force = false) {
+ return this.client.users.fetchFlags(this.id, { force });
+ }
- /**
- * Fetches this user's flags.
- * @param {boolean} [force=false] Whether to skip the cache check and request the API
- * @returns {Promise}
- */
- fetchFlags(force = false) {
- return this.client.users.fetchFlags(this.id, { force });
- }
+ /**
+ * Fetches this user.
+ * @param {boolean} [force=true] Whether to skip the cache check and request the API
+ * @returns {Promise}
+ */
+ fetch(force = true) {
+ return this.client.users.fetch(this.id, { force });
+ }
- /**
- * Fetches this user.
- * @param {boolean} [force=true] Whether to skip the cache check and request the API
- * @returns {Promise}
- */
- fetch(force = true) {
- return this.client.users.fetch(this.id, { force });
- }
+ /**
+ * When concatenated with a string, this automatically returns the user's mention instead of the User object.
+ * @returns {string}
+ * @example
+ * // Logs: Hello from <@123456789012345678>!
+ * console.log(`Hello from ${user}!`);
+ */
+ toString() {
+ return `<@${this.id}>`;
+ }
- /**
- * When concatenated with a string, this automatically returns the user's mention instead of the User object.
- * @returns {string}
- * @example
- * // Logs: Hello from <@123456789012345678>!
- * console.log(`Hello from ${user}!`);
- */
- toString() {
- return `<@${this.id}>`;
- }
+ toJSON(...props) {
+ const json = super.toJSON(
+ {
+ createdTimestamp: true,
+ defaultAvatarURL: true,
+ hexAccentColor: true,
+ tag: true,
+ },
+ ...props,
+ );
+ json.avatarURL = this.avatarURL();
+ json.displayAvatarURL = this.displayAvatarURL();
+ json.bannerURL = this.banner ? this.bannerURL() : this.banner;
+ return json;
+ }
- toJSON(...props) {
- const json = super.toJSON(
- {
- createdTimestamp: true,
- defaultAvatarURL: true,
- hexAccentColor: true,
- tag: true,
- },
- ...props,
- );
- json.avatarURL = this.avatarURL();
- json.displayAvatarURL = this.displayAvatarURL();
- json.bannerURL = this.banner ? this.bannerURL() : this.banner;
- return json;
- }
+ /**
+ * Set note to user
+ * @param {String} note Note to set
+ * @returns {Promise}
+ */
+ async setNote(note = null) {
+ await this.client.api.users['@me'].notes(id).put({ data: { note } });
+ return (this.note = note);
+ }
- /**
- * Set note to user
- * @param {String} note Note to set
- * @returns {Promise}
- */
- async setNote(note = null) {
- await this.client.api.users['@me']
- .notes(id)
- .put({ data: { note } });
- return this.note = note;
- }
-
- // These are here only for documentation purposes - they are implemented by TextBasedChannel
- /* eslint-disable no-empty-function */
- send() {}
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ /* eslint-disable no-empty-function */
+ send() {}
}
TextBasedChannel.applyToClass(User);
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 394d27e..0c32160 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -1897,12 +1897,25 @@ export class OAuth2Guild extends BaseGuild {
public permissions: Readonly;
}
-export class PartialGroupDMChannel extends Channel {
+export class PartialGroupDMChannel extends TextBasedChannelMixin(Channel, ['bulkDelete']) {
private constructor(client: Client, data: RawPartialGroupDMChannelData);
public name: string | null;
public icon: string | null;
- public recipients: PartialRecipient[];
+ public recipients: Collection;
+ public messages: MessageManager;
+ public invites: Collection;
+ public lastMessageId: Snowflake | null;
+ public lastPinTimestamp: String | null;
+ public owner: User | null;
+ public ownerId: Snowflake | null;
public iconURL(options?: StaticImageURLOptions): string | null;
+ public addMember(user: User): Promise;
+ public removeMember(user: User): Promise;
+ public setName(name: string): Promise;
+ public setIcon(icon: Base64Resolvable | null): Promise;
+ public getInvite(): Promise;
+ public fetchInvite(force: boolean): Promise;
+ public removeInvite(invite: Invite): Promise;
}
export class PermissionOverwrites extends Base {
@@ -2449,6 +2462,8 @@ export class User extends PartialTextBasedChannel(Base) {
public banner: string | null | undefined;
public bot: boolean;
public readonly createdAt: Date;
+ public readonly friend: Boolean;
+ public readonly blocked: Boolean;
public readonly createdTimestamp: number;
public discriminator: string;
public readonly defaultAvatarURL: string;
@@ -3044,6 +3059,7 @@ export class BaseGuildEmojiManager extends CachedManager {
private constructor(client: Client, iterable: Iterable);
public fetch(id: Snowflake, options?: FetchChannelOptions): Promise;
+ public createGroupDM(recipients: Array): Promise;
}
export class ClientUserSettingManager extends CachedManager {