diff --git a/scripts/generateRequires.mjs b/scripts/generateRequires.mjs
index 4b8ab236ade7..2b3eab7c9c5c 100644
--- a/scripts/generateRequires.mjs
+++ b/scripts/generateRequires.mjs
@@ -16,7 +16,18 @@ async function writeWebsocketHandlerImports() {
}
async function writeClientActionImports() {
- const lines = ["'use strict';\n", 'class ActionsManager {', ' constructor(client) {', ' this.client = client;\n'];
+ const lines = [
+ "'use strict';\n",
+ 'class ActionsManager {',
+ ' constructor(client) {',
+ ' this.client = client;\n',
+ ' // These symbols represent fully built data that we inject at times when calling actions manually.',
+ ' // Action#getUser for example, will return the injected data (which is assumed to be a built structure)',
+ ' // instead of trying to make it from provided data',
+ " this.injectedUser = Symbol('djs.actions.injectedUser');",
+ " this.injectedChannel = Symbol('djs.actions.injectedChannel');",
+ " this.injectedMessage = Symbol('djs.actions.injectedMessage');\n",
+ ];
const actionsDirectory = new URL('../src/client/actions', import.meta.url);
for (const file of (await readdir(actionsDirectory)).sort()) {
diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js
index 65415dd20af8..5594fdd84585 100644
--- a/src/client/actions/ActionsManager.js
+++ b/src/client/actions/ActionsManager.js
@@ -4,9 +4,9 @@ class ActionsManager {
constructor(client) {
this.client = client;
- // These symbols represent fully built data that we inject at times when calling actions manually. Action#getUser,
- // for example, will return the injected data (which is assumed to be a built structure) instead of trying to make
- // it from provided data
+ // These symbols represent fully built data that we inject at times when calling actions manually.
+ // Action#getUser for example, will return the injected data (which is assumed to be a built structure)
+ // instead of trying to make it from provided data
this.injectedUser = Symbol('djs.actions.injectedUser');
this.injectedChannel = Symbol('djs.actions.injectedChannel');
this.injectedMessage = Symbol('djs.actions.injectedMessage');
diff --git a/src/client/websocket/handlers/index.js b/src/client/websocket/handlers/index.js
index df3c2ea2deca..271613878cdf 100644
--- a/src/client/websocket/handlers/index.js
+++ b/src/client/websocket/handlers/index.js
@@ -11,7 +11,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')],
['GUILD_CREATE', require('./GUILD_CREATE')],
['GUILD_DELETE', require('./GUILD_DELETE')],
['GUILD_UPDATE', require('./GUILD_UPDATE')],
@@ -62,6 +61,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 0f7e914077e4..85fc03d92482 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 fc37a7b1a74a..f56723f98be2 100644
--- a/src/managers/ThreadManager.js
+++ b/src/managers/ThreadManager.js
@@ -59,10 +59,9 @@ 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!
+ * @property {FetchArchivedThreadOptions} [archived] Options used to fetch archived threads
*/
/**
@@ -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
*/
@@ -128,7 +127,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 1ebb3c5319aa..a2f4bed0e27b 100644
--- a/src/structures/AutocompleteInteraction.js
+++ b/src/structures/AutocompleteInteraction.js
@@ -96,9 +96,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 9136752f0b3f..e07b181df81a 100644
--- a/src/structures/ClientUser.js
+++ b/src/structures/ClientUser.js
@@ -53,11 +53,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);
- const newData = await this.client.api.users('@me').patch({ data });
- this.client.token = newData.token;
- 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 9b3517c077ec..100799d69bdd 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -716,9 +716,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;
@@ -1341,7 +1338,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() {
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 28e7770cdba3..2fff24990d37 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -62,6 +62,11 @@ class GuildMember extends Base {
*/
this.communicationDisabledUntilTimestamp = null;
+ /**
+ * The role ids of the member
+ * @type {Snowflake[]}
+ * @private
+ */
this._roles = [];
if (data) this._patch(data);
}
@@ -352,6 +357,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);
@@ -404,7 +419,7 @@ class GuildMember extends Base {
* .catch(console.error);
*/
ban(options) {
- return this.guild.members.ban(this, options);
+ return this.guild.bans.create(this, options);
}
/**
@@ -418,6 +433,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);
@@ -494,12 +514,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 30752424aee3..38714bea79bf 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -599,11 +599,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;
}
@@ -645,12 +651,11 @@ class Message extends Base {
* channel.bulkDelete(messages.filter(message => message.bulkDeletable));
*/
get bulkDeletable() {
- const permissions = this.channel?.permissionsFor(this.client.user);
return (
(this.inGuild() &&
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
);
}
diff --git a/src/structures/Presence.js b/src/structures/Presence.js
index b9cf29ed4943..345e4f186eff 100644
--- a/src/structures/Presence.js
+++ b/src/structures/Presence.js
@@ -155,6 +155,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 });
/**
@@ -325,6 +331,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 cf59017bdf76..c35a42491d1a 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 588519053db3..dd1880edec3d 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -310,12 +310,22 @@ class User extends Base {
json.bannerURL = this.banner ? this.bannerURL() : this.banner;
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 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 e44cceb79490..a05a1281c118 100644
--- a/src/structures/interfaces/TextBasedChannel.js
+++ b/src/structures/interfaces/TextBasedChannel.js
@@ -136,25 +136,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 c34f36246c3c..fd920a3c501d 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 d1eff3bfaf95..01b53deb742d 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -166,6 +166,7 @@ import {
export class Activity {
private constructor(presence: Presence, data?: RawActivityData);
+ public readonly presence: Presence;
public applicationId: Snowflake | null;
public assets: RichPresenceAssets | null;
public buttons: string[];
@@ -1189,6 +1190,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 */
@@ -2124,6 +2126,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;
@@ -3203,6 +3206,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;
}
@@ -5080,7 +5084,6 @@ export interface FetchReactionUsersOptions {
export interface FetchThreadsOptions {
archived?: FetchArchivedThreadOptions;
- active?: boolean;
}
export interface FileOptions {