* health check

* Update Dockerfile

* simplifying the deployment

* Update Bot.js

makes the find team command public

* test (#9)

* Dev (#7)

* health check

* Update Dockerfile

* simplifying the deployment

* Dev (#8)

* health check

* Update Dockerfile

* simplifying the deployment

* Update Bot.js

makes the find team command public

* Update PlayerService.js

* massive update????

could break stuff

* Update Bot.js

update
This commit is contained in:
VinceC
2025-07-07 21:38:19 -05:00
committed by GitHub
parent 0c86148835
commit 3453be6947
1742 changed files with 28844 additions and 67711 deletions

4
node_modules/discord.js/README.md generated vendored
View File

@@ -9,7 +9,8 @@
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Tests status" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
<a href="https://github.com/discordjs/discord.js/commits/main/packages/discord.js"><img alt="Last commit." src="https://img.shields.io/github/last-commit/discordjs/discord.js?logo=github&logoColor=ffffff&path=packages%2Fdiscord.js"></a>
<a href="https://codecov.io/gh/discordjs/discord.js"><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
</p>
<p>
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>
@@ -42,7 +43,6 @@ bun add discord.js
- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for WebSocket data compression and inflation (`npm install zlib-sync`)
- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection (`npm install bufferutil`)
- [utf-8-validate](https://www.npmjs.com/package/utf-8-validate) in combination with `bufferutil` for much faster WebSocket processing (`npm install utf-8-validate`)
- [@discordjs/voice](https://www.npmjs.com/package/@discordjs/voice) for interacting with the Discord Voice API (`npm install @discordjs/voice`)
## Example usage

22
node_modules/discord.js/package.json generated vendored
View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "discord.js",
"version": "14.16.2",
"version": "14.21.0",
"description": "A powerful library for interacting with the Discord API",
"main": "./src/index.js",
"types": "./typings/index.d.ts",
@@ -23,7 +23,8 @@
},
"files": [
"src",
"typings"
"typings/*.d.ts",
"typings/*.d.mts"
],
"contributors": [
"Crawl <icrawltogo@gmail.com>",
@@ -52,18 +53,19 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@discordjs/builders": "^1.9.0",
"@discordjs/builders": "^1.11.2",
"@discordjs/collection": "1.5.3",
"@discordjs/ws": "1.1.1",
"@discordjs/formatters": "^0.6.1",
"@discordjs/ws": "^1.2.3",
"@sapphire/snowflake": "3.5.3",
"discord-api-types": "0.37.97",
"discord-api-types": "^0.38.1",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.19.8",
"@discordjs/formatters": "^0.5.0",
"@discordjs/util": "^1.1.1",
"@discordjs/rest": "^2.4.0"
"undici": "6.21.3",
"@discordjs/rest": "^2.5.1",
"@discordjs/util": "^1.1.1"
},
"devDependencies": {
"@favware/cliff-jumper": "^4.1.0",
@@ -80,8 +82,8 @@
"tslint": "6.1.3",
"turbo": "^2.0.14",
"typescript": "~5.5.4",
"@discordjs/docgen": "^0.12.1",
"@discordjs/api-extractor": "^7.38.1",
"@discordjs/docgen": "^0.12.1",
"@discordjs/scripts": "^0.1.0"
},
"engines": {

View File

@@ -18,6 +18,7 @@ const ClientPresence = require('../structures/ClientPresence');
const GuildPreview = require('../structures/GuildPreview');
const GuildTemplate = require('../structures/GuildTemplate');
const Invite = require('../structures/Invite');
const { SoundboardSound } = require('../structures/SoundboardSound');
const { Sticker } = require('../structures/Sticker');
const StickerPack = require('../structures/StickerPack');
const VoiceRegion = require('../structures/VoiceRegion');
@@ -107,20 +108,20 @@ class Client extends BaseClient {
: null;
/**
* All of the {@link User} objects that have been cached at any point, mapped by their ids
* The user manager of this client
* @type {UserManager}
*/
this.users = new UserManager(this);
/**
* All of the guilds the client is currently handling, mapped by their ids -
* A manager of all the guilds the client is currently handling -
* as long as sharding isn't being used, this will be *every* guild the bot is a member of
* @type {GuildManager}
*/
this.guilds = new GuildManager(this);
/**
* All of the {@link BaseChannel}s that the client is currently handling, mapped by their ids -
* All of the {@link BaseChannel}s that the client is currently handling -
* as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
* is a member of. Note that DM channels will not be initially cached, and thus not be present
* in the Manager without their explicit fetching or use.
@@ -174,7 +175,7 @@ class Client extends BaseClient {
}
/**
* All custom emojis that the client has access to, mapped by their ids
* A manager of all the custom emojis that the client has access to
* @type {BaseGuildEmojiManager}
* @readonly
*/
@@ -276,7 +277,6 @@ class Client extends BaseClient {
const code = resolveInviteCode(invite);
const query = makeURLSearchParams({
with_counts: true,
with_expiration: true,
guild_scheduled_event_id: options?.guildScheduledEventId,
});
const data = await this.rest.get(Routes.invite(code), { query });
@@ -390,6 +390,19 @@ class Client extends BaseClient {
return this.fetchStickerPacks();
}
/**
* Obtains the list of default soundboard sounds.
* @returns {Promise<Collection<string, SoundboardSound>>}
* @example
* client.fetchDefaultSoundboardSounds()
* .then(sounds => console.log(`Available soundboard sounds are: ${sounds.map(sound => sound.name).join(', ')}`))
* .catch(console.error);
*/
async fetchDefaultSoundboardSounds() {
const data = await this.rest.get(Routes.soundboardDefaultSounds());
return new Collection(data.map(sound => [sound.sound_id, new SoundboardSound(this, sound)]));
}
/**
* Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds.
* @param {GuildResolvable} guild The guild to fetch the preview for

View File

@@ -112,6 +112,10 @@ class GenericAction {
return this.getPayload({ user_id: id }, manager, id, Partials.ThreadMember, false);
}
getSoundboardSound(data, guild) {
return this.getPayload(data, guild.soundboardSounds, data.sound_id, Partials.SoundboardSound);
}
spreadInjectedData(data) {
return Object.fromEntries(Object.getOwnPropertySymbols(data).map(symbol => [symbol, data[symbol]]));
}

View File

@@ -43,6 +43,7 @@ class ActionsManager {
this.register(require('./GuildScheduledEventUpdate'));
this.register(require('./GuildScheduledEventUserAdd'));
this.register(require('./GuildScheduledEventUserRemove'));
this.register(require('./GuildSoundboardSoundDelete.js'));
this.register(require('./GuildStickerCreate'));
this.register(require('./GuildStickerDelete'));
this.register(require('./GuildStickerUpdate'));

View File

@@ -9,6 +9,7 @@ const ChatInputCommandInteraction = require('../../structures/ChatInputCommandIn
const MentionableSelectMenuInteraction = require('../../structures/MentionableSelectMenuInteraction');
const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction');
const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction');
const PrimaryEntryPointCommandInteraction = require('../../structures/PrimaryEntryPointCommandInteraction');
const RoleSelectMenuInteraction = require('../../structures/RoleSelectMenuInteraction');
const StringSelectMenuInteraction = require('../../structures/StringSelectMenuInteraction');
const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction');
@@ -38,6 +39,9 @@ class InteractionCreateAction extends Action {
if (channel && !channel.isTextBased()) return;
InteractionClass = MessageContextMenuCommandInteraction;
break;
case ApplicationCommandType.PrimaryEntryPoint:
InteractionClass = PrimaryEntryPointCommandInteraction;
break;
default:
client.emit(
Events.Debug,

View File

@@ -6,7 +6,11 @@ const Events = require('../../util/Events');
class MessageCreateAction extends Action {
handle(data) {
const client = this.client;
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id, author: data.author });
const channel = this.getChannel({
id: data.channel_id,
author: data.author,
...('guild_id' in data && { guild_id: data.guild_id }),
});
if (channel) {
if (!channel.isTextBased()) return {};

View File

@@ -6,7 +6,7 @@ const Events = require('../../util/Events');
class MessageDeleteAction extends Action {
handle(data) {
const client = this.client;
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
let message;
if (channel) {
if (!channel.isTextBased()) return {};

View File

@@ -5,7 +5,7 @@ const Events = require('../../util/Events');
class MessagePollVoteAddAction extends Action {
handle(data) {
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
if (!channel?.isTextBased()) return false;
const message = this.getMessage(data, channel);

View File

@@ -5,7 +5,7 @@ const Events = require('../../util/Events');
class MessagePollVoteRemoveAction extends Action {
handle(data) {
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
if (!channel?.isTextBased()) return false;
const message = this.getMessage(data, channel);

View File

@@ -25,7 +25,7 @@ class MessageReactionAdd extends Action {
// Verify channel
const channel = this.getChannel({
id: data.channel_id,
guild_id: data.guild_id,
...('guild_id' in data && { guild_id: data.guild_id }),
user_id: data.user_id,
...this.spreadInjectedData(data),
});
@@ -51,6 +51,7 @@ class MessageReactionAdd extends Action {
/**
* Provides additional information about altered reaction
* @typedef {Object} MessageReactionEventDetails
* @property {ReactionType} type The type of the reaction
* @property {boolean} burst Determines whether a super reaction was used
*/
/**
@@ -60,7 +61,7 @@ class MessageReactionAdd extends Action {
* @param {User} user The user that applied the guild or reaction emoji
* @param {MessageReactionEventDetails} details Details of adding the reaction
*/
this.client.emit(Events.MessageReactionAdd, reaction, user, { burst: data.burst });
this.client.emit(Events.MessageReactionAdd, reaction, user, { type: data.type, burst: data.burst });
return { message, reaction, user };
}

View File

@@ -19,7 +19,11 @@ class MessageReactionRemove extends Action {
if (!user) return false;
// Verify channel
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id, user_id: data.user_id });
const channel = this.getChannel({
id: data.channel_id,
...('guild_id' in data && { guild_id: data.guild_id }),
user_id: data.user_id,
});
if (!channel?.isTextBased()) return false;
// Verify message
@@ -37,7 +41,7 @@ class MessageReactionRemove extends Action {
* @param {User} user The user whose emoji or reaction emoji was removed
* @param {MessageReactionEventDetails} details Details of removing the reaction
*/
this.client.emit(Events.MessageReactionRemove, reaction, user, { burst: data.burst });
this.client.emit(Events.MessageReactionRemove, reaction, user, { type: data.type, burst: data.burst });
return { message, reaction, user };
}

View File

@@ -6,7 +6,7 @@ const Events = require('../../util/Events');
class MessageReactionRemoveAll extends Action {
handle(data) {
// Verify channel
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
if (!channel?.isTextBased()) return false;
// Verify message

View File

@@ -5,7 +5,7 @@ const Events = require('../../util/Events');
class MessageReactionRemoveEmoji extends Action {
handle(data) {
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
if (!channel?.isTextBased()) return false;
const message = this.getMessage(data, channel);

View File

@@ -4,7 +4,7 @@ const Action = require('./Action');
class MessageUpdateAction extends Action {
handle(data) {
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
if (channel) {
if (!channel.isTextBased()) return {};

View File

@@ -2,11 +2,14 @@
const Action = require('./Action');
const Events = require('../../util/Events');
const Partials = require('../../util/Partials');
class PresenceUpdateAction extends Action {
handle(data) {
let user = this.client.users.cache.get(data.user.id);
if (!user && data.user.username) user = this.client.users._add(data.user);
if (!user && ('username' in data.user || this.client.options.partials.includes(Partials.User))) {
user = this.client.users._add(data.user);
}
if (!user) return;
if (data.user.username) {

View File

@@ -13,7 +13,7 @@ class ThreadListSyncAction extends Action {
if (data.channel_ids) {
for (const id of data.channel_ids) {
const channel = client.channels.resolve(id);
const channel = client.channels.cache.get(id);
if (channel) this.removeStale(channel);
}
} else {

View File

@@ -6,7 +6,7 @@ const Events = require('../../util/Events');
class TypingStart extends Action {
handle(data) {
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id });
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) });
if (!channel) return;
if (!channel.isTextBased()) {

View File

@@ -36,10 +36,13 @@ const BeforeReadyWhitelist = [
const WaitingForGuildEvents = [GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.GuildDelete];
const UNRESUMABLE_CLOSE_CODES = [
CloseCodes.Normal,
GatewayCloseCodes.AlreadyAuthenticated,
GatewayCloseCodes.InvalidSeq,
const UNRECOVERABLE_CLOSE_CODES = [
GatewayCloseCodes.AuthenticationFailed,
GatewayCloseCodes.InvalidShard,
GatewayCloseCodes.ShardingRequired,
GatewayCloseCodes.InvalidAPIVersion,
GatewayCloseCodes.InvalidIntents,
GatewayCloseCodes.DisallowedIntents,
];
const reasonIsDeprecated = 'the reason property is deprecated, use the code property to determine the reason';
@@ -242,7 +245,7 @@ class WebSocketManager extends EventEmitter {
this._ws.on(WSWebSocketShardEvents.Closed, ({ code, shardId }) => {
const shard = this.shards.get(shardId);
shard.emit(WebSocketShardEvents.Close, { code, reason: reasonIsDeprecated, wasClean: true });
if (UNRESUMABLE_CLOSE_CODES.includes(code) && this.destroyed) {
if (UNRECOVERABLE_CLOSE_CODES.includes(code)) {
shard.status = Status.Disconnected;
/**
* Emitted when a shard's WebSocket disconnects and will no longer reconnect.
@@ -251,7 +254,7 @@ class WebSocketManager extends EventEmitter {
* @param {number} id The shard id that disconnected
*/
this.client.emit(Events.ShardDisconnect, { code, reason: reasonIsDeprecated, wasClean: true }, shardId);
this.debug([`Shard not resumable: ${code} (${GatewayCloseCodes[code] ?? CloseCodes[code]})`], shardId);
this.debug([`Shard not recoverable: ${code} (${GatewayCloseCodes[code] ?? CloseCodes[code]})`], shardId);
return;
}

View File

@@ -32,6 +32,10 @@ const handlers = Object.fromEntries([
['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')],
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')],
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
['GUILD_SOUNDBOARD_SOUNDS_UPDATE', require('./GUILD_SOUNDBOARD_SOUNDS_UPDATE.js')],
['GUILD_SOUNDBOARD_SOUND_CREATE', require('./GUILD_SOUNDBOARD_SOUND_CREATE.js')],
['GUILD_SOUNDBOARD_SOUND_DELETE', require('./GUILD_SOUNDBOARD_SOUND_DELETE.js')],
['GUILD_SOUNDBOARD_SOUND_UPDATE', require('./GUILD_SOUNDBOARD_SOUND_UPDATE.js')],
['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE')],
['GUILD_UPDATE', require('./GUILD_UPDATE')],
['INTERACTION_CREATE', require('./INTERACTION_CREATE')],
@@ -50,9 +54,13 @@ const handlers = Object.fromEntries([
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
['READY', require('./READY')],
['RESUMED', require('./RESUMED')],
['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS.js')],
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
['SUBSCRIPTION_CREATE', require('./SUBSCRIPTION_CREATE')],
['SUBSCRIPTION_DELETE', require('./SUBSCRIPTION_DELETE')],
['SUBSCRIPTION_UPDATE', require('./SUBSCRIPTION_UPDATE')],
['THREAD_CREATE', require('./THREAD_CREATE')],
['THREAD_DELETE', require('./THREAD_DELETE')],
['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')],
@@ -61,6 +69,7 @@ const handlers = Object.fromEntries([
['THREAD_UPDATE', require('./THREAD_UPDATE')],
['TYPING_START', require('./TYPING_START')],
['USER_UPDATE', require('./USER_UPDATE')],
['VOICE_CHANNEL_EFFECT_SEND', require('./VOICE_CHANNEL_EFFECT_SEND')],
['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')],
['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')],
['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')],

View File

@@ -106,6 +106,7 @@
* @property {'GuildChannelUnowned'} GuildChannelUnowned
* @property {'GuildOwned'} GuildOwned
* @property {'GuildMembersTimeout'} GuildMembersTimeout
* @property {'GuildSoundboardSoundsTimeout'} GuildSoundboardSoundsTimeout
* @property {'GuildUncachedMe'} GuildUncachedMe
* @property {'ChannelNotCached'} ChannelNotCached
* @property {'StageChannelResolve'} StageChannelResolve
@@ -131,6 +132,8 @@
* @property {'MissingManageEmojisAndStickersPermission'} MissingManageEmojisAndStickersPermission
* <warn>This property is deprecated. Use `MissingManageGuildExpressionsPermission` instead.</warn>
*
* @property {'NotGuildSoundboardSound'} NotGuildSoundboardSound
* @property {'NotGuildSticker'} NotGuildSticker
* @property {'ReactionResolveUser'} ReactionResolveUser
@@ -266,6 +269,7 @@ const keys = [
'GuildChannelUnowned',
'GuildOwned',
'GuildMembersTimeout',
'GuildSoundboardSoundsTimeout',
'GuildUncachedMe',
'ChannelNotCached',
'StageChannelResolve',
@@ -290,6 +294,7 @@ const keys = [
'MissingManageGuildExpressionsPermission',
'MissingManageEmojisAndStickersPermission',
'NotGuildSoundboardSound',
'NotGuildSticker',
'ReactionResolveUser',

View File

@@ -91,11 +91,13 @@ const Messages = {
[DjsErrorCodes.GuildChannelUnowned]: "The fetched channel does not belong to this manager's guild.",
[DjsErrorCodes.GuildOwned]: 'Guild is owned by the client.',
[DjsErrorCodes.GuildMembersTimeout]: "Members didn't arrive in time.",
[DjsErrorCodes.GuildSoundboardSoundsTimeout]: "Soundboard sounds didn't arrive in time.",
[DjsErrorCodes.GuildUncachedMe]: 'The client user as a member of this guild is uncached.',
[DjsErrorCodes.ChannelNotCached]: 'Could not find the channel where this message came from in the cache!',
[DjsErrorCodes.StageChannelResolve]: 'Could not resolve channel to a stage channel.',
[DjsErrorCodes.GuildScheduledEventResolve]: 'Could not resolve the guild scheduled event.',
[DjsErrorCodes.FetchOwnerId]: type => `Couldn't resolve the ${type} ownerId to fetch the ${type} member.`,
[DjsErrorCodes.FetchOwnerId]: type =>
`Couldn't resolve the ${type} ownerId to fetch the ${type} ${type === 'group DM' ? 'owner' : 'member'}.`,
[DjsErrorCodes.InvalidType]: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
[DjsErrorCodes.InvalidElement]: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`,
@@ -117,6 +119,8 @@ const Messages = {
[DjsErrorCodes.MissingManageEmojisAndStickersPermission]: guild =>
`Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`,
[DjsErrorCodes.NotGuildSoundboardSound]: action =>
`Soundboard sound is a default (non-guild) soundboard sound and can't be ${action}.`,
[DjsErrorCodes.NotGuildSticker]: 'Sticker is a standard (non-guild) sticker and has no author.',
[DjsErrorCodes.ReactionResolveUser]: "Couldn't resolve the user id to remove from the reaction.",

19
node_modules/discord.js/src/index.js generated vendored
View File

@@ -29,7 +29,6 @@ exports.ChannelFlagsBitField = require('./util/ChannelFlagsBitField');
exports.Collection = require('@discordjs/collection').Collection;
exports.Constants = require('./util/Constants');
exports.Colors = require('./util/Colors');
__exportStar(require('./util/DataResolver.js'), exports);
exports.Events = require('./util/Events');
exports.Formatters = require('./util/Formatters');
exports.GuildMemberFlagsBitField = require('./util/GuildMemberFlagsBitField').GuildMemberFlagsBitField;
@@ -76,6 +75,7 @@ exports.GuildMemberManager = require('./managers/GuildMemberManager');
exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager');
exports.GuildMessageManager = require('./managers/GuildMessageManager');
exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager');
exports.GuildSoundboardSoundManager = require('./managers/GuildSoundboardSoundManager.js').GuildSoundboardSoundManager;
exports.GuildStickerManager = require('./managers/GuildStickerManager');
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager');
exports.MessageManager = require('./managers/MessageManager');
@@ -85,6 +85,7 @@ exports.ReactionManager = require('./managers/ReactionManager');
exports.ReactionUserManager = require('./managers/ReactionUserManager');
exports.RoleManager = require('./managers/RoleManager');
exports.StageInstanceManager = require('./managers/StageInstanceManager');
exports.SubscriptionManager = require('./managers/SubscriptionManager').SubscriptionManager;
exports.ThreadManager = require('./managers/ThreadManager');
exports.ThreadMemberManager = require('./managers/ThreadMemberManager');
exports.UserManager = require('./managers/UserManager');
@@ -123,12 +124,14 @@ exports.CommandInteraction = require('./structures/CommandInteraction');
exports.Collector = require('./structures/interfaces/Collector');
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
exports.Component = require('./structures/Component');
exports.ContainerComponent = require('./structures/ContainerComponent');
exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
exports.DMChannel = require('./structures/DMChannel');
exports.Embed = require('./structures/Embed');
exports.EmbedBuilder = require('./structures/EmbedBuilder');
exports.Emoji = require('./structures/Emoji').Emoji;
exports.Entitlement = require('./structures/Entitlement').Entitlement;
exports.FileComponent = require('./structures/FileComponent');
exports.ForumChannel = require('./structures/ForumChannel');
exports.Guild = require('./structures/Guild').Guild;
exports.GuildAuditLogs = require('./structures/GuildAuditLogs');
@@ -146,6 +149,9 @@ exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildS
exports.GuildTemplate = require('./structures/GuildTemplate');
exports.Integration = require('./structures/Integration');
exports.IntegrationApplication = require('./structures/IntegrationApplication');
exports.InteractionCallback = require('./structures/InteractionCallback');
exports.InteractionCallbackResource = require('./structures/InteractionCallbackResource');
exports.InteractionCallbackResponse = require('./structures/InteractionCallbackResponse');
exports.BaseInteraction = require('./structures/BaseInteraction');
exports.InteractionCollector = require('./structures/InteractionCollector');
exports.InteractionResponse = require('./structures/InteractionResponse');
@@ -158,6 +164,8 @@ exports.Attachment = require('./structures/Attachment');
exports.AttachmentBuilder = require('./structures/AttachmentBuilder');
exports.ModalBuilder = require('./structures/ModalBuilder');
exports.MediaChannel = require('./structures/MediaChannel');
exports.MediaGalleryComponent = require('./structures/MediaGalleryComponent');
exports.MediaGalleryItem = require('./structures/MediaGalleryItem');
exports.MessageCollector = require('./structures/MessageCollector');
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction');
@@ -172,11 +180,13 @@ exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
exports.Poll = require('./structures/Poll').Poll;
exports.PollAnswer = require('./structures/PollAnswer').PollAnswer;
exports.PrimaryEntryPointCommandInteraction = require('./structures/PrimaryEntryPointCommandInteraction');
exports.Presence = require('./structures/Presence').Presence;
exports.ReactionCollector = require('./structures/ReactionCollector');
exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role;
exports.SectionComponent = require('./structures/SectionComponent');
exports.SelectMenuBuilder = require('./structures/SelectMenuBuilder');
exports.ChannelSelectMenuBuilder = require('./structures/ChannelSelectMenuBuilder');
exports.MentionableSelectMenuBuilder = require('./structures/MentionableSelectMenuBuilder');
@@ -198,23 +208,30 @@ exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteract
exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction');
exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction');
exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder');
exports.SeparatorComponent = require('./structures/SeparatorComponent');
exports.SKU = require('./structures/SKU').SKU;
exports.SoundboardSound = require('./structures/SoundboardSound.js').SoundboardSound;
exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder');
exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance;
exports.Subscription = require('./structures/Subscription').Subscription;
exports.Sticker = require('./structures/Sticker').Sticker;
exports.StickerPack = require('./structures/StickerPack');
exports.Team = require('./structures/Team');
exports.TeamMember = require('./structures/TeamMember');
exports.TextChannel = require('./structures/TextChannel');
exports.TextDisplayComponent = require('./structures/TextDisplayComponent');
exports.TextInputBuilder = require('./structures/TextInputBuilder');
exports.TextInputComponent = require('./structures/TextInputComponent');
exports.ThreadChannel = require('./structures/ThreadChannel');
exports.ThreadMember = require('./structures/ThreadMember');
exports.ThreadOnlyChannel = require('./structures/ThreadOnlyChannel');
exports.ThumbnailComponent = require('./structures/ThumbnailComponent');
exports.Typing = require('./structures/Typing');
exports.UnfurledMediaItem = require('./structures/UnfurledMediaItem');
exports.User = require('./structures/User');
exports.UserContextMenuCommandInteraction = require('./structures/UserContextMenuCommandInteraction');
exports.VoiceChannelEffect = require('./structures/VoiceChannelEffect');
exports.VoiceChannel = require('./structures/VoiceChannel');
exports.VoiceRegion = require('./structures/VoiceRegion');
exports.VoiceState = require('./structures/VoiceState');

View File

@@ -82,7 +82,7 @@ class ApplicationCommandManager extends CachedManager {
* Options used to fetch Application Commands from Discord
* @typedef {BaseFetchOptions} FetchApplicationCommandOptions
* @property {Snowflake} [guildId] The guild's id to fetch commands for, for when the guild is not cached
* @property {LocaleString} [locale] The locale to use when fetching this command
* @property {Locale} [locale] The locale to use when fetching this command
* @property {boolean} [withLocalizations] Whether to fetch all localization data
*/
@@ -261,6 +261,7 @@ class ApplicationCommandManager extends CachedManager {
dm_permission: command.dmPermission ?? command.dm_permission,
integration_types: command.integrationTypes ?? command.integration_types,
contexts: command.contexts,
handler: command.handler,
};
}
}

View File

@@ -1,6 +1,7 @@
'use strict';
const CachedManager = require('./CachedManager');
const ApplicationEmoji = require('../structures/ApplicationEmoji');
const GuildEmoji = require('../structures/GuildEmoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const { parseEmoji } = require('../util/Util');
@@ -25,7 +26,8 @@ class BaseGuildEmojiManager extends CachedManager {
* * A Snowflake
* * A GuildEmoji object
* * A ReactionEmoji object
* @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable
* * An ApplicationEmoji object
* @typedef {Snowflake|GuildEmoji|ReactionEmoji|ApplicationEmoji} EmojiResolvable
*/
/**
@@ -34,7 +36,8 @@ class BaseGuildEmojiManager extends CachedManager {
* @returns {?GuildEmoji}
*/
resolve(emoji) {
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
if (emoji instanceof ReactionEmoji) return this.cache.get(emoji.id) ?? null;
if (emoji instanceof ApplicationEmoji) return this.cache.get(emoji.id) ?? null;
return super.resolve(emoji);
}
@@ -45,6 +48,7 @@ class BaseGuildEmojiManager extends CachedManager {
*/
resolveId(emoji) {
if (emoji instanceof ReactionEmoji) return emoji.id;
if (emoji instanceof ApplicationEmoji) return emoji.id;
return super.resolveId(emoji);
}
@@ -65,6 +69,7 @@ class BaseGuildEmojiManager extends CachedManager {
const emojiResolvable = this.resolve(emoji);
if (emojiResolvable) return emojiResolvable.identifier;
if (emoji instanceof ReactionEmoji) return emoji.identifier;
if (emoji instanceof ApplicationEmoji) return emoji.identifier;
if (typeof emoji === 'string') {
const res = parseEmoji(emoji);
if (res?.name.length) {

View File

@@ -69,6 +69,13 @@ class ChannelManager extends CachedManager {
channel?.parent?.threads?.cache.delete(id);
this.cache.delete(id);
if (channel?.threads) {
for (const threadId of channel.threads.cache.keys()) {
this.cache.delete(threadId);
channel.guild?.channels.cache.delete(threadId);
}
}
}
/**

View File

@@ -37,6 +37,12 @@ class EntitlementManager extends CachedManager {
* @typedef {SKU|Snowflake} SKUResolvable
*/
/**
* Options used to fetch an entitlement
* @typedef {BaseFetchOptions} FetchEntitlementOptions
* @property {EntitlementResolvable} entitlement The entitlement to fetch
*/
/**
* Options used to fetch entitlements
* @typedef {Object} FetchEntitlementsOptions
@@ -45,6 +51,7 @@ class EntitlementManager extends CachedManager {
* @property {UserResolvable} [user] The user to fetch entitlements for
* @property {SKUResolvable[]} [skus] The SKUs to fetch entitlements for
* @property {boolean} [excludeEnded] Whether to exclude ended entitlements
* @property {boolean} [excludeDeleted] Whether to exclude deleted entitlements
* @property {boolean} [cache=true] Whether to cache the fetched entitlements
* @property {Snowflake} [before] Consider only entitlements before this entitlement id
* @property {Snowflake} [after] Consider only entitlements after this entitlement id
@@ -53,21 +60,49 @@ class EntitlementManager extends CachedManager {
/**
* Fetches entitlements for this application
* @param {FetchEntitlementsOptions} [options={}] Options for fetching the entitlements
* @returns {Promise<Collection<Snowflake, Entitlement>>}
* @param {EntitlementResolvable|FetchEntitlementOptions|FetchEntitlementsOptions} [options]
* Options for fetching the entitlements
* @returns {Promise<Entitlement|Collection<Snowflake, Entitlement>>}
*/
async fetch({ limit, guild, user, skus, excludeEnded, cache = true, before, after } = {}) {
async fetch(options) {
if (!options) return this._fetchMany(options);
const { entitlement, cache, force } = options;
const resolvedEntitlement = this.resolveId(entitlement ?? options);
if (resolvedEntitlement) {
return this._fetchSingle({ entitlement: resolvedEntitlement, cache, force });
}
return this._fetchMany(options);
}
async _fetchSingle({ entitlement, cache, force = false }) {
if (!force) {
const existing = this.cache.get(entitlement);
if (existing) {
return existing;
}
}
const data = await this.client.rest.get(Routes.entitlement(this.client.application.id, entitlement));
return this._add(data, cache);
}
async _fetchMany({ limit, guild, user, skus, excludeEnded, excludeDeleted, cache, before, after } = {}) {
const query = makeURLSearchParams({
limit,
guild_id: guild && this.client.guilds.resolveId(guild),
user_id: user && this.client.users.resolveId(user),
sku_ids: skus?.map(sku => resolveSKUId(sku)).join(','),
exclude_ended: excludeEnded,
exclude_deleted: excludeDeleted,
before,
after,
});
const entitlements = await this.client.rest.get(Routes.entitlements(this.client.application.id), { query });
return entitlements.reduce(
(coll, entitlement) => coll.set(entitlement.id, this._add(entitlement, cache)),
new Collection(),

View File

@@ -97,14 +97,14 @@ class GuildBanManager extends CachedManager {
* .then(console.log)
* .catch(console.error);
*/
fetch(options) {
async fetch(options) {
if (!options) return this._fetchMany();
const { user, cache, force, limit, before, after } = options;
const resolvedUser = this.client.users.resolveId(user ?? options);
if (resolvedUser) return this._fetchSingle({ user: resolvedUser, cache, force });
if (!before && !after && !limit && cache === undefined) {
return Promise.reject(new DiscordjsError(ErrorCodes.FetchBanResolveId));
throw new DiscordjsError(ErrorCodes.FetchBanResolveId);
}
return this._fetchMany(options);
@@ -175,7 +175,7 @@ class GuildBanManager extends CachedManager {
reason: options.reason,
});
if (user instanceof GuildMember) return user;
const _user = this.client.users.resolve(id);
const _user = this.client.users.cache.get(id);
if (_user) {
return this.guild.members.resolve(_user) ?? _user;
}

View File

@@ -84,7 +84,7 @@ class GuildChannelManager extends CachedManager {
* @returns {?(GuildChannel|ThreadChannel)}
*/
resolve(channel) {
if (channel instanceof ThreadChannel) return super.resolve(channel.id);
if (channel instanceof ThreadChannel) return this.cache.get(channel.id) ?? null;
return super.resolve(channel);
}
@@ -287,7 +287,7 @@ class GuildChannelManager extends CachedManager {
const resolvedChannel = this.resolve(channel);
if (!resolvedChannel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable');
const parent = options.parent && this.client.channels.resolveId(options.parent);
const parentId = options.parent && this.client.channels.resolveId(options.parent);
if (options.position !== undefined) {
await this.setPosition(resolvedChannel, options.position, { position: options.position, reason: options.reason });
@@ -298,8 +298,8 @@ class GuildChannelManager extends CachedManager {
);
if (options.lockPermissions) {
if (parent) {
const newParent = this.guild.channels.resolve(parent);
if (parentId) {
const newParent = this.cache.get(parentId);
if (newParent?.type === ChannelType.GuildCategory) {
permission_overwrites = newParent.permissionOverwrites.cache.map(overwrite =>
PermissionOverwrites.resolve(overwrite, this.guild),
@@ -322,7 +322,7 @@ class GuildChannelManager extends CachedManager {
user_limit: options.userLimit,
rtc_region: options.rtcRegion,
video_quality_mode: options.videoQualityMode,
parent_id: parent,
parent_id: parentId,
lock_permissions: options.lockPermissions,
rate_limit_per_user: options.rateLimitPerUser,
default_auto_archive_duration: options.defaultAutoArchiveDuration,

View File

@@ -39,14 +39,14 @@ class GuildEmojiRoleManager extends DataManager {
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add
* @returns {Promise<GuildEmoji>}
*/
add(roleOrRoles) {
async add(roleOrRoles) {
if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles];
const resolvedRoles = [];
for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role));
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role);
}
resolvedRoles.push(resolvedRole);
}
@@ -60,14 +60,14 @@ class GuildEmojiRoleManager extends DataManager {
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove
* @returns {Promise<GuildEmoji>}
*/
remove(roleOrRoles) {
async remove(roleOrRoles) {
if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles];
const resolvedRoleIds = [];
for (const role of roleOrRoles.values()) {
const roleId = this.guild.roles.resolveId(role);
if (!roleId) {
return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role));
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role);
}
resolvedRoleIds.push(roleId);
}

View File

@@ -121,22 +121,22 @@ class GuildInviteManager extends CachedManager {
* .then(console.log)
* .catch(console.error);
*/
fetch(options) {
async fetch(options) {
if (!options) return this._fetchMany();
if (typeof options === 'string') {
const code = resolveInviteCode(options);
if (!code) return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode));
if (!code) throw new DiscordjsError(ErrorCodes.InviteResolveCode);
return this._fetchSingle({ code, cache: true });
}
if (!options.code) {
if (options.channelId) {
const id = this.guild.channels.resolveId(options.channelId);
if (!id) return Promise.reject(new DiscordjsError(ErrorCodes.GuildChannelResolve));
if (!id) throw new DiscordjsError(ErrorCodes.GuildChannelResolve);
return this._fetchChannelMany(id, options.cache);
}
if ('cache' in options) return this._fetchMany(options.cache);
return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode));
throw new DiscordjsError(ErrorCodes.InviteResolveCode);
}
return this._fetchSingle({
...options,

View File

@@ -4,8 +4,9 @@ const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes, RouteBases } = require('discord-api-types/v10');
const { GatewayOpcodes, Routes, RouteBases } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { ErrorCodes, DiscordjsError } = require('../errors/index.js');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const { Guild } = require('../structures/Guild');
const GuildChannel = require('../structures/GuildChannel');
@@ -18,6 +19,7 @@ const { resolveImage } = require('../util/DataResolver');
const Events = require('../util/Events');
const PermissionsBitField = require('../util/PermissionsBitField');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const { _transformAPIIncidentsData } = require('../util/Transformers.js');
const { resolveColor } = require('../util/Util');
let cacheWarningEmitted = false;
@@ -281,6 +283,112 @@ class GuildManager extends CachedManager {
return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection());
}
/**
* @typedef {Object} FetchSoundboardSoundsOptions
* @param {Snowflake[]} guildIds The ids of the guilds to fetch soundboard sounds for
* @param {number} [time=10_000] The timeout for receipt of the soundboard sounds
*/
/**
* Fetches soundboard sounds for the specified guilds.
* @param {FetchSoundboardSoundsOptions} options The options for fetching soundboard sounds
* @returns {Promise<Collection<Snowflake, Collection<Snowflake, SoundboardSound>>>}
* @example
* // Fetch soundboard sounds for multiple guilds
* const soundboardSounds = await client.guilds.fetchSoundboardSounds({
* guildIds: ['123456789012345678', '987654321098765432'],
* })
*
* console.log(soundboardSounds.get('123456789012345678'));
*/
async fetchSoundboardSounds({ guildIds, time = 10_000 }) {
const shardCount = this.client.options.shardCount;
const shardIds = new Map();
for (const guildId of guildIds) {
const shardId = ShardClientUtil.shardIdForGuildId(guildId, shardCount);
const group = shardIds.get(shardId);
if (group) group.push(guildId);
else shardIds.set(shardId, [guildId]);
}
for (const [shardId, shardGuildIds] of shardIds) {
this.client.ws.shards.get(shardId).send({
op: GatewayOpcodes.RequestSoundboardSounds,
d: {
guild_ids: shardGuildIds,
},
});
}
return new Promise((resolve, reject) => {
const remainingGuildIds = new Set(guildIds);
const fetchedSoundboardSounds = new Collection();
const handler = (soundboardSounds, guild) => {
timeout.refresh();
if (!remainingGuildIds.has(guild.id)) return;
fetchedSoundboardSounds.set(guild.id, soundboardSounds);
remainingGuildIds.delete(guild.id);
if (remainingGuildIds.size === 0) {
clearTimeout(timeout);
this.client.removeListener(Events.SoundboardSounds, handler);
this.client.decrementMaxListeners();
resolve(fetchedSoundboardSounds);
}
};
const timeout = setTimeout(() => {
this.client.removeListener(Events.SoundboardSounds, handler);
this.client.decrementMaxListeners();
reject(new DiscordjsError(ErrorCodes.GuildSoundboardSoundsTimeout));
}, time).unref();
this.client.incrementMaxListeners();
this.client.on(Events.SoundboardSounds, handler);
});
}
/**
* Options used to set incident actions. Supplying `null` to any option will disable the action.
* @typedef {Object} IncidentActionsEditOptions
* @property {?DateResolvable} [invitesDisabledUntil] When invites should be enabled again
* @property {?DateResolvable} [dmsDisabledUntil] When direct messages should be enabled again
*/
/**
* Sets the incident actions for a guild.
* @param {GuildResolvable} guild The guild
* @param {IncidentActionsEditOptions} incidentActions The incident actions to set
* @returns {Promise<IncidentActions>}
*/
async setIncidentActions(guild, { invitesDisabledUntil, dmsDisabledUntil }) {
const guildId = this.resolveId(guild);
const data = await this.client.rest.put(Routes.guildIncidentActions(guildId), {
body: {
invites_disabled_until: invitesDisabledUntil && new Date(invitesDisabledUntil).toISOString(),
dms_disabled_until: dmsDisabledUntil && new Date(dmsDisabledUntil).toISOString(),
},
});
const parsedData = _transformAPIIncidentsData(data);
const resolvedGuild = this.resolve(guild);
if (resolvedGuild) {
resolvedGuild.incidentsData = parsedData;
}
return parsedData;
}
/**
* Returns a URL for the PNG widget of a guild.
* @param {GuildResolvable} guild The guild of the widget image

View File

@@ -54,8 +54,8 @@ class GuildMemberManager extends CachedManager {
resolve(member) {
const memberResolvable = super.resolve(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
if (userResolvable) return super.resolve(userResolvable);
const userId = this.client.users.resolveId(member);
if (userId) return this.cache.get(userId) ?? null;
return null;
}
@@ -67,8 +67,8 @@ class GuildMemberManager extends CachedManager {
resolveId(member) {
const memberResolvable = super.resolveId(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
return this.cache.has(userResolvable) ? userResolvable : null;
const userId = this.client.users.resolveId(member);
return this.cache.has(userId) ? userId : null;
}
/**
@@ -144,7 +144,7 @@ class GuildMemberManager extends CachedManager {
*/
get me() {
return (
this.resolve(this.client.user.id) ??
this.cache.get(this.client.user.id) ??
(this.client.options.partials.includes(Partials.GuildMember)
? this._add({ user: { id: this.client.user.id } }, true)
: null)
@@ -223,7 +223,7 @@ class GuildMemberManager extends CachedManager {
return this._add(data, cache);
}
_fetchMany({
async _fetchMany({
limit = 0,
withPresences: presences,
users,
@@ -231,7 +231,7 @@ class GuildMemberManager extends CachedManager {
time = 120e3,
nonce = DiscordSnowflake.generate().toString(),
} = {}) {
if (nonce.length > 32) return Promise.reject(new DiscordjsRangeError(ErrorCodes.MemberFetchNonceLength));
if (nonce.length > 32) throw new DiscordjsRangeError(ErrorCodes.MemberFetchNonceLength);
return new Promise((resolve, reject) => {
if (!query && !users) query = '';
@@ -461,7 +461,7 @@ class GuildMemberManager extends CachedManager {
*/
async kick(user, reason) {
const id = this.client.users.resolveId(user);
if (!id) return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable'));
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable');
await this.client.rest.delete(Routes.guildMember(this.guild.id, id), { reason });
@@ -535,7 +535,7 @@ class GuildMemberManager extends CachedManager {
*/
async addRole(options) {
const { user, role, reason } = options;
const userId = this.guild.members.resolveId(user);
const userId = this.resolveId(user);
const roleId = this.guild.roles.resolveId(role);
await this.client.rest.put(Routes.guildMemberRole(this.guild.id, userId, roleId), { reason });
@@ -549,7 +549,7 @@ class GuildMemberManager extends CachedManager {
*/
async removeRole(options) {
const { user, role, reason } = options;
const userId = this.guild.members.resolveId(user);
const userId = this.resolveId(user);
const roleId = this.guild.roles.resolveId(role);
await this.client.rest.delete(Routes.guildMemberRole(this.guild.id, userId, roleId), { reason });

View File

@@ -101,6 +101,8 @@ class GuildMemberRoleManager extends DataManager {
/**
* Adds a role (or multiple roles) to the member.
*
* <info>Uses the idempotent PUT route for singular roles, otherwise PATCHes the underlying guild member</info>
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add
* @param {string} [reason] Reason for adding the role(s)
* @returns {Promise<GuildMember>}
@@ -138,6 +140,8 @@ class GuildMemberRoleManager extends DataManager {
/**
* Removes a role (or multiple roles) from the member.
*
* <info>Uses the idempotent DELETE route for singular roles, otherwise PATCHes the underlying guild member</info>
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove
* @param {string} [reason] Reason for removing the role(s)
* @returns {Promise<GuildMember>}

View File

@@ -7,6 +7,7 @@ const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors');
const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent');
const { resolveImage } = require('../util/DataResolver');
const { _transformGuildScheduledEventRecurrenceRule } = require('../util/Transformers');
/**
* Manages API methods for GuildScheduledEvents and stores their cache.
@@ -36,6 +37,18 @@ class GuildScheduledEventManager extends CachedManager {
* @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable
*/
/**
* Options for setting a recurrence rule for a guild scheduled event.
* @typedef {Object} GuildScheduledEventRecurrenceRuleOptions
* @property {DateResolvable} startAt The time the recurrence rule interval starts at
* @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs
* @property {number} interval The spacing between the events
* @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on
* @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on
* @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on
* @property {?number[]} byMonthDay The days within a month to recur on
*/
/**
* Options used to create a guild scheduled event.
* @typedef {Object} GuildScheduledEventCreateOptions
@@ -54,6 +67,8 @@ class GuildScheduledEventManager extends CachedManager {
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* @property {string} [reason] The reason for creating the guild scheduled event
* @property {GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule]
* The recurrence rule of the guild scheduled event
*/
/**
@@ -81,6 +96,7 @@ class GuildScheduledEventManager extends CachedManager {
entityMetadata,
reason,
image,
recurrenceRule,
} = options;
let entity_metadata, channel_id;
@@ -104,6 +120,7 @@ class GuildScheduledEventManager extends CachedManager {
entity_type: entityType,
entity_metadata,
image: image && (await resolveImage(image)),
recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule),
},
reason,
});
@@ -153,10 +170,7 @@ class GuildScheduledEventManager extends CachedManager {
return data.reduce(
(coll, rawGuildScheduledEventData) =>
coll.set(
rawGuildScheduledEventData.id,
this.guild.scheduledEvents._add(rawGuildScheduledEventData, options.cache),
),
coll.set(rawGuildScheduledEventData.id, this._add(rawGuildScheduledEventData, options.cache)),
new Collection(),
);
}
@@ -178,6 +192,8 @@ class GuildScheduledEventManager extends CachedManager {
* {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* @property {string} [reason] The reason for editing the guild scheduled event
* @property {?GuildScheduledEventRecurrenceRuleOptions} [recurrenceRule]
* The recurrence rule of the guild scheduled event
*/
/**
@@ -203,6 +219,7 @@ class GuildScheduledEventManager extends CachedManager {
entityMetadata,
reason,
image,
recurrenceRule,
} = options;
let entity_metadata;
@@ -224,6 +241,7 @@ class GuildScheduledEventManager extends CachedManager {
status,
image: image && (await resolveImage(image)),
entity_metadata,
recurrence_rule: recurrenceRule && _transformGuildScheduledEventRecurrenceRule(recurrenceRule),
},
reason,
});

View File

@@ -62,15 +62,13 @@ class PermissionOverwriteManager extends CachedManager {
* },
* ], 'Needed to change permissions');
*/
set(overwrites, reason) {
async set(overwrites, reason) {
if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) {
return Promise.reject(
new DiscordjsTypeError(
ErrorCodes.InvalidType,
'overwrites',
'Array or Collection of Permission Overwrites',
true,
),
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'overwrites',
'Array or Collection of Permission Overwrites',
true,
);
}
return this.channel.edit({ permissionOverwrites: overwrites, reason });

View File

@@ -38,8 +38,8 @@ class PresenceManager extends CachedManager {
resolve(presence) {
const presenceResolvable = super.resolve(presence);
if (presenceResolvable) return presenceResolvable;
const UserResolvable = this.client.users.resolveId(presence);
return super.resolve(UserResolvable);
const userId = this.client.users.resolveId(presence);
return super.cache.get(userId) ?? null;
}
/**
@@ -50,8 +50,8 @@ class PresenceManager extends CachedManager {
resolveId(presence) {
const presenceResolvable = super.resolveId(presence);
if (presenceResolvable) return presenceResolvable;
const userResolvable = this.client.users.resolveId(presence);
return this.cache.has(userResolvable) ? userResolvable : null;
const userId = this.client.users.resolveId(presence);
return this.cache.has(userId) ? userId : null;
}
}

View File

@@ -97,7 +97,7 @@ class ThreadManager extends CachedManager {
* Data that can be resolved to a Date object. This can be:
* * A Date object
* * A number representing a timestamp
* * An [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) string
* * An {@link https://en.wikipedia.org/wiki/ISO_8601 ISO 8601} string
* @typedef {Date|number|string} DateResolvable
*/

View File

@@ -1,11 +1,15 @@
'use strict';
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const ThreadMember = require('../structures/ThreadMember');
const { emitDeprecationWarningForRemoveThreadMember } = require('../util/Util');
let deprecationEmittedForAdd = false;
/**
* Manages API methods for GuildMembers and stores their cache.
@@ -53,7 +57,7 @@ class ThreadMemberManager extends CachedManager {
* @readonly
*/
get me() {
return this.resolve(this.client.user.id);
return this.cache.get(this.client.user.id) ?? null;
}
/**
@@ -71,8 +75,8 @@ class ThreadMemberManager extends CachedManager {
resolve(member) {
const memberResolvable = super.resolve(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
if (userResolvable) return super.resolve(userResolvable);
const userId = this.client.users.resolveId(member);
if (userId) return super.cache.get(userId) ?? null;
return null;
}
@@ -92,9 +96,20 @@ class ThreadMemberManager extends CachedManager {
* Adds a member to the thread.
* @param {UserResolvable|'@me'} member The member to add
* @param {string} [reason] The reason for adding this member
* <warn>This parameter is **deprecated**. Reasons cannot be used.</warn>
* @returns {Promise<Snowflake>}
*/
async add(member, reason) {
if (reason !== undefined && !deprecationEmittedForAdd) {
process.emitWarning(
// eslint-disable-next-line max-len
'The reason parameter of ThreadMemberManager#add() is deprecated as Discord does not parse them. It will be removed in the next major version.',
'DeprecationWarning',
);
deprecationEmittedForAdd = true;
}
const id = member === '@me' ? member : this.client.users.resolveId(member);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'member', 'UserResolvable');
await this.client.rest.put(Routes.threadMembers(this.thread.id, id), { reason });
@@ -105,9 +120,14 @@ class ThreadMemberManager extends CachedManager {
* Remove a user from the thread.
* @param {UserResolvable|'@me'} member The member to remove
* @param {string} [reason] The reason for removing this member from the thread
* <warn>This parameter is **deprecated**. Reasons cannot be used.</warn>
* @returns {Promise<Snowflake>}
*/
async remove(member, reason) {
if (reason !== undefined) {
emitDeprecationWarningForRemoveThreadMember(this.constructor.name);
}
const id = member === '@me' ? member : this.client.users.resolveId(member);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'member', 'UserResolvable');
await this.client.rest.delete(Routes.threadMembers(this.thread.id, id), { reason });

View File

@@ -7,6 +7,7 @@ const { GuildMember } = require('../structures/GuildMember');
const { Message } = require('../structures/Message');
const ThreadMember = require('../structures/ThreadMember');
const User = require('../structures/User');
const { emitDeprecationWarningForUserFetchFlags } = require('../util/Util');
/**
* Manages API methods for users and stores their cache.
@@ -100,8 +101,11 @@ class UserManager extends CachedManager {
* @param {UserResolvable} user The UserResolvable to identify
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<UserFlagsBitField>}
* @deprecated <warn>This method is deprecated and will be removed in the next major version.
* Flags may still be retrieved via {@link UserManager#fetch}.</warn>
*/
async fetchFlags(user, options) {
emitDeprecationWarningForUserFetchFlags(this.constructor.name);
return (await this.fetch(user, options)).flags;
}

View File

@@ -120,7 +120,7 @@ class Shard extends EventEmitter {
* before resolving (`-1` or `Infinity` for no wait)
* @returns {Promise<ChildProcess>}
*/
spawn(timeout = 30_000) {
async spawn(timeout = 30_000) {
if (this.process) throw new DiscordjsError(ErrorCodes.ShardingProcessExists, this.id);
if (this.worker) throw new DiscordjsError(ErrorCodes.ShardingWorkerExists, this.id);
@@ -161,7 +161,7 @@ class Shard extends EventEmitter {
*/
this.emit(ShardEvents.Spawn, child);
if (timeout === -1 || timeout === Infinity) return Promise.resolve(child);
if (timeout === -1 || timeout === Infinity) return child;
return new Promise((resolve, reject) => {
const cleanup = () => {
clearTimeout(spawnTimeoutTimer);
@@ -260,10 +260,10 @@ class Shard extends EventEmitter {
* .then(count => console.log(`${count} guilds in shard ${shard.id}`))
* .catch(console.error);
*/
fetchClientValue(prop) {
async fetchClientValue(prop) {
// Shard is dead (maybe respawning), don't cache anything and error immediately
if (!this.process && !this.worker) {
return Promise.reject(new DiscordjsError(ErrorCodes.ShardingNoChildExists, this.id));
throw new DiscordjsError(ErrorCodes.ShardingNoChildExists, this.id);
}
// Cached promise from previous call
@@ -302,13 +302,13 @@ class Shard extends EventEmitter {
* @param {*} [context] The context for the eval
* @returns {Promise<*>} Result of the script execution
*/
eval(script, context) {
async eval(script, context) {
// Stringify the script if it's a Function
const _eval = typeof script === 'function' ? `(${script})(this, ${JSON.stringify(context)})` : script;
// Shard is dead (maybe respawning), don't cache anything and error immediately
if (!this.process && !this.worker) {
return Promise.reject(new DiscordjsError(ErrorCodes.ShardingNoChildExists, this.id));
throw new DiscordjsError(ErrorCodes.ShardingNoChildExists, this.id);
}
// Cached promise from previous call

View File

@@ -8,7 +8,7 @@ const { makeError, makePlainError } = require('../util/Util');
/**
* Helper class for sharded clients spawned as a child process/worker, such as from a {@link ShardingManager}.
* Utilises IPC to send and receive data to/from the master process and other shards.
* Utilizes IPC to send and receive data to/from the master process and other shards.
*/
class ShardClientUtil {
constructor(client, mode) {
@@ -225,7 +225,8 @@ class ShardClientUtil {
* Emitted when the client encounters an error.
* <warn>Errors thrown within this event do not have a catch handler, it is
* recommended to not use async functions as `error` event handlers. See the
* [Node.js docs](https://nodejs.org/api/events.html#capture-rejections-of-promises) for details.</warn>
* {@link https://nodejs.org/api/events.html#capture-rejections-of-promises Node.js documentation}
* for details.)</warn>
* @event Client#error
* @param {Error} error The error encountered
*/

View File

@@ -14,7 +14,7 @@ const { fetchRecommendedShardCount } = require('../util/Util');
* This is a utility class that makes multi-process sharding of a bot an easy and painless experience.
* It works by spawning a self-contained {@link ChildProcess} or {@link Worker} for each individual shard, each
* containing its own instance of your bot's {@link Client}. They all have a line of communication with the master
* process, and there are several useful methods that utilise it in order to simplify tasks that are normally difficult
* process, and there are several useful methods that utilize it in order to simplify tasks that are normally difficult
* with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a
* path to your main bot script to launch for each one.
* @extends {EventEmitter}
@@ -256,9 +256,9 @@ class ShardingManager extends EventEmitter {
* @param {BroadcastEvalOptions} [options={}] The options for the broadcast
* @returns {Promise<*|Array<*>>} Results of the script execution
*/
broadcastEval(script, options = {}) {
async broadcastEval(script, options = {}) {
if (typeof script !== 'function') {
return Promise.reject(new DiscordjsTypeError(ErrorCodes.ShardingInvalidEvalBroadcast));
throw new DiscordjsTypeError(ErrorCodes.ShardingInvalidEvalBroadcast);
}
return this._performOnShards('eval', [`(${script})(this, ${JSON.stringify(options.context)})`], options.shard);
}
@@ -285,16 +285,16 @@ class ShardingManager extends EventEmitter {
* @returns {Promise<*|Array<*>>} Results of the method execution
* @private
*/
_performOnShards(method, args, shard) {
if (this.shards.size === 0) return Promise.reject(new DiscordjsError(ErrorCodes.ShardingNoShards));
async _performOnShards(method, args, shard) {
if (this.shards.size === 0) throw new DiscordjsError(ErrorCodes.ShardingNoShards);
if (typeof shard === 'number') {
if (this.shards.has(shard)) return this.shards.get(shard)[method](...args);
return Promise.reject(new DiscordjsError(ErrorCodes.ShardingShardNotFound, shard));
throw new DiscordjsError(ErrorCodes.ShardingShardNotFound, shard);
}
if (this.shards.size !== this.shardList.length) {
return Promise.reject(new DiscordjsError(ErrorCodes.ShardingInProcess));
throw new DiscordjsError(ErrorCodes.ShardingInProcess);
}
const promises = [];

View File

@@ -174,6 +174,18 @@ class ApplicationCommand extends Base {
this.contexts ??= null;
}
if ('handler' in data) {
/**
* Determines whether the interaction is handled by the app's interactions handler or by Discord.
* <info>Only available for {@link ApplicationCommandType.PrimaryEntryPoint} commands on
* applications with the {@link ApplicationFlags.Embedded} flag (i.e, those that have an Activity)</info>
* @type {?EntryPointCommandHandlerType}
*/
this.handler = data.handler;
} else {
this.handler ??= null;
}
if ('version' in data) {
/**
* Autoincrementing version identifier updated during substantial record changes
@@ -216,15 +228,20 @@ class ApplicationCommand extends Base {
* @property {string} name The name of the command, must be in all lowercase if type is
* {@link ApplicationCommandType.ChatInput}
* @property {Object<Locale, string>} [nameLocalizations] The localizations for the command name
* @property {string} description The description of the command, if type is {@link ApplicationCommandType.ChatInput}
* @property {string} description The description of the command,
* if type is {@link ApplicationCommandType.ChatInput} or {@link ApplicationCommandType.PrimaryEntryPoint}
* @property {boolean} [nsfw] Whether the command is age-restricted
* @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the command description,
* if type is {@link ApplicationCommandType.ChatInput}
* if type is {@link ApplicationCommandType.ChatInput} or {@link ApplicationCommandType.PrimaryEntryPoint}
* @property {ApplicationCommandType} [type=ApplicationCommandType.ChatInput] The type of the command
* @property {ApplicationCommandOptionData[]} [options] Options for the command
* @property {?PermissionResolvable} [defaultMemberPermissions] The bitfield used to determine the default permissions
* a member needs in order to run the command
* @property {boolean} [dmPermission] Whether the command is enabled in DMs
* @property {ApplicationIntegrationType[]} [integrationTypes] Installation contexts where the command is available
* @property {InteractionContextType[]} [contexts] Interaction contexts where the command can be used
* @property {EntryPointCommandHandlerType} [handler] Whether the interaction is handled by the app's
* interactions handler or by Discord.
*/
/**
@@ -419,7 +436,8 @@ class ApplicationCommand extends Base {
this.descriptionLocalizations ?? {},
) ||
!isEqual(command.integrationTypes ?? command.integration_types ?? [], this.integrationTypes ?? []) ||
!isEqual(command.contexts ?? [], this.contexts ?? [])
!isEqual(command.contexts ?? [], this.contexts ?? []) ||
('handler' in command && command.handler !== this.handler)
) {
return false;
}

View File

@@ -107,6 +107,27 @@ class BaseInteraction extends Base {
(coll, entitlement) => coll.set(entitlement.id, this.client.application.entitlements._add(entitlement)),
new Collection(),
);
/* eslint-disable max-len */
/**
* Mapping of installation contexts that the interaction was authorized for the related user or guild ids
* @type {APIAuthorizingIntegrationOwnersMap}
* @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-authorizing-integration-owners-object}
*/
this.authorizingIntegrationOwners = data.authorizing_integration_owners;
/* eslint-enable max-len */
/**
* Context where the interaction was triggered from
* @type {?InteractionContextType}
*/
this.context = data.context ?? null;
/**
* Attachment size limit in bytes
* @type {number}
*/
this.attachmentSizeLimit = data.attachment_size_limit;
}
/**
@@ -204,6 +225,16 @@ class BaseInteraction extends Base {
);
}
/**
* Indicates whether this interaction is a {@link PrimaryEntryPointCommandInteraction}
* @returns {boolean}
*/
isPrimaryEntryPointCommand() {
return (
this.type === InteractionType.ApplicationCommand && this.commandType === ApplicationCommandType.PrimaryEntryPoint
);
}
/**
* Indicates whether this interaction is a {@link MessageComponentInteraction}
* @returns {boolean}

View File

@@ -9,6 +9,7 @@ const Application = require('./interfaces/Application');
const ApplicationCommandManager = require('../managers/ApplicationCommandManager');
const ApplicationEmojiManager = require('../managers/ApplicationEmojiManager');
const { EntitlementManager } = require('../managers/EntitlementManager');
const { SubscriptionManager } = require('../managers/SubscriptionManager');
const ApplicationFlagsBitField = require('../util/ApplicationFlagsBitField');
const { resolveImage } = require('../util/DataResolver');
const PermissionsBitField = require('../util/PermissionsBitField');
@@ -44,6 +45,12 @@ class ClientApplication extends Application {
* @type {EntitlementManager}
*/
this.entitlements = new EntitlementManager(this.client);
/**
* The subscription manager for this application
* @type {SubscriptionManager}
*/
this.subscriptions = new SubscriptionManager(this.client);
}
_patch(data) {
@@ -156,6 +163,17 @@ class ClientApplication extends Application {
this.approximateUserInstallCount ??= null;
}
if ('approximate_user_authorization_count' in data) {
/**
* An approximate amount of users that have OAuth2 authorizations for this application.
*
* @type {?number}
*/
this.approximateUserAuthorizationCount = data.approximate_user_authorization_count;
} else {
this.approximateUserAuthorizationCount ??= null;
}
if ('guild_id' in data) {
/**
* The id of the guild associated with this application.
@@ -166,26 +184,6 @@ class ClientApplication extends Application {
this.guildId ??= null;
}
if ('cover_image' in data) {
/**
* The hash of the application's cover image
* @type {?string}
*/
this.cover = data.cover_image;
} else {
this.cover ??= null;
}
if ('rpc_origins' in data) {
/**
* The application's RPC origins, if enabled
* @type {string[]}
*/
this.rpcOrigins = data.rpc_origins;
} else {
this.rpcOrigins ??= [];
}
if ('bot_require_code_grant' in data) {
/**
* If this application's bot requires a code grant when using the OAuth2 flow
@@ -236,6 +234,36 @@ class ClientApplication extends Application {
this.roleConnectionsVerificationURL ??= null;
}
if ('event_webhooks_url' in data) {
/**
* This application's URL to receive event webhooks
* @type {?string}
*/
this.eventWebhooksURL = data.event_webhooks_url;
} else {
this.eventWebhooksURL ??= null;
}
if ('event_webhooks_status' in data) {
/**
* This application's event webhooks status
* @type {?ApplicationWebhookEventStatus}
*/
this.eventWebhooksStatus = data.event_webhooks_status;
} else {
this.eventWebhooksStatus ??= null;
}
if ('event_webhooks_types' in data) {
/**
* List of event webhooks types this application subscribes to
* @type {?ApplicationWebhookEventType[]}
*/
this.eventWebhooksTypes = data.event_webhooks_types;
} else {
this.eventWebhooksTypes ??= null;
}
/**
* The owner of this OAuth application
* @type {?(User|Team)}
@@ -277,6 +305,10 @@ class ClientApplication extends Application {
* @property {?(BufferResolvable|Base64Resolvable)} [icon] The application's icon
* @property {?(BufferResolvable|Base64Resolvable)} [coverImage] The application's cover image
* @property {string} [interactionsEndpointURL] The application's interaction endpoint URL
* @property {string} [eventWebhooksURL] The application's event webhooks URL
* @property {ApplicationWebhookEventStatus.Enabled|ApplicationWebhookEventStatus.Disabled} [eventWebhooksStatus]
* The application's event webhooks status.
* @property {ApplicationWebhookEventType[]} [eventWebhooksTypes] The application's event webhooks types
* @property {string[]} [tags] The application's tags
*/
@@ -294,6 +326,9 @@ class ClientApplication extends Application {
icon,
coverImage,
interactionsEndpointURL,
eventWebhooksURL,
eventWebhooksStatus,
eventWebhooksTypes,
tags,
} = {}) {
const data = await this.client.rest.patch(Routes.currentApplication(), {
@@ -306,6 +341,9 @@ class ClientApplication extends Application {
icon: icon && (await resolveImage(icon)),
cover_image: coverImage && (await resolveImage(coverImage)),
interactions_endpoint_url: interactionsEndpointURL,
event_webhooks_url: eventWebhooksURL,
event_webhooks_status: eventWebhooksStatus,
event_webhooks_types: eventWebhooksTypes,
tags,
},
});

View File

@@ -64,8 +64,6 @@ class ClientUser extends User {
},
});
this.client.token = data.token;
this.client.rest.setToken(data.token);
const { updated } = this.client.actions.UserUpdate.handle(data);
return updated ?? this;
}

View File

@@ -45,21 +45,6 @@ class CommandInteraction extends BaseInteraction {
*/
this.commandGuildId = data.data.guild_id ?? null;
/* eslint-disable max-len */
/**
* Mapping of installation contexts that the interaction was authorized for the related user or guild ids
* @type {APIAuthorizingIntegrationOwnersMap}
* @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-authorizing-integration-owners-object}
*/
this.authorizingIntegrationOwners = data.authorizing_integration_owners;
/* eslint-enable max-len */
/**
* Context where the interaction was triggered from
* @type {?InteractionContextType}
*/
this.context = data.context ?? null;
/**
* Whether the reply to this interaction has been deferred
* @type {boolean}
@@ -167,6 +152,7 @@ class CommandInteraction extends BaseInteraction {
editReply() {}
deleteReply() {}
followUp() {}
launchActivity() {}
showModal() {}
sendPremiumRequired() {}
awaitModalSubmit() {}

View File

@@ -14,6 +14,15 @@ class Component {
this.data = data;
}
/**
* The id of this component
* @type {number}
* @readonly
*/
get id() {
return this.data.id;
}
/**
* The type of the component
* @type {ComponentType}

View File

@@ -73,10 +73,9 @@ class Entitlement extends Base {
if ('starts_at' in data) {
/**
* The timestamp at which this entitlement is valid
* <info>This is only `null` for test entitlements</info>
* @type {?number}
*/
this.startsTimestamp = Date.parse(data.starts_at);
this.startsTimestamp = data.starts_at ? Date.parse(data.starts_at) : null;
} else {
this.startsTimestamp ??= null;
}
@@ -84,10 +83,9 @@ class Entitlement extends Base {
if ('ends_at' in data) {
/**
* The timestamp at which this entitlement is no longer valid
* <info>This is only `null` for test entitlements</info>
* @type {?number}
*/
this.endsTimestamp = Date.parse(data.ends_at);
this.endsTimestamp = data.ends_at ? Date.parse(data.ends_at) : null;
} else {
this.endsTimestamp ??= null;
}
@@ -114,7 +112,6 @@ class Entitlement extends Base {
/**
* The start date at which this entitlement is valid
* <info>This is only `null` for test entitlements</info>
* @type {?Date}
*/
get startsAt() {
@@ -123,7 +120,6 @@ class Entitlement extends Base {
/**
* The end date at which this entitlement is no longer valid
* <info>This is only `null` for test entitlements</info>
* @type {?Date}
*/
get endsAt() {

View File

@@ -21,6 +21,7 @@ const GuildEmojiManager = require('../managers/GuildEmojiManager');
const GuildInviteManager = require('../managers/GuildInviteManager');
const GuildMemberManager = require('../managers/GuildMemberManager');
const GuildScheduledEventManager = require('../managers/GuildScheduledEventManager');
const { GuildSoundboardSoundManager } = require('../managers/GuildSoundboardSoundManager');
const GuildStickerManager = require('../managers/GuildStickerManager');
const PresenceManager = require('../managers/PresenceManager');
const RoleManager = require('../managers/RoleManager');
@@ -29,6 +30,7 @@ const VoiceStateManager = require('../managers/VoiceStateManager');
const { resolveImage } = require('../util/DataResolver');
const Status = require('../util/Status');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const { _transformAPIIncidentsData } = require('../util/Transformers.js');
const { discordSort, getSortableGroupTypes, resolvePartialEmoji } = require('../util/Util');
/**
@@ -107,6 +109,12 @@ class Guild extends AnonymousGuild {
*/
this.autoModerationRules = new AutoModerationRuleManager(this);
/**
* A manager of the soundboard sounds of this guild.
* @type {GuildSoundboardSoundManager}
*/
this.soundboardSounds = new GuildSoundboardSoundManager(this);
if (!data) return;
if (data.unavailable) {
/**
@@ -470,6 +478,34 @@ class Guild extends AnonymousGuild {
stickers: data.stickers,
});
}
if ('incidents_data' in data) {
/**
* Incident actions of a guild.
* @typedef {Object} IncidentActions
* @property {?Date} invitesDisabledUntil When invites would be enabled again
* @property {?Date} dmsDisabledUntil When direct messages would be enabled again
* @property {?Date} dmSpamDetectedAt When direct message spam was detected
* @property {?Date} raidDetectedAt When a raid was detected
*/
/**
* The incidents data of this guild.
* <info>You will need to fetch the guild using {@link BaseGuild#fetch} if you want to receive
* this property.</info>
* @type {?IncidentActions}
*/
this.incidentsData = data.incidents_data && _transformAPIIncidentsData(data.incidents_data);
} else {
this.incidentsData ??= null;
}
if (data.soundboard_sounds) {
this.soundboardSounds.cache.clear();
for (const soundboardSound of data.soundboard_sounds) {
this.soundboardSounds._add(soundboardSound);
}
}
}
/**
@@ -1365,6 +1401,15 @@ class Guild extends AnonymousGuild {
return this.edit({ features });
}
/**
* Sets the incident actions for a guild.
* @param {IncidentActionsEditOptions} incidentActions The incident actions to set
* @returns {Promise<IncidentActions>}
*/
async setIncidentActions(incidentActions) {
return this.client.guilds.setIncidentActions(this.id, incidentActions);
}
/**
* Whether this guild equals another guild. It compares all properties, so for most operations
* it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often

View File

@@ -32,6 +32,7 @@ const Targets = {
AutoModeration: 'AutoModeration',
GuildOnboarding: 'GuildOnboarding',
GuildOnboardingPrompt: 'GuildOnboardingPrompt',
SoundboardSound: 'SoundboardSound',
Unknown: 'Unknown',
};
@@ -53,10 +54,11 @@ const Targets = {
* * An application command
* * An auto moderation rule
* * A guild onboarding prompt
* * A soundboard sound
* * An object with an id key if target was deleted or fake entity
* * An object where the keys represent either the new value or the old value
* @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker|
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt)} AuditLogEntryTarget
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt|SoundboardSound)} AuditLogEntryTarget
*/
/**
@@ -86,6 +88,8 @@ const Targets = {
* * ApplicationCommandPermission
* * GuildOnboarding
* * GuildOnboardingPrompt
* * SoundboardSound
* * AutoModeration
* * Unknown
* @typedef {string} AuditLogTargetType
*/
@@ -199,7 +203,6 @@ class GuildAuditLogsEntry {
case AuditLogEvent.MemberMove:
case AuditLogEvent.MessageDelete:
case AuditLogEvent.MessageBulkDelete:
this.extra = {
channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id },
count: Number(data.options.count),
@@ -214,6 +217,7 @@ class GuildAuditLogsEntry {
};
break;
case AuditLogEvent.MessageBulkDelete:
case AuditLogEvent.MemberDisconnect:
this.extra = {
count: Number(data.options.count),
@@ -365,10 +369,14 @@ class GuildAuditLogsEntry {
data.action_type === AuditLogEvent.OnboardingPromptCreate
? new GuildOnboardingPrompt(guild.client, changesReduce(this.changes, { id: data.target_id }), guild.id)
: changesReduce(this.changes, { id: data.target_id });
} else if (targetType === Targets.GuildOnboarding) {
this.target = changesReduce(this.changes, { id: data.target_id });
} else if (targetType === Targets.Role) {
this.target = guild.roles.cache.get(data.target_id) ?? { id: data.target_id };
} else if (targetType === Targets.Emoji) {
this.target = guild.emojis.cache.get(data.target_id) ?? { id: data.target_id };
} else if (targetType === Targets.SoundboardSound) {
this.target = guild.soundboardSounds.cache.get(data.target_id) ?? { id: data.target_id };
} else if (data.target_id) {
this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id };
this.target = { id: data.target_id };
}
}
@@ -392,7 +400,9 @@ class GuildAuditLogsEntry {
if (target < 110) return Targets.GuildScheduledEvent;
if (target < 120) return Targets.Thread;
if (target < 130) return Targets.ApplicationCommand;
if (target >= 140 && target < 150) return Targets.AutoModeration;
if (target < 140) return Targets.SoundboardSound;
if (target < 143) return Targets.AutoModeration;
if (target < 146) return Targets.User;
if (target >= 163 && target <= 165) return Targets.GuildOnboardingPrompt;
if (target >= 160 && target < 170) return Targets.GuildOnboarding;
return Targets.Unknown;
@@ -420,6 +430,7 @@ class GuildAuditLogsEntry {
AuditLogEvent.StickerCreate,
AuditLogEvent.GuildScheduledEventCreate,
AuditLogEvent.ThreadCreate,
AuditLogEvent.SoundboardSoundCreate,
AuditLogEvent.AutoModerationRuleCreate,
AuditLogEvent.AutoModerationBlockMessage,
AuditLogEvent.OnboardingPromptCreate,
@@ -449,6 +460,7 @@ class GuildAuditLogsEntry {
AuditLogEvent.StickerDelete,
AuditLogEvent.GuildScheduledEventDelete,
AuditLogEvent.ThreadDelete,
AuditLogEvent.SoundboardSoundDelete,
AuditLogEvent.AutoModerationRuleDelete,
AuditLogEvent.OnboardingPromptDelete,
].includes(action)
@@ -473,8 +485,12 @@ class GuildAuditLogsEntry {
AuditLogEvent.StickerUpdate,
AuditLogEvent.GuildScheduledEventUpdate,
AuditLogEvent.ThreadUpdate,
AuditLogEvent.SoundboardSoundUpdate,
AuditLogEvent.ApplicationCommandPermissionUpdate,
AuditLogEvent.AutoModerationRuleUpdate,
AuditLogEvent.AutoModerationBlockMessage,
AuditLogEvent.AutoModerationFlagToChannel,
AuditLogEvent.AutoModerationUserCommunicationDisabled,
AuditLogEvent.OnboardingPromptUpdate,
AuditLogEvent.OnboardingUpdate,
].includes(action)

View File

@@ -265,8 +265,8 @@ class GuildChannel extends BaseChannel {
* Locks in the permission overwrites from the parent channel.
* @returns {Promise<GuildChannel>}
*/
lockPermissions() {
if (!this.parent) return Promise.reject(new DiscordjsError(ErrorCodes.GuildChannelOrphan));
async lockPermissions() {
if (!this.parent) throw new DiscordjsError(ErrorCodes.GuildChannelOrphan);
const permissionOverwrites = this.parent.permissionOverwrites.cache.map(overwrite => overwrite.toJSON());
return this.edit({ permissionOverwrites });
}

View File

@@ -84,6 +84,17 @@ class GuildMember extends Base {
} else if (typeof this.avatar !== 'string') {
this.avatar = null;
}
if ('banner' in data) {
/**
* The guild member's banner hash.
* @type {?string}
*/
this.banner = data.banner;
} else {
this.banner ??= null;
}
if ('joined_at' in data) this.joinedTimestamp = Date.parse(data.joined_at);
if ('premium_since' in data) {
this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null;
@@ -111,6 +122,20 @@ class GuildMember extends Base {
} else {
this.flags ??= new GuildMemberFlagsBitField().freeze();
}
if (data.avatar_decoration_data) {
/**
* The member avatar decoration's data
*
* @type {?AvatarDecorationData}
*/
this.avatarDecorationData = {
asset: data.avatar_decoration_data.asset,
skuId: data.avatar_decoration_data.sku_id,
};
} else {
this.avatarDecorationData = null;
}
}
_clone() {
@@ -155,6 +180,24 @@ class GuildMember extends Base {
return this.avatar && this.client.rest.cdn.guildMemberAvatar(this.guild.id, this.id, this.avatar, options);
}
/**
* A link to the member's avatar decoration.
*
* @returns {?string}
*/
avatarDecorationURL() {
return this.avatarDecorationData ? this.client.rest.cdn.avatarDecoration(this.avatarDecorationData.asset) : null;
}
/**
* A link to the member's banner.
* @param {ImageURLOptions} [options={}] Options for the banner URL
* @returns {?string}
*/
bannerURL(options = {}) {
return this.banner && this.client.rest.cdn.guildMemberBanner(this.guild.id, this.id, this.banner, options);
}
/**
* A link to the member's guild avatar if they have one.
* Otherwise, a link to their {@link User#displayAvatarURL} will be returned.
@@ -165,6 +208,26 @@ class GuildMember extends Base {
return this.avatarURL(options) ?? this.user.displayAvatarURL(options);
}
/**
* A link to the member's guild banner if they have one.
* Otherwise, a link to their {@link User#bannerURL} will be returned.
* @param {ImageURLOptions} [options={}] Options for the image URL
* @returns {?string}
*/
displayBannerURL(options) {
return this.bannerURL(options) ?? this.user.bannerURL(options);
}
/**
* A link to the member's guild avatar decoration if they have one.
* Otherwise, a link to their {@link User#avatarDecorationURL} will be returned.
*
* @returns {?string}
*/
displayAvatarDecorationURL() {
return this.avatarDecorationURL() ?? this.user.avatarDecorationURL();
}
/**
* The time this member joined the guild
* @type {?Date}
@@ -198,7 +261,7 @@ class GuildMember extends Base {
* @readonly
*/
get presence() {
return this.guild.presences.resolve(this.id);
return this.guild.presences.cache.get(this.id) ?? null;
}
/**
@@ -464,11 +527,15 @@ class GuildMember extends Base {
this.joinedTimestamp === member.joinedTimestamp &&
this.nickname === member.nickname &&
this.avatar === member.avatar &&
this.banner === member.banner &&
this.pending === member.pending &&
this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp &&
this.flags.bitfield === member.flags.bitfield &&
(this._roles === member._roles ||
(this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i])))
(this._roles.length === member._roles.length &&
this._roles.every((role, index) => role === member._roles[index]))) &&
this.avatarDecorationData?.asset === member.avatarDecorationData?.asset &&
this.avatarDecorationData?.skuId === member.avatarDecorationData?.skuId
);
}
@@ -491,7 +558,10 @@ class GuildMember extends Base {
roles: true,
});
json.avatarURL = this.avatarURL();
json.bannerURL = this.bannerURL();
json.displayAvatarURL = this.displayAvatarURL();
json.displayBannerURL = this.displayBannerURL();
json.avatarDecorationURL = this.avatarDecorationURL();
return json;
}
}

View File

@@ -79,7 +79,7 @@ class GuildOnboardingPromptOption extends Base {
*/
get emoji() {
if (!this._emoji.id && !this._emoji.name) return null;
return this.client.emojis.resolve(this._emoji.id) ?? new Emoji(this.client, this._emoji);
return this.client.emojis.cache.get(this._emoji.id) ?? new Emoji(this.client, this._emoji);
}
}

View File

@@ -189,6 +189,56 @@ class GuildScheduledEvent extends Base {
} else {
this.image ??= null;
}
/**
* Represents the recurrence rule for a {@link GuildScheduledEvent}.
* @typedef {Object} GuildScheduledEventRecurrenceRule
* @property {number} startTimestamp The timestamp the recurrence rule interval starts at
* @property {Date} startAt The time the recurrence rule interval starts at
* @property {?number} endTimestamp The timestamp the recurrence rule interval ends at
* @property {?Date} endAt The time the recurrence rule interval ends at
* @property {GuildScheduledEventRecurrenceRuleFrequency} frequency How often the event occurs
* @property {number} interval The spacing between the events
* @property {?GuildScheduledEventRecurrenceRuleWeekday[]} byWeekday The days within a week to recur on
* @property {?GuildScheduledEventRecurrenceRuleNWeekday[]} byNWeekday The days within a week to recur on
* @property {?GuildScheduledEventRecurrenceRuleMonth[]} byMonth The months to recur on
* @property {?number[]} byMonthDay The days within a month to recur on
* @property {?number[]} byYearDay The days within a year to recur on
* @property {?number} count The total amount of times the event is allowed to recur before stopping
*/
/**
* @typedef {Object} GuildScheduledEventRecurrenceRuleNWeekday
* @property {number} n The week to recur on
* @property {GuildScheduledEventRecurrenceRuleWeekday} day The day within the week to recur on
*/
if ('recurrence_rule' in data) {
/**
* The recurrence rule for this scheduled event
* @type {?GuildScheduledEventRecurrenceRule}
*/
this.recurrenceRule = data.recurrence_rule && {
startTimestamp: Date.parse(data.recurrence_rule.start),
get startAt() {
return new Date(this.startTimestamp);
},
endTimestamp: data.recurrence_rule.end && Date.parse(data.recurrence_rule.end),
get endAt() {
return this.endTimestamp && new Date(this.endTimestamp);
},
frequency: data.recurrence_rule.frequency,
interval: data.recurrence_rule.interval,
byWeekday: data.recurrence_rule.by_weekday,
byNWeekday: data.recurrence_rule.by_n_weekday,
byMonth: data.recurrence_rule.by_month,
byMonthDay: data.recurrence_rule.by_month_day,
byYearDay: data.recurrence_rule.by_year_day,
count: data.recurrence_rule.count,
};
} else {
this.recurrenceRule ??= null;
}
}
/**

View File

@@ -153,8 +153,8 @@ class InteractionCollector extends Collector {
if (this.messageId && interaction.message?.id !== this.messageId) return null;
if (
this.messageInteractionId &&
interaction.message?.interaction?.id &&
interaction.message.interaction.id !== this.messageInteractionId
interaction.message?.interactionMetadata?.id &&
interaction.message.interactionMetadata.id !== this.messageInteractionId
) {
return null;
}
@@ -180,8 +180,8 @@ class InteractionCollector extends Collector {
if (this.messageId && interaction.message?.id !== this.messageId) return null;
if (
this.messageInteractionId &&
interaction.message?.interaction?.id &&
interaction.message.interaction.id !== this.messageInteractionId
interaction.message?.interactionMetadata?.id &&
interaction.message.interactionMetadata.id !== this.messageInteractionId
) {
return null;
}
@@ -224,7 +224,7 @@ class InteractionCollector extends Collector {
this.stop('messageDelete');
}
if (message.interaction?.id === this.messageInteractionId) {
if (message.interactionMetadata?.id === this.messageInteractionId) {
this.stop('messageDelete');
}
}

View File

@@ -40,7 +40,7 @@ class Invite extends Base {
*/
this.guild ??= null;
if (data.guild) {
this.guild = this.client.guilds.resolve(data.guild.id) ?? new InviteGuild(this.client, data.guild);
this.guild = this.client.guilds.cache.get(data.guild.id) ?? new InviteGuild(this.client, data.guild);
}
if ('code' in data) {

View File

@@ -9,6 +9,7 @@ const {
MessageType,
MessageFlags,
PermissionFlagsBits,
MessageReferenceType,
} = require('discord-api-types/v10');
const Attachment = require('./Attachment');
const Base = require('./Base');
@@ -22,7 +23,7 @@ const ReactionCollector = require('./ReactionCollector');
const { Sticker } = require('./Sticker');
const { DiscordjsError, ErrorCodes } = require('../errors');
const ReactionManager = require('../managers/ReactionManager');
const { createComponent } = require('../util/Components');
const { createComponent, findComponentByCustomId } = require('../util/Components');
const { NonSystemMessageTypes, MaxBulkDeletableMessageAge, UndeletableMessageTypes } = require('../util/Constants');
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const PermissionsBitField = require('../util/PermissionsBitField');
@@ -150,10 +151,10 @@ class Message extends Base {
if ('components' in data) {
/**
* An array of action rows in the message.
* An array of components in the message.
* <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged intent
* in a guild for messages that do not mention the client.</info>
* @type {ActionRow[]}
* @type {Component[]}
*/
this.components = data.components.map(component => createComponent(component));
} else {
@@ -364,6 +365,7 @@ class Message extends Base {
* @property {Snowflake} channelId The channel id that was referenced
* @property {Snowflake|undefined} guildId The guild id that was referenced
* @property {Snowflake|undefined} messageId The message id that was referenced
* @property {MessageReferenceType} type The type of message reference
*/
if ('message_reference' in data) {
@@ -375,6 +377,7 @@ class Message extends Base {
channelId: data.message_reference.channel_id,
guildId: data.message_reference.guild_id,
messageId: data.message_reference.message_id,
type: data.message_reference.type,
};
} else {
this.reference ??= null;
@@ -448,6 +451,29 @@ class Message extends Base {
this.poll ??= null;
}
if (data.message_snapshots) {
/**
* The message snapshots associated with the message reference
* @type {Collection<Snowflake, Message>}
*/
this.messageSnapshots = data.message_snapshots.reduce((coll, snapshot) => {
const channel = this.client.channels.resolve(this.reference.channelId);
const snapshotData = {
...snapshot.message,
id: this.reference.messageId,
channel_id: this.reference.channelId,
guild_id: this.reference.guildId,
};
return coll.set(
this.reference.messageId,
channel ? channel.messages._add(snapshotData) : new this.constructor(this.client, snapshotData),
);
}, new Collection());
} else {
this.messageSnapshots ??= new Collection();
}
/**
* A call associated with a message
* @typedef {Object} MessageCall
@@ -545,7 +571,7 @@ class Message extends Base {
* @readonly
*/
get thread() {
return this.channel?.threads?.resolve(this.id) ?? null;
return this.channel?.threads?.cache.get(this.id) ?? null;
}
/**
@@ -565,7 +591,7 @@ class Message extends Base {
*/
get cleanContent() {
// eslint-disable-next-line eqeqeq
return this.content != null ? cleanContent(this.content, this.channel) : null;
return this.content != null && this.channel ? cleanContent(this.content, this.channel) : null;
}
/**
@@ -679,7 +705,11 @@ class Message extends Base {
* @readonly
*/
get editable() {
const precheck = Boolean(this.author.id === this.client.user.id && (!this.guild || this.channel?.viewable));
const precheck = Boolean(
this.author.id === this.client.user.id &&
(!this.guild || this.channel?.viewable) &&
this.reference?.type !== MessageReferenceType.Forward,
);
// Regardless of permissions thread messages cannot be edited if
// the thread is archived or the thread is locked and the bot does not have permission to manage threads.
@@ -799,8 +829,8 @@ class Message extends Base {
* .then(msg => console.log(`Updated the content of a message to ${msg.content}`))
* .catch(console.error);
*/
edit(options) {
if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached));
async edit(options) {
if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached);
return this.channel.messages.edit(this, options);
}
@@ -815,8 +845,8 @@ class Message extends Base {
* .catch(console.error);
* }
*/
crosspost() {
if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached));
async crosspost() {
if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached);
return this.channel.messages.crosspost(this.id);
}
@@ -914,8 +944,8 @@ class Message extends Base {
* .then(() => console.log(`Replied to message "${message.content}"`))
* .catch(console.error);
*/
reply(options) {
if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached));
async reply(options) {
if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached);
let data;
if (options instanceof MessagePayload) {
@@ -931,6 +961,24 @@ class Message extends Base {
return this.channel.send(data);
}
/**
* Forwards this message
*
* @param {TextBasedChannelResolvable} channel The channel to forward this message to.
* @returns {Promise<Message>}
*/
forward(channel) {
const resolvedChannel = this.client.channels.resolve(channel);
if (!resolvedChannel) throw new DiscordjsError(ErrorCodes.InvalidType, 'channel', 'TextBasedChannelResolvable');
return resolvedChannel.send({
forward: {
message: this.id,
channel: this.channelId,
guild: this.guildId,
},
});
}
/**
* Options for starting a thread on a message.
* @typedef {Object} StartThreadOptions
@@ -947,12 +995,12 @@ class Message extends Base {
* @param {StartThreadOptions} [options] Options for starting a thread on this message
* @returns {Promise<ThreadChannel>}
*/
startThread(options = {}) {
if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached));
async startThread(options = {}) {
if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached);
if (![ChannelType.GuildText, ChannelType.GuildAnnouncement].includes(this.channel.type)) {
return Promise.reject(new DiscordjsError(ErrorCodes.MessageThreadParent));
throw new DiscordjsError(ErrorCodes.MessageThreadParent);
}
if (this.hasThread) return Promise.reject(new DiscordjsError(ErrorCodes.MessageExistingThread));
if (this.hasThread) throw new DiscordjsError(ErrorCodes.MessageExistingThread);
return this.channel.threads.create({ ...options, startMessage: this });
}
@@ -961,8 +1009,8 @@ class Message extends Base {
* @param {boolean} [force=true] Whether to skip the cache check and request the API
* @returns {Promise<Message>}
*/
fetch(force = true) {
if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached));
async fetch(force = true) {
if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached);
return this.channel.messages.fetch({ message: this.id, force });
}
@@ -970,9 +1018,9 @@ class Message extends Base {
* Fetches the webhook used to create this message.
* @returns {Promise<?Webhook>}
*/
fetchWebhook() {
if (!this.webhookId) return Promise.reject(new DiscordjsError(ErrorCodes.WebhookMessage));
if (this.webhookId === this.applicationId) return Promise.reject(new DiscordjsError(ErrorCodes.WebhookApplication));
async fetchWebhook() {
if (!this.webhookId) throw new DiscordjsError(ErrorCodes.WebhookMessage);
if (this.webhookId === this.applicationId) throw new DiscordjsError(ErrorCodes.WebhookApplication);
return this.client.fetchWebhook(this.webhookId);
}
@@ -1007,7 +1055,7 @@ class Message extends Base {
* @returns {?MessageActionRowComponent}
*/
resolveComponent(customId) {
return this.components.flatMap(row => row.components).find(component => component.customId === customId) ?? null;
return findComponentByCustomId(this.components, customId);
}
/**

View File

@@ -4,6 +4,7 @@ const { lazy } = require('@discordjs/util');
const BaseInteraction = require('./BaseInteraction');
const InteractionWebhook = require('./InteractionWebhook');
const InteractionResponses = require('./interfaces/InteractionResponses');
const { findComponentByCustomId } = require('../util/Components');
const getMessage = lazy(() => require('./Message').Message);
@@ -79,13 +80,11 @@ class MessageComponentInteraction extends BaseInteraction {
/**
* The component which was interacted with
* @type {MessageActionRowComponent|APIMessageActionRowComponent}
* @type {MessageActionRowComponent|APIComponentInMessageActionRow}
* @readonly
*/
get component() {
return this.message.components
.flatMap(row => row.components)
.find(component => (component.customId ?? component.custom_id) === this.customId);
return findComponentByCustomId(this.message.components, this.customId);
}
// These are here only for documentation purposes - they are implemented by InteractionResponses
@@ -98,6 +97,7 @@ class MessageComponentInteraction extends BaseInteraction {
followUp() {}
deferUpdate() {}
update() {}
launchActivity() {}
showModal() {}
sendPremiumRequired() {}
awaitModalSubmit() {}

View File

@@ -3,8 +3,7 @@
const { Buffer } = require('node:buffer');
const { lazy, isJSONEncodable } = require('@discordjs/util');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { MessageFlags } = require('discord-api-types/v10');
const ActionRowBuilder = require('./ActionRowBuilder');
const { MessageFlags, MessageReferenceType } = require('discord-api-types/v10');
const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors');
const { resolveFile } = require('../util/DataResolver');
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
@@ -92,6 +91,7 @@ class MessagePayload {
* Whether or not the target is an {@link BaseInteraction} or an {@link InteractionWebhook}
* @type {boolean}
* @readonly
* @deprecated This will no longer serve a purpose in the next major version.
*/
get isInteraction() {
const BaseInteraction = getBaseInteraction();
@@ -148,7 +148,7 @@ class MessagePayload {
}
const components = this.options.components?.map(component =>
(isJSONEncodable(component) ? component : new ActionRowBuilder(component)).toJSON(),
isJSONEncodable(component) ? component.toJSON() : this.target.client.options.jsonTransformer(component),
);
let username;
@@ -164,15 +164,10 @@ class MessagePayload {
let flags;
if (
this.options.flags !== undefined ||
(this.isMessage && this.options.reply === undefined) ||
this.isMessageManager
// eslint-disable-next-line eqeqeq
this.options.flags != null
) {
flags =
// eslint-disable-next-line eqeqeq
this.options.flags != null
? new MessageFlagsBitField(this.options.flags).bitfield
: this.target.flags?.bitfield;
flags = new MessageFlagsBitField(this.options.flags).bitfield;
}
if (isInteraction && this.options.ephemeral) {
@@ -201,6 +196,22 @@ class MessagePayload {
}
}
if (typeof this.options.forward === 'object') {
const reference = this.options.forward.message;
const channel_id = reference.channelId ?? this.target.client.channels.resolveId(this.options.forward.channel);
const guild_id = reference.guildId ?? this.target.client.guilds.resolveId(this.options.forward.guild);
const message_id = this.target.messages.resolveId(reference);
if (message_id) {
if (!channel_id) throw new DiscordjsError(ErrorCodes.InvalidType, 'channelId', 'TextBasedChannelResolvable');
message_reference = {
type: MessageReferenceType.Forward,
message_id,
channel_id,
guild_id: guild_id ?? undefined,
};
}
}
const attachments = this.options.files?.map((file, index) => ({
id: index.toString(),
description: file.description,
@@ -237,7 +248,10 @@ class MessagePayload {
components,
username,
avatar_url: avatarURL,
allowed_mentions: content === undefined && message_reference === undefined ? undefined : allowedMentions,
allowed_mentions:
this.isMessage && message_reference === undefined && this.target.author.id !== this.target.client.user.id
? undefined
: allowedMentions,
flags,
message_reference,
attachments: this.options.attachments,

View File

@@ -1,6 +1,7 @@
'use strict';
const { Routes } = require('discord-api-types/v10');
const ApplicationEmoji = require('./ApplicationEmoji');
const GuildEmoji = require('./GuildEmoji');
const ReactionEmoji = require('./ReactionEmoji');
const ReactionUserManager = require('../managers/ReactionUserManager');
@@ -35,7 +36,7 @@ class MessageReaction {
* Whether the client has super-reacted using this emoji
* @type {boolean}
*/
this.meBurst = data.me_burst;
this.meBurst = Boolean(data.me_burst);
/**
* A manager of the users that have given this reaction
@@ -51,7 +52,7 @@ class MessageReaction {
}
_patch(data) {
if ('burst_colors' in data) {
if (data.burst_colors) {
/**
* Hexadecimal colors used for this super reaction
* @type {?string[]}
@@ -108,16 +109,24 @@ class MessageReaction {
}
/**
* The emoji of this reaction. Either a {@link GuildEmoji} object for known custom emojis, or a {@link ReactionEmoji}
* object which has fewer properties. Whatever the prototype of the emoji, it will still have
* The emoji of this reaction. Either a {@link GuildEmoji} object for known custom emojis,
* {@link ApplicationEmoji} for application emojis, or a {@link ReactionEmoji} object
* which has fewer properties. Whatever the prototype of the emoji, it will still have
* `name`, `id`, `identifier` and `toString()`
* @type {GuildEmoji|ReactionEmoji}
* @type {GuildEmoji|ReactionEmoji|ApplicationEmoji}
* @readonly
*/
get emoji() {
if (this._emoji instanceof GuildEmoji) return this._emoji;
if (this._emoji instanceof ApplicationEmoji) return this._emoji;
// Check to see if the emoji has become known to the client
if (this._emoji.id) {
const applicationEmojis = this.message.client.application.emojis.cache;
if (applicationEmojis.has(this._emoji.id)) {
const emoji = applicationEmojis.get(this._emoji.id);
this._emoji = emoji;
return emoji;
}
const emojis = this.message.client.emojis.cache;
if (emojis.has(this._emoji.id)) {
const emoji = emojis.get(this._emoji.id);

View File

@@ -119,6 +119,7 @@ class ModalSubmitInteraction extends BaseInteraction {
deferUpdate() {}
update() {}
sendPremiumRequired() {}
launchActivity() {}
}
InteractionResponses.applyToClass(ModalSubmitInteraction, 'showModal');

View File

@@ -1,12 +1,14 @@
'use strict';
const { BaseChannel } = require('./BaseChannel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { DiscordjsError, ErrorCodes } = require('../errors');
const PartialGroupDMMessageManager = require('../managers/PartialGroupDMMessageManager');
/**
* Represents a Partial Group DM Channel on Discord.
* @extends {BaseChannel}
* @implements {TextBasedChannel}
*/
class PartialGroupDMChannel extends BaseChannel {
constructor(client, data) {
@@ -25,7 +27,7 @@ class PartialGroupDMChannel extends BaseChannel {
* The hash of the channel icon
* @type {?string}
*/
this.icon = data.icon;
this.icon = data.icon ?? null;
/**
* Recipient data received in a {@link PartialGroupDMChannel}.
@@ -37,13 +39,43 @@ class PartialGroupDMChannel extends BaseChannel {
* The recipients of this Group DM Channel.
* @type {PartialRecipient[]}
*/
this.recipients = data.recipients;
this.recipients = data.recipients ?? [];
/**
* A manager of the messages belonging to this channel
* @type {PartialGroupDMMessageManager}
*/
this.messages = new PartialGroupDMMessageManager(this);
if ('owner_id' in data) {
/**
* The user id of the owner of this Group DM Channel
* @type {?Snowflake}
*/
this.ownerId = data.owner_id;
} else {
this.ownerId ??= null;
}
if ('last_message_id' in data) {
/**
* The channel's last message id, if one was sent
* @type {?Snowflake}
*/
this.lastMessageId = data.last_message_id;
} else {
this.lastMessageId ??= null;
}
if ('last_pin_timestamp' in data) {
/**
* The timestamp when the last pinned message was pinned, if there was one
* @type {?number}
*/
this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null;
} else {
this.lastPinTimestamp ??= null;
}
}
/**
@@ -55,13 +87,45 @@ class PartialGroupDMChannel extends BaseChannel {
return this.icon && this.client.rest.cdn.channelIcon(this.id, this.icon, options);
}
delete() {
return Promise.reject(new DiscordjsError(ErrorCodes.DeleteGroupDMChannel));
/**
* Fetches the owner of this Group DM Channel.
* @param {BaseFetchOptions} [options] The options for fetching the user
* @returns {Promise<User>}
*/
async fetchOwner(options) {
if (!this.ownerId) {
throw new DiscordjsError(ErrorCodes.FetchOwnerId, 'group DM');
}
return this.client.users.fetch(this.ownerId, options);
}
fetch() {
return Promise.reject(new DiscordjsError(ErrorCodes.FetchGroupDMChannel));
async delete() {
throw new DiscordjsError(ErrorCodes.DeleteGroupDMChannel);
}
async fetch() {
throw new DiscordjsError(ErrorCodes.FetchGroupDMChannel);
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
get lastMessage() {}
get lastPinAt() {}
createMessageComponentCollector() {}
awaitMessageComponent() {}
}
TextBasedChannel.applyToClass(PartialGroupDMChannel, true, [
'bulkDelete',
'send',
'sendTyping',
'createMessageCollector',
'awaitMessages',
'fetchWebhooks',
'createWebhook',
'setRateLimitPerUser',
'setNSFW',
]);
module.exports = PartialGroupDMChannel;

View File

@@ -181,7 +181,10 @@ class PermissionOverwrites extends Base {
}
const userOrRole = guild.roles.resolve(overwrite.id) ?? guild.client.users.resolve(overwrite.id);
if (!userOrRole) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'User nor a Role');
if (!userOrRole) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'cached User or Role');
}
const type = userOrRole instanceof Role ? OverwriteType.Role : OverwriteType.Member;
return {

View File

@@ -97,9 +97,9 @@ class Poll extends Base {
* Ends this poll.
* @returns {Promise<Message>}
*/
end() {
async end() {
if (Date.now() > this.expiresTimestamp) {
return Promise.reject(new DiscordjsError(ErrorCodes.PollAlreadyExpired));
throw new DiscordjsError(ErrorCodes.PollAlreadyExpired);
}
return this.message.channel.messages.endPoll(this.message.id);

View File

@@ -61,7 +61,7 @@ class PollAnswer extends Base {
*/
get emoji() {
if (!this._emoji || (!this._emoji.id && !this._emoji.name)) return null;
return this.client.emojis.resolve(this._emoji.id) ?? new Emoji(this.client, this._emoji);
return this.client.emojis.cache.get(this._emoji.id) ?? new Emoji(this.client, this._emoji);
}
/**

View File

@@ -182,8 +182,8 @@ class Sticker extends Base {
* Fetches the pack that contains this sticker.
* @returns {Promise<?StickerPack>} The sticker pack or `null` if this sticker does not belong to one.
*/
fetchPack() {
if (!this.packId) return Promise.resolve(null);
async fetchPack() {
if (!this.packId) return null;
return this.client.fetchStickerPacks({ packId: this.packId });
}
@@ -225,7 +225,7 @@ class Sticker extends Base {
* @returns {Promise<Sticker>}
* @param {string} [reason] Reason for deleting this sticker
* @example
* // Delete a message
* // Delete a sticker
* sticker.delete()
* .then(sticker => console.log(`Deleted sticker ${sticker.name}`))
* .catch(console.error);

View File

@@ -6,7 +6,7 @@ const { RESTJSONErrorCodes, ChannelFlags, ChannelType, PermissionFlagsBits, Rout
const { BaseChannel } = require('./BaseChannel');
const getThreadOnlyChannel = lazy(() => require('./ThreadOnlyChannel'));
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors');
const { DiscordjsRangeError, ErrorCodes } = require('../errors');
const GuildMessageManager = require('../managers/GuildMessageManager');
const ThreadMemberManager = require('../managers/ThreadMemberManager');
const ChannelFlagsBitField = require('../util/ChannelFlagsBitField');
@@ -32,6 +32,12 @@ class ThreadChannel extends BaseChannel {
*/
this.guildId = guild?.id ?? data.guild_id;
/**
* The id of the member who created this thread
* @type {Snowflake}
*/
this.ownerId = data.owner_id;
/**
* A manager of the messages sent to this thread
* @type {GuildMessageManager}
@@ -122,16 +128,6 @@ class ThreadChannel extends BaseChannel {
this._createdTimestamp ??= this.type === ChannelType.PrivateThread ? super.createdTimestamp : null;
if ('owner_id' in data) {
/**
* The id of the member who created this thread
* @type {?Snowflake}
*/
this.ownerId = data.owner_id;
} else {
this.ownerId ??= null;
}
if ('last_message_id' in data) {
/**
* The last message id sent in this thread, if one was sent
@@ -288,17 +284,19 @@ class ThreadChannel extends BaseChannel {
return this.parent?.permissionsFor(memberOrRole, checkAdmin) ?? null;
}
/**
* Options used to fetch a thread owner.
* @typedef {BaseFetchOptions} FetchThreadOwnerOptions
* @property {boolean} [withMember] Whether to also return the guild member associated with this thread member
*/
/**
* Fetches the owner of this thread. If the thread member object isn't needed,
* use {@link ThreadChannel#ownerId} instead.
* @param {BaseFetchOptions} [options] The options for fetching the member
* @param {FetchThreadOwnerOptions} [options] Options for fetching the owner
* @returns {Promise<?ThreadMember>}
*/
async fetchOwner(options) {
if (!this.ownerId) {
throw new DiscordjsError(ErrorCodes.FetchOwnerId, 'thread');
}
// TODO: Remove that catch in the next major version
const member = await this.members._fetchSingle({ ...options, member: this.ownerId }).catch(error => {
if (error instanceof DiscordAPIError && error.code === RESTJSONErrorCodes.UnknownMember) {
@@ -319,7 +317,6 @@ class ThreadChannel extends BaseChannel {
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<?Message<true>>}
*/
// eslint-disable-next-line require-await
async fetchStarterMessage(options) {
const channel = this.parent instanceof getThreadOnlyChannel() ? this : this.parent;
return channel?.messages.fetch({ message: this.id, ...options }) ?? null;
@@ -409,9 +406,9 @@ class ThreadChannel extends BaseChannel {
* @param {string} [reason] Reason for changing invite
* @returns {Promise<ThreadChannel>}
*/
setInvitable(invitable = true, reason) {
async setInvitable(invitable = true, reason) {
if (this.type !== ChannelType.PrivateThread) {
return Promise.reject(new DiscordjsRangeError(ErrorCodes.ThreadInvitableType, this.type));
throw new DiscordjsRangeError(ErrorCodes.ThreadInvitableType, this.type);
}
return this.edit({ invitable, reason });
}

View File

@@ -2,6 +2,7 @@
const Base = require('./Base');
const ThreadMemberFlagsBitField = require('../util/ThreadMemberFlagsBitField');
const { emitDeprecationWarningForRemoveThreadMember } = require('../util/Util');
/**
* Represents a Member for a Thread.
@@ -69,7 +70,7 @@ class ThreadMember extends Base {
* @readonly
*/
get guildMember() {
return this.member ?? this.thread.guild.members.resolve(this.id);
return this.member ?? this.thread.guild.members.cache.get(this.id) ?? null;
}
/**
@@ -87,7 +88,7 @@ class ThreadMember extends Base {
* @readonly
*/
get user() {
return this.client.users.resolve(this.id);
return this.client.users.cache.get(this.id) ?? null;
}
/**
@@ -102,9 +103,14 @@ class ThreadMember extends Base {
/**
* Removes this member from the thread.
* @param {string} [reason] Reason for removing the member
* <warn>This parameter is **deprecated**. Reasons cannot be used.</warn>
* @returns {Promise<ThreadMember>}
*/
async remove(reason) {
if (reason !== undefined) {
emitDeprecationWarningForRemoveThreadMember(this.constructor.name);
}
await this.thread.members.remove(this.id, reason);
return this;
}

View File

@@ -36,7 +36,7 @@ const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require(
*/
/**
* Represents symbols utilised by thread-only channels.
* Represents symbols utilized by thread-only channels.
* @extends {GuildChannel}
* @implements {TextBasedChannel}
* @abstract

View File

@@ -6,6 +6,7 @@ const { DiscordSnowflake } = require('@sapphire/snowflake');
const Base = require('./Base');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const UserFlagsBitField = require('../util/UserFlagsBitField');
const { emitDeprecationWarningForUserFetchFlags } = require('../util/Util');
/**
* Represents a user on Discord.
@@ -346,8 +347,11 @@ class User extends Base {
* Fetches this user's flags.
* @param {boolean} [force=false] Whether to skip the cache check and request the API
* @returns {Promise<UserFlagsBitField>}
* @deprecated <warn>This method is deprecated and will be removed in the next major version.
* Flags may still be retrieved via {@link User#fetch}.</warn>
*/
fetchFlags(force = false) {
emitDeprecationWarningForUserFetchFlags(this.constructor.name);
return this.client.users.fetchFlags(this.id, { force });
}

View File

@@ -1,6 +1,6 @@
'use strict';
const { PermissionFlagsBits } = require('discord-api-types/v10');
const { PermissionFlagsBits, Routes } = require('discord-api-types/v10');
const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
/**
@@ -35,6 +35,26 @@ class VoiceChannel extends BaseGuildVoiceChannel {
permissions.has(PermissionFlagsBits.Speak, false)
);
}
/**
* @typedef {Object} SendSoundboardSoundOptions
* @property {string} soundId The id of the soundboard sound to send
* @property {string} [guildId] The id of the guild the soundboard sound is a part of
*/
/**
* Send a soundboard sound to a voice channel the user is connected to.
* @param {SoundboardSound|SendSoundboardSoundOptions} sound The sound to send
* @returns {Promise<void>}
*/
async sendSoundboardSound(sound) {
await this.client.rest.post(Routes.sendSoundboardSound(this.id), {
body: {
sound_id: sound.soundId,
source_guild_id: sound.guildId ?? undefined,
},
});
}
}
/**

View File

@@ -108,7 +108,7 @@ class Webhook {
* The source guild of the webhook
* @type {?(Guild|APIGuild)}
*/
this.sourceGuild = this.client.guilds?.resolve(data.source_guild.id) ?? data.source_guild;
this.sourceGuild = this.client.guilds?.cache.get(data.source_guild.id) ?? data.source_guild;
} else {
this.sourceGuild ??= null;
}
@@ -118,7 +118,7 @@ class Webhook {
* The source channel of the webhook
* @type {?(NewsChannel|APIChannel)}
*/
this.sourceChannel = this.client.channels?.resolve(data.source_channel?.id) ?? data.source_channel;
this.sourceChannel = this.client.channels?.cache.get(data.source_channel?.id) ?? data.source_channel;
} else {
this.sourceChannel ??= null;
}
@@ -126,7 +126,7 @@ class Webhook {
/**
* Options that can be passed into send.
* @typedef {BaseMessageOptions} WebhookMessageCreateOptions
* @typedef {BaseMessageOptionsWithPoll} WebhookMessageCreateOptions
* @property {boolean} [tts=false] Whether the message should be spoken aloud
* @property {MessageFlags} [flags] Which flags to set for the message.
* <info>Only the {@link MessageFlags.SuppressEmbeds} flag can be set.</info>
@@ -137,12 +137,13 @@ class Webhook {
* @property {string} [threadName] Name of the thread to create (only available if the webhook is in a forum channel)
* @property {Snowflake[]} [appliedTags]
* The tags to apply to the created thread (only available if the webhook is in a forum channel)
* @property {boolean} [withComponents] Whether to allow sending non-interactive components in the message.
* <info>For application-owned webhooks, this property is ignored</info>
*/
/**
* Options that can be passed into editMessage.
* @typedef {BaseMessageOptions} WebhookMessageEditOptions
* @property {Attachment[]} [attachments] Attachments to send with the message
* @typedef {MessageEditOptions} WebhookMessageEditOptions
* @property {Snowflake} [threadId] The id of the thread this message belongs to
* <info>For interaction webhooks, this property is ignored</info>
*/
@@ -215,12 +216,14 @@ class Webhook {
messagePayload = MessagePayload.create(this, options).resolveBody();
}
const { body, files } = await messagePayload.resolveFiles();
const query = makeURLSearchParams({
wait: true,
thread_id: messagePayload.options.threadId,
with_components: messagePayload.options.withComponents,
});
const { body, files } = await messagePayload.resolveFiles();
const d = await this.client.rest.post(Routes.webhook(this.id, this.token), {
body,
files,
@@ -299,6 +302,8 @@ class Webhook {
* @property {boolean} [cache=true] Whether to cache the message.
* @property {Snowflake} [threadId] The id of the thread this message belongs to.
* <info>For interaction webhooks, this property is ignored</info>
* @property {boolean} [withComponents] Whether to allow sending non-interactive components in the message.
* <info>For application-owned webhooks, this property is ignored</info>
*/
/**
@@ -338,14 +343,17 @@ class Webhook {
const { body, files } = await messagePayload.resolveBody().resolveFiles();
const query = makeURLSearchParams({
thread_id: messagePayload.options.threadId,
with_components: messagePayload.options.withComponents,
});
const d = await this.client.rest.patch(
Routes.webhookMessage(this.id, this.token, typeof message === 'string' ? message : message.id),
{
body,
files,
query: messagePayload.options.threadId
? makeURLSearchParams({ thread_id: messagePayload.options.threadId })
: undefined,
query,
auth: false,
},
);

View File

@@ -53,7 +53,7 @@ class WelcomeChannel extends Base {
* @type {GuildEmoji|Emoji}
*/
get emoji() {
return this.client.emojis.resolve(this._emoji.id) ?? new Emoji(this.client, this._emoji);
return this.client.emojis.cache.get(this._emoji.id) ?? new Emoji(this.client, this._emoji);
}
}

View File

@@ -50,6 +50,56 @@ class Application extends Base {
} else {
this.icon ??= null;
}
if ('terms_of_service_url' in data) {
/**
* The URL of the application's terms of service
* @type {?string}
*/
this.termsOfServiceURL = data.terms_of_service_url;
} else {
this.termsOfServiceURL ??= null;
}
if ('privacy_policy_url' in data) {
/**
* The URL of the application's privacy policy
* @type {?string}
*/
this.privacyPolicyURL = data.privacy_policy_url;
} else {
this.privacyPolicyURL ??= null;
}
if ('rpc_origins' in data) {
/**
* The application's RPC origins, if enabled
* @type {string[]}
*/
this.rpcOrigins = data.rpc_origins;
} else {
this.rpcOrigins ??= [];
}
if ('cover_image' in data) {
/**
* The hash of the application's cover image
* @type {?string}
*/
this.cover = data.cover_image;
} else {
this.cover ??= null;
}
if ('verify_key' in data) {
/**
* The hex-encoded key for verification in interactions and the GameSDK's GetTicket
* @type {?string}
*/
this.verifyKey = data.verify_key;
} else {
this.verifyKey ??= null;
}
}
/**

View File

@@ -1,14 +1,20 @@
'use strict';
const process = require('node:process');
const { deprecate } = require('node:util');
const { makeURLSearchParams } = require('@discordjs/rest');
const { isJSONEncodable } = require('@discordjs/util');
const { InteractionResponseType, MessageFlags, Routes, InteractionType } = require('discord-api-types/v10');
const { DiscordjsError, ErrorCodes } = require('../../errors');
const MessageFlagsBitField = require('../../util/MessageFlagsBitField');
const InteractionCallbackResponse = require('../InteractionCallbackResponse');
const InteractionCollector = require('../InteractionCollector');
const InteractionResponse = require('../InteractionResponse');
const MessagePayload = require('../MessagePayload');
let deprecationEmittedForEphemeralOption = false;
let deprecationEmittedForFetchReplyOption = false;
/**
* @typedef {Object} ModalComponentData
* @property {string} title The title of the modal
@@ -24,23 +30,33 @@ class InteractionResponses {
/**
* Options for deferring the reply to an {@link BaseInteraction}.
* @typedef {Object} InteractionDeferReplyOptions
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
* @property {boolean} [ephemeral] Whether the reply should be ephemeral.
* <warn>This option is deprecated. Use `flags` instead.</warn>
* @property {MessageFlagsResolvable} [flags] Flags for the reply.
* <info>Only `MessageFlags.Ephemeral` can be set.</info>
* @property {boolean} [withResponse] Whether to return an {@link InteractionCallbackResponse} as the response
* @property {boolean} [fetchReply] Whether to fetch the reply
* <warn>This option is deprecated. Use `withResponse` or fetch the response instead.</warn>
*/
/**
* Options for deferring and updating the reply to a {@link MessageComponentInteraction}.
* @typedef {Object} InteractionDeferUpdateOptions
* @property {boolean} [withResponse] Whether to return an {@link InteractionCallbackResponse} as the response
* @property {boolean} [fetchReply] Whether to fetch the reply
* <warn>This option is deprecated. Use `withResponse` or fetch the response instead.</warn>
*/
/**
* Options for a reply to a {@link BaseInteraction}.
* @typedef {BaseMessageOptions} InteractionReplyOptions
* @typedef {BaseMessageOptionsWithPoll} InteractionReplyOptions
* @property {boolean} [ephemeral] Whether the reply should be ephemeral.
* <warn>This option is deprecated. Use `flags` instead.</warn>
* @property {boolean} [tts=false] Whether the message should be spoken aloud
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
* @property {boolean} [withResponse] Whether to return an {@link InteractionCallbackResponse} as the response
* @property {boolean} [fetchReply] Whether to fetch the reply
* @property {MessageFlags} [flags] Which flags to set for the message.
* <warn>This option is deprecated. Use `withResponse` or fetch the response instead.</warn>
* @property {MessageFlagsResolvable} [flags] Which flags to set for the message.
* <info>Only `MessageFlags.Ephemeral`, `MessageFlags.SuppressEmbeds`, and `MessageFlags.SuppressNotifications`
* can be set.</info>
*/
@@ -48,13 +64,27 @@ class InteractionResponses {
/**
* Options for updating the message received from a {@link MessageComponentInteraction}.
* @typedef {MessageEditOptions} InteractionUpdateOptions
* @property {boolean} [withResponse] Whether to return an {@link InteractionCallbackResponse} as the response
* @property {boolean} [fetchReply] Whether to fetch the reply
* <warn>This option is deprecated. Use `withResponse` or fetch the response instead.</warn>
*/
/**
* Options for launching activity in response to a {@link BaseInteraction}
* @typedef {Object} LaunchActivityOptions
* @property {boolean} [withResponse] Whether to return an {@link InteractionCallbackResponse} as the response
*/
/**
* Options for showing a modal in response to a {@link BaseInteraction}
* @typedef {Object} ShowModalOptions
* @property {boolean} [withResponse] Whether to return an {@link InteractionCallbackResponse} as the response
*/
/**
* Defers the reply to this interaction.
* @param {InteractionDeferReplyOptions} [options] Options for deferring the reply to this interaction
* @returns {Promise<Message|InteractionResponse>}
* @returns {Promise<InteractionCallbackResponse|Message|InteractionResponse>}
* @example
* // Defer the reply to this interaction
* interaction.deferReply()
@@ -62,67 +92,129 @@ class InteractionResponses {
* .catch(console.error)
* @example
* // Defer to send an ephemeral reply later
* interaction.deferReply({ ephemeral: true })
* interaction.deferReply({ flags: MessageFlags.Ephemeral })
* .then(console.log)
* .catch(console.error);
*/
async deferReply(options = {}) {
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
this.ephemeral = options.ephemeral ?? false;
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
if ('ephemeral' in options) {
if (!deprecationEmittedForEphemeralOption) {
process.emitWarning(
`Supplying "ephemeral" for interaction response options is deprecated. Utilize flags instead.`,
);
deprecationEmittedForEphemeralOption = true;
}
}
if ('fetchReply' in options) {
if (!deprecationEmittedForFetchReplyOption) {
process.emitWarning(
// eslint-disable-next-line max-len
`Supplying "fetchReply" for interaction response options is deprecated. Utilize "withResponse" instead or fetch the response after using the method.`,
);
deprecationEmittedForFetchReplyOption = true;
}
}
const flags = new MessageFlagsBitField(options.flags);
if (options.ephemeral) {
flags.add(MessageFlags.Ephemeral);
}
const response = await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
body: {
type: InteractionResponseType.DeferredChannelMessageWithSource,
data: {
flags: options.ephemeral ? MessageFlags.Ephemeral : undefined,
flags: flags.bitfield,
},
},
auth: false,
query: makeURLSearchParams({ with_response: options.withResponse ?? false }),
});
this.deferred = true;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this);
this.deferred = true;
this.ephemeral = flags.has(MessageFlags.Ephemeral);
return options.withResponse
? new InteractionCallbackResponse(this.client, response)
: options.fetchReply
? this.fetchReply()
: new InteractionResponse(this);
}
/**
* Creates a reply to this interaction.
* <info>Use the `fetchReply` option to get the bot's reply message.</info>
* <info>Use the `withResponse` option to get the interaction callback response.</info>
* @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply
* @returns {Promise<Message|InteractionResponse>}
* @returns {Promise<InteractionCallbackResponse|Message|InteractionResponse>}
* @example
* // Reply to the interaction and fetch the response
* interaction.reply({ content: 'Pong!', fetchReply: true })
* .then((message) => console.log(`Reply sent with content ${message.content}`))
* interaction.reply({ content: 'Pong!', withResponse: true })
* .then((response) => console.log(`Reply sent with content ${response.resource.message.content}`))
* .catch(console.error);
* @example
* // Create an ephemeral reply with an embed
* const embed = new EmbedBuilder().setDescription('Pong!');
*
* interaction.reply({ embeds: [embed], ephemeral: true })
* interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral })
* .then(() => console.log('Reply sent.'))
* .catch(console.error);
*/
async reply(options) {
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
if (typeof options !== 'string') {
if ('ephemeral' in options) {
if (!deprecationEmittedForEphemeralOption) {
process.emitWarning(
`Supplying "ephemeral" for interaction response options is deprecated. Utilize flags instead.`,
);
deprecationEmittedForEphemeralOption = true;
}
}
if ('fetchReply' in options) {
if (!deprecationEmittedForFetchReplyOption) {
process.emitWarning(
// eslint-disable-next-line max-len
`Supplying "fetchReply" for interaction response options is deprecated. Utilize "withResponse" instead or fetch the response after using the method.`,
);
deprecationEmittedForFetchReplyOption = true;
}
}
}
let messagePayload;
if (options instanceof MessagePayload) messagePayload = options;
else messagePayload = MessagePayload.create(this, options);
const { body: data, files } = await messagePayload.resolveBody().resolveFiles();
this.ephemeral = new MessageFlagsBitField(data.flags).has(MessageFlags.Ephemeral);
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
const response = await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
body: {
type: InteractionResponseType.ChannelMessageWithSource,
data,
},
files,
auth: false,
query: makeURLSearchParams({ with_response: options.withResponse ?? false }),
});
this.ephemeral = Boolean(data.flags & MessageFlags.Ephemeral);
this.replied = true;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this);
return options.withResponse
? new InteractionCallbackResponse(this.client, response)
: options.fetchReply
? this.fetchReply()
: new InteractionResponse(this);
}
/**
@@ -176,6 +268,8 @@ class InteractionResponses {
* .catch(console.error);
*/
async deleteReply(message = '@original') {
if (!this.deferred && !this.replied) throw new DiscordjsError(ErrorCodes.InteractionNotReplied);
await this.webhook.deleteMessage(message);
}
@@ -184,15 +278,17 @@ class InteractionResponses {
* @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply
* @returns {Promise<Message>}
*/
followUp(options) {
if (!this.deferred && !this.replied) return Promise.reject(new DiscordjsError(ErrorCodes.InteractionNotReplied));
return this.webhook.send(options);
async followUp(options) {
if (!this.deferred && !this.replied) throw new DiscordjsError(ErrorCodes.InteractionNotReplied);
const msg = await this.webhook.send(options);
this.replied = true;
return msg;
}
/**
* Defers an update to the message to which the component was attached.
* @param {InteractionDeferUpdateOptions} [options] Options for deferring the update to this interaction
* @returns {Promise<Message|InteractionResponse>}
* @returns {Promise<InteractionCallbackResponse|Message|InteractionResponse>}
* @example
* // Defer updating and reset the component's loading state
* interaction.deferUpdate()
@@ -201,21 +297,38 @@ class InteractionResponses {
*/
async deferUpdate(options = {}) {
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
if ('fetchReply' in options) {
if (!deprecationEmittedForFetchReplyOption) {
process.emitWarning(
// eslint-disable-next-line max-len
`Supplying "fetchReply" for interaction response options is deprecated. Utilize "withResponse" instead or fetch the response after using the method.`,
);
deprecationEmittedForFetchReplyOption = true;
}
}
const response = await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
body: {
type: InteractionResponseType.DeferredMessageUpdate,
},
auth: false,
query: makeURLSearchParams({ with_response: options.withResponse ?? false }),
});
this.deferred = true;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this, this.message?.interaction?.id);
return options.withResponse
? new InteractionCallbackResponse(this.client, response)
: options.fetchReply
? this.fetchReply()
: new InteractionResponse(this, this.message?.interactionMetadata?.id);
}
/**
* Updates the original message of the component on which the interaction was received on.
* @param {string|MessagePayload|InteractionUpdateOptions} options The options for the updated message
* @returns {Promise<Message|void>}
* @returns {Promise<InteractionCallbackResponse|Message|void>}
* @example
* // Remove the components from the message
* interaction.update({
@@ -228,40 +341,79 @@ class InteractionResponses {
async update(options) {
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
if (typeof options !== 'string' && 'fetchReply' in options) {
if (!deprecationEmittedForFetchReplyOption) {
process.emitWarning(
// eslint-disable-next-line max-len
`Supplying "fetchReply" for interaction response options is deprecated. Utilize "withResponse" instead or fetch the response after using the method.`,
);
deprecationEmittedForFetchReplyOption = true;
}
}
let messagePayload;
if (options instanceof MessagePayload) messagePayload = options;
else messagePayload = MessagePayload.create(this, options);
const { body: data, files } = await messagePayload.resolveBody().resolveFiles();
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
const response = await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
body: {
type: InteractionResponseType.UpdateMessage,
data,
},
files,
auth: false,
query: makeURLSearchParams({ with_response: options.withResponse ?? false }),
});
this.replied = true;
return options.fetchReply ? this.fetchReply() : new InteractionResponse(this, this.message.interaction?.id);
return options.withResponse
? new InteractionCallbackResponse(this.client, response)
: options.fetchReply
? this.fetchReply()
: new InteractionResponse(this, this.message.interactionMetadata?.id);
}
/**
* Launches this application's activity, if enabled
* @param {LaunchActivityOptions} [options={}] Options for launching the activity
* @returns {Promise<InteractionCallbackResponse|undefined>}
*/
async launchActivity({ withResponse } = {}) {
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
const response = await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
query: makeURLSearchParams({ with_response: withResponse ?? false }),
body: {
type: InteractionResponseType.LaunchActivity,
},
auth: false,
});
this.replied = true;
return withResponse ? new InteractionCallbackResponse(this.client, response) : undefined;
}
/**
* Shows a modal component
* @param {ModalBuilder|ModalComponentData|APIModalInteractionResponseCallbackData} modal The modal to show
* @returns {Promise<void>}
* @param {ShowModalOptions} [options={}] The options for sending this interaction response
* @returns {Promise<InteractionCallbackResponse|undefined>}
*/
async showModal(modal) {
async showModal(modal, options = {}) {
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
const response = await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
body: {
type: InteractionResponseType.Modal,
data: isJSONEncodable(modal) ? modal.toJSON() : this.client.options.jsonTransformer(modal),
},
auth: false,
query: makeURLSearchParams({ with_response: options.withResponse ?? false }),
});
this.replied = true;
return options.withResponse ? new InteractionCallbackResponse(this.client, response) : undefined;
}
/**
@@ -300,7 +452,7 @@ class InteractionResponses {
* .then(interaction => console.log(`${interaction.customId} was submitted!`))
* .catch(console.error);
*/
awaitModalSubmit(options) {
async awaitModalSubmit(options) {
if (typeof options.time !== 'number') throw new DiscordjsError(ErrorCodes.InvalidType, 'time', 'number');
const _options = { ...options, max: 1, interactionType: InteractionType.ModalSubmit };
return new Promise((resolve, reject) => {
@@ -323,6 +475,7 @@ class InteractionResponses {
'followUp',
'deferUpdate',
'update',
'launchActivity',
'showModal',
'sendPremiumRequired',
'awaitModalSubmit',

View File

@@ -75,11 +75,19 @@ class TextBasedChannel {
* @property {?string} [content=''] The content for the message. This can only be `null` when editing a message.
* @property {Array<(EmbedBuilder|Embed|APIEmbed)>} [embeds] The embeds for the message
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
* (see [here](https://discord.com/developers/docs/resources/message#allowed-mentions-object) for more details)
* (see {@link https://discord.com/developers/docs/resources/message#allowed-mentions-object here} for more details)
* @property {Array<(AttachmentBuilder|Attachment|AttachmentPayload|BufferResolvable)>} [files]
* The files to send with the message.
* @property {Array<(ActionRowBuilder|ActionRow|APIActionRowComponent)>} [components]
* Action rows containing interactive components for the message (buttons, select menus)
* @property {Array<(ActionRowBuilder|MessageTopLevelComponent|APIMessageTopLevelComponent)>} [components]
* Action rows containing interactive components for the message (buttons, select menus) and other
* top-level components.
* <info>When using components v2, the flag {@link MessageFlags.IsComponentsV2} needs to be set
* and `content`, `embeds`, `stickers`, and `poll` cannot be used.</info>
*/
/**
* The base message options for messages including a poll.
* @typedef {BaseMessageOptions} BaseMessageOptionsWithPoll
* @property {PollData} [poll] The poll to send with the message
*/
@@ -93,7 +101,7 @@ class TextBasedChannel {
/**
* The options for sending a message.
* @typedef {BaseMessageOptions} BaseMessageCreateOptions
* @typedef {BaseMessageOptionsWithPoll} BaseMessageCreateOptions
* @property {boolean} [tts=false] Whether the message should be spoken aloud
* @property {string} [nonce] The nonce for the message
* <info>This property is required if `enforceNonce` set to `true`.</info>
@@ -102,13 +110,23 @@ class TextBasedChannel {
* that message will be returned and no new message will be created
* @property {StickerResolvable[]} [stickers=[]] The stickers to send in the message
* @property {MessageFlags} [flags] Which flags to set for the message.
* <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info>
* <info>Only {@link MessageFlags.SuppressEmbeds}, {@link MessageFlags.SuppressNotifications} and
* {@link MessageFlags.IsComponentsV2} can be set.</info>
* <info>{@link MessageFlags.IsComponentsV2} is required if passing components that aren't action rows</info>
*/
/**
* @typedef {Object} ForwardOptions
* @property {MessageResolvable} message The originating message
* @property {TextBasedChannelResolvable} [channel] The channel of the originating message
* @property {GuildResolvable} [guild] The guild of the originating message
*/
/**
* The options for sending a message.
* @typedef {BaseMessageCreateOptions} MessageCreateOptions
* @property {ReplyOptions} [reply] The options for replying to a message
* @property {ForwardOptions} [forward] The options for forwarding a message
*/
/**

View File

@@ -60,6 +60,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIChannelSelectComponent}
*/
/**
* @external APIContainerComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIContainerComponent}
*/
/**
* @external APIEmbed
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmbed}
@@ -80,6 +85,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmoji}
*/
/**
* @external APIFileComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIFileComponent}
*/
/**
* @external APIGuild
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuild}
@@ -100,6 +110,16 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildMember}
*/
/**
* @external APIGuildScheduledEventRecurrenceRule
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildScheduledEventRecurrenceRule}
*/
/**
* @external APIIncidentsData
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIIncidentsData}
*/
/**
* @external APIInteraction
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIInteraction}
@@ -125,6 +145,16 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIInteractionGuildMember}
*/
/**
* @external APIMediaGalleryComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIMediaGalleryComponent}
*/
/**
* @external APIMediaGalleryItem
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIMediaGalleryItem}
*/
/**
* @external APIMentionableSelectComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIMentionableSelectComponent}
@@ -136,8 +166,8 @@
*/
/**
* @external APIMessageActionRowComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIMessageActionRowComponent}
* @external APIComponentInMessageActionRow
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIComponentInMessageActionRow}
*/
/**
@@ -155,6 +185,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIMessageInteractionMetadata}
*/
/**
* @external APIMessageTopLevelComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIMessageTopLevelComponent}
*/
/**
* @external APIModalInteractionResponse
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIModalInteractionResponse}
@@ -195,6 +230,16 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIRoleSelectComponent}
*/
/**
* @external APISectionComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APISectionComponent}
*/
/**
* @external APISeparatorComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APISeparatorComponent}
*/
/**
* @external APISelectMenuOption
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APISelectMenuOption}
@@ -215,6 +260,16 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APITextInputComponent}
*/
/**
* @external APIThumbnailComponent
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIThumbnailComponent}
*/
/**
* @external APIUnfurledMediaItem
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIUnfurledMediaItem}
*/
/**
* @external APIUser
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIUser}
@@ -250,6 +305,16 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationRoleConnectionMetadataType}
*/
/**
* @external ApplicationWebhookEventStatus
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationWebhookEventStatus}
*/
/**
* @external ApplicationWebhookEventType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationWebhookEventType}
*/
/**
* @external AttachmentFlags
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AttachmentFlags}
@@ -305,6 +370,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/EntitlementType}
*/
/**
* @external EntryPointCommandHandlerType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/EntryPointCommandHandlerType}
*/
/**
* @external ForumLayoutType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ForumLayoutType}
@@ -390,6 +460,21 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventPrivacyLevel}
*/
/**
* @external GuildScheduledEventRecurrenceRuleFrequency
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleFrequency}
*/
/**
* @external GuildScheduledEventRecurrenceRuleMonth
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleMonth}
*/
/**
* @external GuildScheduledEventRecurrenceRuleWeekday
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventRecurrenceRuleWeekday}
*/
/**
* @external GuildScheduledEventStatus
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventStatus}
@@ -442,12 +527,7 @@
/**
* @external Locale
* @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common/enum/Locale}
*/
/**
* @external LocaleString
* @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common#LocaleString}
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/Locale}
*/
/**
@@ -455,6 +535,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageActivityType}
*/
/**
* @external MessageReferenceType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageReferenceType}
*/
/**
* @external MessageType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageType}
@@ -477,7 +562,7 @@
/**
* @external PermissionFlagsBits
* @see {@link https://discord-api-types.dev/api/discord-api-types-payloads/common#PermissionFlagsBits}
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#PermissionFlagsBits}
*/
/**
@@ -485,6 +570,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/PollLayoutType}
*/
/**
* @external ReactionType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ReactionType}
*/
/**
* @external RoleFlags
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/RoleFlags}
@@ -497,7 +587,7 @@
/**
* @external RESTJSONErrorCodes
* @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common/enum/RESTJSONErrorCodes}
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/RESTJSONErrorCodes}
*/
/**
@@ -560,6 +650,11 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/VideoQualityMode}
*/
/**
* @external VoiceChannelEffectSendAnimationType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/VoiceChannelEffectSendAnimationType}
*/
/**
* @external WebhookType
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/WebhookType}

View File

@@ -1,10 +1,11 @@
'use strict';
// This file contains the typedefs for camel-cased JSON data
const { ComponentBuilder } = require('@discordjs/builders');
const { ComponentType } = require('discord-api-types/v10');
/**
* @typedef {Object} BaseComponentData
* @property {number} [id] the id of this component
* @property {ComponentType} type The type of component
*/
@@ -16,30 +17,30 @@ const { ComponentType } = require('discord-api-types/v10');
/**
* @typedef {BaseComponentData} ButtonComponentData
* @property {ButtonStyle} style The style of the button
* @property {?boolean} disabled Whether this button is disabled
* @property {boolean} [disabled] Whether this button is disabled
* @property {string} label The label of this button
* @property {?APIMessageComponentEmoji} emoji The emoji on this button
* @property {?string} customId The custom id of the button
* @property {?string} url The URL of the button
* @property {APIMessageComponentEmoji} [emoji] The emoji on this button
* @property {string} [customId] The custom id of the button
* @property {string} [url] The URL of the button
*/
/**
* @typedef {object} SelectMenuComponentOptionData
* @property {string} label The label of the option
* @property {string} value The value of the option
* @property {?string} description The description of the option
* @property {?APIMessageComponentEmoji} emoji The emoji on the option
* @property {?boolean} default Whether this option is selected by default
* @property {string} [description] The description of the option
* @property {APIMessageComponentEmoji} [emoji] The emoji on the option
* @property {boolean} [default] Whether this option is selected by default
*/
/**
* @typedef {BaseComponentData} SelectMenuComponentData
* @property {string} customId The custom id of the select menu
* @property {?boolean} disabled Whether the select menu is disabled or not
* @property {?number} maxValues The maximum amount of options that can be selected
* @property {?number} minValues The minimum amount of options that can be selected
* @property {?SelectMenuComponentOptionData[]} options The options in this select menu
* @property {?string} placeholder The placeholder of the select menu
* @property {boolean} [disabled] Whether the select menu is disabled or not
* @property {number} [maxValues] The maximum amount of options that can be selected
* @property {number} [minValues] The minimum amount of options that can be selected
* @property {SelectMenuComponentOptionData[]} [options] The options in this select menu
* @property {string} [placeholder] The placeholder of the select menu
*/
/**
@@ -51,15 +52,76 @@ const { ComponentType } = require('discord-api-types/v10');
* @property {string} customId The custom id of the text input
* @property {TextInputStyle} style The style of the text input
* @property {string} label The text that appears on top of the text input field
* @property {?number} minLength The minimum number of characters that can be entered in the text input
* @property {?number} maxLength The maximum number of characters that can be entered in the text input
* @property {?boolean} required Whether or not the text input is required or not
* @property {?string} value The pre-filled text in the text input
* @property {?string} placeholder Placeholder for the text input
* @property {number} [minLength] The minimum number of characters that can be entered in the text input
* @property {number} [maxLength] The maximum number of characters that can be entered in the text input
* @property {boolean} [required] Whether or not the text input is required or not
* @property {string} [value] The pre-filled text in the text input
* @property {string} [placeholder] Placeholder for the text input
*/
/**
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData|TextInputComponentData} ComponentData
* @typedef {Object} UnfurledMediaItemData
* @property {string} url The url of this media item. Accepts either http:, https: or attachment: protocol
*/
/**
* @typedef {BaseComponentData} ThumbnailComponentData
* @property {UnfurledMediaItemData} media The media for the thumbnail
* @property {string} [description] The description of the thumbnail
* @property {boolean} [spoiler] Whether the thumbnail should be spoilered
*/
/**
* @typedef {BaseComponentData} FileComponentData
* @property {UnfurledMediaItemData} file The file media in this component
* @property {boolean} [spoiler] Whether the file should be spoilered
*/
/**
* @typedef {Object} MediaGalleryItemData
* @property {UnfurledMediaItemData} media The media for the media gallery item
* @property {string} [description] The description of the media gallery item
* @property {boolean} [spoiler] Whether the media gallery item should be spoilered
*/
/**
* @typedef {BaseComponentData} MediaGalleryComponentData
* @property {MediaGalleryItemData[]} items The media gallery items in this media gallery component
*/
/**
* @typedef {BaseComponentData} SeparatorComponentData
* @property {SeparatorSpacingSize} [spacing] The spacing size of this component
* @property {boolean} [divider] Whether the separator shows as a divider
*/
/**
* @typedef {BaseComponentData} SectionComponentData
* @property {Components[]} components The components in this section
* @property {ButtonComponentData|ThumbnailComponentData} accessory The accessory shown next to this section
*/
/**
* @typedef {BaseComponentData} TextDisplayComponentData
* @property {string} content The content displayed in this component
*/
/**
* @typedef {ActionRowData|FileComponentData|MediaGalleryComponentData|SectionComponentData|
* SeparatorComponentData|TextDisplayComponentData} ComponentInContainerData
*/
/**
* @typedef {BaseComponentData} ContainerComponentData
* @property {ComponentInContainerData} components The components in this container
* @property {?number} [accentColor] The accent color of this container
* @property {boolean} [spoiler] Whether the container should be spoilered
*/
/**
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData|TextInputComponentData|
* ThumbnailComponentData|FileComponentData|MediaGalleryComponentData|SeparatorComponentData|
* SectionComponentData|TextDisplayComponentData|ContainerComponentData} ComponentData
*/
/**
@@ -67,71 +129,66 @@ const { ComponentType } = require('discord-api-types/v10');
* @typedef {APIMessageComponentEmoji|string} ComponentEmojiResolvable
*/
/**
* @typedef {ActionRow|ContainerComponent|FileComponent|MediaGalleryComponent|
* SectionComponent|SeparatorComponent|TextDisplayComponent} MessageTopLevelComponent
*/
/**
* Transforms API data into a component
* @param {APIMessageComponent|Component} data The data to create the component from
* @returns {Component}
* @ignore
*/
function createComponent(data) {
if (data instanceof Component) {
return data;
}
switch (data.type) {
case ComponentType.ActionRow:
return new ActionRow(data);
case ComponentType.Button:
return new ButtonComponent(data);
case ComponentType.StringSelect:
return new StringSelectMenuComponent(data);
case ComponentType.TextInput:
return new TextInputComponent(data);
case ComponentType.UserSelect:
return new UserSelectMenuComponent(data);
case ComponentType.RoleSelect:
return new RoleSelectMenuComponent(data);
case ComponentType.MentionableSelect:
return new MentionableSelectMenuComponent(data);
case ComponentType.ChannelSelect:
return new ChannelSelectMenuComponent(data);
default:
return new Component(data);
}
return data instanceof Component ? data : new (ComponentTypeToComponent[data.type] ?? Component)(data);
}
/**
* Transforms API data into a component builder
* @param {APIMessageComponent|ComponentBuilder} data The data to create the component from
* @returns {ComponentBuilder}
* @ignore
*/
function createComponentBuilder(data) {
if (data instanceof ComponentBuilder) {
return data;
}
return data instanceof ComponentBuilder ? data : new (ComponentTypeToBuilder[data.type] ?? ComponentBuilder)(data);
}
switch (data.type) {
/**
* Extracts all interactive components from the component tree
* @param {Component|APIMessageComponent} component The component to find all interactive components in
* @returns {Array<Component|APIMessageComponent>}
* @ignore
*/
function extractInteractiveComponents(component) {
switch (component.type) {
case ComponentType.ActionRow:
return new ActionRowBuilder(data);
case ComponentType.Button:
return new ButtonBuilder(data);
case ComponentType.StringSelect:
return new StringSelectMenuBuilder(data);
case ComponentType.TextInput:
return new TextInputBuilder(data);
case ComponentType.UserSelect:
return new UserSelectMenuBuilder(data);
case ComponentType.RoleSelect:
return new RoleSelectMenuBuilder(data);
case ComponentType.MentionableSelect:
return new MentionableSelectMenuBuilder(data);
case ComponentType.ChannelSelect:
return new ChannelSelectMenuBuilder(data);
return component.components;
case ComponentType.Section:
return [...component.components, component.accessory];
case ComponentType.Container:
return component.components.flatMap(extractInteractiveComponents);
default:
return new ComponentBuilder(data);
return [component];
}
}
module.exports = { createComponent, createComponentBuilder };
/**
* Finds a component by customId in nested components
* @param {Array<Component|APIMessageComponent>} components The components to search in
* @param {string} customId The customId to search for
* @returns {Component|APIMessageComponent}
* @ignore
*/
function findComponentByCustomId(components, customId) {
return (
components
.flatMap(extractInteractiveComponents)
.find(component => (component.customId ?? component.custom_id) === customId) ?? null
);
}
module.exports = { createComponent, createComponentBuilder, findComponentByCustomId };
const ActionRow = require('../structures/ActionRow');
const ActionRowBuilder = require('../structures/ActionRowBuilder');
@@ -140,13 +197,49 @@ const ButtonComponent = require('../structures/ButtonComponent');
const ChannelSelectMenuBuilder = require('../structures/ChannelSelectMenuBuilder');
const ChannelSelectMenuComponent = require('../structures/ChannelSelectMenuComponent');
const Component = require('../structures/Component');
const ContainerComponent = require('../structures/ContainerComponent');
const FileComponent = require('../structures/FileComponent');
const MediaGalleryComponent = require('../structures/MediaGalleryComponent');
const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder');
const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent');
const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder');
const RoleSelectMenuComponent = require('../structures/RoleSelectMenuComponent');
const SectionComponent = require('../structures/SectionComponent');
const SeparatorComponent = require('../structures/SeparatorComponent');
const StringSelectMenuBuilder = require('../structures/StringSelectMenuBuilder');
const StringSelectMenuComponent = require('../structures/StringSelectMenuComponent');
const TextDisplayComponent = require('../structures/TextDisplayComponent');
const TextInputBuilder = require('../structures/TextInputBuilder');
const TextInputComponent = require('../structures/TextInputComponent');
const ThumbnailComponent = require('../structures/ThumbnailComponent');
const UserSelectMenuBuilder = require('../structures/UserSelectMenuBuilder');
const UserSelectMenuComponent = require('../structures/UserSelectMenuComponent');
const ComponentTypeToComponent = {
[ComponentType.ActionRow]: ActionRow,
[ComponentType.Button]: ButtonComponent,
[ComponentType.StringSelect]: StringSelectMenuComponent,
[ComponentType.TextInput]: TextInputComponent,
[ComponentType.UserSelect]: UserSelectMenuComponent,
[ComponentType.RoleSelect]: RoleSelectMenuComponent,
[ComponentType.MentionableSelect]: MentionableSelectMenuComponent,
[ComponentType.ChannelSelect]: ChannelSelectMenuComponent,
[ComponentType.Container]: ContainerComponent,
[ComponentType.TextDisplay]: TextDisplayComponent,
[ComponentType.File]: FileComponent,
[ComponentType.MediaGallery]: MediaGalleryComponent,
[ComponentType.Section]: SectionComponent,
[ComponentType.Separator]: SeparatorComponent,
[ComponentType.Thumbnail]: ThumbnailComponent,
};
const ComponentTypeToBuilder = {
[ComponentType.ActionRow]: ActionRowBuilder,
[ComponentType.Button]: ButtonBuilder,
[ComponentType.StringSelect]: StringSelectMenuBuilder,
[ComponentType.TextInput]: TextInputBuilder,
[ComponentType.UserSelect]: UserSelectMenuBuilder,
[ComponentType.RoleSelect]: RoleSelectMenuBuilder,
[ComponentType.MentionableSelect]: MentionableSelectMenuBuilder,
[ComponentType.ChannelSelect]: ChannelSelectMenuBuilder,
};

View File

@@ -113,13 +113,14 @@ async function resolveFile(resource) {
*/
/**
* Resolves a Base64Resolvable to a Base 64 image.
* Resolves a Base64Resolvable to a Base 64 string.
* @param {Base64Resolvable} data The base 64 resolvable you want to resolve
* @param {string} [contentType='image/jpg'] The content type of the data
* @returns {?string}
* @private
*/
function resolveBase64(data) {
if (Buffer.isBuffer(data)) return `data:image/jpg;base64,${data.toString('base64')}`;
function resolveBase64(data, contentType = 'image/jpg') {
if (Buffer.isBuffer(data)) return `data:${contentType};base64,${data.toString('base64')}`;
return data;
}

View File

@@ -41,6 +41,10 @@
* @property {string} GuildScheduledEventUpdate guildScheduledEventUpdate
* @property {string} GuildScheduledEventUserAdd guildScheduledEventUserAdd
* @property {string} GuildScheduledEventUserRemove guildScheduledEventUserRemove
* @property {string} GuildSoundboardSoundCreate guildSoundboardSoundCreate
* @property {string} GuildSoundboardSoundDelete guildSoundboardSoundDelete
* @property {string} GuildSoundboardSoundsUpdate guildSoundboardSoundsUpdate
* @property {string} GuildSoundboardSoundUpdate guildSoundboardSoundUpdate
* @property {string} GuildStickerCreate stickerCreate
* @property {string} GuildStickerDelete stickerDelete
* @property {string} GuildStickerUpdate stickerUpdate
@@ -61,6 +65,7 @@
* @property {string} MessageReactionRemoveEmoji messageReactionRemoveEmoji
* @property {string} MessageUpdate messageUpdate
* @property {string} PresenceUpdate presenceUpdate
* @property {string} SoundboardSounds soundboardSounds
* @property {string} ShardDisconnect shardDisconnect
* @property {string} ShardError shardError
* @property {string} ShardReady shardReady
@@ -69,6 +74,9 @@
* @property {string} StageInstanceCreate stageInstanceCreate
* @property {string} StageInstanceDelete stageInstanceDelete
* @property {string} StageInstanceUpdate stageInstanceUpdate
* @property {string} SubscriptionCreate subscriptionCreate
* @property {string} SubscriptionUpdate subscriptionUpdate
* @property {string} SubscriptionDelete subscriptionDelete
* @property {string} ThreadCreate threadCreate
* @property {string} ThreadDelete threadDelete
* @property {string} ThreadListSync threadListSync
@@ -77,6 +85,7 @@
* @property {string} ThreadUpdate threadUpdate
* @property {string} TypingStart typingStart
* @property {string} UserUpdate userUpdate
* @property {string} VoiceChannelEffectSend voiceChannelEffectSend
* @property {string} VoiceServerUpdate voiceServerUpdate
* @property {string} VoiceStateUpdate voiceStateUpdate
* @property {string} Warn warn
@@ -128,6 +137,10 @@ module.exports = {
GuildScheduledEventUpdate: 'guildScheduledEventUpdate',
GuildScheduledEventUserAdd: 'guildScheduledEventUserAdd',
GuildScheduledEventUserRemove: 'guildScheduledEventUserRemove',
GuildSoundboardSoundCreate: 'guildSoundboardSoundCreate',
GuildSoundboardSoundDelete: 'guildSoundboardSoundDelete',
GuildSoundboardSoundsUpdate: 'guildSoundboardSoundsUpdate',
GuildSoundboardSoundUpdate: 'guildSoundboardSoundUpdate',
GuildStickerCreate: 'stickerCreate',
GuildStickerDelete: 'stickerDelete',
GuildStickerUpdate: 'stickerUpdate',
@@ -148,6 +161,7 @@ module.exports = {
MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji',
MessageUpdate: 'messageUpdate',
PresenceUpdate: 'presenceUpdate',
SoundboardSounds: 'soundboardSounds',
Raw: 'raw',
ShardDisconnect: 'shardDisconnect',
ShardError: 'shardError',
@@ -157,6 +171,9 @@ module.exports = {
StageInstanceCreate: 'stageInstanceCreate',
StageInstanceDelete: 'stageInstanceDelete',
StageInstanceUpdate: 'stageInstanceUpdate',
SubscriptionCreate: 'subscriptionCreate',
SubscriptionUpdate: 'subscriptionUpdate',
SubscriptionDelete: 'subscriptionDelete',
ThreadCreate: 'threadCreate',
ThreadDelete: 'threadDelete',
ThreadListSync: 'threadListSync',
@@ -165,6 +182,7 @@ module.exports = {
ThreadUpdate: 'threadUpdate',
TypingStart: 'typingStart',
UserUpdate: 'userUpdate',
VoiceChannelEffectSend: 'voiceChannelEffectSend',
VoiceServerUpdate: 'voiceServerUpdate',
VoiceStateUpdate: 'voiceStateUpdate',
Warn: 'warn',

View File

@@ -23,6 +23,15 @@ class MessageFlagsBitField extends BitField {
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Data that can be resolved to give a message flags bit field. This can be:
* * A string (see {@link MessageFlagsBitField.Flags})
* * A message flag
* * An instance of {@link MessageFlagsBitField}
* * An array of `MessageFlagsResolvable`
* @typedef {string|number|MessageFlagsBitField|MessageFlagsResolvable[]} MessageFlagsResolvable
*/
/**
* Bitfield of the packed bits
* @type {number}

View File

@@ -30,7 +30,7 @@ const { version } = require('../../package.json');
* @property {MessageMentionOptions} [allowedMentions] The default value for {@link BaseMessageOptions#allowedMentions}
* @property {Partials[]} [partials] Structures allowed to be partial. This means events can be emitted even when
* they're missing all the data for a particular structure. See the "Partial Structures" topic on the
* [guide](https://discordjs.guide/popular-topics/partials.html) for some
* {@link https://discordjs.guide/popular-topics/partials.html guide} for some
* important usage information, as partials require you to put checks in place when handling data.
* @property {boolean} [failIfNotExists=true] The default value for {@link MessageReplyOptions#failIfNotExists}
* @property {PresenceData} [presence={}] Presence data to use upon login
@@ -41,7 +41,7 @@ const { version } = require('../../package.json');
* @property {WebsocketOptions} [ws] Options for the WebSocket
* @property {RESTOptions} [rest] Options for the REST manager
* @property {Function} [jsonTransformer] A function used to transform outgoing json data
* @property {boolean} [enforceNonce=false] The default value for {@link MessageReplyOptions#enforceNonce}
* @property {boolean} [enforceNonce=false] The default value for {@link MessageCreateOptions#enforceNonce}
*/
/**

View File

@@ -26,6 +26,7 @@ const { createEnum } = require('./Enums');
* @property {number} Reaction The partial to receive uncached reactions.
* @property {number} GuildScheduledEvent The partial to receive uncached guild scheduled events.
* @property {number} ThreadMember The partial to receive uncached thread members.
* @property {number} SoundboardSound The partial to receive uncached soundboard sounds.
*/
// JSDoc for IntelliSense purposes
@@ -41,4 +42,5 @@ module.exports = createEnum([
'Reaction',
'GuildScheduledEvent',
'ThreadMember',
'SoundboardSound',
]);

View File

@@ -2,6 +2,7 @@
const { isJSONEncodable } = require('@discordjs/util');
const snakeCase = require('lodash.snakecase');
const { resolvePartialEmoji } = require('./Util');
/**
* Transforms camel-cased keys into snake cased keys
@@ -13,7 +14,14 @@ function toSnakeCase(obj) {
if (obj instanceof Date) return obj;
if (isJSONEncodable(obj)) return toSnakeCase(obj.toJSON());
if (Array.isArray(obj)) return obj.map(toSnakeCase);
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [snakeCase(key), toSnakeCase(value)]));
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
snakeCase(key),
// TODO: The special handling of 'emoji' is just a temporary fix for v14, will be dropped in v15.
// See https://github.com/discordjs/discord.js/issues/10909
key === 'emoji' && typeof value === 'string' ? resolvePartialEmoji(value) : toSnakeCase(value),
]),
);
}
/**
@@ -54,4 +62,43 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad
};
}
module.exports = { toSnakeCase, _transformAPIAutoModerationAction, _transformAPIMessageInteractionMetadata };
/**
* Transforms a guild scheduled event recurrence rule object to a snake-cased variant.
* @param {GuildScheduledEventRecurrenceRuleOptions} recurrenceRule The recurrence rule to transform
* @returns {APIGuildScheduledEventRecurrenceRule}
* @ignore
*/
function _transformGuildScheduledEventRecurrenceRule(recurrenceRule) {
return {
start: new Date(recurrenceRule.startAt).toISOString(),
frequency: recurrenceRule.frequency,
interval: recurrenceRule.interval,
by_weekday: recurrenceRule.byWeekday,
by_n_weekday: recurrenceRule.byNWeekday,
by_month: recurrenceRule.byMonth,
by_month_day: recurrenceRule.byMonthDay,
};
}
/**
* Transforms API incidents data to a camel-cased variant.
* @param {APIIncidentsData} data The incidents data to transform
* @returns {IncidentActions}
* @ignore
*/
function _transformAPIIncidentsData(data) {
return {
invitesDisabledUntil: data.invites_disabled_until ? new Date(data.invites_disabled_until) : null,
dmsDisabledUntil: data.dms_disabled_until ? new Date(data.dms_disabled_until) : null,
dmSpamDetectedAt: data.dm_spam_detected_at ? new Date(data.dm_spam_detected_at) : null,
raidDetectedAt: data.raid_detected_at ? new Date(data.raid_detected_at) : null,
};
}
module.exports = {
toSnakeCase,
_transformAPIAutoModerationAction,
_transformAPIMessageInteractionMetadata,
_transformGuildScheduledEventRecurrenceRule,
_transformAPIIncidentsData,
};

View File

@@ -1,6 +1,7 @@
'use strict';
const { parse } = require('node:path');
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { ChannelType, RouteBases, Routes } = require('discord-api-types/v10');
const { fetch } = require('undici');
@@ -8,6 +9,9 @@ const Colors = require('./Colors');
const { DiscordjsError, DiscordjsRangeError, DiscordjsTypeError, ErrorCodes } = require('../errors');
const isObject = d => typeof d === 'object' && d !== null;
let deprecationEmittedForUserFetchFlags = false;
let deprecationEmittedForRemoveThreadMember = false;
/**
* Flatten an object. Any properties that are collections will get converted to an array of keys.
* @param {Object} obj The object to flatten.
@@ -499,6 +503,32 @@ function resolveSKUId(resolvable) {
return null;
}
/**
* Deprecation function for fetching user flags.
* @param {string} name Name of the class
* @private
*/
function emitDeprecationWarningForUserFetchFlags(name) {
if (deprecationEmittedForUserFetchFlags) return;
process.emitWarning(`${name}#fetchFlags() is deprecated. Use ${name}#fetch() instead.`);
deprecationEmittedForUserFetchFlags = true;
}
/**
* Deprecation function for the reason parameter of removing thread members.
* @param {string} name Name of the class
* @private
*/
function emitDeprecationWarningForRemoveThreadMember(name) {
if (deprecationEmittedForRemoveThreadMember) return;
process.emitWarning(
`The reason parameter of ${name}#remove() is deprecated as Discord does not parse them. It will be removed in the next major version.`,
);
deprecationEmittedForRemoveThreadMember = true;
}
module.exports = {
flatten,
fetchRecommendedShardCount,
@@ -518,6 +548,8 @@ module.exports = {
parseWebhookURL,
transformResolved,
resolveSKUId,
emitDeprecationWarningForUserFetchFlags,
emitDeprecationWarningForRemoveThreadMember,
};
// Fixes Circular

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
// It should be published with your NPM package. It should not be tracked by Git.
{
"tsdocVersion": "0.12",
"toolPackages": [
{
"packageName": "@discordjs/api-extractor",
"packageVersion": "7.38.1"
}
]
}