From 54d0e9d272d0e6366237607265b179f32bf890c4 Mon Sep 17 00:00:00 2001 From: March 7th <71698422+aiko-chan-ai@users.noreply.github.com> Date: Sun, 3 Apr 2022 11:53:09 +0700 Subject: [PATCH] Group DM channel Feature list: Create, Add member, Remove member, fetchInvite, createInvite, setName, setIcon, send Message, leave .-. --- DOCUMENT.md | 36 +- package.json | 4 +- src/errors/Messages.js | 9 +- src/managers/ChannelManager.js | 21 + src/managers/DMGroupManager.js | 1 - src/structures/PartialGroupDMChannel.js | 167 ++++++- src/structures/User.js | 610 ++++++++++++------------ typings/index.d.ts | 20 +- 8 files changed, 561 insertions(+), 307 deletions(-) delete mode 100644 src/managers/DMGroupManager.js 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 {