Downgrade to v13

[vi] cảm giác đau khổ
This commit is contained in:
March 7th 2022-03-24 17:55:32 +07:00
parent 9596b1a210
commit 7dfdef46a5
218 changed files with 8584 additions and 9108 deletions

View File

@ -164,13 +164,13 @@ Custom Status
```js
const RichPresence = require('discord-rpc-contructor'); // My module :))
const custom = new RichPresence.CustomStatus()
.setUnicodeEmoji('🎮') // Set Unicode Emoji [Using one]
.setUnicodeEmoji('🎮') // Set Unicode Emoji [Using one]
.setDiscordEmoji({ // Set Custom Emoji (Nitro) [Using one]
name: 'nom',
id: '737373737373737373',
animated: false,
})
.setState('Testing') // Name of presence
.setState('Testing') // Name of presence
.toDiscord();
client.user.setActivity(custom);
```
@ -238,4 +238,4 @@ And you can change the status 5 times every 20 seconds!
## Warning
- This is a beta version, so there are some bugs.
- If you use the `Client.destroy()` function, for an account that uses 2FA, it will cause a logout and the Token will no longer be usable.
- With bot account you can login using `Client.login('Bot Token', true)`, but there will be some missing constructor like MessageEmbed, MessageActionRow, MessageButton, ...
- Downgrade to Discord.js v13 (Old version is Discord.js v14@dev)

View File

@ -14,8 +14,7 @@
## About
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts (and bot .-.) to interact with the Discord API v10. (and discord.js@v14-dev) =))
- Fork from [this](https://github.com/TheDevYellowy/dsb.js) module.
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts (and bot .-.) to interact with the Discord API v10.
### <strong>I don't take any responsibility for blocked Discord accounts that used this module.</strong>
### <strong>Using this on a user account is prohibited by the [Discord TOS](https://discord.com/terms) and can lead to the account block.</strong>
@ -69,10 +68,8 @@ client.login('token');
- Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs).
- See [the contribution guide](https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
- <strong>Thanks to <img src="https://avatars.githubusercontent.com/u/64450187" alt="." width="16" height="16"/> [TheDevYellowy](https://github.com/TheDevYellowy/) for patching this module!</strong>
## Need help?
Contact me in Discord: [Shiraori#1782](https://discord.com/users/721746046543331449) (UserID: 721746046543331449)
Contact me in Discord: [Shiraori#1782](https://discord.com/users/721746046543331449)
## <strong><img src="https://cdn.discordapp.com/attachments/820557032016969751/952436539118456882/flag-vietnam_1f1fb-1f1f3.png" alt="." width="20" height="20"/> Vietnamese</strong>
- Tóm lại là module này dùng Discord.js v13 (thực ra là dev của v14), API v10 nên chưa chết sớm đâu, cứ dùng đi =))
- Tóm lại là module này dùng Discord.js v13 , API v10 nên chưa chết sớm đâu, cứ dùng đi =))

View File

