diff --git a/src/client/websocket/handlers/index.js b/src/client/websocket/handlers/index.js index 340dd12..0c44808 100644 --- a/src/client/websocket/handlers/index.js +++ b/src/client/websocket/handlers/index.js @@ -15,7 +15,6 @@ const handlers = Object.fromEntries([ ['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')], ['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')], ['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')], - ['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')], ['CALL_CREATE', require('./CALL_CREATE')], ['CALL_UPDATE', require('./CALL_UPDATE')], ['CALL_DELETE', require('./CALL_DELETE')], @@ -81,6 +80,7 @@ const handlers = Object.fromEntries([ ['GUILD_SCHEDULED_EVENT_DELETE', require('./GUILD_SCHEDULED_EVENT_DELETE')], ['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')], ['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')], + ['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')], ]); module.exports = handlers; diff --git a/src/managers/CachedManager.js b/src/managers/CachedManager.js index 0f7e914..85fc03d 100644 --- a/src/managers/CachedManager.js +++ b/src/managers/CachedManager.js @@ -12,6 +12,13 @@ class CachedManager extends DataManager { constructor(client, holds, iterable) { super(client, holds); + /** + * The private cache of items for this manager. + * @type {Collection} + * @private + * @readonly + * @name CachedManager#_cache + */ Object.defineProperty(this, '_cache', { value: this.client.options.makeCache(this.constructor, this.holds) }); let cleanup = this._cache[_cleanupSymbol]?.(); diff --git a/src/managers/ThreadManager.js b/src/managers/ThreadManager.js index 81c6be1..a9c6dd1 100644 --- a/src/managers/ThreadManager.js +++ b/src/managers/ThreadManager.js @@ -59,15 +59,14 @@ class ThreadManager extends CachedManager { */ /** - * The options for fetching multiple threads, the properties are mutually exclusive + * Options for fetching multiple threads. * @typedef {Object} FetchThreadsOptions * @property {FetchArchivedThreadOptions} [archived] The options used to fetch archived threads - * @property {boolean} [active] When true, fetches active threads. If `archived` is set, this is ignored! */ /** * Obtains a thread from Discord, or the channel cache if it's already available. - * @param {ThreadChannelResolvable|FetchChannelThreadsOptions} [options] The options to fetch threads. If it is a + * @param {ThreadChannelResolvable|FetchChannelThreadsOptions|FetchThreadsOptions} [options] The options to fetch threads. If it is a * ThreadChannelResolvable then the specified thread will be fetched. Fetches all active threads if `undefined` * @param {BaseFetchOptions} [cacheOptions] Additional options for this fetch. The `force` field gets ignored * if `options` is not a {@link ThreadChannelResolvable} @@ -78,10 +77,10 @@ class ThreadManager extends CachedManager { * .then(channel => console.log(channel.name)) * .catch(console.error); */ - fetch(options, { cache = true, force = false } = {}) { + fetch(options, { cache, force } = {}) { if (!options) return this.fetchActive(cache); const channel = this.client.channels.resolveId(options); - if (channel) return this.client.channels.fetch(channel, cache, force); + if (channel) return this.client.channels.fetch(channel, { cache, force }); if (options.archived) { return this.fetchArchived(options.archived, cache); } @@ -102,7 +101,7 @@ class ThreadManager extends CachedManager { * @property {string} [type='public'] The type of threads to fetch, either `public` or `private` * @property {boolean} [fetchAll=false] Whether to fetch **all** archived threads when type is `private`. * Requires `MANAGE_THREADS` if true - * @property {DateResolvable|ThreadChannelResolvable} [before] Only return threads that were created before this Date + * @property {DateResolvable|ThreadChannelResolvable} [before] Only return threads that were archived before this Date * or Snowflake. Must be a {@link ThreadChannelResolvable} when type is `private` and fetchAll is `false` * @property {number} [limit] Maximum number of threads to return */ @@ -130,7 +129,7 @@ class ThreadManager extends CachedManager { let timestamp; let id; if (typeof before !== 'undefined') { - if (before instanceof ThreadChannel || /^\d{16,19}$/.test(String(before))) { + if (before instanceof ThreadChannel || /^\d{17,19}$/.test(String(before))) { id = this.resolveId(before); timestamp = this.resolve(before)?.archivedAt?.toISOString(); } else { diff --git a/src/structures/AutocompleteInteraction.js b/src/structures/AutocompleteInteraction.js index 3f1a2e3..a1d3db2 100644 --- a/src/structures/AutocompleteInteraction.js +++ b/src/structures/AutocompleteInteraction.js @@ -95,9 +95,7 @@ class AutocompleteInteraction extends Interaction { await this.client.api.interactions(this.id, this.token).callback.post({ data: { type: InteractionResponseTypes.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT, - data: { - choices: options, - }, + data: { choices: { ...options, name_localizations: options.nameLocalizations } }, }, auth: false, }); diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index e74589e..a70acfb 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -142,17 +142,13 @@ class ClientUser extends User { * @param {ClientUserEditData} data The new data * @returns {Promise} */ - async edit(data) { - if (typeof data.avatar !== 'undefined') { - data.avatar = await DataResolver.resolveImage(data.avatar); - } - if (typeof data.banner !== 'undefined') { - data.banner = await DataResolver.resolveImage(data.banner); - } - const newData = await this.client.api.users('@me').patch({ data }); - this.client.token = newData.token; - this.client.password = data?.password ? data?.password : this.client.password; - const { updated } = this.client.actions.UserUpdate.handle(newData); + async edit({ username, avatar }) { + const data = await this.client.api + .users('@me') + .patch({ username, avatar: avatar && (await DataResolver.resolveImage(avatar)) }); + + this.client.token = data.token; + const { updated } = this.client.actions.UserUpdate.handle(data); return updated ?? this; } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 408276b..bd9f827 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -769,9 +769,6 @@ class Guild extends AnonymousGuild { * .catch(console.error); */ async fetchVanityData() { - if (!this.features.includes('VANITY_URL')) { - throw new Error('VANITY_URL'); - } const data = await this.client.api.guilds(this.id, 'vanity-url').get(); this.vanityURLCode = data.code; this.vanityURLUses = data.uses; @@ -1462,7 +1459,7 @@ class Guild extends AnonymousGuild { * @example * // Leave a guild * guild.leave() - * .then(g => console.log(`Left the guild ${g}`)) + * .then(guild => console.log(`Left the guild ${guild.name}`)) * .catch(console.error); */ async leave() { @@ -1488,7 +1485,7 @@ class Guild extends AnonymousGuild { * @example * // Delete a guild * guild.delete() - * .then(g => console.log(`Deleted the guild ${g}`)) + * .then(guild => console.log(`Deleted the guild ${guild.name}`)) * .catch(console.error); */ async delete() { @@ -1634,9 +1631,6 @@ class Guild extends AnonymousGuild { * .catch(console.error); */ async setVanityCode(code = '') { - if (!this.features.includes('VANITY_URL')) { - throw new Error('VANITY_URL'); - } if (typeof code !== 'string') throw new TypeError('INVALID_VANITY_URL_CODE'); const data = await this.client.api.guilds(this.id, 'vanity-url').patch({ data: { code }, diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 72c1a86..0e9aa30 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -63,6 +63,11 @@ class GuildMember extends Base { */ this.communicationDisabledUntilTimestamp = null; + /** + * The role ids of the member + * @type {Snowflake[]} + * @private + */ this._roles = []; if (data) this._patch(data); } @@ -414,6 +419,16 @@ class GuildMember extends Base { * @param {?string} nick The nickname for the guild member, or `null` if you want to reset their nickname * @param {string} [reason] Reason for setting the nickname * @returns {Promise} + * @example + * // Set a nickname for a guild member + * guildMember.setNickname('cool nickname', 'Needed a new nickname') + * .then(member => console.log(`Set nickname of ${member.user.username}`)) + * .catch(console.error); + * @example + * // Remove a nickname for a guild member + * guildMember.setNickname(null, 'No nicknames allowed!') + * .then(member => console.log(`Removed nickname for ${member.user.username}`)) + * .catch(console.error); */ setNickname(nick, reason) { return this.edit({ nick }, reason); @@ -539,7 +554,7 @@ class GuildMember extends Base { * .catch(console.error); */ ban(options) { - return this.guild.members.ban(this, options); + return this.guild.bans.create(this, options); } /** @@ -553,6 +568,11 @@ class GuildMember extends Base { * guildMember.disableCommunicationUntil(Date.now() + (5 * 60 * 1000), 'They deserved it') * .then(console.log) * .catch(console.error); + * @example + * // Remove the timeout of a guild member + * guildMember.disableCommunicationUntil(null) + * .then(member => console.log(`Removed timeout for ${member.displayName}`)) + * .catch(console.error); */ disableCommunicationUntil(communicationDisabledUntil, reason) { return this.edit({ communicationDisabledUntil }, reason); @@ -639,12 +659,22 @@ class GuildMember extends Base { json.displayAvatarURL = this.displayAvatarURL(); return json; } - - // These are here only for documentation purposes - they are implemented by TextBasedChannel - /* eslint-disable no-empty-function */ - send() {} } +/** + * Sends a message to this user. + * @method send + * @memberof GuildMember + * @instance + * @param {string|MessagePayload|MessageOptions} options The options to provide + * @returns {Promise} + * @example + * // Send a direct message + * guildMember.send('Hello!') + * .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) + * .catch(console.error); + */ + TextBasedChannel.applyToClass(GuildMember); exports.GuildMember = GuildMember; diff --git a/src/structures/Message.js b/src/structures/Message.js index 78610b5..80c5786 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -21,7 +21,6 @@ const MessageFlags = require('../util/MessageFlags'); const Permissions = require('../util/Permissions'); const SnowflakeUtil = require('../util/SnowflakeUtil'); const Util = require('../util/Util'); -// Const { ApplicationCommand } = require('discord.js-selfbot-v13'); - Not being used in this file, not necessary. /** * @type {WeakSet} @@ -337,10 +336,7 @@ class Message extends Base { } if (data.referenced_message) { - this.channel?.messages._add({ - guild_id: data.message_reference?.guild_id, - ...data.referenced_message, - }); + this.channel?.messages._add({ guild_id: data.message_reference?.guild_id, ...data.referenced_message }); } /** @@ -605,11 +601,17 @@ class Message extends Base { const precheck = Boolean( this.author.id === this.client.user.id && !deletedMessages.has(this) && (!this.guild || this.channel?.viewable), ); + // Regardless of permissions thread messages cannot be edited if - // the thread is locked. + // the thread is archived or the thread is locked and the bot does not have permission to manage threads. if (this.channel?.isThread()) { - return precheck && !this.channel.locked; + if (this.channel.archived) return false; + if (this.channel.locked) { + const permissions = this.permissionsFor(this.client.user); + if (!permissions?.has(Permissions.FLAGS.MANAGE_THREADS, true)) return false; + } } + return precheck; } @@ -651,13 +653,12 @@ class Message extends Base { * channel.bulkDelete(messages.filter(message => message.bulkDeletable)); */ get bulkDeletable() { - if (!this.client.user.bot) return false; - const permissions = this.channel?.permissionsFor(this.client.user); return ( (this.inGuild() && + this.client.user.bot && Date.now() - this.createdTimestamp < MaxBulkDeletableMessageAge && this.deletable && - permissions?.has(Permissions.FLAGS.MANAGE_MESSAGES, false)) ?? + this.channel?.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_MESSAGES, false)) ?? false ); } @@ -901,9 +902,7 @@ class Message extends Base { if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(this.channel.type)) { return Promise.reject(new Error('MESSAGE_THREAD_PARENT')); } - if (this.hasThread) { - return Promise.reject(new Error('MESSAGE_EXISTING_THREAD')); - } + if (this.hasThread) return Promise.reject(new Error('MESSAGE_EXISTING_THREAD')); return this.channel.threads.create({ ...options, startMessage: this }); } @@ -923,9 +922,7 @@ class Message extends Base { */ fetchWebhook() { if (!this.webhookId) return Promise.reject(new Error('WEBHOOK_MESSAGE')); - if (this.webhookId === this.applicationId) { - return Promise.reject(new Error('WEBHOOK_APPLICATION')); - } + if (this.webhookId === this.applicationId) return Promise.reject(new Error('WEBHOOK_APPLICATION')); return this.client.fetchWebhook(this.webhookId); } @@ -974,9 +971,7 @@ class Message extends Base { equals(message, rawData) { if (!message) return false; const embedUpdate = !message.author && !message.attachments; - if (embedUpdate) { - return this.id === message.id && this.embeds.length === message.embeds.length; - } + if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length; let equal = this.id === message.id && diff --git a/src/structures/Presence.js b/src/structures/Presence.js index cbed0fc..cf8a24a 100644 --- a/src/structures/Presence.js +++ b/src/structures/Presence.js @@ -176,6 +176,12 @@ class Presence extends Base { */ class Activity { constructor(presence, data) { + /** + * The presence of the Activity + * @type {Presence} + * @readonly + * @name Activity#presence + */ Object.defineProperty(this, 'presence', { value: presence }); /** @@ -346,6 +352,12 @@ class Activity { */ class RichPresenceAssets { constructor(activity, assets) { + /** + * The activity of the RichPresenceAssets + * @type {Activity} + * @readonly + * @name RichPresenceAssets#activity + */ Object.defineProperty(this, 'activity', { value: activity }); /** diff --git a/src/structures/Role.js b/src/structures/Role.js index cf59017..c35a424 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -233,6 +233,10 @@ class Role extends Base { * @param {RoleResolvable} role Role to compare to this one * @returns {number} Negative number if this role's position is lower (other role's is higher), * positive number if this one is higher (other's is lower), 0 if equal + * @example + * // Compare the position of a role to another + * const roleCompare = role.comparePositionTo(otherRole); + * if (roleCompare >= 1) console.log(`${role.name} is higher than ${otherRole.name}`); */ comparePositionTo(role) { return this.guild.roles.comparePositions(this, role); diff --git a/src/structures/User.js b/src/structures/User.js index a6fd582..e9fb5a2 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -645,12 +645,22 @@ class User extends Base { ); return data; } - - // These are here only for documentation purposes - they are implemented by TextBasedChannel - /* eslint-disable no-empty-function */ - send() {} } +/** + * Sends a message to this user. + * @method send + * @memberof User + * @instance + * @param {string|MessagePayload|MessageOptions} options The options to provide + * @returns {Promise} + * @example + * // Send a direct message + * user.send('Hello!') + * .then(message => console.log(`Sent message: ${message.content} to ${user.tag}`)) + * .catch(console.error); + */ + TextBasedChannel.applyToClass(User); module.exports = User; diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index a0ac2da..c07fed2 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -155,25 +155,6 @@ class TextBasedChannel { * }) * .then(console.log) * .catch(console.error); - * @example - * // Send an embed with a local image inside - * channel.send({ - * content: 'This is an embed', - * embeds: [ - * { - * thumbnail: { - * url: 'attachment://file.jpg' - * } - * } - * ], - * files: [{ - * attachment: 'entire/path/to/file.jpg', - * name: 'file.jpg' - * description: 'A description of the file' - * }] - * }) - * .then(console.log) - * .catch(console.error); */ async send(options) { const User = require('../User'); diff --git a/src/util/BitField.js b/src/util/BitField.js index c34f362..fd920a3 100644 --- a/src/util/BitField.js +++ b/src/util/BitField.js @@ -146,8 +146,8 @@ class BitField { if (bit instanceof BitField) return bit.bitfield; if (Array.isArray(bit)) return bit.map(p => this.resolve(p)).reduce((prev, p) => prev | p, defaultBit); if (typeof bit === 'string') { - if (typeof this.FLAGS[bit] !== 'undefined') return this.FLAGS[bit]; if (!isNaN(bit)) return typeof defaultBit === 'bigint' ? BigInt(bit) : Number(bit); + if (this.FLAGS[bit] !== undefined) return this.FLAGS[bit]; } throw new RangeError('BITFIELD_INVALID', bit); } diff --git a/typings/index.d.ts b/typings/index.d.ts index f75c4f2..8192c5c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -378,6 +378,7 @@ export class CustomStatus { export class Activity { private constructor(presence: Presence, data?: RawActivityData); + public readonly presence: Presence; public applicationId: Snowflake | null; public assets: RichPresenceAssets | null; public buttons: string[]; @@ -1595,6 +1596,7 @@ export class GuildEmoji extends BaseGuildEmoji { export class GuildMember extends PartialTextBasedChannel(Base) { private constructor(client: Client, data: RawGuildMemberData, guild: Guild); + private _roles: Snowflake[]; public avatar: string | null; public readonly bannable: boolean; /** @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091 */ @@ -2647,6 +2649,7 @@ export class ReactionEmoji extends Emoji { export class RichPresenceAssets { private constructor(activity: Activity, assets: RawRichPresenceAssets); + public readonly activity: Activity; public largeImage: Snowflake | null; public largeText: string | null; public smallImage: Snowflake | null; @@ -3769,6 +3772,7 @@ export abstract class DataManager extends BaseManager { export abstract class CachedManager extends DataManager { protected constructor(client: Client, holds: Constructable); + private readonly _cache: Collection; private _add(data: unknown, cache?: boolean, { id, extras }?: { id: K; extras: unknown[] }): Holds; } @@ -6007,7 +6011,6 @@ export type FetchThreadMembersOptions = export interface FetchThreadsOptions { archived?: FetchArchivedThreadOptions; - active?: boolean; } export interface FileOptions {