@ -1,6 +1,6 @@
{
"name": "discord.js-selfbot-v13",
"version": "0.3.0",
"version": "1.0.2",
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
"main": "./src/index.js",
"types": "./typings/index.d.ts",
@ -34,15 +34,13 @@
"bugs": {
"url": "https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues"
},
"contributors": [
"TheDevYellowy <Yellowy#0001>"
],
"homepage": "https://github.com/aiko-chan-ai/discord.js-selfbot-v13#readme",
"dependencies": {
"@discordjs/builders": "^0.12.0",
"@discordjs/collection": "^0.5.0",
"@sapphire/async-queue": "^1.3.0",
"@sapphire/snowflake": "^3.2.0",
"@types/node-fetch": "^2.5.12",
"@types/ws": "^8.5.2",
"axios": "^0.26.1",
"chalk": "^4.1.2",
@ -53,7 +51,7 @@
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"lodash.snakecase": "^4.1.1",
"node-fetch": "^3.2.2",
"node-fetch": "^2.6.1",
"undici": "^4.15.0",
"ws": "^8.5.0"
},

209
src/RPC/index.d.ts vendored
View File

@ -1,209 +0,0 @@
declare class RpcError extends Error {
name: string;
}
declare class Rpc {
game: discordPresence | null;
constructor(rpcObj?: discordPresence | null, readonly?: boolean);
lock(): void;
toDiscord(): {
game: discordPresence | null;
};
toObject(): discordPresence;
toString(): string;
setName(name: string | null): this;
setApplicationId(id: string | null): this;
setType(type: PresenceType | number): this;
setUrl(url: string | null): this;
setDetails(details: string | null): this;
setState(state: string | null): this;
setSyncId(sync_id: string | null): this;
setId(id: string | null): this;
setSessionId(session_id: string | null): this;
setParty(party: discordPresence["party"]): this;
setFlags(flags: number | null): this;
setCreatedAt(created_at: number | null): this;
setAssets(assetsFunc: (AssetsObj: setAssetsObj) => void): Rpc;
/**
*
* @param large_image *ID image*
*/
setAssetsLargeImage(large_image: string | null): this;
/**
*
* @param large_image *ID image*
*/
setAssetsSmallImage(small_image: string | null): this;
setAssetsLargeText(large_text: string | null): this;
setAssetsSmallText(small_text: string | null): this;
setStartTimestamp(start: number | null): this;
setEndTimestamp(end: number | null): this;
setPartySize(size: [number, number] | null): this;
setPartyId(id: string | null): this;
setJoinSecret(secret: string | null): this;
setSpectateSecret(secret: string | null): this;
setMatch(secret: string | null): this;
setSecrets(secrets: discordPresence["secrets"] | null): this;
/**
* Twitch
*/
setTwitchAssets(assetsFunc: (AssetsObj: setAssetsObj) => void): Rpc;
/**
*
* @param large_image *ID Image*
*/
setTwitchAssetsLargeImage(large_image: string | null): this;
/**
*
* @param large_image *ID Image*
*/
setTwitchAssetsSmallImage(small_image: string | null): this;
/** Spotify */
setSpotifyAssets(assetsFunc: (AssetsObj: setAssetsObj) => void): Rpc;
/**
*
* @param large_image *ID Image*
*/
setSpotifyAssetsLargeImage(large_image: string | null): this;
/**
*
* @param large_image *ID Image*
*/
setSpotifyAssetsSmallImage(small_image: string | null): this;
private verifyNull;
private verifyNullAssets;
private verifyNullTimestamps;
private verifyNullParty;
private verifyNullSecrets;
}
declare class CustomStatus {
game: CustomStatusGame;
constructor(CustomStatusGame?: CustomStatusGame);
/**
* Name
* @param state Name of the status
*/
setState(state: string): CustomStatus;
/**
* Custom Status with Emoji Custom
* @param emoji Object
* emoji.name: string
* emoji.id: string
* emoji.animated: boolean
*/
setDiscordEmoji(emoji: emojiLike): CustomStatus;
/**
* Unicode Emoji
* @param emoji String
*/
setUnicodeEmoji(emoji: string): CustomStatus;
/** Convert to JSON Activity */
toDiscord(): CustomStatusGame;
toObject(): CustomStatusGame;
toString(): string;
}
interface setEmojiObj {
setName(name: string): setEmojiObj;
setId(id: string): setEmojiObj;
setAnimated(animated: boolean): setEmojiObj;
}
interface setAssetsObj {
setLargeImage(img: string | null): setAssetsObj;
setSmallImage(img: string | null): setAssetsObj;
setLargeText(text: string | null): setAssetsObj;
setSmallText(text: string | null): setAssetsObj;
setNull(): setAssetsObj;
}
interface CustomStatusGame {
name: string;
emoji: {
name: string;
id: string | null;
animated: boolean;
} | null;
state: string;
}
interface rpcManager {
default?: rpcManager;
Rpc: {
new (rpcobj?: discordPresence): Rpc;
};
PresenceTypes: PresenceType[];
PresenceTypesString: PresenceTypeString[];
PresenceTypesNumber: PresenceTypeNumber[];
RpcError: {
new (message: string): RpcError;
};
getRpcImages(application_id: string): Promise<Image[]>;
getRpcImage(application_id: string, name: string): Promise<Image>;
__esModule: true;
createSpotifyRpc(client: clientLike, rpcobj?: discordPresence): Rpc;
version: string;
CustomStatus: {
new (CustomStatusGame?: CustomStatusGame): CustomStatus;
};
uuid(): string;
}
interface emojiLike {
id: string;
animated: boolean;
name: string;
[k: string]: any;
}
interface clientLike {
ws: {
connection: {
sessionID: string;
[k: string]: any;
};
[k: string]: any;
};
user: {
id: string;
[k: string]: any;
};
[k: string]: any;
}
interface discordPresence {
"name": string;
"platform"?: string;
"application_id"?: string;
"type": PresenceTypeNumber;
"url"?: string;
"details"?: string;
"state"?: string;
"sync_id"?: string;
"id"?: string;
"session_id"?: string;
"party"?: {
"size"?: [number, number];
"id": string;
};
"flags"?: number;
"created_at"?: number;
"assets"?: {
"large_image"?: string;
"small_image"?: string;
"small_text"?: string;
"large_text"?: string;
};
"timestamps"?: {
"start"?: number;
"end"?: number;
};
"secrets"?: {
"join"?: string;
"spectate"?: string;
"match"?: string;
};
}
/** getRPC {@link getRpcImage} */
declare type Image = {
name: string;
id: string;
type: number;
};
declare type PresenceTypeString = "PLAYING" | "STREAMING" | "LISTENING" | "WATCHING" | "CUSTOM" | "COMPETING";
declare type PresenceTypeNumber = 0 | 1 | 2 | 3 | 4 | 5;
declare type PresenceType = PresenceTypeNumber | PresenceTypeString;
declare var rpcManager: rpcManager;
export = rpcManager;

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
const EventEmitter = require('node:events');
const RESTManager = require('../rest/RESTManager');
const { TypeError } = require('../errors');
const Options = require('../util/Options');
const Util = require('../util/Util');
@ -12,11 +11,7 @@ const Util = require('../util/Util');
*/
class BaseClient extends EventEmitter {
constructor(options = {}) {
super({ captureRejections: true });
if (typeof options !== 'object' || options === null) {
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
}
super();
/**
* The options the client was instantiated with
@ -26,11 +21,18 @@ class BaseClient extends EventEmitter {
/**
* The REST manager of the client
* @type {REST}
* @type {RESTManager}
* @private
*/
this.rest = new RESTManager(this);
}
/**
* API shortcut
* @type {Object}
* @readonly
* @private
*/
get api() {
return this.rest.api;
}
@ -40,7 +42,7 @@ class BaseClient extends EventEmitter {
* @returns {void}
*/
destroy() {
if(this.rest.sweepInterval) clearInterval(this.rest.sweepInterval);
if (this.rest.sweepInterval) clearInterval(this.rest.sweepInterval);
}
/**
@ -73,6 +75,7 @@ class BaseClient extends EventEmitter {
module.exports = BaseClient;
/**
* @external REST
* @see {@link https://discord.js.org/#/docs/rest/main/class/REST}
* Emitted for general debugging information.
* @event BaseClient#debug
* @param {string} info The debug information
*/

View File

@ -1,8 +1,8 @@
'use strict';
const process = require('node:process');
const { setInterval } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { OAuth2Scopes, Routes } = require('discord-api-types/v9');
const BaseClient = require('./BaseClient');
const ActionsManager = require('./actions/ActionsManager');
const ClientVoiceManager = require('./voice/ClientVoiceManager');
@ -12,9 +12,6 @@ const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
const ChannelManager = require('../managers/ChannelManager');
const GuildManager = require('../managers/GuildManager');
const UserManager = require('../managers/UserManager');
const FriendsManager = require('../managers/FriendsManager');
const BlockedManager = require('../managers/BlockedManager');
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const ClientPresence = require('../structures/ClientPresence');
const GuildPreview = require('../structures/GuildPreview');
@ -25,13 +22,16 @@ const StickerPack = require('../structures/StickerPack');
const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook');
const Widget = require('../structures/Widget');
const { Events, InviteScopes, Status } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Events = require('../util/Events');
const IntentsBitField = require('../util/IntentsBitField');
const Intents = require('../util/Intents');
const Options = require('../util/Options');
const PermissionsBitField = require('../util/PermissionsBitField');
const Status = require('../util/Status');
const Permissions = require('../util/Permissions');
const Sweepers = require('../util/Sweepers');
// Patch
const FriendsManager = require('../managers/FriendsManager');
const BlockedManager = require('../managers/BlockedManager');
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
/**
* The main hub for interacting with the Discord API, and the starting point for any bot.
@ -79,6 +79,20 @@ class Client extends BaseClient {
this._validateOptions();
/**
* Functions called when a cache is garbage collected or the Client is destroyed
* @type {Set<Function>}
* @private
*/
this._cleanups = new Set();
/**
* The finalizers used to cleanup items.
* @type {FinalizationRegistry}
* @private
*/
this._finalizers = new FinalizationRegistry(this._finalize.bind(this));
/**
* The WebSocket manager of the client
* @type {WebSocketManager}
@ -111,6 +125,10 @@ class Client extends BaseClient {
* @type {UserManager}
*/
this.users = new UserManager(this);
/** Patch
*
*/
this.friends = new FriendsManager(this);
this.blocked = new BlockedManager(this);
this.setting = new ClientUserSettingManager(this);
@ -156,12 +174,6 @@ class Client extends BaseClient {
this.token = null;
}
/**
* used for interacitons
* @type {?String}
*/
this.session_id = null;
/**
* User that the client is logged in as
* @type {?ClientUser}
@ -173,13 +185,24 @@ class Client extends BaseClient {
* @type {?ClientApplication}
*/
this.application = null;
this.bot = null;
/**
* Timestamp of the time the client was last `READY` at
* @type {?number}
* Time at which the client was last regarded as being in the `READY` state
* (each time the client disconnects and successfully reconnects, this will be overwritten)
* @type {?Date}
*/
this.readyTimestamp = null;
this.readyAt = null;
if (this.options.messageSweepInterval > 0) {
process.emitWarning(
'The message sweeping client options are deprecated, use the global sweepers instead.',
'DeprecationWarning',
);
this.sweepMessageInterval = setInterval(
this.sweepMessages.bind(this),
this.options.messageSweepInterval * 1_000,
).unref();
}
}
/**
@ -196,13 +219,12 @@ class Client extends BaseClient {
}
/**
* Time at which the client was last regarded as being in the `READY` state
* (each time the client disconnects and successfully reconnects, this will be overwritten)
* @type {?Date}
* Timestamp of the time the client was last `READY` at
* @type {?number}
* @readonly
*/
get readyAt() {
return this.readyTimestamp && new Date(this.readyTimestamp);
get readyTimestamp() {
return this.readyAt?.getTime() ?? null;
}
/**
@ -211,23 +233,21 @@ class Client extends BaseClient {
* @readonly
*/
get uptime() {
return this.readyTimestamp && Date.now() - this.readyTimestamp;
return this.readyAt ? Date.now() - this.readyAt : null;
}
/**
* Logs the client in, establishing a WebSocket connection to Discord.
* @param {string} [token=this.token] Token of the account to log in with
* @param {Boolean} [bot=false] Wether the token used is a bot account or not
* @returns {Promise<string>} Token of the account used
* @example
* client.login('my token');
*/
async login(token = this.token, bot = false) {
async login(token = this.token) {
if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
this.token = token = token.replace(/^(Bot|Bearer)\s*/i, '');
this.bot = bot;
this.emit(
Events.Debug,
Events.DEBUG,
`Provided token: ${token
.split('.')
.map((val, i) => (i > 1 ? val.replace(/./g, '*') : val))
@ -238,7 +258,7 @@ class Client extends BaseClient {
this.options.ws.presence = this.presence._parse(this.options.presence);
}
this.emit(Events.Debug, 'Preparing to connect to the gateway...');
this.emit(Events.DEBUG, 'Preparing to connect to the gateway...');
try {
await this.ws.connect();
@ -255,7 +275,7 @@ class Client extends BaseClient {
* @returns {boolean}
*/
isReady() {
return this.ws.status === Status.Ready;
return this.ws.status === Status.READY;
}
/**
@ -265,10 +285,14 @@ class Client extends BaseClient {
destroy() {
super.destroy();
for (const fn of this._cleanups) fn();
this._cleanups.clear();
if (this.sweepMessageInterval) clearInterval(this.sweepMessageInterval);
this.sweepers.destroy();
this.ws.destroy();
this.token = null;
//this.rest.setToken(null);
}
/**
@ -290,14 +314,9 @@ class Client extends BaseClient {
*/
async fetchInvite(invite, options) {
const code = DataResolver.resolveInviteCode(invite);
const query = new URLSearchParams({
with_counts: true,
with_expiration: true,
const data = await this.api.invites(code).get({
query: { with_counts: true, with_expiration: true, guild_scheduled_event_id: options?.guildScheduledEventId },
});
if (options?.guildScheduledEventId) {
query.set('guild_scheduled_event_id', options.guildScheduledEventId);
}
const data = await this.api.invites(code).get({ query });
return new Invite(this, data);
}
@ -327,7 +346,7 @@ class Client extends BaseClient {
* .catch(console.error);
*/
async fetchWebhook(id, token) {
const data = await this.api.webhook(id, token).get();
const data = await this.api.webhooks(id, token).get();
return new Webhook(this, { token, ...data });
}
@ -372,6 +391,50 @@ class Client extends BaseClient {
const data = await this.api('sticker-packs').get();
return new Collection(data.sticker_packs.map(p => [p.id, new StickerPack(this, p)]));
}
/**
* A last ditch cleanup function for garbage collection.
* @param {Function} options.cleanup The function called to GC
* @param {string} [options.message] The message to send after a successful GC
* @param {string} [options.name] The name of the item being GCed
* @private
*/
_finalize({ cleanup, message, name }) {
try {
cleanup();
this._cleanups.delete(cleanup);
if (message) {
this.emit(Events.DEBUG, message);
}
} catch {
this.emit(Events.DEBUG, `Garbage collection failed on ${name ?? 'an unknown item'}.`);
}
}
/**
* Sweeps all text-based channels' messages and removes the ones older than the max message lifetime.
* If the message has been edited, the time of the edit is used rather than the time of the original message.
* @param {number} [lifetime=this.options.messageCacheLifetime] Messages that are older than this (in seconds)
* will be removed from the caches. The default is based on {@link ClientOptions#messageCacheLifetime}
* @returns {number} Amount of messages that were removed from the caches,
* or -1 if the message cache lifetime is unlimited
* @example
* // Remove all messages older than 1800 seconds from the messages cache
* const amount = client.sweepMessages(1800);
* console.log(`Successfully removed ${amount} messages from the cache.`);
*/
sweepMessages(lifetime = this.options.messageCacheLifetime) {
if (typeof lifetime !== 'number' || isNaN(lifetime)) {
throw new TypeError('INVALID_TYPE', 'lifetime', 'number');
}
if (lifetime <= 0) {
this.emit(Events.DEBUG, "Didn't sweep messages - lifetime is unlimited");
return -1;
}
const messages = this.sweepers.sweepMessages(Sweepers.outdatedMessageSweepFilter(lifetime)());
this.emit(Events.DEBUG, `Swept ${messages} messages older than ${lifetime} seconds`);
return messages;
}
/**
* Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds.
@ -400,7 +463,7 @@ class Client extends BaseClient {
/**
* Options for {@link Client#generateInvite}.
* @typedef {Object} InviteGenerationOptions
* @property {OAuth2Scopes[]} scopes Scopes that should be requested
* @property {InviteScope[]} scopes Scopes that should be requested
* @property {PermissionResolvable} [permissions] Permissions to request
* @property {GuildResolvable} [guild] Guild to preselect
* @property {boolean} [disableGuildSelect] Whether to disable the guild selection
@ -412,17 +475,17 @@ class Client extends BaseClient {
* @returns {string}
* @example
* const link = client.generateInvite({
* scopes: [OAuth2Scopes.ApplicationsCommands],
* scopes: ['applications.commands'],
* });
* console.log(`Generated application invite link: ${link}`);
* @example
* const link = client.generateInvite({
* permissions: [
* PermissionFlagsBits.SendMessages,
* PermissionFlagsBits.ManageGuild,
* PermissionFlagsBits.MentionEveryone,
* Permissions.FLAGS.SEND_MESSAGES,
* Permissions.FLAGS.MANAGE_GUILD,
* Permissions.FLAGS.MENTION_EVERYONE,
* ],
* scopes: [OAuth2Scopes.Bot],
* scopes: ['bot'],
* });
* console.log(`Generated bot invite link: ${link}`);
*/
@ -441,18 +504,17 @@ class Client extends BaseClient {
if (!Array.isArray(scopes)) {
throw new TypeError('INVALID_TYPE', 'scopes', 'Array of Invite Scopes', true);
}
if (!scopes.some(scope => [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands].includes(scope))) {
if (!scopes.some(scope => ['bot', 'applications.commands'].includes(scope))) {
throw new TypeError('INVITE_MISSING_SCOPES');
}
const validScopes = Object.values(OAuth2Scopes);
const invalidScope = scopes.find(scope => !validScopes.includes(scope));
const invalidScope = scopes.find(scope => !InviteScopes.includes(scope));
if (invalidScope) {
throw new TypeError('INVALID_ELEMENT', 'Array', 'scopes', invalidScope);
}
query.set('scope', scopes.join(' '));
if (options.permissions) {
const permissions = PermissionsBitField.resolve(options.permissions);
const permissions = Permissions.resolve(options.permissions);
if (permissions) query.set('permissions', permissions);
}
@ -466,7 +528,7 @@ class Client extends BaseClient {
query.set('guild_id', guildId);
}
return `${this.options.rest.api}${Routes.oauth2Authorization()}?${query}`;
return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
}
toJSON() {
@ -495,7 +557,7 @@ class Client extends BaseClient {
if (typeof options.intents === 'undefined') {
throw new TypeError('CLIENT_MISSING_INTENTS');
} else {
options.intents = IntentsBitField.resolve(options.intents);
options.intents = Intents.resolve(options.intents);
}
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) {
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1');
@ -507,42 +569,56 @@ class Client extends BaseClient {
if (typeof options.makeCache !== 'function') {
throw new TypeError('CLIENT_INVALID_OPTION', 'makeCache', 'a function');
}
if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number');
}
if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number');
}
if (typeof options.sweepers !== 'object' || options.sweepers === null) {
throw new TypeError('CLIENT_INVALID_OPTION', 'sweepers', 'an object');
}
if (typeof options.invalidRequestWarningInterval !== 'number' || isNaN(options.invalidRequestWarningInterval)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'invalidRequestWarningInterval', 'a number');
}
if (!Array.isArray(options.partials)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
}
if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number');
}
if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
}
if (typeof options.restRequestTimeout !== 'number' || isNaN(options.restRequestTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restRequestTimeout', 'a number');
}
if (typeof options.restGlobalRateLimit !== 'number' || isNaN(options.restGlobalRateLimit)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restGlobalRateLimit', 'a number');
}
if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number');
}
if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number');
}
if (typeof options.failIfNotExists !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'failIfNotExists', 'a boolean');
}
if (!Array.isArray(options.userAgentSuffix)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'userAgentSuffix', 'an array of strings');
}
if (
typeof options.rejectOnRateLimit !== 'undefined' &&
!(typeof options.rejectOnRateLimit === 'function' || Array.isArray(options.rejectOnRateLimit))
) {
throw new TypeError('CLIENT_INVALID_OPTION', 'rejectOnRateLimit', 'an array or a function');
}
}
}
module.exports = Client;
/**
* A {@link https://developer.twitter.com/en/docs/twitter-ids Twitter snowflake},
* except the epoch is 2015-01-01T00:00:00.000Z.
*
* If we have a snowflake '266241948824764416' we can represent it as binary:
* ```
* 64 22 17 12 0
* 000000111011000111100001101001000101000000 00001 00000 000000000000
* number of milliseconds since Discord epoch worker pid increment
* ```
* @typedef {string} Snowflake
*/
/**
* Emitted for general debugging information.
* @event Client#debug
* @param {string} info The debug information
*/
/**
* Emitted for general warnings.
* @event Client#warn
@ -553,13 +629,3 @@ module.exports = Client;
* @external Collection
* @see {@link https://discord.js.org/#/docs/collection/main/class/Collection}
*/
/**
* @external ImageURLOptions
* @see {@link https://discord.js.org/#/docs/rest/main/typedef/ImageURLOptions}
*/
/**
* @external BaseImageURLOptions
* @see {@link https://discord.js.org/#/docs/rest/main/typedef/BaseImageURLOptions}
*/

View File

@ -30,7 +30,7 @@ class WebhookClient extends BaseClient {
if ('url' in data) {
const url = data.url.match(
// eslint-disable-next-line no-useless-escape
/https?:\/\/(?:ptb\.|canary\.)?discord\.com\/api(?:\/v\d{1,2})?\/webhooks\/(\d{17,19})\/([\w-]{68})/i,
/^https?:\/\/(?:canary|ptb)?\.?discord\.com\/api\/webhooks(?:\/v[0-9]\d*)?\/([^\/]+)\/([^\/]+)/i,
);
if (!url || url.length <= 1) throw new Error('WEBHOOK_URL_INVALID');

View File

@ -1,6 +1,6 @@
'use strict';
const Partials = require('../../util/Partials');
const { PartialTypes } = require('../../util/Constants');
/*
@ -43,7 +43,7 @@ class GenericAction {
},
this.client.channels,
id,
Partials.Channel,
PartialTypes.CHANNEL,
)
);
}
@ -60,7 +60,7 @@ class GenericAction {
},
channel.messages,
id,
Partials.Message,
PartialTypes.MESSAGE,
cache,
)
);
@ -76,17 +76,17 @@ class GenericAction {
},
message.reactions,
id,
Partials.Reaction,
PartialTypes.REACTION,
);
}
getMember(data, guild) {
return this.getPayload(data, guild.members, data.user.id, Partials.GuildMember);
return this.getPayload(data, guild.members, data.user.id, PartialTypes.GUILD_MEMBER);
}
getUser(data) {
const id = data.user_id;
return data.user ?? this.getPayload({ id }, this.client.users, id, Partials.User);
return data.user ?? this.getPayload({ id }, this.client.users, id, PartialTypes.USER);
}
getUserFromMember(data) {
@ -107,7 +107,7 @@ class GenericAction {
{ id, guild_id: data.guild_id ?? guild.id },
guild.scheduledEvents,
id,
Partials.GuildScheduledEvent,
PartialTypes.GUILD_SCHEDULED_EVENT,
);
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class ChannelCreateAction extends Action {
handle(data) {
@ -14,7 +14,7 @@ class ChannelCreateAction extends Action {
* @event Client#channelCreate
* @param {GuildChannel} channel The channel that was created
*/
client.emit(Events.ChannelCreate, channel);
client.emit(Events.CHANNEL_CREATE, channel);
}
return { channel };
}

View File

@ -1,22 +1,38 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedChannels } = require('../../structures/Channel');
const DMChannel = require('../../structures/DMChannel');
const { deletedMessages } = require('../../structures/Message');
const { Events } = require('../../util/Constants');
class ChannelDeleteAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();
}
handle(data) {
const client = this.client;
const channel = client.channels.cache.get(data.id);
if (channel) {
client.channels._remove(channel.id);
deletedChannels.add(channel);
if (channel.messages && !(channel instanceof DMChannel)) {
for (const message of channel.messages.cache.values()) {
deletedMessages.add(message);
}
}
/**
* Emitted whenever a channel is deleted.
* @event Client#channelDelete
* @param {DMChannel|GuildChannel} channel The channel that was deleted
*/
client.emit(Events.ChannelDelete, channel);
client.emit(Events.CHANNEL_DELETE, channel);
}
return { channel };
}
}

View File

@ -2,6 +2,7 @@
const Action = require('./Action');
const { Channel } = require('../../structures/Channel');
const { ChannelTypes } = require('../../util/Constants');
class ChannelUpdateAction extends Action {
handle(data) {
@ -11,7 +12,7 @@ class ChannelUpdateAction extends Action {
if (channel) {
const old = channel._update(data);
if (channel.type !== data.type) {
if (ChannelTypes[channel.type] !== data.type) {
const newChannel = Channel.create(this.client, data, channel.guild);
for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message);
channel = newChannel;

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildBanAdd extends Action {
handle(data) {
@ -13,7 +13,7 @@ class GuildBanAdd extends Action {
* @event Client#guildBanAdd
* @param {GuildBan} ban The ban that occurred
*/
if (guild) client.emit(Events.GuildBanAdd, guild.bans._add(data));
if (guild) client.emit(Events.GUILD_BAN_ADD, guild.bans._add(data));
}
}

View File

@ -2,7 +2,7 @@
const Action = require('./Action');
const GuildBan = require('../../structures/GuildBan');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildBanRemove extends Action {
handle(data) {
@ -17,7 +17,7 @@ class GuildBanRemove extends Action {
if (guild) {
const ban = guild.bans.cache.get(data.user.id) ?? new GuildBan(client, data, guild);
guild.bans.cache.delete(ban.user.id);
client.emit(Events.GuildBanRemove, ban);
client.emit(Events.GUILD_BAN_REMOVE, ban);
}
}
}

View File

@ -1,9 +1,16 @@
'use strict';
const { setTimeout } = require('node:timers');
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedGuilds } = require('../../structures/Guild');
const { Events } = require('../../util/Constants');
class GuildDeleteAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();
}
handle(data) {
const client = this.client;
@ -18,11 +25,13 @@ class GuildDeleteAction extends Action {
* @event Client#guildUnavailable
* @param {Guild} guild The guild that has become unavailable
*/
client.emit(Events.GuildUnavailable, guild);
client.emit(Events.GUILD_UNAVAILABLE, guild);
// Stops the GuildDelete packet thinking a guild was actually deleted,
// handles emitting of event itself
return;
return {
guild: null,
};
}
for (const channel of guild.channels.cache.values()) this.client.channels._remove(channel.id);
@ -30,14 +39,26 @@ class GuildDeleteAction extends Action {
// Delete guild
client.guilds.cache.delete(guild.id);
deletedGuilds.add(guild);
/**
* Emitted whenever a guild kicks the client or the guild is deleted/left.
* @event Client#guildDelete
* @param {Guild} guild The guild that was deleted
*/
client.emit(Events.GuildDelete, guild);
client.emit(Events.GUILD_DELETE, guild);
this.deleted.set(guild.id, guild);
this.scheduleForDeletion(guild.id);
} else {
guild = this.deleted.get(data.id) ?? null;
}
return { guild };
}
scheduleForDeletion(id) {
setTimeout(() => this.deleted.delete(id), this.client.options.restWsBridgeTimeout).unref();
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildEmojiCreateAction extends Action {
handle(guild, createdEmoji) {
@ -12,7 +12,7 @@ class GuildEmojiCreateAction extends Action {
* @event Client#emojiCreate
* @param {GuildEmoji} emoji The emoji that was created
*/
if (!already) this.client.emit(Events.GuildEmojiCreate, emoji);
if (!already) this.client.emit(Events.GUILD_EMOJI_CREATE, emoji);
return { emoji };
}
}

View File

@ -1,17 +1,19 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedEmojis } = require('../../structures/Emoji');
const { Events } = require('../../util/Constants');
class GuildEmojiDeleteAction extends Action {
handle(emoji) {
emoji.guild.emojis.cache.delete(emoji.id);
deletedEmojis.add(emoji);
/**
* Emitted whenever a custom emoji is deleted in a guild.
* @event Client#emojiDelete
* @param {GuildEmoji} emoji The emoji that was deleted
*/
this.client.emit(Events.GuildEmojiDelete, emoji);
this.client.emit(Events.GUILD_EMOJI_DELETE, emoji);
return { emoji };
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildEmojiUpdateAction extends Action {
handle(current, data) {
@ -12,7 +12,7 @@ class GuildEmojiUpdateAction extends Action {
* @param {GuildEmoji} oldEmoji The old emoji
* @param {GuildEmoji} newEmoji The new emoji
*/
this.client.emit(Events.GuildEmojiUpdate, old, current);
this.client.emit(Events.GUILD_EMOJI_UPDATE, old, current);
return { emoji: current };
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildIntegrationsUpdate extends Action {
handle(data) {
@ -12,7 +12,7 @@ class GuildIntegrationsUpdate extends Action {
* @event Client#guildIntegrationsUpdate
* @param {Guild} guild The guild whose integrations were updated
*/
if (guild) client.emit(Events.GuildIntegrationsUpdate, guild);
if (guild) client.emit(Events.GUILD_INTEGRATIONS_UPDATE, guild);
}
}

View File

@ -1,8 +1,8 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const Status = require('../../util/Status');
const { deletedGuildMembers } = require('../../structures/GuildMember');
const { Events, Status } = require('../../util/Constants');
class GuildMemberRemoveAction extends Action {
handle(data, shard) {
@ -13,13 +13,14 @@ class GuildMemberRemoveAction extends Action {
member = this.getMember({ user: data.user }, guild);
guild.memberCount--;
if (member) {
deletedGuildMembers.add(member);
guild.members.cache.delete(member.id);
/**
* Emitted whenever a member leaves a guild, or is kicked.
* @event Client#guildMemberRemove
* @param {GuildMember} member The member that has left/been kicked from the guild
*/
if (shard.status === Status.Ready) client.emit(Events.GuildMemberRemove, member);
if (shard.status === Status.READY) client.emit(Events.GUILD_MEMBER_REMOVE, member);
}
guild.voiceStates.cache.delete(data.user.id);
}

View File

@ -1,8 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const Status = require('../../util/Status');
const { Status, Events } = require('../../util/Constants');
class GuildMemberUpdateAction extends Action {
handle(data, shard) {
@ -27,7 +26,7 @@ class GuildMemberUpdateAction extends Action {
* @param {GuildMember} oldMember The member before the update
* @param {GuildMember} newMember The member after the update
*/
if (shard.status === Status.Ready && !member.equals(old)) client.emit(Events.GuildMemberUpdate, old, member);
if (shard.status === Status.READY && !member.equals(old)) client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
} else {
const newMember = guild.members._add(data);
/**
@ -35,7 +34,7 @@ class GuildMemberUpdateAction extends Action {
* @event Client#guildMemberAvailable
* @param {GuildMember} member The member that became available
*/
this.client.emit(Events.GuildMemberAvailable, newMember);
this.client.emit(Events.GUILD_MEMBER_AVAILABLE, newMember);
}
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildRoleCreate extends Action {
handle(data) {
@ -16,7 +16,7 @@ class GuildRoleCreate extends Action {
* @event Client#roleCreate
* @param {Role} role The role that was created
*/
if (!already) client.emit(Events.GuildRoleCreate, role);
if (!already) client.emit(Events.GUILD_ROLE_CREATE, role);
}
return { role };
}

View File

@ -1,7 +1,8 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedRoles } = require('../../structures/Role');
const { Events } = require('../../util/Constants');
class GuildRoleDeleteAction extends Action {
handle(data) {
@ -13,12 +14,13 @@ class GuildRoleDeleteAction extends Action {
role = guild.roles.cache.get(data.role_id);
if (role) {
guild.roles.cache.delete(data.role_id);
deletedRoles.add(role);
/**
* Emitted whenever a guild role is deleted.
* @event Client#roleDelete
* @param {Role} role The role that was deleted
*/
client.emit(Events.GuildRoleDelete, role);
client.emit(Events.GUILD_ROLE_DELETE, role);
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildRoleUpdateAction extends Action {
handle(data) {
@ -20,7 +20,7 @@ class GuildRoleUpdateAction extends Action {
* @param {Role} oldRole The role before the update
* @param {Role} newRole The role after the update
*/
client.emit(Events.GuildRoleUpdate, old, role);
client.emit(Events.GUILD_ROLE_UPDATE, old, role);
}
return {

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildScheduledEventCreateAction extends Action {
handle(data) {
@ -15,7 +15,7 @@ class GuildScheduledEventCreateAction extends Action {
* @event Client#guildScheduledEventCreate
* @param {GuildScheduledEvent} guildScheduledEvent The created guild scheduled event
*/
client.emit(Events.GuildScheduledEventCreate, guildScheduledEvent);
client.emit(Events.GUILD_SCHEDULED_EVENT_CREATE, guildScheduledEvent);
return { guildScheduledEvent };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildScheduledEventDeleteAction extends Action {
handle(data) {
@ -18,7 +18,7 @@ class GuildScheduledEventDeleteAction extends Action {
* @event Client#guildScheduledEventDelete
* @param {GuildScheduledEvent} guildScheduledEvent The deleted guild scheduled event
*/
client.emit(Events.GuildScheduledEventDelete, guildScheduledEvent);
client.emit(Events.GUILD_SCHEDULED_EVENT_DELETE, guildScheduledEvent);
return { guildScheduledEvent };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildScheduledEventUpdateAction extends Action {
handle(data) {
@ -18,7 +18,7 @@ class GuildScheduledEventUpdateAction extends Action {
* @param {?GuildScheduledEvent} oldGuildScheduledEvent The guild scheduled event object before the update
* @param {GuildScheduledEvent} newGuildScheduledEvent The guild scheduled event object after the update
*/
client.emit(Events.GuildScheduledEventUpdate, oldGuildScheduledEvent, newGuildScheduledEvent);
client.emit(Events.GUILD_SCHEDULED_EVENT_UPDATE, oldGuildScheduledEvent, newGuildScheduledEvent);
return { oldGuildScheduledEvent, newGuildScheduledEvent };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildScheduledEventUserAddAction extends Action {
handle(data) {
@ -19,7 +19,7 @@ class GuildScheduledEventUserAddAction extends Action {
* @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event
* @param {User} user The user who subscribed
*/
client.emit(Events.GuildScheduledEventUserAdd, guildScheduledEvent, user);
client.emit(Events.GUILD_SCHEDULED_EVENT_USER_ADD, guildScheduledEvent, user);
return { guildScheduledEvent, user };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildScheduledEventUserRemoveAction extends Action {
handle(data) {
@ -19,7 +19,7 @@ class GuildScheduledEventUserRemoveAction extends Action {
* @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event
* @param {User} user The user who unsubscribed
*/
client.emit(Events.GuildScheduledEventUserRemove, guildScheduledEvent, user);
client.emit(Events.GUILD_SCHEDULED_EVENT_USER_REMOVE, guildScheduledEvent, user);
return { guildScheduledEvent, user };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildStickerCreateAction extends Action {
handle(guild, createdSticker) {
@ -12,7 +12,7 @@ class GuildStickerCreateAction extends Action {
* @event Client#stickerCreate
* @param {Sticker} sticker The sticker that was created
*/
if (!already) this.client.emit(Events.GuildStickerCreate, sticker);
if (!already) this.client.emit(Events.GUILD_STICKER_CREATE, sticker);
return { sticker };
}
}

View File

@ -1,17 +1,19 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedStickers } = require('../../structures/Sticker');
const { Events } = require('../../util/Constants');
class GuildStickerDeleteAction extends Action {
handle(sticker) {
sticker.guild.stickers.cache.delete(sticker.id);
deletedStickers.add(sticker);
/**
* Emitted whenever a custom sticker is deleted in a guild.
* @event Client#stickerDelete
* @param {Sticker} sticker The sticker that was deleted
*/
this.client.emit(Events.GuildStickerDelete, sticker);
this.client.emit(Events.GUILD_STICKER_DELETE, sticker);
return { sticker };
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildStickerUpdateAction extends Action {
handle(current, data) {
@ -12,7 +12,7 @@ class GuildStickerUpdateAction extends Action {
* @param {Sticker} oldSticker The old sticker
* @param {Sticker} newSticker The new sticker
*/
this.client.emit(Events.GuildStickerUpdate, old, current);
this.client.emit(Events.GUILD_STICKER_UPDATE, old, current);
return { sticker: current };
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class GuildUpdateAction extends Action {
handle(data) {
@ -16,7 +16,7 @@ class GuildUpdateAction extends Action {
* @param {Guild} oldGuild The guild before the update
* @param {Guild} newGuild The guild after the update
*/
client.emit(Events.GuildUpdate, old, guild);
client.emit(Events.GUILD_UPDATE, old, guild);
return {
old,
updated: guild,

View File

@ -1,14 +1,16 @@
'use strict';
const { InteractionType, ComponentType, ApplicationCommandType } = require('discord-api-types/v9');
const process = require('node:process');
const Action = require('./Action');
const AutocompleteInteraction = require('../../structures/AutocompleteInteraction');
const ButtonInteraction = require('../../structures/ButtonInteraction');
const ChatInputCommandInteraction = require('../../structures/ChatInputCommandInteraction');
const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction');
const CommandInteraction = require('../../structures/CommandInteraction');
const MessageContextMenuInteraction = require('../../structures/MessageContextMenuInteraction');
const SelectMenuInteraction = require('../../structures/SelectMenuInteraction');
const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction');
const Events = require('../../util/Events');
const UserContextMenuInteraction = require('../../structures/UserContextMenuInteraction');
const { Events, InteractionTypes, MessageComponentTypes, ApplicationCommandTypes } = require('../../util/Constants');
let deprecationEmitted = false;
class InteractionCreateAction extends Action {
handle(data) {
@ -17,59 +19,70 @@ class InteractionCreateAction extends Action {
// Resolve and cache partial channels for Interaction#channel getter
this.getChannel(data);
let InteractionClass;
let InteractionType;
switch (data.type) {
case InteractionType.ApplicationCommand:
case InteractionTypes.APPLICATION_COMMAND:
switch (data.data.type) {
case ApplicationCommandType.ChatInput:
InteractionClass = ChatInputCommandInteraction;
case ApplicationCommandTypes.CHAT_INPUT:
InteractionType = CommandInteraction;
break;
case ApplicationCommandType.User:
InteractionClass = UserContextMenuCommandInteraction;
case ApplicationCommandTypes.USER:
InteractionType = UserContextMenuInteraction;
break;
case ApplicationCommandType.Message:
InteractionClass = MessageContextMenuCommandInteraction;
case ApplicationCommandTypes.MESSAGE:
InteractionType = MessageContextMenuInteraction;
break;
default:
client.emit(
Events.Debug,
Events.DEBUG,
`[INTERACTION] Received application command interaction with unknown type: ${data.data.type}`,
);
return;
}
break;
case InteractionType.MessageComponent:
case InteractionTypes.MESSAGE_COMPONENT:
switch (data.data.component_type) {
case ComponentType.Button:
InteractionClass = ButtonInteraction;
case MessageComponentTypes.BUTTON:
InteractionType = ButtonInteraction;
break;
case ComponentType.SelectMenu:
InteractionClass = SelectMenuInteraction;
case MessageComponentTypes.SELECT_MENU:
InteractionType = SelectMenuInteraction;
break;
default:
client.emit(
Events.Debug,
Events.DEBUG,
`[INTERACTION] Received component interaction with unknown type: ${data.data.component_type}`,
);
return;
}
break;
case InteractionType.ApplicationCommandAutocomplete:
InteractionClass = AutocompleteInteraction;
case InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE:
InteractionType = AutocompleteInteraction;
break;
default:
client.emit(Events.Debug, `[INTERACTION] Received interaction with unknown type: ${data.type}`);
client.emit(Events.DEBUG, `[INTERACTION] Received interaction with unknown type: ${data.type}`);
return;
}
const interaction = new InteractionClass(client, data);
const interaction = new InteractionType(client, data);
/**
* Emitted when an interaction is created.
* @event Client#interactionCreate
* @param {Interaction} interaction The interaction which was created
*/
client.emit(Events.InteractionCreate, interaction);
client.emit(Events.INTERACTION_CREATE, interaction);
/**
* Emitted when an interaction is created.
* @event Client#interaction
* @param {Interaction} interaction The interaction which was created
* @deprecated Use {@link Client#event:interactionCreate} instead
*/
if (client.emit('interaction', interaction) && !deprecationEmitted) {
deprecationEmitted = true;
process.emitWarning('The interaction event is deprecated. Use interactionCreate instead', 'DeprecationWarning');
}
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class InviteCreateAction extends Action {
handle(data) {
@ -20,7 +20,7 @@ class InviteCreateAction extends Action {
* @event Client#inviteCreate
* @param {Invite} invite The invite that was created
*/
client.emit(Events.InviteCreate, invite);
client.emit(Events.INVITE_CREATE, invite);
return { invite };
}
}

View File

@ -2,7 +2,7 @@
const Action = require('./Action');
const Invite = require('../../structures/Invite');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class InviteDeleteAction extends Action {
handle(data) {
@ -22,7 +22,7 @@ class InviteDeleteAction extends Action {
* @event Client#inviteDelete
* @param {Invite} invite The invite that was deleted
*/
client.emit(Events.InviteDelete, invite);
client.emit(Events.INVITE_DELETE, invite);
return { invite };
}
}

View File

@ -1,14 +1,17 @@
'use strict';
const process = require('node:process');
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
let deprecationEmitted = false;
class MessageCreateAction extends Action {
handle(data) {
const client = this.client;
const channel = this.getChannel(data);
if (channel) {
if (!channel.isTextBased()) return {};
if (!channel.isText()) return {};
const existing = channel.messages.cache.get(data.id);
if (existing) return { message: existing };
@ -20,7 +23,18 @@ class MessageCreateAction extends Action {
* @event Client#messageCreate
* @param {Message} message The created message
*/
client.emit(Events.MessageCreate, message);
client.emit(Events.MESSAGE_CREATE, message);
/**
* Emitted whenever a message is created.
* @event Client#message
* @param {Message} message The created message
* @deprecated Use {@link Client#event:messageCreate} instead
*/
if (client.emit('message', message) && !deprecationEmitted) {
deprecationEmitted = true;
process.emitWarning('The message event is deprecated. Use messageCreate instead', 'DeprecationWarning');
}
return { message };
}

View File

@ -1,7 +1,8 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedMessages } = require('../../structures/Message');
const { Events } = require('../../util/Constants');
class MessageDeleteAction extends Action {
handle(data) {
@ -9,17 +10,18 @@ class MessageDeleteAction extends Action {
const channel = this.getChannel(data);
let message;
if (channel) {
if (!channel.isTextBased()) return {};
if (!channel.isText()) return {};
message = this.getMessage(data, channel);
if (message) {
channel.messages.cache.delete(message.id);
deletedMessages.add(message);
/**
* Emitted whenever a message is deleted.
* @event Client#messageDelete
* @param {Message} message The deleted message
*/
client.emit(Events.MessageDelete, message);
client.emit(Events.MESSAGE_DELETE, message);
}
}

View File

@ -2,7 +2,8 @@
const { Collection } = require('@discordjs/collection');
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedMessages } = require('../../structures/Message');
const { Events } = require('../../util/Constants');
class MessageDeleteBulkAction extends Action {
handle(data) {
@ -10,7 +11,7 @@ class MessageDeleteBulkAction extends Action {
const channel = client.channels.cache.get(data.channel_id);
if (channel) {
if (!channel.isTextBased()) return {};
if (!channel.isText()) return {};
const ids = data.ids;
const messages = new Collection();
@ -24,6 +25,7 @@ class MessageDeleteBulkAction extends Action {
false,
);
if (message) {
deletedMessages.add(message);
messages.set(message.id, message);
channel.messages.cache.delete(id);
}
@ -34,7 +36,7 @@ class MessageDeleteBulkAction extends Action {
* @event Client#messageDeleteBulk
* @param {Collection<Snowflake, Message>} messages The deleted messages, mapped by their id
*/
if (messages.size > 0) client.emit(Events.MessageBulkDelete, messages);
if (messages.size > 0) client.emit(Events.MESSAGE_BULK_DELETE, messages);
return { messages };
}
return {};

View File

@ -1,8 +1,8 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const Partials = require('../../util/Partials');
const { Events } = require('../../util/Constants');
const { PartialTypes } = require('../../util/Constants');
/*
{ user_id: 'id',
@ -23,14 +23,14 @@ class MessageReactionAdd extends Action {
// Verify channel
const channel = this.getChannel(data);
if (!channel?.isTextBased()) return false;
if (!channel || !channel.isText()) return false;
// Verify message
const message = this.getMessage(data, channel);
if (!message) return false;
// Verify reaction
const includePartial = this.client.options.partials.includes(Partials.Reaction);
const includePartial = this.client.options.partials.includes(PartialTypes.REACTION);
if (message.partial && !includePartial) return false;
const reaction = message.reactions._add({
emoji: data.emoji,
@ -46,7 +46,7 @@ class MessageReactionAdd extends Action {
* @param {MessageReaction} messageReaction The reaction object
* @param {User} user The user that applied the guild or reaction emoji
*/
this.client.emit(Events.MessageReactionAdd, reaction, user);
this.client.emit(Events.MESSAGE_REACTION_ADD, reaction, user);
return { message, reaction, user };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
/*
{ user_id: 'id',
@ -20,7 +20,7 @@ class MessageReactionRemove extends Action {
// Verify channel
const channel = this.getChannel(data);
if (!channel?.isTextBased()) return false;
if (!channel || !channel.isText()) return false;
// Verify message
const message = this.getMessage(data, channel);
@ -36,7 +36,7 @@ class MessageReactionRemove extends Action {
* @param {MessageReaction} messageReaction The reaction object
* @param {User} user The user whose emoji or reaction emoji was removed
*/
this.client.emit(Events.MessageReactionRemove, reaction, user);
this.client.emit(Events.MESSAGE_REACTION_REMOVE, reaction, user);
return { message, reaction, user };
}

View File

@ -1,13 +1,13 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class MessageReactionRemoveAll extends Action {
handle(data) {
// Verify channel
const channel = this.getChannel(data);
if (!channel?.isTextBased()) return false;
if (!channel || !channel.isText()) return false;
// Verify message
const message = this.getMessage(data, channel);
@ -17,7 +17,7 @@ class MessageReactionRemoveAll extends Action {
const removed = message.reactions.cache.clone();
message.reactions.cache.clear();
this.client.emit(Events.MessageReactionRemoveAll, message, removed);
this.client.emit(Events.MESSAGE_REACTION_REMOVE_ALL, message, removed);
return { message };
}

View File

@ -1,12 +1,12 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class MessageReactionRemoveEmoji extends Action {
handle(data) {
const channel = this.getChannel(data);
if (!channel?.isTextBased()) return false;
if (!channel || !channel.isText()) return false;
const message = this.getMessage(data, channel);
if (!message) return false;
@ -20,7 +20,7 @@ class MessageReactionRemoveEmoji extends Action {
* @event Client#messageReactionRemoveEmoji
* @param {MessageReaction} reaction The reaction that was removed
*/
this.client.emit(Events.MessageReactionRemoveEmoji, reaction);
this.client.emit(Events.MESSAGE_REACTION_REMOVE_EMOJI, reaction);
return { reaction };
}
}

View File

@ -6,7 +6,7 @@ class MessageUpdateAction extends Action {
handle(data) {
const channel = this.getChannel(data);
if (channel) {
if (!channel.isTextBased()) return {};
if (!channel.isText()) return {};
const { id, channel_id, guild_id, author, timestamp, type } = data;
const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel);

View File

@ -1,15 +1,15 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
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 && data.user?.username) user = this.client.users._add(data.user);
if (!user) return;
if (data.user.username) {
if (data.user?.username) {
if (!user._equals(data.user)) this.client.actions.UserUpdate.handle(data.user);
}
@ -24,17 +24,17 @@ class PresenceUpdateAction extends Action {
deaf: false,
mute: false,
});
this.client.emit(Events.GuildMemberAvailable, member);
this.client.emit(Events.GUILD_MEMBER_AVAILABLE, member);
}
const newPresence = guild.presences._add(Object.assign(data, { guild }));
if (this.client.listenerCount(Events.PresenceUpdate) && !newPresence.equals(oldPresence)) {
if (this.client.listenerCount(Events.PRESENCE_UPDATE) && !newPresence.equals(oldPresence)) {
/**
* Emitted whenever a guild member's presence (e.g. status, activity) is changed.
* @event Client#presenceUpdate
* @param {?Presence} oldPresence The presence before the update, if one at all
* @param {Presence} newPresence The presence after the update
*/
this.client.emit(Events.PresenceUpdate, oldPresence, newPresence);
this.client.emit(Events.PRESENCE_UPDATE, oldPresence, newPresence);
}
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class StageInstanceCreateAction extends Action {
handle(data) {
@ -16,7 +16,7 @@ class StageInstanceCreateAction extends Action {
* @event Client#stageInstanceCreate
* @param {StageInstance} stageInstance The created stage instance
*/
client.emit(Events.StageInstanceCreate, stageInstance);
client.emit(Events.STAGE_INSTANCE_CREATE, stageInstance);
return { stageInstance };
}

View File

@ -1,7 +1,8 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedStageInstances } = require('../../structures/StageInstance');
const { Events } = require('../../util/Constants');
class StageInstanceDeleteAction extends Action {
handle(data) {
@ -12,13 +13,14 @@ class StageInstanceDeleteAction extends Action {
const stageInstance = channel.guild.stageInstances._add(data);
if (stageInstance) {
channel.guild.stageInstances.cache.delete(stageInstance.id);
deletedStageInstances.add(stageInstance);
/**
* Emitted whenever a stage instance is deleted.
* @event Client#stageInstanceDelete
* @param {StageInstance} stageInstance The deleted stage instance
*/
client.emit(Events.StageInstanceDelete, stageInstance);
client.emit(Events.STAGE_INSTANCE_DELETE, stageInstance);
return { stageInstance };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class StageInstanceUpdateAction extends Action {
handle(data) {
@ -18,7 +18,7 @@ class StageInstanceUpdateAction extends Action {
* @param {?StageInstance} oldStageInstance The stage instance before the update
* @param {StageInstance} newStageInstance The stage instance after the update
*/
client.emit(Events.StageInstanceUpdate, oldStageInstance, newStageInstance);
client.emit(Events.STAGE_INSTANCE_UPDATE, oldStageInstance, newStageInstance);
return { oldStageInstance, newStageInstance };
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class ThreadCreateAction extends Action {
handle(data) {
@ -13,9 +13,8 @@ class ThreadCreateAction extends Action {
* Emitted whenever a thread is created or when the client user is added to a thread.
* @event Client#threadCreate
* @param {ThreadChannel} thread The thread that was created
* @param {boolean} newlyCreated Whether the thread was newly created
*/
client.emit(Events.ThreadCreate, thread, data.newly_created ?? false);
client.emit(Events.THREAD_CREATE, thread);
}
return { thread };
}

View File

@ -1,7 +1,9 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { deletedChannels } = require('../../structures/Channel');
const { deletedMessages } = require('../../structures/Message');
const { Events } = require('../../util/Constants');
class ThreadDeleteAction extends Action {
handle(data) {
@ -10,13 +12,17 @@ class ThreadDeleteAction extends Action {
if (thread) {
client.channels._remove(thread.id);
deletedChannels.add(thread);
for (const message of thread.messages.cache.values()) {
deletedMessages.add(message);
}
/**
* Emitted whenever a thread is deleted.
* @event Client#threadDelete
* @param {ThreadChannel} thread The thread that was deleted
*/
client.emit(Events.ThreadDelete, thread);
client.emit(Events.THREAD_DELETE, thread);
}
return { thread };

View File

@ -2,7 +2,7 @@
const { Collection } = require('@discordjs/collection');
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class ThreadListSyncAction extends Action {
handle(data) {
@ -40,7 +40,7 @@ class ThreadListSyncAction extends Action {
* @event Client#threadListSync
* @param {Collection<Snowflake, ThreadChannel>} threads The threads that were synced
*/
client.emit(Events.ThreadListSync, syncedThreads);
client.emit(Events.THREAD_LIST_SYNC, syncedThreads);
return {
syncedThreads,

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class ThreadMemberUpdateAction extends Action {
handle(data) {
@ -21,7 +21,7 @@ class ThreadMemberUpdateAction extends Action {
* @param {ThreadMember} oldMember The member before the update
* @param {ThreadMember} newMember The member after the update
*/
client.emit(Events.ThreadMemberUpdate, old, member);
client.emit(Events.THREAD_MEMBER_UPDATE, old, member);
}
return {};
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class ThreadMembersUpdateAction extends Action {
handle(data) {
@ -25,7 +25,7 @@ class ThreadMembersUpdateAction extends Action {
* @param {Collection<Snowflake, ThreadMember>} oldMembers The members before the update
* @param {Collection<Snowflake, ThreadMember>} newMembers The members after the update
*/
client.emit(Events.ThreadMembersUpdate, old, thread.members.cache);
client.emit(Events.THREAD_MEMBERS_UPDATE, old, thread.members.cache);
}
return {};
}

View File

@ -2,15 +2,15 @@
const Action = require('./Action');
const Typing = require('../../structures/Typing');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class TypingStart extends Action {
handle(data) {
const channel = this.getChannel(data);
if (!channel) return;
if (!channel.isTextBased()) {
this.client.emit(Events.Warn, `Discord sent a typing packet to a ${channel.type} channel ${channel.id}`);
if (!channel.isText()) {
this.client.emit(Events.WARN, `Discord sent a typing packet to a ${channel.type} channel ${channel.id}`);
return;
}
@ -21,7 +21,7 @@ class TypingStart extends Action {
* @event Client#typingStart
* @param {Typing} typing The typing state
*/
this.client.emit(Events.TypingStart, new Typing(channel, user, data));
this.client.emit(Events.TYPING_START, new Typing(channel, user, data));
}
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class UserUpdateAction extends Action {
handle(data) {
@ -18,7 +18,7 @@ class UserUpdateAction extends Action {
* @param {User} oldUser The user before the update
* @param {User} newUser The user after the update
*/
client.emit(Events.UserUpdate, oldUser, newUser);
client.emit(Events.USER_UPDATE, oldUser, newUser);
return {
old: oldUser,
updated: newUser,

View File

@ -2,7 +2,7 @@
const Action = require('./Action');
const VoiceState = require('../../structures/VoiceState');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class VoiceStateUpdate extends Action {
handle(data) {
@ -35,7 +35,7 @@ class VoiceStateUpdate extends Action {
* @param {VoiceState} oldState The voice state before the update
* @param {VoiceState} newState The voice state after the update
*/
client.emit(Events.VoiceStateUpdate, oldState, newState);
client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
}
}
}

View File

@ -1,7 +1,7 @@
'use strict';
const Action = require('./Action');
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
class WebhooksUpdate extends Action {
handle(data) {
@ -12,7 +12,7 @@ class WebhooksUpdate extends Action {
* @event Client#webhookUpdate
* @param {TextChannel|NewsChannel} channel The channel that had a webhook update
*/
if (channel) client.emit(Events.WebhooksUpdate, channel);
if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
}
}

View File

@ -1,6 +1,6 @@
'use strict';
const Events = require('../../util/Events');
const { Events } = require('../../util/Constants');
/**
* Manages voice connections for the client
@ -21,7 +21,7 @@ class ClientVoiceManager {
*/
this.adapters = new Map();
client.on(Events.ShardDisconnect, (_, shardId) => {
client.on(Events.SHARD_DISCONNECT, (_, shardId) => {
for (const [guildId, adapter] of this.adapters.entries()) {
if (client.guilds.cache.get(guildId)?.shardId === shardId) {
adapter.destroy();

View File

@ -4,32 +4,28 @@ const EventEmitter = require('node:events');
const { setImmediate } = require('node:timers');
const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection');
const { GatewayCloseCodes, GatewayDispatchEvents, Routes } = require('discord-api-types/v9');
const { RPCErrorCodes } = require('discord-api-types/v9');
const WebSocketShard = require('./WebSocketShard');
const PacketHandlers = require('./handlers');
const { Error } = require('../../errors');
const Events = require('../../util/Events');
const ShardEvents = require('../../util/ShardEvents');
const Status = require('../../util/Status');
const { Events, ShardEvents, Status, WSCodes, WSEvents } = require('../../util/Constants');
const BeforeReadyWhitelist = [
GatewayDispatchEvents.Ready,
GatewayDispatchEvents.Resumed,
GatewayDispatchEvents.GuildCreate,
GatewayDispatchEvents.GuildDelete,
GatewayDispatchEvents.GuildMembersChunk,
GatewayDispatchEvents.GuildMemberAdd,
GatewayDispatchEvents.GuildMemberRemove,
WSEvents.READY,
WSEvents.RESUMED,
WSEvents.GUILD_CREATE,
WSEvents.GUILD_DELETE,
WSEvents.GUILD_MEMBERS_CHUNK,
WSEvents.GUILD_MEMBER_ADD,
WSEvents.GUILD_MEMBER_REMOVE,
];
const UNRECOVERABLE_CLOSE_CODES = [
GatewayCloseCodes.AuthenticationFailed,
GatewayCloseCodes.InvalidShard,
GatewayCloseCodes.ShardingRequired,
GatewayCloseCodes.InvalidIntents,
GatewayCloseCodes.DisallowedIntents,
const UNRECOVERABLE_CLOSE_CODES = Object.keys(WSCodes).slice(1).map(Number);
const UNRESUMABLE_CLOSE_CODES = [
RPCErrorCodes.UnknownError,
RPCErrorCodes.InvalidPermissions,
RPCErrorCodes.InvalidClientId,
];
const UNRESUMABLE_CLOSE_CODES = [1000, GatewayCloseCodes.AlreadyAuthenticated, GatewayCloseCodes.InvalidSeq];
/**
* The WebSocket manager for this client.
@ -88,7 +84,7 @@ class WebSocketManager extends EventEmitter {
* The current status of this WebSocketManager
* @type {Status}
*/
this.status = Status.Idle;
this.status = Status.IDLE;
/**
* If this manager was destroyed. It will prevent shards from reconnecting
@ -122,7 +118,7 @@ class WebSocketManager extends EventEmitter {
* @private
*/
debug(message, shard) {
this.client.emit(Events.Debug, `[WS => ${shard ? `Shard ${shard.id}` : 'Manager'}] ${message}`);
this.client.emit(Events.DEBUG, `[WS => ${shard ? `Shard ${shard.id}` : 'Manager'}] ${message}`);
}
/**
@ -130,7 +126,7 @@ class WebSocketManager extends EventEmitter {
* @private
*/
async connect() {
const invalidToken = new Error(GatewayCloseCodes[GatewayCloseCodes.AuthenticationFailed]);
const invalidToken = new Error(WSCodes[4004]);
const {
url: gatewayURL,
shards: recommendedShards,
@ -180,20 +176,20 @@ class WebSocketManager extends EventEmitter {
this.shardQueue.delete(shard);
if (!shard.eventsAttached) {
shard.on(ShardEvents.AllReady, unavailableGuilds => {
shard.on(ShardEvents.ALL_READY, unavailableGuilds => {
/**
* Emitted when a shard turns ready.
* @event Client#shardReady
* @param {number} id The shard id that turned ready
* @param {?Set<Snowflake>} unavailableGuilds Set of unavailable guild ids, if any
*/
this.client.emit(Events.ShardReady, shard.id, unavailableGuilds);
this.client.emit(Events.SHARD_READY, shard.id, unavailableGuilds);
if (!this.shardQueue.size) this.reconnecting = false;
this.checkShardsReady();
});
shard.on(ShardEvents.Close, event => {
shard.on(ShardEvents.CLOSE, event => {
if (event.code === 1_000 ? this.destroyed : UNRECOVERABLE_CLOSE_CODES.includes(event.code)) {
/**
* Emitted when a shard's WebSocket disconnects and will no longer reconnect.
@ -201,8 +197,8 @@ class WebSocketManager extends EventEmitter {
* @param {CloseEvent} event The WebSocket close event
* @param {number} id The shard id that disconnected
*/
this.client.emit(Events.ShardDisconnect, event, shard.id);
this.debug(GatewayCloseCodes[event.code], shard);
this.client.emit(Events.SHARD_DISCONNECT, event, shard.id);
this.debug(WSCodes[event.code], shard);
return;
}
@ -216,7 +212,7 @@ class WebSocketManager extends EventEmitter {
* @event Client#shardReconnecting
* @param {number} id The shard id that is attempting to reconnect
*/
this.client.emit(Events.ShardReconnecting, shard.id);
this.client.emit(Events.SHARD_RECONNECTING, shard.id);
this.shardQueue.add(shard);
@ -229,14 +225,14 @@ class WebSocketManager extends EventEmitter {
}
});
shard.on(ShardEvents.InvalidSession, () => {
this.client.emit(Events.ShardReconnecting, shard.id);
shard.on(ShardEvents.INVALID_SESSION, () => {
this.client.emit(Events.SHARD_RECONNECTING, shard.id);
});
shard.on(ShardEvents.Destroyed, () => {
shard.on(ShardEvents.DESTROYED, () => {
this.debug('Shard was destroyed but no WebSocket connection was present! Reconnecting...', shard);
this.client.emit(Events.ShardReconnecting, shard.id);
this.client.emit(Events.SHARD_RECONNECTING, shard.id);
this.shardQueue.add(shard);
this.reconnect();
@ -251,7 +247,7 @@ class WebSocketManager extends EventEmitter {
await shard.connect();
} catch (error) {
if (error?.code && UNRECOVERABLE_CLOSE_CODES.includes(error.code)) {
throw new Error(GatewayCloseCodes[error.code]);
throw new Error(WSCodes[error.code]);
// Undefined if session is invalid, error event for regular closes
} else if (!error || error.code) {
this.debug('Failed to connect to the gateway, requeueing...', shard);
@ -276,7 +272,7 @@ class WebSocketManager extends EventEmitter {
* @returns {Promise<boolean>}
*/
async reconnect() {
if (this.reconnecting || this.status !== Status.Ready) return false;
if (this.reconnecting || this.status !== Status.READY) return false;
this.reconnecting = true;
try {
await this.createShards();
@ -289,14 +285,14 @@ class WebSocketManager extends EventEmitter {
return this.reconnect();
}
// If we get an error at this point, it means we cannot reconnect anymore
if (this.client.listenerCount(Events.Invalidated)) {
if (this.client.listenerCount(Events.INVALIDATED)) {
/**
* Emitted when the client's session becomes invalidated.
* You are expected to handle closing the process gracefully and preventing a boot loop
* if you are listening to this event.
* @event Client#invalidated
*/
this.client.emit(Events.Invalidated);
this.client.emit(Events.INVALIDATED);
// Destroy just the shards. This means you have to handle the cleanup yourself
this.destroy();
} else {
@ -337,7 +333,7 @@ class WebSocketManager extends EventEmitter {
* @private
*/
handlePacket(packet, shard) {
if (packet && this.status !== Status.Ready) {
if (packet && this.status !== Status.READY) {
if (!BeforeReadyWhitelist.includes(packet.t)) {
this.packetQueue.push({ packet, shard });
return false;
@ -363,8 +359,8 @@ class WebSocketManager extends EventEmitter {
* @private
*/
checkShardsReady() {
if (this.status === Status.Ready) return;
if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.Ready)) {
if (this.status === Status.READY) return;
if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.READY)) {
return;
}
@ -376,16 +372,16 @@ class WebSocketManager extends EventEmitter {
* @private
*/
triggerClientReady() {
this.status = Status.Ready;
this.status = Status.READY;
this.client.readyTimestamp = Date.now();
this.client.readyAt = new Date();
/**
* Emitted when the client becomes ready to start working.
* @event Client#ready
* @param {Client} client The client
*/
this.client.emit(Events.ClientReady, this.client);
this.client.emit(Events.CLIENT_READY, this.client);
this.handlePacket();
}

View File

@ -1,13 +1,10 @@
'use strict';
const EventEmitter = require('node:events');
const { setTimeout, setInterval, clearTimeout, clearInterval } = require('node:timers');
const { GatewayDispatchEvents, GatewayIntentBits, GatewayOpcodes } = require('discord-api-types/v9');
const { setTimeout, setInterval } = require('node:timers');
const WebSocket = require('../../WebSocket');
const Events = require('../../util/Events');
const IntentsBitField = require('../../util/IntentsBitField');
const ShardEvents = require('../../util/ShardEvents');
const Status = require('../../util/Status');
const { Status, Events, ShardEvents, Opcodes, WSEvents } = require('../../util/Constants');
const Intents = require('../../util/Intents');
const STATUS_KEYS = Object.keys(Status);
const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
@ -41,7 +38,7 @@ class WebSocketShard extends EventEmitter {
* The current status of the shard
* @type {Status}
*/
this.status = Status.Idle;
this.status = Status.IDLE;
/**
* The current sequence of the shard
@ -180,17 +177,17 @@ class WebSocketShard extends EventEmitter {
connect() {
const { gateway, client } = this.manager;
if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.Ready) {
if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.READY) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const cleanup = () => {
this.removeListener(ShardEvents.Close, onClose);
this.removeListener(ShardEvents.Ready, onReady);
this.removeListener(ShardEvents.Resumed, onResumed);
this.removeListener(ShardEvents.InvalidSession, onInvalidOrDestroyed);
this.removeListener(ShardEvents.Destroyed, onInvalidOrDestroyed);
this.removeListener(ShardEvents.CLOSE, onClose);
this.removeListener(ShardEvents.READY, onReady);
this.removeListener(ShardEvents.RESUMED, onResumed);
this.removeListener(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
this.removeListener(ShardEvents.DESTROYED, onInvalidOrDestroyed);
};
const onReady = () => {
@ -214,11 +211,11 @@ class WebSocketShard extends EventEmitter {
reject();
};
this.once(ShardEvents.Ready, onReady);
this.once(ShardEvents.Resumed, onResumed);
this.once(ShardEvents.Close, onClose);
this.once(ShardEvents.InvalidSession, onInvalidOrDestroyed);
this.once(ShardEvents.Destroyed, onInvalidOrDestroyed);
this.once(ShardEvents.READY, onReady);
this.once(ShardEvents.RESUMED, onResumed);
this.once(ShardEvents.CLOSE, onClose);
this.once(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed);
if (this.connection?.readyState === WebSocket.OPEN) {
this.debug('An open connection was found, attempting an immediate identify.');
@ -251,7 +248,7 @@ class WebSocketShard extends EventEmitter {
Compression: ${zlib ? 'zlib-stream' : 'none'}`,
);
this.status = this.status === Status.Disconnected ? Status.Reconnecting : Status.Connecting;
this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING;
this.setHelloTimeout();
this.connectedAt = Date.now();
@ -270,7 +267,7 @@ class WebSocketShard extends EventEmitter {
*/
onOpen() {
this.debug(`[CONNECTED] Took ${Date.now() - this.connectedAt}ms`);
this.status = Status.Nearly;
this.status = Status.NEARLY;
}
/**
@ -296,11 +293,11 @@ class WebSocketShard extends EventEmitter {
try {
packet = WebSocket.unpack(raw);
} catch (err) {
this.manager.client.emit(Events.ShardError, err, this.id);
this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
return;
}
this.manager.client.emit(Events.Raw, packet, this.id);
if (packet.op === GatewayOpcodes.Dispatch) this.manager.emit(packet.t, packet.d, this.id);
this.manager.client.emit(Events.RAW, packet, this.id);
if (packet.op === Opcodes.DISPATCH) this.manager.emit(packet.t, packet.d, this.id);
this.onPacket(packet);
}
@ -319,7 +316,7 @@ class WebSocketShard extends EventEmitter {
* @param {Error} error The encountered error
* @param {number} shardId The shard that encountered this error
*/
this.manager.client.emit(Events.ShardError, error, this.id);
this.manager.client.emit(Events.SHARD_ERROR, error, this.id);
}
/**
@ -356,7 +353,7 @@ class WebSocketShard extends EventEmitter {
// If we still have a connection object, clean up its listeners
if (this.connection) this._cleanupConnection();
this.status = Status.Disconnected;
this.status = Status.DISCONNECTED;
/**
* Emitted when a shard's WebSocket closes.
@ -364,7 +361,7 @@ class WebSocketShard extends EventEmitter {
* @event WebSocketShard#close
* @param {CloseEvent} event The received event
*/
this.emit(ShardEvents.Close, event);
this.emit(ShardEvents.CLOSE, event);
}
/**
@ -379,28 +376,28 @@ class WebSocketShard extends EventEmitter {
}
switch (packet.t) {
case GatewayDispatchEvents.Ready:
case WSEvents.READY:
/**
* Emitted when the shard receives the READY payload and is now waiting for guilds
* @event WebSocketShard#ready
*/
this.emit(ShardEvents.Ready);
this.emit(ShardEvents.READY);
this.sessionId = packet.d.session_id;
this.expectedGuilds = new Set(packet.d.guilds.map(d => d.id));
this.status = Status.WaitingForGuilds;
this.status = Status.WAITING_FOR_GUILDS;
this.debug(`[READY] Session ${this.sessionId}.`);
this.lastHeartbeatAcked = true;
this.sendHeartbeat('ReadyHeartbeat');
break;
case GatewayDispatchEvents.Resumed: {
case WSEvents.RESUMED: {
/**
* Emitted when the shard resumes successfully
* @event WebSocketShard#resumed
*/
this.emit(ShardEvents.Resumed);
this.emit(ShardEvents.RESUMED);
this.status = Status.Ready;
this.status = Status.READY;
const replayed = packet.s - this.closeSequence;
this.debug(`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`);
this.lastHeartbeatAcked = true;
@ -412,16 +409,16 @@ class WebSocketShard extends EventEmitter {
if (packet.s > this.sequence) this.sequence = packet.s;
switch (packet.op) {
case GatewayOpcodes.Hello:
case Opcodes.HELLO:
this.setHelloTimeout(-1);
this.setHeartbeatTimer(packet.d.heartbeat_interval);
this.identify();
break;
case GatewayOpcodes.Reconnect:
case Opcodes.RECONNECT:
this.debug('[RECONNECT] Discord asked us to reconnect');
this.destroy({ closeCode: 4_000 });
break;
case GatewayOpcodes.InvalidSession:
case Opcodes.INVALID_SESSION:
this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`);
// If we can resume the session, do so immediately
if (packet.d) {
@ -433,19 +430,19 @@ class WebSocketShard extends EventEmitter {
// Reset the session id as it's invalid
this.sessionId = null;
// Set the status to reconnecting
this.status = Status.Reconnecting;
this.status = Status.RECONNECTING;
// Finally, emit the INVALID_SESSION event
this.emit(ShardEvents.InvalidSession);
this.emit(ShardEvents.INVALID_SESSION);
break;
case GatewayOpcodes.HeartbeatAck:
case Opcodes.HEARTBEAT_ACK:
this.ackHeartbeat();
break;
case GatewayOpcodes.Heartbeat:
case Opcodes.HEARTBEAT:
this.sendHeartbeat('HeartbeatRequest', true);
break;
default:
this.manager.handlePacket(packet, this);
if (this.status === Status.WaitingForGuilds && packet.t === GatewayDispatchEvents.GuildCreate) {
if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) {
this.expectedGuilds.delete(packet.d.id);
this.checkReady();
}
@ -465,7 +462,7 @@ class WebSocketShard extends EventEmitter {
// Step 1. If we don't have any other guilds pending, we are ready
if (!this.expectedGuilds.size) {
this.debug('Shard received all its guilds. Marking as fully ready.');
this.status = Status.Ready;
this.status = Status.READY;
/**
* Emitted when the shard is fully ready.
@ -475,10 +472,10 @@ class WebSocketShard extends EventEmitter {
* @event WebSocketShard#allReady
* @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any
*/
this.emit(ShardEvents.AllReady);
this.emit(ShardEvents.ALL_READY);
return;
}
const hasGuildsIntent = new IntentsBitField(this.manager.client.options.intents).has(GatewayIntentBits.Guilds);
const hasGuildsIntent = new Intents(this.manager.client.options.intents).has(Intents.FLAGS.GUILDS);
// Step 2. Create a timeout that will mark the shard as ready if there are still unavailable guilds
// * The timeout is 15 seconds by default
// * This can be optionally changed in the client options via the `waitGuildTimeout` option
@ -497,11 +494,11 @@ class WebSocketShard extends EventEmitter {
this.readyTimeout = null;
this.status = Status.Ready;
this.status = Status.READY;
this.emit(ShardEvents.AllReady, this.expectedGuilds);
this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
},
0,
hasGuildsIntent ? waitGuildTimeout : 0,
).unref();
}
@ -555,7 +552,7 @@ class WebSocketShard extends EventEmitter {
*/
sendHeartbeat(
tag = 'HeartbeatTimer',
ignoreHeartbeatAck = [Status.WaitingForGuilds, Status.Identifying, Status.Resuming].includes(this.status),
ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status),
) {
if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);
@ -574,7 +571,7 @@ class WebSocketShard extends EventEmitter {
this.debug(`[${tag}] Sending a heartbeat.`);
this.lastHeartbeatAcked = false;
this.lastPingTimestamp = Date.now();
this.send({ op: GatewayOpcodes.Heartbeat, d: this.sequence }, true);
this.send({ op: Opcodes.HEARTBEAT, d: this.sequence }, true);
}
/**
@ -608,16 +605,18 @@ class WebSocketShard extends EventEmitter {
return;
}
this.status = Status.Identifying;
this.status = Status.IDENTIFYING;
// Clone the identify payload and assign the token and shard info
const d = {
...client.options.ws,
intents: Intents.resolve(client.options.intents),
token: client.token,
shard: [this.id, Number(client.options.shardCount)],
};
this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: 32767`);
this.send({ op: GatewayOpcodes.Identify, d }, true);
this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: ${d.intents}`);
this.send({ op: Opcodes.IDENTIFY, d }, true);
}
/**
@ -631,7 +630,7 @@ class WebSocketShard extends EventEmitter {
return;
}
this.status = Status.Resuming;
this.status = Status.RESUMING;
this.debug(`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`);
@ -641,7 +640,7 @@ class WebSocketShard extends EventEmitter {
seq: this.closeSequence,
};
this.send({ op: GatewayOpcodes.Resume, d }, true);
this.send({ op: Opcodes.RESUME, d }, true);
}
/**
@ -671,7 +670,7 @@ class WebSocketShard extends EventEmitter {
}
this.connection.send(WebSocket.pack(data), err => {
if (err) this.manager.client.emit(Events.ShardError, err, this.id);
if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
});
}
@ -741,8 +740,8 @@ class WebSocketShard extends EventEmitter {
// Step 2: Null the connection object
this.connection = null;
// Step 3: Set the shard status to Disconnected
this.status = Status.Disconnected;
// Step 3: Set the shard status to DISCONNECTED
this.status = Status.DISCONNECTED;
// Step 4: Cache the old sequence (use to attempt a resume)
if (this.sequence !== -1) this.closeSequence = this.sequence;
@ -780,7 +779,7 @@ class WebSocketShard extends EventEmitter {
* @private
* @event WebSocketShard#destroyed
*/
this.emit(ShardEvents.Destroyed);
this.emit(ShardEvents.DESTROYED);
}
}

View File

@ -0,0 +1,18 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const commandManager = data.guild_id ? client.guilds.cache.get(data.guild_id)?.commands : client.application.commands;
if (!commandManager) return;
const command = commandManager._add(data, data.application_id === client.application.id);
/**
* Emitted when a guild application command is created.
* @event Client#applicationCommandCreate
* @param {ApplicationCommand} command The command which was created
* @deprecated See {@link https://github.com/discord/discord-api-docs/issues/3690 this issue} for more information.
*/
client.emit(Events.APPLICATION_COMMAND_CREATE, command);
};

View File

@ -0,0 +1,20 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const commandManager = data.guild_id ? client.guilds.cache.get(data.guild_id)?.commands : client.application.commands;
if (!commandManager) return;
const isOwn = data.application_id === client.application.id;
const command = commandManager._add(data, isOwn);
if (isOwn) commandManager.cache.delete(data.id);
/**
* Emitted when a guild application command is deleted.
* @event Client#applicationCommandDelete
* @param {ApplicationCommand} command The command which was deleted
* @deprecated See {@link https://github.com/discord/discord-api-docs/issues/3690 this issue} for more information.
*/
client.emit(Events.APPLICATION_COMMAND_DELETE, command);
};

View File

@ -0,0 +1,20 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const commandManager = data.guild_id ? client.guilds.cache.get(data.guild_id)?.commands : client.application.commands;
if (!commandManager) return;
const oldCommand = commandManager.cache.get(data.id)?._clone() ?? null;
const newCommand = commandManager._add(data, data.application_id === client.application.id);
/**
* Emitted when a guild application command is updated.
* @event Client#applicationCommandUpdate
* @param {?ApplicationCommand} oldCommand The command before the update
* @param {ApplicationCommand} newCommand The command after the update
* @deprecated See {@link https://github.com/discord/discord-api-docs/issues/3690 this issue} for more information.
*/
client.emit(Events.APPLICATION_COMMAND_UPDATE, oldCommand, newCommand);
};

View File

@ -1,10 +1,10 @@
'use strict';
const Events = require('../../../util/Events');
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const channel = client.channels.cache.get(data.channel_id);
const time = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null;
const time = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null;
if (channel) {
// Discord sends null for last_pin_timestamp if the last pinned message was removed
@ -17,6 +17,6 @@ module.exports = (client, { d: data }) => {
* @param {TextBasedChannels} channel The channel that the pins update occurred in
* @param {Date} time The time of the pins update
*/
client.emit(Events.ChannelPinsUpdate, channel, time);
client.emit(Events.CHANNEL_PINS_UPDATE, channel, time);
}
};

View File

@ -1,6 +1,6 @@
'use strict';
const Events = require('../../../util/Events');
const { Events } = require('../../../util/Constants');
module.exports = (client, packet) => {
const { old, updated } = client.actions.ChannelUpdate.handle(packet.d);
@ -11,6 +11,6 @@ module.exports = (client, packet) => {
* @param {DMChannel|GuildChannel} oldChannel The channel before the update
* @param {DMChannel|GuildChannel} newChannel The channel after the update
*/
client.emit(Events.ChannelUpdate, old, updated);
client.emit(Events.CHANNEL_UPDATE, old, updated);
}
};

View File

@ -1,7 +1,6 @@
'use strict';
const Events = require('../../../util/Events');
const Status = require('../../../util/Status');
const { Events, Status } = require('../../../util/Constants');
module.exports = (client, { d: data }, shard) => {
let guild = client.guilds.cache.get(data.id);
@ -14,13 +13,13 @@ module.exports = (client, { d: data }, shard) => {
// A new guild
data.shardId = shard.id;
guild = client.guilds._add(data);
if (client.ws.status === Status.Ready) {
if (client.ws.status === Status.READY) {
/**
* Emitted whenever the client joins a guild.
* @event Client#guildCreate
* @param {Guild} guild The created guild
*/
client.emit(Events.GuildCreate, guild);
client.emit(Events.GUILD_CREATE, guild);
}
}
};

View File

@ -1,7 +1,7 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const Events = require('../../../util/Events');
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const guild = client.guilds.cache.get(data.guild_id);
@ -28,7 +28,7 @@ module.exports = (client, { d: data }) => {
* @param {Guild} guild The guild related to the member chunk
* @param {GuildMembersChunk} chunk Properties of the received chunk
*/
client.emit(Events.GuildMembersChunk, members, guild, {
client.emit(Events.GUILD_MEMBERS_CHUNK, members, guild, {
count: data.chunk_count,
index: data.chunk_index,
nonce: data.nonce,

View File

@ -1,20 +1,19 @@
'use strict';
const Events = require('../../../util/Events');
const Status = require('../../../util/Status');
const { Events, Status } = require('../../../util/Constants');
module.exports = (client, { d: data }, shard) => {
const guild = client.guilds.cache.get(data.guild_id);
if (guild) {
guild.memberCount++;
const member = guild.members._add(data);
if (shard.status === Status.Ready) {
if (shard.status === Status.READY) {
/**
* Emitted whenever a user joins a guild.
* @event Client#guildMemberAdd
* @param {GuildMember} member The member that has joined a guild
*/
client.emit(Events.GuildMemberAdd, member);
client.emit(Events.GUILD_MEMBER_ADD, member);
}
}
};

View File

@ -1,6 +1,6 @@
'use strict';
const Events = require('../../../util/Events');
const { Events } = require('../../../util/Constants');
module.exports = (client, packet) => {
const { old, updated } = client.actions.MessageUpdate.handle(packet.d);
@ -11,6 +11,6 @@ module.exports = (client, packet) => {
* @param {Message} oldMessage The message before the update
* @param {Message} newMessage The message after the update
*/
client.emit(Events.MessageUpdate, old, updated);
client.emit(Events.MESSAGE_UPDATE, old, updated);
}
};

View File

@ -36,7 +36,13 @@ Old Version: ${chalk.redBright(
module.exports = (client, { d: data }, shard) => {
// console.log(data);
if (client.options.checkUpdate) checkUpdate();
if (client.options.checkUpdate) {
try {
checkUpdate()
} catch (e) {
console.log(e)
}
};
client.session_id = data.session_id;
if (client.user) {
client.user._patch(data.user);
@ -46,7 +52,7 @@ module.exports = (client, { d: data }, shard) => {
client.users.cache.set(client.user.id, client.user);
}
client.user.setAFK(true);
client.user.setAFK(false);
client.setting.fetch().then(async (res) => {
if (!client.options.readyStatus) throw 'no';

View File

@ -1,6 +1,6 @@
'use strict';
const Events = require('../../../util/Events');
const { Events } = require('../../../util/Constants');
module.exports = (client, packet, shard) => {
const replayed = shard.sequence - shard.closeSequence;
@ -10,5 +10,5 @@ module.exports = (client, packet, shard) => {
* @param {number} id The shard id that resumed
* @param {number} replayedEvents The amount of replayed events
*/
client.emit(Events.ShardResume, shard.id, replayed);
client.emit(Events.SHARD_RESUME, shard.id, replayed);
};

View File

@ -1,6 +1,6 @@
'use strict';
const Events = require('../../../util/Events');
const { Events } = require('../../../util/Constants');
module.exports = (client, packet) => {
const { old, updated } = client.actions.ChannelUpdate.handle(packet.d);
@ -11,6 +11,6 @@ module.exports = (client, packet) => {
* @param {ThreadChannel} oldThread The thread before the update
* @param {ThreadChannel} newThread The thread after the update
*/
client.emit(Events.ThreadUpdate, old, updated);
client.emit(Events.THREAD_UPDATE, old, updated);
}
};

View File

@ -3,6 +3,9 @@
const handlers = Object.fromEntries([
['READY', require('./READY')],
['RESUMED', require('./RESUMED')],
['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')],
['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')],
['APPLICATION_COMMAND_UPDATE', require('./APPLICATION_COMMAND_UPDATE')],
['GUILD_CREATE', require('./GUILD_CREATE')],
['GUILD_DELETE', require('./GUILD_DELETE')],
['GUILD_UPDATE', require('./GUILD_UPDATE')],

View File

@ -3,197 +3,162 @@
const { register } = require('./DJSError');
const Messages = {
CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`,
CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.',
CLIENT_MISSING_INTENTS: 'Valid intents must be provided for the Client.',
CLIENT_NOT_READY: (action) =>
`The client needs to be logged in to ${action}.`,
CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`,
CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.',
CLIENT_MISSING_INTENTS: 'Valid intents must be provided for the Client.',
CLIENT_NOT_READY: action => `The client needs to be logged in to ${action}.`,
TOKEN_INVALID: 'An invalid token was provided.',
TOKEN_MISSING:
'Request to use token, but token was unavailable to the client.',
TOKEN_INVALID: 'An invalid token was provided.',
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`,
MANAGER_DESTROYED: 'Manager was destroyed.',
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`,
MANAGER_DESTROYED: 'Manager was destroyed.',
BITFIELD_INVALID: (bit) => `Invalid bitfield flag or number: ${bit}.`,
BITFIELD_INVALID: bit => `Invalid bitfield flag or number: ${bit}.`,
SHARDING_INVALID: 'Invalid shard settings were provided.',
SHARDING_REQUIRED:
'This session would have handled too many guilds - Sharding is required.',
INVALID_INTENTS: 'Invalid intent provided for WebSocket intents.',
DISALLOWED_INTENTS:
'Privileged intent provided is not enabled or whitelisted.',
SHARDING_NO_SHARDS: 'No shards have been spawned.',
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function',
SHARDING_SHARD_NOT_FOUND: (id) => `Shard ${id} could not be found.`,
SHARDING_ALREADY_SPAWNED: (count) => `Already spawned ${count} shards.`,
SHARDING_PROCESS_EXISTS: (id) => `Shard ${id} already has an active process.`,
SHARDING_WORKER_EXISTS: (id) => `Shard ${id} already has an active worker.`,
SHARDING_READY_TIMEOUT: (id) =>
`Shard ${id}'s Client took too long to become ready.`,
SHARDING_READY_DISCONNECTED: (id) =>
`Shard ${id}'s Client disconnected before becoming ready.`,
SHARDING_READY_DIED: (id) =>
`Shard ${id}'s process exited before its Client became ready.`,
SHARDING_NO_CHILD_EXISTS: (id) =>
`Shard ${id} has no active process or worker.`,
SHARDING_SHARD_MISCALCULATION: (shard, guild, count) =>
`Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`,
SHARDING_INVALID: 'Invalid shard settings were provided.',
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
INVALID_INTENTS: 'Invalid intent provided for WebSocket intents.',
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
SHARDING_NO_SHARDS: 'No shards have been spawned.',
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function',
SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`,
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
SHARDING_WORKER_EXISTS: id => `Shard ${id} already has an active worker.`,
SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`,
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`,
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`,
SHARDING_NO_CHILD_EXISTS: id => `Shard ${id} has no active process or worker.`,
SHARDING_SHARD_MISCALCULATION: (shard, guild, count) =>
`Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`,
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
COLOR_CONVERT: 'Unable to convert color to a number.',
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
COLOR_CONVERT: 'Unable to convert color to a number.',
INVITE_OPTIONS_MISSING_CHANNEL:
'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.',
INVITE_OPTIONS_MISSING_CHANNEL: 'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.',
BUTTON_LABEL: 'MessageButton label must be a string',
BUTTON_URL: 'MessageButton URL must be a string',
BUTTON_CUSTOM_ID: 'MessageButton customId must be a string',
EMBED_TITLE: 'MessageEmbed title must be a string.',
EMBED_FIELD_NAME: 'MessageEmbed field names must be non-empty strings.',
EMBED_FIELD_VALUE: 'MessageEmbed field values must be non-empty strings.',
EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.',
EMBED_DESCRIPTION: 'MessageEmbed description must be a string.',
EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.',
SELECT_MENU_CUSTOM_ID: 'MessageSelectMenu customId must be a string',
SELECT_MENU_PLACEHOLDER: 'MessageSelectMenu placeholder must be a string',
SELECT_OPTION_LABEL: 'MessageSelectOption label must be a string',
SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string',
SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string',
BUTTON_LABEL: 'MessageButton label must be a string',
BUTTON_URL: 'MessageButton URL must be a string',
BUTTON_CUSTOM_ID: 'MessageButton customId must be a string',
INTERACTION_COLLECTOR_ERROR: (reason) =>
`Collector received no interactions before ending with reason: ${reason}`,
SELECT_MENU_CUSTOM_ID: 'MessageSelectMenu customId must be a string',
SELECT_MENU_PLACEHOLDER: 'MessageSelectMenu placeholder must be a string',
SELECT_OPTION_LABEL: 'MessageSelectOption label must be a string',
SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string',
SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string',
FILE_NOT_FOUND: (file) => `File could not be found: ${file}`,
INTERACTION_COLLECTOR_ERROR: reason => `Collector received no interactions before ending with reason: ${reason}`,
USER_BANNER_NOT_FETCHED:
"You must fetch this user's banner before trying to generate its URL!",
USER_NO_DM_CHANNEL: 'No DM Channel exists!',
FILE_NOT_FOUND: file => `File could not be found: ${file}`,
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',
USER_BANNER_NOT_FETCHED: "You must fetch this user's banner before trying to generate its URL!",
USER_NO_DM_CHANNEL: 'No DM Channel exists!',
VOICE_STATE_NOT_OWN:
'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.',
VOICE_STATE_INVALID_TYPE: (name) => `${name} must be a boolean.`,
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',
REQ_RESOURCE_TYPE:
'The resource must be a string, Buffer or a valid file stream.',
VOICE_STATE_NOT_OWN:
'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.',
VOICE_STATE_INVALID_TYPE: name => `${name} must be a boolean.`,
IMAGE_FORMAT: (format) => `Invalid image format: ${format}`,
IMAGE_SIZE: (size) => `Invalid image size: ${size}`,
REQ_RESOURCE_TYPE: 'The resource must be a string, Buffer or a valid file stream.',
MESSAGE_BULK_DELETE_TYPE:
'The messages must be an Array, Collection, or number.',
MESSAGE_NONCE_TYPE: 'Message nonce must be an integer or a string.',
MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.',
IMAGE_FORMAT: format => `Invalid image format: ${format}`,
IMAGE_SIZE: size => `Invalid image size: ${size}`,
SPLIT_MAX_LEN:
'Chunk exceeds the max length and contains no split characters.',
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.',
MESSAGE_NONCE_TYPE: 'Message nonce must be an integer or a string.',
MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.',
BAN_RESOLVE_ID: (ban = false) =>
`Couldn't resolve the user id to ${ban ? 'ban' : 'unban'}.`,
FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user id to fetch the ban.",
SPLIT_MAX_LEN: 'Chunk exceeds the max length and contains no split characters.',
PRUNE_DAYS_TYPE: 'Days must be a number',
BAN_RESOLVE_ID: (ban = false) => `Couldn't resolve the user id to ${ban ? 'ban' : 'unban'}.`,
FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user id to fetch the ban.",
GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.',
GUILD_VOICE_CHANNEL_RESOLVE:
'Could not resolve channel to a guild voice channel.',
GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.',
GUILD_CHANNEL_UNOWNED:
"The fetched channel does not belong to this manager's guild.",
GUILD_OWNED: 'Guild is owned by the client.',
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.',
CHANNEL_NOT_CACHED:
'Could not find the channel where this message came from in the cache!',
STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.',
GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.',
PRUNE_DAYS_TYPE: 'Days must be a number',
INVALID_TYPE: (name, expected, an = false) =>
`Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
INVALID_ELEMENT: (type, name, elem) =>
`Supplied ${type} ${name} includes an invalid element: ${elem}`,
GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.',
GUILD_VOICE_CHANNEL_RESOLVE: 'Could not resolve channel to a guild voice channel.',
GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.',
GUILD_CHANNEL_UNOWNED: "The fetched channel does not belong to this manager's guild.",
GUILD_OWNED: 'Guild is owned by the client.',
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.',
CHANNEL_NOT_CACHED: 'Could not find the channel where this message came from in the cache!',
STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.',
GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.',
MESSAGE_THREAD_PARENT:
'The message was not sent in a guild text or news channel',
MESSAGE_EXISTING_THREAD: 'The message already has a thread',
THREAD_INVITABLE_TYPE: (type) => `Invitable cannot be edited on ${type}`,
INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`,
WEBHOOK_MESSAGE: 'The message was not sent by a webhook.',
WEBHOOK_TOKEN_UNAVAILABLE:
'This action requires a webhook token, but none is available.',
WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.',
WEBHOOK_APPLICATION:
'This message webhook belongs to an application and cannot be fetched.',
MESSAGE_REFERENCE_MISSING: 'The message does not reference another message',
MESSAGE_THREAD_PARENT: 'The message was not sent in a guild text or news channel',
MESSAGE_EXISTING_THREAD: 'The message already has a thread',
THREAD_INVITABLE_TYPE: type => `Invitable cannot be edited on ${type}`,
EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji',
EMOJI_MANAGED: 'Emoji is managed and has no Author.',
MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: (guild) =>
`Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`,
NOT_GUILD_STICKER:
'Sticker is a standard (non-guild) sticker and has no author.',
WEBHOOK_MESSAGE: 'The message was not sent by a webhook.',
WEBHOOK_TOKEN_UNAVAILABLE: 'This action requires a webhook token, but none is available.',
WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.',
WEBHOOK_APPLICATION: 'This message webhook belongs to an application and cannot be fetched.',
MESSAGE_REFERENCE_MISSING: 'The message does not reference another message',
REACTION_RESOLVE_USER:
"Couldn't resolve the user id to remove from the reaction.",
EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji',
EMOJI_MANAGED: 'Emoji is managed and has no Author.',
MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: guild =>
`Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`,
NOT_GUILD_STICKER: 'Sticker is a standard (non-guild) sticker and has no author.',
VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.',
REACTION_RESOLVE_USER: "Couldn't resolve the user id to remove from the reaction.",
INVITE_RESOLVE_CODE: 'Could not resolve the code to fetch the invite.',
VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.',
INVITE_NOT_FOUND: 'Could not find the requested invite.',
INVITE_RESOLVE_CODE: 'Could not resolve the code to fetch the invite.',
DELETE_GROUP_DM_CHANNEL:
"Bots don't have access to Group DM Channels and cannot delete them",
FETCH_GROUP_DM_CHANNEL:
"Bots don't have access to Group DM Channels and cannot fetch them",
INVITE_NOT_FOUND: 'Could not find the requested invite.',
MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.',
DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them",
FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them",
GLOBAL_COMMAND_PERMISSIONS:
'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' +
"or from a guild's application command manager.",
GUILD_UNCACHED_ROLE_RESOLVE:
'Cannot resolve roles from an arbitrary guild, provide an id instead',
MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.',
INTERACTION_ALREADY_REPLIED:
'The reply to this interaction has already been sent or deferred.',
INTERACTION_NOT_REPLIED:
'The reply to this interaction has not been sent or deferred.',
INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be deleted.',
GLOBAL_COMMAND_PERMISSIONS:
'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' +
"or from a guild's application command manager.",
GUILD_UNCACHED_ROLE_RESOLVE: 'Cannot resolve roles from an arbitrary guild, provide an id instead',
COMMAND_INTERACTION_OPTION_NOT_FOUND: (name) =>
`Required option "${name}" not found.`,
COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) =>
`Option "${name}" is of type: ${type}; expected ${expected}.`,
COMMAND_INTERACTION_OPTION_EMPTY: (name, type) =>
`Required option "${name}" is of type: ${type}; expected a non-empty value.`,
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND:
'No subcommand specified for interaction.',
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP:
'No subcommand group specified for interaction.',
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION:
'No focused option for autocomplete interaction.',
INTERACTION_ALREADY_REPLIED: 'The reply to this interaction has already been sent or deferred.',
INTERACTION_NOT_REPLIED: 'The reply to this interaction has not been sent or deferred.',
INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be deleted.',
INVITE_MISSING_SCOPES:
'At least one valid scope must be provided for the invite',
COMMAND_INTERACTION_OPTION_NOT_FOUND: name => `Required option "${name}" not found.`,
COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) =>
`Option "${name}" is of type: ${type}; expected ${expected}.`,
COMMAND_INTERACTION_OPTION_EMPTY: (name, type) =>
`Required option "${name}" is of type: ${type}; expected a non-empty value.`,
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND: 'No subcommand specified for interaction.',
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.',
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.',
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite',
SWEEP_FILTER_RETURN:
'The return value of the sweepFilter function was not false or a Function',
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
INVALID_BOT_METHOD: `Bot accounts cannot use this method`,
SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function',
INVALID_BOT_METHOD: `Bot accounts cannot use this method`,
INVALID_USER_METHOD: `User accounts cannot use this method`,
INVALID_LOCALE: 'Unable to select this location',
FOLDER_NOT_FOUND: 'Server directory not found',
FOLDER_POSITION_INVALID: 'The server index in the directory is invalid',
};
Messages.AuthenticationFailed = Messages.TOKEN_INVALID;
Messages.InvalidShard = Messages.SHARDING_INVALID;
Messages.ShardingRequired = Messages.SHARDING_REQUIRED;
Messages.InvalidIntents = Messages.INVALID_INTENTS;
Messages.DisallowedIntents = Messages.DISALLOWED_INTENTS;
for (const [name, message] of Object.entries(Messages)) register(name, message);

View File

@ -9,30 +9,27 @@ exports.ShardingManager = require('./sharding/ShardingManager');
exports.WebhookClient = require('./client/WebhookClient');
// Utilities
exports.ActivityFlagsBitField = require('./util/ActivityFlagsBitField');
exports.ApplicationFlagsBitField = require('./util/ApplicationFlagsBitField');
exports.ActivityFlags = require('./util/ActivityFlags');
exports.ApplicationFlags = require('./util/ApplicationFlags');
exports.BaseManager = require('./managers/BaseManager');
exports.BitField = require('./util/BitField');
exports.Collection = require('@discordjs/collection').Collection;
exports.Constants = require('./util/Constants');
exports.Colors = require('./util/Colors');
exports.DataResolver = require('./util/DataResolver');
exports.EnumResolvers = require('./util/EnumResolvers');
exports.Events = require('./util/Events');
exports.DiscordAPIError = require('./rest/DiscordAPIError');
exports.Formatters = require('./util/Formatters');
exports.IntentsBitField = require('./util/IntentsBitField');
exports.HTTPError = require('./rest/HTTPError');
exports.Intents = require('./util/Intents');
exports.LimitedCollection = require('./util/LimitedCollection');
exports.MessageFlagsBitField = require('./util/MessageFlagsBitField');
exports.MessageFlags = require('./util/MessageFlags');
exports.Options = require('./util/Options');
exports.Partials = require('./util/Partials');
exports.PermissionsBitField = require('./util/PermissionsBitField');
exports.ShardEvents = require('./util/ShardEvents');
exports.Status = require('./util/Status');
exports.SnowflakeUtil = require('@sapphire/snowflake').DiscordSnowflake;
exports.Permissions = require('./util/Permissions');
exports.RateLimitError = require('./rest/RateLimitError');
exports.SnowflakeUtil = require('./util/SnowflakeUtil');
exports.Sweepers = require('./util/Sweepers');
exports.SystemChannelFlagsBitField = require('./util/SystemChannelFlagsBitField');
exports.ThreadMemberFlagsBitField = require('./util/ThreadMemberFlagsBitField');
exports.UserFlagsBitField = require('./util/UserFlagsBitField');
exports.SystemChannelFlags = require('./util/SystemChannelFlags');
exports.ThreadMemberFlags = require('./util/ThreadMemberFlags');
exports.UserFlags = require('./util/UserFlags');
exports.Util = require('./util/Util');
exports.version = require('../package.json').version;
@ -43,7 +40,6 @@ exports.BaseGuildEmojiManager = require('./managers/BaseGuildEmojiManager');
exports.CachedManager = require('./managers/CachedManager');
exports.ChannelManager = require('./managers/ChannelManager');
exports.ClientVoiceManager = require('./client/voice/ClientVoiceManager');
exports.ClientUserSettingManager = require('./managers/ClientUserSettingManager');
exports.DataManager = require('./managers/DataManager');
exports.GuildApplicationCommandManager = require('./managers/GuildApplicationCommandManager');
exports.GuildBanManager = require('./managers/GuildBanManager');
@ -71,34 +67,29 @@ exports.WebSocketManager = require('./client/websocket/WebSocketManager');
exports.WebSocketShard = require('./client/websocket/WebSocketShard');
// Structures
exports.RichPresence = require('./RPC/index');
// exports.RichPresence = require('discord-rpc-contructor');
exports.ActionRow = require('./structures/ActionRow');
exports.Activity = require('./structures/Presence').Activity;
exports.AnonymousGuild = require('./structures/AnonymousGuild');
exports.Application = require('./structures/interfaces/Application');
exports.ApplicationCommand = require('./structures/ApplicationCommand');
exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction');
exports.Base = require('./structures/Base');
exports.BaseCommandInteraction = require('./structures/BaseCommandInteraction');
exports.BaseGuild = require('./structures/BaseGuild');
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel');
exports.ButtonComponent = require('./structures/ButtonComponent');
exports.BaseMessageComponent = require('./structures/BaseMessageComponent');
exports.ButtonInteraction = require('./structures/ButtonInteraction');
exports.CategoryChannel = require('./structures/CategoryChannel');
exports.Channel = require('./structures/Channel').Channel;
exports.ChatInputCommandInteraction = require('./structures/ChatInputCommandInteraction');
exports.ClientApplication = require('./structures/ClientApplication');
exports.ClientPresence = require('./structures/ClientPresence');
exports.ClientUser = require('./structures/ClientUser');
exports.CommandInteraction = require('./structures/CommandInteraction');
exports.Collector = require('./structures/interfaces/Collector');
exports.CommandInteraction = require('./structures/CommandInteraction');
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
exports.ContextMenuInteraction = require('./structures/ContextMenuInteraction');
exports.DMChannel = require('./structures/DMChannel');
exports.Embed = require('./structures/Embed');
exports.UnsafeEmbed = require('@discordjs/builders').UnsafeEmbed;
exports.Emoji = require('./structures/Emoji').Emoji;
exports.Guild = require('./structures/Guild').Guild;
exports.GuildAuditLogs = require('./structures/GuildAuditLogs');
@ -120,13 +111,17 @@ exports.Invite = require('./structures/Invite');
exports.InviteStageInstance = require('./structures/InviteStageInstance');
exports.InviteGuild = require('./structures/InviteGuild');
exports.Message = require('./structures/Message').Message;
exports.MessageActionRow = require('./structures/MessageActionRow');
exports.MessageAttachment = require('./structures/MessageAttachment');
exports.MessageButton = require('./structures/MessageButton');
exports.MessageCollector = require('./structures/MessageCollector');
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction');
exports.MessageContextMenuInteraction = require('./structures/MessageContextMenuInteraction');
exports.MessageEmbed = require('./structures/MessageEmbed');
exports.MessageMentions = require('./structures/MessageMentions');
exports.MessagePayload = require('./structures/MessagePayload');
exports.MessageReaction = require('./structures/MessageReaction');
exports.MessageSelectMenu = require('./structures/MessageSelectMenu');
exports.NewsChannel = require('./structures/NewsChannel');
exports.OAuth2Guild = require('./structures/OAuth2Guild');
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
@ -136,7 +131,6 @@ exports.ReactionCollector = require('./structures/ReactionCollector');
exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role;
exports.SelectMenuComponent = require('./structures/SelectMenuComponent');
exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction');
exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance;
@ -150,7 +144,7 @@ exports.ThreadChannel = require('./structures/ThreadChannel');
exports.ThreadMember = require('./structures/ThreadMember');
exports.Typing = require('./structures/Typing');
exports.User = require('./structures/User');
exports.UserContextMenuCommandInteraction = require('./structures/UserContextMenuCommandInteraction');
exports.UserContextMenuInteraction = require('./structures/UserContextMenuInteraction');
exports.VoiceChannel = require('./structures/VoiceChannel');
exports.VoiceRegion = require('./structures/VoiceRegion');
exports.VoiceState = require('./structures/VoiceState');
@ -161,47 +155,3 @@ exports.WelcomeChannel = require('./structures/WelcomeChannel');
exports.WelcomeScreen = require('./structures/WelcomeScreen');
exports.WebSocket = require('./WebSocket');
// External
exports.ActivityType = require('discord-api-types/v9').ActivityType;
exports.ApplicationCommandType = require('discord-api-types/v9').ApplicationCommandType;
exports.ApplicationCommandOptionType = require('discord-api-types/v9').ApplicationCommandOptionType;
exports.ApplicationCommandPermissionType = require('discord-api-types/v9').ApplicationCommandPermissionType;
exports.AuditLogEvent = require('discord-api-types/v9').AuditLogEvent;
exports.ButtonStyle = require('discord-api-types/v9').ButtonStyle;
exports.ChannelType = require('discord-api-types/v9').ChannelType;
exports.ComponentType = require('discord-api-types/v9').ComponentType;
exports.GatewayCloseCodes = require('discord-api-types/v9').GatewayCloseCodes;
exports.GatewayDispatchEvents = require('discord-api-types/v9').GatewayDispatchEvents;
exports.GatewayIntentBits = require('discord-api-types/v9').GatewayIntentBits;
exports.GatewayOpcodes = require('discord-api-types/v9').GatewayOpcodes;
exports.GuildFeature = require('discord-api-types/v9').GuildFeature;
exports.GuildMFALevel = require('discord-api-types/v9').GuildMFALevel;
exports.GuildNSFWLevel = require('discord-api-types/v9').GuildNSFWLevel;
exports.GuildPremiumTier = require('discord-api-types/v9').GuildPremiumTier;
exports.GuildScheduledEventEntityType = require('discord-api-types/v9').GuildScheduledEventEntityType;
exports.GuildScheduledEventPrivacyLevel = require('discord-api-types/v9').GuildScheduledEventPrivacyLevel;
exports.GuildScheduledEventStatus = require('discord-api-types/v9').GuildScheduledEventStatus;
exports.GuildSystemChannelFlags = require('discord-api-types/v9').GuildSystemChannelFlags;
exports.GuildVerificationLevel = require('discord-api-types/v9').GuildVerificationLevel;
exports.InteractionType = require('discord-api-types/v9').InteractionType;
exports.InteractionResponseType = require('discord-api-types/v9').InteractionResponseType;
exports.InviteTargetType = require('discord-api-types/v9').InviteTargetType;
exports.Locale = require('discord-api-types/v9').Locale;
exports.MessageType = require('discord-api-types/v9').MessageType;
exports.MessageFlags = require('discord-api-types/v9').MessageFlags;
exports.OAuth2Scopes = require('discord-api-types/v9').OAuth2Scopes;
exports.PermissionFlagsBits = require('discord-api-types/v9').PermissionFlagsBits;
exports.RESTJSONErrorCodes = require('discord-api-types/v9').RESTJSONErrorCodes;
exports.StageInstancePrivacyLevel = require('discord-api-types/v9').StageInstancePrivacyLevel;
exports.StickerType = require('discord-api-types/v9').StickerType;
exports.StickerFormatType = require('discord-api-types/v9').StickerFormatType;
exports.UserFlags = require('discord-api-types/v9').UserFlags;
exports.WebhookType = require('discord-api-types/v9').WebhookType;
exports.UnsafeButtonComponent = require('@discordjs/builders').UnsafeButtonComponent;
exports.UnsafeSelectMenuComponent = require('@discordjs/builders').UnsafeSelectMenuComponent;
exports.SelectMenuOption = require('@discordjs/builders').SelectMenuOption;
exports.UnsafeSelectMenuOption = require('@discordjs/builders').UnsafeSelectMenuOption;
exports.DiscordAPIError = require('./rest/DiscordAPIError');
exports.HTTPError = require('./rest/HTTPError');
exports.RateLimitError = require('./rest/RateLimitError');

View File

@ -1,11 +1,11 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const ApplicationCommand = require('../structures/ApplicationCommand');
const { ApplicationCommandTypes } = require('../util/Constants');
/**
* Manages API methods for application commands and stores their cache.
@ -37,12 +37,12 @@ class ApplicationCommandManager extends CachedManager {
* @param {Snowflake} [options.id] The application command's id
* @param {Snowflake} [options.guildId] The guild's id to use in the path,
* ignored when using a {@link GuildApplicationCommandManager}
* @returns {string}
* @returns {Object}
* @private
*/
commandPath({ id, guildId } = {}) {
let path = this.client.api.applications(this.client.application.id);
if(this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
return id ? path.commands(id) : path.commands;
}
@ -169,7 +169,7 @@ class ApplicationCommandManager extends CachedManager {
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
const patched = await this.commandPath({ id, guildId }).patch({
body: this.constructor.transformCommand(data),
data: this.constructor.transformCommand(data),
});
return this._add(patched, true, guildId);
}
@ -207,7 +207,7 @@ class ApplicationCommandManager extends CachedManager {
return {
name: command.name,
description: command.description,
type: command.type,
type: typeof command.type === 'number' ? command.type : ApplicationCommandTypes[command.type],
options: command.options?.map(o => ApplicationCommand.transformOption(o)),
default_permission: command.defaultPermission ?? command.default_permission,
};

View File

@ -1,9 +1,9 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { RESTJSONErrorCodes, Routes } = require('discord-api-types/v9');
const BaseManager = require('./BaseManager');
const { Error, TypeError } = require('../errors');
const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Constants');
/**
* Manages API methods for permissions of Application Commands.
@ -43,7 +43,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
* The APIRouter path to the commands
* @param {Snowflake} guildId The guild's id to use in the path,
* @param {Snowflake} [commandId] The application command's id
* @returns {string}
* @returns {Object}
* @private
*/
permissionsPath(guildId, commandId) {
@ -137,7 +137,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
* permissions: [
* {
* id: '876543210987654321',
* type: ApplicationCommandOptionType.User,
* type: 'USER',
* permission: false,
* },
* ]})
@ -150,7 +150,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
* id: '123456789012345678',
* permissions: [{
* id: '876543210987654321',
* type: ApplicationCommandOptionType.User,
* type: 'USER',
* permission: false,
* }],
* },
@ -212,7 +212,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
* guild.commands.permissions.add({ command: '123456789012345678', permissions: [
* {
* id: '876543211234567890',
* type: ApplicationCommandPermissionType.Role,
* type: 'ROLE',
* permission: false
* },
* ]})
@ -230,7 +230,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
try {
existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) {
if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error;
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
}
const newPermissions = permissions.slice();
@ -319,7 +319,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
try {
existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) {
if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error;
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
}
const permissions = existing.filter(perm => !resolvedIds.includes(perm.id));
@ -366,7 +366,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
try {
existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) {
if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error;
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
}
return existing.some(perm => perm.id === resolvedId);
@ -388,6 +388,24 @@ class ApplicationCommandPermissionsManager extends BaseManager {
}
return { guildId, commandId };
}
/**
* Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API.
* @param {ApplicationCommandPermissionData} permissions The permissions to transform
* @param {boolean} [received] Whether these permissions have been received from Discord
* @returns {APIApplicationCommandPermissions}
* @private
*/
static transformPermissions(permissions, received) {
return {
id: permissions.id,
permission: permissions.permission,
type:
typeof permissions.type === 'number' && !received
? permissions.type
: ApplicationCommandPermissionTypes[permissions.type],
};
}
}
module.exports = ApplicationCommandPermissionsManager;

View File

@ -1,6 +1,7 @@
'use strict';
const DataManager = require('./DataManager');
const { _cleanupSymbol } = require('../util/Constants');
/**
* Manages the API methods of a data model with a mutable cache of instances.
@ -13,6 +14,19 @@ class CachedManager extends DataManager {
Object.defineProperty(this, '_cache', { value: this.client.options.makeCache(this.constructor, this.holds) });
let cleanup = this._cache[_cleanupSymbol]?.();
if (cleanup) {
cleanup = cleanup.bind(this._cache);
client._cleanups.add(cleanup);
client._finalizers.register(this, {
cleanup,
message:
`Garbage collection completed on ${this.constructor.name}, ` +
`which had a ${this._cache.constructor.name} of ${this.holds.name}.`,
name: this.constructor.name,
});
}
if (iterable) {
for (const item of iterable) {
this._add(item);

View File

@ -1,69 +0,0 @@
'use strict';
const DataManager = require('./DataManager');
const GuildChannel = require('../structures/GuildChannel');
/**
* Manages API methods for CategoryChannels' children.
* @extends {DataManager}
*/
class CategoryChannelChildManager extends DataManager {
constructor(channel) {
super(channel.client, GuildChannel);
/**
* The category channel this manager belongs to
* @type {CategoryChannel}
*/
this.channel = channel;
}
/**
* The channels that are a part of this category
* @type {Collection<Snowflake, GuildChannel>}
* @readonly
*/
get cache() {
return this.guild.channels.cache.filter(c => c.parentId === this.channel.id);
}
/**
* The guild this manager belongs to
* @type {Guild}
* @readonly
*/
get guild() {
return this.channel.guild;
}
/**
* Options for creating a channel using {@link CategoryChannel#createChannel}.
* @typedef {Object} CategoryCreateChannelOptions
* @property {ChannelType} [type=ChannelType.GuildText] The type of the new channel.
* @property {string} [topic] The topic for the new channel
* @property {boolean} [nsfw] Whether the new channel is NSFW
* @property {number} [bitrate] Bitrate of the new channel in bits (only voice)
* @property {number} [userLimit] Maximum amount of users allowed in the new channel (only voice)
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites of the new channel
* @property {number} [position] Position of the new channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
* @property {string} [rtcRegion] The specific region of the new channel.
* @property {string} [reason] Reason for creating the new channel
*/
/**
* Creates a new channel within this category.
* <info>You cannot create a channel of type {@link ChannelType.GuildCategory} inside a CategoryChannel.</info>
* @param {string} name The name of the new channel
* @param {CategoryCreateChannelOptions} options Options for creating the new channel
* @returns {Promise<GuildChannel>}
*/
create(name, options) {
return this.guild.channels.create(name, {
...options,
parent: this.channel.id,
});
}
}
module.exports = CategoryChannelChildManager;

View File

@ -1,11 +1,9 @@
'use strict';
const process = require('node:process');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { Channel } = require('../structures/Channel');
const { ThreadChannelTypes } = require('../util/Constants');
const Events = require('../util/Events');
const { Events, ThreadChannelTypes } = require('../util/Constants');
let cacheWarningEmitted = false;
@ -18,8 +16,8 @@ class ChannelManager extends CachedManager {
super(client, Channel, iterable);
const defaultCaching =
this._cache.constructor.name === 'Collection' ||
this._cache.maxSize === undefined ||
this._cache.maxSize === Infinity;
((this._cache.maxSize === undefined || this._cache.maxSize === Infinity) &&
(this._cache.sweepFilter === undefined || this._cache.sweepFilter.isDefault));
if (!cacheWarningEmitted && !defaultCaching) {
cacheWarningEmitted = true;
process.emitWarning(
@ -49,7 +47,7 @@ class ChannelManager extends CachedManager {
const channel = Channel.create(this.client, data, guild, { allowUnknownGuild, fromInteraction });
if (!channel) {
this.client.emit(Events.Debug, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`);
this.client.emit(Events.DEBUG, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`);
return null;
}

View File

@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError, Error } = require('../errors');
const GuildBan = require('../structures/GuildBan');
@ -120,7 +119,7 @@ class GuildBanManager extends CachedManager {
/**
* Options used to ban a user from a guild.
* @typedef {Object} BanOptions
* @property {number} [deleteMessageDays] Number of days of messages to delete, must be between 0 and 7, inclusive
* @property {number} [days=0] Number of days of messages to delete, must be between 0 and 7, inclusive
* @property {string} [reason] The reason for the ban
*/
@ -137,14 +136,17 @@ class GuildBanManager extends CachedManager {
* .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`))
* .catch(console.error);
*/
async create(user, options = {}) {
async create(user, options = { days: 0 }) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
const id = this.client.users.resolveId(user);
if (!id) throw new Error('BAN_RESOLVE_ID', true);
await this.client.api.guilds(this.guild.id).bans(id).put({
body: { delete_message_days: options.deleteMessageDays },
reason: options.reason,
});
await this.client.api
.guilds(this.guild.id)
.bans(id)
.put({
data: { delete_message_days: options.days },
reason: options.reason,
});
if (user instanceof GuildMember) return user;
const _user = this.client.users.resolve(id);
if (_user) {

View File

@ -2,17 +2,13 @@
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { ChannelType, Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const ThreadManager = require('./ThreadManager');
const { Error, TypeError } = require('../errors');
const { Error } = require('../errors');
const GuildChannel = require('../structures/GuildChannel');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const ThreadChannel = require('../structures/ThreadChannel');
const Webhook = require('../structures/Webhook');
const { ThreadChannelTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util');
const { ChannelTypes, ThreadChannelTypes } = require('../util/Constants');
let cacheWarningEmitted = false;
let storeChannelDeprecationEmitted = false;
@ -26,8 +22,8 @@ class GuildChannelManager extends CachedManager {
super(guild.client, GuildChannel, iterable);
const defaultCaching =
this._cache.constructor.name === 'Collection' ||
this._cache.maxSize === undefined ||
this._cache.maxSize === Infinity;
((this._cache.maxSize === undefined || this._cache.maxSize === Infinity) &&
(this._cache.sweepFilter === undefined || this._cache.sweepFilter.isDefault));
if (!cacheWarningEmitted && !defaultCaching) {
cacheWarningEmitted = true;
process.emitWarning(
@ -116,11 +112,11 @@ class GuildChannelManager extends CachedManager {
* @example
* // Create a new channel with permission overwrites
* guild.channels.create('new-voice', {
* type: ChannelType.GuildVoice,
* type: 'GUILD_VOICE',
* permissionOverwrites: [
* {
* id: message.author.id,
* deny: [PermissionFlagsBits.ViewChannel],
* deny: [Permissions.FLAGS.VIEW_CHANNEL],
* },
* ],
* })
@ -143,8 +139,9 @@ class GuildChannelManager extends CachedManager {
) {
parent &&= this.client.channels.resolveId(parent);
permissionOverwrites &&= permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
const intType = typeof type === 'number' ? type : ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT;
if (type === ChannelType.GuildStore && !storeChannelDeprecationEmitted) {
if (intType === ChannelTypes.GUILD_STORE && !storeChannelDeprecationEmitted) {
storeChannelDeprecationEmitted = true;
process.emitWarning(
// eslint-disable-next-line max-len
@ -154,10 +151,10 @@ class GuildChannelManager extends CachedManager {
}
const data = await this.client.api.guilds(this.guild.id).channels.post({
body: {
data: {
name,
topic,
type,
type: intType,
nsfw,
bitrate,
user_limit: userLimit,
@ -172,148 +169,6 @@ class GuildChannelManager extends CachedManager {
return this.client.actions.ChannelCreate.handle(data).channel;
}
/**
* Creates a webhook for the channel.
* @param {GuildChannelResolvable} channel The channel to create the webhook for
* @param {string} name The name of the webhook
* @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
* @example
* // Create a webhook for the current channel
* guild.channels.createWebhook('222197033908436994', 'Snek', {
* avatar: 'https://i.imgur.com/mI8XcpG.jpg',
* reason: 'Needed a cool new Webhook'
* })
* .then(console.log)
* .catch(console.error)
*/
async createWebhook(channel, name, { avatar, reason } = {}) {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
avatar = await DataResolver.resolveImage(avatar);
}
const data = await this.client.api.channels(id).webhooks.post({
body: {
name,
avatar,
},
reason,
});
return new Webhook(this.client, data);
}
/**
* The data for a guild channel.
* @typedef {Object} ChannelData
* @property {string} [name] The name of the channel
* @property {ChannelType} [type] The type of the channel (only conversion between text and news is supported)
* @property {number} [position] The position of the channel
* @property {string} [topic] The topic of the text channel
* @property {boolean} [nsfw] Whether the channel is NSFW
* @property {number} [bitrate] The bitrate of the voice channel
* @property {number} [userLimit] The user limit of the voice channel
* @property {?CategoryChannelResolvable} [parent] The parent of the channel
* @property {boolean} [lockPermissions]
* Lock the permissions of the channel to what the parent's permissions are
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites for the channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the channel in seconds
* @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration]
* The default auto archive duration for all new threads in this channel
* @property {?string} [rtcRegion] The RTC region of the channel
*/
/**
* Edits the channel.
* @param {GuildChannelResolvable} channel The channel to edit
* @param {ChannelData} data The new data for the channel
* @param {string} [reason] Reason for editing this channel
* @returns {Promise<GuildChannel>}
* @example
* // Edit a channel
* guild.channels.edit('222197033908436994', { name: 'new-channel' })
* .then(console.log)
* .catch(console.error);
*/
async edit(channel, data, reason) {
channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const parent = data.parent && this.client.channels.resolveId(data.parent);
if (typeof data.position !== 'undefined') await this.setPosition(channel, data.position, { reason });
let permission_overwrites = data.permissionOverwrites?.map(o => PermissionOverwrites.resolve(o, this.guild));
if (data.lockPermissions) {
if (parent) {
const newParent = this.guild.channels.resolve(parent);
if (newParent?.type === ChannelType.GuildCategory) {
permission_overwrites = newParent.permissionOverwrites.cache.map(o =>
PermissionOverwrites.resolve(o, this.guild),
);
}
} else if (channel.parent) {
permission_overwrites = this.parent.permissionOverwrites.cache.map(o =>
PermissionOverwrites.resolve(o, this.guild),
);
}
}
const newData = await this.client.api.channels(channel.id).patch({
body: {
name: (data.name ?? channel.name).trim(),
type: data.type,
topic: data.topic,
nsfw: data.nsfw,
bitrate: data.bitrate ?? channel.bitrate,
user_limit: data.userLimit ?? channel.userLimit,
rtc_region: data.rtcRegion ?? channel.rtcRegion,
parent_id: parent,
lock_permissions: data.lockPermissions,
rate_limit_per_user: data.rateLimitPerUser,
default_auto_archive_duration: data.defaultAutoArchiveDuration,
permission_overwrites,
},
reason,
})
return this.client.actions.ChannelUpdate.handle(newData).updated;
}
/**
* Sets a new position for the guild channel.
* @param {GuildChannelResolvable} channel The channel to set the position for
* @param {number} position The new position for the guild channel
* @param {SetChannelPositionOptions} [options] Options for setting position
* @returns {Promise<GuildChannel>}
* @example
* // Set a new channel position
* guild.channels.setPosition('222078374472843266', 2)
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
* .catch(console.error);
*/
async setPosition(channel, position, { relative, reason } = {}) {
channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const updatedChannels = await Util.setPosition(
channel,
position,
relative,
this.guild._sortedChannels(channel),
this.client,
Routes.guildChannels(this.guild.id),
reason,
);
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.guild.id,
channels: updatedChannels,
});
return channel;
}
/**
* Obtains one or more guild channels from Discord, or the channel cache if they're already available.
* @param {Snowflake} [id] The channel's id
@ -349,39 +204,6 @@ class GuildChannelManager extends CachedManager {
return channels;
}
/**
* Fetches all webhooks for the channel.
* @param {GuildChannelResolvable} channel The channel to fetch webhooks for
* @returns {Promise<Collection<Snowflake, Webhook>>}
* @example
* // Fetch webhooks
* guild.channels.fetchWebhooks('769862166131245066')
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
async fetchWebhooks(channel) {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const data = await this.client.api.channels(id).webhooks.get();
return data.reduce((hooks, hook) => hooks.set(hook.id, new Webhook(this.client, hook)), new Collection());
}
/**
* Data that can be resolved to give a Category Channel object. This can be:
* * A CategoryChannel object
* * A Snowflake
* @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable
*/
/**
* The data needed for updating a channel's position.
* @typedef {Object} ChannelPosition
* @property {GuildChannel|Snowflake} channel Channel to update
* @property {number} [position] New position for the channel
* @property {CategoryChannelResolvable} [parent] Parent channel for this channel
* @property {boolean} [lockPermissions] If the overwrites should be locked to the parents overwrites
*/
/**
* Batch-updates the guild's channels' positions.
* <info>Only one channel's parent can be changed at a time</info>
@ -400,7 +222,7 @@ class GuildChannelManager extends CachedManager {
parent_id: typeof r.parent !== 'undefined' ? this.channels.resolveId(r.parent) : undefined,
}));
await this.client.api.guilds(this.guild.id).channels.post({ body: channelPositions });
await this.client.api.guilds(this.guild.id).channels.patch({ data: channelPositions });
return this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.guild.id,
channels: channelPositions,
@ -421,23 +243,6 @@ class GuildChannelManager extends CachedManager {
const raw = await this.client.api.guilds(this.guild.id).threads.active.get();
return ThreadManager._mapThreads(raw, this.client, { guild: this.guild, cache });
}
/**
* Deletes the channel.
* @param {GuildChannelResolvable} channel The channel to delete
* @param {string} [reason] Reason for deleting this channel
* @returns {Promise<void>}
* @example
* // Delete the channel
* guild.channels.delete('858850993013260338', 'making room for new channels')
* .then(console.log)
* .catch(console.error);
*/
async delete(channel, reason) {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
await this.client.api.channels(id).delete({ reason });
}
}
module.exports = GuildChannelManager;

View File

@ -1,9 +1,8 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes, PermissionFlagsBits } = require('discord-api-types/v9');
const BaseGuildEmojiManager = require('./BaseGuildEmojiManager');
const { Error, TypeError } = require('../errors');
const { TypeError } = require('../errors');
const DataResolver = require('../util/DataResolver');
/**
@ -53,20 +52,20 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
attachment = await DataResolver.resolveImage(attachment);
if (!attachment) throw new TypeError('REQ_RESOURCE_TYPE');
const body = { image: attachment, name };
const data = { image: attachment, name };
if (roles) {
if (!Array.isArray(roles) && !(roles instanceof Collection)) {
throw new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true);
}
body.roles = [];
data.roles = [];
for (const role of roles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'options.roles', role);
body.roles.push(resolvedRole);
data.roles.push(resolvedRole);
}
}
const emoji = await this.client.api.guilds(this.guild.id).emojis.post({ body, reason });
const emoji = await this.client.api.guilds(this.guild.id).emojis.post({ data, reason });
return this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji;
}
@ -101,68 +100,6 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache));
return emojis;
}
/**
* Deletes an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to delete
* @param {string} [reason] Reason for deleting the emoji
* @returns {Promise<void>}
*/
async delete(emoji, reason) {
const id = this.resolveId(emoji);
if (!id) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
await this.client.api.guilds(this.guild.id).emojis(id).delete({ reason });
}
/**
* Edits an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to edit
* @param {GuildEmojiEditData} data The new data for the emoji
* @param {string} [reason] Reason for editing this emoji
* @returns {Promise<GuildEmoji>}
*/
async edit(emoji, data, reason) {
const id = this.resolveId(emoji);
if (!id) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
const roles = data.roles?.map(r => this.guild.roles.resolveId(r));
const newData = await this.client.api.guilds(this.guild.id).emojis(id).patch({
body: {
name: data.name,
roles,
},
reason,
})
const existing = this.cache.get(id);
if (existing) {
const clone = existing._clone();
clone._patch(newData);
return clone;
}
return this._add(newData);
}
/**
* Fetches the author for this emoji
* @param {EmojiResolvable} emoji The emoji to fetch the author of
* @returns {Promise<User>}
*/
async fetchAuthor(emoji) {
emoji = this.resolve(emoji);
if (!emoji) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
if (emoji.managed) {
throw new Error('EMOJI_MANAGED');
}
const { me } = this.guild;
if (!me) throw new Error('GUILD_UNCACHED_ME');
if (!me.permissions.has(PermissionFlagsBits.ManageEmojisAndStickers)) {
throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild);
}
const data = await this.client.api.guilds(this.guild.id).emojis(emoji.id).get();
emoji._patch(data);
return emoji.author;
}
}
module.exports = GuildEmojiManager;

View File

@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { Error } = require('../errors');
const Invite = require('../structures/Invite');
@ -178,13 +177,13 @@ class GuildInviteManager extends CachedManager {
*/
async create(
channel,
{ temporary, maxAge, maxUses, unique, targetUser, targetApplication, targetType, reason } = {},
{ temporary = false, maxAge = 86400, maxUses = 0, unique, targetUser, targetApplication, targetType, reason } = {},
) {
const id = this.guild.channels.resolveId(channel);
if (!id) throw new Error('GUILD_CHANNEL_RESOLVE');
const invite = await this.client.api.channels(id).invites.post({
body: {
data: {
temporary,
max_age: maxAge,
max_uses: maxUses,

View File

@ -1,9 +1,8 @@
'use strict';
const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers');
const { setTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { Guild } = require('../structures/Guild');
const GuildChannel = require('../structures/GuildChannel');
@ -12,10 +11,17 @@ const { GuildMember } = require('../structures/GuildMember');
const Invite = require('../structures/Invite');
const OAuth2Guild = require('../structures/OAuth2Guild');
const { Role } = require('../structures/Role');
const {
ChannelTypes,
Events,
OverwriteTypes,
VerificationLevels,
DefaultMessageNotificationLevels,
ExplicitContentFilterLevels,
} = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Events = require('../util/Events');
const PermissionsBitField = require('../util/PermissionsBitField');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const Permissions = require('../util/Permissions');
const SystemChannelFlags = require('../util/SystemChannelFlags');
const { resolveColor } = require('../util/Util');
let cacheWarningEmitted = false;
@ -175,8 +181,17 @@ class GuildManager extends CachedManager {
} = {},
) {
icon = await DataResolver.resolveImage(icon);
if (typeof verificationLevel === 'string') {
verificationLevel = VerificationLevels[verificationLevel];
}
if (typeof defaultMessageNotifications === 'string') {
defaultMessageNotifications = DefaultMessageNotificationLevels[defaultMessageNotifications];
}
if (typeof explicitContentFilter === 'string') {
explicitContentFilter = ExplicitContentFilterLevels[explicitContentFilter];
}
for (const channel of channels) {
channel.type &&= typeof channel.type === 'number' ? channel.type : ChannelTypes[channel.type];
channel.parent_id = channel.parentId;
delete channel.parentId;
channel.user_limit = channel.userLimit;
@ -188,20 +203,23 @@ class GuildManager extends CachedManager {
if (!channel.permissionOverwrites) continue;
for (const overwrite of channel.permissionOverwrites) {
overwrite.allow &&= PermissionsBitField.resolve(overwrite.allow).toString();
overwrite.deny &&= PermissionsBitField.resolve(overwrite.deny).toString();
if (typeof overwrite.type === 'string') {
overwrite.type = OverwriteTypes[overwrite.type];
}
overwrite.allow &&= Permissions.resolve(overwrite.allow).toString();
overwrite.deny &&= Permissions.resolve(overwrite.deny).toString();
}
channel.permission_overwrites = channel.permissionOverwrites;
delete channel.permissionOverwrites;
}
for (const role of roles) {
role.color &&= resolveColor(role.color);
role.permissions &&= PermissionsBitField.resolve(role.permissions).toString();
role.permissions &&= Permissions.resolve(role.permissions).toString();
}
systemChannelFlags &&= SystemChannelFlagsBitField.resolve(systemChannelFlags);
systemChannelFlags &&= SystemChannelFlags.resolve(systemChannelFlags);
const data = await this.client.api.guilds.post({
body: {
data: {
name,
icon,
verification_level: verificationLevel,
@ -222,16 +240,16 @@ class GuildManager extends CachedManager {
const handleGuild = guild => {
if (guild.id === data.id) {
clearTimeout(timeout);
this.client.removeListener(Events.GuildCreate, handleGuild);
this.client.removeListener(Events.GUILD_CREATE, handleGuild);
this.client.decrementMaxListeners();
resolve(guild);
}
};
this.client.incrementMaxListeners();
this.client.on(Events.GuildCreate, handleGuild);
this.client.on(Events.GUILD_CREATE, handleGuild);
const timeout = setTimeout(() => {
this.client.removeListener(Events.GuildCreate, handleGuild);
this.client.removeListener(Events.GUILD_CREATE, handleGuild);
this.client.decrementMaxListeners();
resolve(this.client.guilds._add(data));
}, 10_000).unref();
@ -250,7 +268,7 @@ class GuildManager extends CachedManager {
* @typedef {Object} FetchGuildsOptions
* @property {Snowflake} [before] Get guilds before this guild id
* @property {Snowflake} [after] Get guilds after this guild id
* @property {number} [limit] Maximum number of guilds to request (1-200)
* @property {number} [limit=200] Maximum number of guilds to request (1-200)
*/
/**
@ -267,13 +285,11 @@ class GuildManager extends CachedManager {
if (existing) return existing;
}
const data = await this.client.api.guilds(id).get({
query: new URLSearchParams({ with_counts: options.withCounts ?? true }),
})
const data = await this.client.api.guilds(id).get({ query: { with_counts: options.withCounts ?? true } });
return this._add(data, options.cache);
}
const data = await this.client.api.users('@me').guilds.get({ query: new URLSearchParams(options) });
const data = await this.client.api.users('@me').guilds.get({ query: options });
return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection());
}
}

View File

@ -1,16 +1,15 @@
'use strict';
const { Buffer } = require('node:buffer');
const { setTimeout, clearTimeout } = require('node:timers');
const { setTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { Routes, GatewayOpcodes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { Error, TypeError, RangeError } = require('../errors');
const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
const { GuildMember } = require('../structures/GuildMember');
const { Role } = require('../structures/Role');
const Events = require('../util/Events');
const { Events, Opcodes } = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Manages API methods for GuildMembers and stores their cache.
@ -114,7 +113,7 @@ class GuildMemberManager extends CachedManager {
}
resolvedOptions.roles = resolvedRoles;
}
const data = await this.client.rest.put(Routes.guildMember(this.guild.id, userId), { body: resolvedOptions });
const data = await this.client.api.guilds(this.guild.id).members(userId).put({ data: resolvedOptions });
// Data is an empty buffer if the member is already part of the guild.
return data instanceof Buffer ? (options.fetchWhenExisting === false ? null : this.fetch(userId)) : this._add(data);
}
@ -194,7 +193,7 @@ class GuildMemberManager extends CachedManager {
* Options used for searching guild members.
* @typedef {Object} GuildSearchMembersOptions
* @property {string} query Filter members whose username or nickname start with this query
* @property {number} [limit] Maximum number of members to search
* @property {number} [limit=1] Maximum number of members to search
* @property {boolean} [cache=true] Whether or not to cache the fetched member(s)
*/
@ -203,10 +202,8 @@ class GuildMemberManager extends CachedManager {
* @param {GuildSearchMembersOptions} options Options for searching members
* @returns {Promise<Collection<Snowflake, GuildMember>>}
*/
async search({ query, limit, cache = true } = {}) {
const data = await this.client.api.guilds(this.guild.id).members.search.get({
query: new URLSearchParams({ query, limit }),
})
async search({ query, limit = 1, cache = true } = {}) {
const data = await this.client.api.guilds(this.guild.id).members.search.get({ query: { query, limit } });
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection());
}
@ -214,7 +211,7 @@ class GuildMemberManager extends CachedManager {
* Options used for listing guild members.
* @typedef {Object} GuildListMembersOptions
* @property {Snowflake} [after] Limit fetching members to those with an id greater than the supplied id
* @property {number} [limit] Maximum number of members to list
* @property {number} [limit=1] Maximum number of members to list
* @property {boolean} [cache=true] Whether or not to cache the fetched member(s)
*/
@ -223,12 +220,8 @@ class GuildMemberManager extends CachedManager {
* @param {GuildListMembersOptions} [options] Options for listing members
* @returns {Promise<Collection<Snowflake, GuildMember>>}
*/
async list({ after, limit, cache = true } = {}) {
const query = new URLSearchParams({ limit });
if (after) {
query.set('after', after);
}
const data = await this.client.api.guilds(this.guild.id).members.get({ query });
async list({ after, limit = 1, cache = true } = {}) {
const data = await this.client.api.guilds(this.guild.id).members.get({ query: { after, limit } });
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection());
}
@ -273,10 +266,7 @@ class GuildMemberManager extends CachedManager {
_data.roles &&= _data.roles.map(role => (role instanceof Role ? role.id : role));
_data.communication_disabled_until =
// eslint-disable-next-line eqeqeq
_data.communicationDisabledUntil != null
? new Date(_data.communicationDisabledUntil).toISOString()
: _data.communicationDisabledUntil;
_data.communicationDisabledUntil && new Date(_data.communicationDisabledUntil).toISOString();
let endpoint = this.client.api.guilds(this.guild.id);
if (id === this.client.user.id) {
@ -298,9 +288,9 @@ class GuildMemberManager extends CachedManager {
* <info>It's recommended to set {@link GuildPruneMembersOptions#count options.count}
* to `false` for large guilds.</info>
* @typedef {Object} GuildPruneMembersOptions
* @property {number} [days] Number of days of inactivity required to kick
* @property {number} [days=7] Number of days of inactivity required to kick
* @property {boolean} [dry=false] Get the number of users that will be kicked, without actually kicking them
* @property {boolean} [count] Whether or not to return the number of users that have been kicked.
* @property {boolean} [count=true] Whether or not to return the number of users that have been kicked.
* @property {RoleResolvable[]} [roles] Array of roles to bypass the "...and no roles" constraint when pruning
* @property {string} [reason] Reason for this prune
*/
@ -325,7 +315,7 @@ class GuildMemberManager extends CachedManager {
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
* .catch(console.error);
*/
async prune({ days, dry = false, count: compute_prune_count, roles = [], reason } = {}) {
async prune({ days = 7, dry = false, count: compute_prune_count = true, roles = [], reason } = {}) {
if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE');
const query = { days };
@ -346,8 +336,8 @@ class GuildMemberManager extends CachedManager {
const endpoint = this.client.api.guilds(this.guild.id).prune;
const { pruned } = await (dry
? endpoint.get({ query: new URLSearchParams(query), reason })
: endpoint.post({ body: { ...query, compute_prune_count }, reason }));
? endpoint.get({ query, reason })
: endpoint.post({ data: { ...query, compute_prune_count }, reason }));
return pruned;
}
@ -363,14 +353,14 @@ class GuildMemberManager extends CachedManager {
* @example
* // Kick a user by id (or with a user/guild member object)
* guild.members.kick('84484653687267328')
* .then(kickInfo => console.log(`Kicked ${kickInfo.user?.tag ?? kickInfo.tag ?? kickInfo}`))
* .then(banInfo => console.log(`Kicked ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`))
* .catch(console.error);
*/
async kick(user, reason) {
const id = this.client.users.resolveId(user);
if (!id) return Promise.reject(new TypeError('INVALID_TYPE', 'user', 'UserResolvable'));
await this.clinet.api.guilds(this.guild.id).members(id).delete({ reason });
await this.client.api.guilds(this.guild.id).members(id).delete({ reason });
return this.resolve(user) ?? this.client.users.resolve(user) ?? id;
}
@ -386,10 +376,10 @@ class GuildMemberManager extends CachedManager {
* @example
* // Ban a user by id (or with a user/guild member object)
* guild.members.ban('84484653687267328')
* .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`))
* .then(kickInfo => console.log(`Banned ${kickInfo.user?.tag ?? kickInfo.tag ?? kickInfo}`))
* .catch(console.error);
*/
ban(user, options) {
ban(user, options = { days: 0 }) {
return this.guild.bans.create(user, options);
}
@ -424,13 +414,13 @@ class GuildMemberManager extends CachedManager {
user: user_ids,
query,
time = 120e3,
nonce = DiscordSnowflake.generate().toString(),
nonce = SnowflakeUtil.generate(),
} = {}) {
return new Promise((resolve, reject) => {
if (!query && !user_ids) query = '';
if (nonce.length > 32) throw new RangeError('MEMBER_FETCH_NONCE_LENGTH');
this.guild.shard.send({
op: GatewayOpcodes.RequestGuildMembers,
op: Opcodes.REQUEST_GUILD_MEMBERS,
d: {
guild_id: this.guild.id,
presences,
@ -451,7 +441,7 @@ class GuildMemberManager extends CachedManager {
}
if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) {
clearTimeout(timeout);
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler);
this.client.decrementMaxListeners();
let fetched = fetchedMembers;
if (user_ids && !Array.isArray(user_ids) && fetched.size) fetched = fetched.first();
@ -459,12 +449,12 @@ class GuildMemberManager extends CachedManager {
}
};
const timeout = setTimeout(() => {
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler);
this.client.decrementMaxListeners();
reject(new Error('GUILD_MEMBERS_TIMEOUT'));
}, time).unref();
this.client.incrementMaxListeners();
this.client.on(Events.GuildMembersChunk, handler);
this.client.on(Events.GUILD_MEMBERS_CHUNK, handler);
});
}
}

View File

@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const DataManager = require('./DataManager');
const { TypeError } = require('../errors');
const { Role } = require('../structures/Role');

View File

@ -1,11 +1,10 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { GuildScheduledEventEntityType, Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError, Error } = require('../errors');
const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent');
const DataResolver = require('../util/DataResolver');
const { PrivacyLevels, GuildScheduledEventEntityTypes, GuildScheduledEventStatuses } = require('../util/Constants');
/**
* Manages API methods for GuildScheduledEvents and stores their cache.
@ -41,17 +40,15 @@ class GuildScheduledEventManager extends CachedManager {
* @property {string} name The name of the guild scheduled event
* @property {DateResolvable} scheduledStartTime The time to schedule the event at
* @property {DateResolvable} [scheduledEndTime] The time to end the event at
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* <warn>This is required if `entityType` is 'EXTERNAL'</warn>
* @property {PrivacyLevel|number} privacyLevel The privacy level of the guild scheduled event
* @property {GuildScheduledEventEntityType|number} entityType The scheduled entity type of the event
* @property {string} [description] The description of the guild scheduled event
* @property {GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.StageInstance} or
* {@link GuildScheduledEventEntityType.Voice}</warn>
* <warn>This is required if `entityType` is 'STAGE_INSTANCE' or `VOICE`</warn>
* @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the
* guild scheduled event
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* <warn>This is required if `entityType` is 'EXTERNAL'</warn>
* @property {string} [reason] The reason for creating the guild scheduled event
*/
@ -59,7 +56,7 @@ class GuildScheduledEventManager extends CachedManager {
* Options used to set entity metadata of a guild scheduled event.
* @typedef {Object} GuildScheduledEventEntityMetadataOptions
* @property {string} [location] The location of the guild scheduled event
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* <warn>This is required if `entityType` is 'EXTERNAL'</warn>
*/
/**
@ -79,11 +76,13 @@ class GuildScheduledEventManager extends CachedManager {
scheduledEndTime,
entityMetadata,
reason,
image,
} = options;
if (typeof privacyLevel === 'string') privacyLevel = PrivacyLevels[privacyLevel];
if (typeof entityType === 'string') entityType = GuildScheduledEventEntityTypes[entityType];
let entity_metadata, channel_id;
if (entityType === GuildScheduledEventEntityType.External) {
if (entityType === GuildScheduledEventEntityTypes.EXTERNAL) {
channel_id = typeof channel === 'undefined' ? channel : null;
entity_metadata = { location: entityMetadata?.location };
} else {
@ -93,7 +92,7 @@ class GuildScheduledEventManager extends CachedManager {
}
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events').post({
body: {
data: {
channel_id,
name,
privacy_level: privacyLevel,
@ -102,10 +101,9 @@ class GuildScheduledEventManager extends CachedManager {
description,
entity_type: entityType,
entity_metadata,
image: image && (await DataResolver.resolveImage(image)),
},
reason,
})
});
return this._add(data);
}
@ -140,15 +138,15 @@ class GuildScheduledEventManager extends CachedManager {
if (existing) return existing;
}
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', id).get({
query: new URLSearchParams({ with_user_count: options.withUserCount ?? true }),
})
const data = await this.client.api
.guilds(this.guild.id, 'scheduled-events', id)
.get({ query: { with_user_count: options.withUserCount ?? true } });
return this._add(data, options.cache);
}
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events').get({
query: new URLSearchParams({ with_user_count: options.withUserCount ?? true }),
})
const data = await this.client.api
.guilds(this.guild.id, 'scheduled-events')
.get({ query: { with_user_count: options.withUserCount ?? true } });
return data.reduce(
(coll, rawGuildScheduledEventData) =>
@ -173,9 +171,7 @@ class GuildScheduledEventManager extends CachedManager {
* @property {GuildScheduledEventStatus|number} [status] The status of the guild scheduled event
* @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the
* guild scheduled event
* <warn>This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is
* {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* <warn>This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is 'EXTERNAL'</warn>
* @property {string} [reason] The reason for editing the guild scheduled event
*/
@ -201,9 +197,12 @@ class GuildScheduledEventManager extends CachedManager {
scheduledEndTime,
entityMetadata,
reason,
image,
} = options;
if (typeof privacyLevel === 'string') privacyLevel = PrivacyLevels[privacyLevel];
if (typeof entityType === 'string') entityType = GuildScheduledEventEntityTypes[entityType];
if (typeof status === 'string') status = GuildScheduledEventStatuses[status];
let entity_metadata;
if (entityMetadata) {
entity_metadata = {
@ -212,7 +211,7 @@ class GuildScheduledEventManager extends CachedManager {
}
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).patch({
body: {
data: {
channel_id: typeof channel === 'undefined' ? channel : this.guild.channels.resolveId(channel),
name,
privacy_level: privacyLevel,
@ -221,11 +220,10 @@ class GuildScheduledEventManager extends CachedManager {
description,
entity_type: entityType,
status,
image: image && (await DataResolver.resolveImage(image)),
entity_metadata,
},
reason,
})
});
return this._add(data);
}
@ -272,26 +270,8 @@ class GuildScheduledEventManager extends CachedManager {
let { limit, withMember, before, after } = options;
const query = new URLSearchParams();
if (limit) {
query.set('limit', limit);
}
if (typeof withMember !== 'undefined') {
query.set('with_member', withMember);
}
if (before) {
query.set('before', before);
}
if (after) {
query.set('after', after);
}
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).users.get({
query,
query: { limit, with_member: withMember, before, after },
});
return data.reduce(

View File

@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const MessagePayload = require('../structures/MessagePayload');
@ -62,14 +61,11 @@ class GuildStickerManager extends CachedManager {
if (!resolvedFile) throw new TypeError('REQ_RESOURCE_TYPE');
file = { ...resolvedFile, key: 'file' };
const body = { name, tags, description: description ?? '' };
const data = { name, tags, description: description ?? '' };
const sticker = await this.client.api.guilds(this.guild.id).stickers.post({
appendToFormData: true,
body,
files: [file],
reason,
});
const sticker = await this.client.api
.guilds(this.guild.id)
.stickers.post({ data, files: [file], reason, dontUsePayloadJSON: true });
return this.client.actions.GuildStickerCreate.handle(this.guild, sticker).sticker;
}
@ -110,7 +106,7 @@ class GuildStickerManager extends CachedManager {
if (!stickerId) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable');
const d = await this.client.api.guilds(this.guild.id).stickers(stickerId).patch({
body: data,
data,
reason,
});
@ -165,19 +161,6 @@ class GuildStickerManager extends CachedManager {
const data = await this.client.api.guilds(this.guild.id).stickers.get();
return new Collection(data.map(sticker => [sticker.id, this._add(sticker, cache)]));
}
/**
* Fetches the user who uploaded this sticker, if this is a guild sticker.
* @param {StickerResolvable} sticker The sticker to fetch the user for
* @returns {Promise<?User>}
*/
async fetchUser(sticker) {
sticker = this.resolve(sticker);
if (!sticker) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable');
const data = await this.client.api.guilds(this.guild.id).stickers(sticker.id).get();
sticker._patch(data);
return sticker.user;
}
}
module.exports = GuildStickerManager;

View File

@ -1,13 +1,11 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const { Message } = require('../structures/Message');
const MessagePayload = require('../structures/MessagePayload');
const Util = require('../util/Util');
const DiscordAPIError = require('../rest/DiscordAPIError');
/**
* Manages API methods for Messages and holds their cache.
@ -38,7 +36,7 @@ class MessageManager extends CachedManager {
* The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
* `after` are mutually exclusive. All the parameters are optional.
* @typedef {Object} ChannelLogsQueryOptions
* @property {number} [limit] Number of messages to acquire
* @property {number} [limit=50] Number of messages to acquire
* @property {Snowflake} [before] The message's id to get the messages that were posted before it
* @property {Snowflake} [after] The message's id to get the messages that were posted after it
* @property {Snowflake} [around] The message's id to get the messages that were posted around it
@ -84,7 +82,7 @@ class MessageManager extends CachedManager {
* .catch(console.error);
*/
async fetchPinned(cache = true) {
const data = await this.client.api.channels(this.channel.id).pins.get();
const data = await this.client.api.channels[this.channel.id].pins.get();
const messages = new Collection();
for (const message of data) messages.set(message.id, this._add(message, cache));
return messages;
@ -125,13 +123,14 @@ class MessageManager extends CachedManager {
const messageId = this.resolveId(message);
if (!messageId) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
const { body, files } = await (options instanceof MessagePayload
const { data, files } = await (options instanceof MessagePayload
? options
: MessagePayload.create(message instanceof Message ? message : this, options)
)
.resolveBody()
.resolveData()
.resolveFiles();
const d = await this.client.api.channels(this.channel.id).messages(messageId).patch({ body, files });
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, files });
const existing = this.cache.get(messageId);
if (existing) {
const clone = existing._clone();
@ -157,27 +156,25 @@ class MessageManager extends CachedManager {
/**
* Pins a message to the channel's pinned messages, even if it's not cached.
* @param {MessageResolvable} message The message to pin
* @param {string} [reason] Reason for pinning
* @returns {Promise<void>}
*/
async pin(message, reason) {
async pin(message) {
message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).pins(message).put({ reason });
await this.client.api.channels(this.channel.id).pins(message).put();
}
/**
* Unpins a message from the channel's pinned messages, even if it's not cached.
* @param {MessageResolvable} message The message to unpin
* @param {string} [reason] Reason for unpinning
* @returns {Promise<void>}
*/
async unpin(message, reason) {
async unpin(message) {
message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).pins(message).delete({ reason });
await this.client.api.channels(this.channel.id).pins(message).delete();
}
/**
@ -197,6 +194,7 @@ class MessageManager extends CachedManager {
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
: encodeURIComponent(emoji.name);
// eslint-disable-next-line newline-per-chained-call
await this.client.api.channels(this.channel.id).messages(message).reactions(emojiId, '@me').put();
}
@ -218,14 +216,12 @@ class MessageManager extends CachedManager {
if (existing && !existing.partial) return existing;
}
const data = await this.client.api.channels(this.channel.id).messages(messageId).get();
const data = await this.client.api.channels[this.channel.id].messages[messageId].get();
return this._add(data, cache);
}
async _fetchMany(options = {}, cache) {
const data = await this.client.api.channels(this.channel.id).messages.get({
query: new URLSearchParams(options),
});
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
const messages = new Collection();
for (const message of data) messages.set(message.id, this._add(message, cache));
return messages;

View File

@ -2,11 +2,11 @@
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { OverwriteType, Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const { Role } = require('../structures/Role');
const { OverwriteTypes } = require('../util/Constants');
let cacheWarningEmitted = false;
@ -58,7 +58,7 @@ class PermissionOverwriteManager extends CachedManager {
* message.channel.permissionOverwrites.set([
* {
* id: message.author.id,
* deny: [PermissionsFlagsBit.ViewChannel],
* deny: [Permissions.FLAGS.VIEW_CHANNEL],
* },
* ], 'Needed to change permissions');
*/
@ -94,15 +94,18 @@ class PermissionOverwriteManager extends CachedManager {
if (typeof type !== 'number') {
userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole);
if (!userOrRole) throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role');
type = userOrRole instanceof Role ? OverwriteType.Role : OverwriteType.Member;
type = userOrRole instanceof Role ? OverwriteTypes.role : OverwriteTypes.member;
}
const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options, existing);
await this.client.api.channels(this.channel.id).permissions(userOrRoleId).put({
body: { id: userOrRoleId, type, allow, deny },
reason,
});
await this.client.api
.channels(this.channel.id)
.permissions(userOrRoleId)
.put({
data: { id: userOrRoleId, type, allow, deny },
reason,
});
return this.channel;
}

View File

@ -1,6 +1,5 @@
'use strict';
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const MessageReaction = require('../structures/MessageReaction');

View File

@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { Error } = require('../errors');
const User = require('../structures/User');
@ -41,11 +40,9 @@ class ReactionUserManager extends CachedManager {
*/
async fetch({ limit = 100, after } = {}) {
const message = this.reaction.message;
const query = new URLSearchParams({ limit });
if (after) {
query.set('after', after);
}
const data = await this.client.api.channels(message.channelId).messages(message.id).reactions(this.reaction.emoji.identifier).get({ query });
const data = await this.client.api.channels[message.channelId].messages[message.id].reactions[
this.reaction.emoji.identifier
].get({ query: { limit, after } });
const users = new Collection();
for (const rawUser of data) {
const user = this.client.users._add(rawUser);
@ -64,10 +61,9 @@ class ReactionUserManager extends CachedManager {
const userId = this.client.users.resolveId(user);
if (!userId) throw new Error('REACTION_RESOLVE_USER');
const message = this.reaction.message;
await this.client.api.channels[message.channelId]
.messages[message.id]
.reactions[this.reaction.emoji.identifier][userId === this.client.user.id ? '@me' : userId]
.delete();
await this.client.api.channels[message.channelId].messages[message.id].reactions[this.reaction.emoji.identifier][
userId === this.client.user.id ? '@me' : userId
].delete();
return this.reaction;
}
}

View File

@ -2,14 +2,12 @@
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const { Role } = require('../structures/Role');
const DataResolver = require('../util/DataResolver');
const PermissionsBitField = require('../util/PermissionsBitField');
const { resolveColor } = require('../util/Util');
const Util = require('../util/Util');
const Permissions = require('../util/Permissions');
const { resolveColor, setPosition } = require('../util/Util');
let cacheWarningEmitted = false;
@ -129,7 +127,7 @@ class RoleManager extends CachedManager {
* // Create a new role with data and a reason
* guild.roles.create({
* name: 'Super Cool Blue People',
* color: Colors.Blue,
* color: 'BLUE',
* reason: 'we needed a role for Super Cool People',
* })
* .then(console.log)
@ -138,7 +136,7 @@ class RoleManager extends CachedManager {
async create(options = {}) {
let { name, color, hoist, permissions, position, mentionable, reason, icon, unicodeEmoji } = options;
color &&= resolveColor(color);
if (typeof permissions !== 'undefined') permissions = new PermissionsBitField(permissions);
if (typeof permissions !== 'undefined') permissions = new Permissions(permissions);
if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.url;
icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon);
@ -146,7 +144,7 @@ class RoleManager extends CachedManager {
}
const data = await this.client.api.guilds(this.guild.id).roles.post({
body: {
data: {
name,
color,
hoist,
@ -156,12 +154,12 @@ class RoleManager extends CachedManager {
unicode_emoji: unicodeEmoji,
},
reason,
})
});
const { role } = this.client.actions.GuildRoleCreate.handle({
guild_id: this.guild.id,
role: data,
});
if (position) return this.setPosition(role, position, { reason });
if (position) return role.setPosition(position, reason);
return role;
}
@ -182,7 +180,19 @@ class RoleManager extends CachedManager {
if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');
if (typeof data.position === 'number') {
await this.setPosition(role, data.position, { reason });
const updatedRoles = await setPosition(
role,
data.position,
false,
this.guild._sortedRoles(),
this.client.api.guilds(this.guild.id).roles,
reason,
);
this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.guild.id,
roles: updatedRoles,
});
}
let icon = data.icon;
@ -192,17 +202,17 @@ class RoleManager extends CachedManager {
if (typeof icon !== 'string') icon = undefined;
}
const body = {
const _data = {
name: data.name,
color: typeof data.color === 'undefined' ? undefined : resolveColor(data.color),
hoist: data.hoist,
permissions: typeof data.permissions === 'undefined' ? undefined : new PermissionsBitField(data.permissions),
permissions: typeof data.permissions === 'undefined' ? undefined : new Permissions(data.permissions),
mentionable: data.mentionable,
icon,
unicode_emoji: data.unicodeEmoji,
};
const d = await this.client.api.guilds(this.guild.id).roles(role.id).patch({ body, reason });
const d = await this.client.api.guilds(this.guild.id).roles(role.id).patch({ data: _data, reason });
const clone = role._clone();
clone._patch(d);
@ -217,54 +227,15 @@ class RoleManager extends CachedManager {
* @example
* // Delete a role
* guild.roles.delete('222079219327434752', 'The role needed to go')
* .then(() => console.log('Deleted the role'))
* .then(deleted => console.log(`Deleted role ${deleted.name}`))
* .catch(console.error);
*/
async delete(role, reason) {
const id = this.resolveId(role);
await this.client.api.guilds(this.guild.id).roles(id).delete({ reason });
await this.client.api.guilds[this.guild.id].roles[id].delete({ reason });
this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: id });
}
/**
* Sets the new position of the role.
* @param {RoleResolvable} role The role to change the position of
* @param {number} position The new position for the role
* @param {SetRolePositionOptions} [options] Options for setting the position
* @returns {Promise<Role>}
* @example
* // Set the position of the role
* guild.roles.setPosition('222197033908436994', 1)
* .then(updated => console.log(`Role position: ${updated.position}`))
* .catch(console.error);
*/
async setPosition(role, position, { relative, reason } = {}) {
role = this.resolve(role);
if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');
const updatedRoles = await Util.setPosition(
role,
position,
relative,
this.guild._sortedRoles(),
this.client,
Routes.guildRoles(this.guild.id),
reason,
);
this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.guild.id,
roles: updatedRoles,
});
return role;
}
/**
* The data needed for updating a guild role's position
* @typedef {Object} GuildRolePosition
* @property {RoleResolvable} role The role's id
* @property {number} position The position to update
*/
/**
* Batch-updates the guild's role positions
* @param {GuildRolePosition[]} rolePositions Role positions to update
@ -282,7 +253,9 @@ class RoleManager extends CachedManager {
}));
// Call the API to update role positions
await this.client.api.guilds(this.guild.id).roles.patch({ body: rolePositions });
await this.client.api.guilds(this.guild.id).roles.patch({
data: rolePositions,
});
return this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.guild.id,
roles: rolePositions,

View File

@ -1,9 +1,9 @@
'use strict';
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError, Error } = require('../errors');
const { StageInstance } = require('../structures/StageInstance');
const { PrivacyLevels } = require('../util/Constants');
/**
* Manages API methods for {@link StageInstance} objects and holds their cache.
@ -49,7 +49,7 @@ class StageInstanceManager extends CachedManager {
* // Create a stage instance
* guild.stageInstances.create('1234567890123456789', {
* topic: 'A very creative topic',
* privacyLevel: GuildPrivacyLevel.GuildOnly
* privacyLevel: 'GUILD_ONLY'
* })
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
@ -60,8 +60,10 @@ class StageInstanceManager extends CachedManager {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
let { topic, privacyLevel } = options;
privacyLevel &&= typeof privacyLevel === 'number' ? privacyLevel : PrivacyLevels[privacyLevel];
const data = await this.client.api['stage-instances'].post({
body: {
data: {
channel_id: channelId,
topic,
privacy_level: privacyLevel,
@ -120,12 +122,14 @@ class StageInstanceManager extends CachedManager {
let { topic, privacyLevel } = options;
privacyLevel &&= typeof privacyLevel === 'number' ? privacyLevel : PrivacyLevels[privacyLevel];
const data = await this.client.api('stage-instances', channelId).patch({
body: {
data: {
topic,
privacy_level: privacyLevel,
},
})
});
if (this.cache.has(data.id)) {
const clone = this.cache.get(data.id)._clone();

View File

@ -1,10 +1,10 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { ChannelType, Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const ThreadChannel = require('../structures/ThreadChannel');
const { ChannelTypes } = require('../util/Constants');
/**
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
@ -64,12 +64,12 @@ class ThreadManager extends CachedManager {
* @typedef {StartThreadOptions} ThreadCreateOptions
* @property {MessageResolvable} [startMessage] The message to start a thread from. <warn>If this is defined then type
* of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored</warn>
* @property {ThreadChannelTypes|number} [type] The type of thread to create.
* Defaults to {@link ChannelType.GuildPublicThread} if created in a {@link TextChannel}
* <warn>When creating threads in a {@link NewsChannel} this is ignored and is always
* {@link ChannelType.GuildNewsThread}</warn>
* @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if
* created in a {@link TextChannel} <warn>When creating threads in a {@link NewsChannel} this is ignored and is always
* `GUILD_NEWS_THREAD`</warn>
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
* <info>Can only be set when type will be {@link ChannelType.GuildPrivateThread}</info>
* <info>Can only be set when type will be `GUILD_PRIVATE_THREAD`</info>
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
*/
/**
@ -92,7 +92,7 @@ class ThreadManager extends CachedManager {
* .create({
* name: 'mod-talk',
* autoArchiveDuration: 60,
* type: ChannelType.GuildPrivateThread,
* type: 'GUILD_PRIVATE_THREAD',
* reason: 'Needed a separate thread for moderation',
* })
* .then(threadChannel => console.log(threadChannel))
@ -107,17 +107,18 @@ class ThreadManager extends CachedManager {
reason,
rateLimitPerUser,
} = {}) {
let path = this.client.api.channels(this.channel.id);
if (type && typeof type !== 'string' && typeof type !== 'number') {
throw new TypeError('INVALID_TYPE', 'type', 'ThreadChannelType or Number');
}
let resolvedType =
this.channel.type === ChannelType.GuildNews ? ChannelType.GuildNewsThread : ChannelType.GuildPublicThread;
let startMessageId;
this.channel.type === 'GUILD_NEWS' ? ChannelTypes.GUILD_NEWS_THREAD : ChannelTypes.GUILD_PUBLIC_THREAD;
if (startMessage) {
startMessageId = this.channel.messages.resolveId(startMessage);
const startMessageId = this.channel.messages.resolveId(startMessage);
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable');
} else if (this.channel.type !== ChannelType.GuildNews) {
resolvedType = type ?? resolvedType;
path = path.messages(startMessageId);
} else if (this.channel.type !== 'GUILD_NEWS') {
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType;
}
if (autoArchiveDuration === 'MAX') {
autoArchiveDuration = 1440;
@ -128,12 +129,12 @@ class ThreadManager extends CachedManager {
}
}
const data = await this.client.api.channels(this.channel.id).messages(startMessageId).threads.post({
body: {
const data = await path.threads.post({
data: {
name,
auto_archive_duration: autoArchiveDuration,
type: resolvedType,
invitable: resolvedType === ChannelType.GuildPrivateThread ? invitable : undefined,
invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined,
rate_limit_per_user: rateLimitPerUser,
},
reason,
@ -211,31 +212,21 @@ class ThreadManager extends CachedManager {
}
let timestamp;
let id;
const query = new URLSearchParams();
if (typeof before !== 'undefined') {
if (before instanceof ThreadChannel || /^\d{16,19}$/.test(String(before))) {
id = this.resolveId(before);
timestamp = this.resolve(before)?.archivedAt?.toISOString();
const toUse = type === 'private' && !fetchAll ? id : timestamp;
if (toUse) {
query.set('before', toUse);
}
} else {
try {
timestamp = new Date(before).toISOString();
if (type === 'public' || fetchAll) {
query.set('before', timestamp);
}
} catch {
throw new TypeError('INVALID_TYPE', 'before', 'DateResolvable or ThreadChannelResolvable');
}
}
}
if (limit) {
query.set('limit', limit);
}
const raw = await path.threads.archived(type).get({ query });
const raw = await path.threads
.archived(type)
.get({ query: { before: type === 'private' && !fetchAll ? id : timestamp, limit } });
return this.constructor._mapThreads(raw, this.client, { parent: this.channel, cache });
}

View File

@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const ThreadMember = require('../structures/ThreadMember');
@ -104,22 +103,19 @@ class ThreadMemberManager extends CachedManager {
}
async _fetchMany(cache) {
const raw = await this.client.api.channels(this.thread.id, 'thread-members');
const raw = await this.client.api.channels(this.thread.id, 'thread-members').get();
return raw.reduce((col, member) => col.set(member.user_id, this._add(member, cache)), new Collection());
}
/**
* @typedef {BaseFetchOptions} ThreadMemberFetchOptions
* @property {UserResolvable} [member] The specific user to fetch from the thread
*/
/**
* Fetches member(s) for the thread from Discord, requires access to the `GUILD_MEMBERS` gateway intent.
* @param {ThreadMemberFetchOptions|boolean} [options] Additional options for this fetch, when a `boolean` is provided
* all members are fetched with `options.cache` set to the boolean value
* @param {UserResolvable|boolean} [member] The member to fetch. If `undefined`, all members
* in the thread are fetched, and will be cached based on `options.cache`. If boolean, this serves
* the purpose of `options.cache`.
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<ThreadMember|Collection<Snowflake, ThreadMember>>}
*/
fetch({ member, cache = true, force = false } = {}) {
fetch(member, { cache = true, force = false } = {}) {
const id = this.resolveId(member);
return id ? this._fetchOne(id, cache, force) : this._fetchMany(member ?? cache);
}

View File

@ -1,6 +1,5 @@
'use strict';
const { ChannelType, Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const { GuildMember } = require('../structures/GuildMember');
const { Message } = require('../structures/Message');
@ -39,7 +38,7 @@ class UserManager extends CachedManager {
* @private
*/
dmChannel(userId) {
return this.client.channels.cache.find(c => c.type === ChannelType.DM && c.recipient.id === userId) ?? null;
return this.client.channels.cache.find(c => c.type === 'DM' && c.recipient.id === userId) ?? null;
}
/**
@ -56,7 +55,11 @@ class UserManager extends CachedManager {
if (dmChannel && !dmChannel.partial) return dmChannel;
}
const data = await this.client.api.users('@me').channels.post({ body: { recipient_id: id } });
const data = await this.client.api.users(this.client.user.id).channels.post({
data: {
recipient_id: id,
},
});
return this.client.channels._add(data, null, { cache });
}
@ -69,7 +72,7 @@ class UserManager extends CachedManager {
const id = this.resolveId(user);
const dmChannel = this.dmChannel(id);
if (!dmChannel) throw new Error('USER_NO_DM_CHANNEL');
await this.client.channels(dmChannel.id).delete();
await this.client.api.channels(dmChannel.id).delete();
this.client.channels._remove(dmChannel.id);
return dmChannel;
}
@ -97,7 +100,7 @@ class UserManager extends CachedManager {
* Fetches a user's flags.
* @param {UserResolvable} user The UserResolvable to identify
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<UserFlagsBitField>}
* @returns {Promise<UserFlags>}
*/
async fetchFlags(user, options) {
return (await this.fetch(user, options)).flags;

View File

@ -2,7 +2,8 @@
const https = require('node:https');
const { setTimeout } = require('node:timers');
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
const FormData = require('form-data');
const fetch = require('node-fetch');
const { UserAgent } = require('../util/Constants');
let agent = null;
@ -16,6 +17,9 @@ class APIRequest {
this.options = options;
this.retries = 0;
const { userAgentSuffix } = this.client.options;
this.fullUserAgent = `${UserAgent}${userAgentSuffix.length ? `, ${userAgentSuffix.join(', ')}` : ''}`;
let queryString = '';
if (options.query) {
const query = Object.entries(options.query)
@ -26,7 +30,7 @@ class APIRequest {
this.path = `${path}${queryString && `?${queryString}`}`;
}
async make() {
make() {
agent ??= new https.Agent({ ...this.client.options.http.agent, keepAlive: true });
const API =
@ -37,7 +41,7 @@ class APIRequest {
let headers = {
...this.client.options.http.headers,
'User-Agent': UserAgent,
'User-Agent': this.fullUserAgent,
};
if (this.options.auth !== false) headers.Authorization = this.rest.getAuth();
@ -46,32 +50,23 @@ class APIRequest {
let body;
if (this.options.files?.length) {
body = new FormData();
for (const [index, file] of this.options.files.entries()) {
if (file?.file)
body.append(file.key ?? `files[${index}]`, file.file, file.name);
}
if (
typeof this.options.data !== 'undefined' ||
typeof this.options.body !== 'undefined'
) {
if (this.options.dontUsePayloadJSON) {
for (const [key, value] of Object.entries(this.options.data || this.options.body)) {
body.append(key, value);
}
} else {
body.append('payload_json', JSON.stringify(this.options.data || this.options.body));
}
}
headers = Object.assign(headers, body.getHeaders());
// eslint-disable-next-line eqeqeq
} else if (this.options.data != null) {
body = JSON.stringify(this.options.data);
headers['Content-Type'] = 'application/json';
} else if (this.options.body != null) {
body = JSON.stringify(this.options.body);
headers['Content-Type'] = 'application/json';
}
body = new FormData();
for (const [index, file] of this.options.files.entries()) {
if (file?.file) body.append(file.key ?? `files[${index}]`, file.file, file.name);
}
if (typeof this.options.data !== 'undefined') {
if (this.options.dontUsePayloadJSON) {
for (const [key, value] of Object.entries(this.options.data)) body.append(key, value);
} else {
body.append('payload_json', JSON.stringify(this.options.data));
}
}
headers = Object.assign(headers, body.getHeaders());
// eslint-disable-next-line eqeqeq
} else if (this.options.data != null) {
body = JSON.stringify(this.options.data);
headers['Content-Type'] = 'application/json';
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.client.options.restRequestTimeout).unref();
@ -85,4 +80,4 @@ class APIRequest {
}
}
module.exports = APIRequest;
module.exports = APIRequest;

View File

@ -50,4 +50,4 @@ function buildRoute(manager) {
return new Proxy(noop, handler);
}
module.exports = buildRoute;
module.exports = buildRoute;

Some files were not shown because too many files have changed in this diff Show More