diff --git a/DOCUMENT.md b/DOCUMENT.md
index 2d87b3e..b66c221 100644
--- a/DOCUMENT.md
+++ b/DOCUMENT.md
@@ -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, ...
\ No newline at end of file
+- Downgrade to Discord.js v13 (Old version is Discord.js v14@dev)
\ No newline at end of file
diff --git a/README.md b/README.md
index b561c15..78f95cd 100644
--- a/README.md
+++ b/README.md
@@ -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.
### I don't take any responsibility for blocked Discord accounts that used this module.
### Using this on a user account is prohibited by the [Discord TOS](https://discord.com/terms) and can lead to the account block.
@@ -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.
-- Thanks to [TheDevYellowy](https://github.com/TheDevYellowy/) for patching this module!
## 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)
## Vietnamese
-- 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 =))
\ No newline at end of file
+- 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 =))
\ No newline at end of file
diff --git a/package.json b/package.json
index 3988aef..e16df60 100644
--- a/package.json
+++ b/package.json
@@ -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 "
- ],
"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"
},
diff --git a/src/RPC/index.d.ts b/src/RPC/index.d.ts
deleted file mode 100644
index 589831c..00000000
--- a/src/RPC/index.d.ts
+++ /dev/null
@@ -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;
- getRpcImage(application_id: string, name: string): Promise;
- __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;
diff --git a/src/RPC/index.js b/src/RPC/index.js
deleted file mode 100644
index d551ac2..00000000
--- a/src/RPC/index.js
+++ /dev/null
@@ -1,1876 +0,0 @@
-module.exports = (function (e) {
- var t = {};
- function s(r) {
- if (t[r]) return t[r].exports;
- var i = (t[r] = { i: r, l: !1, exports: {} });
- return e[r].call(i.exports, i, i.exports, s), (i.l = !0), i.exports;
- }
- return (
- (s.m = e),
- (s.c = t),
- (s.d = function (e, t, r) {
- s.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: r });
- }),
- (s.r = function (e) {
- "undefined" != typeof Symbol &&
- Symbol.toStringTag &&
- Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }),
- Object.defineProperty(e, "__esModule", { value: !0 });
- }),
- (s.t = function (e, t) {
- if ((1 & t && (e = s(e)), 8 & t)) return e;
- if (4 & t && "object" == typeof e && e && e.__esModule) return e;
- var r = Object.create(null);
- if (
- (s.r(r),
- Object.defineProperty(r, "default", { enumerable: !0, value: e }),
- 2 & t && "string" != typeof e)
- )
- for (var i in e)
- s.d(
- r,
- i,
- function (t) {
- return e[t];
- }.bind(null, i)
- );
- return r;
- }),
- (s.n = function (e) {
- var t =
- e && e.__esModule
- ? function () {
- return e.default;
- }
- : function () {
- return e;
- };
- return s.d(t, "a", t), t;
- }),
- (s.o = function (e, t) {
- return Object.prototype.hasOwnProperty.call(e, t);
- }),
- (s.p = "/assets/"),
- s((s.s = 0))
- );
- })([
- function (e, t, s) {
- "use strict";
- const r = s(1),
- i = s(9),
- n = s(10);
- var o = [
- 'PLAYING',
- 'STREAMING',
- 'LISTENING',
- 'WATCHING',
- 'CUSTOM',
- 'COMPETING',
- ],
- a = [0, 1, 2, 3, 4, 5],
- l = [].concat(a).concat(o);
- async function u(e) {
- if (!e || "string" != typeof e)
- throw new c(
- `'${e}' không phải là ID Application Discord. Typeof: `
- );
- let t = await r(i.default.discord().application(e).assets(), {
- headers: {
- "User-Agent": i.default.ua,
- "accept-language": i.default.acceptedLangs,
- },
- });
- if (200 !== t.status) {
- let e,
- s = await t.text();
- if (!s) throw new c("Lỗi không xác định: " + t.status + " " + t.statusText);
- try {
- e = JSON.parse(s);
- } catch (e) {
- throw new c(s);
- }
- if (e.message) throw new c(e.message);
- if (e.application_id) throw new c(e.application_id[0]);
- throw new c(JSON.stringify(e));
- }
- return await t.json();
- }
- class c extends Error {
- constructor() {
- super(...arguments), (this.name = "RpcError");
- }
- }
- class h {
- constructor(e, t = !1) {
- (this.game = null), (this.game = e || null), t && this.lock();
- }
- lock() {
- Object.freeze(this.game);
- }
- toDiscord() {
- return { game: this.game };
- }
- toObject() {
- return this.game;
- }
- toString() {
- return this.game
- ? `${this.game.name}${
- this.game.application_id
- ? ' (' + this.game.application_id + ')'
- : ''
- } `
- : 'No game';
- }
- setButton(array) {
- // Check value
- if (!Array.isArray(array)) throw new c(`'${array}' not an array`);
- if (array.length > 2) throw new c(`'${array}' length > 2`);
- if (array.find((o) => !o?.label)) throw new c(`'${array}' has button with noname`);
- return (
- this.verifyNull(),
- this.verifyNullButton(),
- null === array
- ? (delete this.game.buttons, this)
- : ((this.game.buttons = array), this)
- );
- }
- setName(e) {
- return this.verifyNull(), (this.game.name = e), this;
- }
- setApplicationId(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.application_id, this)
- : ((this.game.application_id = e), this)
- );
- }
- setType(e) {
- let t = 0;
- if ('string' == typeof e) {
- if (!o.includes(e))
- throw new c(
- `'${e}' không phải là Presence hợp lệ: ${l.join(', ')}`,
- );
- t = o.indexOf(e);
- } else t = e;
- return this.verifyNull(), (this.game.type = t), this;
- }
- setUrl(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.url, this)
- : ((this.game.url = e), this)
- );
- }
- setDetails(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.details, this)
- : ((this.game.details = e), this)
- );
- }
- setState(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.state, this)
- : ((this.game.state = e), this)
- );
- }
- setSyncId(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.sync_id, this)
- : ((this.game.sync_id = e), this)
- );
- }
- setId(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.id, this)
- : ((this.game.id = e), this)
- );
- }
- setSessionId(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.session_id, this)
- : ((this.game.session_id = e), this)
- );
- }
- setParty(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.party, this)
- : ((this.game.party = e), this)
- );
- }
- setFlags(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.flags, this)
- : ((this.game.flags = e), this)
- );
- }
- setCreatedAt(e) {
- return (
- this.verifyNull(),
- null === e
- ? (delete this.game.created_at, this)
- : ((this.game.created_at = e), this)
- );
- }
- setAssets(e) {
- this.verifyNull();
- let t = {
- setLargeImage: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.large_image = e), t
- );
- }.bind(this),
- setSmallImage: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.small_image = e), t
- );
- }.bind(this),
- setLargeText: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.large_text = e), t
- );
- }.bind(this),
- setSmallText: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.small_text = e), t
- );
- }.bind(this),
- setNull: function () {
- return (this.game.assets = null), t;
- }.bind(this),
- };
- return e(t), this;
- }
- setAssetsLargeImage(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.large_image, this)
- : ((this.game.assets.large_image = e), this)
- );
- }
- setAssetsSmallImage(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.small_image, this)
- : ((this.game.assets.small_image = e), this)
- );
- }
- setAssetsLargeText(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.large_text, this)
- : ((this.game.assets.large_text = e), this)
- );
- }
- setAssetsSmallText(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.small_text, this)
- : ((this.game.assets.small_text = e), this)
- );
- }
- setStartTimestamp(e) {
- return (
- this.verifyNull(),
- this.verifyNullTimestamps(),
- null === e
- ? (delete this.game.timestamps.start, this)
- : ((this.game.timestamps.start = e), this)
- );
- }
- setEndTimestamp(e) {
- return (
- this.verifyNull(),
- this.verifyNullTimestamps(),
- null === e
- ? (delete this.game.timestamps.end, this)
- : ((this.game.timestamps.end = e), this)
- );
- }
- setPartySize(e) {
- return (
- this.verifyNull(),
- this.verifyNullParty(),
- null === e
- ? (delete this.game.party.size, this)
- : ((this.game.party.size = e), this)
- );
- }
- setPartyId(e) {
- return (
- this.verifyNull(),
- this.verifyNullParty(),
- null === e
- ? (delete this.game.party.id, this)
- : ((this.game.party.id = e), this)
- );
- }
- setJoinSecret(e) {
- return (
- this.verifyNull(),
- this.verifyNullSecrets(),
- null === e
- ? (delete this.game.secrets.join, this)
- : ((this.game.secrets.join = e), this)
- );
- }
- setSpectateSecret(e) {
- return (
- this.verifyNull(),
- this.verifyNullSecrets(),
- null === e
- ? (delete this.game.secrets.spectate, this)
- : ((this.game.secrets.spectate = e), this)
- );
- }
- setMatch(e) {
- return (
- this.verifyNull(),
- this.verifyNullSecrets(),
- null === e
- ? (delete this.game.secrets.match, this)
- : ((this.game.secrets.match = e), this)
- );
- }
- setSecrets(e) {
- return (
- this.verifyNull(),
- this.verifyNullSecrets(),
- null === e
- ? (delete this.game.secrets, this)
- : ((this.game.secrets = e), this)
- );
- }
- setTwitchAssets(e) {
- this.verifyNull();
- let t = {
- setLargeImage: function (e) {
- return (
- this.verifyNullAssets(),
- (this.game.assets.large_image =
- 'twitch:' + e.replace(/twitch\:/g, '')),
- t
- );
- }.bind(this),
- setSmallImage: function (e) {
- return (
- this.verifyNullAssets(),
- (this.game.assets.small_image =
- 'twitch:' + e.replace(/twitch\:/g, '')),
- t
- );
- }.bind(this),
- setLargeText: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.large_text = e), t
- );
- }.bind(this),
- setSmallText: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.small_text = e), t
- );
- }.bind(this),
- setNull: function () {
- return (this.game.assets = null), t;
- }.bind(this),
- };
- return e(t), this;
- }
- setTwitchAssetsLargeImage(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.large_image, this)
- : ((this.game.assets.large_image =
- 'twitch:' + e.replace(/twitch\:/g, '')),
- this)
- );
- }
- setTwitchAssetsSmallImage(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.small_image, this)
- : ((this.game.assets.small_image =
- 'twitch:' + e.replace(/twitch\:/g, '')),
- this)
- );
- }
- setSpotifyAssets(e) {
- this.verifyNull();
- let t = {
- setLargeImage: function (e) {
- return (
- this.verifyNullAssets(),
- (this.game.assets.large_image =
- 'spotify:' + e.replace(/spotify\:/g, '')),
- t
- );
- }.bind(this),
- setSmallImage: function (e) {
- return (
- this.verifyNullAssets(),
- (this.game.assets.small_image =
- 'spotify:' + e.replace(/spotify\:/g, '')),
- t
- );
- }.bind(this),
- setLargeText: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.large_text = e), t
- );
- }.bind(this),
- setSmallText: function (e) {
- return (
- this.verifyNullAssets(), (this.game.assets.small_text = e), t
- );
- }.bind(this),
- setNull: function () {
- return (this.game.assets = null), t;
- }.bind(this),
- };
- return e(t), this;
- }
- setSpotifyAssetsLargeImage(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.large_image, this)
- : ((this.game.assets.large_image =
- 'spotify:' + e.replace(/spotify\:/g, '')),
- this)
- );
- }
- setSpotifyAssetsSmallImage(e) {
- return (
- this.verifyNull(),
- this.verifyNullAssets(),
- null === e
- ? (delete this.game.assets.small_image, this)
- : ((this.game.assets.small_image =
- 'spotify:' + e.replace(/spotify\:/g, '')),
- this)
- );
- }
- verifyNull() {
- this.game || (this.game = { name: '', type: 0 });
- }
- verifyNullAssets() {
- this.game.assets || (this.game.assets = {});
- }
- verifyNullTimestamps() {
- this.game.timestamps || (this.game.timestamps = {});
- }
- verifyNullParty() {
- this.game.party || (this.game.party = { id: '' });
- }
- verifyNullSecrets() {
- this.game.secrets || (this.game.secrets = {});
- }
- verifyNullButton() {
- this.game.buttons || (this.game.buttons = []);
- }
- }
- var f = {
- Rpc: h,
- PresenceTypes: l,
- PresenceTypesString: o,
- PresenceTypesNumber: a,
- RpcError: c,
- getRpcImages: u,
- getRpcImage: async function (e, t) {
- if ("string" != typeof t || !t)
- throw new c(`'${t}' không phải là String`);
- let s = await u(e),
- r = s.find((e) => e.name === t);
- if (!r)
- throw new c(
- `Image '${t}' không có trong ApplicationID ${e}. Các hình ảnh sẵn có là : ${s
- .map((e) => e.name)
- .join(", ")}.`
- );
- return r;
- },
- __esModule: !0,
- createSpotifyRpc: (e, t) =>
- new h(t)
- .setType(2)
- .setEndTimestamp(Date.now() + 864e5)
- .setSyncId("6l7PqWKsgm4NLomOE7Veou")
- .setSessionId(e.ws.connection.sessionID)
- .setPartyId("spotify:" + e.user.id)
- .setName("Spotify")
- .setId("spotify:1")
- .setFlags(48)
- .setCreatedAt(1561389854174)
- .setSecrets({
- join: "025ed05c71f639de8bfaa0d679d7c94b2fdce12f",
- spectate: "e7eb30d2ee025ed05c71ea495f770b76454ee4e0",
- match: "4b2fdce12f639de8bfa7e3591b71a0d679d7c93f",
- }),
- version: n.version,
- CustomStatus: class {
- constructor(e) {
- (this.game = {
- name: 'Custom Status',
- emoji: null,
- type: 4,
- state: null,
- }),
- e && (this.game = e);
- }
- setState(e) {
- return (this.game.state = e), this;
- }
- setEmoji(e) {
- let t = {
- setName: function (e) {
- return (
- this.game.emoji || (this.game.emoji = {}),
- (this.game.emoji.name = e),
- t
- );
- }.bind(this),
- setId: function (e) {
- return (
- this.game.emoji || (this.game.emoji = {}),
- (this.game.emoji.id = e),
- t
- );
- }.bind(this),
- setAnimated: function (e) {
- return (
- this.game.emoji || (this.game.emoji = {}),
- (this.game.emoji.animated = e),
- t
- );
- }.bind(this),
- };
- return e(t), this;
- }
- setDiscordEmoji(e) {
- return (
- (this.game.emoji = {
- name: e.name,
- id: e.id,
- animated: e.animated,
- }),
- this
- );
- }
- setUnicodeEmoji(e) {
- return (this.game.emoji = { name: e, id: null, animated: !1 }), this;
- }
- toDiscord() {
- return this.game;
- }
- toObject() {
- return this.game;
- }
- toString() {
- return `${this.game.name}: ${
- this.game.emoji
- ? ((e = this.game.emoji),
- (null === e.id
- ? e.name
- : `<${e.animated ? "a" : ""}:${e.name}:${e.id}>`) + " ")
- : ""
- }${this.game.state}`;
- var e;
- }
- },
- };
- (f.default = f), (e.exports = f);
- },
- function (e, t, s) {
- "use strict";
- function r(e) {
- return e && "object" == typeof e && "default" in e ? e.default : e;
- }
- Object.defineProperty(t, "__esModule", { value: !0 });
- var i = s(2),
- n = r(s(3)),
- o = r(s(4)),
- a = r(s(5)),
- l = r(s(6));
- const u = i.Readable,
- c = Symbol("buffer"),
- h = Symbol("type");
- class f {
- constructor() {
- this[h] = "";
- const e = arguments[0],
- t = arguments[1],
- s = [];
- let r = 0;
- if (e) {
- const t = e,
- i = Number(t.length);
- for (let e = 0; e < i; e++) {
- const i = t[e];
- let n;
- (n =
- i instanceof Buffer
- ? i
- : ArrayBuffer.isView(i)
- ? Buffer.from(i.buffer, i.byteOffset, i.byteLength)
- : i instanceof ArrayBuffer
- ? Buffer.from(i)
- : i instanceof f
- ? i[c]
- : Buffer.from("string" == typeof i ? i : String(i))),
- (r += n.length),
- s.push(n);
- }
- }
- this[c] = Buffer.concat(s);
- let i = t && void 0 !== t.type && String(t.type).toLowerCase();
- i && !/[^\u0020-\u007E]/.test(i) && (this[h] = i);
- }
- get size() {
- return this[c].length;
- }
- get type() {
- return this[h];
- }
- text() {
- return Promise.resolve(this[c].toString());
- }
- arrayBuffer() {
- const e = this[c],
- t = e.buffer.slice(e.byteOffset, e.byteOffset + e.byteLength);
- return Promise.resolve(t);
- }
- stream() {
- const e = new u();
- return (e._read = function () {}), e.push(this[c]), e.push(null), e;
- }
- toString() {
- return "[object Blob]";
- }
- slice() {
- const e = this.size,
- t = arguments[0],
- s = arguments[1];
- let r, i;
- (r = void 0 === t ? 0 : t < 0 ? Math.max(e + t, 0) : Math.min(t, e)),
- (i = void 0 === s ? e : s < 0 ? Math.max(e + s, 0) : Math.min(s, e));
- const n = Math.max(i - r, 0),
- o = this[c].slice(r, r + n),
- a = new f([], { type: arguments[2] });
- return (a[c] = o), a;
- }
- }
- function m(e, t, s) {
- Error.call(this, e),
- (this.message = e),
- (this.type = t),
- s && (this.code = this.errno = s.code),
- Error.captureStackTrace(this, this.constructor);
- }
- Object.defineProperties(f.prototype, {
- size: { enumerable: !0 },
- type: { enumerable: !0 },
- slice: { enumerable: !0 },
- }),
- Object.defineProperty(f.prototype, Symbol.toStringTag, {
- value: "Blob",
- writable: !1,
- enumerable: !1,
- configurable: !0,
- }),
- (m.prototype = Object.create(Error.prototype)),
- (m.prototype.constructor = m),
- (m.prototype.name = "FetchError");
- const d = Symbol("Body internals"),
- p = i.PassThrough;
- function g(e) {
- var t = this,
- s = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {},
- r = s.size;
- let n = void 0 === r ? 0 : r;
- var o = s.timeout;
- let a = void 0 === o ? 0 : o;
- null == e
- ? (e = null)
- : b(e)
- ? (e = Buffer.from(e.toString()))
- : v(e) ||
- Buffer.isBuffer(e) ||
- ("[object ArrayBuffer]" === Object.prototype.toString.call(e)
- ? (e = Buffer.from(e))
- : ArrayBuffer.isView(e)
- ? (e = Buffer.from(e.buffer, e.byteOffset, e.byteLength))
- : e instanceof i || (e = Buffer.from(String(e)))),
- (this[d] = { body: e, disturbed: !1, error: null }),
- (this.size = n),
- (this.timeout = a),
- e instanceof i &&
- e.on("error", function (e) {
- const s =
- "AbortError" === e.name
- ? e
- : new m(
- `Invalid response body while trying to fetch ${t.url}: ${e.message}`,
- "system",
- e
- );
- t[d].error = s;
- });
- }
- function y() {
- var e = this;
- if (this[d].disturbed)
- return g.Promise.reject(
- new TypeError(`body used already for: ${this.url}`)
- );
- if (((this[d].disturbed = !0), this[d].error))
- return g.Promise.reject(this[d].error);
- let t = this.body;
- if (null === t) return g.Promise.resolve(Buffer.alloc(0));
- if ((v(t) && (t = t.stream()), Buffer.isBuffer(t)))
- return g.Promise.resolve(t);
- if (!(t instanceof i)) return g.Promise.resolve(Buffer.alloc(0));
- let s = [],
- r = 0,
- n = !1;
- return new g.Promise(function (i, o) {
- let a;
- e.timeout &&
- (a = setTimeout(function () {
- (n = !0),
- o(
- new m(
- `Response timeout while trying to fetch ${e.url} (over ${e.timeout}ms)`,
- "body-timeout"
- )
- );
- }, e.timeout)),
- t.on("error", function (t) {
- "AbortError" === t.name
- ? ((n = !0), o(t))
- : o(
- new m(
- `Invalid response body while trying to fetch ${e.url}: ${t.message}`,
- "system",
- t
- )
- );
- }),
- t.on("data", function (t) {
- if (!n && null !== t) {
- if (e.size && r + t.length > e.size)
- return (
- (n = !0),
- void o(
- new m(
- `content size at ${e.url} over limit: ${e.size}`,
- "max-size"
- )
- )
- );
- (r += t.length), s.push(t);
- }
- }),
- t.on("end", function () {
- if (!n) {
- clearTimeout(a);
- try {
- i(Buffer.concat(s, r));
- } catch (t) {
- o(
- new m(
- `Could not create Buffer from response body for ${e.url}: ${t.message}`,
- "system",
- t
- )
- );
- }
- }
- });
- });
- }
- function b(e) {
- return (
- "object" == typeof e &&
- "function" == typeof e.append &&
- "function" == typeof e.delete &&
- "function" == typeof e.get &&
- "function" == typeof e.getAll &&
- "function" == typeof e.has &&
- "function" == typeof e.set &&
- ("URLSearchParams" === e.constructor.name ||
- "[object URLSearchParams]" === Object.prototype.toString.call(e) ||
- "function" == typeof e.sort)
- );
- }
- function v(e) {
- return (
- "object" == typeof e &&
- "function" == typeof e.arrayBuffer &&
- "string" == typeof e.type &&
- "function" == typeof e.stream &&
- "function" == typeof e.constructor &&
- "string" == typeof e.constructor.name &&
- /^(Blob|File)$/.test(e.constructor.name) &&
- /^(Blob|File)$/.test(e[Symbol.toStringTag])
- );
- }
- function w(e) {
- let t,
- s,
- r = e.body;
- if (e.bodyUsed) throw new Error("cannot clone body after it is used");
- return (
- r instanceof i &&
- "function" != typeof r.getBoundary &&
- ((t = new p()),
- (s = new p()),
- r.pipe(t),
- r.pipe(s),
- (e[d].body = t),
- (r = s)),
- r
- );
- }
- function S(e) {
- return null === e
- ? null
- : "string" == typeof e
- ? "text/plain;charset=UTF-8"
- : b(e)
- ? "application/x-www-form-urlencoded;charset=UTF-8"
- : v(e)
- ? e.type || null
- : Buffer.isBuffer(e)
- ? null
- : "[object ArrayBuffer]" === Object.prototype.toString.call(e)
- ? null
- : ArrayBuffer.isView(e)
- ? null
- : "function" == typeof e.getBoundary
- ? `multipart/form-data;boundary=${e.getBoundary()}`
- : e instanceof i
- ? null
- : "text/plain;charset=UTF-8";
- }
- function x(e) {
- const t = e.body;
- return null === t
- ? 0
- : v(t)
- ? t.size
- : Buffer.isBuffer(t)
- ? t.length
- : t &&
- "function" == typeof t.getLengthSync &&
- ((t._lengthRetrievers && 0 == t._lengthRetrievers.length) ||
- (t.hasKnownLength && t.hasKnownLength()))
- ? t.getLengthSync()
- : null;
- }
- (g.prototype = {
- get body() {
- return this[d].body;
- },
- get bodyUsed() {
- return this[d].disturbed;
- },
- arrayBuffer() {
- return y.call(this).then(function (e) {
- return e.buffer.slice(e.byteOffset, e.byteOffset + e.byteLength);
- });
- },
- blob() {
- let e = (this.headers && this.headers.get("content-type")) || "";
- return y.call(this).then(function (t) {
- return Object.assign(new f([], { type: e.toLowerCase() }), {
- [c]: t,
- });
- });
- },
- json() {
- var e = this;
- return y.call(this).then(function (t) {
- try {
- return JSON.parse(t.toString());
- } catch (t) {
- return g.Promise.reject(
- new m(
- `invalid json response body at ${e.url} reason: ${t.message}`,
- "invalid-json"
- )
- );
- }
- });
- },
- text() {
- return y.call(this).then(function (e) {
- return e.toString();
- });
- },
- buffer() {
- return y.call(this);
- },
- textConverted() {
- var e = this;
- return y.call(this).then(function (t) {
- return (function (e, t) {
- throw new Error(
- "The package `encoding` must be installed to use the textConverted() function"
- );
- const s = t.get("content-type");
- let r,
- i,
- n = "utf-8";
- s && (r = /charset=([^;]*)/i.exec(s));
- (i = e.slice(0, 1024).toString()),
- !r && i && (r = / 0 && void 0 !== arguments[0]
- ? arguments[0]
- : void 0;
- if (((this[_] = Object.create(null)), e instanceof P)) {
- const t = e.raw(),
- s = Object.keys(t);
- for (const e of s) for (const s of t[e]) this.append(e, s);
- } else if (null == e);
- else {
- if ("object" != typeof e)
- throw new TypeError("Provided initializer must be an object");
- {
- const t = e[Symbol.iterator];
- if (null != t) {
- if ("function" != typeof t)
- throw new TypeError("Header pairs must be iterable");
- const s = [];
- for (const t of e) {
- if (
- "object" != typeof t ||
- "function" != typeof t[Symbol.iterator]
- )
- throw new TypeError("Each header pair must be iterable");
- s.push(Array.from(t));
- }
- for (const e of s) {
- if (2 !== e.length)
- throw new TypeError(
- "Each header pair must be a name/value tuple"
- );
- this.append(e[0], e[1]);
- }
- } else
- for (const t of Object.keys(e)) {
- const s = e[t];
- this.append(t, s);
- }
- }
- }
- }
- get(e) {
- N((e = `${e}`));
- const t = O(this[_], e);
- return void 0 === t ? null : this[_][t].join(", ");
- }
- forEach(e) {
- let t =
- arguments.length > 1 && void 0 !== arguments[1]
- ? arguments[1]
- : void 0,
- s = E(this),
- r = 0;
- for (; r < s.length; ) {
- var i = s[r];
- const n = i[0],
- o = i[1];
- e.call(t, o, n, this), (s = E(this)), r++;
- }
- }
- set(e, t) {
- (t = `${t}`), N((e = `${e}`)), A(t);
- const s = O(this[_], e);
- this[_][void 0 !== s ? s : e] = [t];
- }
- append(e, t) {
- (t = `${t}`), N((e = `${e}`)), A(t);
- const s = O(this[_], e);
- void 0 !== s ? this[_][s].push(t) : (this[_][e] = [t]);
- }
- has(e) {
- return N((e = `${e}`)), void 0 !== O(this[_], e);
- }
- delete(e) {
- N((e = `${e}`));
- const t = O(this[_], e);
- void 0 !== t && delete this[_][t];
- }
- raw() {
- return this[_];
- }
- keys() {
- return $(this, "key");
- }
- values() {
- return $(this, "value");
- }
- [Symbol.iterator]() {
- return $(this, "key+value");
- }
- }
- function E(e) {
- let t =
- arguments.length > 1 && void 0 !== arguments[1]
- ? arguments[1]
- : "key+value";
- const s = Object.keys(e[_]).sort();
- return s.map(
- "key" === t
- ? function (e) {
- return e.toLowerCase();
- }
- : "value" === t
- ? function (t) {
- return e[_][t].join(", ");
- }
- : function (t) {
- return [t.toLowerCase(), e[_][t].join(", ")];
- }
- );
- }
- (P.prototype.entries = P.prototype[Symbol.iterator]),
- Object.defineProperty(P.prototype, Symbol.toStringTag, {
- value: "Headers",
- writable: !1,
- enumerable: !1,
- configurable: !0,
- }),
- Object.defineProperties(P.prototype, {
- get: { enumerable: !0 },
- forEach: { enumerable: !0 },
- set: { enumerable: !0 },
- append: { enumerable: !0 },
- has: { enumerable: !0 },
- delete: { enumerable: !0 },
- keys: { enumerable: !0 },
- values: { enumerable: !0 },
- entries: { enumerable: !0 },
- });
- const L = Symbol("internal");
- function $(e, t) {
- const s = Object.create(k);
- return (s[L] = { target: e, kind: t, index: 0 }), s;
- }
- const k = Object.setPrototypeOf(
- {
- next() {
- if (!this || Object.getPrototypeOf(this) !== k)
- throw new TypeError("Value of `this` is not a HeadersIterator");
- var e = this[L];
- const t = e.target,
- s = e.kind,
- r = e.index,
- i = E(t, s);
- return r >= i.length
- ? { value: void 0, done: !0 }
- : ((this[L].index = r + 1), { value: i[r], done: !1 });
- },
- },
- Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
- );
- function I(e) {
- const t = Object.assign({ __proto__: null }, e[_]),
- s = O(e[_], "Host");
- return void 0 !== s && (t[s] = t[s][0]), t;
- }
- Object.defineProperty(k, Symbol.toStringTag, {
- value: "HeadersIterator",
- writable: !1,
- enumerable: !1,
- configurable: !0,
- });
- const B = Symbol("Response internals"),
- C = n.STATUS_CODES;
- class R {
- constructor() {
- let e =
- arguments.length > 0 && void 0 !== arguments[0]
- ? arguments[0]
- : null,
- t =
- arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
- g.call(this, e, t);
- const s = t.status || 200,
- r = new P(t.headers);
- if (null != e && !r.has("Content-Type")) {
- const t = S(e);
- t && r.append("Content-Type", t);
- }
- this[B] = {
- url: t.url,
- status: s,
- statusText: t.statusText || C[s],
- headers: r,
- counter: t.counter,
- };
- }
- get url() {
- return this[B].url || "";
- }
- get status() {
- return this[B].status;
- }
- get ok() {
- return this[B].status >= 200 && this[B].status < 300;
- }
- get redirected() {
- return this[B].counter > 0;
- }
- get statusText() {
- return this[B].statusText;
- }
- get headers() {
- return this[B].headers;
- }
- clone() {
- return new R(w(this), {
- url: this.url,
- status: this.status,
- statusText: this.statusText,
- headers: this.headers,
- ok: this.ok,
- redirected: this.redirected,
- });
- }
- }
- g.mixIn(R.prototype),
- Object.defineProperties(R.prototype, {
- url: { enumerable: !0 },
- status: { enumerable: !0 },
- ok: { enumerable: !0 },
- redirected: { enumerable: !0 },
- statusText: { enumerable: !0 },
- headers: { enumerable: !0 },
- clone: { enumerable: !0 },
- }),
- Object.defineProperty(R.prototype, Symbol.toStringTag, {
- value: "Response",
- writable: !1,
- enumerable: !1,
- configurable: !0,
- });
- const z = Symbol("Request internals"),
- U = o.parse,
- q = o.format,
- F = "destroy" in i.Readable.prototype;
- function D(e) {
- return "object" == typeof e && "object" == typeof e[z];
- }
- class M {
- constructor(e) {
- let t,
- s =
- arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
- D(e)
- ? (t = U(e.url))
- : ((t = e && e.href ? U(e.href) : U(`${e}`)), (e = {}));
- let r = s.method || e.method || "GET";
- if (
- ((r = r.toUpperCase()),
- (null != s.body || (D(e) && null !== e.body)) &&
- ("GET" === r || "HEAD" === r))
- )
- throw new TypeError("Request with GET/HEAD method cannot have body");
- let i = null != s.body ? s.body : D(e) && null !== e.body ? w(e) : null;
- g.call(this, i, {
- timeout: s.timeout || e.timeout || 0,
- size: s.size || e.size || 0,
- });
- const n = new P(s.headers || e.headers || {});
- if (null != i && !n.has("Content-Type")) {
- const e = S(i);
- e && n.append("Content-Type", e);
- }
- let o = D(e) ? e.signal : null;
- if (
- ("signal" in s && (o = s.signal),
- null != o &&
- !(function (e) {
- const t = e && "object" == typeof e && Object.getPrototypeOf(e);
- return !(!t || "AbortSignal" !== t.constructor.name);
- })(o))
- )
- throw new TypeError(
- "Expected signal to be an instanceof AbortSignal"
- );
- (this[z] = {
- method: r,
- redirect: s.redirect || e.redirect || "follow",
- headers: n,
- parsedURL: t,
- signal: o,
- }),
- (this.follow =
- void 0 !== s.follow
- ? s.follow
- : void 0 !== e.follow
- ? e.follow
- : 20),
- (this.compress =
- void 0 !== s.compress
- ? s.compress
- : void 0 === e.compress || e.compress),
- (this.counter = s.counter || e.counter || 0),
- (this.agent = s.agent || e.agent);
- }
- get method() {
- return this[z].method;
- }
- get url() {
- return q(this[z].parsedURL);
- }
- get headers() {
- return this[z].headers;
- }
- get redirect() {
- return this[z].redirect;
- }
- get signal() {
- return this[z].signal;
- }
- clone() {
- return new M(this);
- }
- }
- function H(e) {
- Error.call(this, e),
- (this.type = "aborted"),
- (this.message = e),
- Error.captureStackTrace(this, this.constructor);
- }
- g.mixIn(M.prototype),
- Object.defineProperty(M.prototype, Symbol.toStringTag, {
- value: "Request",
- writable: !1,
- enumerable: !1,
- configurable: !0,
- }),
- Object.defineProperties(M.prototype, {
- method: { enumerable: !0 },
- url: { enumerable: !0 },
- headers: { enumerable: !0 },
- redirect: { enumerable: !0 },
- clone: { enumerable: !0 },
- signal: { enumerable: !0 },
- }),
- (H.prototype = Object.create(Error.prototype)),
- (H.prototype.constructor = H),
- (H.prototype.name = "AbortError");
- const G = i.PassThrough,
- V = o.resolve;
- function W(e, t) {
- if (!W.Promise)
- throw new Error(
- "native promise missing, set fetch.Promise to your favorite alternative"
- );
- return (
- (g.Promise = W.Promise),
- new W.Promise(function (r, o) {
- const c = new M(e, t);
- if ("data:" == c[z].parsedURL.protocol) {
- if ("get" !== c.method.toLowerCase())
- return o(
- new m("Cannot access " + e + " with another method than get.")
- );
- let t = q(c[z].parsedURL);
- if (((t = t.slice(5)), !t)) return o(new m("Invalid data url"));
- let s,
- i,
- [n, a] = t.split(",");
- if (n) {
- let [e, t] = n.split(";");
- (i = e || "text/plain"), (s = t || "ascii");
- } else (s = "ascii"), (i = "text/plain");
- let l = new u({}),
- h = new P();
- h.set("Content-Type", i + ";charset=ascii");
- const f = {
- url: c.url,
- status: 200,
- statusText: "",
- headers: h,
- size: 0,
- timeout: c.timeout,
- counter: c.counter,
- };
- let d = new R(l, f);
- return l.push(a, s), l.push(null), r(d);
- }
- if ("file:" == c[z].parsedURL.protocol) {
- let t = ["get", "delete", "post"];
- if (!t.includes(c.method.toLowerCase()))
- return o(
- new m(
- "Cannot access " +
- e +
- " with another method than " +
- t.join(", ") +
- "."
- )
- );
- let i = q(c[z].parsedURL);
- if (
- ((i = i.slice(5)),
- !(
- "win32" == process.platform
- ? /file:\/\/\/[A-Z]:\/(([\w ]+\/?(\.[\w ]+)?)+)?/
- : /file:\/\/\/((\w+\/?(\.\w+)?)+)?/
- ).test(q(c[z].parsedURL)))
- )
- return o(new m("INVALID URL"));
- i = i.slice(3);
- let n = s(7);
- return n.exists(
- i,
- (e) => (
- console.log(e),
- e
- ? n.stat(i, (e, t) => {
- if (e) return o(e);
- if (t.isDirectory())
- n.readdir(
- i,
- { encoding: "utf-8", withFileTypes: !0 },
- (e, t) => {
- if (e) return o(new m(e.message || e));
- let s = new P(),
- i = new u({}),
- n = JSON.stringify(
- t.map((e) => ({
- name: e.name,
- isDirectory: e.isDirectory(),
- isFile: e.isFile(),
- }))
- );
- s.set("type", "folder");
- const a = {
- url: c.url,
- status: 200,
- statusText: "FOLDER",
- headers: s,
- timeout: c.timeout,
- counter: c.counter,
- };
- let l = new R(i, a);
- return i.push(n), i.push(null), r(l);
- }
- );
- else if (t.isFile()) {
- let e = n.createReadStream(i, { encoding: "utf-8" });
- e.on("error", function (e) {
- o(
- new m(
- `request to ${c.url} failed, reason: ${e.message}`,
- "system",
- e
- )
- );
- });
- let t = new P();
- t.set("type", "file");
- let l = s(8).basename(i).split("."),
- u = "";
- function a(e) {
- u = e;
- }
- if (l[1]) {
- let e = l[l.length - 1].toLowerCase();
- if (["gif", "jpeg", "jpg", "png", "tiff"].includes(e))
- a("image/" + e);
- else
- switch (e) {
- case "tif":
- a("image/tiff");
- break;
- case "apng":
- a("image/png");
- break;
- case "ico":
- a("image/x-icon");
- break;
- case "svg":
- case "svgz":
- a("image/svg+xml");
- break;
- case "css":
- a("text/css");
- case "csv":
- a("text/csv");
- break;
- case "html":
- a("text/html");
- break;
- case "js":
- a("application/javascript");
- break;
- case "xml":
- a("text/xml");
- break;
- case "mpg":
- case "mpeg":
- case "mp1":
- case "mp2":
- case "mp3":
- case "m1v":
- case "mpv":
- case "m1a":
- case "m2a":
- case "mpa":
- a(
- (["mpe", "mpeg", "mpg"].includes(e)
- ? "video"
- : "audio") + "/mpeg"
- );
- break;
- case "mp4":
- case "m4a":
- case "m4p":
- case "m4b":
- case "m4r":
- case "m4v":
- let t = "audio";
- "mp4" == e && (t = "video"), a(t + "/mp4");
- break;
- case "mov":
- a("video/quicktime");
- break;
- case "wmv":
- case "wm":
- a("video/x-ms-wmv");
- break;
- case "avi":
- a("video/x-msvideo");
- break;
- case "flv":
- a("video/x-flv");
- break;
- case "json":
- a("application/json");
- break;
- case "zip":
- a("application/zip");
- break;
- case "jsonld":
- a("application/ld+json");
- break;
- case "ogg":
- case "ogv":
- case "oga":
- case "ogx":
- case "spx":
- case "opus":
- case "ogm":
- a("application/ogg");
- break;
- case "pdf":
- a("application/pdf");
- break;
- case "xhtml":
- case "xht":
- case "htm":
- a("application/xhtml+xml");
- break;
- case "wav":
- a("audio/wav");
- break;
- default:
- a("text/plain");
- }
- } else u = "text/plain";
- (u += ";charset=utf-8"), t.set("content-type", u);
- let h = e.pipe(G());
- const f = {
- url: c.url,
- status: 200,
- statusText: "FILE",
- headers: t,
- timeout: c.timeout,
- counter: c.counter,
- };
- let d = new R(h, f);
- return r(d);
- }
- })
- : (function () {
- let e = new P(),
- t = new u({});
- const s = {
- url: c.url,
- status: 404,
- statusText: "ERR_FILE_NOT_FOUND",
- headers: e,
- size: 0,
- timeout: c.timeout,
- counter: c.counter,
- };
- let i = new R(t, s);
- return t.push(null), r(i);
- })()
- )
- );
- }
- const h = (function (e) {
- const t = e[z].parsedURL,
- s = new P(e[z].headers);
- if (
- (s.has("Accept") || s.set("Accept", "*/*"),
- !t.protocol || !t.hostname)
- )
- throw new TypeError("Only absolute URLs are supported");
- if (!/^https?:$/.test(t.protocol))
- throw new TypeError("Only HTTP(S) protocols are supported");
- if (e.signal && e.body instanceof i.Readable && !F)
- throw new Error(
- "Cancellation of streamed requests with AbortSignal is not supported in node < 8"
- );
- let r = null;
- if (
- (null == e.body && /^(POST|PUT)$/i.test(e.method) && (r = "0"),
- null != e.body)
- ) {
- const t = x(e);
- "number" == typeof t && (r = String(t));
- }
- r && s.set("Content-Length", r),
- s.has("User-Agent") ||
- s.set(
- "User-Agent",
- "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
- ),
- e.compress &&
- !s.has("Accept-Encoding") &&
- s.set("Accept-Encoding", "gzip,deflate");
- let n = e.agent;
- return (
- "function" == typeof n && (n = n(t)),
- s.has("Connection") || n || s.set("Connection", "close"),
- Object.assign({}, t, {
- method: e.method,
- headers: I(s),
- agent: n,
- })
- );
- })(c),
- f = ("https:" === h.protocol ? a : n).request,
- d = c.signal;
- let p = null;
- const g = function () {
- let e = new H("The user aborted a request.");
- o(e),
- c.body && c.body instanceof i.Readable && c.body.destroy(e),
- p && p.body && p.body.emit("error", e);
- };
- if (d && d.aborted) return void g();
- const y = function () {
- g(), S();
- },
- b = f(h);
- let w;
- function S() {
- b.abort(), d && d.removeEventListener("abort", y), clearTimeout(w);
- }
- d && d.addEventListener("abort", y),
- c.timeout &&
- b.once("socket", function (e) {
- w = setTimeout(function () {
- o(new m(`network timeout at: ${c.url}`, "request-timeout")),
- S();
- }, c.timeout);
- }),
- b.on("error", function (e) {
- o(
- new m(
- `request to ${c.url} failed, reason: ${e.message}`,
- "system",
- e
- )
- ),
- S();
- }),
- b.on("response", function (e) {
- clearTimeout(w);
- const t = (function (e) {
- const t = new P();
- for (const s of Object.keys(e))
- if (!j.test(s))
- if (Array.isArray(e[s]))
- for (const r of e[s])
- T.test(r) ||
- (void 0 === t[_][s]
- ? (t[_][s] = [r])
- : t[_][s].push(r));
- else T.test(e[s]) || (t[_][s] = [e[s]]);
- return t;
- })(e.headers);
- if (W.isRedirect(e.statusCode)) {
- const s = t.get("Location"),
- i = null === s ? null : V(c.url, s);
- switch (c.redirect) {
- case "error":
- return (
- o(
- new m(
- `redirect mode is set to error: ${c.url}`,
- "no-redirect"
- )
- ),
- void S()
- );
- case "manual":
- if (null !== i)
- try {
- t.set("Location", i);
- } catch (e) {
- o(e);
- }
- break;
- case "follow":
- if (null === i) break;
- if (c.counter >= c.follow)
- return (
- o(
- new m(
- `maximum redirect reached at: ${c.url}`,
- "max-redirect"
- )
- ),
- void S()
- );
- const s = {
- headers: new P(c.headers),
- follow: c.follow,
- counter: c.counter + 1,
- agent: c.agent,
- compress: c.compress,
- method: c.method,
- body: c.body,
- signal: c.signal,
- timeout: c.timeout,
- };
- return 303 !== e.statusCode && c.body && null === x(c)
- ? (o(
- new m(
- "Cannot follow redirect with body being a readable stream",
- "unsupported-redirect"
- )
- ),
- void S())
- : ((303 !== e.statusCode &&
- ((301 !== e.statusCode && 302 !== e.statusCode) ||
- "POST" !== c.method)) ||
- ((s.method = "GET"),
- (s.body = void 0),
- s.headers.delete("content-length")),
- r(W(new M(i, s))),
- void S());
- }
- }
- e.once("end", function () {
- d && d.removeEventListener("abort", y);
- });
- let s = e.pipe(new G());
- const i = {
- url: c.url,
- status: e.statusCode,
- statusText: e.statusMessage,
- headers: t,
- size: c.size,
- timeout: c.timeout,
- counter: c.counter,
- },
- n = t.get("Content-Encoding");
- if (
- !c.compress ||
- "HEAD" === c.method ||
- null === n ||
- 204 === e.statusCode ||
- 304 === e.statusCode
- )
- return (p = new R(s, i)), void r(p);
- const a = { flush: l.Z_SYNC_FLUSH, finishFlush: l.Z_SYNC_FLUSH };
- if ("gzip" == n || "x-gzip" == n)
- return (
- (s = s.pipe(l.createGunzip(a))), (p = new R(s, i)), void r(p)
- );
- if ("deflate" != n && "x-deflate" != n) {
- if ("br" == n && "function" == typeof l.createBrotliDecompress)
- return (
- (s = s.pipe(l.createBrotliDecompress())),
- (p = new R(s, i)),
- void r(p)
- );
- (p = new R(s, i)), r(p);
- } else {
- e.pipe(new G()).once("data", function (e) {
- (s =
- 8 == (15 & e[0])
- ? s.pipe(l.createInflate())
- : s.pipe(l.createInflateRaw())),
- (p = new R(s, i)),
- r(p);
- });
- }
- }),
- (function (e, t) {
- const s = t.body;
- null === s
- ? e.end()
- : v(s)
- ? s.stream().pipe(e)
- : Buffer.isBuffer(s)
- ? (e.write(s), e.end())
- : s.pipe(e);
- })(b, c);
- })
- );
- }
- (W.isRedirect = function (e) {
- return 301 === e || 302 === e || 303 === e || 307 === e || 308 === e;
- }),
- (W.Promise = global.Promise),
- (e.exports = t = W),
- Object.defineProperty(t, "__esModule", { value: !0 }),
- (t.default = t),
- (t.Headers = P),
- (t.Request = M),
- (t.Response = R),
- (t.FetchError = m);
- },
- function (e, t) {
- e.exports = require("stream");
- },
- function (e, t) {
- e.exports = require("http");
- },
- function (e, t) {
- e.exports = require("url");
- },
- function (e, t) {
- e.exports = require("https");
- },
- function (e, t) {
- e.exports = require("zlib");
- },
- function (e, t) {
- e.exports = require("fs");
- },
- function (e, t) {
- e.exports = require("path");
- },
- function (e, t, s) {
- "use strict";
- Object.defineProperty(t, "__esModule", { value: !0 }),
- (t.default = {
- discord() {
- let e = "https://discord.com/api/v9";
- return {
- toString: () => e,
- users(t) {
- let s = e + "/users/" + t;
- return { toString: () => s, guilds: () => s + "/guilds" };
- },
- application(t) {
- let s = e + "/oauth2/applications/" + t;
- return { toString: () => s, assets: () => s + "/assets" };
- },
- };
- },
- ua: "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/0.0.305 Chrome/69.0.3497.128 Electron/4.0.8 Safari/537.36",
- acceptedLangs: "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
- });
- },
- function (e, t) {
- e.exports = require("../../package.json");
- },
- ]);
- module.exports.uuid = () => {
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (a) =>
- (a ^ ((Math.random() * 16) >> (a / 4))).toString(16),
- );
- };
\ No newline at end of file
diff --git a/src/client/BaseClient.js b/src/client/BaseClient.js
index cdedff8..3bf037f 100644
--- a/src/client/BaseClient.js
+++ b/src/client/BaseClient.js
@@ -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
*/
diff --git a/src/client/Client.js b/src/client/Client.js
index 56979a5..d198965 100644
--- a/src/client/Client.js
+++ b/src/client/Client.js
@@ -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}
+ * @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} 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}
- */
diff --git a/src/client/WebhookClient.js b/src/client/WebhookClient.js
index 1c66194..ce458a7 100644
--- a/src/client/WebhookClient.js
+++ b/src/client/WebhookClient.js
@@ -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');
diff --git a/src/client/actions/Action.js b/src/client/actions/Action.js
index f70d3ca..8d0b6df 100644
--- a/src/client/actions/Action.js
+++ b/src/client/actions/Action.js
@@ -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,
);
}
}
diff --git a/src/client/actions/ChannelCreate.js b/src/client/actions/ChannelCreate.js
index fdf8ddd..68eb2dc 100644
--- a/src/client/actions/ChannelCreate.js
+++ b/src/client/actions/ChannelCreate.js
@@ -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 };
}
diff --git a/src/client/actions/ChannelDelete.js b/src/client/actions/ChannelDelete.js
index acf03d9..43fdb32 100644
--- a/src/client/actions/ChannelDelete.js
+++ b/src/client/actions/ChannelDelete.js
@@ -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 };
}
}
diff --git a/src/client/actions/ChannelUpdate.js b/src/client/actions/ChannelUpdate.js
index 88ee7f1..34d1a86 100644
--- a/src/client/actions/ChannelUpdate.js
+++ b/src/client/actions/ChannelUpdate.js
@@ -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;
diff --git a/src/client/actions/GuildBanAdd.js b/src/client/actions/GuildBanAdd.js
index 2ef4b11..e97e789 100644
--- a/src/client/actions/GuildBanAdd.js
+++ b/src/client/actions/GuildBanAdd.js
@@ -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));
}
}
diff --git a/src/client/actions/GuildBanRemove.js b/src/client/actions/GuildBanRemove.js
index 8048efd..5154735 100644
--- a/src/client/actions/GuildBanRemove.js
+++ b/src/client/actions/GuildBanRemove.js
@@ -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);
}
}
}
diff --git a/src/client/actions/GuildDelete.js b/src/client/actions/GuildDelete.js
index eb0a44d..8ab860b 100644
--- a/src/client/actions/GuildDelete.js
+++ b/src/client/actions/GuildDelete.js
@@ -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();
}
}
diff --git a/src/client/actions/GuildEmojiCreate.js b/src/client/actions/GuildEmojiCreate.js
index 61858cf..ccc634c 100644
--- a/src/client/actions/GuildEmojiCreate.js
+++ b/src/client/actions/GuildEmojiCreate.js
@@ -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 };
}
}
diff --git a/src/client/actions/GuildEmojiDelete.js b/src/client/actions/GuildEmojiDelete.js
index e3373c2..0c20287 100644
--- a/src/client/actions/GuildEmojiDelete.js
+++ b/src/client/actions/GuildEmojiDelete.js
@@ -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 };
}
}
diff --git a/src/client/actions/GuildEmojiUpdate.js b/src/client/actions/GuildEmojiUpdate.js
index 6bf9657..9fa59c9 100644
--- a/src/client/actions/GuildEmojiUpdate.js
+++ b/src/client/actions/GuildEmojiUpdate.js
@@ -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 };
}
}
diff --git a/src/client/actions/GuildIntegrationsUpdate.js b/src/client/actions/GuildIntegrationsUpdate.js
index 28b9bbb..bbce076 100644
--- a/src/client/actions/GuildIntegrationsUpdate.js
+++ b/src/client/actions/GuildIntegrationsUpdate.js
@@ -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);
}
}
diff --git a/src/client/actions/GuildMemberRemove.js b/src/client/actions/GuildMemberRemove.js
index 646f4ec..5776696 100644
--- a/src/client/actions/GuildMemberRemove.js
+++ b/src/client/actions/GuildMemberRemove.js
@@ -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);
}
diff --git a/src/client/actions/GuildMemberUpdate.js b/src/client/actions/GuildMemberUpdate.js
index dc41a79..2839685 100644
--- a/src/client/actions/GuildMemberUpdate.js
+++ b/src/client/actions/GuildMemberUpdate.js
@@ -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);
}
}
}
diff --git a/src/client/actions/GuildRoleCreate.js b/src/client/actions/GuildRoleCreate.js
index 461443b..102e0f6 100644
--- a/src/client/actions/GuildRoleCreate.js
+++ b/src/client/actions/GuildRoleCreate.js
@@ -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 };
}
diff --git a/src/client/actions/GuildRoleDelete.js b/src/client/actions/GuildRoleDelete.js
index e043a1a..9d156db 100644
--- a/src/client/actions/GuildRoleDelete.js
+++ b/src/client/actions/GuildRoleDelete.js
@@ -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);
}
}
diff --git a/src/client/actions/GuildRoleUpdate.js b/src/client/actions/GuildRoleUpdate.js
index b0632c5..faea120 100644
--- a/src/client/actions/GuildRoleUpdate.js
+++ b/src/client/actions/GuildRoleUpdate.js
@@ -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 {
diff --git a/src/client/actions/GuildScheduledEventCreate.js b/src/client/actions/GuildScheduledEventCreate.js
index 0a2fb9b..b1a2d92 100644
--- a/src/client/actions/GuildScheduledEventCreate.js
+++ b/src/client/actions/GuildScheduledEventCreate.js
@@ -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 };
}
diff --git a/src/client/actions/GuildScheduledEventDelete.js b/src/client/actions/GuildScheduledEventDelete.js
index 636bfc5..0e7baa1 100644
--- a/src/client/actions/GuildScheduledEventDelete.js
+++ b/src/client/actions/GuildScheduledEventDelete.js
@@ -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 };
}
diff --git a/src/client/actions/GuildScheduledEventUpdate.js b/src/client/actions/GuildScheduledEventUpdate.js
index 7cabd85..dfb86db 100644
--- a/src/client/actions/GuildScheduledEventUpdate.js
+++ b/src/client/actions/GuildScheduledEventUpdate.js
@@ -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 };
}
diff --git a/src/client/actions/GuildScheduledEventUserAdd.js b/src/client/actions/GuildScheduledEventUserAdd.js
index 03520db..ea6e4c4 100644
--- a/src/client/actions/GuildScheduledEventUserAdd.js
+++ b/src/client/actions/GuildScheduledEventUserAdd.js
@@ -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 };
}
diff --git a/src/client/actions/GuildScheduledEventUserRemove.js b/src/client/actions/GuildScheduledEventUserRemove.js
index 2a04849..b32e3ce 100644
--- a/src/client/actions/GuildScheduledEventUserRemove.js
+++ b/src/client/actions/GuildScheduledEventUserRemove.js
@@ -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 };
}
diff --git a/src/client/actions/GuildStickerCreate.js b/src/client/actions/GuildStickerCreate.js
index 7d81de9..c02cafa 100644
--- a/src/client/actions/GuildStickerCreate.js
+++ b/src/client/actions/GuildStickerCreate.js
@@ -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 };
}
}
diff --git a/src/client/actions/GuildStickerDelete.js b/src/client/actions/GuildStickerDelete.js
index 7fd6b57..4adfe52 100644
--- a/src/client/actions/GuildStickerDelete.js
+++ b/src/client/actions/GuildStickerDelete.js
@@ -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 };
}
}
diff --git a/src/client/actions/GuildStickerUpdate.js b/src/client/actions/GuildStickerUpdate.js
index 5561c7e..0c3edc7 100644
--- a/src/client/actions/GuildStickerUpdate.js
+++ b/src/client/actions/GuildStickerUpdate.js
@@ -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 };
}
}
diff --git a/src/client/actions/GuildUpdate.js b/src/client/actions/GuildUpdate.js
index ef1f51b..78a8afb 100644
--- a/src/client/actions/GuildUpdate.js
+++ b/src/client/actions/GuildUpdate.js
@@ -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,
diff --git a/src/client/actions/InteractionCreate.js b/src/client/actions/InteractionCreate.js
index 8c36ec9..04774a6 100644
--- a/src/client/actions/InteractionCreate.js
+++ b/src/client/actions/InteractionCreate.js
@@ -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');
+ }
}
}
diff --git a/src/client/actions/InviteCreate.js b/src/client/actions/InviteCreate.js
index 2dc3019..2f7c216 100644
--- a/src/client/actions/InviteCreate.js
+++ b/src/client/actions/InviteCreate.js
@@ -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 };
}
}
diff --git a/src/client/actions/InviteDelete.js b/src/client/actions/InviteDelete.js
index 37b8143..47c77fa 100644
--- a/src/client/actions/InviteDelete.js
+++ b/src/client/actions/InviteDelete.js
@@ -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 };
}
}
diff --git a/src/client/actions/MessageCreate.js b/src/client/actions/MessageCreate.js
index 9a099e2..894886e 100644
--- a/src/client/actions/MessageCreate.js
+++ b/src/client/actions/MessageCreate.js
@@ -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 };
}
diff --git a/src/client/actions/MessageDelete.js b/src/client/actions/MessageDelete.js
index cb55c67..22921d3 100644
--- a/src/client/actions/MessageDelete.js
+++ b/src/client/actions/MessageDelete.js
@@ -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);
}
}
diff --git a/src/client/actions/MessageDeleteBulk.js b/src/client/actions/MessageDeleteBulk.js
index 148e665..31a3717 100644
--- a/src/client/actions/MessageDeleteBulk.js
+++ b/src/client/actions/MessageDeleteBulk.js
@@ -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} 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 {};
diff --git a/src/client/actions/MessageReactionAdd.js b/src/client/actions/MessageReactionAdd.js
index ea97bd6..736b1e4 100644
--- a/src/client/actions/MessageReactionAdd.js
+++ b/src/client/actions/MessageReactionAdd.js
@@ -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 };
}
diff --git a/src/client/actions/MessageReactionRemove.js b/src/client/actions/MessageReactionRemove.js
index 9ca3a8e..7a05949 100644
--- a/src/client/actions/MessageReactionRemove.js
+++ b/src/client/actions/MessageReactionRemove.js
@@ -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 };
}
diff --git a/src/client/actions/MessageReactionRemoveAll.js b/src/client/actions/MessageReactionRemoveAll.js
index b1c023f..e2a3924 100644
--- a/src/client/actions/MessageReactionRemoveAll.js
+++ b/src/client/actions/MessageReactionRemoveAll.js
@@ -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 };
}
diff --git a/src/client/actions/MessageReactionRemoveEmoji.js b/src/client/actions/MessageReactionRemoveEmoji.js
index 3290214..f8f28e8 100644
--- a/src/client/actions/MessageReactionRemoveEmoji.js
+++ b/src/client/actions/MessageReactionRemoveEmoji.js
@@ -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 };
}
}
diff --git a/src/client/actions/MessageUpdate.js b/src/client/actions/MessageUpdate.js
index fe757c0..9448ed6 100644
--- a/src/client/actions/MessageUpdate.js
+++ b/src/client/actions/MessageUpdate.js
@@ -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);
diff --git a/src/client/actions/PresenceUpdate.js b/src/client/actions/PresenceUpdate.js
index 0b4aaab..a09688b 100644
--- a/src/client/actions/PresenceUpdate.js
+++ b/src/client/actions/PresenceUpdate.js
@@ -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);
}
}
}
diff --git a/src/client/actions/StageInstanceCreate.js b/src/client/actions/StageInstanceCreate.js
index 4edd530..bff4591 100644
--- a/src/client/actions/StageInstanceCreate.js
+++ b/src/client/actions/StageInstanceCreate.js
@@ -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 };
}
diff --git a/src/client/actions/StageInstanceDelete.js b/src/client/actions/StageInstanceDelete.js
index 0d5da38..a227a9c 100644
--- a/src/client/actions/StageInstanceDelete.js
+++ b/src/client/actions/StageInstanceDelete.js
@@ -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 };
}
diff --git a/src/client/actions/StageInstanceUpdate.js b/src/client/actions/StageInstanceUpdate.js
index 008a53c..17d74f9 100644
--- a/src/client/actions/StageInstanceUpdate.js
+++ b/src/client/actions/StageInstanceUpdate.js
@@ -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 };
}
diff --git a/src/client/actions/ThreadCreate.js b/src/client/actions/ThreadCreate.js
index a8ff6c6..f7c36e6 100644
--- a/src/client/actions/ThreadCreate.js
+++ b/src/client/actions/ThreadCreate.js
@@ -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 };
}
diff --git a/src/client/actions/ThreadDelete.js b/src/client/actions/ThreadDelete.js
index 3ec81a4..9307dd2 100644
--- a/src/client/actions/ThreadDelete.js
+++ b/src/client/actions/ThreadDelete.js
@@ -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 };
diff --git a/src/client/actions/ThreadListSync.js b/src/client/actions/ThreadListSync.js
index bad1a47..1ffa7d5 100644
--- a/src/client/actions/ThreadListSync.js
+++ b/src/client/actions/ThreadListSync.js
@@ -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} threads The threads that were synced
*/
- client.emit(Events.ThreadListSync, syncedThreads);
+ client.emit(Events.THREAD_LIST_SYNC, syncedThreads);
return {
syncedThreads,
diff --git a/src/client/actions/ThreadMemberUpdate.js b/src/client/actions/ThreadMemberUpdate.js
index 0b17f70..b84bcac 100644
--- a/src/client/actions/ThreadMemberUpdate.js
+++ b/src/client/actions/ThreadMemberUpdate.js
@@ -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 {};
}
diff --git a/src/client/actions/ThreadMembersUpdate.js b/src/client/actions/ThreadMembersUpdate.js
index 26ab70e..c9833d6 100644
--- a/src/client/actions/ThreadMembersUpdate.js
+++ b/src/client/actions/ThreadMembersUpdate.js
@@ -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} oldMembers The members before the update
* @param {Collection} 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 {};
}
diff --git a/src/client/actions/TypingStart.js b/src/client/actions/TypingStart.js
index 4e79920..9d0ae37 100644
--- a/src/client/actions/TypingStart.js
+++ b/src/client/actions/TypingStart.js
@@ -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));
}
}
}
diff --git a/src/client/actions/UserUpdate.js b/src/client/actions/UserUpdate.js
index 1bf236a..df992f7 100644
--- a/src/client/actions/UserUpdate.js
+++ b/src/client/actions/UserUpdate.js
@@ -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,
diff --git a/src/client/actions/VoiceStateUpdate.js b/src/client/actions/VoiceStateUpdate.js
index fc7400f..f750e04 100644
--- a/src/client/actions/VoiceStateUpdate.js
+++ b/src/client/actions/VoiceStateUpdate.js
@@ -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);
}
}
}
diff --git a/src/client/actions/WebhooksUpdate.js b/src/client/actions/WebhooksUpdate.js
index b362b3b..7efccd7 100644
--- a/src/client/actions/WebhooksUpdate.js
+++ b/src/client/actions/WebhooksUpdate.js
@@ -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);
}
}
diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js
index 192e700..e6ea6ea 100644
--- a/src/client/voice/ClientVoiceManager.js
+++ b/src/client/voice/ClientVoiceManager.js
@@ -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();
diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js
index a08b88a..65a23f9 100644
--- a/src/client/websocket/WebSocketManager.js
+++ b/src/client/websocket/WebSocketManager.js
@@ -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} 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}
*/
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();
}
diff --git a/src/client/websocket/WebSocketShard.js b/src/client/websocket/WebSocketShard.js
index 34f34f7..35174f6 100644
--- a/src/client/websocket/WebSocketShard.js
+++ b/src/client/websocket/WebSocketShard.js
@@ -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} 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);
}
}
diff --git a/src/client/websocket/handlers/APPLICATION_COMMAND_CREATE.js b/src/client/websocket/handlers/APPLICATION_COMMAND_CREATE.js
new file mode 100644
index 00000000..17af7d5
--- /dev/null
+++ b/src/client/websocket/handlers/APPLICATION_COMMAND_CREATE.js
@@ -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);
+};
diff --git a/src/client/websocket/handlers/APPLICATION_COMMAND_DELETE.js b/src/client/websocket/handlers/APPLICATION_COMMAND_DELETE.js
new file mode 100644
index 00000000..4b09274
--- /dev/null
+++ b/src/client/websocket/handlers/APPLICATION_COMMAND_DELETE.js
@@ -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);
+};
diff --git a/src/client/websocket/handlers/APPLICATION_COMMAND_UPDATE.js b/src/client/websocket/handlers/APPLICATION_COMMAND_UPDATE.js
new file mode 100644
index 00000000..730d7f1e
--- /dev/null
+++ b/src/client/websocket/handlers/APPLICATION_COMMAND_UPDATE.js
@@ -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);
+};
diff --git a/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js b/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
index c46e527..b49e311 100644
--- a/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
+++ b/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
@@ -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);
}
};
diff --git a/src/client/websocket/handlers/CHANNEL_UPDATE.js b/src/client/websocket/handlers/CHANNEL_UPDATE.js
index 8f35121..d441478 100644
--- a/src/client/websocket/handlers/CHANNEL_UPDATE.js
+++ b/src/client/websocket/handlers/CHANNEL_UPDATE.js
@@ -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);
}
};
diff --git a/src/client/websocket/handlers/GUILD_CREATE.js b/src/client/websocket/handlers/GUILD_CREATE.js
index 7202dc8..acff7c2 100644
--- a/src/client/websocket/handlers/GUILD_CREATE.js
+++ b/src/client/websocket/handlers/GUILD_CREATE.js
@@ -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);
}
}
};
diff --git a/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js b/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
index 6f7ca7e..f9ac7f9 100644
--- a/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
+++ b/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
@@ -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,
diff --git a/src/client/websocket/handlers/GUILD_MEMBER_ADD.js b/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
index fece5d7..67590db 100644
--- a/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
+++ b/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
@@ -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);
}
}
};
diff --git a/src/client/websocket/handlers/MESSAGE_UPDATE.js b/src/client/websocket/handlers/MESSAGE_UPDATE.js
index c2a470b..7428e90 100644
--- a/src/client/websocket/handlers/MESSAGE_UPDATE.js
+++ b/src/client/websocket/handlers/MESSAGE_UPDATE.js
@@ -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);
}
};
diff --git a/src/client/websocket/handlers/READY.js b/src/client/websocket/handlers/READY.js
index 4f3cb38..334e1fa 100644
--- a/src/client/websocket/handlers/READY.js
+++ b/src/client/websocket/handlers/READY.js
@@ -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';
diff --git a/src/client/websocket/handlers/RESUMED.js b/src/client/websocket/handlers/RESUMED.js
index 39824bc..bf72d3a 100644
--- a/src/client/websocket/handlers/RESUMED.js
+++ b/src/client/websocket/handlers/RESUMED.js
@@ -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);
};
diff --git a/src/client/websocket/handlers/THREAD_UPDATE.js b/src/client/websocket/handlers/THREAD_UPDATE.js
index 481dcd4..795cb29 100644
--- a/src/client/websocket/handlers/THREAD_UPDATE.js
+++ b/src/client/websocket/handlers/THREAD_UPDATE.js
@@ -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);
}
};
diff --git a/src/client/websocket/handlers/index.js b/src/client/websocket/handlers/index.js
index d7739c1..65880ad 100644
--- a/src/client/websocket/handlers/index.js
+++ b/src/client/websocket/handlers/index.js
@@ -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')],
diff --git a/src/errors/Messages.js b/src/errors/Messages.js
index 483fcbb..2823ed6 100644
--- a/src/errors/Messages.js
+++ b/src/errors/Messages.js
@@ -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);
diff --git a/src/index.js b/src/index.js
index 34812cd..eb99a64 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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');
diff --git a/src/managers/ApplicationCommandManager.js b/src/managers/ApplicationCommandManager.js
index b04ecb2..5456dd5 100644
--- a/src/managers/ApplicationCommandManager.js
+++ b/src/managers/ApplicationCommandManager.js
@@ -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,
};
diff --git a/src/managers/ApplicationCommandPermissionsManager.js b/src/managers/ApplicationCommandPermissionsManager.js
index b944e1d..a93a8f2 100644
--- a/src/managers/ApplicationCommandPermissionsManager.js
+++ b/src/managers/ApplicationCommandPermissionsManager.js
@@ -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;
diff --git a/src/managers/CachedManager.js b/src/managers/CachedManager.js
index 1058285..0f7e914 100644
--- a/src/managers/CachedManager.js
+++ b/src/managers/CachedManager.js
@@ -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);
diff --git a/src/managers/CategoryChannelChildManager.js b/src/managers/CategoryChannelChildManager.js
deleted file mode 100644
index 0280251..00000000
--- a/src/managers/CategoryChannelChildManager.js
+++ /dev/null
@@ -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}
- * @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} [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.
- * You cannot create a channel of type {@link ChannelType.GuildCategory} inside a CategoryChannel.
- * @param {string} name The name of the new channel
- * @param {CategoryCreateChannelOptions} options Options for creating the new channel
- * @returns {Promise}
- */
- create(name, options) {
- return this.guild.channels.create(name, {
- ...options,
- parent: this.channel.id,
- });
- }
-}
-
-module.exports = CategoryChannelChildManager;
diff --git a/src/managers/ChannelManager.js b/src/managers/ChannelManager.js
index 9142702..06b25dc 100644
--- a/src/managers/ChannelManager.js
+++ b/src/managers/ChannelManager.js
@@ -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;
}
diff --git a/src/managers/GuildBanManager.js b/src/managers/GuildBanManager.js
index e882a56..e7039b3 100644
--- a/src/managers/GuildBanManager.js
+++ b/src/managers/GuildBanManager.js
@@ -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) {
diff --git a/src/managers/GuildChannelManager.js b/src/managers/GuildChannelManager.js
index 09a4f39..687816f 100644
--- a/src/managers/GuildChannelManager.js
+++ b/src/managers/GuildChannelManager.js
@@ -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} 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} [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}
- * @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}
- * @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>}
- * @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.
* Only one channel's parent can be changed at a time
@@ -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}
- * @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;
diff --git a/src/managers/GuildEmojiManager.js b/src/managers/GuildEmojiManager.js
index 1935d03..58ffa62 100644
--- a/src/managers/GuildEmojiManager.js
+++ b/src/managers/GuildEmojiManager.js
@@ -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}
- */
- 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}
- */
- 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}
- */
- 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;
diff --git a/src/managers/GuildInviteManager.js b/src/managers/GuildInviteManager.js
index 435d08e..c229a58 100644
--- a/src/managers/GuildInviteManager.js
+++ b/src/managers/GuildInviteManager.js
@@ -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,
diff --git a/src/managers/GuildManager.js b/src/managers/GuildManager.js
index d76f9f7..ad6c0d0 100644
--- a/src/managers/GuildManager.js
+++ b/src/managers/GuildManager.js
@@ -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());
}
}
diff --git a/src/managers/GuildMemberManager.js b/src/managers/GuildMemberManager.js
index 857b7b4..9a1ddb9 100644
--- a/src/managers/GuildMemberManager.js
+++ b/src/managers/GuildMemberManager.js
@@ -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>}
*/
- 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>}
*/
- 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 {
* It's recommended to set {@link GuildPruneMembersOptions#count options.count}
* to `false` for large guilds.
* @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);
});
}
}
diff --git a/src/managers/GuildMemberRoleManager.js b/src/managers/GuildMemberRoleManager.js
index ce645b1..bdb60ae 100644
--- a/src/managers/GuildMemberRoleManager.js
+++ b/src/managers/GuildMemberRoleManager.js
@@ -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');
diff --git a/src/managers/GuildScheduledEventManager.js b/src/managers/GuildScheduledEventManager.js
index 98fdb2b..b35e45c 100644
--- a/src/managers/GuildScheduledEventManager.js
+++ b/src/managers/GuildScheduledEventManager.js
@@ -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
- * This is required if `entityType` is {@link GuildScheduledEventEntityType.External}
+ * This is required if `entityType` is 'EXTERNAL'
* @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
- * This is required if `entityType` is {@link GuildScheduledEventEntityType.StageInstance} or
- * {@link GuildScheduledEventEntityType.Voice}
+ * This is required if `entityType` is 'STAGE_INSTANCE' or `VOICE`
* @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the
* guild scheduled event
- * This is required if `entityType` is {@link GuildScheduledEventEntityType.External}
- * @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
+ * This is required if `entityType` is 'EXTERNAL'
* @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
- * This is required if `entityType` is {@link GuildScheduledEventEntityType.External}
+ * This is required if `entityType` is 'EXTERNAL'
*/
/**
@@ -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
- * This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is
- * {@link GuildScheduledEventEntityType.External}
- * @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
+ * This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is 'EXTERNAL'
* @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(
diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js
index 29202cf..68abfb0 100644
--- a/src/managers/GuildStickerManager.js
+++ b/src/managers/GuildStickerManager.js
@@ -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}
- */
- 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;
diff --git a/src/managers/MessageManager.js b/src/managers/MessageManager.js
index 9331d05..a1e9a8b 100644
--- a/src/managers/MessageManager.js
+++ b/src/managers/MessageManager.js
@@ -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}
*/
- 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}
*/
- 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;
diff --git a/src/managers/PermissionOverwriteManager.js b/src/managers/PermissionOverwriteManager.js
index 1dfb767..8dbc881 100644
--- a/src/managers/PermissionOverwriteManager.js
+++ b/src/managers/PermissionOverwriteManager.js
@@ -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;
}
diff --git a/src/managers/ReactionManager.js b/src/managers/ReactionManager.js
index b30635e..b587a0e 100644
--- a/src/managers/ReactionManager.js
+++ b/src/managers/ReactionManager.js
@@ -1,6 +1,5 @@
'use strict';
-const { Routes } = require('discord-api-types/v9');
const CachedManager = require('./CachedManager');
const MessageReaction = require('../structures/MessageReaction');
diff --git a/src/managers/ReactionUserManager.js b/src/managers/ReactionUserManager.js
index 41f6a03..cc86187 100644
--- a/src/managers/ReactionUserManager.js
+++ b/src/managers/ReactionUserManager.js
@@ -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;
}
}
diff --git a/src/managers/RoleManager.js b/src/managers/RoleManager.js
index 1dd64eb..94f35b4 100644
--- a/src/managers/RoleManager.js
+++ b/src/managers/RoleManager.js
@@ -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}
- * @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,
diff --git a/src/managers/StageInstanceManager.js b/src/managers/StageInstanceManager.js
index 68f1f77..478f26f 100644
--- a/src/managers/StageInstanceManager.js
+++ b/src/managers/StageInstanceManager.js
@@ -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();
diff --git a/src/managers/ThreadManager.js b/src/managers/ThreadManager.js
index 34cd12e..9cd4f04 100644
--- a/src/managers/ThreadManager.js
+++ b/src/managers/ThreadManager.js
@@ -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. If this is defined then type
* of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored
- * @property {ThreadChannelTypes|number} [type] The type of thread to create.
- * Defaults to {@link ChannelType.GuildPublicThread} if created in a {@link TextChannel}
- * When creating threads in a {@link NewsChannel} this is ignored and is always
- * {@link ChannelType.GuildNewsThread}
+ * @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if
+ * created in a {@link TextChannel} When creating threads in a {@link NewsChannel} this is ignored and is always
+ * `GUILD_NEWS_THREAD`
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
- * Can only be set when type will be {@link ChannelType.GuildPrivateThread}
+ * Can only be set when type will be `GUILD_PRIVATE_THREAD`
+ * @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 });
}
diff --git a/src/managers/ThreadMemberManager.js b/src/managers/ThreadMemberManager.js
index 366894d..ce6988e 100644
--- a/src/managers/ThreadMemberManager.js
+++ b/src/managers/ThreadMemberManager.js
@@ -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>}
*/
- 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);
}
diff --git a/src/managers/UserManager.js b/src/managers/UserManager.js
index fc6b8e9..90d82af 100644
--- a/src/managers/UserManager.js
+++ b/src/managers/UserManager.js
@@ -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}
+ * @returns {Promise}
*/
async fetchFlags(user, options) {
return (await this.fetch(user, options)).flags;
diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js
index 4a73346..d3b5d5e 100644
--- a/src/rest/APIRequest.js
+++ b/src/rest/APIRequest.js
@@ -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;
\ No newline at end of file
+module.exports = APIRequest;
diff --git a/src/rest/APIRouter.js b/src/rest/APIRouter.js
index 6923a30..b22b37f 100644
--- a/src/rest/APIRouter.js
+++ b/src/rest/APIRouter.js
@@ -50,4 +50,4 @@ function buildRoute(manager) {
return new Proxy(noop, handler);
}
-module.exports = buildRoute;
\ No newline at end of file
+module.exports = buildRoute;
diff --git a/src/rest/DiscordAPIError.js b/src/rest/DiscordAPIError.js
index 4e75562..3137b7b 100644
--- a/src/rest/DiscordAPIError.js
+++ b/src/rest/DiscordAPIError.js
@@ -79,4 +79,4 @@ module.exports = DiscordAPIError;
/**
* @external APIError
* @see {@link https://discord.com/developers/docs/reference#error-messages}
- */
\ No newline at end of file
+ */
diff --git a/src/rest/HTTPError.js b/src/rest/HTTPError.js
index cec44e9..0e9ab9f 100644
--- a/src/rest/HTTPError.js
+++ b/src/rest/HTTPError.js
@@ -58,4 +58,4 @@ class HTTPError extends Error {
}
}
-module.exports = HTTPError;
\ No newline at end of file
+module.exports = HTTPError;
diff --git a/src/rest/RESTManager.js b/src/rest/RESTManager.js
index 84cecf2..485b7bb 100644
--- a/src/rest/RESTManager.js
+++ b/src/rest/RESTManager.js
@@ -29,9 +29,11 @@ class RESTManager {
}
getAuth() {
- const token = this.client.token ?? this.client.accessToken;
- if (token && !this.client.bot) return `${token}`;
- else if(token && this.client.bot) return `Bot ${token}`;
+ if (this.client.token && this.client.user && this.client.user.bot) {
+ return `Bot ${this.client.token}`;
+ } else if (this.client.token) {
+ return this.client.token;
+ }
throw new Error('TOKEN_MISSING');
}
@@ -60,4 +62,4 @@ class RESTManager {
}
}
-module.exports = RESTManager;
\ No newline at end of file
+module.exports = RESTManager;
diff --git a/src/rest/RateLimitError.js b/src/rest/RateLimitError.js
index 0be61d0..494954c 100644
--- a/src/rest/RateLimitError.js
+++ b/src/rest/RateLimitError.js
@@ -52,4 +52,4 @@ class RateLimitError extends Error {
}
}
-module.exports = RateLimitError;
\ No newline at end of file
+module.exports = RateLimitError;
diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js
index 59d197f..355e0ea 100644
--- a/src/rest/RequestHandler.js
+++ b/src/rest/RequestHandler.js
@@ -376,4 +376,4 @@ module.exports = RequestHandler;
/**
* @external Response
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Response}
- */
\ No newline at end of file
+ */
diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js
index ceaab75..67d9d03 100644
--- a/src/sharding/Shard.js
+++ b/src/sharding/Shard.js
@@ -3,7 +3,7 @@
const EventEmitter = require('node:events');
const path = require('node:path');
const process = require('node:process');
-const { setTimeout, clearTimeout } = require('node:timers');
+const { setTimeout } = require('node:timers');
const { setTimeout: sleep } = require('node:timers/promises');
const { Error } = require('../errors');
const Util = require('../util/Util');
@@ -249,18 +249,14 @@ class Shard extends EventEmitter {
const listener = message => {
if (message?._fetchProp !== prop) return;
child.removeListener('message', listener);
- this.decrementMaxListeners(child);
this._fetches.delete(prop);
if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error));
};
-
- this.incrementMaxListeners(child);
child.on('message', listener);
this.send({ _fetchProp: prop }).catch(err => {
child.removeListener('message', listener);
- this.decrementMaxListeners(child);
this._fetches.delete(prop);
reject(err);
});
@@ -292,18 +288,14 @@ class Shard extends EventEmitter {
const listener = message => {
if (message?._eval !== _eval) return;
child.removeListener('message', listener);
- this.decrementMaxListeners(child);
this._evals.delete(_eval);
if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error));
};
-
- this.incrementMaxListeners(child);
child.on('message', listener);
this.send({ _eval }).catch(err => {
child.removeListener('message', listener);
- this.decrementMaxListeners(child);
this._evals.delete(_eval);
reject(err);
});
@@ -414,30 +406,6 @@ class Shard extends EventEmitter {
if (respawn) this.spawn(timeout).catch(err => this.emit('error', err));
}
-
- /**
- * Increments max listeners by one for a given emitter, if they are not zero.
- * @param {EventEmitter|process} emitter The emitter that emits the events.
- * @private
- */
- incrementMaxListeners(emitter) {
- const maxListeners = emitter.getMaxListeners();
- if (maxListeners !== 0) {
- emitter.setMaxListeners(maxListeners + 1);
- }
- }
-
- /**
- * Decrements max listeners by one for a given emitter, if they are not zero.
- * @param {EventEmitter|process} emitter The emitter that emits the events.
- * @private
- */
- decrementMaxListeners(emitter) {
- const maxListeners = emitter.getMaxListeners();
- if (maxListeners !== 0) {
- emitter.setMaxListeners(maxListeners - 1);
- }
- }
}
module.exports = Shard;
diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js
index 0772af6..81cba42 100644
--- a/src/sharding/ShardClientUtil.js
+++ b/src/sharding/ShardClientUtil.js
@@ -2,7 +2,7 @@
const process = require('node:process');
const { Error } = require('../errors');
-const Events = require('../util/Events');
+const { Events } = require('../util/Constants');
const Util = require('../util/Util');
/**
@@ -111,16 +111,13 @@ class ShardClientUtil {
const listener = message => {
if (message?._sFetchProp !== prop || message._sFetchPropShard !== shard) return;
parent.removeListener('message', listener);
- this.decrementMaxListeners(parent);
if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error));
};
- this.incrementMaxListeners(parent);
parent.on('message', listener);
this.send({ _sFetchProp: prop, _sFetchPropShard: shard }).catch(err => {
parent.removeListener('message', listener);
- this.decrementMaxListeners(parent);
reject(err);
});
});
@@ -149,15 +146,13 @@ class ShardClientUtil {
const listener = message => {
if (message?._sEval !== script || message._sEvalShard !== options.shard) return;
parent.removeListener('message', listener);
- this.decrementMaxListeners(parent);
if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error));
};
- this.incrementMaxListeners(parent);
parent.on('message', listener);
+
this.send({ _sEval: script, _sEvalShard: options.shard }).catch(err => {
parent.removeListener('message', listener);
- this.decrementMaxListeners(parent);
reject(err);
});
});
@@ -210,13 +205,10 @@ class ShardClientUtil {
error.stack = err.stack;
/**
* Emitted when the client encounters an error.
- * Errors thrown within this event do not have a catch handler, it is
- * recommended to not use async functions as `error` event handlers. See the
- * [Node.js docs](https://nodejs.org/api/events.html#capture-rejections-of-promises) for details.
* @event Client#error
* @param {Error} error The error encountered
*/
- this.client.emit(Events.Error, error);
+ this.client.emit(Events.ERROR, error);
});
}
@@ -231,7 +223,7 @@ class ShardClientUtil {
this._singleton = new this(client, mode);
} else {
client.emit(
- Events.Warn,
+ Events.WARN,
'Multiple clients created in child process/worker; only the first will handle sharding helpers.',
);
}
@@ -249,30 +241,6 @@ class ShardClientUtil {
if (shard < 0) throw new Error('SHARDING_SHARD_MISCALCULATION', shard, guildId, shardCount);
return shard;
}
-
- /**
- * Increments max listeners by one for a given emitter, if they are not zero.
- * @param {EventEmitter|process} emitter The emitter that emits the events.
- * @private
- */
- incrementMaxListeners(emitter) {
- const maxListeners = emitter.getMaxListeners();
- if (maxListeners !== 0) {
- emitter.setMaxListeners(maxListeners + 1);
- }
- }
-
- /**
- * Decrements max listeners by one for a given emitter, if they are not zero.
- * @param {EventEmitter|process} emitter The emitter that emits the events.
- * @private
- */
- decrementMaxListeners(emitter) {
- const maxListeners = emitter.getMaxListeners();
- if (maxListeners !== 0) {
- emitter.setMaxListeners(maxListeners - 1);
- }
- }
}
module.exports = ShardClientUtil;
diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js
index db8581c..5ddf5ab 100644
--- a/src/sharding/ShardingManager.js
+++ b/src/sharding/ShardingManager.js
@@ -36,7 +36,7 @@ class ShardingManager extends EventEmitter {
* @property {boolean} [respawn=true] Whether shards should automatically respawn upon exiting
* @property {string[]} [shardArgs=[]] Arguments to pass to the shard script when spawning
* (only available when mode is set to 'process')
- * @property {string[]} [execArgv=[]] Arguments to pass to the shard script executable when spawning
+ * @property {string} [execArgv=[]] Arguments to pass to the shard script executable when spawning
* (only available when mode is set to 'process')
* @property {string} [token] Token to use for automatic shard count and passing to shards
*/
diff --git a/src/structures/ActionRow.js b/src/structures/ActionRow.js
deleted file mode 100644
index 450a9f3..00000000
--- a/src/structures/ActionRow.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-const { ActionRow: BuildersActionRow } = require('@discordjs/builders');
-const Transformers = require('../util/Transformers');
-
-class ActionRow extends BuildersActionRow {
- constructor(data) {
- super(Transformers.toSnakeCase(data));
- }
-}
-
-module.exports = ActionRow;
diff --git a/src/structures/AnonymousGuild.js b/src/structures/AnonymousGuild.js
index b919d82..5415e54 100644
--- a/src/structures/AnonymousGuild.js
+++ b/src/structures/AnonymousGuild.js
@@ -1,6 +1,7 @@
'use strict';
const BaseGuild = require('./BaseGuild');
+const { VerificationLevels, NSFWLevels } = require('../util/Constants');
/**
* Bundles common attributes and methods between {@link Guild} and {@link InviteGuild}
@@ -43,9 +44,9 @@ class AnonymousGuild extends BaseGuild {
if ('verification_level' in data) {
/**
* The verification level of the guild
- * @type {GuildVerificationLevel}
+ * @type {VerificationLevel}
*/
- this.verificationLevel = data.verification_level;
+ this.verificationLevel = VerificationLevels[data.verification_level];
}
if ('vanity_url_code' in data) {
@@ -59,36 +60,28 @@ class AnonymousGuild extends BaseGuild {
if ('nsfw_level' in data) {
/**
* The NSFW level of this guild
- * @type {GuildNSFWLevel}
+ * @type {NSFWLevel}
*/
- this.nsfwLevel = data.nsfw_level;
- }
-
- if ('premium_subscription_count' in data) {
- /**
- * The total number of boosts for this server
- * @type {?number}
- */
- this.premiumSubscriptionCount = data.premium_subscription_count;
+ this.nsfwLevel = NSFWLevels[data.nsfw_level];
}
}
/**
* The URL to this guild's banner.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- bannerURL(options = {}) {
- return this.banner && this.client.rest.cdn.banner(this.id, this.banner, options);
+ bannerURL({ format, size } = {}) {
+ return this.banner && this.client.rest.cdn.Banner(this.id, this.banner, format, size);
}
/**
* The URL to this guild's invite splash image.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- splashURL(options = {}) {
- return this.splash && this.client.rest.cdn.splash(this.id, this.splash, options);
+ splashURL({ format, size } = {}) {
+ return this.splash && this.client.rest.cdn.Splash(this.id, this.splash, format, size);
}
}
diff --git a/src/structures/ApplicationCommand.js b/src/structures/ApplicationCommand.js
index f43304d..ea4558e 100644
--- a/src/structures/ApplicationCommand.js
+++ b/src/structures/ApplicationCommand.js
@@ -1,9 +1,9 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { ApplicationCommandOptionType } = require('discord-api-types/v9');
const Base = require('./Base');
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
+const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes } = require('../util/Constants');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Represents an application command.
@@ -48,7 +48,7 @@ class ApplicationCommand extends Base {
* The type of this application command
* @type {ApplicationCommandType}
*/
- this.type = data.type;
+ this.type = ApplicationCommandTypes[data.type];
this._patch(data);
}
@@ -103,7 +103,7 @@ class ApplicationCommand extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -127,13 +127,11 @@ class ApplicationCommand extends Base {
/**
* Data for creating or editing an application command.
* @typedef {Object} ApplicationCommandData
- * @property {string} name The name of the command, must be in all lowercase if type is
- * {@link ApplicationCommandType.ChatInput}
- * @property {string} description The description of the command, if type is {@link ApplicationCommandType.ChatInput}
- * @property {ApplicationCommandType} [type=ApplicationCommandType.ChatInput] The type of the command
+ * @property {string} name The name of the command
+ * @property {string} description The description of the command
+ * @property {ApplicationCommandType} [type] The type of the command
* @property {ApplicationCommandOptionData[]} [options] Options for the command
- * @property {boolean} [defaultPermission=true] Whether the command is enabled by default when the app is added to a
- * guild
+ * @property {boolean} [defaultPermission] Whether the command is enabled by default when the app is added to a guild
*/
/**
@@ -143,21 +141,17 @@ class ApplicationCommand extends Base {
* Note that providing a value for the `camelCase` counterpart for any `snake_case` property
* will discard the provided `snake_case` property.
* @typedef {Object} ApplicationCommandOptionData
- * @property {ApplicationCommandOptionType} type The type of the option
+ * @property {ApplicationCommandOptionType|number} type The type of the option
* @property {string} name The name of the option
* @property {string} description The description of the option
- * @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a
- * {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
+ * @property {boolean} [autocomplete] Whether the option is an autocomplete option
* @property {boolean} [required] Whether the option is required
* @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from
* @property {ApplicationCommandOptionData[]} [options] Additional options if this option is a subcommand (group)
- * @property {ChannelType[]} [channelTypes] When the option type is channel,
+ * @property {ChannelType[]|number[]} [channelTypes] When the option type is channel,
* the allowed types of channels that can be selected
- * @property {number} [minValue] The minimum value for an {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
- * @property {number} [maxValue] The maximum value for an {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
+ * @property {number} [minValue] The minimum value for an `INTEGER` or `NUMBER` option
+ * @property {number} [maxValue] The maximum value for an `INTEGER` or `NUMBER` option
*/
/**
@@ -239,12 +233,13 @@ class ApplicationCommand extends Base {
if (command.id && this.id !== command.id) return false;
// Check top level parameters
+ const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type];
if (
command.name !== this.name ||
('description' in command && command.description !== this.description) ||
('version' in command && command.version !== this.version) ||
('autocomplete' in command && command.autocomplete !== this.autocomplete) ||
- (command.type && command.type !== this.type) ||
+ (commandType && commandType !== this.type) ||
// Future proof for options being nullable
// TODO: remove ?? 0 on each when nullable
(command.options?.length ?? 0) !== (this.options?.length ?? 0) ||
@@ -294,15 +289,14 @@ class ApplicationCommand extends Base {
* @private
*/
static _optionEquals(existing, option, enforceOptionOrder = false) {
+ const optionType = typeof option.type === 'string' ? option.type : ApplicationCommandOptionTypes[option.type];
if (
option.name !== existing.name ||
- option.type !== existing.type ||
+ optionType !== existing.type ||
option.description !== existing.description ||
option.autocomplete !== existing.autocomplete ||
- (option.required ??
- ([ApplicationCommandOptionType.Subcommand, ApplicationCommandOptionType.SubcommandGroup].includes(option.type)
- ? undefined
- : false)) !== existing.required ||
+ (option.required ?? (['SUB_COMMAND', 'SUB_COMMAND_GROUP'].includes(optionType) ? undefined : false)) !==
+ existing.required ||
option.choices?.length !== existing.choices?.length ||
option.options?.length !== existing.options?.length ||
(option.channelTypes ?? option.channel_types)?.length !== existing.channelTypes?.length ||
@@ -331,7 +325,9 @@ class ApplicationCommand extends Base {
}
if (existing.channelTypes) {
- const newTypes = option.channelTypes ?? option.channel_types;
+ const newTypes = (option.channelTypes ?? option.channel_types).map(type =>
+ typeof type === 'number' ? ChannelTypes[type] : type,
+ );
for (const type of existing.channelTypes) {
if (!newTypes.includes(type)) return false;
}
@@ -350,17 +346,13 @@ class ApplicationCommand extends Base {
* @property {string} name The name of the option
* @property {string} description The description of the option
* @property {boolean} [required] Whether the option is required
- * @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a
- * {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
+ * @property {boolean} [autocomplete] Whether the option is an autocomplete option
* @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from
* @property {ApplicationCommandOption[]} [options] Additional options if this option is a subcommand (group)
* @property {ChannelType[]} [channelTypes] When the option type is channel,
* the allowed types of channels that can be selected
- * @property {number} [minValue] The minimum value for an {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
- * @property {number} [maxValue] The maximum value for an {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
+ * @property {number} [minValue] The minimum value for an `INTEGER` or `NUMBER` option
+ * @property {number} [maxValue] The maximum value for an `INTEGER` or `NUMBER` option
*/
/**
@@ -378,23 +370,24 @@ class ApplicationCommand extends Base {
* @private
*/
static transformOption(option, received) {
+ const stringType = typeof option.type === 'string' ? option.type : ApplicationCommandOptionTypes[option.type];
const channelTypesKey = received ? 'channelTypes' : 'channel_types';
const minValueKey = received ? 'minValue' : 'min_value';
const maxValueKey = received ? 'maxValue' : 'max_value';
return {
- type: option.type,
+ type: typeof option.type === 'number' && !received ? option.type : ApplicationCommandOptionTypes[option.type],
name: option.name,
description: option.description,
required:
- option.required ??
- (option.type === ApplicationCommandOptionType.Subcommand ||
- option.type === ApplicationCommandOptionType.SubcommandGroup
- ? undefined
- : false),
+ option.required ?? (stringType === 'SUB_COMMAND' || stringType === 'SUB_COMMAND_GROUP' ? undefined : false),
autocomplete: option.autocomplete,
choices: option.choices,
options: option.options?.map(o => this.transformOption(o, received)),
- [channelTypesKey]: option.channelTypes ?? option.channel_types,
+ [channelTypesKey]: received
+ ? option.channel_types?.map(type => ChannelTypes[type])
+ : option.channelTypes?.map(type => (typeof type === 'string' ? ChannelTypes[type] : type)) ??
+ // When transforming to API data, accept API data
+ option.channel_types,
[minValueKey]: option.minValue ?? option.min_value,
[maxValueKey]: option.maxValue ?? option.max_value,
};
diff --git a/src/structures/AutocompleteInteraction.js b/src/structures/AutocompleteInteraction.js
index c05afcb..e942a6d 100644
--- a/src/structures/AutocompleteInteraction.js
+++ b/src/structures/AutocompleteInteraction.js
@@ -1,8 +1,8 @@
'use strict';
-const { InteractionResponseType, Routes } = require('discord-api-types/v9');
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
const Interaction = require('./Interaction');
+const { InteractionResponseTypes, ApplicationCommandOptionTypes } = require('../util/Constants');
/**
* Represents an autocomplete interaction.
@@ -30,12 +30,6 @@ class AutocompleteInteraction extends Interaction {
*/
this.commandName = data.data.name;
- /**
- * The invoked application command's type
- * @type {ApplicationCommandType.ChatInput}
- */
- this.commandType = data.data.type;
-
/**
* Whether this interaction has already received a response
* @type {boolean}
@@ -46,7 +40,10 @@ class AutocompleteInteraction extends Interaction {
* The options passed to the command
* @type {CommandInteractionOptionResolver}
*/
- this.options = new CommandInteractionOptionResolver(this.client, data.data.options ?? []);
+ this.options = new CommandInteractionOptionResolver(
+ this.client,
+ data.data.options?.map(option => this.transformOption(option, data.data.resolved)) ?? [],
+ );
}
/**
@@ -58,6 +55,25 @@ class AutocompleteInteraction extends Interaction {
return this.guild?.commands.cache.get(id) ?? this.client.application.commands.cache.get(id) ?? null;
}
+ /**
+ * Transforms an option received from the API.
+ * @param {APIApplicationCommandOption} option The received option
+ * @returns {CommandInteractionOption}
+ * @private
+ */
+ transformOption(option) {
+ const result = {
+ name: option.name,
+ type: ApplicationCommandOptionTypes[option.type],
+ };
+
+ if ('value' in option) result.value = option.value;
+ if ('options' in option) result.options = option.options.map(opt => this.transformOption(opt));
+ if ('focused' in option) result.focused = option.focused;
+
+ return result;
+ }
+
/**
* Sends results for the autocomplete of this interaction.
* @param {ApplicationCommandOptionChoice[]} options The options for the autocomplete
@@ -77,14 +93,14 @@ class AutocompleteInteraction extends Interaction {
if (this.responded) throw new Error('INTERACTION_ALREADY_REPLIED');
await this.client.api.interactions(this.id, this.token).callback.post({
- body: {
- type: InteractionResponseType.ApplicationCommandAutocompleteResult,
+ data: {
+ type: InteractionResponseTypes.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT,
data: {
choices: options,
},
},
auth: false,
- })
+ });
this.responded = true;
}
}
diff --git a/src/structures/BaseCommandInteraction.js b/src/structures/BaseCommandInteraction.js
new file mode 100644
index 00000000..0ddaf6b
--- /dev/null
+++ b/src/structures/BaseCommandInteraction.js
@@ -0,0 +1,195 @@
+'use strict';
+
+const { Collection } = require('@discordjs/collection');
+const Interaction = require('./Interaction');
+const InteractionWebhook = require('./InteractionWebhook');
+const InteractionResponses = require('./interfaces/InteractionResponses');
+const { ApplicationCommandOptionTypes } = require('../util/Constants');
+
+/**
+ * Represents a command interaction.
+ * @extends {Interaction}
+ * @implements {InteractionResponses}
+ * @abstract
+ */
+class BaseCommandInteraction extends Interaction {
+ constructor(client, data) {
+ super(client, data);
+
+ /**
+ * The id of the channel this interaction was sent in
+ * @type {Snowflake}
+ * @name BaseCommandInteraction#channelId
+ */
+
+ /**
+ * The invoked application command's id
+ * @type {Snowflake}
+ */
+ this.commandId = data.data.id;
+
+ /**
+ * The invoked application command's name
+ * @type {string}
+ */
+ this.commandName = data.data.name;
+
+ /**
+ * Whether the reply to this interaction has been deferred
+ * @type {boolean}
+ */
+ this.deferred = false;
+
+ /**
+ * Whether this interaction has already been replied to
+ * @type {boolean}
+ */
+ this.replied = false;
+
+ /**
+ * Whether the reply to this interaction is ephemeral
+ * @type {?boolean}
+ */
+ this.ephemeral = null;
+
+ /**
+ * An associated interaction webhook, can be used to further interact with this interaction
+ * @type {InteractionWebhook}
+ */
+ this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token);
+ }
+
+ /**
+ * The invoked application command, if it was fetched before
+ * @type {?ApplicationCommand}
+ */
+ get command() {
+ const id = this.commandId;
+ return this.guild?.commands.cache.get(id) ?? this.client.application.commands.cache.get(id) ?? null;
+ }
+
+ /**
+ * Represents the resolved data of a received command interaction.
+ * @typedef {Object} CommandInteractionResolvedData
+ * @property {Collection} [users] The resolved users
+ * @property {Collection} [members] The resolved guild members
+ * @property {Collection} [roles] The resolved roles
+ * @property {Collection} [channels] The resolved channels
+ * @property {Collection} [messages] The resolved messages
+ */
+
+ /**
+ * Transforms the resolved received from the API.
+ * @param {APIInteractionDataResolved} resolved The received resolved objects
+ * @returns {CommandInteractionResolvedData}
+ * @private
+ */
+ transformResolved({ members, users, channels, roles, messages }) {
+ const result = {};
+
+ if (members) {
+ result.members = new Collection();
+ for (const [id, member] of Object.entries(members)) {
+ const user = users[id];
+ result.members.set(id, this.guild?.members._add({ user, ...member }) ?? member);
+ }
+ }
+
+ if (users) {
+ result.users = new Collection();
+ for (const user of Object.values(users)) {
+ result.users.set(user.id, this.client.users._add(user));
+ }
+ }
+
+ if (roles) {
+ result.roles = new Collection();
+ for (const role of Object.values(roles)) {
+ result.roles.set(role.id, this.guild?.roles._add(role) ?? role);
+ }
+ }
+
+ if (channels) {
+ result.channels = new Collection();
+ for (const channel of Object.values(channels)) {
+ result.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel);
+ }
+ }
+
+ if (messages) {
+ result.messages = new Collection();
+ for (const message of Object.values(messages)) {
+ result.messages.set(message.id, this.channel?.messages?._add(message) ?? message);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Represents an option of a received command interaction.
+ * @typedef {Object} CommandInteractionOption
+ * @property {string} name The name of the option
+ * @property {ApplicationCommandOptionType} type The type of the option
+ * @property {boolean} [autocomplete] Whether the option is an autocomplete option
+ * @property {string|number|boolean} [value] The value of the option
+ * @property {CommandInteractionOption[]} [options] Additional options if this option is a
+ * subcommand (group)
+ * @property {User} [user] The resolved user
+ * @property {GuildMember|APIGuildMember} [member] The resolved member
+ * @property {GuildChannel|ThreadChannel|APIChannel} [channel] The resolved channel
+ * @property {Role|APIRole} [role] The resolved role
+ */
+
+ /**
+ * Transforms an option received from the API.
+ * @param {APIApplicationCommandOption} option The received option
+ * @param {APIInteractionDataResolved} resolved The resolved interaction data
+ * @returns {CommandInteractionOption}
+ * @private
+ */
+ transformOption(option, resolved) {
+ const result = {
+ name: option.name,
+ type: ApplicationCommandOptionTypes[option.type],
+ };
+
+ if ('value' in option) result.value = option.value;
+ if ('options' in option) result.options = option.options.map(opt => this.transformOption(opt, resolved));
+
+ if (resolved) {
+ const user = resolved.users?.[option.value];
+ if (user) result.user = this.client.users._add(user);
+
+ const member = resolved.members?.[option.value];
+ if (member) result.member = this.guild?.members._add({ user, ...member }) ?? member;
+
+ const channel = resolved.channels?.[option.value];
+ if (channel) result.channel = this.client.channels._add(channel, this.guild) ?? channel;
+
+ const role = resolved.roles?.[option.value];
+ if (role) result.role = this.guild?.roles._add(role) ?? role;
+ }
+
+ return result;
+ }
+
+ // These are here only for documentation purposes - they are implemented by InteractionResponses
+ /* eslint-disable no-empty-function */
+ deferReply() {}
+ reply() {}
+ fetchReply() {}
+ editReply() {}
+ deleteReply() {}
+ followUp() {}
+}
+
+InteractionResponses.applyToClass(BaseCommandInteraction, ['deferUpdate', 'update']);
+
+module.exports = BaseCommandInteraction;
+
+/* eslint-disable max-len */
+/**
+ * @external APIInteractionDataResolved
+ * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure}
+ */
diff --git a/src/structures/BaseGuild.js b/src/structures/BaseGuild.js
index e9d43d5..a39c44e 100644
--- a/src/structures/BaseGuild.js
+++ b/src/structures/BaseGuild.js
@@ -1,8 +1,7 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { Routes } = require('discord-api-types/v9');
const Base = require('./Base');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* The base class for {@link Guild}, {@link OAuth2Guild} and {@link InviteGuild}.
@@ -33,7 +32,7 @@ class BaseGuild extends Base {
/**
* An array of features available to this guild
- * @type {GuildFeature[]}
+ * @type {Features[]}
*/
this.features = data.features;
}
@@ -44,7 +43,7 @@ class BaseGuild extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -88,11 +87,12 @@ class BaseGuild extends Base {
/**
* The URL to this guild's icon.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- iconURL(options = {}) {
- return this.icon && this.client.rest.cdn.icon(this.id, this.icon, options);
+ iconURL({ format, size, dynamic } = {}) {
+ if (!this.icon) return null;
+ return this.client.rest.cdn.Icon(this.id, this.icon, format, size, dynamic);
}
/**
@@ -100,9 +100,7 @@ class BaseGuild extends Base {
* @returns {Promise}
*/
async fetch() {
- const data = await this.client.api.guilds(this.id).get({
- query: new URLSearchParams({ with_counts: true }),
- });
+ const data = await this.client.api.guilds(this.id).get({ query: { with_counts: true } });
return this.client.guilds._add(data);
}
diff --git a/src/structures/BaseGuildTextChannel.js b/src/structures/BaseGuildTextChannel.js
index d0f28bb..abb4075 100644
--- a/src/structures/BaseGuildTextChannel.js
+++ b/src/structures/BaseGuildTextChannel.js
@@ -1,9 +1,12 @@
'use strict';
+const { Collection } = require('@discordjs/collection');
const GuildChannel = require('./GuildChannel');
+const Webhook = require('./Webhook');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const MessageManager = require('../managers/MessageManager');
const ThreadManager = require('../managers/ThreadManager');
+const DataResolver = require('../util/DataResolver');
/**
* Represents a text-based guild channel on Discord.
@@ -63,7 +66,7 @@ class BaseGuildTextChannel extends GuildChannel {
* The timestamp when the last pinned message was pinned, if there was one
* @type {?number}
*/
- this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null;
+ this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null;
}
if ('default_auto_archive_duration' in data) {
@@ -118,8 +121,11 @@ class BaseGuildTextChannel extends GuildChannel {
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
- fetchWebhooks() {
- return this.guild.channels.fetchWebhooks(this.id);
+ async fetchWebhooks() {
+ const data = await this.client.api.channels[this.id].webhooks.get();
+ const hooks = new Collection();
+ for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
+ return hooks;
}
/**
@@ -143,8 +149,18 @@ class BaseGuildTextChannel extends GuildChannel {
* .then(console.log)
* .catch(console.error)
*/
- createWebhook(name, options = {}) {
- return this.guild.channels.createWebhook(this.id, name, options);
+ async createWebhook(name, { avatar, reason } = {}) {
+ if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
+ avatar = await DataResolver.resolveImage(avatar);
+ }
+ const data = await this.client.api.channels[this.id].webhooks.post({
+ data: {
+ name,
+ avatar,
+ },
+ reason,
+ });
+ return new Webhook(this.client, data);
}
/**
@@ -162,28 +178,19 @@ class BaseGuildTextChannel extends GuildChannel {
return this.edit({ topic }, reason);
}
- /**
- * Data that can be resolved to an Application. This can be:
- * * An Application
- * * An Activity with associated Application
- * * A Snowflake
- * @typedef {Application|Snowflake} ApplicationResolvable
- */
-
/**
* Options used to create an invite to a guild channel.
* @typedef {Object} CreateInviteOptions
- * @property {boolean} [temporary] Whether members that joined via the invite should be automatically
+ * @property {boolean} [temporary=false] Whether members that joined via the invite should be automatically
* kicked after 24 hours if they have not yet received a role
- * @property {number} [maxAge] How long the invite should last (in seconds, 0 for forever)
- * @property {number} [maxUses] Maximum number of uses
- * @property {boolean} [unique] Create a unique invite, or use an existing one with similar settings
+ * @property {number} [maxAge=86400] How long the invite should last (in seconds, 0 for forever)
+ * @property {number} [maxUses=0] Maximum number of uses
+ * @property {boolean} [unique=false] Create a unique invite, or use an existing one with similar settings
* @property {UserResolvable} [targetUser] The user whose stream to display for this invite,
- * required if `targetType` is {@link InviteTargetType.Stream}, the user must be streaming in the channel
+ * required if `targetType` is 1, the user must be streaming in the channel
* @property {ApplicationResolvable} [targetApplication] The embedded application to open for this invite,
- * required if `targetType` is {@link InviteTargetType.Stream}, the application must have the
- * {@link InviteTargetType.EmbeddedApplication} flag
- * @property {InviteTargetType} [targetType] The type of the target for this voice channel invite
+ * required if `targetType` is 2, the application must have the `EMBEDDED` flag
+ * @property {TargetType} [targetType] The type of the target for this voice channel invite
* @property {string} [reason] The reason for creating the invite
*/
diff --git a/src/structures/BaseGuildVoiceChannel.js b/src/structures/BaseGuildVoiceChannel.js
index ad905cd..e048eb3 100644
--- a/src/structures/BaseGuildVoiceChannel.js
+++ b/src/structures/BaseGuildVoiceChannel.js
@@ -1,8 +1,8 @@
'use strict';
const { Collection } = require('@discordjs/collection');
-const { PermissionFlagsBits } = require('discord-api-types/v9');
const GuildChannel = require('./GuildChannel');
+const Permissions = require('../util/Permissions');
/**
* Represents a voice-based guild channel on Discord.
@@ -72,11 +72,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
if (!permissions) return false;
// This flag allows joining even if timed out
- if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
return (
this.guild.me.communicationDisabledUntilTimestamp < Date.now() &&
- permissions.has(PermissionFlagsBits.Connect, false)
+ permissions.has(Permissions.FLAGS.CONNECT, false)
);
}
diff --git a/src/structures/BaseMessageComponent.js b/src/structures/BaseMessageComponent.js
new file mode 100644
index 00000000..c2470e0
--- /dev/null
+++ b/src/structures/BaseMessageComponent.js
@@ -0,0 +1,103 @@
+'use strict';
+
+const { TypeError } = require('../errors');
+const { MessageComponentTypes, Events } = require('../util/Constants');
+
+/**
+ * Represents an interactive component of a Message. It should not be necessary to construct this directly.
+ * See {@link MessageComponent}
+ */
+class BaseMessageComponent {
+ /**
+ * Options for a BaseMessageComponent
+ * @typedef {Object} BaseMessageComponentOptions
+ * @property {MessageComponentTypeResolvable} type The type of this component
+ */
+
+ /**
+ * Data that can be resolved into options for a MessageComponent. This can be:
+ * * MessageActionRowOptions
+ * * MessageButtonOptions
+ * * MessageSelectMenuOptions
+ * @typedef {MessageActionRowOptions|MessageButtonOptions|MessageSelectMenuOptions} MessageComponentOptions
+ */
+
+ /**
+ * Components that can be sent in a message. These can be:
+ * * MessageActionRow
+ * * MessageButton
+ * * MessageSelectMenu
+ * @typedef {MessageActionRow|MessageButton|MessageSelectMenu} MessageComponent
+ * @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types}
+ */
+
+ /**
+ * Data that can be resolved to a MessageComponentType. This can be:
+ * * MessageComponentType
+ * * string
+ * * number
+ * @typedef {string|number|MessageComponentType} MessageComponentTypeResolvable
+ */
+
+ /**
+ * @param {BaseMessageComponent|BaseMessageComponentOptions} [data={}] The options for this component
+ */
+ constructor(data) {
+ /**
+ * The type of this component
+ * @type {?MessageComponentType}
+ */
+ this.type = 'type' in data ? BaseMessageComponent.resolveType(data.type) : null;
+ }
+
+ /**
+ * Constructs a MessageComponent based on the type of the incoming data
+ * @param {MessageComponentOptions} data Data for a MessageComponent
+ * @param {Client|WebhookClient} [client] Client constructing this component
+ * @returns {?MessageComponent}
+ * @private
+ */
+ static create(data, client) {
+ let component;
+ let type = data.type;
+
+ if (typeof type === 'string') type = MessageComponentTypes[type];
+
+ switch (type) {
+ case MessageComponentTypes.ACTION_ROW: {
+ const MessageActionRow = require('./MessageActionRow');
+ component = data instanceof MessageActionRow ? data : new MessageActionRow(data, client);
+ break;
+ }
+ case MessageComponentTypes.BUTTON: {
+ const MessageButton = require('./MessageButton');
+ component = data instanceof MessageButton ? data : new MessageButton(data);
+ break;
+ }
+ case MessageComponentTypes.SELECT_MENU: {
+ const MessageSelectMenu = require('./MessageSelectMenu');
+ component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data);
+ break;
+ }
+ default:
+ if (client) {
+ client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`);
+ } else {
+ throw new TypeError('INVALID_TYPE', 'data.type', 'valid MessageComponentType');
+ }
+ }
+ return component;
+ }
+
+ /**
+ * Resolves the type of a MessageComponent
+ * @param {MessageComponentTypeResolvable} type The type to resolve
+ * @returns {MessageComponentType}
+ * @private
+ */
+ static resolveType(type) {
+ return typeof type === 'string' ? type : MessageComponentTypes[type];
+ }
+}
+
+module.exports = BaseMessageComponent;
diff --git a/src/structures/ButtonComponent.js b/src/structures/ButtonComponent.js
deleted file mode 100644
index a6cce10..00000000
--- a/src/structures/ButtonComponent.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-const { ButtonComponent: BuildersButtonComponent } = require('@discordjs/builders');
-const Transformers = require('../util/Transformers');
-
-class ButtonComponent extends BuildersButtonComponent {
- constructor(data) {
- super(Transformers.toSnakeCase(data));
- }
-}
-
-module.exports = ButtonComponent;
diff --git a/src/structures/CategoryChannel.js b/src/structures/CategoryChannel.js
index 6f53858..ce5b5f6 100644
--- a/src/structures/CategoryChannel.js
+++ b/src/structures/CategoryChannel.js
@@ -1,7 +1,6 @@
'use strict';
const GuildChannel = require('./GuildChannel');
-const CategoryChannelChildManager = require('../managers/CategoryChannelChildManager');
/**
* Represents a guild category channel on Discord.
@@ -9,12 +8,12 @@ const CategoryChannelChildManager = require('../managers/CategoryChannelChildMan
*/
class CategoryChannel extends GuildChannel {
/**
- * A manager of the channels belonging to this category
- * @type {CategoryChannelChildManager}
+ * Channels that are a part of this category
+ * @type {Collection}
* @readonly
*/
get children() {
- return new CategoryChannelChildManager(this);
+ return this.guild.channels.cache.filter(c => c.parentId === this.id);
}
/**
@@ -27,6 +26,36 @@ class CategoryChannel extends GuildChannel {
* @param {SetParentOptions} [options={}] The options for setting the parent
* @returns {Promise}
*/
+
+ /**
+ * Options for creating a channel using {@link CategoryChannel#createChannel}.
+ * @typedef {Object} CategoryCreateChannelOptions
+ * @property {ChannelType|number} [type='GUILD_TEXT'] 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} [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.
+ * You cannot create a channel of type `GUILD_CATEGORY` inside a CategoryChannel.
+ * @param {string} name The name of the new channel
+ * @param {CategoryCreateChannelOptions} options Options for creating the new channel
+ * @returns {Promise}
+ */
+ createChannel(name, options) {
+ return this.guild.channels.create(name, {
+ ...options,
+ parent: this.id,
+ });
+ }
}
module.exports = CategoryChannel;
diff --git a/src/structures/Channel.js b/src/structures/Channel.js
index 01086d3..cec50c7 100644
--- a/src/structures/Channel.js
+++ b/src/structures/Channel.js
@@ -1,9 +1,7 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { ChannelType, Routes } = require('discord-api-types/v9');
+const process = require('node:process');
const Base = require('./Base');
-const { ThreadChannelTypes } = require('../util/Constants');
let CategoryChannel;
let DMChannel;
let NewsChannel;
@@ -12,6 +10,16 @@ let StoreChannel;
let TextChannel;
let ThreadChannel;
let VoiceChannel;
+const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedChannels = new WeakSet();
+let deprecationEmittedForDeleted = false;
/**
* Represents any channel on Discord.
@@ -22,11 +30,12 @@ class Channel extends Base {
constructor(client, data, immediatePatch = true) {
super(client);
+ const type = ChannelTypes[data?.type];
/**
* The type of the channel
* @type {ChannelType}
*/
- this.type = data.type;
+ this.type = type ?? 'UNKNOWN';
if (data && immediatePatch) this._patch(data);
}
@@ -45,7 +54,7 @@ class Channel extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -58,12 +67,33 @@ class Channel extends Base {
}
/**
- * The URL to the channel
- * @type {string}
- * @readonly
+ * Whether or not the structure has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
*/
- get url() {
- return `https://discord.com/channels/${this.isDMBased() ? '@me' : this.guildId}/${this.id}`;
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedChannels.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedChannels.add(this);
+ else deletedChannels.delete(this);
}
/**
@@ -111,59 +141,19 @@ class Channel extends Base {
}
/**
- * Indicates whether this channel is a {@link TextChannel}.
+ * Indicates whether this channel is {@link TextBasedChannels text-based}.
* @returns {boolean}
*/
isText() {
- return this.type === ChannelType.GuildText;
+ return 'messages' in this;
}
/**
- * Indicates whether this channel is a {@link DMChannel}.
- * @returns {boolean}
- */
- isDM() {
- return this.type === ChannelType.DM;
- }
-
- /**
- * Indicates whether this channel is a {@link VoiceChannel}.
+ * Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}.
* @returns {boolean}
*/
isVoice() {
- return this.type === ChannelType.GuildVoice;
- }
-
- /**
- * Indicates whether this channel is a {@link PartialGroupDMChannel}.
- * @returns {boolean}
- */
- isGroupDM() {
- return this.type === ChannelType.GroupDM;
- }
-
- /**
- * Indicates whether this channel is a {@link CategoryChannel}.
- * @returns {boolean}
- */
- isCategory() {
- return this.type === ChannelType.GuildCategory;
- }
-
- /**
- * Indicates whether this channel is a {@link NewsChannel}.
- * @returns {boolean}
- */
- isNews() {
- return this.type === ChannelType.GuildNews;
- }
-
- /**
- * Indicates whether this channel is a {@link StoreChannel}.
- * @returns {boolean}
- */
- isStore() {
- return this.type === ChannelType.GuildStore;
+ return VoiceBasedChannelTypes.includes(this.type);
}
/**
@@ -174,38 +164,6 @@ class Channel extends Base {
return ThreadChannelTypes.includes(this.type);
}
- /**
- * Indicates whether this channel is a {@link StageChannel}.
- * @returns {boolean}
- */
- isStage() {
- return this.type === ChannelType.GuildStageVoice;
- }
-
- /**
- * Indicates whether this channel is {@link TextBasedChannels text-based}.
- * @returns {boolean}
- */
- isTextBased() {
- return 'messages' in this;
- }
-
- /**
- * Indicates whether this channel is DM-based (either a {@link DMChannel} or a {@link PartialGroupDMChannel}).
- * @returns {boolean}
- */
- isDMBased() {
- return [ChannelType.DM, ChannelType.GroupDM].includes(this.type);
- }
-
- /**
- * Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}.
- * @returns {boolean}
- */
- isVoiceBased() {
- return 'bitrate' in this;
- }
-
static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
CategoryChannel ??= require('./CategoryChannel');
DMChannel ??= require('./DMChannel');
@@ -218,9 +176,9 @@ class Channel extends Base {
let channel;
if (!data.guild_id && !guild) {
- if ((data.recipients && data.type !== ChannelType.GroupDM) || data.type === ChannelType.DM) {
+ if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) {
channel = new DMChannel(client, data);
- } else if (data.type === ChannelType.GroupDM) {
+ } else if (data.type === ChannelTypes.GROUP_DM) {
const PartialGroupDMChannel = require('./PartialGroupDMChannel');
channel = new PartialGroupDMChannel(client, data);
}
@@ -229,33 +187,33 @@ class Channel extends Base {
if (guild || allowUnknownGuild) {
switch (data.type) {
- case ChannelType.GuildText: {
+ case ChannelTypes.GUILD_TEXT: {
channel = new TextChannel(guild, data, client);
break;
}
- case ChannelType.GuildVoice: {
+ case ChannelTypes.GUILD_VOICE: {
channel = new VoiceChannel(guild, data, client);
break;
}
- case ChannelType.GuildCategory: {
+ case ChannelTypes.GUILD_CATEGORY: {
channel = new CategoryChannel(guild, data, client);
break;
}
- case ChannelType.GuildNews: {
+ case ChannelTypes.GUILD_NEWS: {
channel = new NewsChannel(guild, data, client);
break;
}
- case ChannelType.GuildStore: {
+ case ChannelTypes.GUILD_STORE: {
channel = new StoreChannel(guild, data, client);
break;
}
- case ChannelType.GuildStageVoice: {
+ case ChannelTypes.GUILD_STAGE_VOICE: {
channel = new StageChannel(guild, data, client);
break;
}
- case ChannelType.GuildNewsThread:
- case ChannelType.GuildPublicThread:
- case ChannelType.GuildPrivateThread: {
+ case ChannelTypes.GUILD_NEWS_THREAD:
+ case ChannelTypes.GUILD_PUBLIC_THREAD:
+ case ChannelTypes.GUILD_PRIVATE_THREAD: {
channel = new ThreadChannel(guild, data, client, fromInteraction);
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
break;
@@ -273,6 +231,7 @@ class Channel extends Base {
}
exports.Channel = Channel;
+exports.deletedChannels = deletedChannels;
/**
* @external APIChannel
diff --git a/src/structures/ChatInputCommandInteraction.js b/src/structures/ChatInputCommandInteraction.js
deleted file mode 100644
index 35175e4..00000000
--- a/src/structures/ChatInputCommandInteraction.js
+++ /dev/null
@@ -1,41 +0,0 @@
-'use strict';
-
-const CommandInteraction = require('./CommandInteraction');
-const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
-
-/**
- * Represents a command interaction.
- * @extends {CommandInteraction}
- */
-class ChatInputCommandInteraction extends CommandInteraction {
- constructor(client, data) {
- super(client, data);
-
- /**
- * The options passed to the command.
- * @type {CommandInteractionOptionResolver}
- */
- this.options = new CommandInteractionOptionResolver(
- this.client,
- data.data.options?.map(option => this.transformOption(option, data.data.resolved)) ?? [],
- this.transformResolved(data.data.resolved ?? {}),
- );
- }
-
- /**
- * Returns a string representation of the command interaction.
- * This can then be copied by a user and executed again in a new command while keeping the option order.
- * @returns {string}
- */
- toString() {
- const properties = [
- this.commandName,
- this.options._group,
- this.options._subcommand,
- ...this.options._hoistedOptions.map(o => `${o.name}:${o.value}`),
- ];
- return `/${properties.filter(Boolean).join(' ')}`;
- }
-}
-
-module.exports = ChatInputCommandInteraction;
diff --git a/src/structures/ClientApplication.js b/src/structures/ClientApplication.js
index de8270e..80dfd36 100644
--- a/src/structures/ClientApplication.js
+++ b/src/structures/ClientApplication.js
@@ -3,8 +3,8 @@
const Team = require('./Team');
const { Error } = require('../errors/DJSError');
const Application = require('./interfaces/Application');
-const ApplicationFlagsBitField = require('../util/ApplicationFlagsBitField');
const ApplicationCommandManager = require('../managers/ApplicationCommandManager');
+const ApplicationFlags = require('../util/ApplicationFlags');
/**
* Represents a Client OAuth2 Application.
@@ -24,14 +24,12 @@ class ClientApplication extends Application {
_patch(data) {
super._patch(data);
- if(!data) return;
-
if ('flags' in data) {
/**
* The flags this application has
- * @type {ApplicationFlagsBitField}
+ * @type {ApplicationFlags}
*/
- this.flags = new ApplicationFlagsBitField(data.flags).freeze();
+ this.flags = new ApplicationFlags(data.flags).freeze();
}
if ('cover_image' in data) {
diff --git a/src/structures/ClientPresence.js b/src/structures/ClientPresence.js
index c4dc743..897fd8e 100644
--- a/src/structures/ClientPresence.js
+++ b/src/structures/ClientPresence.js
@@ -1,8 +1,8 @@
'use strict';
-const { GatewayOpcodes } = require('discord-api-types/v9');
const { Presence } = require('./Presence');
const { TypeError } = require('../errors');
+const { ActivityTypes, Opcodes } = require('../util/Constants');
/**
* Represents the client's presence.
@@ -10,7 +10,7 @@ const { TypeError } = require('../errors');
*/
class ClientPresence extends Presence {
constructor(client, data = {}) {
- super(client, Object.assign(data, { status: data.status || client.setting.status || 'online', user: { id: null } }));
+ super(client, Object.assign(data, { status: data.status ?? 'online', user: { id: null } }));
}
/**
@@ -22,13 +22,13 @@ class ClientPresence extends Presence {
const packet = this._parse(presence);
this._patch(packet);
if (typeof presence.shardId === 'undefined') {
- this.client.ws.broadcast({ op: GatewayOpcodes.PresenceUpdate, d: packet });
+ this.client.ws.broadcast({ op: Opcodes.STATUS_UPDATE, d: packet });
} else if (Array.isArray(presence.shardId)) {
for (const shardId of presence.shardId) {
- this.client.ws.shards.get(shardId).send({ op: GatewayOpcodes.PresenceUpdate, d: packet });
+ this.client.ws.shards.get(shardId).send({ op: Opcodes.STATUS_UPDATE, d: packet });
}
} else {
- this.client.ws.shards.get(presence.shardId).send({ op: GatewayOpcodes.PresenceUpdate, d: packet });
+ this.client.ws.shards.get(presence.shardId).send({ op: Opcodes.STATUS_UPDATE, d: packet });
}
return this;
}
diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js
index 30b82a4..9136752 100644
--- a/src/structures/ClientUser.js
+++ b/src/structures/ClientUser.js
@@ -1,6 +1,5 @@
'use strict';
-const { Routes } = require('discord-api-types/v9');
const User = require('./User');
const DataResolver = require('../util/DataResolver');
@@ -159,7 +158,7 @@ class ClientUser extends User {
* @returns {ClientPresence}
* @example
* // Set the client user's activity
- * client.user.setActivity('discord.js', { type: ActivityType.Watching });
+ * client.user.setActivity('discord.js', { type: 'WATCHING' });
*/
setActivity(name, options = {}) {
if (!name) return this.setPresence({ activities: [], shardId: options.shardId });
diff --git a/src/structures/CommandInteraction.js b/src/structures/CommandInteraction.js
index 4987d09..c5dcfa2 100644
--- a/src/structures/CommandInteraction.js
+++ b/src/structures/CommandInteraction.js
@@ -1,216 +1,41 @@
'use strict';
-const { Collection } = require('@discordjs/collection');
-const Interaction = require('./Interaction');
-const InteractionWebhook = require('./InteractionWebhook');
-const MessageAttachment = require('./MessageAttachment');
-const InteractionResponses = require('./interfaces/InteractionResponses');
+const BaseCommandInteraction = require('./BaseCommandInteraction');
+const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
/**
* Represents a command interaction.
- * @extends {Interaction}
- * @implements {InteractionResponses}
- * @abstract
+ * @extends {BaseCommandInteraction}
*/
-class CommandInteraction extends Interaction {
+class CommandInteraction extends BaseCommandInteraction {
constructor(client, data) {
super(client, data);
/**
- * The id of the channel this interaction was sent in
- * @type {Snowflake}
- * @name CommandInteraction#channelId
+ * The options passed to the command.
+ * @type {CommandInteractionOptionResolver}
*/
-
- /**
- * The invoked application command's id
- * @type {Snowflake}
- */
- this.commandId = data.data.id;
-
- /**
- * The invoked application command's name
- * @type {string}
- */
- this.commandName = data.data.name;
-
- /**
- * The invoked application command's type
- * @type {ApplicationCommandType}
- */
- this.commandType = data.data.type;
-
- /**
- * Whether the reply to this interaction has been deferred
- * @type {boolean}
- */
- this.deferred = false;
-
- /**
- * Whether this interaction has already been replied to
- * @type {boolean}
- */
- this.replied = false;
-
- /**
- * Whether the reply to this interaction is ephemeral
- * @type {?boolean}
- */
- this.ephemeral = null;
-
- /**
- * An associated interaction webhook, can be used to further interact with this interaction
- * @type {InteractionWebhook}
- */
- this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token);
+ this.options = new CommandInteractionOptionResolver(
+ this.client,
+ data.data.options?.map(option => this.transformOption(option, data.data.resolved)) ?? [],
+ this.transformResolved(data.data.resolved ?? {}),
+ );
}
/**
- * The invoked application command, if it was fetched before
- * @type {?ApplicationCommand}
+ * Returns a string representation of the command interaction.
+ * This can then be copied by a user and executed again in a new command while keeping the option order.
+ * @returns {string}
*/
- get command() {
- const id = this.commandId;
- return this.guild?.commands.cache.get(id) ?? this.client.application.commands.cache.get(id) ?? null;
+ toString() {
+ const properties = [
+ this.commandName,
+ this.options._group,
+ this.options._subcommand,
+ ...this.options._hoistedOptions.map(o => `${o.name}:${o.value}`),
+ ];
+ return `/${properties.filter(Boolean).join(' ')}`;
}
-
- /**
- * Represents the resolved data of a received command interaction.
- * @typedef {Object} CommandInteractionResolvedData
- * @property {Collection} [users] The resolved users
- * @property {Collection} [members] The resolved guild members
- * @property {Collection} [roles] The resolved roles
- * @property {Collection} [channels] The resolved channels
- * @property {Collection} [messages] The resolved messages
- * @property {Collection} [attachments] The resolved attachments
- */
-
- /**
- * Transforms the resolved received from the API.
- * @param {APIInteractionDataResolved} resolved The received resolved objects
- * @returns {CommandInteractionResolvedData}
- * @private
- */
- transformResolved({ members, users, channels, roles, messages, attachments }) {
- const result = {};
-
- if (members) {
- result.members = new Collection();
- for (const [id, member] of Object.entries(members)) {
- const user = users[id];
- result.members.set(id, this.guild?.members._add({ user, ...member }) ?? member);
- }
- }
-
- if (users) {
- result.users = new Collection();
- for (const user of Object.values(users)) {
- result.users.set(user.id, this.client.users._add(user));
- }
- }
-
- if (roles) {
- result.roles = new Collection();
- for (const role of Object.values(roles)) {
- result.roles.set(role.id, this.guild?.roles._add(role) ?? role);
- }
- }
-
- if (channels) {
- result.channels = new Collection();
- for (const channel of Object.values(channels)) {
- result.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel);
- }
- }
-
- if (messages) {
- result.messages = new Collection();
- for (const message of Object.values(messages)) {
- result.messages.set(message.id, this.channel?.messages?._add(message) ?? message);
- }
- }
-
- if (attachments) {
- result.attachments = new Collection();
- for (const attachment of Object.values(attachments)) {
- const patched = new MessageAttachment(attachment.url, attachment.filename, attachment);
- result.attachments.set(attachment.id, patched);
- }
- }
-
- return result;
- }
-
- /**
- * Represents an option of a received command interaction.
- * @typedef {Object} CommandInteractionOption
- * @property {string} name The name of the option
- * @property {ApplicationCommandOptionType} type The type of the option
- * @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a
- * {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or
- * {@link ApplicationCommandOptionType.Number} option
- * @property {string|number|boolean} [value] The value of the option
- * @property {CommandInteractionOption[]} [options] Additional options if this option is a
- * subcommand (group)
- * @property {User} [user] The resolved user
- * @property {GuildMember|APIGuildMember} [member] The resolved member
- * @property {GuildChannel|ThreadChannel|APIChannel} [channel] The resolved channel
- * @property {Role|APIRole} [role] The resolved role
- * @property {MessageAttachment} [attachment] The resolved attachment
- */
-
- /**
- * Transforms an option received from the API.
- * @param {APIApplicationCommandOption} option The received option
- * @param {APIInteractionDataResolved} resolved The resolved interaction data
- * @returns {CommandInteractionOption}
- * @private
- */
- transformOption(option, resolved) {
- const result = {
- name: option.name,
- type: option.type,
- };
-
- if ('value' in option) result.value = option.value;
- if ('options' in option) result.options = option.options.map(opt => this.transformOption(opt, resolved));
-
- if (resolved) {
- const user = resolved.users?.[option.value];
- if (user) result.user = this.client.users._add(user);
-
- const member = resolved.members?.[option.value];
- if (member) result.member = this.guild?.members._add({ user, ...member }) ?? member;
-
- const channel = resolved.channels?.[option.value];
- if (channel) result.channel = this.client.channels._add(channel, this.guild) ?? channel;
-
- const role = resolved.roles?.[option.value];
- if (role) result.role = this.guild?.roles._add(role) ?? role;
-
- const attachment = resolved.attachments?.[option.value];
- if (attachment) result.attachment = new MessageAttachment(attachment.url, attachment.filename, attachment);
- }
-
- return result;
- }
-
- // These are here only for documentation purposes - they are implemented by InteractionResponses
- /* eslint-disable no-empty-function */
- deferReply() {}
- reply() {}
- fetchReply() {}
- editReply() {}
- deleteReply() {}
- followUp() {}
}
-InteractionResponses.applyToClass(CommandInteraction, ['deferUpdate', 'update']);
-
module.exports = CommandInteraction;
-
-/* eslint-disable max-len */
-/**
- * @external APIInteractionDataResolved
- * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure}
- */
diff --git a/src/structures/CommandInteractionOptionResolver.js b/src/structures/CommandInteractionOptionResolver.js
index 2aa6938..c83fd8e 100644
--- a/src/structures/CommandInteractionOptionResolver.js
+++ b/src/structures/CommandInteractionOptionResolver.js
@@ -1,6 +1,5 @@
'use strict';
-const { ApplicationCommandOptionType } = require('discord-api-types/v9');
const { TypeError } = require('../errors');
/**
@@ -39,12 +38,12 @@ class CommandInteractionOptionResolver {
this._hoistedOptions = options;
// Hoist subcommand group if present
- if (this._hoistedOptions[0]?.type === ApplicationCommandOptionType.SubcommandGroup) {
+ if (this._hoistedOptions[0]?.type === 'SUB_COMMAND_GROUP') {
this._group = this._hoistedOptions[0].name;
this._hoistedOptions = this._hoistedOptions[0].options ?? [];
}
// Hoist subcommand if present
- if (this._hoistedOptions[0]?.type === ApplicationCommandOptionType.Subcommand) {
+ if (this._hoistedOptions[0]?.type === 'SUB_COMMAND') {
this._subcommand = this._hoistedOptions[0].name;
this._hoistedOptions = this._hoistedOptions[0].options ?? [];
}
@@ -117,10 +116,10 @@ class CommandInteractionOptionResolver {
/**
* Gets the selected subcommand group.
- * @param {boolean} [required=false] Whether to throw an error if there is no subcommand group.
+ * @param {boolean} [required=true] Whether to throw an error if there is no subcommand group.
* @returns {?string} The name of the selected subcommand group, or null if not set and not required.
*/
- getSubcommandGroup(required = false) {
+ getSubcommandGroup(required = true) {
if (required && !this._group) {
throw new TypeError('COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP');
}
@@ -134,7 +133,7 @@ class CommandInteractionOptionResolver {
* @returns {?boolean} The value of the option, or null if not set and not required.
*/
getBoolean(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.Boolean, ['value'], required);
+ const option = this._getTypedOption(name, 'BOOLEAN', ['value'], required);
return option?.value ?? null;
}
@@ -146,7 +145,7 @@ class CommandInteractionOptionResolver {
* The value of the option, or null if not set and not required.
*/
getChannel(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.Channel, ['channel'], required);
+ const option = this._getTypedOption(name, 'CHANNEL', ['channel'], required);
return option?.channel ?? null;
}
@@ -157,7 +156,7 @@ class CommandInteractionOptionResolver {
* @returns {?string} The value of the option, or null if not set and not required.
*/
getString(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.String, ['value'], required);
+ const option = this._getTypedOption(name, 'STRING', ['value'], required);
return option?.value ?? null;
}
@@ -168,7 +167,7 @@ class CommandInteractionOptionResolver {
* @returns {?number} The value of the option, or null if not set and not required.
*/
getInteger(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.Integer, ['value'], required);
+ const option = this._getTypedOption(name, 'INTEGER', ['value'], required);
return option?.value ?? null;
}
@@ -179,7 +178,7 @@ class CommandInteractionOptionResolver {
* @returns {?number} The value of the option, or null if not set and not required.
*/
getNumber(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.Number, ['value'], required);
+ const option = this._getTypedOption(name, 'NUMBER', ['value'], required);
return option?.value ?? null;
}
@@ -190,18 +189,19 @@ class CommandInteractionOptionResolver {
* @returns {?User} The value of the option, or null if not set and not required.
*/
getUser(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.User, ['user'], required);
+ const option = this._getTypedOption(name, 'USER', ['user'], required);
return option?.user ?? null;
}
/**
* Gets a member option.
* @param {string} name The name of the option.
+ * @param {boolean} [required=false] Whether to throw an error if the option is not found.
* @returns {?(GuildMember|APIGuildMember)}
- * The value of the option, or null if the user is not present in the guild or the option is not set.
+ * The value of the option, or null if not set and not required.
*/
- getMember(name) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.User, ['member'], false);
+ getMember(name, required = false) {
+ const option = this._getTypedOption(name, 'USER', ['member'], required);
return option?.member ?? null;
}
@@ -212,21 +212,10 @@ class CommandInteractionOptionResolver {
* @returns {?(Role|APIRole)} The value of the option, or null if not set and not required.
*/
getRole(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.Role, ['role'], required);
+ const option = this._getTypedOption(name, 'ROLE', ['role'], required);
return option?.role ?? null;
}
- /**
- * Gets an attachment option.
- * @param {string} name The name of the option.
- * @param {boolean} [required=false] Whether to throw an error if the option is not found.
- * @returns {?MessageAttachment} The value of the option, or null if not set and not required.
- */
- getAttachment(name, required = false) {
- const option = this._getTypedOption(name, ApplicationCommandOptionType.Attachment, ['attachment'], required);
- return option?.attachment ?? null;
- }
-
/**
* Gets a mentionable option.
* @param {string} name The name of the option.
@@ -235,12 +224,7 @@ class CommandInteractionOptionResolver {
* The value of the option, or null if not set and not required.
*/
getMentionable(name, required = false) {
- const option = this._getTypedOption(
- name,
- ApplicationCommandOptionType.Mentionable,
- ['user', 'member', 'role'],
- required,
- );
+ const option = this._getTypedOption(name, 'MENTIONABLE', ['user', 'member', 'role'], required);
return option?.member ?? option?.user ?? option?.role ?? null;
}
diff --git a/src/structures/ContextMenuCommandInteraction.js b/src/structures/ContextMenuInteraction.js
similarity index 71%
rename from src/structures/ContextMenuCommandInteraction.js
rename to src/structures/ContextMenuInteraction.js
index 360f97e..f4f6ef4 100644
--- a/src/structures/ContextMenuCommandInteraction.js
+++ b/src/structures/ContextMenuInteraction.js
@@ -1,14 +1,14 @@
'use strict';
-const { ApplicationCommandOptionType } = require('discord-api-types/v9');
-const CommandInteraction = require('./CommandInteraction');
+const BaseCommandInteraction = require('./BaseCommandInteraction');
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
+const { ApplicationCommandOptionTypes, ApplicationCommandTypes } = require('../util/Constants');
/**
* Represents a context menu interaction.
- * @extends {CommandInteraction}
+ * @extends {BaseCommandInteraction}
*/
-class ContextMenuCommandInteraction extends CommandInteraction {
+class ContextMenuInteraction extends BaseCommandInteraction {
constructor(client, data) {
super(client, data);
/**
@@ -26,6 +26,12 @@ class ContextMenuCommandInteraction extends CommandInteraction {
* @type {Snowflake}
*/
this.targetId = data.data.target_id;
+
+ /**
+ * The type of the target of the interaction; either USER or MESSAGE
+ * @type {ApplicationCommandType}
+ */
+ this.targetType = ApplicationCommandTypes[data.data.type];
}
/**
@@ -39,7 +45,7 @@ class ContextMenuCommandInteraction extends CommandInteraction {
if (resolved.users?.[target_id]) {
result.push(
- this.transformOption({ name: 'user', type: ApplicationCommandOptionType.User, value: target_id }, resolved),
+ this.transformOption({ name: 'user', type: ApplicationCommandOptionTypes.USER, value: target_id }, resolved),
);
}
@@ -56,4 +62,4 @@ class ContextMenuCommandInteraction extends CommandInteraction {
}
}
-module.exports = ContextMenuCommandInteraction;
+module.exports = ContextMenuInteraction;
diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js
index 5117a0e..555de4f 100644
--- a/src/structures/DMChannel.js
+++ b/src/structures/DMChannel.js
@@ -1,6 +1,5 @@
'use strict';
-const { ChannelType } = require('discord-api-types/v9');
const { Channel } = require('./Channel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const MessageManager = require('../managers/MessageManager');
@@ -15,7 +14,7 @@ class DMChannel extends Channel {
super(client, data);
// Override the channel type so partials have a known type
- this.type = ChannelType.DM;
+ this.type = 'DM';
/**
* A manager of the messages belonging to this channel
@@ -48,7 +47,7 @@ class DMChannel extends Channel {
* The timestamp when the last pinned message was pinned, if there was one
* @type {?number}
*/
- this.lastPinTimestamp = Date.parse(data.last_pin_timestamp);
+ this.lastPinTimestamp = new Date(data.last_pin_timestamp).getTime();
} else {
this.lastPinTimestamp ??= null;
}
diff --git a/src/structures/Embed.js b/src/structures/Embed.js
deleted file mode 100644
index b200237..00000000
--- a/src/structures/Embed.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-const { Embed: BuildersEmbed } = require('@discordjs/builders');
-const Transformers = require('../util/Transformers');
-
-class Embed extends BuildersEmbed {
- constructor(data) {
- super(Transformers.toSnakeCase(data));
- }
-}
-
-module.exports = Embed;
diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js
index 409d292..be7ae17 100644
--- a/src/structures/Emoji.js
+++ b/src/structures/Emoji.js
@@ -1,7 +1,16 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
+const process = require('node:process');
const Base = require('./Base');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedEmojis = new WeakSet();
+let deprecationEmittedForDeleted = false;
/**
* Represents raw emoji data from the API
@@ -37,6 +46,36 @@ class Emoji extends Base {
this.id = emoji.id;
}
+ /**
+ * Whether or not the structure has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Emoji#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedEmojis.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Emoji#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedEmojis.add(this);
+ else deletedEmojis.delete(this);
+ }
+
/**
* The identifier of this emoji, used for message reactions
* @type {string}
@@ -53,7 +92,7 @@ class Emoji extends Base {
* @readonly
*/
get url() {
- return this.id && this.client.rest.cdn.emoji(this.id, this.animated ? 'gif' : 'png');
+ return this.id && this.client.rest.cdn.Emoji(this.id, this.animated ? 'gif' : 'png');
}
/**
@@ -62,7 +101,7 @@ class Emoji extends Base {
* @readonly
*/
get createdTimestamp() {
- return this.id && DiscordSnowflake.timestampFrom(this.id);
+ return this.id && SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -101,6 +140,7 @@ class Emoji extends Base {
}
exports.Emoji = Emoji;
+exports.deletedEmojis = deletedEmojis;
/**
* @external APIEmoji
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index a9b919a..9c00fb8 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -1,7 +1,7 @@
'use strict';
+const process = require('node:process');
const { Collection } = require('@discordjs/collection');
-const { ChannelType, GuildPremiumTier, Routes } = require('discord-api-types/v9');
const AnonymousGuild = require('./AnonymousGuild');
const GuildAuditLogs = require('./GuildAuditLogs');
const GuildPreview = require('./GuildPreview');
@@ -9,7 +9,7 @@ const GuildTemplate = require('./GuildTemplate');
const Integration = require('./Integration');
const Webhook = require('./Webhook');
const WelcomeScreen = require('./WelcomeScreen');
-const { Error, TypeError } = require('../errors');
+const { Error } = require('../errors');
const GuildApplicationCommandManager = require('../managers/GuildApplicationCommandManager');
const GuildBanManager = require('../managers/GuildBanManager');
const GuildChannelManager = require('../managers/GuildChannelManager');
@@ -22,12 +22,31 @@ const PresenceManager = require('../managers/PresenceManager');
const RoleManager = require('../managers/RoleManager');
const StageInstanceManager = require('../managers/StageInstanceManager');
const VoiceStateManager = require('../managers/VoiceStateManager');
+const {
+ ChannelTypes,
+ DefaultMessageNotificationLevels,
+ PartialTypes,
+ VerificationLevels,
+ ExplicitContentFilterLevels,
+ Status,
+ MFALevels,
+ PremiumTiers,
+} = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
-const Partials = require('../util/Partials');
-const Status = require('../util/Status');
-const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
+const SystemChannelFlags = require('../util/SystemChannelFlags');
const Util = require('../util/Util');
+let deprecationEmittedForSetChannelPositions = false;
+let deprecationEmittedForSetRolePositions = false;
+let deprecationEmittedForDeleted = false;
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedGuilds = new WeakSet();
+
/**
* Represents a guild (or a server) on Discord.
* It's recommended to see if a guild is available before performing operations or reading data from it. You can
@@ -35,1132 +54,1130 @@ const Util = require('../util/Util');
* @extends {AnonymousGuild}
*/
class Guild extends AnonymousGuild {
- constructor(client, data) {
- super(client, data, false);
-
- /**
- * A manager of the application commands belonging to this guild
- * @type {GuildApplicationCommandManager}
- */
- this.commands = new GuildApplicationCommandManager(this);
-
- /**
- * A manager of the members belonging to this guild
- * @type {GuildMemberManager}
- */
- this.members = new GuildMemberManager(this);
-
- /**
- * A manager of the channels belonging to this guild
- * @type {GuildChannelManager}
- */
- this.channels = new GuildChannelManager(this);
-
- /**
- * A manager of the bans belonging to this guild
- * @type {GuildBanManager}
- */
- this.bans = new GuildBanManager(this);
-
- /**
- * A manager of the roles belonging to this guild
- * @type {RoleManager}
- */
- this.roles = new RoleManager(this);
-
- /**
- * A manager of the presences belonging to this guild
- * @type {PresenceManager}
- */
- this.presences = new PresenceManager(this.client);
-
- /**
- * A manager of the voice states of this guild
- * @type {VoiceStateManager}
- */
- this.voiceStates = new VoiceStateManager(this);
-
- /**
- * A manager of the stage instances of this guild
- * @type {StageInstanceManager}
- */
- this.stageInstances = new StageInstanceManager(this);
-
- /**
- * A manager of the invites of this guild
- * @type {GuildInviteManager}
- */
- this.invites = new GuildInviteManager(this);
-
- /**
- * A manager of the scheduled events of this guild
- * @type {GuildScheduledEventManager}
- */
- this.scheduledEvents = new GuildScheduledEventManager(this);
-
- if (!data) return;
- if (data.unavailable) {
- /**
- * Whether the guild is available to access. If it is not available, it indicates a server outage
- * @type {boolean}
- */
- this.available = false;
- } else {
- this._patch(data);
- if (!data.channels) this.available = false;
- }
-
- /**
- * The id of the shard this Guild belongs to.
- * @type {number}
- */
- this.shardId = data.shardId;
- }
-
- /**
- * The Shard this Guild belongs to.
- * @type {WebSocketShard}
- * @readonly
- */
- get shard() {
- return this.client.ws.shards.get(this.shardId);
- }
-
- _patch(data) {
- super._patch(data);
- this.id = data.id;
- if ('name' in data) this.name = data.name;
- if ('icon' in data) this.icon = data.icon;
- if ('unavailable' in data) {
- this.available = !data.unavailable;
- } else {
- this.available ??= true;
- }
-
- if ('discovery_splash' in data) {
- /**
- * The hash of the guild discovery splash image
- * @type {?string}
- */
- this.discoverySplash = data.discovery_splash;
- }
-
- if ('member_count' in data) {
- /**
- * The full amount of members in this guild
- * @type {number}
- */
- this.memberCount = data.member_count;
- }
-
- if ('large' in data) {
- /**
- * Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default)
- * @type {boolean}
- */
- this.large = Boolean(data.large);
- }
-
- if ('premium_progress_bar_enabled' in data) {
- /**
- * Whether this guild has its premium (boost) progress bar enabled
- * @type {boolean}
- */
- this.premiumProgressBarEnabled = data.premium_progress_bar_enabled;
- }
-
- if ('application_id' in data) {
- /**
- * The id of the application that created this guild (if applicable)
- * @type {?Snowflake}
- */
- this.applicationId = data.application_id;
- }
-
- if ('afk_timeout' in data) {
- /**
- * The time in seconds before a user is counted as "away from keyboard"
- * @type {?number}
- */
- this.afkTimeout = data.afk_timeout;
- }
-
- if ('afk_channel_id' in data) {
- /**
- * The id of the voice channel where AFK members are moved
- * @type {?Snowflake}
- */
- this.afkChannelId = data.afk_channel_id;
- }
-
- if ('system_channel_id' in data) {
- /**
- * The system channel's id
- * @type {?Snowflake}
- */
- this.systemChannelId = data.system_channel_id;
- }
-
- if ('premium_tier' in data) {
- /**
- * The premium tier of this guild
- * @type {GuildPremiumTier}
- */
- this.premiumTier = data.premium_tier;
- }
-
- if ('widget_enabled' in data) {
- /**
- * Whether widget images are enabled on this guild
- * @type {?boolean}
- */
- this.widgetEnabled = data.widget_enabled;
- }
-
- if ('widget_channel_id' in data) {
- /**
- * The widget channel's id, if enabled
- * @type {?string}
- */
- this.widgetChannelId = data.widget_channel_id;
- }
-
- if ('explicit_content_filter' in data) {
- /**
- * The explicit content filter level of the guild
- * @type {GuildExplicitContentFilter}
- */
- this.explicitContentFilter = data.explicit_content_filter;
- }
-
- if ('mfa_level' in data) {
- /**
- * The required MFA level for this guild
- * @type {MFALevel}
- */
- this.mfaLevel = data.mfa_level;
- }
-
- if ('joined_at' in data) {
- /**
- * The timestamp the client user joined the guild at
- * @type {number}
- */
- this.joinedTimestamp = Date.parse(data.joined_at);
- }
-
- if ('default_message_notifications' in data) {
- /**
- * The default message notification level of the guild
- * @type {GuildDefaultMessageNotifications}
- */
- this.defaultMessageNotifications = data.default_message_notifications;
- }
-
- if ('system_channel_flags' in data) {
- /**
- * The value set for the guild's system channel flags
- * @type {Readonly}
- */
- this.systemChannelFlags = new SystemChannelFlagsBitField(
- data.system_channel_flags,
- ).freeze();
- }
-
- if ('max_members' in data) {
- /**
- * The maximum amount of members the guild can have
- * @type {?number}
- */
- this.maximumMembers = data.max_members;
- } else {
- this.maximumMembers ??= null;
- }
-
- if ('max_presences' in data) {
- /**
- * The maximum amount of presences the guild can have (this is `null` for all but the largest of guilds)
- * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter
- * @type {?number}
- */
- this.maximumPresences = data.max_presences;
- } else {
- this.maximumPresences ??= null;
- }
-
- if ('approximate_member_count' in data) {
- /**
- * The approximate amount of members the guild has
- * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter
- * @type {?number}
- */
- this.approximateMemberCount = data.approximate_member_count;
- } else {
- this.approximateMemberCount ??= null;
- }
-
- if ('approximate_presence_count' in data) {
- /**
- * The approximate amount of presences the guild has
- * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter
- * @type {?number}
- */
- this.approximatePresenceCount = data.approximate_presence_count;
- } else {
- this.approximatePresenceCount ??= null;
- }
-
- /**
- * The use count of the vanity URL code of the guild, if any
- * You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it
- * @type {?number}
- */
- this.vanityURLUses ??= null;
-
- if ('rules_channel_id' in data) {
- /**
- * The rules channel's id for the guild
- * @type {?Snowflake}
- */
- this.rulesChannelId = data.rules_channel_id;
- }
-
- if ('public_updates_channel_id' in data) {
- /**
- * The community updates channel's id for the guild
- * @type {?Snowflake}
- */
- this.publicUpdatesChannelId = data.public_updates_channel_id;
- }
-
- if ('preferred_locale' in data) {
- /**
- * The preferred locale of the guild, defaults to `en-US`
- * @type {string}
- * @see {@link https://discord.com/developers/docs/reference#locales}
- */
- this.preferredLocale = data.preferred_locale;
- }
-
- if (data.channels) {
- this.channels.cache.clear();
- for (const rawChannel of data.channels) {
- this.client.channels._add(rawChannel, this);
- }
- }
-
- if (data.threads) {
- for (const rawThread of data.threads) {
- this.client.channels._add(rawThread, this);
- }
- }
-
- if (data.roles) {
- this.roles.cache.clear();
- for (const role of data.roles) this.roles._add(role);
- }
-
- if (data.members) {
- this.members.cache.clear();
- for (const guildUser of data.members) this.members._add(guildUser);
- }
-
- if ('owner_id' in data) {
- /**
- * The user id of this guild's owner
- * @type {Snowflake}
- */
- this.ownerId = data.owner_id;
- }
-
- if (data.presences) {
- for (const presence of data.presences) {
- this.presences._add(Object.assign(presence, { guild: this }));
- }
- }
-
- if (data.stage_instances) {
- this.stageInstances.cache.clear();
- for (const stageInstance of data.stage_instances) {
- this.stageInstances._add(stageInstance);
- }
- }
-
- if (data.guild_scheduled_events) {
- this.scheduledEvents.cache.clear();
- for (const scheduledEvent of data.guild_scheduled_events) {
- this.scheduledEvents._add(scheduledEvent);
- }
- }
-
- if (data.voice_states) {
- this.voiceStates.cache.clear();
- for (const voiceState of data.voice_states) {
- this.voiceStates._add(voiceState);
- }
- }
-
- if (!this.emojis) {
- /**
- * A manager of the emojis belonging to this guild
- * @type {GuildEmojiManager}
- */
- this.emojis = new GuildEmojiManager(this);
- if (data.emojis) for (const emoji of data.emojis) this.emojis._add(emoji);
- } else if (data.emojis) {
- this.client.actions.GuildEmojisUpdate.handle({
- guild_id: this.id,
- emojis: data.emojis,
- });
- }
-
- if (!this.stickers) {
- /**
- * A manager of the stickers belonging to this guild
- * @type {GuildStickerManager}
- */
- this.stickers = new GuildStickerManager(this);
- if (data.stickers)
- for (const sticker of data.stickers) this.stickers._add(sticker);
- } else if (data.stickers) {
- this.client.actions.GuildStickersUpdate.handle({
- guild_id: this.id,
- stickers: data.stickers,
- });
- }
- }
-
- /**
- * The time the client user joined the guild
- * @type {Date}
- * @readonly
- */
- get joinedAt() {
- return new Date(this.joinedTimestamp);
- }
-
- /**
- * Positions of the guild [User Account]
- * @type {number}
- * @readonly
- */
- get position() {
- return (
- this.client.setting.guildMetadata.get(this.id.toString())?.guildIndex ||
- null
- );
- }
-
- /**
- * Folder Guilds
- * @type {object}
- * @readonly
- */
- get folder() {
- return this.client.setting.guildMetadata.get(this.id.toString()) || {};
- }
-
- /**
- * The URL to this guild's discovery splash image.
- * @param {ImageURLOptions} [options={}] Options for the image URL
- * @returns {?string}
- */
- discoverySplashURL(options = {}) {
- return (
- this.discoverySplash &&
- this.client.rest.cdn.discoverySplash(
- this.id,
- this.discoverySplash,
- options,
- )
- );
- }
-
- /**
- * Fetches the owner of the guild.
- * If the member object isn't needed, use {@link Guild#ownerId} instead.
- * @param {BaseFetchOptions} [options] The options for fetching the member
- * @returns {Promise}
- */
- fetchOwner(options) {
- return this.members.fetch({ ...options, user: this.ownerId });
- }
-
- /**
- * AFK voice channel for this guild
- * @type {?VoiceChannel}
- * @readonly
- */
- get afkChannel() {
- return this.client.channels.resolve(this.afkChannelId);
- }
-
- /**
- * System channel for this guild
- * @type {?TextChannel}
- * @readonly
- */
- get systemChannel() {
- return this.client.channels.resolve(this.systemChannelId);
- }
-
- /**
- * Widget channel for this guild
- * @type {?TextChannel}
- * @readonly
- */
- get widgetChannel() {
- return this.client.channels.resolve(this.widgetChannelId);
- }
-
- /**
- * Rules channel for this guild
- * @type {?TextChannel}
- * @readonly
- */
- get rulesChannel() {
- return this.client.channels.resolve(this.rulesChannelId);
- }
-
- /**
- * Public updates channel for this guild
- * @type {?TextChannel}
- * @readonly
- */
- get publicUpdatesChannel() {
- return this.client.channels.resolve(this.publicUpdatesChannelId);
- }
-
- /**
- * The client user as a GuildMember of this guild
- * @type {?GuildMember}
- * @readonly
- */
- get me() {
- return (
- this.members.resolve(this.client.user.id) ??
- (this.client.options.partials.includes(Partials.GuildMember)
- ? this.members._add({ user: { id: this.client.user.id } }, true)
- : null)
- );
- }
-
- /**
- * The maximum bitrate available for this guild
- * @type {number}
- * @readonly
- */
- get maximumBitrate() {
- if (this.features.includes('VIP_REGIONS')) {
- return 384_000;
- }
-
- switch (this.premiumTier) {
- case GuildPremiumTier.Tier1:
- return 128_000;
- case GuildPremiumTier.Tier2:
- return 256_000;
- case GuildPremiumTier.Tier3:
- return 384_000;
- default:
- return 96_000;
- }
- }
-
- /**
- * Fetches a collection of integrations to this guild.
- * Resolves with a collection mapping integrations by their ids.
- * @returns {Promise>}
- * @example
- * // Fetch integrations
- * guild.fetchIntegrations()
- * .then(integrations => console.log(`Fetched ${integrations.size} integrations`))
- * .catch(console.error);
- */
- async fetchIntegrations() {
- const data = await this.client.api.guilds(this.id).integrations.get();
- return data.reduce(
- (collection, integration) =>
- collection.set(
- integration.id,
- new Integration(this.client, integration, this),
- ),
- new Collection(),
- );
- }
-
- /**
- * Fetches a collection of templates from this guild.
- * Resolves with a collection mapping templates by their codes.
- * @returns {Promise>}
- */
- async fetchTemplates() {
- const templates = await this.client.api.guilds(this.id).templates.get();
- return templates.reduce(
- (col, data) => col.set(data.code, new GuildTemplate(this.client, data)),
- new Collection(),
- );
- }
-
- /**
- * Fetches the welcome screen for this guild.
- * @returns {Promise}
- */
- async fetchWelcomeScreen() {
- const data = await this.client.api.guilds(this.id, 'welcome-screen').get();
- return new WelcomeScreen(this, data);
- }
-
- /**
- * Creates a template for the guild.
- * @param {string} name The name for the template
- * @param {string} [description] The description for the template
- * @returns {Promise}
- */
- async createTemplate(name, description) {
- const data = await this.client.api
- .guilds(this.id)
- .templates.post({ body: { name, description } });
- return new GuildTemplate(this.client, data);
- }
-
- /**
- * Obtains a guild preview for this guild from Discord.
- * @returns {Promise}
- */
- async fetchPreview() {
- const data = await this.client.api.guilds(this.id).preview.get();
- return new GuildPreview(this.client, data);
- }
-
- /**
- * An object containing information about a guild's vanity invite.
- * @typedef {Object} Vanity
- * @property {?string} code Vanity invite code
- * @property {number} uses How many times this invite has been used
- */
-
- /**
- * Fetches the vanity URL invite object to this guild.
- * Resolves with an object containing the vanity URL invite code and the use count
- * @returns {Promise}
- * @example
- * // Fetch invite data
- * guild.fetchVanityData()
- * .then(res => {
- * console.log(`Vanity URL: https://discord.gg/${res.code} with ${res.uses} uses`);
- * })
- * .catch(console.error);
- */
- async fetchVanityData() {
- if (!this.features.includes('VANITY_URL')) {
- throw new Error('VANITY_URL');
- }
- const data = await this.client.api.guilds(this.id, 'vanity-url').get();
- this.vanityURLCode = data.code;
- this.vanityURLUses = data.uses;
-
- return data;
- }
-
- /**
- * Fetches all webhooks for the guild.
- * @returns {Promise>}
- * @example
- * // Fetch webhooks
- * guild.fetchWebhooks()
- * .then(webhooks => console.log(`Fetched ${webhooks.size} webhooks`))
- * .catch(console.error);
- */
- async fetchWebhooks() {
- const apiHooks = await this.client.api.guilds(this.id).webhooks.get();
- const hooks = new Collection();
- for (const hook of apiHooks)
- hooks.set(hook.id, new Webhook(this.client, hook));
- return hooks;
- }
-
- /**
- * Fetches the guild widget data, requires the widget to be enabled.
- * @returns {Promise}
- * @example
- * // Fetches the guild widget data
- * guild.fetchWidget()
- * .then(widget => console.log(`The widget shows ${widget.channels.size} channels`))
- * .catch(console.error);
- */
- fetchWidget() {
- return this.client.fetchGuildWidget(this.id);
- }
-
- /**
- * Data for the Guild Widget Settings object
- * @typedef {Object} GuildWidgetSettings
- * @property {boolean} enabled Whether the widget is enabled
- * @property {?GuildChannel} channel The widget invite channel
- */
-
- /**
- * The Guild Widget Settings object
- * @typedef {Object} GuildWidgetSettingsData
- * @property {boolean} enabled Whether the widget is enabled
- * @property {?GuildChannelResolvable} channel The widget invite channel
- */
-
- /**
- * Fetches the guild widget settings.
- * @returns {Promise}
- * @example
- * // Fetches the guild widget settings
- * guild.fetchWidgetSettings()
- * .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`))
- * .catch(console.error);
- */
- async fetchWidgetSettings() {
- const data = await this.client.api.guilds(this.id).widget.get();
- this.widgetEnabled = data.enabled;
- this.widgetChannelId = data.channel_id;
- return {
- enabled: data.enabled,
- channel: data.channel_id
- ? this.channels.cache.get(data.channel_id)
- : null,
- };
- }
-
- /**
- * Options used to fetch audit logs.
- * @typedef {Object} GuildAuditLogsFetchOptions
- * @property {Snowflake|GuildAuditLogsEntry} [before] Only return entries before this entry
- * @property {number} [limit] The number of entries to return
- * @property {UserResolvable} [user] Only return entries for actions made by this user
- * @property {?AuditLogEvent} [type] Only return entries for this action type
- */
-
- /**
- * Fetches audit logs for this guild.
- * @param {GuildAuditLogsFetchOptions} [options={}] Options for fetching audit logs
- * @returns {Promise}
- * @example
- * // Output audit log entries
- * guild.fetchAuditLogs()
- * .then(audit => console.log(audit.entries.first()))
- * .catch(console.error);
- */
- async fetchAuditLogs(options = {}) {
- if (options.before && options.before instanceof GuildAuditLogs.Entry)
- options.before = options.before.id;
-
- const query = new URLSearchParams();
-
- if (options.before) {
- query.set('before', options.before);
- }
-
- if (options.limit) {
- query.set('limit', options.limit);
- }
-
- if (options.user) {
- const id = this.client.users.resolveId(options.user);
- if (!id) throw new TypeError('INVALID_TYPE', 'user', 'UserResolvable');
- query.set('user_id', id);
- }
-
- if (options.type) {
- query.set('action_type', options.type);
- }
-
- const data = await this.client.api
- .guilds(this.id)
- ['audit-logs'].get({ query });
- return GuildAuditLogs.build(this, data);
- }
-
- /**
- * The data for editing a guild.
- * @typedef {Object} GuildEditData
- * @property {string} [name] The name of the guild
- * @property {VerificationLevel|number} [verificationLevel] The verification level of the guild
- * @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter
- * @property {VoiceChannelResolvable} [afkChannel] The AFK channel of the guild
- * @property {TextChannelResolvable} [systemChannel] The system channel of the guild
- * @property {number} [afkTimeout] The AFK timeout of the guild
- * @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the guild
- * @property {GuildMemberResolvable} [owner] The owner of the guild
- * @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild
- * @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild
- * @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild
- * @property {DefaultMessageNotificationLevel|number} [defaultMessageNotifications] The default message notification
- * level of the guild
- * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild
- * @property {TextChannelResolvable} [rulesChannel] The rules channel of the guild
- * @property {TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild
- * @property {string} [preferredLocale] The preferred locale of the guild
- * @property {boolean} [premiumProgressBarEnabled] Whether the guild's premium progress bar is enabled
- * @property {string} [description] The discovery description of the guild
- * @property {GuildFeature[]} [features] The features of the guild
- */
-
- /**
- * Data that can be resolved to a Text Channel object. This can be:
- * * A TextChannel
- * * A Snowflake
- * @typedef {TextChannel|Snowflake} TextChannelResolvable
- */
-
- /**
- * Data that can be resolved to a Voice Channel object. This can be:
- * * A VoiceChannel
- * * A Snowflake
- * @typedef {VoiceChannel|Snowflake} VoiceChannelResolvable
- */
-
- /**
- * Updates the guild with new information - e.g. a new name.
- * @param {GuildEditData} data The data to update the guild with
- * @param {string} [reason] Reason for editing this guild
- * @returns {Promise}
- * @example
- * // Set the guild name
- * guild.edit({
- * name: 'Discord Guild',
- * })
- * .then(updated => console.log(`New guild name ${updated}`))
- * .catch(console.error);
- */
- async edit(data, reason) {
- const _data = {};
- if (data.name) _data.name = data.name;
- if (typeof data.verificationLevel !== 'undefined') {
- _data.verification_level = data.verificationLevel;
- }
- if (typeof data.afkChannel !== 'undefined') {
- _data.afk_channel_id = this.client.channels.resolveId(data.afkChannel);
- }
- if (typeof data.systemChannel !== 'undefined') {
- _data.system_channel_id = this.client.channels.resolveId(
- data.systemChannel,
- );
- }
- if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout);
- if (typeof data.icon !== 'undefined')
- _data.icon = await DataResolver.resolveImage(data.icon);
- if (data.owner) _data.owner_id = this.client.users.resolveId(data.owner);
- if (typeof data.splash !== 'undefined')
- _data.splash = await DataResolver.resolveImage(data.splash);
- if (typeof data.discoverySplash !== 'undefined') {
- _data.discovery_splash = await DataResolver.resolveImage(
- data.discoverySplash,
- );
- }
- if (typeof data.banner !== 'undefined')
- _data.banner = await DataResolver.resolveImage(data.banner);
- if (typeof data.explicitContentFilter !== 'undefined') {
- _data.explicit_content_filter = data.explicitContentFilter;
- }
- if (typeof data.defaultMessageNotifications !== 'undefined') {
- _data.default_message_notifications = data.defaultMessageNotifications;
- }
- if (typeof data.systemChannelFlags !== 'undefined') {
- _data.system_channel_flags = SystemChannelFlagsBitField.resolve(
- data.systemChannelFlags,
- );
- }
- if (typeof data.rulesChannel !== 'undefined') {
- _data.rules_channel_id = this.client.channels.resolveId(
- data.rulesChannel,
- );
- }
- if (typeof data.publicUpdatesChannel !== 'undefined') {
- _data.public_updates_channel_id = this.client.channels.resolveId(
- data.publicUpdatesChannel,
- );
- }
- if (typeof data.features !== 'undefined') {
- _data.features = data.features;
- }
- if (typeof data.description !== 'undefined') {
- _data.description = data.description;
- }
- if (data.preferredLocale) _data.preferred_locale = data.preferredLocale;
- if ('premiumProgressBarEnabled' in data)
- _data.premium_progress_bar_enabled = data.premiumProgressBarEnabled;
- const newData = await this.client.api
- .guilds(this.id)
- .patch({ body: _data, reason });
- return this.client.actions.GuildUpdate.handle(newData).updated;
- }
-
- /**
- * Welcome channel data
- * @typedef {Object} WelcomeChannelData
- * @property {string} description The description to show for this welcome channel
- * @property {TextChannel|NewsChannel|StoreChannel|Snowflake} channel The channel to link for this welcome channel
- * @property {EmojiIdentifierResolvable} [emoji] The emoji to display for this welcome channel
- */
-
- /**
- * Welcome screen edit data
- * @typedef {Object} WelcomeScreenEditData
- * @property {boolean} [enabled] Whether the welcome screen is enabled
- * @property {string} [description] The description for the welcome screen
- * @property {WelcomeChannelData[]} [welcomeChannels] The welcome channel data for the welcome screen
- */
-
- /**
- * Data that can be resolved to a GuildTextChannel object. This can be:
- * * A TextChannel
- * * A NewsChannel
- * * A Snowflake
- * @typedef {TextChannel|NewsChannel|Snowflake} GuildTextChannelResolvable
- */
-
- /**
- * Data that can be resolved to a GuildVoiceChannel object. This can be:
- * * A VoiceChannel
- * * A StageChannel
- * * A Snowflake
- * @typedef {VoiceChannel|StageChannel|Snowflake} GuildVoiceChannelResolvable
- */
-
- /**
- * Updates the guild's welcome screen
- * @param {WelcomeScreenEditData} data Data to edit the welcome screen with
- * @returns {Promise}
- * @example
- * guild.editWelcomeScreen({
- * description: 'Hello World',
- * enabled: true,
- * welcomeChannels: [
- * {
- * description: 'foobar',
- * channel: '222197033908436994',
- * }
- * ],
- * })
- */
- async editWelcomeScreen(data) {
- const { enabled, description, welcomeChannels } = data;
- const welcome_channels = welcomeChannels?.map((welcomeChannelData) => {
- const emoji = this.emojis.resolve(welcomeChannelData.emoji);
- return {
- emoji_id: emoji?.id,
- emoji_name: emoji?.name ?? welcomeChannelData.emoji,
- channel_id: this.channels.resolveId(welcomeChannelData.channel),
- description: welcomeChannelData.description,
- };
- });
-
- const patchData = await this.client.api
- .guilds(this.id, 'welcome-screen')
- .patch({
- body: {
- welcome_channels,
- description,
- enabled,
- },
- });
- return new WelcomeScreen(this, patchData);
- }
-
- /**
- * Edits the level of the explicit content filter.
- * @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter
- * @param {string} [reason] Reason for changing the level of the guild's explicit content filter
- * @returns {Promise}
- */
- setExplicitContentFilter(explicitContentFilter, reason) {
- return this.edit({ explicitContentFilter }, reason);
- }
-
- /* eslint-disable max-len */
- /**
- * Edits the setting of the default message notifications of the guild.
- * @param {DefaultMessageNotificationLevel|number} defaultMessageNotifications The new default message notification level of the guild
- * @param {string} [reason] Reason for changing the setting of the default message notifications
- * @returns {Promise}
- */
- setDefaultMessageNotifications(defaultMessageNotifications, reason) {
- return this.edit({ defaultMessageNotifications }, reason);
- }
- /* eslint-enable max-len */
-
- /**
- * Edits the flags of the default message notifications of the guild.
- * @param {SystemChannelFlagsResolvable} systemChannelFlags The new flags for the default message notifications
- * @param {string} [reason] Reason for changing the flags of the default message notifications
- * @returns {Promise}
- */
- setSystemChannelFlags(systemChannelFlags, reason) {
- return this.edit({ systemChannelFlags }, reason);
- }
-
- /**
- * Edits the name of the guild.
- * @param {string} name The new name of the guild
- * @param {string} [reason] Reason for changing the guild's name
- * @returns {Promise}
- * @example
- * // Edit the guild name
- * guild.setName('Discord Guild')
- * .then(updated => console.log(`Updated guild name to ${updated.name}`))
- * .catch(console.error);
- */
- setName(name, reason) {
- return this.edit({ name }, reason);
- }
-
- /**
- * Edits the verification level of the guild.
- * @param {VerificationLevel} verificationLevel The new verification level of the guild
- * @param {string} [reason] Reason for changing the guild's verification level
- * @returns {Promise}
- * @example
- * // Edit the guild verification level
- * guild.setVerificationLevel(1)
- * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`))
- * .catch(console.error);
- */
- setVerificationLevel(verificationLevel, reason) {
- return this.edit({ verificationLevel }, reason);
- }
-
- /**
- * Edits the AFK channel of the guild.
- * @param {VoiceChannelResolvable} afkChannel The new AFK channel
- * @param {string} [reason] Reason for changing the guild's AFK channel
- * @returns {Promise}
- * @example
- * // Edit the guild AFK channel
- * guild.setAFKChannel(channel)
- * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel.name}`))
- * .catch(console.error);
- */
- setAFKChannel(afkChannel, reason) {
- return this.edit({ afkChannel }, reason);
- }
-
- /**
- * Edits the system channel of the guild.
- * @param {TextChannelResolvable} systemChannel The new system channel
- * @param {string} [reason] Reason for changing the guild's system channel
- * @returns {Promise}
- * @example
- * // Edit the guild system channel
- * guild.setSystemChannel(channel)
- * .then(updated => console.log(`Updated guild system channel to ${guild.systemChannel.name}`))
- * .catch(console.error);
- */
- setSystemChannel(systemChannel, reason) {
- return this.edit({ systemChannel }, reason);
- }
-
- /**
- * Edits the AFK timeout of the guild.
- * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK
- * @param {string} [reason] Reason for changing the guild's AFK timeout
- * @returns {Promise}
- * @example
- * // Edit the guild AFK channel
- * guild.setAFKTimeout(60)
- * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`))
- * .catch(console.error);
- */
- setAFKTimeout(afkTimeout, reason) {
- return this.edit({ afkTimeout }, reason);
- }
-
- /**
- * Sets a new guild icon.
- * @param {?(Base64Resolvable|BufferResolvable)} icon The new icon of the guild
- * @param {string} [reason] Reason for changing the guild's icon
- * @returns {Promise}
- * @example
- * // Edit the guild icon
- * guild.setIcon('./icon.png')
- * .then(updated => console.log('Updated the guild icon'))
- * .catch(console.error);
- */
- setIcon(icon, reason) {
- return this.edit({ icon }, reason);
- }
-
- /**
- * Sets a new owner of the guild.
- * @param {GuildMemberResolvable} owner The new owner of the guild
- * @param {string} [reason] Reason for setting the new owner
- * @returns {Promise}
- * @example
- * // Edit the guild owner
- * guild.setOwner(guild.members.cache.first())
- * .then(guild => guild.fetchOwner())
- * .then(owner => console.log(`Updated the guild owner to ${owner.displayName}`))
- * .catch(console.error);
- */
- setOwner(owner, reason) {
- return this.edit({ owner }, reason);
- }
-
- /**
- * Sets a new guild invite splash image.
- * @param {?(Base64Resolvable|BufferResolvable)} splash The new invite splash image of the guild
- * @param {string} [reason] Reason for changing the guild's invite splash image
- * @returns {Promise}
- * @example
- * // Edit the guild splash
- * guild.setSplash('./splash.png')
- * .then(updated => console.log('Updated the guild splash'))
- * .catch(console.error);
- */
- setSplash(splash, reason) {
- return this.edit({ splash }, reason);
- }
-
- /**
- * Sets a new guild discovery splash image.
- * @param {?(Base64Resolvable|BufferResolvable)} discoverySplash The new discovery splash image of the guild
- * @param {string} [reason] Reason for changing the guild's discovery splash image
- * @returns {Promise}
- * @example
- * // Edit the guild discovery splash
- * guild.setDiscoverySplash('./discoverysplash.png')
- * .then(updated => console.log('Updated the guild discovery splash'))
- * .catch(console.error);
- */
- setDiscoverySplash(discoverySplash, reason) {
- return this.edit({ discoverySplash }, reason);
- }
-
- /**
- * Sets a new guild banner.
- * @param {?(Base64Resolvable|BufferResolvable)} banner The new banner of the guild
- * @param {string} [reason] Reason for changing the guild's banner
- * @returns {Promise}
- * @example
- * guild.setBanner('./banner.png')
- * .then(updated => console.log('Updated the guild banner'))
- * .catch(console.error);
- */
- setBanner(banner, reason) {
- return this.edit({ banner }, reason);
- }
-
- /**
- * Edits the rules channel of the guild.
- * @param {TextChannelResolvable} rulesChannel The new rules channel
- * @param {string} [reason] Reason for changing the guild's rules channel
- * @returns {Promise}
- * @example
- * // Edit the guild rules channel
- * guild.setRulesChannel(channel)
- * .then(updated => console.log(`Updated guild rules channel to ${guild.rulesChannel.name}`))
- * .catch(console.error);
- */
- setRulesChannel(rulesChannel, reason) {
- return this.edit({ rulesChannel }, reason);
- }
-
+ constructor(client, data) {
+ super(client, data, false);
+
+ /**
+ * A manager of the application commands belonging to this guild
+ * @type {GuildApplicationCommandManager}
+ */
+ this.commands = new GuildApplicationCommandManager(this);
+
+ /**
+ * A manager of the members belonging to this guild
+ * @type {GuildMemberManager}
+ */
+ this.members = new GuildMemberManager(this);
+
+ /**
+ * A manager of the channels belonging to this guild
+ * @type {GuildChannelManager}
+ */
+ this.channels = new GuildChannelManager(this);
+
+ /**
+ * A manager of the bans belonging to this guild
+ * @type {GuildBanManager}
+ */
+ this.bans = new GuildBanManager(this);
+
+ /**
+ * A manager of the roles belonging to this guild
+ * @type {RoleManager}
+ */
+ this.roles = new RoleManager(this);
+
+ /**
+ * A manager of the presences belonging to this guild
+ * @type {PresenceManager}
+ */
+ this.presences = new PresenceManager(this.client);
+
+ /**
+ * A manager of the voice states of this guild
+ * @type {VoiceStateManager}
+ */
+ this.voiceStates = new VoiceStateManager(this);
+
+ /**
+ * A manager of the stage instances of this guild
+ * @type {StageInstanceManager}
+ */
+ this.stageInstances = new StageInstanceManager(this);
+
+ /**
+ * A manager of the invites of this guild
+ * @type {GuildInviteManager}
+ */
+ this.invites = new GuildInviteManager(this);
+
+ /**
+ * A manager of the scheduled events of this guild
+ * @type {GuildScheduledEventManager}
+ */
+ this.scheduledEvents = new GuildScheduledEventManager(this);
+
+ if (!data) return;
+ if (data.unavailable) {
+ /**
+ * Whether the guild is available to access. If it is not available, it indicates a server outage
+ * @type {boolean}
+ */
+ this.available = false;
+ } else {
+ this._patch(data);
+ if (!data.channels) this.available = false;
+ }
+
+ /**
+ * The id of the shard this Guild belongs to.
+ * @type {number}
+ */
+ this.shardId = data.shardId;
+ }
+
+ /**
+ * Whether or not the structure has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Guild#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedGuilds.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Guild#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedGuilds.add(this);
+ else deletedGuilds.delete(this);
+ }
+
+ /**
+ * The Shard this Guild belongs to.
+ * @type {WebSocketShard}
+ * @readonly
+ */
+ get shard() {
+ return this.client.ws.shards.get(this.shardId);
+ }
+
+ _patch(data) {
+ super._patch(data);
+ this.id = data.id;
+ if ('name' in data) this.name = data.name;
+ if ('icon' in data) this.icon = data.icon;
+ if ('unavailable' in data) {
+ this.available = !data.unavailable;
+ } else {
+ this.available ??= true;
+ }
+
+ if ('discovery_splash' in data) {
+ /**
+ * The hash of the guild discovery splash image
+ * @type {?string}
+ */
+ this.discoverySplash = data.discovery_splash;
+ }
+
+ if ('member_count' in data) {
+ /**
+ * The full amount of members in this guild
+ * @type {number}
+ */
+ this.memberCount = data.member_count;
+ }
+
+ if ('large' in data) {
+ /**
+ * Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default)
+ * @type {boolean}
+ */
+ this.large = Boolean(data.large);
+ }
+
+ if ('premium_progress_bar_enabled' in data) {
+ /**
+ * Whether this guild has its premium (boost) progress bar enabled
+ * @type {boolean}
+ */
+ this.premiumProgressBarEnabled = data.premium_progress_bar_enabled;
+ }
+
+ /**
+ * An array of enabled guild features, here are the possible values:
+ * * ANIMATED_ICON
+ * * BANNER
+ * * COMMERCE
+ * * COMMUNITY
+ * * DISCOVERABLE
+ * * FEATURABLE
+ * * INVITE_SPLASH
+ * * MEMBER_VERIFICATION_GATE_ENABLED
+ * * NEWS
+ * * PARTNERED
+ * * PREVIEW_ENABLED
+ * * VANITY_URL
+ * * VERIFIED
+ * * VIP_REGIONS
+ * * WELCOME_SCREEN_ENABLED
+ * * TICKETED_EVENTS_ENABLED
+ * * MONETIZATION_ENABLED
+ * * MORE_STICKERS
+ * * THREE_DAY_THREAD_ARCHIVE
+ * * SEVEN_DAY_THREAD_ARCHIVE
+ * * PRIVATE_THREADS
+ * * ROLE_ICONS
+ * @typedef {string} Features
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-guild-features}
+ */
+
+ if ('application_id' in data) {
+ /**
+ * The id of the application that created this guild (if applicable)
+ * @type {?Snowflake}
+ */
+ this.applicationId = data.application_id;
+ }
+
+ if ('afk_timeout' in data) {
+ /**
+ * The time in seconds before a user is counted as "away from keyboard"
+ * @type {?number}
+ */
+ this.afkTimeout = data.afk_timeout;
+ }
+
+ if ('afk_channel_id' in data) {
+ /**
+ * The id of the voice channel where AFK members are moved
+ * @type {?Snowflake}
+ */
+ this.afkChannelId = data.afk_channel_id;
+ }
+
+ if ('system_channel_id' in data) {
+ /**
+ * The system channel's id
+ * @type {?Snowflake}
+ */
+ this.systemChannelId = data.system_channel_id;
+ }
+
+ if ('premium_tier' in data) {
+ /**
+ * The premium tier of this guild
+ * @type {PremiumTier}
+ */
+ this.premiumTier = PremiumTiers[data.premium_tier];
+ }
+
+ if ('premium_subscription_count' in data) {
+ /**
+ * The total number of boosts for this server
+ * @type {?number}
+ */
+ this.premiumSubscriptionCount = data.premium_subscription_count;
+ }
+
+ if ('widget_enabled' in data) {
+ /**
+ * Whether widget images are enabled on this guild
+ * @type {?boolean}
+ */
+ this.widgetEnabled = data.widget_enabled;
+ }
+
+ if ('widget_channel_id' in data) {
+ /**
+ * The widget channel's id, if enabled
+ * @type {?string}
+ */
+ this.widgetChannelId = data.widget_channel_id;
+ }
+
+ if ('explicit_content_filter' in data) {
+ /**
+ * The explicit content filter level of the guild
+ * @type {ExplicitContentFilterLevel}
+ */
+ this.explicitContentFilter = ExplicitContentFilterLevels[data.explicit_content_filter];
+ }
+
+ if ('mfa_level' in data) {
+ /**
+ * The required MFA level for this guild
+ * @type {MFALevel}
+ */
+ this.mfaLevel = MFALevels[data.mfa_level];
+ }
+
+ if ('joined_at' in data) {
+ /**
+ * The timestamp the client user joined the guild at
+ * @type {number}
+ */
+ this.joinedTimestamp = new Date(data.joined_at).getTime();
+ }
+
+ if ('default_message_notifications' in data) {
+ /**
+ * The default message notification level of the guild
+ * @type {DefaultMessageNotificationLevel}
+ */
+ this.defaultMessageNotifications = DefaultMessageNotificationLevels[data.default_message_notifications];
+ }
+
+ if ('system_channel_flags' in data) {
+ /**
+ * The value set for the guild's system channel flags
+ * @type {Readonly}
+ */
+ this.systemChannelFlags = new SystemChannelFlags(data.system_channel_flags).freeze();
+ }
+
+ if ('max_members' in data) {
+ /**
+ * The maximum amount of members the guild can have
+ * @type {?number}
+ */
+ this.maximumMembers = data.max_members;
+ } else {
+ this.maximumMembers ??= null;
+ }
+
+ if ('max_presences' in data) {
+ /**
+ * The maximum amount of presences the guild can have
+ * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter
+ * @type {?number}
+ */
+ this.maximumPresences = data.max_presences ?? 25_000;
+ } else {
+ this.maximumPresences ??= null;
+ }
+
+ if ('approximate_member_count' in data) {
+ /**
+ * The approximate amount of members the guild has
+ * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter
+ * @type {?number}
+ */
+ this.approximateMemberCount = data.approximate_member_count;
+ } else {
+ this.approximateMemberCount ??= null;
+ }
+
+ if ('approximate_presence_count' in data) {
+ /**
+ * The approximate amount of presences the guild has
+ * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter
+ * @type {?number}
+ */
+ this.approximatePresenceCount = data.approximate_presence_count;
+ } else {
+ this.approximatePresenceCount ??= null;
+ }
+
+ /**
+ * The use count of the vanity URL code of the guild, if any
+ * You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it
+ * @type {?number}
+ */
+ this.vanityURLUses ??= null;
+
+ if ('rules_channel_id' in data) {
+ /**
+ * The rules channel's id for the guild
+ * @type {?Snowflake}
+ */
+ this.rulesChannelId = data.rules_channel_id;
+ }
+
+ if ('public_updates_channel_id' in data) {
+ /**
+ * The community updates channel's id for the guild
+ * @type {?Snowflake}
+ */
+ this.publicUpdatesChannelId = data.public_updates_channel_id;
+ }
+
+ if ('preferred_locale' in data) {
+ /**
+ * The preferred locale of the guild, defaults to `en-US`
+ * @type {string}
+ * @see {@link https://discord.com/developers/docs/dispatch/field-values#predefined-field-values-accepted-locales}
+ */
+ this.preferredLocale = data.preferred_locale;
+ }
+
+ if (data.channels) {
+ this.channels.cache.clear();
+ for (const rawChannel of data.channels) {
+ this.client.channels._add(rawChannel, this);
+ }
+ }
+
+ if (data.threads) {
+ for (const rawThread of data.threads) {
+ this.client.channels._add(rawThread, this);
+ }
+ }
+
+ if (data.roles) {
+ this.roles.cache.clear();
+ for (const role of data.roles) this.roles._add(role);
+ }
+
+ if (data.members) {
+ this.members.cache.clear();
+ for (const guildUser of data.members) this.members._add(guildUser);
+ }
+
+ if ('owner_id' in data) {
+ /**
+ * The user id of this guild's owner
+ * @type {Snowflake}
+ */
+ this.ownerId = data.owner_id;
+ }
+
+ if (data.presences) {
+ for (const presence of data.presences) {
+ this.presences._add(Object.assign(presence, { guild: this }));
+ }
+ }
+
+ if (data.stage_instances) {
+ this.stageInstances.cache.clear();
+ for (const stageInstance of data.stage_instances) {
+ this.stageInstances._add(stageInstance);
+ }
+ }
+
+ if (data.guild_scheduled_events) {
+ this.scheduledEvents.cache.clear();
+ for (const scheduledEvent of data.guild_scheduled_events) {
+ this.scheduledEvents._add(scheduledEvent);
+ }
+ }
+
+ if (data.voice_states) {
+ this.voiceStates.cache.clear();
+ for (const voiceState of data.voice_states) {
+ this.voiceStates._add(voiceState);
+ }
+ }
+
+ if (!this.emojis) {
+ /**
+ * A manager of the emojis belonging to this guild
+ * @type {GuildEmojiManager}
+ */
+ this.emojis = new GuildEmojiManager(this);
+ if (data.emojis) for (const emoji of data.emojis) this.emojis._add(emoji);
+ } else if (data.emojis) {
+ this.client.actions.GuildEmojisUpdate.handle({
+ guild_id: this.id,
+ emojis: data.emojis,
+ });
+ }
+
+ if (!this.stickers) {
+ /**
+ * A manager of the stickers belonging to this guild
+ * @type {GuildStickerManager}
+ */
+ this.stickers = new GuildStickerManager(this);
+ if (data.stickers) for (const sticker of data.stickers) this.stickers._add(sticker);
+ } else if (data.stickers) {
+ this.client.actions.GuildStickersUpdate.handle({
+ guild_id: this.id,
+ stickers: data.stickers,
+ });
+ }
+ }
+
+ /**
+ * The time the client user joined the guild
+ * @type {Date}
+ * @readonly
+ */
+ get joinedAt() {
+ return new Date(this.joinedTimestamp);
+ }
+
+ /**
+ * The URL to this guild's discovery splash image.
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
+ * @returns {?string}
+ */
+ discoverySplashURL({ format, size } = {}) {
+ return this.discoverySplash && this.client.rest.cdn.DiscoverySplash(this.id, this.discoverySplash, format, size);
+ }
+
+ /**
+ * Fetches the owner of the guild.
+ * If the member object isn't needed, use {@link Guild#ownerId} instead.
+ * @param {BaseFetchOptions} [options] The options for fetching the member
+ * @returns {Promise}
+ */
+ fetchOwner(options) {
+ return this.members.fetch({ ...options, user: this.ownerId });
+ }
+
+ /**
+ * AFK voice channel for this guild
+ * @type {?VoiceChannel}
+ * @readonly
+ */
+ get afkChannel() {
+ return this.client.channels.resolve(this.afkChannelId);
+ }
+
+ /**
+ * System channel for this guild
+ * @type {?TextChannel}
+ * @readonly
+ */
+ get systemChannel() {
+ return this.client.channels.resolve(this.systemChannelId);
+ }
+
+ /**
+ * Widget channel for this guild
+ * @type {?TextChannel}
+ * @readonly
+ */
+ get widgetChannel() {
+ return this.client.channels.resolve(this.widgetChannelId);
+ }
+
+ /**
+ * Rules channel for this guild
+ * @type {?TextChannel}
+ * @readonly
+ */
+ get rulesChannel() {
+ return this.client.channels.resolve(this.rulesChannelId);
+ }
+
+ /**
+ * Public updates channel for this guild
+ * @type {?TextChannel}
+ * @readonly
+ */
+ get publicUpdatesChannel() {
+ return this.client.channels.resolve(this.publicUpdatesChannelId);
+ }
+
+ /**
+ * The client user as a GuildMember of this guild
+ * @type {?GuildMember}
+ * @readonly
+ */
+ get me() {
+ return (
+ this.members.resolve(this.client.user.id) ??
+ (this.client.options.partials.includes(PartialTypes.GUILD_MEMBER)
+ ? this.members._add({ user: { id: this.client.user.id } }, true)
+ : null)
+ );
+ }
+
+ /**
+ * The maximum bitrate available for this guild
+ * @type {number}
+ * @readonly
+ */
+ get maximumBitrate() {
+ if (this.features.includes('VIP_REGIONS')) {
+ return 384_000;
+ }
+
+ switch (PremiumTiers[this.premiumTier]) {
+ case PremiumTiers.TIER_1:
+ return 128_000;
+ case PremiumTiers.TIER_2:
+ return 256_000;
+ case PremiumTiers.TIER_3:
+ return 384_000;
+ default:
+ return 96_000;
+ }
+ }
+
+ /**
+ * Fetches a collection of integrations to this guild.
+ * Resolves with a collection mapping integrations by their ids.
+ * @returns {Promise>}
+ * @example
+ * // Fetch integrations
+ * guild.fetchIntegrations()
+ * .then(integrations => console.log(`Fetched ${integrations.size} integrations`))
+ * .catch(console.error);
+ */
+ async fetchIntegrations() {
+ const data = await this.client.api.guilds(this.id).integrations.get();
+ return data.reduce(
+ (collection, integration) => collection.set(integration.id, new Integration(this.client, integration, this)),
+ new Collection(),
+ );
+ }
+
+ /**
+ * Fetches a collection of templates from this guild.
+ * Resolves with a collection mapping templates by their codes.
+ * @returns {Promise>}
+ */
+ async fetchTemplates() {
+ const templates = await this.client.api.guilds(this.id).templates.get();
+ return templates.reduce((col, data) => col.set(data.code, new GuildTemplate(this.client, data)), new Collection());
+ }
+
+ /**
+ * Fetches the welcome screen for this guild.
+ * @returns {Promise}
+ */
+ async fetchWelcomeScreen() {
+ const data = await this.client.api.guilds(this.id, 'welcome-screen').get();
+ return new WelcomeScreen(this, data);
+ }
+
+ /**
+ * Creates a template for the guild.
+ * @param {string} name The name for the template
+ * @param {string} [description] The description for the template
+ * @returns {Promise}
+ */
+ async createTemplate(name, description) {
+ const data = await this.client.api.guilds(this.id).templates.post({ data: { name, description } });
+ return new GuildTemplate(this.client, data);
+ }
+
+ /**
+ * Obtains a guild preview for this guild from Discord.
+ * @returns {Promise}
+ */
+ async fetchPreview() {
+ const data = await this.client.api.guilds(this.id).preview.get();
+ return new GuildPreview(this.client, data);
+ }
+
+ /**
+ * An object containing information about a guild's vanity invite.
+ * @typedef {Object} Vanity
+ * @property {?string} code Vanity invite code
+ * @property {number} uses How many times this invite has been used
+ */
+
+ /**
+ * Fetches the vanity URL invite object to this guild.
+ * Resolves with an object containing the vanity URL invite code and the use count
+ * @returns {Promise}
+ * @example
+ * // Fetch invite data
+ * guild.fetchVanityData()
+ * .then(res => {
+ * console.log(`Vanity URL: https://discord.gg/${res.code} with ${res.uses} uses`);
+ * })
+ * .catch(console.error);
+ */
+ async fetchVanityData() {
+ if (!this.features.includes('VANITY_URL')) {
+ throw new Error('VANITY_URL');
+ }
+ const data = await this.client.api.guilds(this.id, 'vanity-url').get();
+ this.vanityURLCode = data.code;
+ this.vanityURLUses = data.uses;
+
+ return data;
+ }
+
+ /**
+ * Fetches all webhooks for the guild.
+ * @returns {Promise>}
+ * @example
+ * // Fetch webhooks
+ * guild.fetchWebhooks()
+ * .then(webhooks => console.log(`Fetched ${webhooks.size} webhooks`))
+ * .catch(console.error);
+ */
+ async fetchWebhooks() {
+ const apiHooks = await this.client.api.guilds(this.id).webhooks.get();
+ const hooks = new Collection();
+ for (const hook of apiHooks) hooks.set(hook.id, new Webhook(this.client, hook));
+ return hooks;
+ }
+
+ /**
+ * Fetches the guild widget data, requires the widget to be enabled.
+ * @returns {Promise}
+ * @example
+ * // Fetches the guild widget data
+ * guild.fetchWidget()
+ * .then(widget => console.log(`The widget shows ${widget.channels.size} channels`))
+ * .catch(console.error);
+ */
+ fetchWidget() {
+ return this.client.fetchGuildWidget(this.id);
+ }
+
+ /**
+ * Data for the Guild Widget Settings object
+ * @typedef {Object} GuildWidgetSettings
+ * @property {boolean} enabled Whether the widget is enabled
+ * @property {?GuildChannel} channel The widget invite channel
+ */
+
+ /**
+ * The Guild Widget Settings object
+ * @typedef {Object} GuildWidgetSettingsData
+ * @property {boolean} enabled Whether the widget is enabled
+ * @property {?GuildChannelResolvable} channel The widget invite channel
+ */
+
+ /**
+ * Fetches the guild widget settings.
+ * @returns {Promise}
+ * @example
+ * // Fetches the guild widget settings
+ * guild.fetchWidgetSettings()
+ * .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`))
+ * .catch(console.error);
+ */
+ async fetchWidgetSettings() {
+ const data = await this.client.api.guilds(this.id).widget.get();
+ this.widgetEnabled = data.enabled;
+ this.widgetChannelId = data.channel_id;
+ return {
+ enabled: data.enabled,
+ channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null,
+ };
+ }
+
+ /**
+ * Options used to fetch audit logs.
+ * @typedef {Object} GuildAuditLogsFetchOptions
+ * @property {Snowflake|GuildAuditLogsEntry} [before] Only return entries before this entry
+ * @property {number} [limit] The number of entries to return
+ * @property {UserResolvable} [user] Only return entries for actions made by this user
+ * @property {AuditLogAction|number} [type] Only return entries for this action type
+ */
+
+ /**
+ * Fetches audit logs for this guild.
+ * @param {GuildAuditLogsFetchOptions} [options={}] Options for fetching audit logs
+ * @returns {Promise}
+ * @example
+ * // Output audit log entries
+ * guild.fetchAuditLogs()
+ * .then(audit => console.log(audit.entries.first()))
+ * .catch(console.error);
+ */
+ async fetchAuditLogs(options = {}) {
+ if (options.before && options.before instanceof GuildAuditLogs.Entry) options.before = options.before.id;
+ if (typeof options.type === 'string') options.type = GuildAuditLogs.Actions[options.type];
+
+ const data = await this.client.api.guilds(this.id)['audit-logs'].get({
+ query: {
+ before: options.before,
+ limit: options.limit,
+ user_id: this.client.users.resolveId(options.user),
+ action_type: options.type,
+ },
+ });
+ return GuildAuditLogs.build(this, data);
+ }
+
+ /**
+ * The data for editing a guild.
+ * @typedef {Object} GuildEditData
+ * @property {string} [name] The name of the guild
+ * @property {VerificationLevel|number} [verificationLevel] The verification level of the guild
+ * @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter
+ * @property {VoiceChannelResolvable} [afkChannel] The AFK channel of the guild
+ * @property {TextChannelResolvable} [systemChannel] The system channel of the guild
+ * @property {number} [afkTimeout] The AFK timeout of the guild
+ * @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the guild
+ * @property {GuildMemberResolvable} [owner] The owner of the guild
+ * @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild
+ * @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild
+ * @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild
+ * @property {DefaultMessageNotificationLevel|number} [defaultMessageNotifications] The default message notification
+ * level of the guild
+ * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild
+ * @property {TextChannelResolvable} [rulesChannel] The rules channel of the guild
+ * @property {TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild
+ * @property {string} [preferredLocale] The preferred locale of the guild
+ * @property {boolean} [premiumProgressBarEnabled] Whether the guild's premium progress bar is enabled
+ * @property {string} [description] The discovery description of the guild
+ * @property {Features[]} [features] The features of the guild
+ */
+
+ /**
+ * Data that can be resolved to a Text Channel object. This can be:
+ * * A TextChannel
+ * * A Snowflake
+ * @typedef {TextChannel|Snowflake} TextChannelResolvable
+ */
+
+ /**
+ * Data that can be resolved to a Voice Channel object. This can be:
+ * * A VoiceChannel
+ * * A Snowflake
+ * @typedef {VoiceChannel|Snowflake} VoiceChannelResolvable
+ */
+
+ /**
+ * Updates the guild with new information - e.g. a new name.
+ * @param {GuildEditData} data The data to update the guild with
+ * @param {string} [reason] Reason for editing this guild
+ * @returns {Promise}
+ * @example
+ * // Set the guild name
+ * guild.edit({
+ * name: 'Discord Guild',
+ * })
+ * .then(updated => console.log(`New guild name ${updated}`))
+ * .catch(console.error);
+ */
+ async edit(data, reason) {
+ const _data = {};
+ if (data.name) _data.name = data.name;
+ if (typeof data.verificationLevel !== 'undefined') {
+ _data.verification_level =
+ typeof data.verificationLevel === 'number'
+ ? data.verificationLevel
+ : VerificationLevels[data.verificationLevel];
+ }
+ if (typeof data.afkChannel !== 'undefined') {
+ _data.afk_channel_id = this.client.channels.resolveId(data.afkChannel);
+ }
+ if (typeof data.systemChannel !== 'undefined') {
+ _data.system_channel_id = this.client.channels.resolveId(data.systemChannel);
+ }
+ if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout);
+ if (typeof data.icon !== 'undefined') _data.icon = await DataResolver.resolveImage(data.icon);
+ if (data.owner) _data.owner_id = this.client.users.resolveId(data.owner);
+ if (typeof data.splash !== 'undefined') _data.splash = await DataResolver.resolveImage(data.splash);
+ if (typeof data.discoverySplash !== 'undefined') {
+ _data.discovery_splash = await DataResolver.resolveImage(data.discoverySplash);
+ }
+ if (typeof data.banner !== 'undefined') _data.banner = await DataResolver.resolveImage(data.banner);
+ if (typeof data.explicitContentFilter !== 'undefined') {
+ _data.explicit_content_filter =
+ typeof data.explicitContentFilter === 'number'
+ ? data.explicitContentFilter
+ : ExplicitContentFilterLevels[data.explicitContentFilter];
+ }
+ if (typeof data.defaultMessageNotifications !== 'undefined') {
+ _data.default_message_notifications =
+ typeof data.defaultMessageNotifications === 'number'
+ ? data.defaultMessageNotifications
+ : DefaultMessageNotificationLevels[data.defaultMessageNotifications];
+ }
+ if (typeof data.systemChannelFlags !== 'undefined') {
+ _data.system_channel_flags = SystemChannelFlags.resolve(data.systemChannelFlags);
+ }
+ if (typeof data.rulesChannel !== 'undefined') {
+ _data.rules_channel_id = this.client.channels.resolveId(data.rulesChannel);
+ }
+ if (typeof data.publicUpdatesChannel !== 'undefined') {
+ _data.public_updates_channel_id = this.client.channels.resolveId(data.publicUpdatesChannel);
+ }
+ if (typeof data.features !== 'undefined') {
+ _data.features = data.features;
+ }
+ if (typeof data.description !== 'undefined') {
+ _data.description = data.description;
+ }
+ if (data.preferredLocale) _data.preferred_locale = data.preferredLocale;
+ if ('premiumProgressBarEnabled' in data) _data.premium_progress_bar_enabled = data.premiumProgressBarEnabled;
+ const newData = await this.client.api.guilds(this.id).patch({ data: _data, reason });
+ return this.client.actions.GuildUpdate.handle(newData).updated;
+ }
+
+ /**
+ * Welcome channel data
+ * @typedef {Object} WelcomeChannelData
+ * @property {string} description The description to show for this welcome channel
+ * @property {TextChannel|NewsChannel|StoreChannel|Snowflake} channel The channel to link for this welcome channel
+ * @property {EmojiIdentifierResolvable} [emoji] The emoji to display for this welcome channel
+ */
+
+ /**
+ * Welcome screen edit data
+ * @typedef {Object} WelcomeScreenEditData
+ * @property {boolean} [enabled] Whether the welcome screen is enabled
+ * @property {string} [description] The description for the welcome screen
+ * @property {WelcomeChannelData[]} [welcomeChannels] The welcome channel data for the welcome screen
+ */
+
+ /**
+ * Data that can be resolved to a GuildTextChannel object. This can be:
+ * * A TextChannel
+ * * A NewsChannel
+ * * A Snowflake
+ * @typedef {TextChannel|NewsChannel|Snowflake} GuildTextChannelResolvable
+ */
+
+ /**
+ * Data that can be resolved to a GuildVoiceChannel object. This can be:
+ * * A VoiceChannel
+ * * A StageChannel
+ * * A Snowflake
+ * @typedef {VoiceChannel|StageChannel|Snowflake} GuildVoiceChannelResolvable
+ */
+
+ /**
+ * Updates the guild's welcome screen
+ * @param {WelcomeScreenEditData} data Data to edit the welcome screen with
+ * @returns {Promise}
+ * @example
+ * guild.editWelcomeScreen({
+ * description: 'Hello World',
+ * enabled: true,
+ * welcomeChannels: [
+ * {
+ * description: 'foobar',
+ * channel: '222197033908436994',
+ * }
+ * ],
+ * })
+ */
+ async editWelcomeScreen(data) {
+ const { enabled, description, welcomeChannels } = data;
+ const welcome_channels = welcomeChannels?.map(welcomeChannelData => {
+ const emoji = this.emojis.resolve(welcomeChannelData.emoji);
+ return {
+ emoji_id: emoji?.id,
+ emoji_name: emoji?.name ?? welcomeChannelData.emoji,
+ channel_id: this.channels.resolveId(welcomeChannelData.channel),
+ description: welcomeChannelData.description,
+ };
+ });
+
+ const patchData = await this.client.api.guilds(this.id, 'welcome-screen').patch({
+ data: {
+ welcome_channels,
+ description,
+ enabled,
+ },
+ });
+ return new WelcomeScreen(this, patchData);
+ }
+
+ /**
+ * Edits the level of the explicit content filter.
+ * @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter
+ * @param {string} [reason] Reason for changing the level of the guild's explicit content filter
+ * @returns {Promise}
+ */
+ setExplicitContentFilter(explicitContentFilter, reason) {
+ return this.edit({ explicitContentFilter }, reason);
+ }
+
+ /* eslint-disable max-len */
+ /**
+ * Edits the setting of the default message notifications of the guild.
+ * @param {DefaultMessageNotificationLevel|number} defaultMessageNotifications The new default message notification level of the guild
+ * @param {string} [reason] Reason for changing the setting of the default message notifications
+ * @returns {Promise}
+ */
+ setDefaultMessageNotifications(defaultMessageNotifications, reason) {
+ return this.edit({ defaultMessageNotifications }, reason);
+ }
+ /* eslint-enable max-len */
+
+ /**
+ * Edits the flags of the default message notifications of the guild.
+ * @param {SystemChannelFlagsResolvable} systemChannelFlags The new flags for the default message notifications
+ * @param {string} [reason] Reason for changing the flags of the default message notifications
+ * @returns {Promise}
+ */
+ setSystemChannelFlags(systemChannelFlags, reason) {
+ return this.edit({ systemChannelFlags }, reason);
+ }
+
+ /**
+ * Edits the name of the guild.
+ * @param {string} name The new name of the guild
+ * @param {string} [reason] Reason for changing the guild's name
+ * @returns {Promise}
+ * @example
+ * // Edit the guild name
+ * guild.setName('Discord Guild')
+ * .then(updated => console.log(`Updated guild name to ${updated.name}`))
+ * .catch(console.error);
+ */
+ setName(name, reason) {
+ return this.edit({ name }, reason);
+ }
+
+ /**
+ * Edits the verification level of the guild.
+ * @param {VerificationLevel|number} verificationLevel The new verification level of the guild
+ * @param {string} [reason] Reason for changing the guild's verification level
+ * @returns {Promise}
+ * @example
+ * // Edit the guild verification level
+ * guild.setVerificationLevel(1)
+ * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`))
+ * .catch(console.error);
+ */
+ setVerificationLevel(verificationLevel, reason) {
+ return this.edit({ verificationLevel }, reason);
+ }
+
+ /**
+ * Edits the AFK channel of the guild.
+ * @param {VoiceChannelResolvable} afkChannel The new AFK channel
+ * @param {string} [reason] Reason for changing the guild's AFK channel
+ * @returns {Promise}
+ * @example
+ * // Edit the guild AFK channel
+ * guild.setAFKChannel(channel)
+ * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel.name}`))
+ * .catch(console.error);
+ */
+ setAFKChannel(afkChannel, reason) {
+ return this.edit({ afkChannel }, reason);
+ }
+
+ /**
+ * Edits the system channel of the guild.
+ * @param {TextChannelResolvable} systemChannel The new system channel
+ * @param {string} [reason] Reason for changing the guild's system channel
+ * @returns {Promise}
+ * @example
+ * // Edit the guild system channel
+ * guild.setSystemChannel(channel)
+ * .then(updated => console.log(`Updated guild system channel to ${guild.systemChannel.name}`))
+ * .catch(console.error);
+ */
+ setSystemChannel(systemChannel, reason) {
+ return this.edit({ systemChannel }, reason);
+ }
+
+ /**
+ * Edits the AFK timeout of the guild.
+ * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK
+ * @param {string} [reason] Reason for changing the guild's AFK timeout
+ * @returns {Promise}
+ * @example
+ * // Edit the guild AFK channel
+ * guild.setAFKTimeout(60)
+ * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`))
+ * .catch(console.error);
+ */
+ setAFKTimeout(afkTimeout, reason) {
+ return this.edit({ afkTimeout }, reason);
+ }
+
+ /**
+ * Sets a new guild icon.
+ * @param {?(Base64Resolvable|BufferResolvable)} icon The new icon of the guild
+ * @param {string} [reason] Reason for changing the guild's icon
+ * @returns {Promise}
+ * @example
+ * // Edit the guild icon
+ * guild.setIcon('./icon.png')
+ * .then(updated => console.log('Updated the guild icon'))
+ * .catch(console.error);
+ */
+ setIcon(icon, reason) {
+ return this.edit({ icon }, reason);
+ }
+
+ /**
+ * Sets a new owner of the guild.
+ * @param {GuildMemberResolvable} owner The new owner of the guild
+ * @param {string} [reason] Reason for setting the new owner
+ * @returns {Promise}
+ * @example
+ * // Edit the guild owner
+ * guild.setOwner(guild.members.cache.first())
+ * .then(guild => guild.fetchOwner())
+ * .then(owner => console.log(`Updated the guild owner to ${owner.displayName}`))
+ * .catch(console.error);
+ */
+ setOwner(owner, reason) {
+ return this.edit({ owner }, reason);
+ }
+
+ /**
+ * Sets a new guild invite splash image.
+ * @param {?(Base64Resolvable|BufferResolvable)} splash The new invite splash image of the guild
+ * @param {string} [reason] Reason for changing the guild's invite splash image
+ * @returns {Promise}
+ * @example
+ * // Edit the guild splash
+ * guild.setSplash('./splash.png')
+ * .then(updated => console.log('Updated the guild splash'))
+ * .catch(console.error);
+ */
+ setSplash(splash, reason) {
+ return this.edit({ splash }, reason);
+ }
+
+ /**
+ * Sets a new guild discovery splash image.
+ * @param {?(Base64Resolvable|BufferResolvable)} discoverySplash The new discovery splash image of the guild
+ * @param {string} [reason] Reason for changing the guild's discovery splash image
+ * @returns {Promise}
+ * @example
+ * // Edit the guild discovery splash
+ * guild.setDiscoverySplash('./discoverysplash.png')
+ * .then(updated => console.log('Updated the guild discovery splash'))
+ * .catch(console.error);
+ */
+ setDiscoverySplash(discoverySplash, reason) {
+ return this.edit({ discoverySplash }, reason);
+ }
+
+ /**
+ * Sets a new guild banner.
+ * @param {?(Base64Resolvable|BufferResolvable)} banner The new banner of the guild
+ * @param {string} [reason] Reason for changing the guild's banner
+ * @returns {Promise}
+ * @example
+ * guild.setBanner('./banner.png')
+ * .then(updated => console.log('Updated the guild banner'))
+ * .catch(console.error);
+ */
+ setBanner(banner, reason) {
+ return this.edit({ banner }, reason);
+ }
+
+ /**
+ * Edits the rules channel of the guild.
+ * @param {TextChannelResolvable} rulesChannel The new rules channel
+ * @param {string} [reason] Reason for changing the guild's rules channel
+ * @returns {Promise}
+ * @example
+ * // Edit the guild rules channel
+ * guild.setRulesChannel(channel)
+ * .then(updated => console.log(`Updated guild rules channel to ${guild.rulesChannel.name}`))
+ * .catch(console.error);
+ */
+ setRulesChannel(rulesChannel, reason) {
+ return this.edit({ rulesChannel }, reason);
+ }
/**
* Change Guild Position (from * to Folder or Home)
* @param {number} position Guild Position
@@ -1216,191 +1233,257 @@ class Guild extends AnonymousGuild {
return this;
}
- /**
- * Edits the community updates channel of the guild.
- * @param {TextChannelResolvable} publicUpdatesChannel The new community updates channel
- * @param {string} [reason] Reason for changing the guild's community updates channel
- * @returns {Promise}
- * @example
- * // Edit the guild community updates channel
- * guild.setPublicUpdatesChannel(channel)
- * .then(updated => console.log(`Updated guild community updates channel to ${guild.publicUpdatesChannel.name}`))
- * .catch(console.error);
- */
- setPublicUpdatesChannel(publicUpdatesChannel, reason) {
- return this.edit({ publicUpdatesChannel }, reason);
- }
+ /**
+ * Edits the community updates channel of the guild.
+ * @param {TextChannelResolvable} publicUpdatesChannel The new community updates channel
+ * @param {string} [reason] Reason for changing the guild's community updates channel
+ * @returns {Promise}
+ * @example
+ * // Edit the guild community updates channel
+ * guild.setPublicUpdatesChannel(channel)
+ * .then(updated => console.log(`Updated guild community updates channel to ${guild.publicUpdatesChannel.name}`))
+ * .catch(console.error);
+ */
+ setPublicUpdatesChannel(publicUpdatesChannel, reason) {
+ return this.edit({ publicUpdatesChannel }, reason);
+ }
- /**
- * Edits the preferred locale of the guild.
- * @param {string} preferredLocale The new preferred locale of the guild
- * @param {string} [reason] Reason for changing the guild's preferred locale
- * @returns {Promise}
- * @example
- * // Edit the guild preferred locale
- * guild.setPreferredLocale('en-US')
- * .then(updated => console.log(`Updated guild preferred locale to ${guild.preferredLocale}`))
- * .catch(console.error);
- */
- setPreferredLocale(preferredLocale, reason) {
- return this.edit({ preferredLocale }, reason);
- }
+ /**
+ * Edits the preferred locale of the guild.
+ * @param {string} preferredLocale The new preferred locale of the guild
+ * @param {string} [reason] Reason for changing the guild's preferred locale
+ * @returns {Promise}
+ * @example
+ * // Edit the guild preferred locale
+ * guild.setPreferredLocale('en-US')
+ * .then(updated => console.log(`Updated guild preferred locale to ${guild.preferredLocale}`))
+ * .catch(console.error);
+ */
+ setPreferredLocale(preferredLocale, reason) {
+ return this.edit({ preferredLocale }, reason);
+ }
- /**
- * Edits the enabled state of the guild's premium progress bar
- * @param {boolean} [enabled=true] The new enabled state of the guild's premium progress bar
- * @param {string} [reason] Reason for changing the state of the guild's premium progress bar
- * @returns {Promise}
- */
- setPremiumProgressBarEnabled(enabled = true, reason) {
- return this.edit({ premiumProgressBarEnabled: enabled }, reason);
- }
+ /**
+ * Edits the enabled state of the guild's premium progress bar
+ * @param {boolean} [enabled=true] The new enabled state of the guild's premium progress bar
+ * @param {string} [reason] Reason for changing the state of the guild's premium progress bar
+ * @returns {Promise}
+ */
+ setPremiumProgressBarEnabled(enabled = true, reason) {
+ return this.edit({ premiumProgressBarEnabled: enabled }, reason);
+ }
- /**
- * Edits the guild's widget settings.
- * @param {GuildWidgetSettingsData} settings The widget settings for the guild
- * @param {string} [reason] Reason for changing the guild's widget settings
- * @returns {Promise}
- */
- async setWidgetSettings(settings, reason) {
- await this.client.api.guilds(this.id).widget.patch({
- body: {
- enabled: settings.enabled,
- channel_id: this.channels.resolveId(settings.channel),
- },
- reason,
- });
- return this;
- }
+ /**
+ * Data that can be resolved to give a Category Channel object. This can be:
+ * * A CategoryChannel object
+ * * A Snowflake
+ * @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable
+ */
- /**
- * Leaves the guild.
- * @returns {Promise}
- * @example
- * // Leave a guild
- * guild.leave()
- * .then(g => console.log(`Left the guild ${g}`))
- * .catch(console.error);
- */
- async leave() {
- if (this.ownerId === this.client.user.id) throw new Error('GUILD_OWNED');
- await this.client.api.users('@me').guilds(this.id).delete();
- return this;
- }
+ /**
+ * 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
+ */
- /**
- * Deletes the guild.
- * @returns {Promise}
- * @example
- * // Delete a guild
- * guild.delete()
- * .then(g => console.log(`Deleted the guild ${g}`))
- * .catch(console.error);
- */
- async delete() {
- await this.client.api.guilds(this.id).delete();
- return this;
- }
+ /**
+ * Batch-updates the guild's channels' positions.
+ * Only one channel's parent can be changed at a time
+ * @param {ChannelPosition[]} channelPositions Channel positions to update
+ * @returns {Promise}
+ * @deprecated Use {@link GuildChannelManager#setPositions} instead
+ * @example
+ * guild.setChannelPositions([{ channel: channelId, position: newChannelIndex }])
+ * .then(guild => console.log(`Updated channel positions for ${guild}`))
+ * .catch(console.error);
+ */
+ setChannelPositions(channelPositions) {
+ if (!deprecationEmittedForSetChannelPositions) {
+ process.emitWarning(
+ 'The Guild#setChannelPositions method is deprecated. Use GuildChannelManager#setPositions instead.',
+ 'DeprecationWarning',
+ );
- /**
- * Whether this guild equals another guild. It compares all properties, so for most operations
- * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often
- * what most users need.
- * @param {Guild} guild The guild to compare with
- * @returns {boolean}
- */
- equals(guild) {
- return (
- guild &&
- guild instanceof this.constructor &&
- this.id === guild.id &&
- this.available === guild.available &&
- this.splash === guild.splash &&
- this.discoverySplash === guild.discoverySplash &&
- this.name === guild.name &&
- this.memberCount === guild.memberCount &&
- this.large === guild.large &&
- this.icon === guild.icon &&
- this.ownerId === guild.ownerId &&
- this.verificationLevel === guild.verificationLevel &&
- (this.features === guild.features ||
- (this.features.length === guild.features.length &&
- this.features.every((feat, i) => feat === guild.features[i])))
- );
- }
+ deprecationEmittedForSetChannelPositions = true;
+ }
- toJSON() {
- const json = super.toJSON({
- available: false,
- createdTimestamp: true,
- nameAcronym: true,
- presences: false,
- voiceStates: false,
- });
- json.iconURL = this.iconURL();
- json.splashURL = this.splashURL();
- json.discoverySplashURL = this.discoverySplashURL();
- json.bannerURL = this.bannerURL();
- return json;
- }
+ return this.channels.setPositions(channelPositions);
+ }
- /**
- * The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice
- * and stage channels.
- * @type {Function}
- * @readonly
- */
- get voiceAdapterCreator() {
- return (methods) => {
- this.client.voice.adapters.set(this.id, methods);
- return {
- sendPayload: (data) => {
- if (this.shard.status !== Status.Ready) return false;
- this.shard.send(data);
- return true;
- },
- destroy: () => {
- this.client.voice.adapters.delete(this.id);
- },
- };
- };
- }
+ /**
+ * 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
+ */
- /**
- * Creates a collection of this guild's roles, sorted by their position and ids.
- * @returns {Collection}
- * @private
- */
- _sortedRoles() {
- return Util.discordSort(this.roles.cache);
- }
+ /**
+ * Batch-updates the guild's role positions
+ * @param {GuildRolePosition[]} rolePositions Role positions to update
+ * @returns {Promise}
+ * @deprecated Use {@link RoleManager#setPositions} instead
+ * @example
+ * guild.setRolePositions([{ role: roleId, position: updatedRoleIndex }])
+ * .then(guild => console.log(`Role positions updated for ${guild}`))
+ * .catch(console.error);
+ */
+ setRolePositions(rolePositions) {
+ if (!deprecationEmittedForSetRolePositions) {
+ process.emitWarning(
+ 'The Guild#setRolePositions method is deprecated. Use RoleManager#setPositions instead.',
+ 'DeprecationWarning',
+ );
- /**
- * Creates a collection of this guild's or a specific category's channels, sorted by their position and ids.
- * @param {GuildChannel} [channel] Category to get the channels of
- * @returns {Collection}
- * @private
- */
- _sortedChannels(channel) {
- const category = channel.type === ChannelType.GuildCategory;
- const channelTypes = [
- ChannelType.GuildText,
- ChannelType.GuildNews,
- ChannelType.GuildStore,
- ];
- return Util.discordSort(
- this.channels.cache.filter(
- (c) =>
- (channelTypes.includes(channel.type)
- ? channelTypes.includes(c.type)
- : c.type === channel.type) &&
- (category || c.parent === channel.parent),
- ),
- );
- }
+ deprecationEmittedForSetRolePositions = true;
+ }
+
+ return this.roles.setPositions(rolePositions);
+ }
+
+ /**
+ * Edits the guild's widget settings.
+ * @param {GuildWidgetSettingsData} settings The widget settings for the guild
+ * @param {string} [reason] Reason for changing the guild's widget settings
+ * @returns {Promise}
+ */
+ async setWidgetSettings(settings, reason) {
+ await this.client.api.guilds(this.id).widget.patch({
+ data: {
+ enabled: settings.enabled,
+ channel_id: this.channels.resolveId(settings.channel),
+ },
+ reason,
+ });
+ return this;
+ }
+
+ /**
+ * Leaves the guild.
+ * @returns {Promise}
+ * @example
+ * // Leave a guild
+ * guild.leave()
+ * .then(g => console.log(`Left the guild ${g}`))
+ * .catch(console.error);
+ */
+ async leave() {
+ if (this.ownerId === this.client.user.id) throw new Error('GUILD_OWNED');
+ await this.client.api.users('@me').guilds(this.id).delete();
+ return this.client.actions.GuildDelete.handle({ id: this.id }).guild;
+ }
+
+ /**
+ * Deletes the guild.
+ * @returns {Promise}
+ * @example
+ * // Delete a guild
+ * guild.delete()
+ * .then(g => console.log(`Deleted the guild ${g}`))
+ * .catch(console.error);
+ */
+ async delete() {
+ await this.client.api.guilds(this.id).delete();
+ return this.client.actions.GuildDelete.handle({ id: this.id }).guild;
+ }
+
+ /**
+ * Whether this guild equals another guild. It compares all properties, so for most operations
+ * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often
+ * what most users need.
+ * @param {Guild} guild The guild to compare with
+ * @returns {boolean}
+ */
+ equals(guild) {
+ return (
+ guild &&
+ guild instanceof this.constructor &&
+ this.id === guild.id &&
+ this.available === guild.available &&
+ this.splash === guild.splash &&
+ this.discoverySplash === guild.discoverySplash &&
+ this.name === guild.name &&
+ this.memberCount === guild.memberCount &&
+ this.large === guild.large &&
+ this.icon === guild.icon &&
+ this.ownerId === guild.ownerId &&
+ this.verificationLevel === guild.verificationLevel &&
+ (this.features === guild.features ||
+ (this.features.length === guild.features.length &&
+ this.features.every((feat, i) => feat === guild.features[i])))
+ );
+ }
+
+ toJSON() {
+ const json = super.toJSON({
+ available: false,
+ createdTimestamp: true,
+ nameAcronym: true,
+ presences: false,
+ voiceStates: false,
+ });
+ json.iconURL = this.iconURL();
+ json.splashURL = this.splashURL();
+ json.discoverySplashURL = this.discoverySplashURL();
+ json.bannerURL = this.bannerURL();
+ return json;
+ }
+
+ /**
+ * The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice
+ * and stage channels.
+ * @type {Function}
+ * @readonly
+ */
+ get voiceAdapterCreator() {
+ return methods => {
+ this.client.voice.adapters.set(this.id, methods);
+ return {
+ sendPayload: data => {
+ if (this.shard.status !== Status.READY) return false;
+ this.shard.send(data);
+ return true;
+ },
+ destroy: () => {
+ this.client.voice.adapters.delete(this.id);
+ },
+ };
+ };
+ }
+
+ /**
+ * Creates a collection of this guild's roles, sorted by their position and ids.
+ * @returns {Collection}
+ * @private
+ */
+ _sortedRoles() {
+ return Util.discordSort(this.roles.cache);
+ }
+
+ /**
+ * Creates a collection of this guild's or a specific category's channels, sorted by their position and ids.
+ * @param {GuildChannel} [channel] Category to get the channels of
+ * @returns {Collection}
+ * @private
+ */
+ _sortedChannels(channel) {
+ const category = channel.type === ChannelTypes.GUILD_CATEGORY;
+ return Util.discordSort(
+ this.channels.cache.filter(
+ c =>
+ (['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_STORE'].includes(channel.type)
+ ? ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_STORE'].includes(c.type)
+ : c.type === channel.type) &&
+ (category || c.parent === channel.parent),
+ ),
+ );
+ }
}
exports.Guild = Guild;
+exports.deletedGuilds = deletedGuilds;
/**
* @external APIGuild
diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js
index 199e9d1..3937184 100644
--- a/src/structures/GuildAuditLogs.js
+++ b/src/structures/GuildAuditLogs.js
@@ -1,32 +1,31 @@
'use strict';
const { Collection } = require('@discordjs/collection');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { OverwriteType, AuditLogEvent } = require('discord-api-types/v9');
const { GuildScheduledEvent } = require('./GuildScheduledEvent');
const Integration = require('./Integration');
const Invite = require('./Invite');
const { StageInstance } = require('./StageInstance');
const { Sticker } = require('./Sticker');
const Webhook = require('./Webhook');
-const Partials = require('../util/Partials');
+const { OverwriteTypes, PartialTypes } = require('../util/Constants');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
const Util = require('../util/Util');
/**
* The target type of an entry. Here are the available types:
- * * Guild
- * * Channel
- * * User
- * * Role
- * * Invite
- * * Webhook
- * * Emoji
- * * Message
- * * Integration
- * * StageInstance
- * * Sticker
- * * Thread
- * * GuildScheduledEvent
+ * * GUILD
+ * * CHANNEL
+ * * USER
+ * * ROLE
+ * * INVITE
+ * * WEBHOOK
+ * * EMOJI
+ * * MESSAGE
+ * * INTEGRATION
+ * * STAGE_INSTANCE
+ * * STICKER
+ * * THREAD
+ * * GUILD_SCHEDULED_EVENT
* @typedef {string} AuditLogTargetType
*/
@@ -36,21 +35,131 @@ const Util = require('../util/Util');
* @type {Object}
*/
const Targets = {
- All: 'All',
- Guild: 'Guild',
- GuildScheduledEvent: 'GuildScheduledEvent',
- Channel: 'Channel',
- User: 'User',
- Role: 'Role',
- Invite: 'Invite',
- Webhook: 'Webhook',
- Emoji: 'Emoji',
- Message: 'Message',
- Integration: 'Integration',
- StageInstance: 'StageInstance',
- Sticker: 'Sticker',
- Thread: 'Thread',
- Unknown: 'Unknown',
+ ALL: 'ALL',
+ GUILD: 'GUILD',
+ GUILD_SCHEDULED_EVENT: 'GUILD_SCHEDULED_EVENT',
+ CHANNEL: 'CHANNEL',
+ USER: 'USER',
+ ROLE: 'ROLE',
+ INVITE: 'INVITE',
+ WEBHOOK: 'WEBHOOK',
+ EMOJI: 'EMOJI',
+ MESSAGE: 'MESSAGE',
+ INTEGRATION: 'INTEGRATION',
+ STAGE_INSTANCE: 'STAGE_INSTANCE',
+ STICKER: 'STICKER',
+ THREAD: 'THREAD',
+ UNKNOWN: 'UNKNOWN',
+};
+
+/**
+ * The action of an entry. Here are the available actions:
+ * * ALL: null
+ * * GUILD_UPDATE: 1
+ * * CHANNEL_CREATE: 10
+ * * CHANNEL_UPDATE: 11
+ * * CHANNEL_DELETE: 12
+ * * CHANNEL_OVERWRITE_CREATE: 13
+ * * CHANNEL_OVERWRITE_UPDATE: 14
+ * * CHANNEL_OVERWRITE_DELETE: 15
+ * * MEMBER_KICK: 20
+ * * MEMBER_PRUNE: 21
+ * * MEMBER_BAN_ADD: 22
+ * * MEMBER_BAN_REMOVE: 23
+ * * MEMBER_UPDATE: 24
+ * * MEMBER_ROLE_UPDATE: 25
+ * * MEMBER_MOVE: 26
+ * * MEMBER_DISCONNECT: 27
+ * * BOT_ADD: 28,
+ * * ROLE_CREATE: 30
+ * * ROLE_UPDATE: 31
+ * * ROLE_DELETE: 32
+ * * INVITE_CREATE: 40
+ * * INVITE_UPDATE: 41
+ * * INVITE_DELETE: 42
+ * * WEBHOOK_CREATE: 50
+ * * WEBHOOK_UPDATE: 51
+ * * WEBHOOK_DELETE: 52
+ * * EMOJI_CREATE: 60
+ * * EMOJI_UPDATE: 61
+ * * EMOJI_DELETE: 62
+ * * MESSAGE_DELETE: 72
+ * * MESSAGE_BULK_DELETE: 73
+ * * MESSAGE_PIN: 74
+ * * MESSAGE_UNPIN: 75
+ * * INTEGRATION_CREATE: 80
+ * * INTEGRATION_UPDATE: 81
+ * * INTEGRATION_DELETE: 82
+ * * STAGE_INSTANCE_CREATE: 83
+ * * STAGE_INSTANCE_UPDATE: 84
+ * * STAGE_INSTANCE_DELETE: 85
+ * * STICKER_CREATE: 90
+ * * STICKER_UPDATE: 91
+ * * STICKER_DELETE: 92
+ * * GUILD_SCHEDULED_EVENT_CREATE: 100
+ * * GUILD_SCHEDULED_EVENT_UPDATE: 101
+ * * GUILD_SCHEDULED_EVENT_DELETE: 102
+ * * THREAD_CREATE: 110
+ * * THREAD_UPDATE: 111
+ * * THREAD_DELETE: 112
+ * @typedef {?(number|string)} AuditLogAction
+ * @see {@link https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events}
+ */
+
+/**
+ * All available actions keyed under their names to their numeric values.
+ * @name GuildAuditLogs.Actions
+ * @type {Object}
+ */
+const Actions = {
+ ALL: null,
+ GUILD_UPDATE: 1,
+ CHANNEL_CREATE: 10,
+ CHANNEL_UPDATE: 11,
+ CHANNEL_DELETE: 12,
+ CHANNEL_OVERWRITE_CREATE: 13,
+ CHANNEL_OVERWRITE_UPDATE: 14,
+ CHANNEL_OVERWRITE_DELETE: 15,
+ MEMBER_KICK: 20,
+ MEMBER_PRUNE: 21,
+ MEMBER_BAN_ADD: 22,
+ MEMBER_BAN_REMOVE: 23,
+ MEMBER_UPDATE: 24,
+ MEMBER_ROLE_UPDATE: 25,
+ MEMBER_MOVE: 26,
+ MEMBER_DISCONNECT: 27,
+ BOT_ADD: 28,
+ ROLE_CREATE: 30,
+ ROLE_UPDATE: 31,
+ ROLE_DELETE: 32,
+ INVITE_CREATE: 40,
+ INVITE_UPDATE: 41,
+ INVITE_DELETE: 42,
+ WEBHOOK_CREATE: 50,
+ WEBHOOK_UPDATE: 51,
+ WEBHOOK_DELETE: 52,
+ EMOJI_CREATE: 60,
+ EMOJI_UPDATE: 61,
+ EMOJI_DELETE: 62,
+ MESSAGE_DELETE: 72,
+ MESSAGE_BULK_DELETE: 73,
+ MESSAGE_PIN: 74,
+ MESSAGE_UNPIN: 75,
+ INTEGRATION_CREATE: 80,
+ INTEGRATION_UPDATE: 81,
+ INTEGRATION_DELETE: 82,
+ STAGE_INSTANCE_CREATE: 83,
+ STAGE_INSTANCE_UPDATE: 84,
+ STAGE_INSTANCE_DELETE: 85,
+ STICKER_CREATE: 90,
+ STICKER_UPDATE: 91,
+ STICKER_DELETE: 92,
+ GUILD_SCHEDULED_EVENT_CREATE: 100,
+ GUILD_SCHEDULED_EVENT_UPDATE: 101,
+ GUILD_SCHEDULED_EVENT_DELETE: 102,
+ THREAD_CREATE: 110,
+ THREAD_UPDATE: 111,
+ THREAD_DELETE: 112,
};
/**
@@ -132,28 +241,28 @@ class GuildAuditLogs {
* @returns {AuditLogTargetType}
*/
static targetType(target) {
- if (target < 10) return Targets.Guild;
- if (target < 20) return Targets.Channel;
- if (target < 30) return Targets.User;
- if (target < 40) return Targets.Role;
- if (target < 50) return Targets.Invite;
- if (target < 60) return Targets.Webhook;
- if (target < 70) return Targets.Emoji;
- if (target < 80) return Targets.Message;
- if (target < 83) return Targets.Integration;
- if (target < 86) return Targets.StageInstance;
- if (target < 100) return Targets.Sticker;
- if (target < 110) return Targets.GuildScheduledEvent;
- if (target < 120) return Targets.Thread;
- return Targets.Unknown;
+ if (target < 10) return Targets.GUILD;
+ if (target < 20) return Targets.CHANNEL;
+ if (target < 30) return Targets.USER;
+ if (target < 40) return Targets.ROLE;
+ if (target < 50) return Targets.INVITE;
+ if (target < 60) return Targets.WEBHOOK;
+ if (target < 70) return Targets.EMOJI;
+ if (target < 80) return Targets.MESSAGE;
+ if (target < 83) return Targets.INTEGRATION;
+ if (target < 86) return Targets.STAGE_INSTANCE;
+ if (target < 100) return Targets.STICKER;
+ if (target < 110) return Targets.GUILD_SCHEDULED_EVENT;
+ if (target < 120) return Targets.THREAD;
+ return Targets.UNKNOWN;
}
/**
- * The action type of an entry, e.g. `Create`. Here are the available types:
- * * Create
- * * Delete
- * * Update
- * * All
+ * The action type of an entry, e.g. `CREATE`. Here are the available types:
+ * * CREATE
+ * * DELETE
+ * * UPDATE
+ * * ALL
* @typedef {string} AuditLogActionType
*/
@@ -165,73 +274,73 @@ class GuildAuditLogs {
static actionType(action) {
if (
[
- AuditLogEvent.ChannelCreate,
- AuditLogEvent.ChannelOverwriteCreate,
- AuditLogEvent.MemberBanRemove,
- AuditLogEvent.BotAdd,
- AuditLogEvent.RoleCreate,
- AuditLogEvent.InviteCreate,
- AuditLogEvent.WebhookCreate,
- AuditLogEvent.EmojiCreate,
- AuditLogEvent.MessagePin,
- AuditLogEvent.IntegrationCreate,
- AuditLogEvent.StageInstanceCreate,
- AuditLogEvent.StickerCreate,
- AuditLogEvent.GuildScheduledEventCreate,
- AuditLogEvent.ThreadCreate,
+ Actions.CHANNEL_CREATE,
+ Actions.CHANNEL_OVERWRITE_CREATE,
+ Actions.MEMBER_BAN_REMOVE,
+ Actions.BOT_ADD,
+ Actions.ROLE_CREATE,
+ Actions.INVITE_CREATE,
+ Actions.WEBHOOK_CREATE,
+ Actions.EMOJI_CREATE,
+ Actions.MESSAGE_PIN,
+ Actions.INTEGRATION_CREATE,
+ Actions.STAGE_INSTANCE_CREATE,
+ Actions.STICKER_CREATE,
+ Actions.GUILD_SCHEDULED_EVENT_CREATE,
+ Actions.THREAD_CREATE,
].includes(action)
) {
- return 'Create';
+ return 'CREATE';
}
if (
[
- AuditLogEvent.ChannelDelete,
- AuditLogEvent.ChannelOverwriteDelete,
- AuditLogEvent.MemberKick,
- AuditLogEvent.MemberPrune,
- AuditLogEvent.MemberBanAdd,
- AuditLogEvent.MemberDisconnect,
- AuditLogEvent.RoleDelete,
- AuditLogEvent.InviteDelete,
- AuditLogEvent.WebhookDelete,
- AuditLogEvent.EmojiDelete,
- AuditLogEvent.MessageDelete,
- AuditLogEvent.MessageBulkDelete,
- AuditLogEvent.MessageUnpin,
- AuditLogEvent.IntegrationDelete,
- AuditLogEvent.StageInstanceDelete,
- AuditLogEvent.StickerDelete,
- AuditLogEvent.GuildScheduledEventDelete,
- AuditLogEvent.ThreadDelete,
+ Actions.CHANNEL_DELETE,
+ Actions.CHANNEL_OVERWRITE_DELETE,
+ Actions.MEMBER_KICK,
+ Actions.MEMBER_PRUNE,
+ Actions.MEMBER_BAN_ADD,
+ Actions.MEMBER_DISCONNECT,
+ Actions.ROLE_DELETE,
+ Actions.INVITE_DELETE,
+ Actions.WEBHOOK_DELETE,
+ Actions.EMOJI_DELETE,
+ Actions.MESSAGE_DELETE,
+ Actions.MESSAGE_BULK_DELETE,
+ Actions.MESSAGE_UNPIN,
+ Actions.INTEGRATION_DELETE,
+ Actions.STAGE_INSTANCE_DELETE,
+ Actions.STICKER_DELETE,
+ Actions.GUILD_SCHEDULED_EVENT_DELETE,
+ Actions.THREAD_DELETE,
].includes(action)
) {
- return 'Delete';
+ return 'DELETE';
}
if (
[
- AuditLogEvent.GuildUpdate,
- AuditLogEvent.ChannelUpdate,
- AuditLogEvent.ChannelOverwriteUpdate,
- AuditLogEvent.MemberUpdate,
- AuditLogEvent.MemberRoleUpdate,
- AuditLogEvent.MemberMove,
- AuditLogEvent.RoleUpdate,
- AuditLogEvent.InviteUpdate,
- AuditLogEvent.WebhookUpdate,
- AuditLogEvent.EmojiUpdate,
- AuditLogEvent.IntegrationUpdate,
- AuditLogEvent.StageInstanceUpdate,
- AuditLogEvent.StickerUpdate,
- AuditLogEvent.GuildScheduledEventUpdate,
- AuditLogEvent.ThreadUpdate,
+ Actions.GUILD_UPDATE,
+ Actions.CHANNEL_UPDATE,
+ Actions.CHANNEL_OVERWRITE_UPDATE,
+ Actions.MEMBER_UPDATE,
+ Actions.MEMBER_ROLE_UPDATE,
+ Actions.MEMBER_MOVE,
+ Actions.ROLE_UPDATE,
+ Actions.INVITE_UPDATE,
+ Actions.WEBHOOK_UPDATE,
+ Actions.EMOJI_UPDATE,
+ Actions.INTEGRATION_UPDATE,
+ Actions.STAGE_INSTANCE_UPDATE,
+ Actions.STICKER_UPDATE,
+ Actions.GUILD_SCHEDULED_EVENT_UPDATE,
+ Actions.THREAD_UPDATE,
].includes(action)
) {
- return 'Update';
+ return 'UPDATE';
}
- return 'All';
+ return 'ALL';
}
toJSON() {
@@ -261,7 +370,7 @@ class GuildAuditLogsEntry {
* Specific action type of this entry in its string presentation
* @type {AuditLogAction}
*/
- this.action = Object.keys(AuditLogEvent).find(k => AuditLogEvent[k] === data.action_type);
+ this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type);
/**
* The reason of this entry
@@ -274,7 +383,7 @@ class GuildAuditLogsEntry {
* @type {?User}
*/
this.executor = data.user_id
- ? guild.client.options.partials.includes(Partials.User)
+ ? guild.client.options.partials.includes(PartialTypes.USER)
? guild.client.users._add({ id: data.user_id })
: guild.client.users.cache.get(data.user_id)
: null;
@@ -305,52 +414,52 @@ class GuildAuditLogsEntry {
*/
this.extra = null;
switch (data.action_type) {
- case AuditLogEvent.MemberPrune:
+ case Actions.MEMBER_PRUNE:
this.extra = {
removed: Number(data.options.members_removed),
days: Number(data.options.delete_member_days),
};
break;
- case AuditLogEvent.MemberMove:
- case AuditLogEvent.MessageDelete:
- case AuditLogEvent.MessageBulkDelete:
+ case Actions.MEMBER_MOVE:
+ case Actions.MESSAGE_DELETE:
+ case Actions.MESSAGE_BULK_DELETE:
this.extra = {
channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id },
count: Number(data.options.count),
};
break;
- case AuditLogEvent.MessagePin:
- case AuditLogEvent.MessageUnpin:
+ case Actions.MESSAGE_PIN:
+ case Actions.MESSAGE_UNPIN:
this.extra = {
channel: guild.client.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id },
messageId: data.options.message_id,
};
break;
- case AuditLogEvent.MemberDisconnect:
+ case Actions.MEMBER_DISCONNECT:
this.extra = {
count: Number(data.options.count),
};
break;
- case AuditLogEvent.ChannelOverwriteCreate:
- case AuditLogEvent.ChannelOverwriteUpdate:
- case AuditLogEvent.ChannelOverwriteDelete:
- switch (data.options.type) {
- case OverwriteType.Role:
+ case Actions.CHANNEL_OVERWRITE_CREATE:
+ case Actions.CHANNEL_OVERWRITE_UPDATE:
+ case Actions.CHANNEL_OVERWRITE_DELETE:
+ switch (Number(data.options.type)) {
+ case OverwriteTypes.role:
this.extra = guild.roles.cache.get(data.options.id) ?? {
id: data.options.id,
name: data.options.role_name,
- type: OverwriteType.Role,
+ type: OverwriteTypes[OverwriteTypes.role],
};
break;
- case OverwriteType.Member:
+ case OverwriteTypes.member:
this.extra = guild.members.cache.get(data.options.id) ?? {
id: data.options.id,
- type: OverwriteType.Member,
+ type: OverwriteTypes[OverwriteTypes.member],
};
break;
@@ -359,9 +468,9 @@ class GuildAuditLogsEntry {
}
break;
- case AuditLogEvent.StageInstanceCreate:
- case AuditLogEvent.StageInstanceDelete:
- case AuditLogEvent.StageInstanceUpdate:
+ case Actions.STAGE_INSTANCE_CREATE:
+ case Actions.STAGE_INSTANCE_DELETE:
+ case Actions.STAGE_INSTANCE_UPDATE:
this.extra = {
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
};
@@ -376,20 +485,20 @@ class GuildAuditLogsEntry {
* @type {?AuditLogEntryTarget}
*/
this.target = null;
- if (targetType === Targets.Unknown) {
+ if (targetType === Targets.UNKNOWN) {
this.target = this.changes.reduce((o, c) => {
o[c.key] = c.new ?? c.old;
return o;
}, {});
this.target.id = data.target_id;
- // MemberDisconnect and similar types do not provide a target_id.
- } else if (targetType === Targets.User && data.target_id) {
- this.target = guild.client.options.partials.includes(Partials.User)
+ // MEMBER_DISCONNECT and similar types do not provide a target_id.
+ } else if (targetType === Targets.USER && data.target_id) {
+ this.target = guild.client.options.partials.includes(PartialTypes.USER)
? guild.client.users._add({ id: data.target_id })
: guild.client.users.cache.get(data.target_id);
- } else if (targetType === Targets.Guild) {
+ } else if (targetType === Targets.GUILD) {
this.target = guild.client.guilds.cache.get(data.target_id);
- } else if (targetType === Targets.Webhook) {
+ } else if (targetType === Targets.WEBHOOK) {
this.target =
logs.webhooks.get(data.target_id) ??
new Webhook(
@@ -405,7 +514,7 @@ class GuildAuditLogsEntry {
},
),
);
- } else if (targetType === Targets.Invite) {
+ } else if (targetType === Targets.INVITE) {
let change = this.changes.find(c => c.key === 'code');
change = change.new ?? change.old;
@@ -421,13 +530,13 @@ class GuildAuditLogsEntry {
{ guild },
),
);
- } else if (targetType === Targets.Message) {
- // Discord sends a channel id for the MessageBulkDelete action type.
+ } else if (targetType === Targets.MESSAGE) {
+ // Discord sends a channel id for the MESSAGE_BULK_DELETE action type.
this.target =
- data.action_type === AuditLogEvent.MessageBulkDelete
+ data.action_type === Actions.MESSAGE_BULK_DELETE
? guild.channels.cache.get(data.target_id) ?? { id: data.target_id }
: guild.client.users.cache.get(data.target_id);
- } else if (targetType === Targets.Integration) {
+ } else if (targetType === Targets.INTEGRATION) {
this.target =
logs.integrations.get(data.target_id) ??
new Integration(
@@ -441,7 +550,7 @@ class GuildAuditLogsEntry {
),
guild,
);
- } else if (targetType === Targets.Channel || targetType === Targets.Thread) {
+ } else if (targetType === Targets.CHANNEL || targetType === Targets.THREAD) {
this.target =
guild.channels.cache.get(data.target_id) ??
this.changes.reduce(
@@ -451,7 +560,7 @@ class GuildAuditLogsEntry {
},
{ id: data.target_id },
);
- } else if (targetType === Targets.StageInstance) {
+ } else if (targetType === Targets.STAGE_INSTANCE) {
this.target =
guild.stageInstances.cache.get(data.target_id) ??
new StageInstance(
@@ -468,7 +577,7 @@ class GuildAuditLogsEntry {
},
),
);
- } else if (targetType === Targets.Sticker) {
+ } else if (targetType === Targets.STICKER) {
this.target =
guild.stickers.cache.get(data.target_id) ??
new Sticker(
@@ -481,7 +590,7 @@ class GuildAuditLogsEntry {
{ id: data.target_id },
),
);
- } else if (targetType === Targets.GuildScheduledEvent) {
+ } else if (targetType === Targets.GUILD_SCHEDULED_EVENT) {
this.target =
guild.scheduledEvents.cache.get(data.target_id) ??
new GuildScheduledEvent(
@@ -505,7 +614,7 @@ class GuildAuditLogsEntry {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -522,6 +631,7 @@ class GuildAuditLogsEntry {
}
}
+GuildAuditLogs.Actions = Actions;
GuildAuditLogs.Targets = Targets;
GuildAuditLogs.Entry = GuildAuditLogsEntry;
diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js
index 67c2f9e..03e86d3 100644
--- a/src/structures/GuildChannel.js
+++ b/src/structures/GuildChannel.js
@@ -1,11 +1,12 @@
'use strict';
-const { PermissionFlagsBits } = require('discord-api-types/v9');
const { Channel } = require('./Channel');
+const PermissionOverwrites = require('./PermissionOverwrites');
const { Error } = require('../errors');
const PermissionOverwriteManager = require('../managers/PermissionOverwriteManager');
-const { VoiceBasedChannelTypes } = require('../util/Constants');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const { ChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
+const Permissions = require('../util/Permissions');
+const Util = require('../util/Util');
/**
* Represents a guild channel from any of the following:
@@ -120,11 +121,11 @@ class GuildChannel extends Channel {
// Handle empty overwrite
if (
(!channelVal &&
- parentVal.deny.bitfield === PermissionsBitField.defaultBit &&
- parentVal.allow.bitfield === PermissionsBitField.defaultBit) ||
+ parentVal.deny.bitfield === Permissions.defaultBit &&
+ parentVal.allow.bitfield === Permissions.defaultBit) ||
(!parentVal &&
- channelVal.deny.bitfield === PermissionsBitField.defaultBit &&
- channelVal.allow.bitfield === PermissionsBitField.defaultBit)
+ channelVal.deny.bitfield === Permissions.defaultBit &&
+ channelVal.allow.bitfield === Permissions.defaultBit)
) {
return true;
}
@@ -153,7 +154,7 @@ class GuildChannel extends Channel {
* Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites.
* @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for
* @param {boolean} [checkAdmin=true] Whether having `ADMINISTRATOR` will return all permissions
- * @returns {?Readonly}
+ * @returns {?Readonly}
*/
permissionsFor(memberOrRole, checkAdmin = true) {
const member = this.guild.members.resolve(memberOrRole);
@@ -192,30 +193,28 @@ class GuildChannel extends Channel {
* Gets the overall set of permissions for a member in this channel, taking into account channel overwrites.
* @param {GuildMember} member The member to obtain the overall permissions for
* @param {boolean} checkAdmin=true Whether having `ADMINISTRATOR` will return all permissions
- * @returns {Readonly}
+ * @returns {Readonly}
* @private
*/
memberPermissions(member, checkAdmin) {
- if (checkAdmin && member.id === this.guild.ownerId) {
- return new PermissionsBitField(PermissionsBitField.All).freeze();
- }
+ if (checkAdmin && member.id === this.guild.ownerId) return new Permissions(Permissions.ALL).freeze();
const roles = member.roles.cache;
- const permissions = new PermissionsBitField(roles.map(role => role.permissions));
+ const permissions = new Permissions(roles.map(role => role.permissions));
- if (checkAdmin && permissions.has(PermissionFlagsBits.Administrator)) {
- return new PermissionsBitField(PermissionsBitField.All).freeze();
+ if (checkAdmin && permissions.has(Permissions.FLAGS.ADMINISTRATOR)) {
+ return new Permissions(Permissions.ALL).freeze();
}
const overwrites = this.overwritesFor(member, true, roles);
return permissions
- .remove(overwrites.everyone?.deny ?? PermissionsBitField.defaultBit)
- .add(overwrites.everyone?.allow ?? PermissionsBitField.defaultBit)
- .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny) : PermissionsBitField.defaultBit)
- .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow) : PermissionsBitField.defaultBit)
- .remove(overwrites.member?.deny ?? PermissionsBitField.defaultBit)
- .add(overwrites.member?.allow ?? PermissionsBitField.defaultBit)
+ .remove(overwrites.everyone?.deny ?? Permissions.defaultBit)
+ .add(overwrites.everyone?.allow ?? Permissions.defaultBit)
+ .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny) : Permissions.defaultBit)
+ .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow) : Permissions.defaultBit)
+ .remove(overwrites.member?.deny ?? Permissions.defaultBit)
+ .add(overwrites.member?.allow ?? Permissions.defaultBit)
.freeze();
}
@@ -223,22 +222,22 @@ class GuildChannel extends Channel {
* Gets the overall set of permissions for a role in this channel, taking into account channel overwrites.
* @param {Role} role The role to obtain the overall permissions for
* @param {boolean} checkAdmin Whether having `ADMINISTRATOR` will return all permissions
- * @returns {Readonly}
+ * @returns {Readonly}
* @private
*/
rolePermissions(role, checkAdmin) {
- if (checkAdmin && role.permissions.has(PermissionFlagsBits.Administrator)) {
- return new PermissionsBitField(PermissionsBitField.All).freeze();
+ if (checkAdmin && role.permissions.has(Permissions.FLAGS.ADMINISTRATOR)) {
+ return new Permissions(Permissions.ALL).freeze();
}
const everyoneOverwrites = this.permissionOverwrites.cache.get(this.guild.id);
const roleOverwrites = this.permissionOverwrites.cache.get(role.id);
return role.permissions
- .remove(everyoneOverwrites?.deny ?? PermissionsBitField.defaultBit)
- .add(everyoneOverwrites?.allow ?? PermissionsBitField.defaultBit)
- .remove(roleOverwrites?.deny ?? PermissionsBitField.defaultBit)
- .add(roleOverwrites?.allow ?? PermissionsBitField.defaultBit)
+ .remove(everyoneOverwrites?.deny ?? Permissions.defaultBit)
+ .add(everyoneOverwrites?.allow ?? Permissions.defaultBit)
+ .remove(roleOverwrites?.deny ?? Permissions.defaultBit)
+ .add(roleOverwrites?.allow ?? Permissions.defaultBit)
.freeze();
}
@@ -260,9 +259,30 @@ class GuildChannel extends Channel {
* @readonly
*/
get members() {
- return this.guild.members.cache.filter(m => this.permissionsFor(m).has(PermissionFlagsBits.ViewChannel, false));
+ return this.guild.members.cache.filter(m => this.permissionsFor(m).has(Permissions.FLAGS.VIEW_CHANNEL, false));
}
+ /**
+ * 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} [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 {ChannelData} data The new data for the channel
@@ -274,8 +294,64 @@ class GuildChannel extends Channel {
* .then(console.log)
* .catch(console.error);
*/
- edit(data, reason) {
- return this.guild.channels.edit(this, data, reason);
+ async edit(data, reason) {
+ data.parent &&= this.client.channels.resolveId(data.parent);
+
+ if (typeof data.position !== 'undefined') {
+ const updatedChannels = await Util.setPosition(
+ this,
+ data.position,
+ false,
+ this.guild._sortedChannels(this),
+ this.client.api.guilds(this.guild.id).channels,
+ reason,
+ );
+ this.client.actions.GuildChannelsPositionUpdate.handle({
+ guild_id: this.guild.id,
+ channels: updatedChannels,
+ });
+ }
+
+ let permission_overwrites;
+
+ if (data.permissionOverwrites) {
+ permission_overwrites = data.permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
+ }
+
+ if (data.lockPermissions) {
+ if (data.parent) {
+ const newParent = this.guild.channels.resolve(data.parent);
+ if (newParent?.type === 'GUILD_CATEGORY') {
+ permission_overwrites = newParent.permissionOverwrites.cache.map(o =>
+ PermissionOverwrites.resolve(o, this.guild),
+ );
+ }
+ } else if (this.parent) {
+ permission_overwrites = this.parent.permissionOverwrites.cache.map(o =>
+ PermissionOverwrites.resolve(o, this.guild),
+ );
+ }
+ }
+
+ const newData = await this.client.api.channels(this.id).patch({
+ data: {
+ name: (data.name ?? this.name).trim(),
+ type: ChannelTypes[data.type],
+ topic: data.topic,
+ nsfw: data.nsfw,
+ bitrate: data.bitrate ?? this.bitrate,
+ user_limit: data.userLimit ?? this.userLimit,
+ rtc_region: data.rtcRegion ?? this.rtcRegion,
+ parent_id: data.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;
}
/**
@@ -339,10 +415,30 @@ class GuildChannel extends Channel {
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
* .catch(console.error);
*/
- setPosition(position, options = {}) {
- return this.guild.channels.setPosition(this, position, options);
+ async setPosition(position, { relative, reason } = {}) {
+ const updatedChannels = await Util.setPosition(
+ this,
+ position,
+ relative,
+ this.guild._sortedChannels(this),
+ this.client.api.guilds(this.guild.id).channels,
+ reason,
+ );
+ this.client.actions.GuildChannelsPositionUpdate.handle({
+ guild_id: this.guild.id,
+ channels: updatedChannels,
+ });
+ return this;
}
+ /**
+ * Data that can be resolved to an Application. This can be:
+ * * An Application
+ * * An Activity with associated Application
+ * * A Snowflake
+ * @typedef {Application|Snowflake} ApplicationResolvable
+ */
+
/**
* Options used to clone a guild channel.
* @typedef {GuildChannelCreateOptions} GuildChannelCloneOptions
@@ -416,12 +512,12 @@ class GuildChannel extends Channel {
if (!permissions) return false;
// This flag allows managing even if timed out
- if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
if (this.guild.me.communicationDisabledUntilTimestamp > Date.now()) return false;
const bitfield = VoiceBasedChannelTypes.includes(this.type)
- ? PermissionFlagsBits.ManageChannels | PermissionFlagsBits.Connect
- : PermissionFlagsBits.ViewChannel | PermissionFlagsBits.ManageChannels;
+ ? Permissions.FLAGS.MANAGE_CHANNELS | Permissions.FLAGS.CONNECT
+ : Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.MANAGE_CHANNELS;
return permissions.has(bitfield, false);
}
@@ -434,7 +530,7 @@ class GuildChannel extends Channel {
if (this.client.user.id === this.guild.ownerId) return true;
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
- return permissions.has(PermissionFlagsBits.ViewChannel, false);
+ return permissions.has(Permissions.FLAGS.VIEW_CHANNEL, false);
}
/**
@@ -448,7 +544,7 @@ class GuildChannel extends Channel {
* .catch(console.error);
*/
async delete(reason) {
- await this.guild.channels.delete(this.id, reason);
+ await this.client.api.channels(this.id).delete({ reason });
return this;
}
}
diff --git a/src/structures/GuildEmoji.js b/src/structures/GuildEmoji.js
index 609083b..e5629fd 100644
--- a/src/structures/GuildEmoji.js
+++ b/src/structures/GuildEmoji.js
@@ -1,9 +1,9 @@
'use strict';
-const { PermissionFlagsBits } = require('discord-api-types/v9');
const BaseGuildEmoji = require('./BaseGuildEmoji');
const { Error } = require('../errors');
const GuildEmojiRoleManager = require('../managers/GuildEmojiRoleManager');
+const Permissions = require('../util/Permissions');
/**
* Represents a custom emoji.
@@ -56,7 +56,7 @@ class GuildEmoji extends BaseGuildEmoji {
*/
get deletable() {
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
- return !this.managed && this.guild.me.permissions.has(PermissionFlagsBits.ManageEmojisAndStickers);
+ return !this.managed && this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS);
}
/**
@@ -72,8 +72,18 @@ class GuildEmoji extends BaseGuildEmoji {
* Fetches the author for this emoji
* @returns {Promise}
*/
- fetchAuthor() {
- return this.guild.emojis.fetchAuthor(this);
+ async fetchAuthor() {
+ if (this.managed) {
+ throw new Error('EMOJI_MANAGED');
+ } else {
+ if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
+ if (!this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS)) {
+ throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild);
+ }
+ }
+ const data = await this.client.api.guilds(this.guild.id).emojis(this.id).get();
+ this._patch(data);
+ return this.author;
}
/**
@@ -94,8 +104,21 @@ class GuildEmoji extends BaseGuildEmoji {
* .then(e => console.log(`Edited emoji ${e}`))
* .catch(console.error);
*/
- edit(data, reason) {
- return this.guild.emojis.edit(this.id, data, reason);
+ async edit(data, reason) {
+ const roles = data.roles?.map(r => r.id ?? r);
+ const newData = await this.client.api
+ .guilds(this.guild.id)
+ .emojis(this.id)
+ .patch({
+ data: {
+ name: data.name,
+ roles,
+ },
+ reason,
+ });
+ const clone = this._clone();
+ clone._patch(newData);
+ return clone;
}
/**
@@ -114,7 +137,7 @@ class GuildEmoji extends BaseGuildEmoji {
* @returns {Promise}
*/
async delete(reason) {
- await this.guild.emojis.delete(this.id, reason);
+ await this.client.api.guilds(this.guild.id).emojis(this.id).delete({ reason });
return this;
}
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index a02fd7a..f271def 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -1,12 +1,20 @@
'use strict';
-const { PermissionFlagsBits } = require('discord-api-types/v9');
+const process = require('node:process');
const Base = require('./Base');
const VoiceState = require('./VoiceState');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { Error } = require('../errors');
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const Permissions = require('../util/Permissions');
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedGuildMembers = new WeakSet();
+let deprecationEmittedForDeleted = false;
/**
* Represents a member of a guild on Discord.
@@ -43,9 +51,9 @@ class GuildMember extends Base {
/**
* Whether this member has yet to pass the guild's membership gate
- * @type {?boolean}
+ * @type {boolean}
*/
- this.pending = null;
+ this.pending = false;
/**
* The timestamp this member's timeout will be removed
@@ -76,18 +84,12 @@ class GuildMember extends Base {
} else if (typeof this.avatar !== 'string') {
this.avatar = null;
}
- if ('joined_at' in data) this.joinedTimestamp = Date.parse(data.joined_at);
+ if ('joined_at' in data) this.joinedTimestamp = new Date(data.joined_at).getTime();
if ('premium_since' in data) {
- this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null;
+ this.premiumSinceTimestamp = data.premium_since ? new Date(data.premium_since).getTime() : null;
}
if ('roles' in data) this._roles = data.roles;
-
- if ('pending' in data) {
- this.pending = data.pending;
- } else if (!this.partial) {
- // See https://github.com/discordjs/discord.js/issues/6546 for more info.
- this.pending ??= false;
- }
+ this.pending = data.pending ?? false;
if ('communication_disabled_until' in data) {
this.communicationDisabledUntilTimestamp =
@@ -101,6 +103,36 @@ class GuildMember extends Base {
return clone;
}
+ /**
+ * Whether or not the structure has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'GuildMember#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedGuildMembers.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'GuildMember#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedGuildMembers.add(this);
+ else deletedGuildMembers.delete(this);
+ }
+
/**
* Whether this GuildMember is a partial
* @type {boolean}
@@ -130,11 +162,12 @@ class GuildMember extends Base {
/**
* A link to the member's guild avatar.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- avatarURL(options = {}) {
- return this.avatar && this.client.rest.cdn.guildMemberAvatar(this.guild.id, this.id, this.avatar, options);
+ avatarURL({ format, size, dynamic } = {}) {
+ if (!this.avatar) return null;
+ return this.client.rest.cdn.GuildMemberAvatar(this.guild.id, this.id, this.avatar, format, size, dynamic);
}
/**
@@ -153,7 +186,7 @@ class GuildMember extends Base {
* @readonly
*/
get joinedAt() {
- return this.joinedTimestamp && new Date(this.joinedTimestamp);
+ return this.joinedTimestamp ? new Date(this.joinedTimestamp) : null;
}
/**
@@ -171,7 +204,7 @@ class GuildMember extends Base {
* @readonly
*/
get premiumSince() {
- return this.premiumSinceTimestamp && new Date(this.premiumSinceTimestamp);
+ return this.premiumSinceTimestamp ? new Date(this.premiumSinceTimestamp) : null;
}
/**
@@ -221,12 +254,12 @@ class GuildMember extends Base {
/**
* The overall set of permissions for this member, taking only roles and owner status into account
- * @type {Readonly}
+ * @type {Readonly}
* @readonly
*/
get permissions() {
- if (this.user.id === this.guild.ownerId) return new PermissionsBitField(PermissionsBitField.All).freeze();
- return new PermissionsBitField(this.roles.cache.map(role => role.permissions)).freeze();
+ if (this.user.id === this.guild.ownerId) return new Permissions(Permissions.ALL).freeze();
+ return new Permissions(this.roles.cache.map(role => role.permissions)).freeze();
}
/**
@@ -249,8 +282,7 @@ class GuildMember extends Base {
* @readonly
*/
get kickable() {
- if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
- return this.manageable && this.guild.me.permissions.has(PermissionFlagsBits.KickMembers);
+ return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.KICK_MEMBERS);
}
/**
@@ -259,8 +291,7 @@ class GuildMember extends Base {
* @readonly
*/
get bannable() {
- if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
- return this.manageable && this.guild.me.permissions.has(PermissionFlagsBits.BanMembers);
+ return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.BAN_MEMBERS);
}
/**
@@ -269,11 +300,7 @@ class GuildMember extends Base {
* @readonly
*/
get moderatable() {
- return (
- !this.permissions.has(PermissionFlagsBits.Administrator) &&
- this.manageable &&
- (this.guild.me?.permissions.has(PermissionFlagsBits.ModerateMembers) ?? false)
- );
+ return this.manageable && (this.guild.me?.permissions.has(Permissions.FLAGS.MODERATE_MEMBERS) ?? false);
}
/**
@@ -288,7 +315,7 @@ class GuildMember extends Base {
* Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel,
* taking into account roles and permission overwrites.
* @param {GuildChannelResolvable} channel The guild channel to use as context
- * @returns {Readonly}
+ * @returns {Readonly}
*/
permissionsIn(channel) {
channel = this.guild.channels.resolve(channel);
@@ -348,7 +375,7 @@ class GuildMember extends Base {
* @returns {Promise}
* @example
* // ban a guild member
- * guildMember.ban({ deleteMessageDays: 7, reason: 'They deserved it' })
+ * guildMember.ban({ days: 7, reason: 'They deserved it' })
* .then(console.log)
* .catch(console.error);
*/
@@ -451,6 +478,7 @@ class GuildMember extends Base {
TextBasedChannel.applyToClass(GuildMember);
exports.GuildMember = GuildMember;
+exports.deletedGuildMembers = deletedGuildMembers;
/**
* @external APIGuildMember
diff --git a/src/structures/GuildPreview.js b/src/structures/GuildPreview.js
index 8047971..4627faf 100644
--- a/src/structures/GuildPreview.js
+++ b/src/structures/GuildPreview.js
@@ -1,11 +1,9 @@
'use strict';
const { Collection } = require('@discordjs/collection');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { Routes } = require('discord-api-types/v9');
const Base = require('./Base');
const GuildPreviewEmoji = require('./GuildPreviewEmoji');
-const { Sticker } = require('./Sticker');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Represents the data about the guild any bot can preview, connected to the specified guild.
@@ -62,7 +60,7 @@ class GuildPreview extends Base {
if ('features' in data) {
/**
* An array of enabled guild features
- * @type {GuildFeature[]}
+ * @type {Features[]}
*/
this.features = data.features;
}
@@ -105,24 +103,14 @@ class GuildPreview extends Base {
for (const emoji of data.emojis) {
this.emojis.set(emoji.id, new GuildPreviewEmoji(this.client, emoji, this));
}
-
- /**
- * Collection of stickers belonging to this guild
- * @type {Collection}
- */
- this.stickers = data.stickers.reduce(
- (stickers, sticker) => stickers.set(sticker.id, new Sticker(this.client, sticker)),
- new Collection(),
- );
}
-
/**
* The timestamp this guild was created at
* @type {number}
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -136,29 +124,29 @@ class GuildPreview extends Base {
/**
* The URL to this guild's splash.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- splashURL(options = {}) {
- return this.splash && this.client.rest.cdn.splash(this.id, this.splash, options);
+ splashURL({ format, size } = {}) {
+ return this.splash && this.client.rest.cdn.Splash(this.id, this.splash, format, size);
}
/**
* The URL to this guild's discovery splash.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- discoverySplashURL(options = {}) {
- return this.discoverySplash && this.client.rest.cdn.discoverySplash(this.id, this.discoverySplash, options);
+ discoverySplashURL({ format, size } = {}) {
+ return this.discoverySplash && this.client.rest.cdn.DiscoverySplash(this.id, this.discoverySplash, format, size);
}
/**
* The URL to this guild's icon.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- iconURL(options = {}) {
- return this.icon && this.client.rest.cdn.icon(this.id, this.icon, options);
+ iconURL({ format, size, dynamic } = {}) {
+ return this.icon && this.client.rest.cdn.Icon(this.id, this.icon, format, size, dynamic);
}
/**
diff --git a/src/structures/GuildScheduledEvent.js b/src/structures/GuildScheduledEvent.js
index b082ed3..ff862b8 100644
--- a/src/structures/GuildScheduledEvent.js
+++ b/src/structures/GuildScheduledEvent.js
@@ -1,9 +1,14 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { GuildScheduledEventStatus, GuildScheduledEventEntityType, RouteBases } = require('discord-api-types/v9');
const Base = require('./Base');
const { Error } = require('../errors');
+const {
+ GuildScheduledEventEntityTypes,
+ GuildScheduledEventStatuses,
+ GuildScheduledEventPrivacyLevels,
+ Endpoints,
+} = require('../util/Constants');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Represents a scheduled event in a {@link Guild}.
@@ -31,8 +36,7 @@ class GuildScheduledEvent extends Base {
_patch(data) {
if ('channel_id' in data) {
/**
- * The channel id in which the scheduled event will be hosted,
- * or `null` if entity type is {@link GuildScheduledEventEntityType.External}
+ * The channel id in which the scheduled event will be hosted, or `null` if entity type is `EXTERNAL`
* @type {?Snowflake}
*/
this.channelId = data.channel_id;
@@ -82,21 +86,21 @@ class GuildScheduledEvent extends Base {
/**
* The privacy level of the guild scheduled event
- * @type {GuildScheduledEventPrivacyLevel}
+ * @type {PrivacyLevel}
*/
- this.privacyLevel = data.privacy_level;
+ this.privacyLevel = GuildScheduledEventPrivacyLevels[data.privacy_level];
/**
* The status of the guild scheduled event
* @type {GuildScheduledEventStatus}
*/
- this.status = data.status;
+ this.status = GuildScheduledEventStatuses[data.status];
/**
* The type of hosting entity associated with the scheduled event
* @type {GuildScheduledEventEntityType}
*/
- this.entityType = data.entity_type;
+ this.entityType = GuildScheduledEventEntityTypes[data.entity_type];
if ('entity_id' in data) {
/**
@@ -152,21 +156,6 @@ class GuildScheduledEvent extends Base {
} else {
this.entityMetadata ??= null;
}
-
- /**
- * The cover image hash for this scheduled event
- * @type {?string}
- */
- this.image = data.image ?? null;
- }
-
- /**
- * The URL of this scheduled event's cover image
- * @param {BaseImageURLOptions} [options={}] Options for image URL
- * @returns {?string}
- */
- coverImageURL(options = {}) {
- return this.image && this.client.rest.cdn.guildScheduledEventCover(this.id, this.image, options);
}
/**
@@ -175,7 +164,7 @@ class GuildScheduledEvent extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -230,15 +219,14 @@ class GuildScheduledEvent extends Base {
* @readonly
*/
get url() {
- return `${RouteBases.scheduledEvent}/${this.guildId}/${this.id}`;
+ return Endpoints.scheduledEvent(this.client.options.http.scheduledEvent, this.guildId, this.id);
}
/**
* Options used to create an invite URL to a {@link GuildScheduledEvent}
* @typedef {CreateInviteOptions} CreateGuildScheduledEventInviteURLOptions
* @property {GuildInvitableChannelResolvable} [channel] The channel to create the invite in.
- * This is required when the `entityType` of `GuildScheduledEvent` is
- * {@link GuildScheduledEventEntityType.External}, gets ignored otherwise
+ * This is required when the `entityType` of `GuildScheduledEvent` is `EXTERNAL`, gets ignored otherwise
*/
/**
@@ -248,13 +236,13 @@ class GuildScheduledEvent extends Base {
*/
async createInviteURL(options) {
let channelId = this.channelId;
- if (this.entityType === GuildScheduledEventEntityType.External) {
+ if (this.entityType === 'EXTERNAL') {
if (!options?.channel) throw new Error('INVITE_OPTIONS_MISSING_CHANNEL');
channelId = this.guild.channels.resolveId(options.channel);
if (!channelId) throw new Error('GUILD_CHANNEL_RESOLVE');
}
const invite = await this.guild.invites.create(channelId, options);
- return `${RouteBases.invite}/${invite.code}?event=${this.id}`;
+ return Endpoints.invite(this.client.options.http.invite, invite.code, this.id);
}
/**
@@ -355,7 +343,7 @@ class GuildScheduledEvent extends Base {
* @returns {Promise}
* @example
* // Set status of a guild scheduled event
- * guildScheduledEvent.setStatus(GuildScheduledEventStatus.Active)
+ * guildScheduledEvent.setStatus('ACTIVE')
* .then(guildScheduledEvent => console.log(`Set the status to: ${guildScheduledEvent.status}`))
* .catch(console.error);
*/
@@ -399,35 +387,35 @@ class GuildScheduledEvent extends Base {
}
/**
- * Indicates whether this guild scheduled event has an {@link GuildScheduledEventStatus.Active} status.
+ * Indicates whether this guild scheduled event has an `ACTIVE` status.
* @returns {boolean}
*/
isActive() {
- return this.status === GuildScheduledEventStatus.Active;
+ return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.ACTIVE;
}
/**
- * Indicates whether this guild scheduled event has a {@link GuildScheduledEventStatus.Canceled} status.
+ * Indicates whether this guild scheduled event has a `CANCELED` status.
* @returns {boolean}
*/
isCanceled() {
- return this.status === GuildScheduledEventStatus.Canceled;
+ return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.CANCELED;
}
/**
- * Indicates whether this guild scheduled event has a {@link GuildScheduledEventStatus.Completed} status.
+ * Indicates whether this guild scheduled event has a `COMPLETED` status.
* @returns {boolean}
*/
isCompleted() {
- return this.status === GuildScheduledEventStatus.Completed;
+ return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.COMPLETED;
}
/**
- * Indicates whether this guild scheduled event has a {@link GuildScheduledEventStatus.Scheduled} status.
+ * Indicates whether this guild scheduled event has a `SCHEDULED` status.
* @returns {boolean}
*/
isScheduled() {
- return this.status === GuildScheduledEventStatus.Scheduled;
+ return GuildScheduledEventStatuses[this.status] === GuildScheduledEventStatuses.SCHEDULED;
}
}
diff --git a/src/structures/GuildTemplate.js b/src/structures/GuildTemplate.js
index 88b4e7b..08f327d 100644
--- a/src/structures/GuildTemplate.js
+++ b/src/structures/GuildTemplate.js
@@ -1,10 +1,9 @@
'use strict';
-const { setTimeout, clearTimeout } = require('node:timers');
-const { RouteBases, Routes } = require('discord-api-types/v9');
+const { setTimeout } = require('node:timers');
const Base = require('./Base');
+const { Events } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
-const Events = require('../util/Events');
/**
* Represents the template for a guild.
@@ -67,18 +66,18 @@ class GuildTemplate extends Base {
if ('created_at' in data) {
/**
- * The timestamp of when this template was created at
- * @type {number}
+ * The time when this template was created at
+ * @type {Date}
*/
- this.createdTimestamp = Date.parse(data.created_at);
+ this.createdAt = new Date(data.created_at);
}
if ('updated_at' in data) {
/**
- * The timestamp of when this template was last synced to the guild
- * @type {number}
+ * The time when this template was last synced to the guild
+ * @type {Date}
*/
- this.updatedTimestamp = Date.parse(data.updated_at);
+ this.updatedAt = new Date(data.updated_at);
}
if ('source_guild_id' in data) {
@@ -115,8 +114,8 @@ class GuildTemplate extends Base {
*/
async createGuild(name, icon) {
const { client } = this;
- const data = await client.rest.post(Routes.template(this.code), {
- body: {
+ const data = await client.api.guilds.templates(this.code).post({
+ data: {
name,
icon: await DataResolver.resolveImage(icon),
},
@@ -126,7 +125,7 @@ class GuildTemplate extends Base {
return new Promise(resolve => {
const resolveGuild = guild => {
- client.off(Events.GuildCreate, handleGuild);
+ client.off(Events.GUILD_CREATE, handleGuild);
client.decrementMaxListeners();
resolve(guild);
};
@@ -139,7 +138,7 @@ class GuildTemplate extends Base {
};
client.incrementMaxListeners();
- client.on(Events.GuildCreate, handleGuild);
+ client.on(Events.GUILD_CREATE, handleGuild);
const timeout = setTimeout(() => resolveGuild(client.guilds._add(data)), 10_000).unref();
});
@@ -158,7 +157,7 @@ class GuildTemplate extends Base {
* @returns {Promise}
*/
async edit({ name, description } = {}) {
- const data = await this.client.api.guilds(this.guildId).templates(this.code).patch({ body: { name, description } });
+ const data = await this.client.api.guilds(this.guildId).templates(this.code).patch({ data: { name, description } });
return this._patch(data);
}
@@ -181,21 +180,21 @@ class GuildTemplate extends Base {
}
/**
- * The time when this template was created at
- * @type {Date}
+ * The timestamp of when this template was created at
+ * @type {number}
* @readonly
*/
- get createdAt() {
- return new Date(this.createdTimestamp);
+ get createdTimestamp() {
+ return this.createdAt.getTime();
}
/**
- * The time when this template was last synced to the guild
- * @type {Date}
+ * The timestamp of when this template was last synced to the guild
+ * @type {number}
* @readonly
*/
- get updatedAt() {
- return new Date(this.updatedTimestamp);
+ get updatedTimestamp() {
+ return this.updatedAt.getTime();
}
/**
@@ -213,7 +212,7 @@ class GuildTemplate extends Base {
* @readonly
*/
get url() {
- return `${RouteBases.template}/${this.code}`;
+ return `${this.client.options.http.template}/${this.code}`;
}
/**
diff --git a/src/structures/Integration.js b/src/structures/Integration.js
index fd68c41..5773de8 100644
--- a/src/structures/Integration.js
+++ b/src/structures/Integration.js
@@ -1,6 +1,5 @@
'use strict';
-const { Routes } = require('discord-api-types/v9');
const Base = require('./Base');
const IntegrationApplication = require('./IntegrationApplication');
@@ -56,21 +55,17 @@ class Integration extends Base {
*/
this.enabled = data.enabled;
- if ('syncing' in data) {
- /**
- * Whether this integration is syncing
- * @type {?boolean}
- */
- this.syncing = data.syncing;
- } else {
- this.syncing ??= null;
- }
+ /**
+ * Whether this integration is syncing
+ * @type {?boolean}
+ */
+ this.syncing = data.syncing;
/**
* The role that this integration uses for subscribers
* @type {?Role}
*/
- this.role = this.guild.roles.resolve(data.role_id);
+ this.role = this.guild.roles.cache.get(data.role_id);
if ('enable_emoticons' in data) {
/**
@@ -89,7 +84,7 @@ class Integration extends Base {
*/
this.user = this.client.users._add(data.user);
} else {
- this.user ??= null;
+ this.user = null;
}
/**
@@ -98,15 +93,11 @@ class Integration extends Base {
*/
this.account = data.account;
- if ('synced_at' in data) {
- /**
- * The timestamp at which this integration was last synced at
- * @type {?number}
- */
- this.syncedTimestamp = Date.parse(data.synced_at);
- } else {
- this.syncedTimestamp ??= null;
- }
+ /**
+ * The last time this integration was last synced
+ * @type {?number}
+ */
+ this.syncedAt = data.synced_at;
if ('subscriber_count' in data) {
/**
@@ -131,15 +122,6 @@ class Integration extends Base {
this._patch(data);
}
- /**
- * The date at which this integration was last synced at
- * @type {?Date}
- * @readonly
- */
- get syncedAt() {
- return this.syncedTimestamp && new Date(this.syncedTimestamp);
- }
-
/**
* All roles that are managed by this integration
* @type {Collection}
@@ -154,21 +136,17 @@ class Integration extends Base {
if ('expire_behavior' in data) {
/**
* The behavior of expiring subscribers
- * @type {?IntegrationExpireBehavior}
+ * @type {?number}
*/
this.expireBehavior = data.expire_behavior;
- } else {
- this.expireBehavior ??= null;
}
if ('expire_grace_period' in data) {
/**
- * The grace period (in days) before expiring subscribers
+ * The grace period before expiring subscribers
* @type {?number}
*/
this.expireGracePeriod = data.expire_grace_period;
- } else {
- this.expireGracePeriod ??= null;
}
if ('application' in data) {
diff --git a/src/structures/Interaction.js b/src/structures/Interaction.js
index a341708..2a19cad 100644
--- a/src/structures/Interaction.js
+++ b/src/structures/Interaction.js
@@ -1,9 +1,9 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v9');
const Base = require('./Base');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const { InteractionTypes, MessageComponentTypes, ApplicationCommandTypes } = require('../util/Constants');
+const Permissions = require('../util/Permissions');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Represents an interaction.
@@ -17,7 +17,7 @@ class Interaction extends Base {
* The interaction's type
* @type {InteractionType}
*/
- this.type = data.type;
+ this.type = InteractionTypes[data.type];
/**
* The interaction's id
@@ -71,16 +71,14 @@ class Interaction extends Base {
/**
* The permissions of the member, if one exists, in the channel this interaction was executed in
- * @type {?Readonly}
+ * @type {?Readonly}
*/
- this.memberPermissions = data.member?.permissions
- ? new PermissionsBitField(data.member.permissions).freeze()
- : null;
+ this.memberPermissions = data.member?.permissions ? new Permissions(data.member.permissions).freeze() : null;
/**
* The locale of the user who invoked this interaction
* @type {string}
- * @see {@link https://discord.com/developers/docs/reference#locales}
+ * @see {@link https://discord.com/developers/docs/dispatch/field-values#predefined-field-values-accepted-locales}
*/
this.locale = data.locale;
@@ -97,7 +95,7 @@ class Interaction extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -151,44 +149,44 @@ class Interaction extends Base {
return Boolean(this.guildId && !this.guild && this.member);
}
+ /**
+ * Indicates whether this interaction is a {@link BaseCommandInteraction}.
+ * @returns {boolean}
+ */
+ isApplicationCommand() {
+ return InteractionTypes[this.type] === InteractionTypes.APPLICATION_COMMAND;
+ }
+
/**
* Indicates whether this interaction is a {@link CommandInteraction}.
* @returns {boolean}
*/
isCommand() {
- return this.type === InteractionType.ApplicationCommand;
+ return InteractionTypes[this.type] === InteractionTypes.APPLICATION_COMMAND && typeof this.targetId === 'undefined';
}
/**
- * Indicates whether this interaction is a {@link ChatInputCommandInteraction}.
+ * Indicates whether this interaction is a {@link ContextMenuInteraction}
* @returns {boolean}
*/
- isChatInputCommand() {
- return this.isCommand() && this.commandType === ApplicationCommandType.ChatInput;
+ isContextMenu() {
+ return InteractionTypes[this.type] === InteractionTypes.APPLICATION_COMMAND && typeof this.targetId !== 'undefined';
}
/**
- * Indicates whether this interaction is a {@link ContextMenuCommandInteraction}
+ * Indicates whether this interaction is a {@link UserContextMenuInteraction}
* @returns {boolean}
*/
- isContextMenuCommand() {
- return this.isCommand() && [ApplicationCommandType.User, ApplicationCommandType.Message].includes(this.commandType);
+ isUserContextMenu() {
+ return this.isContextMenu() && ApplicationCommandTypes[this.targetType] === ApplicationCommandTypes.USER;
}
/**
- * Indicates whether this interaction is a {@link UserContextMenuCommandInteraction}
+ * Indicates whether this interaction is a {@link MessageContextMenuInteraction}
* @returns {boolean}
*/
- isUserContextMenuCommand() {
- return this.isContextMenuCommand() && this.commandType === ApplicationCommandType.User;
- }
-
- /**
- * Indicates whether this interaction is a {@link MessageContextMenuCommandInteraction}
- * @returns {boolean}
- */
- isMessageContextMenuCommand() {
- return this.isContextMenuCommand() && this.commandType === ApplicationCommandType.Message;
+ isMessageContextMenu() {
+ return this.isContextMenu() && ApplicationCommandTypes[this.targetType] === ApplicationCommandTypes.MESSAGE;
}
/**
@@ -196,7 +194,7 @@ class Interaction extends Base {
* @returns {boolean}
*/
isAutocomplete() {
- return this.type === InteractionType.ApplicationCommandAutocomplete;
+ return InteractionTypes[this.type] === InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE;
}
/**
@@ -204,7 +202,7 @@ class Interaction extends Base {
* @returns {boolean}
*/
isMessageComponent() {
- return this.type === InteractionType.MessageComponent;
+ return InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT;
}
/**
@@ -212,7 +210,10 @@ class Interaction extends Base {
* @returns {boolean}
*/
isButton() {
- return this.isMessageComponent() && this.componentType === ComponentType.Button;
+ return (
+ InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
+ MessageComponentTypes[this.componentType] === MessageComponentTypes.BUTTON
+ );
}
/**
@@ -220,15 +221,10 @@ class Interaction extends Base {
* @returns {boolean}
*/
isSelectMenu() {
- return this.isMessageComponent() && this.componentType === ComponentType.SelectMenu;
- }
-
- /**
- * Indicates whether this interaction can be replied to.
- * @returns {boolean}
- */
- isRepliable() {
- return ![InteractionType.Ping, InteractionType.ApplicationCommandAutocomplete].includes(this.type);
+ return (
+ InteractionTypes[this.type] === InteractionTypes.MESSAGE_COMPONENT &&
+ MessageComponentTypes[this.componentType] === MessageComponentTypes.SELECT_MENU
+ );
}
}
diff --git a/src/structures/InteractionCollector.js b/src/structures/InteractionCollector.js
index 56821f3..574c047 100644
--- a/src/structures/InteractionCollector.js
+++ b/src/structures/InteractionCollector.js
@@ -2,13 +2,14 @@
const { Collection } = require('@discordjs/collection');
const Collector = require('./interfaces/Collector');
-const Events = require('../util/Events');
+const { Events } = require('../util/Constants');
+const { InteractionTypes, MessageComponentTypes } = require('../util/Constants');
/**
* @typedef {CollectorOptions} InteractionCollectorOptions
- * @property {TextBasedChannelResolvable} [channel] The channel to listen to interactions from
- * @property {ComponentType} [componentType] The type of component to listen for
- * @property {GuildResolvable} [guild] The guild to listen to interactions from
+ * @property {TextBasedChannels} [channel] The channel to listen to interactions from
+ * @property {MessageComponentType} [componentType] The type of component to listen for
+ * @property {Guild} [guild] The guild to listen to interactions from
* @property {InteractionType} [interactionType] The type of interaction to listen for
* @property {number} [max] The maximum total amount of interactions to collect
* @property {number} [maxComponents] The maximum number of components to collect
@@ -63,13 +64,19 @@ class InteractionCollector extends Collector {
* The type of interaction to collect
* @type {?InteractionType}
*/
- this.interactionType = options.interactionType ?? null;
+ this.interactionType =
+ typeof options.interactionType === 'number'
+ ? InteractionTypes[options.interactionType]
+ : options.interactionType ?? null;
/**
* The type of component to collect
- * @type {?ComponentType}
+ * @type {?MessageComponentType}
*/
- this.componentType = options.componentType ?? null;
+ this.componentType =
+ typeof options.componentType === 'number'
+ ? MessageComponentTypes[options.componentType]
+ : options.componentType ?? null;
/**
* The users that have interacted with this collector
@@ -92,31 +99,31 @@ class InteractionCollector extends Collector {
if (this.messageId) {
this._handleMessageDeletion = this._handleMessageDeletion.bind(this);
- this.client.on(Events.MessageDelete, this._handleMessageDeletion);
- this.client.on(Events.MessageBulkDelete, bulkDeleteListener);
+ this.client.on(Events.MESSAGE_DELETE, this._handleMessageDeletion);
+ this.client.on(Events.MESSAGE_BULK_DELETE, bulkDeleteListener);
}
if (this.channelId) {
this._handleChannelDeletion = this._handleChannelDeletion.bind(this);
this._handleThreadDeletion = this._handleThreadDeletion.bind(this);
- this.client.on(Events.ChannelDelete, this._handleChannelDeletion);
- this.client.on(Events.ThreadDelete, this._handleThreadDeletion);
+ this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion);
+ this.client.on(Events.THREAD_DELETE, this._handleThreadDeletion);
}
if (this.guildId) {
this._handleGuildDeletion = this._handleGuildDeletion.bind(this);
- this.client.on(Events.GuildDelete, this._handleGuildDeletion);
+ this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion);
}
- this.client.on(Events.InteractionCreate, this.handleCollect);
+ this.client.on(Events.INTERACTION_CREATE, this.handleCollect);
this.once('end', () => {
- this.client.removeListener(Events.InteractionCreate, this.handleCollect);
- this.client.removeListener(Events.MessageDelete, this._handleMessageDeletion);
- this.client.removeListener(Events.MessageBulkDelete, bulkDeleteListener);
- this.client.removeListener(Events.ChannelDelete, this._handleChannelDeletion);
- this.client.removeListener(Events.ThreadDelete, this._handleThreadDeletion);
- this.client.removeListener(Events.GuildDelete, this._handleGuildDeletion);
+ this.client.removeListener(Events.INTERACTION_CREATE, this.handleCollect);
+ this.client.removeListener(Events.MESSAGE_DELETE, this._handleMessageDeletion);
+ this.client.removeListener(Events.MESSAGE_BULK_DELETE, bulkDeleteListener);
+ this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion);
+ this.client.removeListener(Events.THREAD_DELETE, this._handleThreadDeletion);
+ this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion);
this.client.decrementMaxListeners();
});
diff --git a/src/structures/Invite.js b/src/structures/Invite.js
index 7dcb613..0ed8b45 100644
--- a/src/structures/Invite.js
+++ b/src/structures/Invite.js
@@ -1,11 +1,14 @@
'use strict';
-const { RouteBases, Routes, PermissionFlagsBits } = require('discord-api-types/v9');
const Base = require('./Base');
const { GuildScheduledEvent } = require('./GuildScheduledEvent');
const IntegrationApplication = require('./IntegrationApplication');
const InviteStageInstance = require('./InviteStageInstance');
const { Error } = require('../errors');
+const { Endpoints } = require('../util/Constants');
+const Permissions = require('../util/Permissions');
+
+// TODO: Convert `inviter` and `channel` in this class to a getter.
/**
* Represents an invitation to a guild channel.
@@ -112,13 +115,20 @@ class Invite extends Base {
* @type {?Snowflake}
*/
this.inviterId = data.inviter_id;
+ this.inviter = this.client.users.resolve(data.inviter_id);
} else {
this.inviterId ??= null;
}
if ('inviter' in data) {
- this.client.users._add(data.inviter);
+ /**
+ * The user who created this invite
+ * @type {?User}
+ */
+ this.inviter ??= this.client.users._add(data.inviter);
this.inviterId = data.inviter.id;
+ } else {
+ this.inviter ??= null;
}
if ('target_user' in data) {
@@ -141,10 +151,18 @@ class Invite extends Base {
this.targetApplication ??= null;
}
+ /**
+ * The type of the invite target:
+ * * 1: STREAM
+ * * 2: EMBEDDED_APPLICATION
+ * @typedef {number} TargetType
+ * @see {@link https://discord.com/developers/docs/resources/invite#invite-object-invite-target-types}
+ */
+
if ('target_type' in data) {
/**
* The target type
- * @type {?InviteTargetType}
+ * @type {?TargetType}
*/
this.targetType = data.target_type;
} else {
@@ -153,21 +171,19 @@ class Invite extends Base {
if ('channel_id' in data) {
/**
- * The id of the channel this invite is for
- * @type {?Snowflake}
+ * The channel's id this invite is for
+ * @type {Snowflake}
*/
this.channelId = data.channel_id;
+ this.channel = this.client.channels.cache.get(data.channel_id);
}
if ('channel' in data) {
/**
* The channel this invite is for
- * @type {?Channel}
+ * @type {Channel}
*/
- this.channel =
- this.client.channels._add(data.channel, this.guild, { cache: false }) ??
- this.client.channels.resolve(this.channelId);
-
+ this.channel ??= this.client.channels._add(data.channel, this.guild, { cache: false });
this.channelId ??= data.channel.id;
}
@@ -176,19 +192,18 @@ class Invite extends Base {
* The timestamp this invite was created at
* @type {?number}
*/
- this.createdTimestamp = Date.parse(data.created_at);
+ this.createdTimestamp = new Date(data.created_at).getTime();
} else {
this.createdTimestamp ??= null;
}
- if ('expires_at' in data) this._expiresTimestamp = Date.parse(data.expires_at);
+ if ('expires_at' in data) this._expiresTimestamp = new Date(data.expires_at).getTime();
else this._expiresTimestamp ??= null;
if ('stage_instance' in data) {
/**
* The stage instance data if there is a public {@link StageInstance} in the stage channel this invite is for
* @type {?InviteStageInstance}
- * @deprecated
*/
this.stageInstance = new InviteStageInstance(this.client, data.stage_instance, this.channel.id, this.guild.id);
} else {
@@ -212,7 +227,7 @@ class Invite extends Base {
* @readonly
*/
get createdAt() {
- return this.createdTimestamp && new Date(this.createdTimestamp);
+ return this.createdTimestamp ? new Date(this.createdTimestamp) : null;
}
/**
@@ -224,9 +239,9 @@ class Invite extends Base {
const guild = this.guild;
if (!guild || !this.client.guilds.cache.has(guild.id)) return false;
if (!guild.me) throw new Error('GUILD_UNCACHED_ME');
- return Boolean(
- this.channel?.permissionsFor(this.client.user).has(PermissionFlagsBits.ManageChannels, false) ||
- guild.me.permissions.has(PermissionFlagsBits.ManageGuild),
+ return (
+ this.channel.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS, false) ||
+ guild.me.permissions.has(Permissions.FLAGS.MANAGE_GUILD)
);
}
@@ -248,16 +263,8 @@ class Invite extends Base {
* @readonly
*/
get expiresAt() {
- return this.expiresTimestamp && new Date(this.expiresTimestamp);
- }
-
- /**
- * The user who created this invite
- * @type {?User}
- * @readonly
- */
- get inviter() {
- return this.inviterId && this.client.users.resolve(this.inviterId);
+ const { expiresTimestamp } = this;
+ return expiresTimestamp ? new Date(expiresTimestamp) : null;
}
/**
@@ -266,7 +273,7 @@ class Invite extends Base {
* @readonly
*/
get url() {
- return `${RouteBases.invite}/${this.code}`;
+ return Endpoints.invite(this.client.options.http.invite, this.code);
}
/**
diff --git a/src/structures/InviteStageInstance.js b/src/structures/InviteStageInstance.js
index 21ede43..73db63a 100644
--- a/src/structures/InviteStageInstance.js
+++ b/src/structures/InviteStageInstance.js
@@ -6,7 +6,6 @@ const Base = require('./Base');
/**
* Represents the data about a public {@link StageInstance} in an {@link Invite}.
* @extends {Base}
- * @deprecated
*/
class InviteStageInstance extends Base {
constructor(client, data, channelId, guildId) {
diff --git a/src/structures/Message.js b/src/structures/Message.js
index dd8d287..d02f3fc 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -1,30 +1,33 @@
'use strict';
-const { createComponent, Embed } = require('@discordjs/builders');
+const process = require('node:process');
const { Collection } = require('@discordjs/collection');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const {
- InteractionType,
- ChannelType,
- MessageType,
- MessageFlags,
- PermissionFlagsBits,
-} = require('discord-api-types/v9');
const Base = require('./Base');
+const BaseMessageComponent = require('./BaseMessageComponent');
const ClientApplication = require('./ClientApplication');
const InteractionCollector = require('./InteractionCollector');
const MessageAttachment = require('./MessageAttachment');
+const Embed = require('./MessageEmbed');
const Mentions = require('./MessageMentions');
const MessagePayload = require('./MessagePayload');
const ReactionCollector = require('./ReactionCollector');
const { Sticker } = require('./Sticker');
const { Error } = require('../errors');
const ReactionManager = require('../managers/ReactionManager');
-const { NonSystemMessageTypes } = require('../util/Constants');
-const MessageFlagsBitField = require('../util/MessageFlagsBitField');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const { InteractionTypes, MessageTypes, SystemMessageTypes } = require('../util/Constants');
+const MessageFlags = require('../util/MessageFlags');
+const Permissions = require('../util/Permissions');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
const Util = require('../util/Util');
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedMessages = new WeakSet();
+let deprecationEmittedForDeleted = false;
+
/**
* Represents a message on Discord.
* @extends {Base}
@@ -59,20 +62,20 @@ class Message extends Base {
* The timestamp the message was sent at
* @type {number}
*/
- this.createdTimestamp = DiscordSnowflake.timestampFrom(this.id);
+ this.createdTimestamp = SnowflakeUtil.timestampFrom(this.id);
if ('type' in data) {
/**
* The type of the message
* @type {?MessageType}
*/
- this.type = data.type;
+ this.type = MessageTypes[data.type];
/**
* Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
* @type {?boolean}
*/
- this.system = !NonSystemMessageTypes.includes(this.type);
+ this.system = SystemMessageTypes.includes(this.type);
} else {
this.system ??= null;
this.type ??= null;
@@ -133,9 +136,9 @@ class Message extends Base {
if ('embeds' in data) {
/**
* A list of embeds in the message - e.g. YouTube Player
- * @type {Embed[]}
+ * @type {MessageEmbed[]}
*/
- this.embeds = data.embeds.map(e => new Embed(e));
+ this.embeds = data.embeds.map(e => new Embed(e, true));
} else {
this.embeds = this.embeds?.slice() ?? [];
}
@@ -143,9 +146,9 @@ class Message extends Base {
if ('components' in data) {
/**
* A list of MessageActionRows in the message
- * @type {ActionRow[]}
+ * @type {MessageActionRow[]}
*/
- this.components = data.components.map(c => createComponent(c));
+ this.components = data.components.map(c => BaseMessageComponent.create(c, this.client));
} else {
this.components = this.components?.slice() ?? [];
}
@@ -183,7 +186,7 @@ class Message extends Base {
* The timestamp the message was last edited at (if applicable)
* @type {?number}
*/
- this.editedTimestamp = Date.parse(data.edited_timestamp);
+ this.editedTimestamp = new Date(data.edited_timestamp).getTime();
} else {
this.editedTimestamp ??= null;
}
@@ -283,21 +286,21 @@ class Message extends Base {
if ('flags' in data) {
/**
* Flags that are applied to the message
- * @type {Readonly}
+ * @type {Readonly}
*/
- this.flags = new MessageFlagsBitField(data.flags).freeze();
+ this.flags = new MessageFlags(data.flags).freeze();
} else {
- this.flags = new MessageFlagsBitField(this.flags).freeze();
+ this.flags = new MessageFlags(this.flags).freeze();
}
/**
* Reference data sent in a message that contains ids identifying the referenced message.
* This can be present in the following types of message:
- * * Crossposted messages (`MessageFlags.Crossposted`)
- * * {@link MessageType.ChannelFollowAdd}
- * * {@link MessageType.ChannelPinnedMessage}
- * * {@link MessageType.Reply}
- * * {@link MessageType.ThreadStarterMessage}
+ * * Crossposted messages (IS_CROSSPOST {@link MessageFlags.FLAGS message flag})
+ * * CHANNEL_FOLLOW_ADD
+ * * CHANNEL_PINNED_MESSAGE
+ * * REPLY
+ * * THREAD_STARTER_MESSAGE
* @see {@link https://discord.com/developers/docs/resources/channel#message-types}
* @typedef {Object} MessageReference
* @property {Snowflake} channelId The channel's id the message was referenced
@@ -339,7 +342,7 @@ class Message extends Base {
*/
this.interaction = {
id: data.interaction.id,
- type: data.interaction.type,
+ type: InteractionTypes[data.interaction.type],
commandName: data.interaction.name,
user: this.client.users._add(data.interaction.user),
};
@@ -348,6 +351,36 @@ class Message extends Base {
}
}
+ /**
+ * Whether or not the structure has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Message#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedMessages.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Message#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedMessages.add(this);
+ else deletedMessages.delete(this);
+ }
+
/**
* The channel that the message was sent in
* @type {TextChannel|DMChannel|NewsChannel|ThreadChannel}
@@ -391,7 +424,7 @@ class Message extends Base {
* @readonly
*/
get editedAt() {
- return this.editedTimestamp && new Date(this.editedTimestamp);
+ return this.editedTimestamp ? new Date(this.editedTimestamp) : null;
}
/**
@@ -409,7 +442,7 @@ class Message extends Base {
* @readonly
*/
get hasThread() {
- return this.flags.has(MessageFlags.HasThread);
+ return this.flags.has(MessageFlags.FLAGS.HAS_THREAD);
}
/**
@@ -488,7 +521,7 @@ class Message extends Base {
/**
* @typedef {CollectorOptions} MessageComponentCollectorOptions
- * @property {ComponentType} [componentType] The type of component to listen for
+ * @property {MessageComponentType} [componentType] The type of component to listen for
* @property {number} [max] The maximum total amount of interactions to collect
* @property {number} [maxComponents] The maximum number of components to collect
* @property {number} [maxUsers] The maximum number of users to interact
@@ -508,7 +541,7 @@ class Message extends Base {
createMessageComponentCollector(options = {}) {
return new InteractionCollector(this.client, {
...options,
- interactionType: InteractionType.MessageComponent,
+ interactionType: InteractionTypes.MESSAGE_COMPONENT,
message: this,
});
}
@@ -518,7 +551,7 @@ class Message extends Base {
* @typedef {Object} AwaitMessageComponentOptions
* @property {CollectorFilter} [filter] The filter applied to this collector
* @property {number} [time] Time to wait for an interaction before rejecting
- * @property {ComponentType} [componentType] The type of component interaction to collect
+ * @property {MessageComponentType} [componentType] The type of component interaction to collect
*/
/**
@@ -551,7 +584,9 @@ class Message extends Base {
* @readonly
*/
get editable() {
- const precheck = Boolean(this.author.id === this.client.user.id && (!this.guild || this.channel?.viewable));
+ const precheck = Boolean(
+ this.author.id === this.client.user.id && !deletedMessages.has(this) && (!this.guild || this.channel?.viewable),
+ );
// Regardless of permissions thread messages cannot be edited if
// the thread is locked.
if (this.channel?.isThread()) {
@@ -566,6 +601,9 @@ class Message extends Base {
* @readonly
*/
get deletable() {
+ if (deletedMessages.has(this)) {
+ return false;
+ }
if (!this.guild) {
return this.author.id === this.client.user.id;
}
@@ -577,11 +615,11 @@ class Message extends Base {
const permissions = this.channel?.permissionsFor(this.client.user);
if (!permissions) return false;
// This flag allows deleting even if timed out
- if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
return Boolean(
this.author.id === this.client.user.id ||
- (permissions.has(PermissionFlagsBits.ManageMessages, false) &&
+ (permissions.has(Permissions.FLAGS.MANAGE_MESSAGES, false) &&
this.guild.me.communicationDisabledUntilTimestamp < Date.now()),
);
}
@@ -595,9 +633,10 @@ class Message extends Base {
const { channel } = this;
return Boolean(
!this.system &&
+ !deletedMessages.has(this) &&
(!this.guild ||
(channel?.viewable &&
- channel?.permissionsFor(this.client.user)?.has(PermissionFlagsBits.ManageMessages, false))),
+ channel?.permissionsFor(this.client.user)?.has(Permissions.FLAGS.MANAGE_MESSAGES, false))),
);
}
@@ -621,15 +660,16 @@ class Message extends Base {
*/
get crosspostable() {
const bitfield =
- PermissionFlagsBits.SendMessages |
- (this.author.id === this.client.user.id ? PermissionsBitField.defaultBit : PermissionFlagsBits.ManageMessages);
+ Permissions.FLAGS.SEND_MESSAGES |
+ (this.author.id === this.client.user.id ? Permissions.defaultBit : Permissions.FLAGS.MANAGE_MESSAGES);
const { channel } = this;
return Boolean(
- channel?.type === ChannelType.GuildNews &&
- !this.flags.has(MessageFlags.Crossposted) &&
- this.type === MessageType.Default &&
+ channel?.type === 'GUILD_NEWS' &&
+ !this.flags.has(MessageFlags.FLAGS.CROSSPOSTED) &&
+ this.type === 'DEFAULT' &&
channel.viewable &&
- channel.permissionsFor(this.client.user)?.has(bitfield, false),
+ channel.permissionsFor(this.client.user)?.has(bitfield, false) &&
+ !deletedMessages.has(this),
);
}
@@ -637,14 +677,13 @@ class Message extends Base {
* Options that can be passed into {@link Message#edit}.
* @typedef {Object} MessageEditOptions
* @property {?string} [content] Content to be edited
- * @property {Embed[]|APIEmbed[]} [embeds] Embeds to be added/edited
+ * @property {MessageEmbed[]|APIEmbed[]} [embeds] Embeds to be added/edited
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
- * @property {MessageFlags} [flags] Which flags to set for the message.
- * Only `MessageFlags.SuppressEmbeds` can be edited.
+ * @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be edited.
* @property {MessageAttachment[]} [attachments] An array of attachments to keep,
* all attachments will be kept if omitted
* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to add to the message
- * @property {ActionRow[]|ActionRowOptions[]} [components]
+ * @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
* Action rows containing interactive components for the message (buttons, select menus)
*/
@@ -668,7 +707,7 @@ class Message extends Base {
* @returns {Promise}
* @example
* // Crosspost a message
- * if (message.channel.type === ChannelType.GuildNews) {
+ * if (message.channel.type === 'GUILD_NEWS') {
* message.crosspost()
* .then(() => console.log('Crossposted message'))
* .catch(console.error);
@@ -681,7 +720,6 @@ class Message extends Base {
/**
* Pins this message to the channel's pinned messages.
- * @param {string} [reason] Reason for pinning
* @returns {Promise}
* @example
* // Pin a message
@@ -689,15 +727,14 @@ class Message extends Base {
* .then(console.log)
* .catch(console.error)
*/
- async pin(reason) {
+ async pin() {
if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
- await this.channel.messages.pin(this.id, reason);
+ await this.channel.messages.pin(this.id);
return this;
}
/**
* Unpins this message from the channel's pinned messages.
- * @param {string} [reason] Reason for unpinning
* @returns {Promise}
* @example
* // Unpin a message
@@ -705,9 +742,9 @@ class Message extends Base {
* .then(console.log)
* .catch(console.error)
*/
- async unpin(reason) {
+ async unpin() {
if (!this.channel) throw new Error('CHANNEL_NOT_CACHED');
- await this.channel.messages.unpin(this.id, reason);
+ await this.channel.messages.unpin(this.id);
return this;
}
@@ -759,8 +796,8 @@ class Message extends Base {
/**
* Options provided when sending a message as an inline reply.
* @typedef {BaseMessageOptions} ReplyMessageOptions
- * @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced
- * message does not exist (creates a standard message in this case when false)
+ * @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
+ * does not exist (creates a standard message in this case when false)
* @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
*/
@@ -820,7 +857,7 @@ class Message extends Base {
*/
startThread(options = {}) {
if (!this.channel) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
- if (![ChannelType.GuildText, ChannelType.GuildNews].includes(this.channel.type)) {
+ if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(this.channel.type)) {
return Promise.reject(new Error('MESSAGE_THREAD_PARENT'));
}
if (this.hasThread) return Promise.reject(new Error('MESSAGE_EXISTING_THREAD'));
@@ -853,12 +890,12 @@ class Message extends Base {
* @returns {Promise}
*/
suppressEmbeds(suppress = true) {
- const flags = new MessageFlagsBitField(this.flags.bitfield);
+ const flags = new MessageFlags(this.flags.bitfield);
if (suppress) {
- flags.add(MessageFlags.SuppressEmbeds);
+ flags.add(MessageFlags.FLAGS.SUPPRESS_EMBEDS);
} else {
- flags.remove(MessageFlags.SuppressEmbeds);
+ flags.remove(MessageFlags.FLAGS.SUPPRESS_EMBEDS);
}
return this.edit({ flags });
@@ -906,8 +943,8 @@ class Message extends Base {
if (equal && rawData) {
equal =
this.mentions.everyone === message.mentions.everyone &&
- this.createdTimestamp === Date.parse(rawData.timestamp) &&
- this.editedTimestamp === Date.parse(rawData.edited_timestamp);
+ this.createdTimestamp === new Date(rawData.timestamp).getTime() &&
+ this.editedTimestamp === new Date(rawData.edited_timestamp).getTime();
}
return equal;
@@ -946,3 +983,4 @@ class Message extends Base {
}
exports.Message = Message;
+exports.deletedMessages = deletedMessages;
diff --git a/src/structures/MessageActionRow.js b/src/structures/MessageActionRow.js
new file mode 100644
index 00000000..c44b32a
--- /dev/null
+++ b/src/structures/MessageActionRow.js
@@ -0,0 +1,101 @@
+'use strict';
+
+const BaseMessageComponent = require('./BaseMessageComponent');
+const { MessageComponentTypes } = require('../util/Constants');
+
+/**
+ * Represents an action row containing message components.
+ * @extends {BaseMessageComponent}
+ */
+class MessageActionRow extends BaseMessageComponent {
+ /**
+ * Components that can be placed in an action row
+ * * MessageButton
+ * * MessageSelectMenu
+ * @typedef {MessageButton|MessageSelectMenu} MessageActionRowComponent
+ */
+
+ /**
+ * Options for components that can be placed in an action row
+ * * MessageButtonOptions
+ * * MessageSelectMenuOptions
+ * @typedef {MessageButtonOptions|MessageSelectMenuOptions} MessageActionRowComponentOptions
+ */
+
+ /**
+ * Data that can be resolved into components that can be placed in an action row
+ * * MessageActionRowComponent
+ * * MessageActionRowComponentOptions
+ * @typedef {MessageActionRowComponent|MessageActionRowComponentOptions} MessageActionRowComponentResolvable
+ */
+
+ /**
+ * @typedef {BaseMessageComponentOptions} MessageActionRowOptions
+ * @property {MessageActionRowComponentResolvable[]} [components]
+ * The components to place in this action row
+ */
+
+ /**
+ * @param {MessageActionRow|MessageActionRowOptions} [data={}] MessageActionRow to clone or raw data
+ * @param {Client} [client] The client constructing this MessageActionRow, if provided
+ */
+ constructor(data = {}, client = null) {
+ super({ type: 'ACTION_ROW' });
+
+ /**
+ * The components in this action row
+ * @type {MessageActionRowComponent[]}
+ */
+ this.components = data.components?.map(c => BaseMessageComponent.create(c, client)) ?? [];
+ }
+
+ /**
+ * Adds components to the action row.
+ * @param {...MessageActionRowComponentResolvable[]} components The components to add
+ * @returns {MessageActionRow}
+ */
+ addComponents(...components) {
+ this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
+ return this;
+ }
+
+ /**
+ * Sets the components of the action row.
+ * @param {...MessageActionRowComponentResolvable[]} components The components to set
+ * @returns {MessageActionRow}
+ */
+ setComponents(...components) {
+ this.spliceComponents(0, this.components.length, components);
+ return this;
+ }
+
+ /**
+ * Removes, replaces, and inserts components in the action row.
+ * @param {number} index The index to start at
+ * @param {number} deleteCount The number of components to remove
+ * @param {...MessageActionRowComponentResolvable[]} [components] The replacing components
+ * @returns {MessageActionRow}
+ */
+ spliceComponents(index, deleteCount, ...components) {
+ this.components.splice(index, deleteCount, ...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
+ return this;
+ }
+
+ /**
+ * Transforms the action row to a plain object.
+ * @returns {APIMessageComponent} The raw data of this action row
+ */
+ toJSON() {
+ return {
+ components: this.components.map(c => c.toJSON()),
+ type: MessageComponentTypes[this.type],
+ };
+ }
+}
+
+module.exports = MessageActionRow;
+
+/**
+ * @external APIMessageComponent
+ * @see {@link https://discord.com/developers/docs/interactions/message-components#component-object}
+ */
diff --git a/src/structures/MessageAttachment.js b/src/structures/MessageAttachment.js
index 3426a17..79e87bf 100644
--- a/src/structures/MessageAttachment.js
+++ b/src/structures/MessageAttachment.js
@@ -124,7 +124,7 @@ class MessageAttachment {
if ('content_type' in data) {
/**
- * The media type of this attachment
+ * This media type of this attachment
* @type {?string}
*/
this.contentType = data.content_type;
diff --git a/src/structures/MessageButton.js b/src/structures/MessageButton.js
new file mode 100644
index 00000000..a94c1ec
--- /dev/null
+++ b/src/structures/MessageButton.js
@@ -0,0 +1,165 @@
+'use strict';
+
+const BaseMessageComponent = require('./BaseMessageComponent');
+const { RangeError } = require('../errors');
+const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
+const Util = require('../util/Util');
+
+/**
+ * Represents a button message component.
+ * @extends {BaseMessageComponent}
+ */
+class MessageButton extends BaseMessageComponent {
+ /**
+ * @typedef {BaseMessageComponentOptions} MessageButtonOptions
+ * @property {string} [label] The text to be displayed on this button
+ * @property {string} [customId] A unique string to be sent in the interaction when clicked
+ * @property {MessageButtonStyleResolvable} [style] The style of this button
+ * @property {EmojiIdentifierResolvable} [emoji] The emoji to be displayed to the left of the text
+ * @property {string} [url] Optional URL for link-style buttons
+ * @property {boolean} [disabled=false] Disables the button to prevent interactions
+ */
+
+ /**
+ * @param {MessageButton|MessageButtonOptions} [data={}] MessageButton to clone or raw data
+ */
+ constructor(data = {}) {
+ super({ type: 'BUTTON' });
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The text to be displayed on this button
+ * @type {?string}
+ */
+ this.label = data.label ?? null;
+
+ /**
+ * A unique string to be sent in the interaction when clicked
+ * @type {?string}
+ */
+ this.customId = data.custom_id ?? data.customId ?? null;
+
+ /**
+ * The style of this button
+ * @type {?MessageButtonStyle}
+ */
+ this.style = data.style ? MessageButton.resolveStyle(data.style) : null;
+
+ /**
+ * Emoji for this button
+ * @type {?RawEmoji}
+ */
+ this.emoji = data.emoji ? Util.resolvePartialEmoji(data.emoji) : null;
+
+ /**
+ * The URL this button links to, if it is a Link style button
+ * @type {?string}
+ */
+ this.url = data.url ?? null;
+
+ /**
+ * Whether this button is currently disabled
+ * @type {boolean}
+ */
+ this.disabled = data.disabled ?? false;
+ }
+
+ /**
+ * Sets the custom id for this button
+ * @param {string} customId A unique string to be sent in the interaction when clicked
+ * @returns {MessageButton}
+ */
+ setCustomId(customId) {
+ this.customId = Util.verifyString(customId, RangeError, 'BUTTON_CUSTOM_ID');
+ return this;
+ }
+
+ /**
+ * Sets the interactive status of the button
+ * @param {boolean} [disabled=true] Whether this button should be disabled
+ * @returns {MessageButton}
+ */
+ setDisabled(disabled = true) {
+ this.disabled = disabled;
+ return this;
+ }
+
+ /**
+ * Set the emoji of this button
+ * @param {EmojiIdentifierResolvable} emoji The emoji to be displayed on this button
+ * @returns {MessageButton}
+ */
+ setEmoji(emoji) {
+ this.emoji = Util.resolvePartialEmoji(emoji);
+ return this;
+ }
+
+ /**
+ * Sets the label of this button
+ * @param {string} label The text to be displayed on this button
+ * @returns {MessageButton}
+ */
+ setLabel(label) {
+ this.label = Util.verifyString(label, RangeError, 'BUTTON_LABEL');
+ return this;
+ }
+
+ /**
+ * Sets the style of this button
+ * @param {MessageButtonStyleResolvable} style The style of this button
+ * @returns {MessageButton}
+ */
+ setStyle(style) {
+ this.style = MessageButton.resolveStyle(style);
+ return this;
+ }
+
+ /**
+ * Sets the URL of this button.
+ * MessageButton#style must be LINK when setting a URL
+ * @param {string} url The URL of this button
+ * @returns {MessageButton}
+ */
+ setURL(url) {
+ this.url = Util.verifyString(url, RangeError, 'BUTTON_URL');
+ return this;
+ }
+
+ /**
+ * Transforms the button to a plain object.
+ * @returns {APIMessageButton} The raw data of this button
+ */
+ toJSON() {
+ return {
+ custom_id: this.customId,
+ disabled: this.disabled,
+ emoji: this.emoji,
+ label: this.label,
+ style: MessageButtonStyles[this.style],
+ type: MessageComponentTypes[this.type],
+ url: this.url,
+ };
+ }
+
+ /**
+ * Data that can be resolved to a MessageButtonStyle. This can be
+ * * MessageButtonStyle
+ * * number
+ * @typedef {number|MessageButtonStyle} MessageButtonStyleResolvable
+ */
+
+ /**
+ * Resolves the style of a button
+ * @param {MessageButtonStyleResolvable} style The style to resolve
+ * @returns {MessageButtonStyle}
+ * @private
+ */
+ static resolveStyle(style) {
+ return typeof style === 'string' ? style : MessageButtonStyles[style];
+ }
+}
+
+module.exports = MessageButton;
diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js
index 736bd68..34deaff 100644
--- a/src/structures/MessageCollector.js
+++ b/src/structures/MessageCollector.js
@@ -1,7 +1,7 @@
'use strict';
const Collector = require('./interfaces/Collector');
-const Events = require('../util/Events');
+const { Events } = require('../util/Constants');
/**
* @typedef {CollectorOptions} MessageCollectorOptions
@@ -46,20 +46,20 @@ class MessageCollector extends Collector {
this._handleGuildDeletion = this._handleGuildDeletion.bind(this);
this.client.incrementMaxListeners();
- this.client.on(Events.MessageCreate, this.handleCollect);
- this.client.on(Events.MessageDelete, this.handleDispose);
- this.client.on(Events.MessageBulkDelete, bulkDeleteListener);
- this.client.on(Events.ChannelDelete, this._handleChannelDeletion);
- this.client.on(Events.ThreadDelete, this._handleThreadDeletion);
- this.client.on(Events.GuildDelete, this._handleGuildDeletion);
+ this.client.on(Events.MESSAGE_CREATE, this.handleCollect);
+ this.client.on(Events.MESSAGE_DELETE, this.handleDispose);
+ this.client.on(Events.MESSAGE_BULK_DELETE, bulkDeleteListener);
+ this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion);
+ this.client.on(Events.THREAD_DELETE, this._handleThreadDeletion);
+ this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion);
this.once('end', () => {
- this.client.removeListener(Events.MessageCreate, this.handleCollect);
- this.client.removeListener(Events.MessageDelete, this.handleDispose);
- this.client.removeListener(Events.MessageBulkDelete, bulkDeleteListener);
- this.client.removeListener(Events.ChannelDelete, this._handleChannelDeletion);
- this.client.removeListener(Events.ThreadDelete, this._handleThreadDeletion);
- this.client.removeListener(Events.GuildDelete, this._handleGuildDeletion);
+ this.client.removeListener(Events.MESSAGE_CREATE, this.handleCollect);
+ this.client.removeListener(Events.MESSAGE_DELETE, this.handleDispose);
+ this.client.removeListener(Events.MESSAGE_BULK_DELETE, bulkDeleteListener);
+ this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion);
+ this.client.removeListener(Events.THREAD_DELETE, this._handleThreadDeletion);
+ this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion);
this.client.decrementMaxListeners();
});
}
diff --git a/src/structures/MessageComponentInteraction.js b/src/structures/MessageComponentInteraction.js
index 1e393b3..dae0107 100644
--- a/src/structures/MessageComponentInteraction.js
+++ b/src/structures/MessageComponentInteraction.js
@@ -3,6 +3,7 @@
const Interaction = require('./Interaction');
const InteractionWebhook = require('./InteractionWebhook');
const InteractionResponses = require('./interfaces/InteractionResponses');
+const { MessageComponentTypes } = require('../util/Constants');
/**
* Represents a message component interaction.
@@ -33,9 +34,9 @@ class MessageComponentInteraction extends Interaction {
/**
* The type of component which was interacted with
- * @type {ComponentType}
+ * @type {string}
*/
- this.componentType = data.data.component_type;
+ this.componentType = MessageComponentInteraction.resolveType(data.data.component_type);
/**
* Whether the reply to this interaction has been deferred
@@ -80,6 +81,16 @@ class MessageComponentInteraction extends Interaction {
.find(component => (component.customId ?? component.custom_id) === this.customId);
}
+ /**
+ * Resolves the type of a MessageComponent
+ * @param {MessageComponentTypeResolvable} type The type to resolve
+ * @returns {MessageComponentType}
+ * @private
+ */
+ static resolveType(type) {
+ return typeof type === 'string' ? type : MessageComponentTypes[type];
+ }
+
// These are here only for documentation purposes - they are implemented by InteractionResponses
/* eslint-disable no-empty-function */
deferReply() {}
@@ -105,28 +116,3 @@ module.exports = MessageComponentInteraction;
* @external APIMessageButton
* @see {@link https://discord.com/developers/docs/interactions/message-components#button-object}
*/
-
-/**
- * @external ButtonComponent
- * @see {@link https://discord.js.org/#/docs/builders/main/class/ButtonComponent}
- */
-
-/**
- * @external SelectMenuComponent
- * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuComponent}
- */
-
-/**
- * @external SelectMenuOption
- * @see {@link https://discord.js.org/#/docs/builders/main/class/SelectMenuComponent}
- */
-
-/**
- * @external ActionRow
- * @see {@link https://discord.js.org/#/docs/builders/main/class/ActionRow}
- */
-
-/**
- * @external Embed
- * @see {@link https://discord.js.org/#/docs/builders/main/class/Embed}
- */
diff --git a/src/structures/MessageContextMenuCommandInteraction.js b/src/structures/MessageContextMenuCommandInteraction.js
deleted file mode 100644
index 1100591..00000000
--- a/src/structures/MessageContextMenuCommandInteraction.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-const ContextMenuCommandInteraction = require('./ContextMenuCommandInteraction');
-
-/**
- * Represents a message context menu interaction.
- * @extends {ContextMenuCommandInteraction}
- */
-class MessageContextMenuCommandInteraction extends ContextMenuCommandInteraction {
- /**
- * The message this interaction was sent from
- * @type {Message|APIMessage}
- * @readonly
- */
- get targetMessage() {
- return this.options.getMessage('message');
- }
-}
-
-module.exports = MessageContextMenuCommandInteraction;
diff --git a/src/structures/MessageContextMenuInteraction.js b/src/structures/MessageContextMenuInteraction.js
new file mode 100644
index 00000000..0855f30
--- /dev/null
+++ b/src/structures/MessageContextMenuInteraction.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const ContextMenuInteraction = require('./ContextMenuInteraction');
+
+/**
+ * Represents a message context menu interaction.
+ * @extends {ContextMenuInteraction}
+ */
+class MessageContextMenuInteraction extends ContextMenuInteraction {
+ /**
+ * The message this interaction was sent from
+ * @type {Message|APIMessage}
+ * @readonly
+ */
+ get targetMessage() {
+ return this.options.getMessage('message');
+ }
+}
+
+module.exports = MessageContextMenuInteraction;
diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js
new file mode 100644
index 00000000..39fef44
--- /dev/null
+++ b/src/structures/MessageEmbed.js
@@ -0,0 +1,575 @@
+'use strict';
+
+const process = require('node:process');
+const { RangeError } = require('../errors');
+const Util = require('../util/Util');
+
+let deprecationEmittedForSetAuthor = false;
+let deprecationEmittedForSetFooter = false;
+
+// TODO: Remove the deprecated code for `setAuthor()` and `setFooter()`.
+
+/**
+ * Represents an embed in a message (image/video preview, rich embed, etc.)
+ */
+class MessageEmbed {
+ /**
+ * A `Partial` object is a representation of any existing object.
+ * This object contains between 0 and all of the original objects parameters.
+ * This is true regardless of whether the parameters are optional in the base object.
+ * @typedef {Object} Partial
+ */
+
+ /**
+ * Represents the possible options for a MessageEmbed
+ * @typedef {Object} MessageEmbedOptions
+ * @property {string} [title] The title of this embed
+ * @property {string} [description] The description of this embed
+ * @property {string} [url] The URL of this embed
+ * @property {Date|number} [timestamp] The timestamp of this embed
+ * @property {ColorResolvable} [color] The color of this embed
+ * @property {EmbedFieldData[]} [fields] The fields of this embed
+ * @property {Partial} [author] The author of this embed
+ * @property {Partial} [thumbnail] The thumbnail of this embed
+ * @property {Partial} [image] The image of this embed
+ * @property {Partial} [video] The video of this embed
+ * @property {Partial} [footer] The footer of this embed
+ */
+
+ // eslint-disable-next-line valid-jsdoc
+ /**
+ * @param {MessageEmbed|MessageEmbedOptions|APIEmbed} [data={}] MessageEmbed to clone or raw embed data
+ */
+ constructor(data = {}, skipValidation = false) {
+ this.setup(data, skipValidation);
+ }
+
+ setup(data, skipValidation) {
+ /**
+ * The type of this embed, either:
+ * * `rich` - a generic embed rendered from embed attributes
+ * * `image` - an image embed
+ * * `video` - a video embed
+ * * `gifv` - an animated gif image embed rendered as a video embed
+ * * `article` - an article embed
+ * * `link` - a link embed
+ * @type {string}
+ * @see {@link https://discord.com/developers/docs/resources/channel#embed-object-embed-types}
+ * @deprecated
+ */
+ this.type = data.type ?? 'rich';
+
+ /**
+ * The title of this embed
+ * @type {?string}
+ */
+ this.title = data.title ?? null;
+
+ /**
+ * The description of this embed
+ * @type {?string}
+ */
+ this.description = data.description ?? null;
+
+ /**
+ * The URL of this embed
+ * @type {?string}
+ */
+ this.url = data.url ?? null;
+
+ /**
+ * The color of this embed
+ * @type {?number}
+ */
+ this.color = 'color' in data ? Util.resolveColor(data.color) : null;
+
+ /**
+ * The timestamp of this embed
+ * @type {?number}
+ */
+ this.timestamp = 'timestamp' in data ? new Date(data.timestamp).getTime() : null;
+
+ /**
+ * Represents a field of a MessageEmbed
+ * @typedef {Object} EmbedField
+ * @property {string} name The name of this field
+ * @property {string} value The value of this field
+ * @property {boolean} inline If this field will be displayed inline
+ */
+
+ /**
+ * The fields of this embed
+ * @type {EmbedField[]}
+ */
+ this.fields = [];
+ if (data.fields) {
+ this.fields = skipValidation ? data.fields.map(Util.cloneObject) : this.constructor.normalizeFields(data.fields);
+ }
+
+ /**
+ * Represents the thumbnail of a MessageEmbed
+ * @typedef {Object} MessageEmbedThumbnail
+ * @property {string} url URL for this thumbnail
+ * @property {string} proxyURL ProxyURL for this thumbnail
+ * @property {number} height Height of this thumbnail
+ * @property {number} width Width of this thumbnail
+ */
+
+ /**
+ * The thumbnail of this embed (if there is one)
+ * @type {?MessageEmbedThumbnail}
+ */
+ this.thumbnail = data.thumbnail
+ ? {
+ url: data.thumbnail.url,
+ proxyURL: data.thumbnail.proxyURL ?? data.thumbnail.proxy_url,
+ height: data.thumbnail.height,
+ width: data.thumbnail.width,
+ }
+ : null;
+
+ /**
+ * Represents the image of a MessageEmbed
+ * @typedef {Object} MessageEmbedImage
+ * @property {string} url URL for this image
+ * @property {string} proxyURL ProxyURL for this image
+ * @property {number} height Height of this image
+ * @property {number} width Width of this image
+ */
+
+ /**
+ * The image of this embed, if there is one
+ * @type {?MessageEmbedImage}
+ */
+ this.image = data.image
+ ? {
+ url: data.image.url,
+ proxyURL: data.image.proxyURL ?? data.image.proxy_url,
+ height: data.image.height,
+ width: data.image.width,
+ }
+ : null;
+
+ /**
+ * Represents the video of a MessageEmbed
+ * @typedef {Object} MessageEmbedVideo
+ * @property {string} url URL of this video
+ * @property {string} proxyURL ProxyURL for this video
+ * @property {number} height Height of this video
+ * @property {number} width Width of this video
+ */
+
+ /**
+ * The video of this embed (if there is one)
+ * @type {?MessageEmbedVideo}
+ * @readonly
+ */
+ this.video = data.video
+ ? {
+ url: data.video.url,
+ proxyURL: data.video.proxyURL ?? data.video.proxy_url,
+ height: data.video.height,
+ width: data.video.width,
+ }
+ : null;
+
+ /**
+ * Represents the author field of a MessageEmbed
+ * @typedef {Object} MessageEmbedAuthor
+ * @property {string} name The name of this author
+ * @property {string} url URL of this author
+ * @property {string} iconURL URL of the icon for this author
+ * @property {string} proxyIconURL Proxied URL of the icon for this author
+ */
+
+ /**
+ * The author of this embed (if there is one)
+ * @type {?MessageEmbedAuthor}
+ */
+ this.author = data.author
+ ? {
+ name: data.author.name,
+ url: data.author.url,
+ iconURL: data.author.iconURL ?? data.author.icon_url,
+ proxyIconURL: data.author.proxyIconURL ?? data.author.proxy_icon_url,
+ }
+ : null;
+
+ /**
+ * Represents the provider of a MessageEmbed
+ * @typedef {Object} MessageEmbedProvider
+ * @property {string} name The name of this provider
+ * @property {string} url URL of this provider
+ */
+
+ /**
+ * The provider of this embed (if there is one)
+ * @type {?MessageEmbedProvider}
+ */
+ this.provider = data.provider
+ ? {
+ name: data.provider.name,
+ url: data.provider.name,
+ }
+ : null;
+
+ /**
+ * Represents the footer field of a MessageEmbed
+ * @typedef {Object} MessageEmbedFooter
+ * @property {string} text The text of this footer
+ * @property {string} iconURL URL of the icon for this footer
+ * @property {string} proxyIconURL Proxied URL of the icon for this footer
+ */
+
+ /**
+ * The footer of this embed
+ * @type {?MessageEmbedFooter}
+ */
+ this.footer = data.footer
+ ? {
+ text: data.footer.text,
+ iconURL: data.footer.iconURL ?? data.footer.icon_url,
+ proxyIconURL: data.footer.proxyIconURL ?? data.footer.proxy_icon_url,
+ }
+ : null;
+ }
+
+ /**
+ * The date displayed on this embed
+ * @type {?Date}
+ * @readonly
+ */
+ get createdAt() {
+ return this.timestamp ? new Date(this.timestamp) : null;
+ }
+
+ /**
+ * The hexadecimal version of the embed color, with a leading hash
+ * @type {?string}
+ * @readonly
+ */
+ get hexColor() {
+ return this.color ? `#${this.color.toString(16).padStart(6, '0')}` : null;
+ }
+
+ /**
+ * The accumulated length for the embed title, description, fields, footer text, and author name
+ * @type {number}
+ * @readonly
+ */
+ get length() {
+ return (
+ (this.title?.length ?? 0) +
+ (this.description?.length ?? 0) +
+ (this.fields.length >= 1
+ ? this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0)
+ : 0) +
+ (this.footer?.text.length ?? 0) +
+ (this.author?.name.length ?? 0)
+ );
+ }
+
+ /**
+ * Checks if this embed is equal to another one by comparing every single one of their properties.
+ * @param {MessageEmbed|APIEmbed} embed The embed to compare with
+ * @returns {boolean}
+ */
+ equals(embed) {
+ return (
+ this.type === embed.type &&
+ this.author?.name === embed.author?.name &&
+ this.author?.url === embed.author?.url &&
+ this.author?.iconURL === (embed.author?.iconURL ?? embed.author?.icon_url) &&
+ this.color === embed.color &&
+ this.title === embed.title &&
+ this.description === embed.description &&
+ this.url === embed.url &&
+ this.timestamp === embed.timestamp &&
+ this.fields.length === embed.fields.length &&
+ this.fields.every((field, i) => this._fieldEquals(field, embed.fields[i])) &&
+ this.footer?.text === embed.footer?.text &&
+ this.footer?.iconURL === (embed.footer?.iconURL ?? embed.footer?.icon_url) &&
+ this.image?.url === embed.image?.url &&
+ this.thumbnail?.url === embed.thumbnail?.url &&
+ this.video?.url === embed.video?.url &&
+ this.provider?.name === embed.provider?.name &&
+ this.provider?.url === embed.provider?.url
+ );
+ }
+
+ /**
+ * Compares two given embed fields to see if they are equal
+ * @param {EmbedFieldData} field The first field to compare
+ * @param {EmbedFieldData} other The second field to compare
+ * @returns {boolean}
+ * @private
+ */
+ _fieldEquals(field, other) {
+ return field.name === other.name && field.value === other.value && field.inline === other.inline;
+ }
+
+ /**
+ * Adds a field to the embed (max 25).
+ * @param {string} name The name of this field
+ * @param {string} value The value of this field
+ * @param {boolean} [inline=false] If this field will be displayed inline
+ * @returns {MessageEmbed}
+ */
+ addField(name, value, inline) {
+ return this.addFields({ name, value, inline });
+ }
+
+ /**
+ * Adds fields to the embed (max 25).
+ * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to add
+ * @returns {MessageEmbed}
+ */
+ addFields(...fields) {
+ this.fields.push(...this.constructor.normalizeFields(fields));
+ return this;
+ }
+
+ /**
+ * Removes, replaces, and inserts fields in the embed (max 25).
+ * @param {number} index The index to start at
+ * @param {number} deleteCount The number of fields to remove
+ * @param {...EmbedFieldData|EmbedFieldData[]} [fields] The replacing field objects
+ * @returns {MessageEmbed}
+ */
+ spliceFields(index, deleteCount, ...fields) {
+ this.fields.splice(index, deleteCount, ...this.constructor.normalizeFields(...fields));
+ return this;
+ }
+
+ /**
+ * Sets the embed's fields (max 25).
+ * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to set
+ * @returns {MessageEmbed}
+ */
+ setFields(...fields) {
+ this.spliceFields(0, this.fields.length, fields);
+ return this;
+ }
+
+ /**
+ * The options to provide for setting an author for a {@link MessageEmbed}.
+ * @typedef {Object} EmbedAuthorData
+ * @property {string} name The name of this author.
+ * @property {string} [url] The URL of this author.
+ * @property {string} [iconURL] The icon URL of this author.
+ */
+
+ /**
+ * Sets the author of this embed.
+ * @param {string|EmbedAuthorData|null} options The options to provide for the author.
+ * Provide `null` to remove the author data.
+ * @param {string} [deprecatedIconURL] The icon URL of this author.
+ * This parameter is **deprecated**. Use the `options` parameter instead.
+ * @param {string} [deprecatedURL] The URL of this author.
+ * This parameter is **deprecated**. Use the `options` parameter instead.
+ * @returns {MessageEmbed}
+ */
+ setAuthor(options, deprecatedIconURL, deprecatedURL) {
+ if (options === null) {
+ this.author = {};
+ return this;
+ }
+
+ if (typeof options === 'string') {
+ if (!deprecationEmittedForSetAuthor) {
+ process.emitWarning(
+ 'Passing strings for MessageEmbed#setAuthor is deprecated. Pass a sole object instead.',
+ 'DeprecationWarning',
+ );
+
+ deprecationEmittedForSetAuthor = true;
+ }
+
+ options = { name: options, url: deprecatedURL, iconURL: deprecatedIconURL };
+ }
+
+ const { name, url, iconURL } = options;
+ this.author = { name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'), url, iconURL };
+ return this;
+ }
+
+ /**
+ * Sets the color of this embed.
+ * @param {ColorResolvable} color The color of the embed
+ * @returns {MessageEmbed}
+ */
+ setColor(color) {
+ this.color = Util.resolveColor(color);
+ return this;
+ }
+
+ /**
+ * Sets the description of this embed.
+ * @param {string} description The description
+ * @returns {MessageEmbed}
+ */
+ setDescription(description) {
+ this.description = Util.verifyString(description, RangeError, 'EMBED_DESCRIPTION');
+ return this;
+ }
+
+ /**
+ * The options to provide for setting a footer for a {@link MessageEmbed}.
+ * @typedef {Object} EmbedFooterData
+ * @property {string} text The text of the footer.
+ * @property {string} [iconURL] The icon URL of the footer.
+ */
+
+ /**
+ * Sets the footer of this embed.
+ * @param {string|EmbedFooterData|null} options The options to provide for the footer.
+ * Provide `null` to remove the footer data.
+ * @param {string} [deprecatedIconURL] The icon URL of this footer.
+ * This parameter is **deprecated**. Use the `options` parameter instead.
+ * @returns {MessageEmbed}
+ */
+ setFooter(options, deprecatedIconURL) {
+ if (options === null) {
+ this.footer = {};
+ return this;
+ }
+
+ if (typeof options === 'string') {
+ if (!deprecationEmittedForSetFooter) {
+ process.emitWarning(
+ 'Passing strings for MessageEmbed#setFooter is deprecated. Pass a sole object instead.',
+ 'DeprecationWarning',
+ );
+
+ deprecationEmittedForSetFooter = true;
+ }
+
+ options = { text: options, iconURL: deprecatedIconURL };
+ }
+
+ const { text, iconURL } = options;
+ this.footer = { text: Util.verifyString(text, RangeError, 'EMBED_FOOTER_TEXT'), iconURL };
+ return this;
+ }
+
+ /**
+ * Sets the image of this embed.
+ * @param {string} url The URL of the image
+ * @returns {MessageEmbed}
+ */
+ setImage(url) {
+ this.image = { url };
+ return this;
+ }
+
+ /**
+ * Sets the thumbnail of this embed.
+ * @param {string} url The URL of the thumbnail
+ * @returns {MessageEmbed}
+ */
+ setThumbnail(url) {
+ this.thumbnail = { url };
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of this embed.
+ * @param {Date|number|null} [timestamp=Date.now()] The timestamp or date.
+ * If `null` then the timestamp will be unset (i.e. when editing an existing {@link MessageEmbed})
+ * @returns {MessageEmbed}
+ */
+ setTimestamp(timestamp = Date.now()) {
+ if (timestamp instanceof Date) timestamp = timestamp.getTime();
+ this.timestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Sets the title of this embed.
+ * @param {string} title The title
+ * @returns {MessageEmbed}
+ */
+ setTitle(title) {
+ this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE');
+ return this;
+ }
+
+ /**
+ * Sets the URL of this embed.
+ * @param {string} url The URL
+ * @returns {MessageEmbed}
+ */
+ setURL(url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Transforms the embed to a plain object.
+ * @returns {APIEmbed} The raw data of this embed
+ */
+ toJSON() {
+ return {
+ title: this.title,
+ type: 'rich',
+ description: this.description,
+ url: this.url,
+ timestamp: this.timestamp && new Date(this.timestamp),
+ color: this.color,
+ fields: this.fields,
+ thumbnail: this.thumbnail,
+ image: this.image,
+ author: this.author && {
+ name: this.author.name,
+ url: this.author.url,
+ icon_url: this.author.iconURL,
+ },
+ footer: this.footer && {
+ text: this.footer.text,
+ icon_url: this.footer.iconURL,
+ },
+ };
+ }
+
+ /**
+ * Normalizes field input and verifies strings.
+ * @param {string} name The name of the field
+ * @param {string} value The value of the field
+ * @param {boolean} [inline=false] Set the field to display inline
+ * @returns {EmbedField}
+ */
+ static normalizeField(name, value, inline = false) {
+ return {
+ name: Util.verifyString(name, RangeError, 'EMBED_FIELD_NAME', false),
+ value: Util.verifyString(value, RangeError, 'EMBED_FIELD_VALUE', false),
+ inline,
+ };
+ }
+
+ /**
+ * @typedef {Object} EmbedFieldData
+ * @property {string} name The name of this field
+ * @property {string} value The value of this field
+ * @property {boolean} [inline] If this field will be displayed inline
+ */
+
+ /**
+ * Normalizes field input and resolves strings.
+ * @param {...EmbedFieldData|EmbedFieldData[]} fields Fields to normalize
+ * @returns {EmbedField[]}
+ */
+ static normalizeFields(...fields) {
+ return fields
+ .flat(2)
+ .map(field =>
+ this.normalizeField(field.name, field.value, typeof field.inline === 'boolean' ? field.inline : false),
+ );
+ }
+}
+
+module.exports = MessageEmbed;
+
+/**
+ * @external APIEmbed
+ * @see {@link https://discord.com/developers/docs/resources/channel#embed-object}
+ */
diff --git a/src/structures/MessageMentions.js b/src/structures/MessageMentions.js
index 6f1588d..9b935f9 100644
--- a/src/structures/MessageMentions.js
+++ b/src/structures/MessageMentions.js
@@ -1,6 +1,7 @@
'use strict';
const { Collection } = require('@discordjs/collection');
+const { ChannelTypes } = require('../util/Constants');
const Util = require('../util/Util');
/**
@@ -111,11 +112,13 @@ class MessageMentions {
this.crosspostedChannels = new Collection(crosspostedChannels);
} else {
this.crosspostedChannels = new Collection();
+ const channelTypes = Object.keys(ChannelTypes);
for (const d of crosspostedChannels) {
+ const type = channelTypes[d.type];
this.crosspostedChannels.set(d.id, {
channelId: d.id,
guildId: d.guild_id,
- type: d.type,
+ type: type ?? 'UNKNOWN',
name: d.name,
});
}
@@ -170,35 +173,28 @@ class MessageMentions {
* @typedef {Object} MessageMentionsHasOptions
* @property {boolean} [ignoreDirect=false] Whether to ignore direct mentions to the item
* @property {boolean} [ignoreRoles=false] Whether to ignore role mentions to a guild member
- * @property {boolean} [ignoreRepliedUser=false] Whether to ignore replied user mention to an user
- * @property {boolean} [ignoreEveryone=false] Whether to ignore `@everyone`/`@here` mentions
+ * @property {boolean} [ignoreEveryone=false] Whether to ignore everyone/here mentions
*/
/**
- * Checks if a user, guild member, thread member, role, or channel is mentioned.
- * Takes into account user mentions, role mentions, channel mentions,
- * replied user mention, and `@everyone`/`@here` mentions.
+ * Checks if a user, guild member, role, or channel is mentioned.
+ * Takes into account user mentions, role mentions, and `@everyone`/`@here` mentions.
* @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for
* @param {MessageMentionsHasOptions} [options] The options for the check
* @returns {boolean}
*/
- has(data, { ignoreDirect = false, ignoreRoles = false, ignoreRepliedUser = false, ignoreEveryone = false } = {}) {
- const user = this.client.users.resolve(data);
- const role = this.guild?.roles.resolve(data);
- const channel = this.client.channels.resolve(data);
-
- if (!ignoreRepliedUser && this.users.has(this.repliedUser?.id) && this.repliedUser?.id === user?.id) return true;
- if (!ignoreDirect) {
- if (this.users.has(user?.id)) return true;
- if (this.roles.has(role?.id)) return true;
- if (this.channels.has(channel?.id)) return true;
+ has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) {
+ if (!ignoreEveryone && this.everyone) return true;
+ const { GuildMember } = require('./GuildMember');
+ if (!ignoreRoles && data instanceof GuildMember) {
+ for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true;
}
- if (user && !ignoreEveryone && this.everyone) return true;
- if (!ignoreRoles) {
- const member = this.guild?.members.resolve(data);
- if (member) {
- for (const mentionedRole of this.roles.values()) if (member.roles.cache.has(mentionedRole.id)) return true;
- }
+
+ if (!ignoreDirect) {
+ const id =
+ this.guild?.roles.resolveId(data) ?? this.client.channels.resolveId(data) ?? this.client.users.resolveId(data);
+
+ return typeof id === 'string' && (this.users.has(id) || this.channels.has(id) || this.roles.has(id));
}
return false;
diff --git a/src/structures/MessagePayload.js b/src/structures/MessagePayload.js
index 0713a2a..339b8f9 100644
--- a/src/structures/MessagePayload.js
+++ b/src/structures/MessagePayload.js
@@ -1,304 +1,270 @@
'use strict';
const { Buffer } = require('node:buffer');
-const { BaseMessageComponent, MessageEmbed } = require('discord.js');
-const { MessageFlags } = require('discord-api-types/v9');
+const BaseMessageComponent = require('./BaseMessageComponent');
+const MessageEmbed = require('./MessageEmbed');
const { RangeError } = require('../errors');
const DataResolver = require('../util/DataResolver');
-const MessageFlagsBitField = require('../util/MessageFlagsBitField');
+const MessageFlags = require('../util/MessageFlags');
const Util = require('../util/Util');
/**
* Represents a message to be sent to the API.
*/
class MessagePayload {
- /**
- * @param {MessageTarget} target The target for this message to be sent to
- * @param {MessageOptions|WebhookMessageOptions} options Options passed in from send
- */
- constructor(target, options) {
- /**
- * The target for this message to be sent to
- * @type {MessageTarget}
- */
- this.target = target;
+ /**
+ * @param {MessageTarget} target The target for this message to be sent to
+ * @param {MessageOptions|WebhookMessageOptions} options Options passed in from send
+ */
+ constructor(target, options) {
+ /**
+ * The target for this message to be sent to
+ * @type {MessageTarget}
+ */
+ this.target = target;
- /**
- * Options passed in from send
- * @type {MessageOptions|WebhookMessageOptions}
- */
- this.options = options;
+ /**
+ * Options passed in from send
+ * @type {MessageOptions|WebhookMessageOptions}
+ */
+ this.options = options;
- /**
- * Body sendable to the API
- * @type {?APIMessage}
- */
- this.body = null;
+ /**
+ * Data sendable to the API
+ * @type {?APIMessage}
+ */
+ this.data = null;
- /**
- * Files sendable to the API
- * @type {?RawFile[]}
- */
- this.files = null;
- }
+ /**
+ * @typedef {Object} MessageFile
+ * @property {Buffer|string|Stream} attachment The original attachment that generated this file
+ * @property {string} name The name of this file
+ * @property {Buffer|Stream} file The file to be sent to the API
+ */
- /**
- * Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
- * @type {boolean}
- * @readonly
- */
- get isWebhook() {
- const Webhook = require('./Webhook');
- const WebhookClient = require('../client/WebhookClient');
- return (
- this.target instanceof Webhook || this.target instanceof WebhookClient
- );
- }
+ /**
+ * Files sendable to the API
+ * @type {?MessageFile[]}
+ */
+ this.files = null;
+ }
- /**
- * Whether or not the target is a {@link User}
- * @type {boolean}
- * @readonly
- */
- get isUser() {
- const User = require('./User');
- const { GuildMember } = require('./GuildMember');
- return this.target instanceof User || this.target instanceof GuildMember;
- }
+ /**
+ * Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
+ * @type {boolean}
+ * @readonly
+ */
+ get isWebhook() {
+ const Webhook = require('./Webhook');
+ const WebhookClient = require('../client/WebhookClient');
+ return this.target instanceof Webhook || this.target instanceof WebhookClient;
+ }
- /**
- * Whether or not the target is a {@link Message}
- * @type {boolean}
- * @readonly
- */
- get isMessage() {
- const { Message } = require('./Message');
- return this.target instanceof Message;
- }
+ /**
+ * Whether or not the target is a {@link User}
+ * @type {boolean}
+ * @readonly
+ */
+ get isUser() {
+ const User = require('./User');
+ const { GuildMember } = require('./GuildMember');
+ return this.target instanceof User || this.target instanceof GuildMember;
+ }
- /**
- * Whether or not the target is a {@link MessageManager}
- * @type {boolean}
- * @readonly
- */
- get isMessageManager() {
- const MessageManager = require('../managers/MessageManager');
- return this.target instanceof MessageManager;
- }
+ /**
+ * Whether or not the target is a {@link Message}
+ * @type {boolean}
+ * @readonly
+ */
+ get isMessage() {
+ const { Message } = require('./Message');
+ return this.target instanceof Message;
+ }
- /**
- * Whether or not the target is an {@link Interaction} or an {@link InteractionWebhook}
- * @type {boolean}
- * @readonly
- */
- get isInteraction() {
- const Interaction = require('./Interaction');
- const InteractionWebhook = require('./InteractionWebhook');
- return (
- this.target instanceof Interaction ||
- this.target instanceof InteractionWebhook
- );
- }
+ /**
+ * Whether or not the target is a {@link MessageManager}
+ * @type {boolean}
+ * @readonly
+ */
+ get isMessageManager() {
+ const MessageManager = require('../managers/MessageManager');
+ return this.target instanceof MessageManager;
+ }
- /**
- * Makes the content of this message.
- * @returns {?string}
- */
- makeContent() {
- let content;
- if (this.options.content === null) {
- content = '';
- } else if (typeof this.options.content !== 'undefined') {
- content = Util.verifyString(
- this.options.content,
- RangeError,
- 'MESSAGE_CONTENT_TYPE',
- false,
- );
- }
+ /**
+ * Whether or not the target is an {@link Interaction} or an {@link InteractionWebhook}
+ * @type {boolean}
+ * @readonly
+ */
+ get isInteraction() {
+ const Interaction = require('./Interaction');
+ const InteractionWebhook = require('./InteractionWebhook');
+ return this.target instanceof Interaction || this.target instanceof InteractionWebhook;
+ }
- return content;
- }
- /**
- * Resolves the body.
- * @returns {MessagePayload}
- */
- resolveBody() {
- if (this.data) return this;
- const isInteraction = this.isInteraction;
- const isWebhook = this.isWebhook;
+ /**
+ * Makes the content of this message.
+ * @returns {?string}
+ */
+ makeContent() {
+ let content;
+ if (this.options.content === null) {
+ content = '';
+ } else if (typeof this.options.content !== 'undefined') {
+ content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', false);
+ }
- const content = this.makeContent();
- const tts = Boolean(this.options.tts);
+ return content;
+ }
- let nonce;
- if (typeof this.options.nonce !== 'undefined') {
- nonce = this.options.nonce;
- // eslint-disable-next-line max-len
- if (
- typeof nonce === 'number'
- ? !Number.isInteger(nonce)
- : typeof nonce !== 'string'
- ) {
- throw new RangeError('MESSAGE_NONCE_TYPE');
- }
- }
+ /**
+ * Resolves data.
+ * @returns {MessagePayload}
+ */
+ resolveData() {
+ if (this.data) return this;
+ const isInteraction = this.isInteraction;
+ const isWebhook = this.isWebhook;
- const components = this.options.components?.map((c) =>
- BaseMessageComponent.create(c).toJSON(),
- );
+ const content = this.makeContent();
+ const tts = Boolean(this.options.tts);
- let username;
- let avatarURL;
- if (isWebhook) {
- username = this.options.username ?? this.target.name;
- if (this.options.avatarURL) avatarURL = this.options.avatarURL;
- }
+ let nonce;
+ if (typeof this.options.nonce !== 'undefined') {
+ nonce = this.options.nonce;
+ // eslint-disable-next-line max-len
+ if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') {
+ throw new RangeError('MESSAGE_NONCE_TYPE');
+ }
+ }
- let flags;
- if (
- typeof this.options.flags !== 'undefined' ||
- (this.isMessage && typeof this.options.reply === 'undefined') ||
- this.isMessageManager
- ) {
- flags =
- // eslint-disable-next-line eqeqeq
- this.options.flags != null
- ? new MessageFlagsBitField(this.options.flags).bitfield
- : this.target.flags?.bitfield;
- }
+ const components = this.options.components?.map(c => BaseMessageComponent.create(c).toJSON());
- if (isInteraction && this.options.ephemeral) {
- flags |= MessageFlags.Ephemeral;
- }
+ let username;
+ let avatarURL;
+ if (isWebhook) {
+ username = this.options.username ?? this.target.name;
+ if (this.options.avatarURL) avatarURL = this.options.avatarURL;
+ }
- let allowedMentions =
- typeof this.options.allowedMentions === 'undefined'
- ? this.target.client.options.allowedMentions
- : this.options.allowedMentions;
+ let flags;
+ if (this.isMessage || this.isMessageManager) {
+ // eslint-disable-next-line eqeqeq
+ flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags?.bitfield;
+ } else if (isInteraction && this.options.ephemeral) {
+ flags = MessageFlags.FLAGS.EPHEMERAL;
+ }
- if (allowedMentions) {
- allowedMentions = Util.cloneObject(allowedMentions);
- allowedMentions.replied_user = allowedMentions.repliedUser;
- delete allowedMentions.repliedUser;
- }
+ let allowedMentions =
+ typeof this.options.allowedMentions === 'undefined'
+ ? this.target.client.options.allowedMentions
+ : this.options.allowedMentions;
- let message_reference;
- if (typeof this.options.reply === 'object') {
- const reference = this.options.reply.messageReference;
- const message_id = this.isMessage
- ? reference.id ?? reference
- : this.target.messages.resolveId(reference);
- if (message_id) {
- message_reference = {
- message_id,
- fail_if_not_exists:
- this.options.reply.failIfNotExists ??
- this.target.client.options.failIfNotExists,
- };
- }
- }
+ if (allowedMentions) {
+ allowedMentions = Util.cloneObject(allowedMentions);
+ allowedMentions.replied_user = allowedMentions.repliedUser;
+ delete allowedMentions.repliedUser;
+ }
- const attachments = this.options.files?.map((file, index) => ({
- id: index.toString(),
- description: file.description,
- }));
- if (Array.isArray(this.options.attachments)) {
- this.options.attachments.push(...(attachments ?? []));
- } else {
- this.options.attachments = attachments;
- }
+ let message_reference;
+ if (typeof this.options.reply === 'object') {
+ const reference = this.options.reply.messageReference;
+ const message_id = this.isMessage ? reference.id ?? reference : this.target.messages.resolveId(reference);
+ if (message_id) {
+ message_reference = {
+ message_id,
+ fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists,
+ };
+ }
+ }
- this.body = {
- content,
- tts,
- nonce,
- embeds: this.options.embeds?.map((embed) =>
- new MessageEmbed(embed).toJSON(),
- ),
- components,
- username,
- avatar_url: avatarURL,
- allowed_mentions:
- typeof content === 'undefined' &&
- typeof message_reference === 'undefined'
- ? undefined
- : allowedMentions,
- flags,
- message_reference,
- attachments: this.options.attachments,
- sticker_ids: this.options.stickers?.map(
- (sticker) => sticker.id ?? sticker,
- ),
- };
- return this;
- }
+ const attachments = this.options.files?.map((file, index) => ({
+ id: index.toString(),
+ description: file.description,
+ }));
+ if (Array.isArray(this.options.attachments)) {
+ this.options.attachments.push(...(attachments ?? []));
+ } else {
+ this.options.attachments = attachments;
+ }
- /**
- * Resolves files.
- * @returns {Promise}
- */
- async resolveFiles() {
- if (this.files) return this;
+ this.data = {
+ content,
+ tts,
+ nonce,
+ embeds: this.options.embeds?.map(embed => new MessageEmbed(embed).toJSON()),
+ components,
+ username,
+ avatar_url: avatarURL,
+ allowed_mentions:
+ typeof content === 'undefined' && typeof message_reference === 'undefined' ? undefined : allowedMentions,
+ flags,
+ message_reference,
+ attachments: this.options.attachments,
+ sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
+ };
+ return this;
+ }
- this.files = await Promise.all(
- this.options.files?.map((file) => this.constructor.resolveFile(file)) ??
- [],
- );
- return this;
- }
+ /**
+ * Resolves files.
+ * @returns {Promise}
+ */
+ async resolveFiles() {
+ if (this.files) return this;
- /**
- * Resolves a single file into an object sendable to the API.
- * @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file
- * @returns {Promise}
- */
- static async resolveFile(fileLike) {
- let attachment;
- let name;
+ this.files = await Promise.all(this.options.files?.map(file => this.constructor.resolveFile(file)) ?? []);
+ return this;
+ }
- const findName = (thing) => {
- if (typeof thing === 'string') {
- return Util.basename(thing);
- }
+ /**
+ * Resolves a single file into an object sendable to the API.
+ * @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file
+ * @returns {Promise}
+ */
+ static async resolveFile(fileLike) {
+ let attachment;
+ let name;
- if (thing.path) {
- return Util.basename(thing.path);
- }
+ const findName = thing => {
+ if (typeof thing === 'string') {
+ return Util.basename(thing);
+ }
- return 'file.jpg';
- };
+ if (thing.path) {
+ return Util.basename(thing.path);
+ }
- const ownAttachment =
- typeof fileLike === 'string' ||
- fileLike instanceof Buffer ||
- typeof fileLike.pipe === 'function';
- if (ownAttachment) {
- attachment = fileLike;
- name = findName(attachment);
- } else {
- attachment = fileLike.attachment;
- name = fileLike.name ?? findName(attachment);
- }
+ return 'file.jpg';
+ };
- const resource = await DataResolver.resolveFile(attachment);
- return { attachment, name, file: resource };
- }
- /**
- * Creates a {@link MessagePayload} from user-level arguments.
- * @param {MessageTarget} target Target to send to
- * @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use
- * @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto specified options
- * @returns {MessagePayload}
- */
- static create(target, options, extra = {}) {
- return new this(
- target,
- typeof options !== 'object' || options === null
- ? { content: options, ...extra }
- : { ...options, ...extra },
- );
- }
+ const ownAttachment =
+ typeof fileLike === 'string' || fileLike instanceof Buffer || typeof fileLike.pipe === 'function';
+ if (ownAttachment) {
+ attachment = fileLike;
+ name = findName(attachment);
+ } else {
+ attachment = fileLike.attachment;
+ name = fileLike.name ?? findName(attachment);
+ }
+
+ const resource = await DataResolver.resolveFile(attachment);
+ return { attachment, name, file: resource };
+ }
+
+ /**
+ * Creates a {@link MessagePayload} from user-level arguments.
+ * @param {MessageTarget} target Target to send to
+ * @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use
+ * @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto specified options
+ * @returns {MessagePayload}
+ */
+ static create(target, options, extra = {}) {
+ return new this(
+ target,
+ typeof options !== 'object' || options === null ? { content: options, ...extra } : { ...options, ...extra },
+ );
+ }
}
module.exports = MessagePayload;
@@ -313,8 +279,3 @@ module.exports = MessagePayload;
* @external APIMessage
* @see {@link https://discord.com/developers/docs/resources/channel#message-object}
*/
-
-/**
- * @external RawFile
- * @see {@link https://discord.js.org/#/docs/rest/main/typedef/RawFile}
- */
\ No newline at end of file
diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js
index 729b284..8a294be 100644
--- a/src/structures/MessageReaction.js
+++ b/src/structures/MessageReaction.js
@@ -1,6 +1,5 @@
'use strict';
-const { Routes } = require('discord-api-types/v9');
const GuildEmoji = require('./GuildEmoji');
const ReactionEmoji = require('./ReactionEmoji');
const ReactionUserManager = require('../managers/ReactionUserManager');
@@ -57,7 +56,11 @@ class MessageReaction {
* @returns {Promise}
*/
async remove() {
- await this.client.api.channels(this.message.channelId).messages(this.message.id).reactions(this._emoji.identifier).delete()
+ await this.client.api
+ .channels(this.message.channelId)
+ .messages(this.message.id)
+ .reactions(this._emoji.identifier)
+ .delete();
return this;
}
@@ -111,7 +114,7 @@ class MessageReaction {
if (this.partial) return;
this.users.cache.set(user.id, user);
if (!this.me || user.id !== this.message.client.user.id || this.count === 0) this.count++;
- this.me ||= user.id === this.message.client.user.id;
+ this.me ??= user.id === this.message.client.user.id;
}
_remove(user) {
diff --git a/src/structures/MessageSelectMenu.js b/src/structures/MessageSelectMenu.js
new file mode 100644
index 00000000..ae1e7a6
--- /dev/null
+++ b/src/structures/MessageSelectMenu.js
@@ -0,0 +1,212 @@
+'use strict';
+
+const BaseMessageComponent = require('./BaseMessageComponent');
+const { MessageComponentTypes } = require('../util/Constants');
+const Util = require('../util/Util');
+
+/**
+ * Represents a select menu message component
+ * @extends {BaseMessageComponent}
+ */
+class MessageSelectMenu extends BaseMessageComponent {
+ /**
+ * @typedef {BaseMessageComponentOptions} MessageSelectMenuOptions
+ * @property {string} [customId] A unique string to be sent in the interaction when clicked
+ * @property {string} [placeholder] Custom placeholder text to display when nothing is selected
+ * @property {number} [minValues] The minimum number of selections required
+ * @property {number} [maxValues] The maximum number of selections allowed
+ * @property {MessageSelectOption[]} [options] Options for the select menu
+ * @property {boolean} [disabled=false] Disables the select menu to prevent interactions
+ */
+
+ /**
+ * @typedef {Object} MessageSelectOption
+ * @property {string} label The text to be displayed on this option
+ * @property {string} value The value to be sent for this option
+ * @property {?string} description Optional description to show for this option
+ * @property {?RawEmoji} emoji Emoji to display for this option
+ * @property {boolean} default Render this option as the default selection
+ */
+
+ /**
+ * @typedef {Object} MessageSelectOptionData
+ * @property {string} label The text to be displayed on this option
+ * @property {string} value The value to be sent for this option
+ * @property {string} [description] Optional description to show for this option
+ * @property {EmojiIdentifierResolvable} [emoji] Emoji to display for this option
+ * @property {boolean} [default] Render this option as the default selection
+ */
+
+ /**
+ * @param {MessageSelectMenu|MessageSelectMenuOptions} [data={}] MessageSelectMenu to clone or raw data
+ */
+ constructor(data = {}) {
+ super({ type: 'SELECT_MENU' });
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * A unique string to be sent in the interaction when clicked
+ * @type {?string}
+ */
+ this.customId = data.custom_id ?? data.customId ?? null;
+
+ /**
+ * Custom placeholder text to display when nothing is selected
+ * @type {?string}
+ */
+ this.placeholder = data.placeholder ?? null;
+
+ /**
+ * The minimum number of selections required
+ * @type {?number}
+ */
+ this.minValues = data.min_values ?? data.minValues ?? null;
+
+ /**
+ * The maximum number of selections allowed
+ * @type {?number}
+ */
+ this.maxValues = data.max_values ?? data.maxValues ?? null;
+
+ /**
+ * Options for the select menu
+ * @type {MessageSelectOption[]}
+ */
+ this.options = this.constructor.normalizeOptions(data.options ?? []);
+
+ /**
+ * Whether this select menu is currently disabled
+ * @type {boolean}
+ */
+ this.disabled = data.disabled ?? false;
+ }
+
+ /**
+ * Sets the custom id of this select menu
+ * @param {string} customId A unique string to be sent in the interaction when clicked
+ * @returns {MessageSelectMenu}
+ */
+ setCustomId(customId) {
+ this.customId = Util.verifyString(customId, RangeError, 'SELECT_MENU_CUSTOM_ID');
+ return this;
+ }
+
+ /**
+ * Sets the interactive status of the select menu
+ * @param {boolean} [disabled=true] Whether this select menu should be disabled
+ * @returns {MessageSelectMenu}
+ */
+ setDisabled(disabled = true) {
+ this.disabled = disabled;
+ return this;
+ }
+
+ /**
+ * Sets the maximum number of selections allowed for this select menu
+ * @param {number} maxValues Number of selections to be allowed
+ * @returns {MessageSelectMenu}
+ */
+ setMaxValues(maxValues) {
+ this.maxValues = maxValues;
+ return this;
+ }
+
+ /**
+ * Sets the minimum number of selections required for this select menu
+ * This will default the maxValues to the number of options, unless manually set
+ * @param {number} minValues Number of selections to be required
+ * @returns {MessageSelectMenu}
+ */
+ setMinValues(minValues) {
+ this.minValues = minValues;
+ return this;
+ }
+
+ /**
+ * Sets the placeholder of this select menu
+ * @param {string} placeholder Custom placeholder text to display when nothing is selected
+ * @returns {MessageSelectMenu}
+ */
+ setPlaceholder(placeholder) {
+ this.placeholder = Util.verifyString(placeholder, RangeError, 'SELECT_MENU_PLACEHOLDER');
+ return this;
+ }
+
+ /**
+ * Adds options to the select menu.
+ * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to add
+ * @returns {MessageSelectMenu}
+ */
+ addOptions(...options) {
+ this.options.push(...this.constructor.normalizeOptions(options));
+ return this;
+ }
+
+ /**
+ * Sets the options of the select menu.
+ * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to set
+ * @returns {MessageSelectMenu}
+ */
+ setOptions(...options) {
+ this.spliceOptions(0, this.options.length, options);
+ return this;
+ }
+
+ /**
+ * Removes, replaces, and inserts options in the select menu.
+ * @param {number} index The index to start at
+ * @param {number} deleteCount The number of options to remove
+ * @param {...MessageSelectOptionData|MessageSelectOptionData[]} [options] The replacing option objects
+ * @returns {MessageSelectMenu}
+ */
+ spliceOptions(index, deleteCount, ...options) {
+ this.options.splice(index, deleteCount, ...this.constructor.normalizeOptions(...options));
+ return this;
+ }
+
+ /**
+ * Transforms the select menu into a plain object
+ * @returns {APIMessageSelectMenu} The raw data of this select menu
+ */
+ toJSON() {
+ return {
+ custom_id: this.customId,
+ disabled: this.disabled,
+ placeholder: this.placeholder,
+ min_values: this.minValues,
+ max_values: this.maxValues ?? (this.minValues ? this.options.length : undefined),
+ options: this.options,
+ type: typeof this.type === 'string' ? MessageComponentTypes[this.type] : this.type,
+ };
+ }
+
+ /**
+ * Normalizes option input and resolves strings and emojis.
+ * @param {MessageSelectOptionData} option The select menu option to normalize
+ * @returns {MessageSelectOption}
+ */
+ static normalizeOption(option) {
+ let { label, value, description, emoji } = option;
+
+ label = Util.verifyString(label, RangeError, 'SELECT_OPTION_LABEL');
+ value = Util.verifyString(value, RangeError, 'SELECT_OPTION_VALUE');
+ emoji = emoji ? Util.resolvePartialEmoji(emoji) : null;
+ description = description ? Util.verifyString(description, RangeError, 'SELECT_OPTION_DESCRIPTION', true) : null;
+
+ return { label, value, description, emoji, default: option.default ?? false };
+ }
+
+ /**
+ * Normalizes option input and resolves strings and emojis.
+ * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The select menu options to normalize
+ * @returns {MessageSelectOption[]}
+ */
+ static normalizeOptions(...options) {
+ return options.flat(Infinity).map(option => this.normalizeOption(option));
+ }
+}
+
+module.exports = MessageSelectMenu;
diff --git a/src/structures/NewsChannel.js b/src/structures/NewsChannel.js
index 3833ef0..e2ef0a8 100644
--- a/src/structures/NewsChannel.js
+++ b/src/structures/NewsChannel.js
@@ -1,6 +1,5 @@
'use strict';
-const { Routes } = require('discord-api-types/v9');
const BaseGuildTextChannel = require('./BaseGuildTextChannel');
const { Error } = require('../errors');
@@ -15,7 +14,7 @@ class NewsChannel extends BaseGuildTextChannel {
* @param {string} [reason] Reason for creating the webhook
* @returns {Promise}
* @example
- * if (channel.type === ChannelType.GuildNews) {
+ * if (channel.type === 'GUILD_NEWS') {
* channel.addFollower('222197033908436994', 'Important announcements')
* .then(() => console.log('Added follower'))
* .catch(console.error);
@@ -24,7 +23,7 @@ class NewsChannel extends BaseGuildTextChannel {
async addFollower(channel, reason) {
const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new Error('GUILD_CHANNEL_RESOLVE');
- await this.client.api.channels(this.id).followers.post({ body: { webhook_channel_id: channelId }, reason });
+ await this.client.api.channels(this.id).followers.post({ data: { webhook_channel_id: channelId }, reason });
return this;
}
}
diff --git a/src/structures/OAuth2Guild.js b/src/structures/OAuth2Guild.js
index d5104ac..3f44ad4 100644
--- a/src/structures/OAuth2Guild.js
+++ b/src/structures/OAuth2Guild.js
@@ -1,7 +1,7 @@
'use strict';
const BaseGuild = require('./BaseGuild');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const Permissions = require('../util/Permissions');
/**
* A partial guild received when using {@link GuildManager#fetch} to fetch multiple guilds.
@@ -19,9 +19,9 @@ class OAuth2Guild extends BaseGuild {
/**
* The permissions that the client user has in this guild
- * @type {Readonly}
+ * @type {Readonly}
*/
- this.permissions = new PermissionsBitField(BigInt(data.permissions)).freeze();
+ this.permissions = new Permissions(BigInt(data.permissions)).freeze();
}
}
diff --git a/src/structures/PartialGroupDMChannel.js b/src/structures/PartialGroupDMChannel.js
index f604e72..715d0b6 100644
--- a/src/structures/PartialGroupDMChannel.js
+++ b/src/structures/PartialGroupDMChannel.js
@@ -38,11 +38,11 @@ class PartialGroupDMChannel extends Channel {
/**
* The URL to this channel's icon.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- iconURL(options = {}) {
- return this.icon && this.client.rest.cdn.channelIcon(this.id, this.icon, options);
+ iconURL({ format, size } = {}) {
+ return this.icon && this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size);
}
delete() {
diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js
index 01d141e..017bf26 100644
--- a/src/structures/PermissionOverwrites.js
+++ b/src/structures/PermissionOverwrites.js
@@ -1,10 +1,10 @@
'use strict';
-const { OverwriteType } = require('discord-api-types/v9');
const Base = require('./Base');
const { Role } = require('./Role');
const { TypeError } = require('../errors');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const { OverwriteTypes } = require('../util/Constants');
+const Permissions = require('../util/Permissions');
/**
* Represents a permission overwrite for a role or member in a guild channel.
@@ -37,23 +37,23 @@ class PermissionOverwrites extends Base {
* The type of this overwrite
* @type {OverwriteType}
*/
- this.type = data.type;
+ this.type = typeof data.type === 'number' ? OverwriteTypes[data.type] : data.type;
}
if ('deny' in data) {
/**
* The permissions that are denied for the user or role.
- * @type {Readonly}
+ * @type {Readonly}
*/
- this.deny = new PermissionsBitField(BigInt(data.deny)).freeze();
+ this.deny = new Permissions(BigInt(data.deny)).freeze();
}
if ('allow' in data) {
/**
* The permissions that are allowed for the user or role.
- * @type {Readonly}
+ * @type {Readonly}
*/
- this.allow = new PermissionsBitField(BigInt(data.allow)).freeze();
+ this.allow = new Permissions(BigInt(data.allow)).freeze();
}
}
@@ -71,7 +71,7 @@ class PermissionOverwrites extends Base {
* .catch(console.error);
*/
async edit(options, reason) {
- await this.channel.permissionOverwrites.upsert(this.id, options, { type: this.type, reason }, this);
+ await this.channel.permissionOverwrites.upsert(this.id, options, { type: OverwriteTypes[this.type], reason }, this);
return this;
}
@@ -88,7 +88,7 @@ class PermissionOverwrites extends Base {
toJSON() {
return {
id: this.id,
- type: this.type,
+ type: OverwriteTypes[this.type],
allow: this.allow,
deny: this.deny,
};
@@ -98,9 +98,9 @@ class PermissionOverwrites extends Base {
* An object mapping permission flags to `true` (enabled), `null` (unset) or `false` (disabled).
* ```js
* {
- * 'SendMessages': true,
- * 'EmbedLinks': null,
- * 'AttachFiles': false,
+ * 'SEND_MESSAGES': true,
+ * 'EMBED_LINKS': null,
+ * 'ATTACH_FILES': false,
* }
* ```
* @typedef {Object} PermissionOverwriteOptions
@@ -108,8 +108,8 @@ class PermissionOverwrites extends Base {
/**
* @typedef {Object} ResolvedOverwriteOptions
- * @property {PermissionsBitField} allow The allowed permissions
- * @property {PermissionsBitField} deny The denied permissions
+ * @property {Permissions} allow The allowed permissions
+ * @property {Permissions} deny The denied permissions
*/
/**
@@ -119,8 +119,8 @@ class PermissionOverwrites extends Base {
* @returns {ResolvedOverwriteOptions}
*/
static resolveOverwriteOptions(options, { allow, deny } = {}) {
- allow = new PermissionsBitField(allow);
- deny = new PermissionsBitField(deny);
+ allow = new Permissions(allow);
+ deny = new Permissions(deny);
for (const [perm, value] of Object.entries(options)) {
if (value === true) {
@@ -171,24 +171,24 @@ class PermissionOverwrites extends Base {
*/
static resolve(overwrite, guild) {
if (overwrite instanceof this) return overwrite.toJSON();
- if (typeof overwrite.id === 'string' && overwrite.type in OverwriteType) {
+ if (typeof overwrite.id === 'string' && overwrite.type in OverwriteTypes) {
return {
id: overwrite.id,
- type: overwrite.type,
- allow: PermissionsBitField.resolve(overwrite.allow ?? PermissionsBitField.defaultBit).toString(),
- deny: PermissionsBitField.resolve(overwrite.deny ?? PermissionsBitField.defaultBit).toString(),
+ type: OverwriteTypes[overwrite.type],
+ allow: Permissions.resolve(overwrite.allow ?? Permissions.defaultBit).toString(),
+ deny: Permissions.resolve(overwrite.deny ?? Permissions.defaultBit).toString(),
};
}
const userOrRole = guild.roles.resolve(overwrite.id) ?? guild.client.users.resolve(overwrite.id);
if (!userOrRole) throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role');
- const type = userOrRole instanceof Role ? OverwriteType.Role : OverwriteType.Member;
+ const type = userOrRole instanceof Role ? OverwriteTypes.role : OverwriteTypes.member;
return {
id: userOrRole.id,
type,
- allow: PermissionsBitField.resolve(overwrite.allow ?? PermissionsBitField.defaultBit).toString(),
- deny: PermissionsBitField.resolve(overwrite.deny ?? PermissionsBitField.defaultBit).toString(),
+ allow: Permissions.resolve(overwrite.allow ?? Permissions.defaultBit).toString(),
+ deny: Permissions.resolve(overwrite.deny ?? Permissions.defaultBit).toString(),
};
}
}
diff --git a/src/structures/Presence.js b/src/structures/Presence.js
index e7da989..ce7620b 100644
--- a/src/structures/Presence.js
+++ b/src/structures/Presence.js
@@ -2,7 +2,8 @@
const Base = require('./Base');
const { Emoji } = require('./Emoji');
-const ActivityFlagsBitField = require('../util/ActivityFlagsBitField');
+const ActivityFlags = require('../util/ActivityFlags');
+const { ActivityTypes } = require('../util/Constants');
const Util = require('../util/Util');
/**
@@ -167,7 +168,7 @@ class Activity {
* The activity status's type
* @type {ActivityType}
*/
- this.type = data.type;
+ this.type = typeof data.type === 'number' ? ActivityTypes[data.type] : data.type;
/**
* If the activity is being streamed, a link to the stream
@@ -244,9 +245,9 @@ class Activity {
/**
* Flags that describe the activity
- * @type {Readonly}
+ * @type {Readonly}
*/
- this.flags = new ActivityFlagsBitField(data.flags).freeze();
+ this.flags = new ActivityFlags(data.flags).freeze();
/**
* Emoji for a custom activity
@@ -270,7 +271,7 @@ class Activity {
* Creation date of the activity
* @type {number}
*/
- this.createdTimestamp = Date.parse(data.created_at);
+ this.createdTimestamp = new Date(data.created_at).getTime();
}
/**
@@ -346,48 +347,35 @@ class RichPresenceAssets {
/**
* Gets the URL of the small image asset
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options] Options for the image URL
* @returns {?string}
*/
- smallImageURL(options = {}) {
- if (!this.smallImage) return null;
- if (this.smallImage.includes(':')) {
- const [platform, id] = this.smallImage.split(':');
- switch (platform) {
- case 'mp':
- return `https://media.discordapp.net/${id}`;
- default:
- return null;
- }
- }
-
- return this.activity.presence.client.rest.cdn.appAsset(this.activity.applicationId, this.smallImage, options);
+ smallImageURL({ format, size } = {}) {
+ return (
+ this.smallImage &&
+ this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationId, this.smallImage, {
+ format,
+ size,
+ })
+ );
}
/**
* Gets the URL of the large image asset
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options] Options for the image URL
* @returns {?string}
*/
- largeImageURL(options = {}) {
+ largeImageURL({ format, size } = {}) {
if (!this.largeImage) return null;
- if (this.largeImage.includes(':')) {
- const [platform, id] = this.largeImage.split(':');
- switch (platform) {
- case 'mp':
- return `https://media.discordapp.net/${id}`;
- case 'spotify':
- return `https://i.scdn.co/image/${id}`;
- case 'youtube':
- return `https://i.ytimg.com/vi/${id}/hqdefault_live.jpg`;
- case 'twitch':
- return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${id}.png`;
- default:
- return null;
- }
+ if (/^spotify:/.test(this.largeImage)) {
+ return `https://i.scdn.co/image/${this.largeImage.slice(8)}`;
+ } else if (/^twitch:/.test(this.largeImage)) {
+ return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${this.largeImage.slice(7)}.png`;
}
-
- return this.activity.presence.client.rest.cdn.appAsset(this.activity.applicationId, this.largeImage, options);
+ return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationId, this.largeImage, {
+ format,
+ size,
+ });
}
}
diff --git a/src/structures/ReactionCollector.js b/src/structures/ReactionCollector.js
index 0c0b9e0..1b0b5f5 100644
--- a/src/structures/ReactionCollector.js
+++ b/src/structures/ReactionCollector.js
@@ -2,7 +2,7 @@
const { Collection } = require('@discordjs/collection');
const Collector = require('./interfaces/Collector');
-const Events = require('../util/Events');
+const { Events } = require('../util/Constants');
/**
* @typedef {CollectorOptions} ReactionCollectorOptions
@@ -57,24 +57,24 @@ class ReactionCollector extends Collector {
};
this.client.incrementMaxListeners();
- this.client.on(Events.MessageReactionAdd, this.handleCollect);
- this.client.on(Events.MessageReactionRemove, this.handleDispose);
- this.client.on(Events.MessageReactionRemoveAll, this.empty);
- this.client.on(Events.MessageDelete, this._handleMessageDeletion);
- this.client.on(Events.MessageBulkDelete, bulkDeleteListener);
- this.client.on(Events.ChannelDelete, this._handleChannelDeletion);
- this.client.on(Events.ThreadDelete, this._handleThreadDeletion);
- this.client.on(Events.GuildDelete, this._handleGuildDeletion);
+ this.client.on(Events.MESSAGE_REACTION_ADD, this.handleCollect);
+ this.client.on(Events.MESSAGE_REACTION_REMOVE, this.handleDispose);
+ this.client.on(Events.MESSAGE_REACTION_REMOVE_ALL, this.empty);
+ this.client.on(Events.MESSAGE_DELETE, this._handleMessageDeletion);
+ this.client.on(Events.MESSAGE_BULK_DELETE, bulkDeleteListener);
+ this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion);
+ this.client.on(Events.THREAD_DELETE, this._handleThreadDeletion);
+ this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion);
this.once('end', () => {
- this.client.removeListener(Events.MessageReactionAdd, this.handleCollect);
- this.client.removeListener(Events.MessageReactionRemove, this.handleDispose);
- this.client.removeListener(Events.MessageReactionRemoveAll, this.empty);
- this.client.removeListener(Events.MessageDelete, this._handleMessageDeletion);
- this.client.removeListener(Events.MessageBulkDelete, bulkDeleteListener);
- this.client.removeListener(Events.ChannelDelete, this._handleChannelDeletion);
- this.client.removeListener(Events.ThreadDelete, this._handleThreadDeletion);
- this.client.removeListener(Events.GuildDelete, this._handleGuildDeletion);
+ this.client.removeListener(Events.MESSAGE_REACTION_ADD, this.handleCollect);
+ this.client.removeListener(Events.MESSAGE_REACTION_REMOVE, this.handleDispose);
+ this.client.removeListener(Events.MESSAGE_REACTION_REMOVE_ALL, this.empty);
+ this.client.removeListener(Events.MESSAGE_DELETE, this._handleMessageDeletion);
+ this.client.removeListener(Events.MESSAGE_BULK_DELETE, bulkDeleteListener);
+ this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion);
+ this.client.removeListener(Events.THREAD_DELETE, this._handleThreadDeletion);
+ this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion);
this.client.decrementMaxListeners();
});
diff --git a/src/structures/Role.js b/src/structures/Role.js
index 5325973..f0ee3a8 100644
--- a/src/structures/Role.js
+++ b/src/structures/Role.js
@@ -1,10 +1,21 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { PermissionFlagsBits } = require('discord-api-types/v9');
+const process = require('node:process');
const Base = require('./Base');
const { Error } = require('../errors');
-const PermissionsBitField = require('../util/PermissionsBitField');
+const Permissions = require('../util/Permissions');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+const Util = require('../util/Util');
+
+let deprecationEmittedForComparePositions = false;
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedRoles = new WeakSet();
+let deprecationEmittedForDeleted = false;
/**
* Represents a role on Discord.
@@ -76,9 +87,9 @@ class Role extends Base {
if ('permissions' in data) {
/**
* The permissions of the role
- * @type {Readonly}
+ * @type {Readonly}
*/
- this.permissions = new PermissionsBitField(BigInt(data.permissions)).freeze();
+ this.permissions = new Permissions(BigInt(data.permissions)).freeze();
}
if ('managed' in data) {
@@ -128,7 +139,7 @@ class Role extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -140,6 +151,36 @@ class Role extends Base {
return new Date(this.createdTimestamp);
}
+ /**
+ * Whether or not the role has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Role#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedRoles.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Role#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedRoles.add(this);
+ else deletedRoles.delete(this);
+ }
+
/**
* The hexadecimal version of the role color, with a leading hashtag
* @type {string}
@@ -166,7 +207,7 @@ class Role extends Base {
get editable() {
if (this.managed) return false;
const clientMember = this.guild.members.resolve(this.client.user);
- if (!clientMember.permissions.has(PermissionFlagsBits.ManageRoles)) return false;
+ if (!clientMember.permissions.has(Permissions.FLAGS.MANAGE_ROLES)) return false;
return clientMember.roles.highest.comparePositionTo(this) > 0;
}
@@ -225,7 +266,7 @@ class Role extends Base {
* taking into account permission overwrites.
* @param {GuildChannel|Snowflake} channel The guild channel to use as context
* @param {boolean} [checkAdmin=true] Whether having `ADMINISTRATOR` will return all permissions
- * @returns {Readonly}
+ * @returns {Readonly}
*/
permissionsIn(channel, checkAdmin = true) {
channel = this.guild.channels.resolve(channel);
@@ -285,7 +326,7 @@ class Role extends Base {
* @returns {Promise}
* @example
* // Set the permissions of the role
- * role.setPermissions([PermissionFlagsBits.KickMembers, PermissionFlagsBits.BanMembers])
+ * role.setPermissions([Permissions.FLAGS.KICK_MEMBERS, Permissions.FLAGS.BAN_MEMBERS])
* .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`))
* .catch(console.error);
* @example
@@ -358,8 +399,20 @@ class Role extends Base {
* .then(updated => console.log(`Role position: ${updated.position}`))
* .catch(console.error);
*/
- setPosition(position, options = {}) {
- return this.guild.roles.setPosition(this, position, options);
+ async setPosition(position, { relative, reason } = {}) {
+ const updatedRoles = await Util.setPosition(
+ this,
+ position,
+ relative,
+ this.guild._sortedRoles(),
+ this.client.api.guilds(this.guild.id).roles,
+ reason,
+ );
+ this.client.actions.GuildRolesPositionUpdate.handle({
+ guild_id: this.guild.id,
+ roles: updatedRoles,
+ });
+ return this;
}
/**
@@ -379,11 +432,12 @@ class Role extends Base {
/**
* A link to the role's icon
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the image URL
* @returns {?string}
*/
- iconURL(options = {}) {
- return this.icon && this.client.rest.cdn.roleIcon(this.id, this.icon, options);
+ iconURL({ format, size } = {}) {
+ if (!this.icon) return null;
+ return this.client.rest.cdn.RoleIcon(this.id, this.icon, format, size);
}
/**
@@ -426,9 +480,31 @@ class Role extends Base {
permissions: this.permissions.toJSON(),
};
}
+
+ /**
+ * Compares the positions of two roles.
+ * @param {Role} role1 First role to compare
+ * @param {Role} role2 Second role to compare
+ * @returns {number} Negative number if the first role's position is lower (second role's is higher),
+ * positive number if the first's is higher (second's is lower), 0 if equal
+ * @deprecated Use {@link RoleManager#comparePositions} instead.
+ */
+ static comparePositions(role1, role2) {
+ if (!deprecationEmittedForComparePositions) {
+ process.emitWarning(
+ 'The Role.comparePositions method is deprecated. Use RoleManager#comparePositions instead.',
+ 'DeprecationWarning',
+ );
+
+ deprecationEmittedForComparePositions = true;
+ }
+
+ return role1.guild.roles.comparePositions(role1, role2);
+ }
}
exports.Role = Role;
+exports.deletedRoles = deletedRoles;
/**
* @external APIRole
diff --git a/src/structures/SelectMenuComponent.js b/src/structures/SelectMenuComponent.js
deleted file mode 100644
index 7758962..00000000
--- a/src/structures/SelectMenuComponent.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-const { SelectMenuComponent: BuildersSelectMenuComponent } = require('@discordjs/builders');
-const Transformers = require('../util/Transformers');
-
-class SelectMenuComponent extends BuildersSelectMenuComponent {
- constructor(data) {
- super(Transformers.toSnakeCase(data));
- }
-}
-
-module.exports = SelectMenuComponent;
diff --git a/src/structures/StageInstance.js b/src/structures/StageInstance.js
index 883ee54..040d1e0 100644
--- a/src/structures/StageInstance.js
+++ b/src/structures/StageInstance.js
@@ -1,7 +1,17 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
+const process = require('node:process');
const Base = require('./Base');
+const { PrivacyLevels } = require('../util/Constants');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedStageInstances = new WeakSet();
+let deprecationEmittedForDeleted = false;
/**
* Represents a stage instance.
@@ -48,16 +58,15 @@ class StageInstance extends Base {
if ('privacy_level' in data) {
/**
* The privacy level of the stage instance
- * @type {StageInstancePrivacyLevel}
+ * @type {PrivacyLevel}
*/
- this.privacyLevel = data.privacy_level;
+ this.privacyLevel = PrivacyLevels[data.privacy_level];
}
if ('discoverable_disabled' in data) {
/**
* Whether or not stage discovery is disabled
* @type {?boolean}
- * @deprecated See https://github.com/discord/discord-api-docs/pull/4296 for more information
*/
this.discoverableDisabled = data.discoverable_disabled;
} else {
@@ -74,6 +83,36 @@ class StageInstance extends Base {
return this.client.channels.resolve(this.channelId);
}
+ /**
+ * Whether or not the stage instance has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'StageInstance#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedStageInstances.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'StageInstance#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedStageInstances.add(this);
+ else deletedStageInstances.delete(this);
+ }
+
/**
* The guild this stage instance belongs to
* @type {?Guild}
@@ -109,6 +148,7 @@ class StageInstance extends Base {
async delete() {
await this.guild.stageInstances.delete(this.channelId);
const clone = this._clone();
+ deletedStageInstances.add(clone);
return clone;
}
@@ -132,7 +172,7 @@ class StageInstance extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -146,3 +186,4 @@ class StageInstance extends Base {
}
exports.StageInstance = StageInstance;
+exports.deletedStageInstances = deletedStageInstances;
diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js
index c58fafd..a334737 100644
--- a/src/structures/Sticker.js
+++ b/src/structures/Sticker.js
@@ -1,8 +1,17 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { Routes, StickerFormatType } = require('discord-api-types/v9');
+const process = require('node:process');
const Base = require('./Base');
+const { StickerFormatTypes, StickerTypes } = require('../util/Constants');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+
+/**
+ * @type {WeakSet}
+ * @private
+ * @internal
+ */
+const deletedStickers = new WeakSet();
+let deprecationEmittedForDeleted = false;
/**
* Represents a Sticker.
@@ -37,7 +46,7 @@ class Sticker extends Base {
* The type of the sticker
* @type {?StickerType}
*/
- this.type = sticker.type;
+ this.type = StickerTypes[sticker.type];
} else {
this.type ??= null;
}
@@ -47,7 +56,7 @@ class Sticker extends Base {
* The format of the sticker
* @type {StickerFormatType}
*/
- this.format = sticker.format_type;
+ this.format = StickerFormatTypes[sticker.format_type];
}
if ('name' in sticker) {
@@ -125,7 +134,7 @@ class Sticker extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -137,6 +146,36 @@ class Sticker extends Base {
return new Date(this.createdTimestamp);
}
+ /**
+ * Whether or not the sticker has been deleted
+ * @type {boolean}
+ * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
+ */
+ get deleted() {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Sticker#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ return deletedStickers.has(this);
+ }
+
+ set deleted(value) {
+ if (!deprecationEmittedForDeleted) {
+ deprecationEmittedForDeleted = true;
+ process.emitWarning(
+ 'Sticker#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
+ 'DeprecationWarning',
+ );
+ }
+
+ if (value) deletedStickers.add(this);
+ else deletedStickers.delete(this);
+ }
+
/**
* Whether this sticker is partial
* @type {boolean}
@@ -157,13 +196,11 @@ class Sticker extends Base {
/**
* A link to the sticker
- * If the sticker's format is {@link StickerFormatType.Lottie}, it returns
- * the URL of the Lottie JSON file.
+ * If the sticker's format is LOTTIE, it returns the URL of the Lottie JSON file.
* @type {string}
- * @readonly
*/
get url() {
- return this.client.rest.cdn.sticker(this.id, this.format === StickerFormatType.Lottie ? 'json' : 'png');
+ return this.client.rest.cdn.Sticker(this.id, this.format);
}
/**
@@ -191,7 +228,10 @@ class Sticker extends Base {
async fetchUser() {
if (this.partial) await this.fetch();
if (!this.guildId) throw new Error('NOT_GUILD_STICKER');
- return this.guild.stickers.fetchUser(this);
+
+ const data = await this.client.api.guilds(this.guildId).stickers(this.id).get();
+ this._patch(data);
+ return this.user;
}
/**
@@ -264,6 +304,7 @@ class Sticker extends Base {
}
exports.Sticker = Sticker;
+exports.deletedStickers = deletedStickers;
/**
* @external APISticker
diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js
index 7e599b7..6fd6a5b 100644
--- a/src/structures/StickerPack.js
+++ b/src/structures/StickerPack.js
@@ -1,9 +1,9 @@
'use strict';
const { Collection } = require('@discordjs/collection');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
const Base = require('./Base');
const { Sticker } = require('./Sticker');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Represents a pack of standard stickers.
@@ -61,7 +61,7 @@ class StickerPack extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -84,11 +84,11 @@ class StickerPack extends Base {
/**
* The URL to this sticker pack's banner.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- bannerURL(options = {}) {
- return this.bannerId && this.client.rest.cdn.stickerPackBanner(this.bannerId, options);
+ bannerURL({ format, size } = {}) {
+ return this.bannerId && this.client.rest.cdn.StickerPackBanner(this.bannerId, format, size);
}
}
diff --git a/src/structures/Team.js b/src/structures/Team.js
index 98eb199..ccb8175 100644
--- a/src/structures/Team.js
+++ b/src/structures/Team.js
@@ -1,9 +1,9 @@
'use strict';
const { Collection } = require('@discordjs/collection');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
const Base = require('./Base');
const TeamMember = require('./TeamMember');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
/**
* Represents a Client OAuth2 Application Team.
@@ -76,7 +76,7 @@ class Team extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -90,11 +90,12 @@ class Team extends Base {
/**
* A link to the team's icon.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- iconURL(options = {}) {
- return this.icon && this.client.rest.cdn.teamIcon(this.id, this.icon, options);
+ iconURL({ format, size } = {}) {
+ if (!this.icon) return null;
+ return this.client.rest.cdn.TeamIcon(this.id, this.icon, { format, size });
}
/**
diff --git a/src/structures/TeamMember.js b/src/structures/TeamMember.js
index 9270418..9bd5993 100644
--- a/src/structures/TeamMember.js
+++ b/src/structures/TeamMember.js
@@ -1,6 +1,7 @@
'use strict';
const Base = require('./Base');
+const { MembershipStates } = require('../util/Constants');
/**
* Represents a Client OAuth2 Application Team Member.
@@ -31,9 +32,9 @@ class TeamMember extends Base {
if ('membership_state' in data) {
/**
* The permissions this Team Member has with regard to the team
- * @type {TeamMemberMembershipState}
+ * @type {MembershipState}
*/
- this.membershipState = data.membership_state;
+ this.membershipState = MembershipStates[data.membership_state];
}
if ('user' in data) {
diff --git a/src/structures/ThreadChannel.js b/src/structures/ThreadChannel.js
index 397f646..7c45cbc 100644
--- a/src/structures/ThreadChannel.js
+++ b/src/structures/ThreadChannel.js
@@ -1,11 +1,11 @@
'use strict';
-const { ChannelType, PermissionFlagsBits, Routes } = require('discord-api-types/v9');
const { Channel } = require('./Channel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { RangeError } = require('../errors');
const MessageManager = require('../managers/MessageManager');
const ThreadMemberManager = require('../managers/ThreadMemberManager');
+const Permissions = require('../util/Permissions');
/**
* Represents a thread channel on Discord.
@@ -79,7 +79,7 @@ class ThreadChannel extends Channel {
* Always `null` in public threads
* @type {?boolean}
*/
- this.invitable = this.type === ChannelType.GuildPrivateThread ? data.thread_metadata.invitable ?? false : null;
+ this.invitable = this.type === 'GUILD_PRIVATE_THREAD' ? data.thread_metadata.invitable ?? false : null;
/**
* Whether the thread is archived
@@ -99,12 +99,7 @@ class ThreadChannel extends Channel {
* created
* @type {?number}
*/
- this.archiveTimestamp = Date.parse(data.thread_metadata.archive_timestamp);
-
- if ('create_timestamp' in data.thread_metadata) {
- // Note: this is needed because we can't assign directly to getters
- this._createdTimestamp = Date.parse(data.thread_metadata.create_timestamp);
- }
+ this.archiveTimestamp = new Date(data.thread_metadata.archive_timestamp).getTime();
} else {
this.locked ??= null;
this.archived ??= null;
@@ -113,8 +108,6 @@ class ThreadChannel extends Channel {
this.invitable ??= null;
}
- this._createdTimestamp ??= this.type === ChannelType.GuildPrivateThread ? super.createdTimestamp : null;
-
if ('owner_id' in data) {
/**
* The id of the member who created this thread
@@ -140,7 +133,7 @@ class ThreadChannel extends Channel {
* The timestamp when the last pinned message was pinned, if there was one
* @type {?number}
*/
- this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null;
+ this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null;
} else {
this.lastPinTimestamp ??= null;
}
@@ -183,16 +176,6 @@ class ThreadChannel extends Channel {
if (data.messages) for (const message of data.messages) this.messages._add(message);
}
- /**
- * The timestamp when this thread was created. This isn't available for threads
- * created before 2022-01-09
- * @type {?number}
- * @readonly
- */
- get createdTimestamp() {
- return this._createdTimestamp;
- }
-
/**
* A collection of associated guild member objects of this thread's members
* @type {Collection}
@@ -209,16 +192,8 @@ class ThreadChannel extends Channel {
* @readonly
*/
get archivedAt() {
- return this.archiveTimestamp && new Date(this.archiveTimestamp);
- }
-
- /**
- * The time the thread was created at
- * @type {?Date}
- * @readonly
- */
- get createdAt() {
- return this.createdTimestamp && new Date(this.createdTimestamp);
+ if (!this.archiveTimestamp) return null;
+ return new Date(this.archiveTimestamp);
}
/**
@@ -253,7 +228,7 @@ class ThreadChannel extends Channel {
* account.
* @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for
* @param {boolean} [checkAdmin=true] Whether having `ADMINISTRATOR` will return all permissions
- * @returns {?Readonly}
+ * @returns {?Readonly}
*/
permissionsFor(memberOrRole, checkAdmin) {
return this.parent?.permissionsFor(memberOrRole, checkAdmin) ?? null;
@@ -297,7 +272,7 @@ class ThreadChannel extends Channel {
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the thread in seconds
* @property {boolean} [locked] Whether the thread is locked
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to a thread
- * Can only be edited on {@link ChannelType.GuildPrivateThread}
+ * Can only be edited on `GUILD_PRIVATE_THREAD`
*/
/**
@@ -322,13 +297,13 @@ class ThreadChannel extends Channel {
}
}
const newData = await this.client.api.channels(this.id).patch({
- body: {
+ data: {
name: (data.name ?? this.name).trim(),
archived: data.archived,
auto_archive_duration: autoArchiveDuration,
rate_limit_per_user: data.rateLimitPerUser,
locked: data.locked,
- invitable: this.type === ChannelType.GuildPrivateThread ? data.invitable : undefined,
+ invitable: this.type === 'GUILD_PRIVATE_THREAD' ? data.invitable : undefined,
},
reason,
});
@@ -377,9 +352,7 @@ class ThreadChannel extends Channel {
* @returns {Promise}
*/
setInvitable(invitable = true, reason) {
- if (this.type !== ChannelType.GuildPrivateThread) {
- return Promise.reject(new RangeError('THREAD_INVITABLE_TYPE', this.type));
- }
+ if (this.type !== 'GUILD_PRIVATE_THREAD') return Promise.reject(new RangeError('THREAD_INVITABLE_TYPE', this.type));
return this.edit({ invitable }, reason);
}
@@ -440,8 +413,7 @@ class ThreadChannel extends Channel {
*/
get editable() {
return (
- (this.ownerId === this.client.user.id && (this.type !== ChannelType.GuildPrivateThread || this.joined)) ||
- this.manageable
+ (this.ownerId === this.client.user.id && (this.type !== 'GUILD_PRIVATE_THREAD' || this.joined)) || this.manageable
);
}
@@ -455,9 +427,7 @@ class ThreadChannel extends Channel {
!this.archived &&
!this.joined &&
this.permissionsFor(this.client.user)?.has(
- this.type === ChannelType.GuildPrivateThread
- ? PermissionFlagsBits.ManageThreads
- : PermissionFlagsBits.ViewChannel,
+ this.type === 'GUILD_PRIVATE_THREAD' ? Permissions.FLAGS.MANAGE_THREADS : Permissions.FLAGS.VIEW_CHANNEL,
false,
)
);
@@ -472,11 +442,11 @@ class ThreadChannel extends Channel {
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
// This flag allows managing even if timed out
- if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
return (
this.guild.me.communicationDisabledUntilTimestamp < Date.now() &&
- permissions.has(PermissionFlagsBits.ManageThreads, false)
+ permissions.has(Permissions.FLAGS.MANAGE_THREADS, false)
);
}
@@ -489,7 +459,7 @@ class ThreadChannel extends Channel {
if (this.client.user.id === this.guild.ownerId) return true;
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
- return permissions.has(PermissionFlagsBits.ViewChannel, false);
+ return permissions.has(Permissions.FLAGS.VIEW_CHANNEL, false);
}
/**
@@ -501,12 +471,12 @@ class ThreadChannel extends Channel {
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
// This flag allows sending even if timed out
- if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
return (
!(this.archived && this.locked && !this.manageable) &&
- (this.type !== ChannelType.GuildPrivateThread || this.joined || this.manageable) &&
- permissions.has(PermissionFlagsBits.SendMessagesInThreads, false) &&
+ (this.type !== 'GUILD_PRIVATE_THREAD' || this.joined || this.manageable) &&
+ permissions.has(Permissions.FLAGS.SEND_MESSAGES_IN_THREADS, false) &&
this.guild.me.communicationDisabledUntilTimestamp < Date.now()
);
}
@@ -517,15 +487,7 @@ class ThreadChannel extends Channel {
* @readonly
*/
get unarchivable() {
- return this.archived && this.sendable && (!this.locked || this.manageable);
- }
-
- /**
- * Whether this thread is a private thread
- * @returns {boolean}
- */
- isPrivate() {
- return this.type === ChannelType.GuildPrivateThread;
+ return this.archived && (this.locked ? this.manageable : this.sendable);
}
/**
@@ -539,7 +501,7 @@ class ThreadChannel extends Channel {
* .catch(console.error);
*/
async delete(reason) {
- await this.guild.channels.delete(this.id, reason);
+ await this.client.api.channels(this.id).delete({ reason });
return this;
}
diff --git a/src/structures/ThreadMember.js b/src/structures/ThreadMember.js
index 9da8677..3dcf3da 100644
--- a/src/structures/ThreadMember.js
+++ b/src/structures/ThreadMember.js
@@ -1,7 +1,7 @@
'use strict';
const Base = require('./Base');
-const ThreadMemberFlagsBitField = require('../util/ThreadMemberFlagsBitField');
+const ThreadMemberFlags = require('../util/ThreadMemberFlags');
/**
* Represents a Member for a Thread.
@@ -33,14 +33,14 @@ class ThreadMember extends Base {
}
_patch(data) {
- if ('join_timestamp' in data) this.joinedTimestamp = Date.parse(data.join_timestamp);
+ if ('join_timestamp' in data) this.joinedTimestamp = new Date(data.join_timestamp).getTime();
if ('flags' in data) {
/**
* The flags for this thread member
- * @type {ThreadMemberFlagsBitField}
+ * @type {ThreadMemberFlags}
*/
- this.flags = new ThreadMemberFlagsBitField(data.flags).freeze();
+ this.flags = new ThreadMemberFlags(data.flags).freeze();
}
}
@@ -59,7 +59,7 @@ class ThreadMember extends Base {
* @readonly
*/
get joinedAt() {
- return this.joinedTimestamp && new Date(this.joinedTimestamp);
+ return this.joinedTimestamp ? new Date(this.joinedTimestamp) : null;
}
/**
diff --git a/src/structures/User.js b/src/structures/User.js
index c4d76d4..ecb9987 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -1,11 +1,11 @@
'use strict';
const Base = require('./Base');
-const { Error } = require('../errors/DJSError');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const UserFlagsBitField = require('../util/UserFlagsBitField');
-const { default: Collection } = require('@discordjs/collection');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
+const { Error } = require('../errors');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+const UserFlags = require('../util/UserFlags');
+const { default: Collection } = require('@discordjs/collection');
/**
* Represents a user on Discord.
@@ -13,22 +13,22 @@ const TextBasedChannel = require('./interfaces/TextBasedChannel');
* @extends {Base}
*/
class User extends Base {
- constructor(client, data) {
- super(client);
+ constructor(client, data) {
+ super(client);
- /**
- * The user's id
- * @type {Snowflake}
- */
- this.id = data.id;
+ /**
+ * The user's id
+ * @type {Snowflake}
+ */
+ this.id = data.id;
- this.bot = null;
+ this.bot = null;
- this.system = null;
+ this.system = null;
- this.flags = null;
+ this.flags = null;
- this.friend = client.friends.cache.has(this.id);
+ this.friend = client.friends.cache.has(this.id);
this.blocked = client.blocked.cache.has(this.id);
@@ -38,91 +38,92 @@ class User extends Base {
this.premiumGuildSince = null;
this.mutualGuilds = new Collection();
- this._patch(data);
- }
+ this._patch(data);
+ }
- _patch(data) {
- if ('username' in data) {
- /**
- * The username of the user
- * @type {?string}
- */
- this.username = data.username;
- } else {
- this.username ??= null;
- }
+ _patch(data) {
+ if ('username' in data) {
+ /**
+ * The username of the user
+ * @type {?string}
+ */
+ this.username = data.username;
+ } else {
+ this.username ??= null;
+ }
- if ('bot' in data) {
- /**
- * Whether or not the user is a bot
- * @type {?boolean}
- */
- this.bot = Boolean(data.bot);
- } else if (!this.partial && typeof this.bot !== 'boolean') {
- this.bot = false;
- }
+ if ('bot' in data) {
+ /**
+ * Whether or not the user is a bot
+ * @type {?boolean}
+ */
+ this.bot = Boolean(data.bot);
+ } else if (!this.partial && typeof this.bot !== 'boolean') {
+ this.bot = false;
+ }
- if ('discriminator' in data) {
- /**
- * A discriminator based on username for the user
- * @type {?string}
- */
- this.discriminator = data.discriminator;
- } else {
- this.discriminator ??= null;
- }
+ if ('discriminator' in data) {
+ /**
+ * A discriminator based on username for the user
+ * @type {?string}
+ */
+ this.discriminator = data.discriminator;
+ } else {
+ this.discriminator ??= null;
+ }
- if ('avatar' in data) {
- /**
- * The user avatar's hash
- * @type {?string}
- */
- this.avatar = data.avatar;
- } else {
- this.avatar ??= null;
- }
+ if ('avatar' in data) {
+ /**
+ * The user avatar's hash
+ * @type {?string}
+ */
+ this.avatar = data.avatar;
+ } else {
+ this.avatar ??= null;
+ }
- if ('banner' in data) {
- /**
- * The user banner's hash
- * The user must be force fetched for this property to be present or be updated
- * @type {?string}
- */
- this.banner = data.banner;
- } else if (this.banner !== null) {
- this.banner ??= undefined;
- }
+ if ('banner' in data) {
+ /**
+ * The user banner's hash
+ * The user must be force fetched for this property to be present or be updated
+ * @type {?string}
+ */
+ this.banner = data.banner;
+ } else if (this.banner !== null) {
+ this.banner ??= undefined;
+ }
- if ('accent_color' in data) {
- /**
- * The base 10 accent color of the user's banner
- * The user must be force fetched for this property to be present or be updated
- * @type {?number}
- */
- this.accentColor = data.accent_color;
- } else if (this.accentColor !== null) {
- this.accentColor ??= undefined;
- }
+ if ('accent_color' in data) {
+ /**
+ * The base 10 accent color of the user's banner
+ * The user must be force fetched for this property to be present or be updated
+ * @type {?number}
+ */
+ this.accentColor = data.accent_color;
+ } else if (this.accentColor !== null) {
+ this.accentColor ??= undefined;
+ }
- if ('system' in data) {
- /**
- * Whether the user is an Official Discord System user (part of the urgent message system)
- * @type {?boolean}
- */
- this.system = Boolean(data.system);
- } else if (!this.partial && typeof this.system !== 'boolean') {
- this.system = false;
- }
+ if ('system' in data) {
+ /**
+ * Whether the user is an Official Discord System user (part of the urgent message system)
+ * @type {?boolean}
+ */
+ this.system = Boolean(data.system);
+ } else if (!this.partial && typeof this.system !== 'boolean') {
+ this.system = false;
+ }
- if ('public_flags' in data) {
- /**
- * The flags for this user
- * @type {?UserFlagsBitField}
- */
- this.flags = new UserFlagsBitField(data.public_flags);
- }
- }
+ if ('public_flags' in data) {
+ /**
+ * The flags for this user
+ * @type {?UserFlags}
+ */
+ this.flags = new UserFlags(data.public_flags);
+ }
+ }
+
// Code written by https://github.com/aiko-chan-ai
_ProfilePatch(data) {
if (!data) return;
@@ -217,224 +218,209 @@ class User extends Base {
.relationships[this.id].delete.then((_) => _);
}
- /**
- * Whether this User is a partial
- * @type {boolean}
- * @readonly
- */
- get partial() {
- return typeof this.username !== 'string';
- }
- /**
- * The timestamp the user was created at
- * @type {number}
- * @readonly
- */
- get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
- }
+ /**
+ * Whether this User is a partial
+ * @type {boolean}
+ * @readonly
+ */
+ get partial() {
+ return typeof this.username !== 'string';
+ }
- /**
- * The time the user was created at
- * @type {Date}
- * @readonly
- */
- get createdAt() {
- return new Date(this.createdTimestamp);
- }
+ /**
+ * The timestamp the user was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return SnowflakeUtil.timestampFrom(this.id);
+ }
- /**
- * A link to the user's avatar.
- * @param {ImageURLOptions} [options={}] Options for the image URL
- * @returns {?string}
- */
- avatarURL({ format, size, dynamic } = {}) {
- if (!this.avatar) return null;
- return this.client.rest.cdn.Avatar(
- this.id,
- this.avatar,
- format,
- size,
- dynamic,
- );
- }
+ /**
+ * The time the user was created at
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
- /**
- * If the user is a bot then it'll return the slash commands else return null
- * @readonly
- */
- get slashCommands() {
- if (this.bot) {
- return this.client.api.applications(this.id).commands.get();
- } else return null;
- }
+ /**
+ * A link to the user's avatar.
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
+ * @returns {?string}
+ */
+ avatarURL({ format, size, dynamic } = {}) {
+ if (!this.avatar) return null;
+ return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic);
+ }
- /**
- * A link to the user's default avatar
- * @type {string}
- * @readonly
- */
- get defaultAvatarURL() {
- return this.client.rest.cdn.defaultAvatar(this.discriminator % 5);
- }
+ /**
+ * A link to the user's default avatar
+ * @type {string}
+ * @readonly
+ */
+ get defaultAvatarURL() {
+ return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5);
+ }
- /**
- * A link to the user's avatar if they have one.
- * Otherwise a link to their default avatar will be returned.
- * @param {ImageURLOptions} [options={}] Options for the Image URL
- * @returns {string}
- */
- displayAvatarURL(options) {
- return this.avatarURL(options) ?? this.defaultAvatarURL;
- }
+ /**
+ * A link to the user's avatar if they have one.
+ * Otherwise a link to their default avatar will be returned.
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
+ * @returns {string}
+ */
+ displayAvatarURL(options) {
+ return this.avatarURL(options) ?? this.defaultAvatarURL;
+ }
- /**
- * The hexadecimal version of the user accent color, with a leading hash
- * The user must be force fetched for this property to be present
- * @type {?string}
- * @readonly
- */
- get hexAccentColor() {
- if (typeof this.accentColor !== 'number') return this.accentColor;
- return `#${this.accentColor.toString(16).padStart(6, '0')}`;
- }
+ /**
+ * The hexadecimal version of the user accent color, with a leading hash
+ * The user must be force fetched for this property to be present
+ * @type {?string}
+ * @readonly
+ */
+ get hexAccentColor() {
+ if (typeof this.accentColor !== 'number') return this.accentColor;
+ return `#${this.accentColor.toString(16).padStart(6, '0')}`;
+ }
- /**
- * A link to the user's banner. See {@link User#banner} for more info
- * @param {ImageURLOptions} [options={}] Options for the image URL
- * @returns {?string}
- */
- bannerURL(options = {}) {
- return (
- this.banner && this.client.rest.cdn.banner(this.id, this.banner, options)
- );
- }
+ /**
+ * A link to the user's banner.
+ * This method will throw an error if called before the user is force fetched.
+ * See {@link User#banner} for more info
+ * @param {ImageURLOptions} [options={}] Options for the Image URL
+ * @returns {?string}
+ */
+ bannerURL({ format, size, dynamic } = {}) {
+ if (typeof this.banner === 'undefined') throw new Error('USER_BANNER_NOT_FETCHED');
+ if (!this.banner) return null;
+ return this.client.rest.cdn.Banner(this.id, this.banner, format, size, dynamic);
+ }
- /**
- * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
- * @type {?string}
- * @readonly
- */
- get tag() {
- return typeof this.username === 'string'
- ? `${this.username}#${this.discriminator}`
- : null;
- }
+ /**
+ * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
+ * @type {?string}
+ * @readonly
+ */
+ get tag() {
+ return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null;
+ }
- /**
- * The DM between the client's user and this user
- * @type {?DMChannel}
- * @readonly
- */
- get dmChannel() {
- return this.client.users.dmChannel(this.id);
- }
+ /**
+ * The DM between the client's user and this user
+ * @type {?DMChannel}
+ * @readonly
+ */
+ get dmChannel() {
+ return this.client.users.dmChannel(this.id);
+ }
- /**
- * Creates a DM channel between the client and the user.
- * @param {boolean} [force=false] Whether to skip the cache check and request the API
- * @returns {Promise}
- */
- createDM(force = false) {
- return this.client.users.createDM(this.id, force);
- }
+ /**
+ * Creates a DM channel between the client and the user.
+ * @param {boolean} [force=false] Whether to skip the cache check and request the API
+ * @returns {Promise}
+ */
+ createDM(force = false) {
+ return this.client.users.createDM(this.id, force);
+ }
- /**
- * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
- * @returns {Promise}
- */
- deleteDM() {
- return this.client.users.deleteDM(this.id);
- }
+ /**
+ * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
+ * @returns {Promise}
+ */
+ deleteDM() {
+ return this.client.users.deleteDM(this.id);
+ }
- /**
- * Checks if the user is equal to another.
- * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
- * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
- * @param {User} user User to compare with
- * @returns {boolean}
- */
- equals(user) {
- return (
- user &&
- this.id === user.id &&
- this.username === user.username &&
- this.discriminator === user.discriminator &&
- this.avatar === user.avatar &&
- this.flags?.bitfield === user.flags?.bitfield &&
- this.banner === user.banner &&
- this.accentColor === user.accentColor
- );
- }
+ /**
+ * Checks if the user is equal to another.
+ * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
+ * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
+ * @param {User} user User to compare with
+ * @returns {boolean}
+ */
+ equals(user) {
+ return (
+ user &&
+ this.id === user.id &&
+ this.username === user.username &&
+ this.discriminator === user.discriminator &&
+ this.avatar === user.avatar &&
+ this.flags?.bitfield === user.flags?.bitfield &&
+ this.banner === user.banner &&
+ this.accentColor === user.accentColor
+ );
+ }
- /**
- * Compares the user with an API user object
- * @param {APIUser} user The API user object to compare
- * @returns {boolean}
- * @private
- */
- _equals(user) {
- return (
- user &&
- this.id === user.id &&
- this.username === user.username &&
- this.discriminator === user.discriminator &&
- this.avatar === user.avatar &&
- this.flags?.bitfield === user.public_flags &&
- ('banner' in user ? this.banner === user.banner : true) &&
- ('accent_color' in user ? this.accentColor === user.accent_color : true)
- );
- }
+ /**
+ * Compares the user with an API user object
+ * @param {APIUser} user The API user object to compare
+ * @returns {boolean}
+ * @private
+ */
+ _equals(user) {
+ return (
+ user &&
+ this.id === user.id &&
+ this.username === user.username &&
+ this.discriminator === user.discriminator &&
+ this.avatar === user.avatar &&
+ this.flags?.bitfield === user.public_flags &&
+ ('banner' in user ? this.banner === user.banner : true) &&
+ ('accent_color' in user ? this.accentColor === user.accent_color : true)
+ );
+ }
- /**
- * Fetches this user's flags.
- * @param {boolean} [force=false] Whether to skip the cache check and request the API
- * @returns {Promise}
- */
- fetchFlags(force = false) {
- return this.client.users.fetchFlags(this.id, { force });
- }
+ /**
+ * Fetches this user's flags.
+ * @param {boolean} [force=false] Whether to skip the cache check and request the API
+ * @returns {Promise}
+ */
+ fetchFlags(force = false) {
+ return this.client.users.fetchFlags(this.id, { force });
+ }
- /**
- * Fetches this user.
- * @param {boolean} [force=true] Whether to skip the cache check and request the API
- * @returns {Promise}
- */
- fetch(force = true) {
- return this.client.users.fetch(this.id, { force });
- }
+ /**
+ * Fetches this user.
+ * @param {boolean} [force=true] Whether to skip the cache check and request the API
+ * @returns {Promise}
+ */
+ fetch(force = true) {
+ return this.client.users.fetch(this.id, { force });
+ }
- /**
- * When concatenated with a string, this automatically returns the user's mention instead of the User object.
- * @returns {string}
- * @example
- * // Logs: Hello from <@123456789012345678>!
- * console.log(`Hello from ${user}!`);
- */
- toString() {
- return `<@${this.id}>`;
- }
+ /**
+ * When concatenated with a string, this automatically returns the user's mention instead of the User object.
+ * @returns {string}
+ * @example
+ * // Logs: Hello from <@123456789012345678>!
+ * console.log(`Hello from ${user}!`);
+ */
+ toString() {
+ return `<@${this.id}>`;
+ }
- toJSON(...props) {
- const json = super.toJSON(
- {
- createdTimestamp: true,
- defaultAvatarURL: true,
- hexAccentColor: true,
- tag: true,
- },
- ...props,
- );
- json.avatarURL = this.avatarURL();
- json.displayAvatarURL = this.displayAvatarURL();
- json.bannerURL = this.banner ? this.bannerURL() : this.banner;
- return json;
- }
+ toJSON(...props) {
+ const json = super.toJSON(
+ {
+ createdTimestamp: true,
+ defaultAvatarURL: true,
+ hexAccentColor: true,
+ tag: true,
+ },
+ ...props,
+ );
+ json.avatarURL = this.avatarURL();
+ json.displayAvatarURL = this.displayAvatarURL();
+ json.bannerURL = this.banner ? this.bannerURL() : this.banner;
+ return json;
+ }
- // These are here only for documentation purposes - they are implemented by TextBasedChannel
- /* eslint-disable no-empty-function */
- send() {}
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ /* eslint-disable no-empty-function */
+ send() {}
}
TextBasedChannel.applyToClass(User);
diff --git a/src/structures/UserContextMenuCommandInteraction.js b/src/structures/UserContextMenuInteraction.js
similarity index 61%
rename from src/structures/UserContextMenuCommandInteraction.js
rename to src/structures/UserContextMenuInteraction.js
index a074de1..98dad00 100644
--- a/src/structures/UserContextMenuCommandInteraction.js
+++ b/src/structures/UserContextMenuInteraction.js
@@ -1,12 +1,12 @@
'use strict';
-const ContextMenuCommandInteraction = require('./ContextMenuCommandInteraction');
+const ContextMenuInteraction = require('./ContextMenuInteraction');
/**
* Represents a user context menu interaction.
- * @extends {ContextMenuCommandInteraction}
+ * @extends {ContextMenuInteraction}
*/
-class UserContextMenuCommandInteraction extends ContextMenuCommandInteraction {
+class UserContextMenuInteraction extends ContextMenuInteraction {
/**
* The user this interaction was sent from
* @type {User}
@@ -26,4 +26,4 @@ class UserContextMenuCommandInteraction extends ContextMenuCommandInteraction {
}
}
-module.exports = UserContextMenuCommandInteraction;
+module.exports = UserContextMenuInteraction;
diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js
index 1b5bc20..d353771 100644
--- a/src/structures/VoiceChannel.js
+++ b/src/structures/VoiceChannel.js
@@ -1,13 +1,35 @@
'use strict';
-const { PermissionFlagsBits } = require('discord-api-types/v9');
+const process = require('node:process');
const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
+const Permissions = require('../util/Permissions');
+
+let deprecationEmittedForEditable = false;
/**
* Represents a guild voice channel on Discord.
* @extends {BaseGuildVoiceChannel}
*/
class VoiceChannel extends BaseGuildVoiceChannel {
+ /**
+ * Whether the channel is editable by the client user
+ * @type {boolean}
+ * @readonly
+ * @deprecated Use {@link VoiceChannel#manageable} instead
+ */
+ get editable() {
+ if (!deprecationEmittedForEditable) {
+ process.emitWarning(
+ 'The VoiceChannel#editable getter is deprecated. Use VoiceChannel#manageable instead.',
+ 'DeprecationWarning',
+ );
+
+ deprecationEmittedForEditable = true;
+ }
+
+ return this.manageable;
+ }
+
/**
* Whether the channel is joinable by the client user
* @type {boolean}
@@ -15,7 +37,7 @@ class VoiceChannel extends BaseGuildVoiceChannel {
*/
get joinable() {
if (!super.joinable) return false;
- if (this.full && !this.permissionsFor(this.client.user).has(PermissionFlagsBits.MoveMembers, false)) return false;
+ if (this.full && !this.permissionsFor(this.client.user).has(Permissions.FLAGS.MOVE_MEMBERS, false)) return false;
return true;
}
@@ -28,11 +50,10 @@ class VoiceChannel extends BaseGuildVoiceChannel {
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
// This flag allows speaking even if timed out
- if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
return (
- this.guild.me.communicationDisabledUntilTimestamp < Date.now() &&
- permissions.has(PermissionFlagsBits.Speak, false)
+ this.guild.me.communicationDisabledUntilTimestamp < Date.now() && permissions.has(Permissions.FLAGS.SPEAK, false)
);
}
diff --git a/src/structures/VoiceRegion.js b/src/structures/VoiceRegion.js
index be46b4d..fe399b4 100644
--- a/src/structures/VoiceRegion.js
+++ b/src/structures/VoiceRegion.js
@@ -19,6 +19,12 @@ class VoiceRegion {
*/
this.name = data.name;
+ /**
+ * Whether the region is VIP-only
+ * @type {boolean}
+ */
+ this.vip = data.vip;
+
/**
* Whether the region is deprecated
* @type {boolean}
diff --git a/src/structures/VoiceState.js b/src/structures/VoiceState.js
index 73762d0..78d9bf5 100644
--- a/src/structures/VoiceState.js
+++ b/src/structures/VoiceState.js
@@ -1,6 +1,5 @@
'use strict';
-const { ChannelType, Routes } = require('discord-api-types/v9');
const Base = require('./Base');
const { Error, TypeError } = require('../errors');
@@ -89,7 +88,7 @@ class VoiceState extends Base {
if ('self_video' in data) {
/**
* Whether this member is streaming using "Screen Share"
- * @type {?boolean}
+ * @type {boolean}
*/
this.streaming = data.self_stream ?? false;
} else {
@@ -109,11 +108,9 @@ class VoiceState extends Base {
if ('suppress' in data) {
/**
* Whether this member is suppressed from speaking. This property is specific to stage channels only.
- * @type {?boolean}
+ * @type {boolean}
*/
this.suppress = data.suppress;
- } else {
- this.suppress ??= null;
}
if ('request_to_speak_timestamp' in data) {
@@ -121,7 +118,7 @@ class VoiceState extends Base {
* The time at which the member requested to speak. This property is specific to stage channels only.
* @type {?number}
*/
- this.requestToSpeakTimestamp = Date.parse(data.request_to_speak_timestamp);
+ this.requestToSpeakTimestamp = new Date(data.request_to_speak_timestamp).getTime();
} else {
this.requestToSpeakTimestamp ??= null;
}
@@ -218,16 +215,16 @@ class VoiceState extends Base {
* @returns {Promise}
*/
async setRequestToSpeak(request = true) {
- if (this.channel?.type !== ChannelType.GuildStageVoice) throw new Error('VOICE_NOT_STAGE_CHANNEL');
+ if (this.channel?.type !== 'GUILD_STAGE_VOICE') throw new Error('VOICE_NOT_STAGE_CHANNEL');
if (this.client.user.id !== this.id) throw new Error('VOICE_STATE_NOT_OWN');
await this.client.api.guilds(this.guild.id, 'voice-states', '@me').patch({
- body: {
+ data: {
channel_id: this.channelId,
request_to_speak_timestamp: request ? new Date().toISOString() : null,
- }
- })
+ },
+ });
}
/**
@@ -250,15 +247,15 @@ class VoiceState extends Base {
async setSuppressed(suppressed = true) {
if (typeof suppressed !== 'boolean') throw new TypeError('VOICE_STATE_INVALID_TYPE', 'suppressed');
- if (this.channel?.type !== ChannelType.GuildStageVoice) throw new Error('VOICE_NOT_STAGE_CHANNEL');
+ if (this.channel?.type !== 'GUILD_STAGE_VOICE') throw new Error('VOICE_NOT_STAGE_CHANNEL');
const target = this.client.user.id === this.id ? '@me' : this.id;
await this.client.api.guilds(this.guild.id, 'voice-states', target).patch({
- body: {
+ data: {
channel_id: this.channelId,
suppress: suppressed,
- }
+ },
});
}
diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js
index 72b1478..022270a 100644
--- a/src/structures/Webhook.js
+++ b/src/structures/Webhook.js
@@ -1,499 +1,449 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { Routes, WebhookType } = require('discord-api-types/v9');
+const process = require('node:process');
const MessagePayload = require('./MessagePayload');
const { Error } = require('../errors');
+const { WebhookTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
-const DiscordAPIError = require('../rest/DiscordAPIError');
+const SnowflakeUtil = require('../util/SnowflakeUtil');
+
+let deprecationEmittedForFetchMessage = false;
/**
* Represents a webhook.
*/
class Webhook {
- constructor(client, data) {
- /**
- * The client that instantiated the webhook
- * @name Webhook#client
- * @type {Client}
- * @readonly
- */
- Object.defineProperty(this, 'client', { value: client });
- if (data) this._patch(data);
- }
+ constructor(client, data) {
+ /**
+ * The client that instantiated the webhook
+ * @name Webhook#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+ if (data) this._patch(data);
+ }
- _patch(data) {
- if ('name' in data) {
- /**
- * The name of the webhook
- * @type {string}
- */
- this.name = data.name;
- }
+ _patch(data) {
+ if ('name' in data) {
+ /**
+ * The name of the webhook
+ * @type {string}
+ */
+ this.name = data.name;
+ }
- /**
- * The token for the webhook, unavailable for follower webhooks and webhooks owned by another application.
- * @name Webhook#token
- * @type {?string}
- */
- Object.defineProperty(this, 'token', {
- value: data.token ?? null,
- writable: true,
- configurable: true,
- });
+ /**
+ * The token for the webhook, unavailable for follower webhooks and webhooks owned by another application.
+ * @name Webhook#token
+ * @type {?string}
+ */
+ Object.defineProperty(this, 'token', { value: data.token ?? null, writable: true, configurable: true });
- if ('avatar' in data) {
- /**
- * The avatar for the webhook
- * @type {?string}
- */
- this.avatar = data.avatar;
- }
+ if ('avatar' in data) {
+ /**
+ * The avatar for the webhook
+ * @type {?string}
+ */
+ this.avatar = data.avatar;
+ }
- /**
- * The webhook's id
- * @type {Snowflake}
- */
- this.id = data.id;
+ /**
+ * The webhook's id
+ * @type {Snowflake}
+ */
+ this.id = data.id;
- if ('type' in data) {
- /**
- * The type of the webhook
- * @type {WebhookType}
- */
- this.type = data.type;
- }
+ if ('type' in data) {
+ /**
+ * The type of the webhook
+ * @type {WebhookType}
+ */
+ this.type = WebhookTypes[data.type];
+ }
- if ('guild_id' in data) {
- /**
- * The guild the webhook belongs to
- * @type {Snowflake}
- */
- this.guildId = data.guild_id;
- }
+ if ('guild_id' in data) {
+ /**
+ * The guild the webhook belongs to
+ * @type {Snowflake}
+ */
+ this.guildId = data.guild_id;
+ }
- if ('channel_id' in data) {
- /**
- * The channel the webhook belongs to
- * @type {Snowflake}
- */
- this.channelId = data.channel_id;
- }
+ if ('channel_id' in data) {
+ /**
+ * The channel the webhook belongs to
+ * @type {Snowflake}
+ */
+ this.channelId = data.channel_id;
+ }
- if ('user' in data) {
- /**
- * The owner of the webhook
- * @type {?(User|APIUser)}
- */
- this.owner = this.client.users?._add(data.user) ?? data.user;
- } else {
- this.owner ??= null;
- }
+ if ('user' in data) {
+ /**
+ * The owner of the webhook
+ * @type {?(User|APIUser)}
+ */
+ this.owner = this.client.users?._add(data.user) ?? data.user;
+ } else {
+ this.owner ??= null;
+ }
- if ('application_id' in data) {
- /**
- * The application that created this webhook
- * @type {?Snowflake}
- */
- this.applicationId = data.application_id;
- } else {
- this.applicationId ??= null;
- }
+ if ('source_guild' in data) {
+ /**
+ * The source guild of the webhook
+ * @type {?(Guild|APIGuild)}
+ */
+ this.sourceGuild = this.client.guilds?.resolve(data.source_guild.id) ?? data.source_guild;
+ } else {
+ this.sourceGuild ??= null;
+ }
- if ('source_guild' in data) {
- /**
- * The source guild of the webhook
- * @type {?(Guild|APIGuild)}
- */
- this.sourceGuild =
- this.client.guilds?.resolve(data.source_guild.id) ?? data.source_guild;
- } else {
- this.sourceGuild ??= null;
- }
+ if ('source_channel' in data) {
+ /**
+ * The source channel of the webhook
+ * @type {?(NewsChannel|APIChannel)}
+ */
+ this.sourceChannel = this.client.channels?.resolve(data.source_channel?.id) ?? data.source_channel;
+ } else {
+ this.sourceChannel ??= null;
+ }
+ }
- if ('source_channel' in data) {
- /**
- * The source channel of the webhook
- * @type {?(NewsChannel|APIChannel)}
- */
- this.sourceChannel =
- this.client.channels?.resolve(data.source_channel?.id) ??
- data.source_channel;
- } else {
- this.sourceChannel ??= null;
- }
- }
+ /**
+ * Options that can be passed into send.
+ * @typedef {BaseMessageOptions} WebhookMessageOptions
+ * @property {string} [username=this.name] Username override for the message
+ * @property {string} [avatarURL] Avatar URL override for the message
+ * @property {Snowflake} [threadId] The id of the thread in the channel to send to.
+ * For interaction webhooks, this property is ignored
+ */
- /**
- * Options that can be passed into send.
- * @typedef {BaseMessageOptions} WebhookMessageOptions
- * @property {string} [username=this.name] Username override for the message
- * @property {string} [avatarURL] Avatar URL override for the message
- * @property {Snowflake} [threadId] The id of the thread in the channel to send to.
- * For interaction webhooks, this property is ignored
- * @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be set.
- */
+ /**
+ * Options that can be passed into editMessage.
+ * @typedef {Object} WebhookEditMessageOptions
+ * @property {MessageEmbed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds}
+ * @property {string} [content] See {@link BaseMessageOptions#content}
+ * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] See {@link BaseMessageOptions#files}
+ * @property {MessageMentionOptions} [allowedMentions] See {@link BaseMessageOptions#allowedMentions}
+ * @property {MessageAttachment[]} [attachments] Attachments to send with the message
+ * @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
+ * Action rows containing interactive components for the message (buttons, select menus)
+ * @property {Snowflake} [threadId] The id of the thread this message belongs to
+ * For interaction webhooks, this property is ignored
+ */
- /**
- * Options that can be passed into editMessage.
- * @typedef {Object} WebhookEditMessageOptions
- * @property {Embed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds}
- * @property {string} [content] See {@link BaseMessageOptions#content}
- * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] See {@link BaseMessageOptions#files}
- * @property {MessageMentionOptions} [allowedMentions] See {@link BaseMessageOptions#allowedMentions}
- * @property {MessageAttachment[]} [attachments] Attachments to send with the message
- * @property {ActionRow[]|ActionRowOptions[]} [components]
- * Action rows containing interactive components for the message (buttons, select menus)
- * @property {Snowflake} [threadId] The id of the thread this message belongs to
- * For interaction webhooks, this property is ignored
- */
+ /**
+ * Sends a message with this webhook.
+ * @param {string|MessagePayload|WebhookMessageOptions} options The options to provide
+ * @returns {Promise}
+ * @example
+ * // Send a basic message
+ * webhook.send('hello!')
+ * .then(message => console.log(`Sent message: ${message.content}`))
+ * .catch(console.error);
+ * @example
+ * // Send a basic message in a thread
+ * webhook.send({ content: 'hello!', threadId: '836856309672348295' })
+ * .then(message => console.log(`Sent message: ${message.content}`))
+ * .catch(console.error);
+ * @example
+ * // Send a remote file
+ * webhook.send({
+ * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Send a local file
+ * webhook.send({
+ * files: [{
+ * attachment: 'entire/path/to/file.jpg',
+ * name: 'file.jpg'
+ * }]
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Send an embed with a local image inside
+ * webhook.send({
+ * content: 'This is an embed',
+ * embeds: [{
+ * thumbnail: {
+ * url: 'attachment://file.jpg'
+ * }
+ * }],
+ * files: [{
+ * attachment: 'entire/path/to/file.jpg',
+ * name: 'file.jpg'
+ * }]
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ async send(options) {
+ if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
- /**
- * Sends a message with this webhook.
- * @param {string|MessagePayload|WebhookMessageOptions} options The options to provide
- * @returns {Promise}
- * @example
- * // Send a basic message
- * webhook.send('hello!')
- * .then(message => console.log(`Sent message: ${message.content}`))
- * .catch(console.error);
- * @example
- * // Send a basic message in a thread
- * webhook.send({ content: 'hello!', threadId: '836856309672348295' })
- * .then(message => console.log(`Sent message: ${message.content}`))
- * .catch(console.error);
- * @example
- * // Send a remote file
- * webhook.send({
- * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
- * })
- * .then(console.log)
- * .catch(console.error);
- * @example
- * // Send a local file
- * webhook.send({
- * files: [{
- * attachment: 'entire/path/to/file.jpg',
- * name: 'file.jpg'
- * }]
- * })
- * .then(console.log)
- * .catch(console.error);
- * @example
- * // Send an embed with a local image inside
- * webhook.send({
- * content: 'This is an embed',
- * embeds: [{
- * thumbnail: {
- * url: 'attachment://file.jpg'
- * }
- * }],
- * files: [{
- * attachment: 'entire/path/to/file.jpg',
- * name: 'file.jpg'
- * }]
- * })
- * .then(console.log)
- * .catch(console.error);
- */
- async send(options) {
- if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
+ let messagePayload;
- let messagePayload;
+ if (options instanceof MessagePayload) {
+ messagePayload = options.resolveData();
+ } else {
+ messagePayload = MessagePayload.create(this, options).resolveData();
+ }
- if (options instanceof MessagePayload) {
- messagePayload = options.resolveBody();
- } else {
- messagePayload = MessagePayload.create(this, options).resolveBody();
- }
+ const { data, files } = await messagePayload.resolveFiles();
+ const d = await this.client.api.webhooks(this.id, this.token).post({
+ data,
+ files,
+ query: { thread_id: messagePayload.options.threadId, wait: true },
+ auth: false,
+ });
+ return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? d;
+ }
- const query = new URLSearchParams({ wait: true });
+ /**
+ * Sends a raw slack message with this webhook.
+ * @param {Object} body The raw body to send
+ * @returns {Promise}
+ * @example
+ * // Send a slack message
+ * webhook.sendSlackMessage({
+ * 'username': 'Wumpus',
+ * 'attachments': [{
+ * 'pretext': 'this looks pretty cool',
+ * 'color': '#F0F',
+ * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png',
+ * 'footer': 'Powered by sneks',
+ * 'ts': Date.now() / 1_000
+ * }]
+ * }).catch(console.error);
+ * @see {@link https://api.slack.com/messaging/webhooks}
+ */
+ async sendSlackMessage(body) {
+ if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
- if (messagePayload.options.threadId) {
- query.set('thread_id', messagePayload.options.threadId);
- }
+ const data = await this.client.api.webhooks(this.id, this.token).slack.post({
+ query: { wait: true },
+ auth: false,
+ data: body,
+ });
+ return data.toString() === 'ok';
+ }
- const { body, files } = await messagePayload.resolveFiles();
- const d = await this.client.api.webhooks(this.id, this.token).post({
- body,
- files,
- query,
- auth: false,
- });
- return (
- this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ??
- d
- );
- }
+ /**
+ * Options used to edit a {@link Webhook}.
+ * @typedef {Object} WebhookEditData
+ * @property {string} [name=this.name] The new name for the webhook
+ * @property {?(BufferResolvable)} [avatar] The new avatar for the webhook
+ * @property {GuildTextChannelResolvable} [channel] The new channel for the webhook
+ */
- /**
- * Sends a raw slack message with this webhook.
- * @param {Object} body The raw body to send
- * @returns {Promise}
- * @example
- * // Send a slack message
- * webhook.sendSlackMessage({
- * 'username': 'Wumpus',
- * 'attachments': [{
- * 'pretext': 'this looks pretty cool',
- * 'color': '#F0F',
- * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png',
- * 'footer': 'Powered by sneks',
- * 'ts': Date.now() / 1_000
- * }]
- * }).catch(console.error);
- * @see {@link https://api.slack.com/messaging/webhooks}
- */
- async sendSlackMessage(body) {
- if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
+ /**
+ * Edits this webhook.
+ * @param {WebhookEditData} options Options for editing the webhook
+ * @param {string} [reason] Reason for editing the webhook
+ * @returns {Promise}
+ */
+ async edit({ name = this.name, avatar, channel }, reason) {
+ if (avatar && !(typeof avatar === 'string' && avatar.startsWith('data:'))) {
+ avatar = await DataResolver.resolveImage(avatar);
+ }
+ channel &&= channel.id ?? channel;
+ const data = await this.client.api.webhooks(this.id, channel ? undefined : this.token).patch({
+ data: { name, avatar, channel_id: channel },
+ reason,
+ auth: !this.token || Boolean(channel),
+ });
- const data = await this.client.api
- .webhooks(this.id, this.token)
- .slack.post({
- query: new URLSearchParams({ wait: true }),
- auth: false,
- body,
- });
- return data.toString() === 'ok';
- }
+ this.name = data.name;
+ this.avatar = data.avatar;
+ this.channelId = data.channel_id;
+ return this;
+ }
- /**
- * Options used to edit a {@link Webhook}.
- * @typedef {Object} WebhookEditData
- * @property {string} [name=this.name] The new name for the webhook
- * @property {?(BufferResolvable)} [avatar] The new avatar for the webhook
- * @property {GuildTextChannelResolvable} [channel] The new channel for the webhook
- */
+ /**
+ * Options that can be passed into fetchMessage.
+ * @typedef {options} WebhookFetchMessageOptions
+ * @property {boolean} [cache=true] Whether to cache the message.
+ * @property {Snowflake} [threadId] The id of the thread this message belongs to.
+ * For interaction webhooks, this property is ignored
+ */
- /**
- * Edits this webhook.
- * @param {WebhookEditData} options Options for editing the webhook
- * @param {string} [reason] Reason for editing the webhook
- * @returns {Promise}
- */
- async edit({ name = this.name, avatar, channel }, reason) {
- if (avatar && !(typeof avatar === 'string' && avatar.startsWith('data:'))) {
- avatar = await DataResolver.resolveImage(avatar);
- }
- channel &&= channel.id ?? channel;
- const data = await this.client.api
- .webhooks(this.id, channel ? undefined : this.token)
- .patch({
- data: { name, avatar, channel_id: channel },
- reason,
- auth: !this.token || Boolean(channel),
- });
+ /**
+ * Gets a message that was sent by this webhook.
+ * @param {Snowflake|'@original'} message The id of the message to fetch
+ * @param {WebhookFetchMessageOptions|boolean} [cacheOrOptions={}] The options to provide to fetch the message.
+ * A **deprecated** boolean may be passed instead to specify whether to cache the message.
+ * @returns {Promise} Returns the raw message data if the webhook was instantiated as a
+ * {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned
+ */
+ async fetchMessage(message, cacheOrOptions = { cache: true }) {
+ if (typeof cacheOrOptions === 'boolean') {
+ if (!deprecationEmittedForFetchMessage) {
+ process.emitWarning(
+ 'Passing a boolean to cache the message in Webhook#fetchMessage is deprecated. Pass an object instead.',
+ 'DeprecationWarning',
+ );
- this.name = data.name;
- this.avatar = data.avatar;
- this.channelId = data.channel_id;
- return this;
- }
+ deprecationEmittedForFetchMessage = true;
+ }
- /**
- * Options that can be passed into fetchMessage.
- * @typedef {options} WebhookFetchMessageOptions
- * @property {boolean} [cache=true] Whether to cache the message.
- * @property {Snowflake} [threadId] The id of the thread this message belongs to.
- * For interaction webhooks, this property is ignored
- */
+ cacheOrOptions = { cache: cacheOrOptions };
+ }
- /**
- * Gets a message that was sent by this webhook.
- * @param {Snowflake|'@original'} message The id of the message to fetch
- * @param {WebhookFetchMessageOptions} [options={}] The options to provide to fetch the message.
- * @returns {Promise} Returns the raw message data if the webhook was instantiated as a
- * {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned
- */
- async fetchMessage(message, { cache = true, threadId } = {}) {
- if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
+ if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
- const data = await this.client.api
- .webhooks(this.id, this.token)
- .messages(message)
- .get({
- query: threadId
- ? new URLSearchParams({
- thread_id: threadId,
- })
- : undefined,
- auth: false,
- });
- return (
- this.client.channels?.cache
- .get(data.channel_id)
- ?.messages._add(data, cache) ?? data
- );
- }
+ const data = await this.client.api
+ .webhooks(this.id, this.token)
+ .messages(message)
+ .get({
+ query: {
+ thread_id: cacheOrOptions.threadId,
+ },
+ auth: false,
+ });
+ return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cacheOrOptions.cache) ?? data;
+ }
- /**
- * Edits a message that was sent by this webhook.
- * @param {MessageResolvable|'@original'} message The message to edit
- * @param {string|MessagePayload|WebhookEditMessageOptions} options The options to provide
- * @returns {Promise} Returns the raw message data if the webhook was instantiated as a
- * {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned
- */
- async editMessage(message, options) {
- if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
+ /**
+ * Edits a message that was sent by this webhook.
+ * @param {MessageResolvable|'@original'} message The message to edit
+ * @param {string|MessagePayload|WebhookEditMessageOptions} options The options to provide
+ * @returns {Promise} Returns the raw message data if the webhook was instantiated as a
+ * {@link WebhookClient} or if the channel is uncached, otherwise a {@link Message} will be returned
+ */
+ async editMessage(message, options) {
+ if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
- let messagePayload;
+ let messagePayload;
- if (options instanceof MessagePayload) messagePayload = options;
- else messagePayload = MessagePayload.create(this, options);
+ if (options instanceof MessagePayload) messagePayload = options;
+ else messagePayload = MessagePayload.create(this, options);
- const { body, files } = await messagePayload.resolveBody().resolveFiles();
+ const { data, files } = await messagePayload.resolveData().resolveFiles();
- const d = await this.client.api
- .webhooks(this.id, this.token)
- .messages(typeof message === 'string' ? message : message.id)
- .patch({
- body,
- files,
- query: messagePayload.options.threadId
- ? new URLSearchParams({ thread_id: messagePayload.options.threadId })
- : undefined,
- auth: false,
- });
+ const d = await this.client.api
+ .webhooks(this.id, this.token)
+ .messages(typeof message === 'string' ? message : message.id)
+ .patch({
+ data,
+ files,
+ query: {
+ thread_id: messagePayload.options.threadId,
+ },
+ auth: false,
+ });
- const messageManager = this.client.channels?.cache.get(
- d.channel_id,
- )?.messages;
- if (!messageManager) return d;
+ const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages;
+ if (!messageManager) return d;
- const existing = messageManager.cache.get(d.id);
- if (!existing) return messageManager._add(d);
+ const existing = messageManager.cache.get(d.id);
+ if (!existing) return messageManager._add(d);
- const clone = existing._clone();
- clone._patch(d);
- return clone;
- }
+ const clone = existing._clone();
+ clone._patch(d);
+ return clone;
+ }
- /**
- * Deletes the webhook.
- * @param {string} [reason] Reason for deleting this webhook
- * @returns {Promise}
- */
- async delete(reason) {
- await this.client.api
- .webhooks(this.id, this.token)
- .delete({ reason, auth: !this.token });
- }
+ /**
+ * Deletes the webhook.
+ * @param {string} [reason] Reason for deleting this webhook
+ * @returns {Promise}
+ */
+ async delete(reason) {
+ await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token });
+ }
- /**
- * Delete a message that was sent by this webhook.
- * @param {MessageResolvable|'@original'} message The message to delete
- * @param {Snowflake} [threadId] The id of the thread this message belongs to
- * @returns {Promise}
- */
- async deleteMessage(message, threadId) {
- if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
+ /**
+ * Delete a message that was sent by this webhook.
+ * @param {MessageResolvable|'@original'} message The message to delete
+ * @param {Snowflake} [threadId] The id of the thread this message belongs to
+ * @returns {Promise}
+ */
+ async deleteMessage(message, threadId) {
+ if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE');
- await this.client.api
- .webhooks(this.id, this.token)
- .messages(typeof message === 'string' ? message : message.id)
- .delete({
- query: threadId
- ? new URLSearchParams({
- thread_id: threadId,
- })
- : undefined,
- auth: false,
- });
- }
+ await this.client.api
+ .webhooks(this.id, this.token)
+ .messages(typeof message === 'string' ? message : message.id)
+ .delete({
+ query: {
+ thread_id: threadId,
+ },
+ auth: false,
+ });
+ }
- /**
- * The timestamp the webhook was created at
- * @type {number}
- * @readonly
- */
- get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
- }
+ /**
+ * The timestamp the webhook was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return SnowflakeUtil.timestampFrom(this.id);
+ }
- /**
- * The time the webhook was created at
- * @type {Date}
- * @readonly
- */
- get createdAt() {
- return new Date(this.createdTimestamp);
- }
+ /**
+ * The time the webhook was created at
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
- /**
- * The URL of this webhook
- * @type {string}
- * @readonly
- */
- get url() {
- return this.client.options.rest.api + Routes.webhook(this.id, this.token);
- }
+ /**
+ * The URL of this webhook
+ * @type {string}
+ * @readonly
+ */
+ get url() {
+ return this.client.options.http.api + this.client.api.webhooks(this.id, this.token);
+ }
- /**
- * A link to the webhook's avatar.
- * @param {ImageURLOptions} [options={}] Options for the image URL
- * @returns {?string}
- */
- avatarURL(options = {}) {
- return (
- this.avatar && this.client.rest.cdn.avatar(this.id, this.avatar, options)
- );
- }
+ /**
+ * A link to the webhook's avatar.
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
+ * @returns {?string}
+ */
+ avatarURL({ format, size } = {}) {
+ if (!this.avatar) return null;
+ return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size);
+ }
- /**
- * Whether this webhook is created by a user.
- * @returns {boolean}
- */
- isUserCreated() {
- return Boolean(
- this.type === WebhookType.Incoming && this.owner && !this.owner.bot,
- );
- }
+ /**
+ * Whether or not this webhook is a channel follower webhook.
+ * @returns {boolean}
+ */
+ isChannelFollower() {
+ return this.type === 'Channel Follower';
+ }
- /**
- * Whether this webhook is created by an application.
- * @returns {boolean}
- */
- isApplicationCreated() {
- return this.type === WebhookType.Application;
- }
+ /**
+ * Whether or not this webhook is an incoming webhook.
+ * @returns {boolean}
+ */
+ isIncoming() {
+ return this.type === 'Incoming';
+ }
- /**
- * Whether or not this webhook is a channel follower webhook.
- * @returns {boolean}
- */
- isChannelFollower() {
- return this.type === WebhookType.ChannelFollower;
- }
-
- /**
- * Whether or not this webhook is an incoming webhook.
- * @returns {boolean}
- */
- isIncoming() {
- return this.type === WebhookType.Incoming;
- }
-
- static applyToClass(structure, ignore = []) {
- for (const prop of [
- 'send',
- 'sendSlackMessage',
- 'fetchMessage',
- 'edit',
- 'editMessage',
- 'delete',
- 'deleteMessage',
- 'createdTimestamp',
- 'createdAt',
- 'url',
- ]) {
- if (ignore.includes(prop)) continue;
- Object.defineProperty(
- structure.prototype,
- prop,
- Object.getOwnPropertyDescriptor(Webhook.prototype, prop),
- );
- }
- }
+ static applyToClass(structure, ignore = []) {
+ for (const prop of [
+ 'send',
+ 'sendSlackMessage',
+ 'fetchMessage',
+ 'edit',
+ 'editMessage',
+ 'delete',
+ 'deleteMessage',
+ 'createdTimestamp',
+ 'createdAt',
+ 'url',
+ ]) {
+ if (ignore.includes(prop)) continue;
+ Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop));
+ }
+ }
}
module.exports = Webhook;
diff --git a/src/structures/Widget.js b/src/structures/Widget.js
index bf6b725..7373d0a 100644
--- a/src/structures/Widget.js
+++ b/src/structures/Widget.js
@@ -1,7 +1,6 @@
'use strict';
const { Collection } = require('@discordjs/collection');
-const { Routes } = require('discord-api-types/v9');
const Base = require('./Base');
const WidgetMember = require('./WidgetMember');
diff --git a/src/structures/interfaces/Application.js b/src/structures/interfaces/Application.js
index 766da07..f1c02e0 100644
--- a/src/structures/interfaces/Application.js
+++ b/src/structures/interfaces/Application.js
@@ -1,8 +1,11 @@
'use strict';
-const { DiscordSnowflake } = require('@sapphire/snowflake');
+const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
+const SnowflakeUtil = require('../../util/SnowflakeUtil');
const Base = require('../Base');
+const AssetTypes = Object.keys(ClientApplicationAssetTypes);
+
/**
* Represents an OAuth2 Application.
* @abstract
@@ -10,12 +13,13 @@ const Base = require('../Base');
class Application extends Base {
constructor(client, data) {
super(client);
- this._patch(data);
+
+ if (data) {
+ this._patch(data);
+ }
}
_patch(data) {
- if(!data) return;
-
/**
* The application's id
* @type {Snowflake}
@@ -59,7 +63,7 @@ class Application extends Base {
* @readonly
*/
get createdTimestamp() {
- return DiscordSnowflake.timestampFrom(this.id);
+ return SnowflakeUtil.timestampFrom(this.id);
}
/**
@@ -73,20 +77,43 @@ class Application extends Base {
/**
* A link to the application's icon.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- iconURL(options = {}) {
- return this.icon && this.client.rest.cdn.appIcon(this.id, this.icon, options);
+ iconURL({ format, size } = {}) {
+ if (!this.icon) return null;
+ return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
}
/**
* A link to this application's cover image.
- * @param {ImageURLOptions} [options={}] Options for the image URL
+ * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
- coverURL(options = {}) {
- return this.cover && this.client.rest.cdn.appIcon(this.id, this.cover, options);
+ coverURL({ format, size } = {}) {
+ if (!this.cover) return null;
+ return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
+ }
+
+ /**
+ * Asset data.
+ * @typedef {Object} ApplicationAsset
+ * @property {Snowflake} id The asset's id
+ * @property {string} name The asset's name
+ * @property {string} type The asset's type
+ */
+
+ /**
+ * Gets the application's rich presence assets.
+ * @returns {Promise>}
+ */
+ async fetchAssets() {
+ const assets = await this.client.api.oauth2.applications(this.id).assets.get();
+ return assets.map(a => ({
+ id: a.id,
+ name: a.name,
+ type: AssetTypes[a.type - 1],
+ }));
}
/**
diff --git a/src/structures/interfaces/Collector.js b/src/structures/interfaces/Collector.js
index 20cb71e..c0793fd 100644
--- a/src/structures/interfaces/Collector.js
+++ b/src/structures/interfaces/Collector.js
@@ -1,7 +1,7 @@
'use strict';
const EventEmitter = require('node:events');
-const { setTimeout, clearTimeout } = require('node:timers');
+const { setTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { TypeError } = require('../../errors');
const Util = require('../../util/Util');
@@ -236,7 +236,7 @@ class Collector extends EventEmitter {
*/
async *[Symbol.asyncIterator]() {
const queue = [];
- const onCollect = (...item) => queue.push(item);
+ const onCollect = item => queue.push(item);
this.on('collect', onCollect);
try {
diff --git a/src/structures/interfaces/InteractionResponses.js b/src/structures/interfaces/InteractionResponses.js
index 15c8830..dbe7c23 100644
--- a/src/structures/interfaces/InteractionResponses.js
+++ b/src/structures/interfaces/InteractionResponses.js
@@ -1,7 +1,8 @@
'use strict';
-const { InteractionResponseType, MessageFlags, Routes } = require('discord-api-types/v9');
const { Error } = require('../../errors');
+const { InteractionResponseTypes } = require('../../util/Constants');
+const MessageFlags = require('../../util/MessageFlags');
const MessagePayload = require('../MessagePayload');
/**
@@ -27,8 +28,6 @@ class InteractionResponses {
* @typedef {BaseMessageOptions} InteractionReplyOptions
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
* @property {boolean} [fetchReply] Whether to fetch the reply
- * @property {MessageFlags} [flags] Which flags to set for the message.
- * Only `MessageFlags.SuppressEmbeds` and `MessageFlags.Ephemeral` can be set.
*/
/**
@@ -56,14 +55,14 @@ class InteractionResponses {
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
this.ephemeral = options.ephemeral ?? false;
await this.client.api.interactions(this.id, this.token).callback.post({
- body: {
- type: InteractionResponseType.DeferredChannelMessageWithSource,
+ data: {
+ type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
- flags: options.ephemeral ? MessageFlags.Ephemeral : undefined,
+ flags: options.ephemeral ? MessageFlags.FLAGS.EPHEMERAL : undefined,
},
},
auth: false,
- })
+ });
this.deferred = true;
return options.fetchReply ? this.fetchReply() : undefined;
@@ -81,7 +80,7 @@ class InteractionResponses {
* .catch(console.error);
* @example
* // Create an ephemeral reply with an embed
- * const embed = new Embed().setDescription('Pong!');
+ * const embed = new MessageEmbed().setDescription('Pong!');
*
* interaction.reply({ embeds: [embed], ephemeral: true })
* .then(() => console.log('Reply sent.'))
@@ -95,11 +94,11 @@ class InteractionResponses {
if (options instanceof MessagePayload) messagePayload = options;
else messagePayload = MessagePayload.create(this, options);
- const { body: data, files } = await messagePayload.resolveBody().resolveFiles();
+ const { data, files } = await messagePayload.resolveData().resolveFiles();
await this.client.api.interactions(this.id, this.token).callback.post({
- body: {
- type: InteractionResponseType.ChannelMessageWithSource,
+ data: {
+ type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
data,
},
files,
@@ -180,8 +179,8 @@ class InteractionResponses {
async deferUpdate(options = {}) {
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
await this.client.api.interactions(this.id, this.token).callback.post({
- body: {
- type: InteractionResponseType.DeferredMessageUpdate,
+ data: {
+ type: InteractionResponseTypes.DEFERRED_MESSAGE_UPDATE,
},
auth: false,
});
@@ -210,11 +209,11 @@ class InteractionResponses {
if (options instanceof MessagePayload) messagePayload = options;
else messagePayload = MessagePayload.create(this, options);
- const { body: data, files } = await messagePayload.resolveBody().resolveFiles();
+ const { data, files } = await messagePayload.resolveData().resolveFiles();
await this.client.api.interactions(this.id, this.token).callback.post({
- body: {
- type: InteractionResponseType.UpdateMessage,
+ data: {
+ type: InteractionResponseTypes.UPDATE_MESSAGE,
data,
},
files,
diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js
index 484cf00..5d2daaa 100644
--- a/src/structures/interfaces/TextBasedChannel.js
+++ b/src/structures/interfaces/TextBasedChannel.js
@@ -1,377 +1,360 @@
'use strict';
-const FormData = require('form-data');
-const { Collection } = require('@discordjs/collection');
-const { DiscordSnowflake } = require('@sapphire/snowflake');
-const { InteractionType, Routes } = require('discord-api-types/v9');
-const { TypeError, Error } = require('../../errors');
-const InteractionCollector = require('../InteractionCollector');
+
+/* eslint-disable import/order */
const MessageCollector = require('../MessageCollector');
const MessagePayload = require('../MessagePayload');
-const DiscordAPIError = require('../../rest/DiscordAPIError');
+const SnowflakeUtil = require('../../util/SnowflakeUtil');
+const { Collection } = require('@discordjs/collection');
+const { InteractionTypes } = require('../../util/Constants');
+const { TypeError, Error } = require('../../errors');
+const InteractionCollector = require('../InteractionCollector');
/**
* Interface for classes that have text-channel-like features.
* @interface
*/
class TextBasedChannel {
- constructor() {
- /**
- * A manager of the messages sent to this channel
- * @type {MessageManager}
- */
- this.messages = new MessageManager(this);
+ constructor() {
+ /**
+ * A manager of the messages sent to this channel
+ * @type {MessageManager}
+ */
+ this.messages = new MessageManager(this);
- /**
- * The channel's last message id, if one was sent
- * @type {?Snowflake}
- */
- this.lastMessageId = null;
+ /**
+ * The channel's last message id, if one was sent
+ * @type {?Snowflake}
+ */
+ this.lastMessageId = null;
- /**
- * The timestamp when the last pinned message was pinned, if there was one
- * @type {?number}
- */
- this.lastPinTimestamp = null;
- }
+ /**
+ * The timestamp when the last pinned message was pinned, if there was one
+ * @type {?number}
+ */
+ this.lastPinTimestamp = null;
+ }
- /**
- * The Message object of the last message in the channel, if one was sent
- * @type {?Message}
- * @readonly
- */
- get lastMessage() {
- return this.messages.resolve(this.lastMessageId);
- }
+ /**
+ * The Message object of the last message in the channel, if one was sent
+ * @type {?Message}
+ * @readonly
+ */
+ get lastMessage() {
+ return this.messages.resolve(this.lastMessageId);
+ }
- /**
- * The date when the last pinned message was pinned, if there was one
- * @type {?Date}
- * @readonly
- */
- get lastPinAt() {
- return this.lastPinTimestamp && new Date(this.lastPinTimestamp);
- }
+ /**
+ * The date when the last pinned message was pinned, if there was one
+ * @type {?Date}
+ * @readonly
+ */
+ get lastPinAt() {
+ return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
+ }
- /**
- * Base options provided when sending.
- * @typedef {Object} BaseMessageOptions
- * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
- * @property {string} [nonce=''] The nonce for the message
- * @property {string} [content=''] The content for the message
- * @property {Embed[]|APIEmbed[]} [embeds] The embeds for the message
- * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
- * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
- * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
- * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message
- * @property {ActionRow[]|ActionRowOptions[]} [components]
- * Action rows containing interactive components for the message (buttons, select menus)
- * @property {MessageAttachment[]} [attachments] Attachments to send in the message
- */
+ /**
+ * Base options provided when sending.
+ * @typedef {Object} BaseMessageOptions
+ * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
+ * @property {string} [nonce=''] The nonce for the message
+ * @property {string} [content=''] The content for the message
+ * @property {MessageEmbed[]|APIEmbed[]} [embeds] The embeds for the message
+ * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
+ * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
+ * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
+ * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message
+ * @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
+ * Action rows containing interactive components for the message (buttons, select menus)
+ * @property {MessageAttachment[]} [attachments] Attachments to send in the message
+ */
- /**
- * Options provided when sending or editing a message.
- * @typedef {BaseMessageOptions} MessageOptions
- * @property {ReplyOptions} [reply] The options for replying to a message
- * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
- * @property {MessageFlags} [flags] Which flags to set for the message. Only `MessageFlags.SuppressEmbeds` can be set.
- */
+ /**
+ * Options provided when sending or editing a message.
+ * @typedef {BaseMessageOptions} MessageOptions
+ * @property {ReplyOptions} [reply] The options for replying to a message
+ * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
+ */
- /**
- * Options provided to control parsing of mentions by Discord
- * @typedef {Object} MessageMentionOptions
- * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
- * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
- * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
- * @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged
- */
+ /**
+ * Options provided to control parsing of mentions by Discord
+ * @typedef {Object} MessageMentionOptions
+ * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
+ * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
+ * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
+ * @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged
+ */
- /**
- * Types of mentions to enable in MessageMentionOptions.
- * - `roles`
- * - `users`
- * - `everyone`
- * @typedef {string} MessageMentionTypes
- */
+ /**
+ * Types of mentions to enable in MessageMentionOptions.
+ * - `roles`
+ * - `users`
+ * - `everyone`
+ * @typedef {string} MessageMentionTypes
+ */
- /**
- * @typedef {Object} FileOptions
- * @property {BufferResolvable} attachment File to attach
- * @property {string} [name='file.jpg'] Filename of the attachment
- * @property {string} description The description of the file
- */
+ /**
+ * @typedef {Object} FileOptions
+ * @property {BufferResolvable} attachment File to attach
+ * @property {string} [name='file.jpg'] Filename of the attachment
+ * @property {string} description The description of the file
+ */
- /**
- * Options for sending a message with a reply.
- * @typedef {Object} ReplyOptions
- * @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system)
- * @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced
- * message does not exist (creates a standard message in this case when false)
- */
+ /**
+ * Options for sending a message with a reply.
+ * @typedef {Object} ReplyOptions
+ * @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system)
+ * @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
+ * does not exist (creates a standard message in this case when false)
+ */
- /**
- * Sends a message to this channel.
- * @param {string|MessagePayload|MessageOptions} options The options to provide
- * @returns {Promise}
- * @example
- * // Send a basic message
- * channel.send('hello!')
- * .then(message => console.log(`Sent message: ${message.content}`))
- * .catch(console.error);
- * @example
- * // Send a remote file
- * channel.send({
- * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
- * })
- * .then(console.log)
- * .catch(console.error);
- * @example
- * // Send a local file
- * channel.send({
- * files: [{
- * attachment: 'entire/path/to/file.jpg',
- * name: 'file.jpg',
- * description: 'A description of the file'
- * }]
- * })
- * .then(console.log)
- * .catch(console.error);
- * @example
- * // Send an embed with a local image inside
- * channel.send({
- * content: 'This is an embed',
- * embeds: [
- * {
- * thumbnail: {
- * url: 'attachment://file.jpg'
- * }
- * }
- * ],
- * files: [{
- * attachment: 'entire/path/to/file.jpg',
- * name: 'file.jpg',
- * description: 'A description of the file'
- * }]
- * })
- * .then(console.log)
- * .catch(console.error);
- */
- async send(options) {
- await this.client.api.channels(this.id).typing.post();
- const User = require('../User');
- const { GuildMember } = require('../GuildMember');
+ /**
+ * Sends a message to this channel.
+ * @param {string|MessagePayload|MessageOptions} options The options to provide
+ * @returns {Promise}
+ * @example
+ * // Send a basic message
+ * channel.send('hello!')
+ * .then(message => console.log(`Sent message: ${message.content}`))
+ * .catch(console.error);
+ * @example
+ * // Send a remote file
+ * channel.send({
+ * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Send a local file
+ * channel.send({
+ * files: [{
+ * attachment: 'entire/path/to/file.jpg',
+ * name: 'file.jpg'
+ * description: 'A description of the file'
+ * }]
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Send an embed with a local image inside
+ * channel.send({
+ * content: 'This is an embed',
+ * embeds: [
+ * {
+ * thumbnail: {
+ * url: 'attachment://file.jpg'
+ * }
+ * }
+ * ],
+ * files: [{
+ * attachment: 'entire/path/to/file.jpg',
+ * name: 'file.jpg'
+ * description: 'A description of the file'
+ * }]
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ async send(options) {
+ const User = require('../User');
+ const { GuildMember } = require('../GuildMember');
- if (this instanceof User || this instanceof GuildMember) {
- const dm = await this.createDM();
- return dm.send(options);
- }
+ if (this instanceof User || this instanceof GuildMember) {
+ const dm = await this.createDM();
+ return dm.send(options);
+ }
- let messagePayload;
+ let messagePayload;
- if (options instanceof MessagePayload) {
- messagePayload = options.resolveBody();
- } else {
- messagePayload = MessagePayload.create(this, options).resolveBody();
- }
+ if (options instanceof MessagePayload) {
+ messagePayload = options.resolveData();
+ } else {
+ messagePayload = MessagePayload.create(this, options).resolveData();
+ }
- const { body, files } = await messagePayload.resolveFiles();
- console.log(body)
- const d = await this.client.api.channels[this.id].messages.post({ body, files });
- return this.messages.cache.get(d.id) ?? this.messages._add(d);
- }
+ const { data, files } = await messagePayload.resolveFiles();
+ const d = await this.client.api.channels[this.id].messages.post({ data, files });
- // Patch send message [fck :(]
- /**
- * Sends a typing indicator in the channel.
- * @returns {Promise} Resolves upon the typing status being sent
- * @example
- * // Start typing in a channel
- * channel.sendTyping();
- */
- async sendTyping() {
- await this.client.api.channels(this.id).typing.post();
- }
+ return this.messages.cache.get(d.id) ?? this.messages._add(d);
+ }
- /**
- * Creates a Message Collector.
- * @param {MessageCollectorOptions} [options={}] The options to pass to the collector
- * @returns {MessageCollector}
- * @example
- * // Create a message collector
- * const filter = m => m.content.includes('discord');
- * const collector = channel.createMessageCollector({ filter, time: 15_000 });
- * collector.on('collect', m => console.log(`Collected ${m.content}`));
- * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
- */
- createMessageCollector(options = {}) {
- return new MessageCollector(this, options);
- }
+ /**
+ * Sends a typing indicator in the channel.
+ * @returns {Promise} Resolves upon the typing status being sent
+ * @example
+ * // Start typing in a channel
+ * channel.sendTyping();
+ */
+ async sendTyping() {
+ await this.client.api.channels(this.id).typing.post();
+ }
- /**
- * An object containing the same properties as CollectorOptions, but a few more:
- * @typedef {MessageCollectorOptions} AwaitMessagesOptions
- * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
- */
+ /**
+ * Creates a Message Collector.
+ * @param {MessageCollectorOptions} [options={}] The options to pass to the collector
+ * @returns {MessageCollector}
+ * @example
+ * // Create a message collector
+ * const filter = m => m.content.includes('discord');
+ * const collector = channel.createMessageCollector({ filter, time: 15_000 });
+ * collector.on('collect', m => console.log(`Collected ${m.content}`));
+ * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
+ */
+ createMessageCollector(options = {}) {
+ return new MessageCollector(this, options);
+ }
- /**
- * Similar to createMessageCollector but in promise form.
- * Resolves with a collection of messages that pass the specified filter.
- * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
- * @returns {Promise>}
- * @example
- * // Await !vote messages
- * const filter = m => m.content.startsWith('!vote');
- * // Errors: ['time'] treats ending because of the time limit as an error
- * channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] })
- * .then(collected => console.log(collected.size))
- * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
- */
- awaitMessages(options = {}) {
- return new Promise((resolve, reject) => {
- const collector = this.createMessageCollector(options);
- collector.once('end', (collection, reason) => {
- if (options.errors?.includes(reason)) {
- reject(collection);
- } else {
- resolve(collection);
- }
- });
- });
- }
+ /**
+ * An object containing the same properties as CollectorOptions, but a few more:
+ * @typedef {MessageCollectorOptions} AwaitMessagesOptions
+ * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
+ */
- /**
- * Creates a component interaction collector.
- * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
- * @returns {InteractionCollector}
- * @example
- * // Create a button interaction collector
- * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
- * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
- * collector.on('collect', i => console.log(`Collected ${i.customId}`));
- * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
- */
- createMessageComponentCollector(options = {}) {
- return new InteractionCollector(this.client, {
- ...options,
- interactionType: InteractionType.MessageComponent,
- channel: this,
- });
- }
+ /**
+ * Similar to createMessageCollector but in promise form.
+ * Resolves with a collection of messages that pass the specified filter.
+ * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
+ * @returns {Promise>}
+ * @example
+ * // Await !vote messages
+ * const filter = m => m.content.startsWith('!vote');
+ * // Errors: ['time'] treats ending because of the time limit as an error
+ * channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] })
+ * .then(collected => console.log(collected.size))
+ * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
+ */
+ awaitMessages(options = {}) {
+ return new Promise((resolve, reject) => {
+ const collector = this.createMessageCollector(options);
+ collector.once('end', (collection, reason) => {
+ if (options.errors?.includes(reason)) {
+ reject(collection);
+ } else {
+ resolve(collection);
+ }
+ });
+ });
+ }
- /**
- * Collects a single component interaction that passes the filter.
- * The Promise will reject if the time expires.
- * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
- * @returns {Promise}
- * @example
- * // Collect a message component interaction
- * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
- * channel.awaitMessageComponent({ filter, time: 15_000 })
- * .then(interaction => console.log(`${interaction.customId} was clicked!`))
- * .catch(console.error);
- */
- awaitMessageComponent(options = {}) {
- const _options = { ...options, max: 1 };
- return new Promise((resolve, reject) => {
- const collector = this.createMessageComponentCollector(_options);
- collector.once('end', (interactions, reason) => {
- const interaction = interactions.first();
- if (interaction) resolve(interaction);
- else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
- });
- });
- }
+ /**
+ * Creates a button interaction collector.
+ * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
+ * @returns {InteractionCollector}
+ * @example
+ * // Create a button interaction collector
+ * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
+ * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
+ * collector.on('collect', i => console.log(`Collected ${i.customId}`));
+ * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
+ */
+ createMessageComponentCollector(options = {}) {
+ return new InteractionCollector(this.client, {
+ ...options,
+ interactionType: InteractionTypes.MESSAGE_COMPONENT,
+ channel: this,
+ });
+ }
- /**
- * Bulk deletes given messages that are newer than two weeks.
- * @param {Collection|MessageResolvable[]|number} messages
- * Messages or number of messages to delete
- * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
- * @returns {Promise>} Returns the deleted messages
- * @example
- * // Bulk delete messages
- * channel.bulkDelete(5)
- * .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
- * .catch(console.error);
- */
- async bulkDelete(messages, filterOld = false) {
- if (Array.isArray(messages) || messages instanceof Collection) {
- let messageIds =
- messages instanceof Collection
- ? [...messages.keys()]
- : messages.map((m) => m.id ?? m);
- if (filterOld) {
- messageIds = messageIds.filter(
- (id) =>
- Date.now() - DiscordSnowflake.timestampFrom(id) < 1_209_600_000,
- );
- }
- if (messageIds.length === 0) return new Collection();
- if (messageIds.length === 1) {
- await this.client.api
- .channels(this.id)
- .messages(messageIds[0])
- .delete();
- const message = this.client.actions.MessageDelete.getMessage(
- {
- message_id: messageIds[0],
- },
- this,
- );
- return message
- ? new Collection([[message.id, message]])
- : new Collection();
- }
- await this.client.api
- .channels(this.id)
- .messages['bulk-delete'].post({ body: { messages: messageIds } });
- return messageIds.reduce(
- (col, id) =>
- col.set(
- id,
- this.client.actions.MessageDeleteBulk.getMessage(
- {
- message_id: id,
- },
- this,
- ),
- ),
- new Collection(),
- );
- }
- if (!isNaN(messages)) {
- const msgs = await this.messages.fetch({ limit: messages });
- return this.bulkDelete(msgs, filterOld);
- }
- throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
- }
+ /**
+ * Collects a single component interaction that passes the filter.
+ * The Promise will reject if the time expires.
+ * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
+ * @returns {Promise}
+ * @example
+ * // Collect a message component interaction
+ * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
+ * channel.awaitMessageComponent({ filter, time: 15_000 })
+ * .then(interaction => console.log(`${interaction.customId} was clicked!`))
+ * .catch(console.error);
+ */
+ awaitMessageComponent(options = {}) {
+ const _options = { ...options, max: 1 };
+ return new Promise((resolve, reject) => {
+ const collector = this.createMessageComponentCollector(_options);
+ collector.once('end', (interactions, reason) => {
+ const interaction = interactions.first();
+ if (interaction) resolve(interaction);
+ else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
+ });
+ });
+ }
- static applyToClass(structure, full = false, ignore = []) {
- const props = ['send'];
- if (full) {
- props.push(
- 'lastMessage',
- 'lastPinAt',
- 'bulkDelete',
- 'sendTyping',
- 'createMessageCollector',
- 'awaitMessages',
- 'createMessageComponentCollector',
- 'awaitMessageComponent',
- );
- }
- for (const prop of props) {
- if (ignore.includes(prop)) continue;
- Object.defineProperty(
- structure.prototype,
- prop,
- Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop),
- );
- }
- }
+ /**
+ * Bulk deletes given messages that are newer than two weeks.
+ * @param {Collection|MessageResolvable[]|number} messages
+ * Messages or number of messages to delete
+ * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
+ * @returns {Promise>} Returns the deleted messages
+ * @example
+ * // Bulk delete messages
+ * channel.bulkDelete(5)
+ * .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
+ * .catch(console.error);
+ */
+ async bulkDelete(messages, filterOld = false) {
+ if (Array.isArray(messages) || messages instanceof Collection) {
+ let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m);
+ if (filterOld) {
+ messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < 1_209_600_000);
+ }
+ if (messageIds.length === 0) return new Collection();
+ if (messageIds.length === 1) {
+ await this.client.api.channels(this.id).messages(messageIds[0]).delete();
+ const message = this.client.actions.MessageDelete.getMessage(
+ {
+ message_id: messageIds[0],
+ },
+ this,
+ );
+ return message ? new Collection([[message.id, message]]) : new Collection();
+ }
+ await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
+ return messageIds.reduce(
+ (col, id) =>
+ col.set(
+ id,
+ this.client.actions.MessageDeleteBulk.getMessage(
+ {
+ message_id: id,
+ },
+ this,
+ ),
+ ),
+ new Collection(),
+ );
+ }
+ if (!isNaN(messages)) {
+ const msgs = await this.messages.fetch({ limit: messages });
+ return this.bulkDelete(msgs, filterOld);
+ }
+ throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
+ }
+
+ static applyToClass(structure, full = false, ignore = []) {
+ const props = ['send'];
+ if (full) {
+ props.push(
+ 'lastMessage',
+ 'lastPinAt',
+ 'bulkDelete',
+ 'sendTyping',
+ 'createMessageCollector',
+ 'awaitMessages',
+ 'createMessageComponentCollector',
+ 'awaitMessageComponent',
+ );
+ }
+ for (const prop of props) {
+ if (ignore.includes(prop)) continue;
+ Object.defineProperty(
+ structure.prototype,
+ prop,
+ Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop),
+ );
+ }
+ }
}
module.exports = TextBasedChannel;
// Fixes Circular
-// eslint-disable-next-line import/order
const MessageManager = require('../../managers/MessageManager');
diff --git a/src/util/ActivityFlags.js b/src/util/ActivityFlags.js
new file mode 100644
index 00000000..e874ed2
--- /dev/null
+++ b/src/util/ActivityFlags.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with an {@link Activity#flags} bitfield.
+ * @extends {BitField}
+ */
+class ActivityFlags extends BitField {}
+
+/**
+ * @name ActivityFlags
+ * @kind constructor
+ * @memberof ActivityFlags
+ * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
+ */
+
+/**
+ * Numeric activity flags. All available properties:
+ * * `INSTANCE`
+ * * `JOIN`
+ * * `SPECTATE`
+ * * `JOIN_REQUEST`
+ * * `SYNC`
+ * * `PLAY`
+ * * `PARTY_PRIVACY_FRIENDS`
+ * * `PARTY_PRIVACY_VOICE_CHANNEL`
+ * * `EMBEDDED`
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/topics/gateway#activity-object-activity-flags}
+ */
+ActivityFlags.FLAGS = {
+ INSTANCE: 1 << 0,
+ JOIN: 1 << 1,
+ SPECTATE: 1 << 2,
+ JOIN_REQUEST: 1 << 3,
+ SYNC: 1 << 4,
+ PLAY: 1 << 5,
+ PARTY_PRIVACY_FRIENDS: 1 << 6,
+ PARTY_PRIVACY_VOICE_CHANNEL: 1 << 7,
+ EMBEDDED: 1 << 8,
+};
+
+module.exports = ActivityFlags;
diff --git a/src/util/ActivityFlagsBitField.js b/src/util/ActivityFlagsBitField.js
deleted file mode 100644
index 2de552f..00000000
--- a/src/util/ActivityFlagsBitField.js
+++ /dev/null
@@ -1,25 +0,0 @@
-'use strict';
-
-const { ActivityFlags } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to interact with an {@link Activity#flags} bitfield.
- * @extends {BitField}
- */
-class ActivityFlagsBitField extends BitField {}
-
-/**
- * @name ActivityFlagsBitField
- * @kind constructor
- * @memberof ActivityFlagsBitField
- * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
- */
-
-/**
- * Numeric activity flags.
- * @type {ActivityFlags}
- */
-ActivityFlagsBitField.Flags = ActivityFlags;
-
-module.exports = ActivityFlagsBitField;
diff --git a/src/util/ApplicationFlags.js b/src/util/ApplicationFlags.js
new file mode 100644
index 00000000..43e1682
--- /dev/null
+++ b/src/util/ApplicationFlags.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with a {@link ClientApplication#flags} bitfield.
+ * @extends {BitField}
+ */
+class ApplicationFlags extends BitField {}
+
+/**
+ * @name ApplicationFlags
+ * @kind constructor
+ * @memberof ApplicationFlags
+ * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
+ */
+
+/**
+ * Bitfield of the packed bits
+ * @type {number}
+ * @name ApplicationFlags#bitfield
+ */
+
+/**
+ * Numeric application flags. All available properties:
+ * * `GATEWAY_PRESENCE`
+ * * `GATEWAY_PRESENCE_LIMITED`
+ * * `GATEWAY_GUILD_MEMBERS`
+ * * `GATEWAY_GUILD_MEMBERS_LIMITED`
+ * * `VERIFICATION_PENDING_GUILD_LIMIT`
+ * * `EMBEDDED`
+ * * `GATEWAY_MESSAGE_CONTENT`
+ * * `GATEWAY_MESSAGE_CONTENT_LIMITED`
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/resources/application#application-object-application-flags}
+ */
+ApplicationFlags.FLAGS = {
+ GATEWAY_PRESENCE: 1 << 12,
+ GATEWAY_PRESENCE_LIMITED: 1 << 13,
+ GATEWAY_GUILD_MEMBERS: 1 << 14,
+ GATEWAY_GUILD_MEMBERS_LIMITED: 1 << 15,
+ VERIFICATION_PENDING_GUILD_LIMIT: 1 << 16,
+ EMBEDDED: 1 << 17,
+ GATEWAY_MESSAGE_CONTENT: 1 << 18,
+ GATEWAY_MESSAGE_CONTENT_LIMITED: 1 << 19,
+};
+
+module.exports = ApplicationFlags;
diff --git a/src/util/ApplicationFlagsBitField.js b/src/util/ApplicationFlagsBitField.js
deleted file mode 100644
index 885260e..00000000
--- a/src/util/ApplicationFlagsBitField.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-
-const { ApplicationFlags } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to interact with a {@link ClientApplication#flags} bitfield.
- * @extends {BitField}
- */
-class ApplicationFlagsBitField extends BitField {}
-
-/**
- * @name ApplicationFlagsBitField
- * @kind constructor
- * @memberof ApplicationFlagsBitField
- * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
- */
-
-/**
- * Bitfield of the packed bits
- * @type {number}
- * @name ApplicationFlagsBitField#bitfield
- */
-
-/**
- * Numeric application flags. All available properties:
- * @type {ApplicationFlags}
- */
-ApplicationFlagsBitField.Flags = ApplicationFlags;
-
-module.exports = ApplicationFlagsBitField;
diff --git a/src/util/BitField.js b/src/util/BitField.js
index c444d2d..c34f362 100644
--- a/src/util/BitField.js
+++ b/src/util/BitField.js
@@ -101,7 +101,7 @@ class BitField {
*/
serialize(...hasParams) {
const serialized = {};
- for (const [flag, bit] of Object.entries(this.constructor.Flags)) serialized[flag] = this.has(bit, ...hasParams);
+ for (const [flag, bit] of Object.entries(this.constructor.FLAGS)) serialized[flag] = this.has(bit, ...hasParams);
return serialized;
}
@@ -111,7 +111,7 @@ class BitField {
* @returns {string[]}
*/
toArray(...hasParams) {
- return Object.keys(this.constructor.Flags).filter(bit => this.has(bit, ...hasParams));
+ return Object.keys(this.constructor.FLAGS).filter(bit => this.has(bit, ...hasParams));
}
toJSON() {
@@ -128,7 +128,7 @@ class BitField {
/**
* Data that can be resolved to give a bitfield. This can be:
- * * A bit number (this can be a number literal or a value taken from {@link BitField.Flags})
+ * * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS})
* * A string bit number
* * An instance of BitField
* * An Array of BitFieldResolvable
@@ -146,7 +146,7 @@ class BitField {
if (bit instanceof BitField) return bit.bitfield;
if (Array.isArray(bit)) return bit.map(p => this.resolve(p)).reduce((prev, p) => prev | p, defaultBit);
if (typeof bit === 'string') {
- if (typeof this.Flags[bit] !== 'undefined') return this.Flags[bit];
+ if (typeof this.FLAGS[bit] !== 'undefined') return this.FLAGS[bit];
if (!isNaN(bit)) return typeof defaultBit === 'bigint' ? BigInt(bit) : Number(bit);
}
throw new RangeError('BITFIELD_INVALID', bit);
@@ -159,7 +159,7 @@ class BitField {
* @type {Object}
* @abstract
*/
-BitField.Flags = {};
+BitField.FLAGS = {};
/**
* @type {number|bigint}
diff --git a/src/util/Colors.js b/src/util/Colors.js
deleted file mode 100644
index 5b4a383..00000000
--- a/src/util/Colors.js
+++ /dev/null
@@ -1,34 +0,0 @@
-'use strict';
-
-module.exports = {
- Default: 0x000000,
- White: 0xffffff,
- Aqua: 0x1abc9c,
- Green: 0x57f287,
- Blue: 0x3498db,
- Yellow: 0xfee75c,
- Purple: 0x9b59b6,
- LuminousVividPink: 0xe91e63,
- Fuchsia: 0xeb459e,
- Gold: 0xf1c40f,
- Orange: 0xe67e22,
- Red: 0xed4245,
- Grey: 0x95a5a6,
- Navy: 0x34495e,
- DarkAqua: 0x11806a,
- DarkGreen: 0x1f8b4c,
- DarkBlue: 0x206694,
- DarkPurple: 0x71368a,
- DarkVividPink: 0xad1457,
- DarkGold: 0xc27c0e,
- DarkOrange: 0xa84300,
- DarkRed: 0x992d22,
- DarkGrey: 0x979c9f,
- DarkerGrey: 0x7f8c8d,
- LightGrey: 0xbcc0c0,
- DarkNavy: 0x2c3e50,
- Blurple: 0x5865f2,
- Greyple: 0x99aab5,
- DarkButNotBlack: 0x2c2f33,
- NotQuiteBlack: 0x23272a,
-};
diff --git a/src/util/Components.js b/src/util/Components.js
deleted file mode 100644
index 59a816b..00000000
--- a/src/util/Components.js
+++ /dev/null
@@ -1,44 +0,0 @@
-'use strict';
-
-/**
- * @typedef {Object} BaseComponentData
- * @property {ComponentType} type
- */
-
-/**
- * @typedef {BaseComponentData} ActionRowData
- * @property {ComponentData[]} components
- */
-
-/**
- * @typedef {BaseComponentData} ButtonComponentData
- * @property {ButtonStyle} style
- * @property {?boolean} disabled
- * @property {string} label
- * @property {?APIComponentEmoji} emoji
- * @property {?string} customId
- * @property {?string} url
- */
-
-/**
- * @typedef {object} SelectMenuComponentOptionData
- * @property {string} label
- * @property {string} value
- * @property {?string} description
- * @property {?APIComponentEmoji} emoji
- * @property {?boolean} default
- */
-
-/**
- * @typedef {BaseComponentData} SelectMenuComponentData
- * @property {string} customId
- * @property {?boolean} disabled
- * @property {?number} maxValues
- * @property {?number} minValues
- * @property {?SelectMenuComponentOptionData[]} options
- * @property {?string} placeholder
- */
-
-/**
- * @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} ComponentData
- */
diff --git a/src/util/Constants.js b/src/util/Constants.js
index 6bbe129..11c455e 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -1,117 +1,128 @@
'use strict';
const process = require('node:process');
-const { ChannelType, MessageType } = require('discord-api-types/v9');
const Package = (exports.Package = require('../../package.json'));
+const { Error, RangeError, TypeError } = require('../errors');
-exports.UserAgent = `Mozilla/5.0 (iPhone; CPU iPhone OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/90.0.4430.78 Mobile/15E148 Safari/604.1`;
+exports.UserAgent = `DiscordBot (${Package.homepage}, ${Package.version}) Node.js/${process.version}`;
+
+exports.WSCodes = {
+ 1000: 'WS_CLOSE_REQUESTED',
+ 4004: 'TOKEN_INVALID',
+ 4010: 'SHARDING_INVALID',
+ 4011: 'SHARDING_REQUIRED',
+ 4013: 'INVALID_INTENTS',
+ 4014: 'DISALLOWED_INTENTS',
+};
+
+const AllowedImageFormats = ['webp', 'png', 'jpg', 'jpeg', 'gif'];
+
+const AllowedImageSizes = [16, 32, 56, 64, 96, 128, 256, 300, 512, 600, 1024, 2048, 4096];
+
+function makeImageUrl(root, { format = 'webp', size } = {}) {
+ if (!['undefined', 'number'].includes(typeof size)) throw new TypeError('INVALID_TYPE', 'size', 'number');
+ if (format && !AllowedImageFormats.includes(format)) throw new Error('IMAGE_FORMAT', format);
+ if (size && !AllowedImageSizes.includes(size)) throw new RangeError('IMAGE_SIZE', size);
+ return `${root}.${format}${size ? `?size=${size}` : ''}`;
+}
/**
- * The name of an item to be swept in Sweepers
- * * `applicationCommands` - both global and guild commands
- * * `bans`
- * * `emojis`
- * * `invites` - accepts the `lifetime` property, using it will sweep based on expires timestamp
- * * `guildMembers`
- * * `messages` - accepts the `lifetime` property, using it will sweep based on edited or created timestamp
- * * `presences`
- * * `reactions`
- * * `stageInstances`
- * * `stickers`
- * * `threadMembers`
- * * `threads` - accepts the `lifetime` property, using it will sweep archived threads based on archived timestamp
- * * `users`
- * * `voiceStates`
- * @typedef {string} SweeperKey
- */
-exports.SweeperKeys = [
- 'applicationCommands',
- 'bans',
- 'emojis',
- 'invites',
- 'guildMembers',
- 'messages',
- 'presences',
- 'reactions',
- 'stageInstances',
- 'stickers',
- 'threadMembers',
- 'threads',
- 'users',
- 'voiceStates',
-];
-
-/**
- * The types of messages that are not `System`. The available types are:
- * * {@link MessageType.Default}
- * * {@link MessageType.Reply}
- * * {@link MessageType.ChatInputCommand}
- * * {@link MessageType.ContextMenuCommand}
- * @typedef {MessageType[]} NonSystemMessageTypes
- */
-exports.NonSystemMessageTypes = [
- MessageType.Default,
- MessageType.Reply,
- MessageType.ChatInputCommand,
- MessageType.ContextMenuCommand,
-];
-
-/**
- * The channels that are text-based.
- * * DMChannel
- * * TextChannel
- * * NewsChannel
- * * ThreadChannel
- * @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel} TextBasedChannels
+ * Options for Image URLs.
+ * @typedef {StaticImageURLOptions} ImageURLOptions
+ * @property {boolean} [dynamic=false] If true, the format will dynamically change to `gif` for animated avatars.
*/
/**
- * The types of channels that are text-based. The available types are:
- * * {@link ChannelType.DM}
- * * {@link ChannelType.GuildText}
- * * {@link ChannelType.GuildNews}
- * * {@link ChannelType.GuildNewsThread}
- * * {@link ChannelType.GuildPublicThread}
- * * {@link ChannelType.GuildPrivateThread}
- * @typedef {ChannelType} TextBasedChannelTypes
+ * Options for static Image URLs.
+ * @typedef {Object} StaticImageURLOptions
+ * @property {string} [format='webp'] One of `webp`, `png`, `jpg`, `jpeg`.
+ * @property {number} [size] One of `16`, `32`, `56`, `64`, `96`, `128`, `256`, `300`, `512`, `600`, `1024`, `2048`,
+ * `4096`
*/
-exports.TextBasedChannelTypes = [
- ChannelType.DM,
- ChannelType.GuildText,
- ChannelType.GuildNews,
- ChannelType.GuildNewsThread,
- ChannelType.GuildPublicThread,
- ChannelType.GuildPrivateThread,
-];
+
+// https://discord.com/developers/docs/reference#image-formatting-cdn-endpoints
+exports.Endpoints = {
+ CDN(root) {
+ return {
+ Emoji: (emojiId, format = 'webp') => `${root}/emojis/${emojiId}.${format}`,
+ Asset: name => `${root}/assets/${name}`,
+ DefaultAvatar: discriminator => `${root}/embed/avatars/${discriminator}.png`,
+ Avatar: (userId, hash, format, size, dynamic = false) => {
+ if (dynamic && hash.startsWith('a_')) format = 'gif';
+ return makeImageUrl(`${root}/avatars/${userId}/${hash}`, { format, size });
+ },
+ GuildMemberAvatar: (guildId, memberId, hash, format = 'webp', size, dynamic = false) => {
+ if (dynamic && hash.startsWith('a_')) format = 'gif';
+ return makeImageUrl(`${root}/guilds/${guildId}/users/${memberId}/avatars/${hash}`, { format, size });
+ },
+ Banner: (id, hash, format, size, dynamic = false) => {
+ if (dynamic && hash.startsWith('a_')) format = 'gif';
+ return makeImageUrl(`${root}/banners/${id}/${hash}`, { format, size });
+ },
+ Icon: (guildId, hash, format, size, dynamic = false) => {
+ if (dynamic && hash.startsWith('a_')) format = 'gif';
+ return makeImageUrl(`${root}/icons/${guildId}/${hash}`, { format, size });
+ },
+ AppIcon: (appId, hash, options) => makeImageUrl(`${root}/app-icons/${appId}/${hash}`, options),
+ AppAsset: (appId, hash, options) => makeImageUrl(`${root}/app-assets/${appId}/${hash}`, options),
+ StickerPackBanner: (bannerId, format, size) =>
+ makeImageUrl(`${root}/app-assets/710982414301790216/store/${bannerId}`, { size, format }),
+ GDMIcon: (channelId, hash, format, size) =>
+ makeImageUrl(`${root}/channel-icons/${channelId}/${hash}`, { size, format }),
+ Splash: (guildId, hash, format, size) => makeImageUrl(`${root}/splashes/${guildId}/${hash}`, { size, format }),
+ DiscoverySplash: (guildId, hash, format, size) =>
+ makeImageUrl(`${root}/discovery-splashes/${guildId}/${hash}`, { size, format }),
+ TeamIcon: (teamId, hash, options) => makeImageUrl(`${root}/team-icons/${teamId}/${hash}`, options),
+ Sticker: (stickerId, stickerFormat) =>
+ `${root}/stickers/${stickerId}.${stickerFormat === 'LOTTIE' ? 'json' : 'png'}`,
+ RoleIcon: (roleId, hash, format = 'webp', size) =>
+ makeImageUrl(`${root}/role-icons/${roleId}/${hash}`, { size, format }),
+ };
+ },
+ invite: (root, code, eventId) => (eventId ? `${root}/${code}?event=${eventId}` : `${root}/${code}`),
+ scheduledEvent: (root, guildId, eventId) => `${root}/${guildId}/${eventId}`,
+ botGateway: '/gateway/bot',
+};
/**
- * The types of channels that are threads. The available types are:
- * * {@link ChannelType.GuildNewsThread}
- * * {@link ChannelType.GuildPublicThread}
- * * {@link ChannelType.GuildPrivateThread}
- * @typedef {ChannelType[]} ThreadChannelTypes
- */
-exports.ThreadChannelTypes = [
- ChannelType.GuildNewsThread,
- ChannelType.GuildPublicThread,
- ChannelType.GuildPrivateThread,
-];
-
-/**
- * The types of channels that are voice-based. The available types are:
- * * {@link ChannelType.GuildVoice}
- * * {@link ChannelType.GuildStageVoice}
- * @typedef {ChannelType[]} VoiceBasedChannelTypes
- */
-exports.VoiceBasedChannelTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice];
-
-/* eslint-enable max-len */
-
-/**
- * @typedef {Object} Constants Constants that can be used in an enum or object-like way.
- * @property {Status} Status The available statuses of the client.
+ * The current status of the client. Here are the available statuses:
+ * * READY: 0
+ * * CONNECTING: 1
+ * * RECONNECTING: 2
+ * * IDLE: 3
+ * * NEARLY: 4
+ * * DISCONNECTED: 5
+ * * WAITING_FOR_GUILDS: 6
+ * * IDENTIFYING: 7
+ * * RESUMING: 8
+ * @typedef {number} Status
*/
+exports.Status = {
+ READY: 0,
+ CONNECTING: 1,
+ RECONNECTING: 2,
+ IDLE: 3,
+ NEARLY: 4,
+ DISCONNECTED: 5,
+ WAITING_FOR_GUILDS: 6,
+ IDENTIFYING: 7,
+ RESUMING: 8,
+};
+exports.Opcodes = {
+ DISPATCH: 0,
+ HEARTBEAT: 1,
+ IDENTIFY: 2,
+ STATUS_UPDATE: 3,
+ VOICE_STATE_UPDATE: 4,
+ VOICE_GUILD_PING: 5,
+ RESUME: 6,
+ RECONNECT: 7,
+ REQUEST_GUILD_MEMBERS: 8,
+ INVALID_SESSION: 9,
+ HELLO: 10,
+ HEARTBEAT_ACK: 11,
+};
exports.Events = {
RATE_LIMIT: 'rateLimit',
@@ -200,73 +211,1030 @@ exports.Events = {
GUILD_SCHEDULED_EVENT_USER_REMOVE: 'guildScheduledEventUserRemove',
};
-const AllowedImageFormats = ['webp', 'png', 'jpg', 'jpeg', 'gif'];
+exports.ShardEvents = {
+ CLOSE: 'close',
+ DESTROYED: 'destroyed',
+ INVALID_SESSION: 'invalidSession',
+ READY: 'ready',
+ RESUMED: 'resumed',
+ ALL_READY: 'allReady',
+};
-const AllowedImageSizes = [
- 16, 32, 56, 64, 96, 128, 256, 300, 512, 600, 1024, 2048, 4096,
+/**
+ * The type of Structure allowed to be a partial:
+ * * USER
+ * * CHANNEL (only affects DMChannels)
+ * * GUILD_MEMBER
+ * * MESSAGE
+ * * REACTION
+ * * GUILD_SCHEDULED_EVENT
+ * Partials require you to put checks in place when handling data. See the "Partial Structures" topic on the
+ * [guide](https://discordjs.guide/popular-topics/partials.html) for more information.
+ * @typedef {string} PartialType
+ */
+exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', 'REACTION', 'GUILD_SCHEDULED_EVENT']);
+
+/**
+ * The type of a WebSocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
+ * * READY
+ * * RESUMED
+ * * APPLICATION_COMMAND_CREATE (deprecated)
+ * * APPLICATION_COMMAND_DELETE (deprecated)
+ * * APPLICATION_COMMAND_UPDATE (deprecated)
+ * * GUILD_CREATE
+ * * GUILD_DELETE
+ * * GUILD_UPDATE
+ * * INVITE_CREATE
+ * * INVITE_DELETE
+ * * GUILD_MEMBER_ADD
+ * * GUILD_MEMBER_REMOVE
+ * * GUILD_MEMBER_UPDATE
+ * * GUILD_MEMBERS_CHUNK
+ * * GUILD_INTEGRATIONS_UPDATE
+ * * GUILD_ROLE_CREATE
+ * * GUILD_ROLE_DELETE
+ * * GUILD_ROLE_UPDATE
+ * * GUILD_BAN_ADD
+ * * GUILD_BAN_REMOVE
+ * * GUILD_EMOJIS_UPDATE
+ * * CHANNEL_CREATE
+ * * CHANNEL_DELETE
+ * * CHANNEL_UPDATE
+ * * CHANNEL_PINS_UPDATE
+ * * MESSAGE_CREATE
+ * * MESSAGE_DELETE
+ * * MESSAGE_UPDATE
+ * * MESSAGE_DELETE_BULK
+ * * MESSAGE_REACTION_ADD
+ * * MESSAGE_REACTION_REMOVE
+ * * MESSAGE_REACTION_REMOVE_ALL
+ * * MESSAGE_REACTION_REMOVE_EMOJI
+ * * THREAD_CREATE
+ * * THREAD_UPDATE
+ * * THREAD_DELETE
+ * * THREAD_LIST_SYNC
+ * * THREAD_MEMBER_UPDATE
+ * * THREAD_MEMBERS_UPDATE
+ * * USER_UPDATE
+ * * PRESENCE_UPDATE
+ * * TYPING_START
+ * * VOICE_STATE_UPDATE
+ * * VOICE_SERVER_UPDATE
+ * * WEBHOOKS_UPDATE
+ * * INTERACTION_CREATE
+ * * STAGE_INSTANCE_CREATE
+ * * STAGE_INSTANCE_UPDATE
+ * * STAGE_INSTANCE_DELETE
+ * * GUILD_STICKERS_UPDATE
+ * * GUILD_SCHEDULED_EVENT_CREATE
+ * * GUILD_SCHEDULED_EVENT_UPDATE
+ * * GUILD_SCHEDULED_EVENT_DELETE
+ * * GUILD_SCHEDULED_EVENT_USER_ADD
+ * * GUILD_SCHEDULED_EVENT_USER_REMOVE
+ * @typedef {string} WSEventType
+ * @see {@link https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events}
+ */
+exports.WSEvents = keyMirror([
+ 'READY',
+ 'RESUMED',
+ 'APPLICATION_COMMAND_CREATE',
+ 'APPLICATION_COMMAND_DELETE',
+ 'APPLICATION_COMMAND_UPDATE',
+ 'GUILD_CREATE',
+ 'GUILD_DELETE',
+ 'GUILD_UPDATE',
+ 'INVITE_CREATE',
+ 'INVITE_DELETE',
+ 'GUILD_MEMBER_ADD',
+ 'GUILD_MEMBER_REMOVE',
+ 'GUILD_MEMBER_UPDATE',
+ 'GUILD_MEMBERS_CHUNK',
+ 'GUILD_INTEGRATIONS_UPDATE',
+ 'GUILD_ROLE_CREATE',
+ 'GUILD_ROLE_DELETE',
+ 'GUILD_ROLE_UPDATE',
+ 'GUILD_BAN_ADD',
+ 'GUILD_BAN_REMOVE',
+ 'GUILD_EMOJIS_UPDATE',
+ 'CHANNEL_CREATE',
+ 'CHANNEL_DELETE',
+ 'CHANNEL_UPDATE',
+ 'CHANNEL_PINS_UPDATE',
+ 'MESSAGE_CREATE',
+ 'MESSAGE_DELETE',
+ 'MESSAGE_UPDATE',
+ 'MESSAGE_DELETE_BULK',
+ 'MESSAGE_REACTION_ADD',
+ 'MESSAGE_REACTION_REMOVE',
+ 'MESSAGE_REACTION_REMOVE_ALL',
+ 'MESSAGE_REACTION_REMOVE_EMOJI',
+ 'THREAD_CREATE',
+ 'THREAD_UPDATE',
+ 'THREAD_DELETE',
+ 'THREAD_LIST_SYNC',
+ 'THREAD_MEMBER_UPDATE',
+ 'THREAD_MEMBERS_UPDATE',
+ 'USER_UPDATE',
+ 'PRESENCE_UPDATE',
+ 'TYPING_START',
+ 'VOICE_STATE_UPDATE',
+ 'VOICE_SERVER_UPDATE',
+ 'WEBHOOKS_UPDATE',
+ 'INTERACTION_CREATE',
+ 'STAGE_INSTANCE_CREATE',
+ 'STAGE_INSTANCE_UPDATE',
+ 'STAGE_INSTANCE_DELETE',
+ 'GUILD_STICKERS_UPDATE',
+ 'GUILD_SCHEDULED_EVENT_CREATE',
+ 'GUILD_SCHEDULED_EVENT_UPDATE',
+ 'GUILD_SCHEDULED_EVENT_DELETE',
+ 'GUILD_SCHEDULED_EVENT_USER_ADD',
+ 'GUILD_SCHEDULED_EVENT_USER_REMOVE',
+]);
+
+/**
+ * A valid scope to request when generating an invite link.
+ * Scopes that require whitelist are not considered valid for this generator
+ * * `applications.builds.read`: allows reading build data for a users applications
+ * * `applications.commands`: allows this bot to create commands in the server
+ * * `applications.entitlements`: allows reading entitlements for a users applications
+ * * `applications.store.update`: allows reading and updating of store data for a users applications
+ * * `bot`: makes the bot join the selected guild
+ * * `connections`: makes the endpoint for getting a users connections available
+ * * `email`: allows the `/users/@me` endpoint return with an email
+ * * `identify`: allows the `/users/@me` endpoint without an email
+ * * `guilds`: makes the `/users/@me/guilds` endpoint available for a user
+ * * `guilds.join`: allows the bot to join the user to any guild it is in using Guild#addMember
+ * * `gdm.join`: allows joining the user to a group dm
+ * * `webhook.incoming`: generates a webhook to a channel
+ * @typedef {string} InviteScope
+ * @see {@link https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes}
+ */
+exports.InviteScopes = [
+ 'applications.builds.read',
+ 'applications.commands',
+ 'applications.entitlements',
+ 'applications.store.update',
+ 'bot',
+ 'connections',
+ 'email',
+ 'identify',
+ 'guilds',
+ 'guilds.join',
+ 'gdm.join',
+ 'webhook.incoming',
];
-function makeImageUrl(root, { format = 'webp', size } = {}) {
- if (!['undefined', 'number'].includes(typeof size)) throw new TypeError('INVALID_TYPE', 'size', 'number');
- if (format && !AllowedImageFormats.includes(format)) throw new Error('IMAGE_FORMAT', format);
- if (size && !AllowedImageSizes.includes(size)) throw new RangeError('IMAGE_SIZE', size);
- return `${root}.${format}${size ? `?size=${size}` : ''}`;
+// TODO: change Integration#expireBehavior to this and clean up Integration
+/**
+ * The behavior of expiring subscribers for Integrations. This can be:
+ * * REMOVE_ROLE
+ * * KICK
+ * @typedef {string} IntegrationExpireBehavior
+ * @see {@link https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors}
+ */
+exports.IntegrationExpireBehaviors = createEnum(['REMOVE_ROLE', 'KICK']);
+
+/**
+ * The type of a message, e.g. `DEFAULT`. Here are the available types:
+ * * DEFAULT
+ * * RECIPIENT_ADD
+ * * RECIPIENT_REMOVE
+ * * CALL
+ * * CHANNEL_NAME_CHANGE
+ * * CHANNEL_ICON_CHANGE
+ * * CHANNEL_PINNED_MESSAGE
+ * * GUILD_MEMBER_JOIN
+ * * USER_PREMIUM_GUILD_SUBSCRIPTION
+ * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1
+ * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2
+ * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3
+ * * CHANNEL_FOLLOW_ADD
+ * * GUILD_DISCOVERY_DISQUALIFIED
+ * * GUILD_DISCOVERY_REQUALIFIED
+ * * GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING
+ * * GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING
+ * * THREAD_CREATED
+ * * REPLY
+ * * APPLICATION_COMMAND
+ * * THREAD_STARTER_MESSAGE
+ * * GUILD_INVITE_REMINDER
+ * * CONTEXT_MENU_COMMAND
+ * @typedef {string} MessageType
+ * @see {@link https://discord.com/developers/docs/resources/channel#message-object-message-types}
+ */
+exports.MessageTypes = [
+ 'DEFAULT',
+ 'RECIPIENT_ADD',
+ 'RECIPIENT_REMOVE',
+ 'CALL',
+ 'CHANNEL_NAME_CHANGE',
+ 'CHANNEL_ICON_CHANGE',
+ 'CHANNEL_PINNED_MESSAGE',
+ 'GUILD_MEMBER_JOIN',
+ 'USER_PREMIUM_GUILD_SUBSCRIPTION',
+ 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1',
+ 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2',
+ 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3',
+ 'CHANNEL_FOLLOW_ADD',
+ null,
+ 'GUILD_DISCOVERY_DISQUALIFIED',
+ 'GUILD_DISCOVERY_REQUALIFIED',
+ 'GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING',
+ 'GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING',
+ 'THREAD_CREATED',
+ 'REPLY',
+ 'APPLICATION_COMMAND',
+ 'THREAD_STARTER_MESSAGE',
+ 'GUILD_INVITE_REMINDER',
+ 'CONTEXT_MENU_COMMAND',
+];
+
+/**
+ * The name of an item to be swept in Sweepers
+ * * `applicationCommands` - both global and guild commands
+ * * `bans`
+ * * `emojis`
+ * * `invites` - accepts the `lifetime` property, using it will sweep based on expires timestamp
+ * * `guildMembers`
+ * * `messages` - accepts the `lifetime` property, using it will sweep based on edited or created timestamp
+ * * `presences`
+ * * `reactions`
+ * * `stageInstances`
+ * * `stickers`
+ * * `threadMembers`
+ * * `threads` - accepts the `lifetime` property, using it will sweep archived threads based on archived timestamp
+ * * `users`
+ * * `voiceStates`
+ * @typedef {string} SweeperKey
+ */
+exports.SweeperKeys = [
+ 'applicationCommands',
+ 'bans',
+ 'emojis',
+ 'invites',
+ 'guildMembers',
+ 'messages',
+ 'presences',
+ 'reactions',
+ 'stageInstances',
+ 'stickers',
+ 'threadMembers',
+ 'threads',
+ 'users',
+ 'voiceStates',
+];
+
+/**
+ * The types of messages that are `System`. The available types are `MessageTypes` excluding:
+ * * DEFAULT
+ * * REPLY
+ * * APPLICATION_COMMAND
+ * * CONTEXT_MENU_COMMAND
+ * @typedef {string} SystemMessageType
+ */
+exports.SystemMessageTypes = exports.MessageTypes.filter(
+ type => type && !['DEFAULT', 'REPLY', 'APPLICATION_COMMAND', 'CONTEXT_MENU_COMMAND'].includes(type),
+);
+
+/**
+ * Bots cannot set a `CUSTOM` activity type, it is only for custom statuses received from users
+ * The type of an activity of a user's presence. Here are the available types:
+ * * PLAYING
+ * * STREAMING
+ * * LISTENING
+ * * WATCHING
+ * * CUSTOM
+ * * COMPETING
+ * @typedef {string} ActivityType
+ * @see {@link https://discord.com/developers/docs/game-sdk/activities#data-models-activitytype-enum}
+ */
+exports.ActivityTypes = createEnum(['PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM', 'COMPETING']);
+
+/**
+ * All available channel types:
+ * * `GUILD_TEXT` - a guild text channel
+ * * `DM` - a DM channel
+ * * `GUILD_VOICE` - a guild voice channel
+ * * `GROUP_DM` - a group DM channel
+ * * `GUILD_CATEGORY` - a guild category channel
+ * * `GUILD_NEWS` - a guild news channel
+ * * `GUILD_STORE` - a guild store channel
+ * Store channels are deprecated and will be removed from Discord in March 2022. See
+ * [Self-serve Game Selling Deprecation](https://support-dev.discord.com/hc/en-us/articles/4414590563479)
+ * for more information.
+ * * `GUILD_NEWS_THREAD` - a guild news channel's public thread channel
+ * * `GUILD_PUBLIC_THREAD` - a guild text channel's public thread channel
+ * * `GUILD_PRIVATE_THREAD` - a guild text channel's private thread channel
+ * * `GUILD_STAGE_VOICE` - a guild stage voice channel
+ * * `UNKNOWN` - a generic channel of unknown type, could be Channel or GuildChannel
+ * @typedef {string} ChannelType
+ * @see {@link https://discord.com/developers/docs/resources/channel#channel-object-channel-types}
+ */
+exports.ChannelTypes = createEnum([
+ 'GUILD_TEXT',
+ 'DM',
+ 'GUILD_VOICE',
+ 'GROUP_DM',
+ 'GUILD_CATEGORY',
+ 'GUILD_NEWS',
+ 'GUILD_STORE',
+ ...Array(3).fill(null),
+ // 10
+ 'GUILD_NEWS_THREAD',
+ 'GUILD_PUBLIC_THREAD',
+ 'GUILD_PRIVATE_THREAD',
+ 'GUILD_STAGE_VOICE',
+]);
+
+/**
+ * The channels that are text-based.
+ * * DMChannel
+ * * TextChannel
+ * * NewsChannel
+ * * ThreadChannel
+ * @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel} TextBasedChannels
+ */
+
+/**
+ * The types of channels that are text-based. The available types are:
+ * * DM
+ * * GUILD_TEXT
+ * * GUILD_NEWS
+ * * GUILD_NEWS_THREAD
+ * * GUILD_PUBLIC_THREAD
+ * * GUILD_PRIVATE_THREAD
+ * @typedef {string} TextBasedChannelTypes
+ */
+exports.TextBasedChannelTypes = [
+ 'DM',
+ 'GUILD_TEXT',
+ 'GUILD_NEWS',
+ 'GUILD_NEWS_THREAD',
+ 'GUILD_PUBLIC_THREAD',
+ 'GUILD_PRIVATE_THREAD',
+];
+
+/**
+ * The types of channels that are threads. The available types are:
+ * * GUILD_NEWS_THREAD
+ * * GUILD_PUBLIC_THREAD
+ * * GUILD_PRIVATE_THREAD
+ * @typedef {string} ThreadChannelTypes
+ */
+exports.ThreadChannelTypes = ['GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'];
+
+/**
+ * The types of channels that are voice-based. The available types are:
+ * * GUILD_VOICE
+ * * GUILD_STAGE_VOICE
+ * @typedef {string} VoiceBasedChannelTypes
+ */
+exports.VoiceBasedChannelTypes = ['GUILD_VOICE', 'GUILD_STAGE_VOICE'];
+
+exports.ClientApplicationAssetTypes = {
+ SMALL: 1,
+ BIG: 2,
+};
+
+exports.Colors = {
+ DEFAULT: 0x000000,
+ WHITE: 0xffffff,
+ AQUA: 0x1abc9c,
+ GREEN: 0x57f287,
+ BLUE: 0x3498db,
+ YELLOW: 0xfee75c,
+ PURPLE: 0x9b59b6,
+ LUMINOUS_VIVID_PINK: 0xe91e63,
+ FUCHSIA: 0xeb459e,
+ GOLD: 0xf1c40f,
+ ORANGE: 0xe67e22,
+ RED: 0xed4245,
+ GREY: 0x95a5a6,
+ NAVY: 0x34495e,
+ DARK_AQUA: 0x11806a,
+ DARK_GREEN: 0x1f8b4c,
+ DARK_BLUE: 0x206694,
+ DARK_PURPLE: 0x71368a,
+ DARK_VIVID_PINK: 0xad1457,
+ DARK_GOLD: 0xc27c0e,
+ DARK_ORANGE: 0xa84300,
+ DARK_RED: 0x992d22,
+ DARK_GREY: 0x979c9f,
+ DARKER_GREY: 0x7f8c8d,
+ LIGHT_GREY: 0xbcc0c0,
+ DARK_NAVY: 0x2c3e50,
+ BLURPLE: 0x5865f2,
+ GREYPLE: 0x99aab5,
+ DARK_BUT_NOT_BLACK: 0x2c2f33,
+ NOT_QUITE_BLACK: 0x23272a,
+};
+
+/**
+ * The value set for the explicit content filter levels for a guild:
+ * * DISABLED
+ * * MEMBERS_WITHOUT_ROLES
+ * * ALL_MEMBERS
+ * @typedef {string} ExplicitContentFilterLevel
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-explicit-content-filter-level}
+ */
+exports.ExplicitContentFilterLevels = createEnum(['DISABLED', 'MEMBERS_WITHOUT_ROLES', 'ALL_MEMBERS']);
+
+/**
+ * The value set for the verification levels for a guild:
+ * * NONE
+ * * LOW
+ * * MEDIUM
+ * * HIGH
+ * * VERY_HIGH
+ * @typedef {string} VerificationLevel
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-verification-level}
+ */
+exports.VerificationLevels = createEnum(['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH']);
+
+/**
+ * An error encountered while performing an API request. Here are the potential errors:
+ * * UNKNOWN_ACCOUNT
+ * * UNKNOWN_APPLICATION
+ * * UNKNOWN_CHANNEL
+ * * UNKNOWN_GUILD
+ * * UNKNOWN_INTEGRATION
+ * * UNKNOWN_INVITE
+ * * UNKNOWN_MEMBER
+ * * UNKNOWN_MESSAGE
+ * * UNKNOWN_OVERWRITE
+ * * UNKNOWN_PROVIDER
+ * * UNKNOWN_ROLE
+ * * UNKNOWN_TOKEN
+ * * UNKNOWN_USER
+ * * UNKNOWN_EMOJI
+ * * UNKNOWN_WEBHOOK
+ * * UNKNOWN_WEBHOOK_SERVICE
+ * * UNKNOWN_SESSION
+ * * UNKNOWN_BAN
+ * * UNKNOWN_SKU
+ * * UNKNOWN_STORE_LISTING
+ * * UNKNOWN_ENTITLEMENT
+ * * UNKNOWN_BUILD
+ * * UNKNOWN_LOBBY
+ * * UNKNOWN_BRANCH
+ * * UNKNOWN_STORE_DIRECTORY_LAYOUT
+ * * UNKNOWN_REDISTRIBUTABLE
+ * * UNKNOWN_GIFT_CODE
+ * * UNKNOWN_STREAM
+ * * UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN
+ * * UNKNOWN_GUILD_TEMPLATE
+ * * UNKNOWN_DISCOVERABLE_SERVER_CATEGORY
+ * * UNKNOWN_STICKER
+ * * UNKNOWN_INTERACTION
+ * * UNKNOWN_APPLICATION_COMMAND
+ * * UNKNOWN_APPLICATION_COMMAND_PERMISSIONS
+ * * UNKNOWN_STAGE_INSTANCE
+ * * UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM
+ * * UNKNOWN_GUILD_WELCOME_SCREEN
+ * * UNKNOWN_GUILD_SCHEDULED_EVENT
+ * * UNKNOWN_GUILD_SCHEDULED_EVENT_USER
+ * * BOT_PROHIBITED_ENDPOINT
+ * * BOT_ONLY_ENDPOINT
+ * * CANNOT_SEND_EXPLICIT_CONTENT
+ * * NOT_AUTHORIZED
+ * * SLOWMODE_RATE_LIMIT
+ * * ACCOUNT_OWNER_ONLY
+ * * ANNOUNCEMENT_EDIT_LIMIT_EXCEEDED
+ * * CHANNEL_HIT_WRITE_RATELIMIT
+ * * SERVER_HIT_WRITE_RATELIMIT
+ * * CONTENT_NOT_ALLOWED
+ * * GUILD_PREMIUM_LEVEL_TOO_LOW
+ * * MAXIMUM_GUILDS
+ * * MAXIMUM_FRIENDS
+ * * MAXIMUM_PINS
+ * * MAXIMUM_RECIPIENTS
+ * * MAXIMUM_ROLES
+ * * MAXIMUM_WEBHOOKS
+ * * MAXIMUM_EMOJIS
+ * * MAXIMUM_REACTIONS
+ * * MAXIMUM_CHANNELS
+ * * MAXIMUM_ATTACHMENTS
+ * * MAXIMUM_INVITES
+ * * MAXIMUM_ANIMATED_EMOJIS
+ * * MAXIMUM_SERVER_MEMBERS
+ * * MAXIMUM_NUMBER_OF_SERVER_CATEGORIES
+ * * GUILD_ALREADY_HAS_TEMPLATE
+ * * MAXIMUM_THREAD_PARTICIPANTS
+ * * MAXIMUM_NON_GUILD_MEMBERS_BANS
+ * * MAXIMUM_BAN_FETCHES
+ * * MAXIMUM_NUMBER_OF_UNCOMPLETED_GUILD_SCHEDULED_EVENTS_REACHED
+ * * MAXIMUM_NUMBER_OF_STICKERS_REACHED
+ * * MAXIMUM_PRUNE_REQUESTS
+ * * MAXIMUM_GUILD_WIDGET_SETTINGS_UPDATE
+ * * UNAUTHORIZED
+ * * ACCOUNT_VERIFICATION_REQUIRED
+ * * DIRECT_MESSAGES_TOO_FAST
+ * * REQUEST_ENTITY_TOO_LARGE
+ * * FEATURE_TEMPORARILY_DISABLED
+ * * USER_BANNED
+ * * TARGET_USER_NOT_CONNECTED_TO_VOICE
+ * * ALREADY_CROSSPOSTED
+ * * MISSING_ACCESS
+ * * INVALID_ACCOUNT_TYPE
+ * * CANNOT_EXECUTE_ON_DM
+ * * EMBED_DISABLED
+ * * CANNOT_EDIT_MESSAGE_BY_OTHER
+ * * CANNOT_SEND_EMPTY_MESSAGE
+ * * CANNOT_MESSAGE_USER
+ * * CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL
+ * * CHANNEL_VERIFICATION_LEVEL_TOO_HIGH
+ * * OAUTH2_APPLICATION_BOT_ABSENT
+ * * MAXIMUM_OAUTH2_APPLICATIONS
+ * * INVALID_OAUTH_STATE
+ * * MISSING_PERMISSIONS
+ * * INVALID_AUTHENTICATION_TOKEN
+ * * NOTE_TOO_LONG
+ * * INVALID_BULK_DELETE_QUANTITY
+ * * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL
+ * * INVALID_OR_TAKEN_INVITE_CODE
+ * * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE
+ * * CANNOT_EXECUTE_ON_CHANNEL_TYPE
+ * * INVALID_OAUTH_TOKEN
+ * * MISSING_OAUTH_SCOPE
+ * * INVALID_WEBHOOK_TOKEN
+ * * INVALID_ROLE
+ * * INVALID_RECIPIENTS
+ * * BULK_DELETE_MESSAGE_TOO_OLD
+ * * INVALID_FORM_BODY
+ * * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT
+ * * INVALID_API_VERSION
+ * * FILE_UPLOADED_EXCEEDS_MAXIMUM_SIZE
+ * * INVALID_FILE_UPLOADED
+ * * CANNOT_SELF_REDEEM_GIFT
+ * * INVALID_GUILD
+ * * PAYMENT_SOURCE_REQUIRED
+ * * CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL
+ * * INVALID_STICKER_SENT
+ * * INVALID_OPERATION_ON_ARCHIVED_THREAD
+ * * INVALID_THREAD_NOTIFICATION_SETTINGS
+ * * PARAMETER_EARLIER_THAN_CREATION
+ * * GUILD_NOT_AVAILABLE_IN_LOCATION
+ * * GUILD_MONETIZATION_REQUIRED
+ * * INSUFFICIENT_BOOSTS
+ * * INVALID_JSON
+ * * TWO_FACTOR_REQUIRED
+ * * NO_USERS_WITH_DISCORDTAG_EXIST
+ * * REACTION_BLOCKED
+ * * RESOURCE_OVERLOADED
+ * * STAGE_ALREADY_OPEN
+ * * CANNOT_REPLY_WITHOUT_READ_MESSAGE_HISTORY_PERMISSION
+ * * MESSAGE_ALREADY_HAS_THREAD
+ * * THREAD_LOCKED
+ * * MAXIMUM_ACTIVE_THREADS
+ * * MAXIMUM_ACTIVE_ANNOUNCEMENT_THREAD
+ * * INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE
+ * * UPLOADED_LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES
+ * * STICKER_MAXIMUM_FRAMERATE_EXCEEDED
+ * * STICKER_FRAME_COUNT_EXCEEDS_MAXIMUM_OF_1000_FRAMES
+ * * LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED
+ * * STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE
+ * * STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS
+ * * CANNOT_UPDATE_A_FINISHED_EVENT
+ * * FAILED_TO_CREATE_STAGE_NEEDED_FOR_STAGE_EVENT
+ * @typedef {string} APIError
+ * @see {@link https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes}
+ */
+exports.APIErrors = {
+ UNKNOWN_ACCOUNT: 10001,
+ UNKNOWN_APPLICATION: 10002,
+ UNKNOWN_CHANNEL: 10003,
+ UNKNOWN_GUILD: 10004,
+ UNKNOWN_INTEGRATION: 10005,
+ UNKNOWN_INVITE: 10006,
+ UNKNOWN_MEMBER: 10007,
+ UNKNOWN_MESSAGE: 10008,
+ UNKNOWN_OVERWRITE: 10009,
+ UNKNOWN_PROVIDER: 10010,
+ UNKNOWN_ROLE: 10011,
+ UNKNOWN_TOKEN: 10012,
+ UNKNOWN_USER: 10013,
+ UNKNOWN_EMOJI: 10014,
+ UNKNOWN_WEBHOOK: 10015,
+ UNKNOWN_WEBHOOK_SERVICE: 10016,
+ UNKNOWN_SESSION: 10020,
+ UNKNOWN_BAN: 10026,
+ UNKNOWN_SKU: 10027,
+ UNKNOWN_STORE_LISTING: 10028,
+ UNKNOWN_ENTITLEMENT: 10029,
+ UNKNOWN_BUILD: 10030,
+ UNKNOWN_LOBBY: 10031,
+ UNKNOWN_BRANCH: 10032,
+ UNKNOWN_STORE_DIRECTORY_LAYOUT: 10033,
+ UNKNOWN_REDISTRIBUTABLE: 10036,
+ UNKNOWN_GIFT_CODE: 10038,
+ UNKNOWN_STREAM: 10049,
+ UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: 10050,
+ UNKNOWN_GUILD_TEMPLATE: 10057,
+ UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: 10059,
+ UNKNOWN_STICKER: 10060,
+ UNKNOWN_INTERACTION: 10062,
+ UNKNOWN_APPLICATION_COMMAND: 10063,
+ UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: 10066,
+ UNKNOWN_STAGE_INSTANCE: 10067,
+ UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: 10068,
+ UNKNOWN_GUILD_WELCOME_SCREEN: 10069,
+ UNKNOWN_GUILD_SCHEDULED_EVENT: 10070,
+ UNKNOWN_GUILD_SCHEDULED_EVENT_USER: 10071,
+ BOT_PROHIBITED_ENDPOINT: 20001,
+ BOT_ONLY_ENDPOINT: 20002,
+ CANNOT_SEND_EXPLICIT_CONTENT: 20009,
+ NOT_AUTHORIZED: 20012,
+ SLOWMODE_RATE_LIMIT: 20016,
+ ACCOUNT_OWNER_ONLY: 20018,
+ ANNOUNCEMENT_EDIT_LIMIT_EXCEEDED: 20022,
+ CHANNEL_HIT_WRITE_RATELIMIT: 20028,
+ SERVER_HIT_WRITE_RATELIMIT: 20029,
+ CONTENT_NOT_ALLOWED: 20031,
+ GUILD_PREMIUM_LEVEL_TOO_LOW: 20035,
+ MAXIMUM_GUILDS: 30001,
+ MAXIMUM_FRIENDS: 30002,
+ MAXIMUM_PINS: 30003,
+ MAXIMUM_RECIPIENTS: 30004,
+ MAXIMUM_ROLES: 30005,
+ MAXIMUM_WEBHOOKS: 30007,
+ MAXIMUM_EMOJIS: 30008,
+ MAXIMUM_REACTIONS: 30010,
+ MAXIMUM_CHANNELS: 30013,
+ MAXIMUM_ATTACHMENTS: 30015,
+ MAXIMUM_INVITES: 30016,
+ MAXIMUM_ANIMATED_EMOJIS: 30018,
+ MAXIMUM_SERVER_MEMBERS: 30019,
+ MAXIMUM_NUMBER_OF_SERVER_CATEGORIES: 30030,
+ GUILD_ALREADY_HAS_TEMPLATE: 30031,
+ MAXIMUM_THREAD_PARTICIPANTS: 30033,
+ MAXIMUM_NON_GUILD_MEMBERS_BANS: 30035,
+ MAXIMUM_BAN_FETCHES: 30037,
+ MAXIMUM_NUMBER_OF_UNCOMPLETED_GUILD_SCHEDULED_EVENTS_REACHED: 30038,
+ MAXIMUM_NUMBER_OF_STICKERS_REACHED: 30039,
+ MAXIMUM_PRUNE_REQUESTS: 30040,
+ MAXIMUM_GUILD_WIDGET_SETTINGS_UPDATE: 30042,
+ UNAUTHORIZED: 40001,
+ ACCOUNT_VERIFICATION_REQUIRED: 40002,
+ DIRECT_MESSAGES_TOO_FAST: 40003,
+ REQUEST_ENTITY_TOO_LARGE: 40005,
+ FEATURE_TEMPORARILY_DISABLED: 40006,
+ USER_BANNED: 40007,
+ TARGET_USER_NOT_CONNECTED_TO_VOICE: 40032,
+ ALREADY_CROSSPOSTED: 40033,
+ MISSING_ACCESS: 50001,
+ INVALID_ACCOUNT_TYPE: 50002,
+ CANNOT_EXECUTE_ON_DM: 50003,
+ EMBED_DISABLED: 50004,
+ CANNOT_EDIT_MESSAGE_BY_OTHER: 50005,
+ CANNOT_SEND_EMPTY_MESSAGE: 50006,
+ CANNOT_MESSAGE_USER: 50007,
+ CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008,
+ CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009,
+ OAUTH2_APPLICATION_BOT_ABSENT: 50010,
+ MAXIMUM_OAUTH2_APPLICATIONS: 50011,
+ INVALID_OAUTH_STATE: 50012,
+ MISSING_PERMISSIONS: 50013,
+ INVALID_AUTHENTICATION_TOKEN: 50014,
+ NOTE_TOO_LONG: 50015,
+ INVALID_BULK_DELETE_QUANTITY: 50016,
+ CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019,
+ INVALID_OR_TAKEN_INVITE_CODE: 50020,
+ CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021,
+ CANNOT_EXECUTE_ON_CHANNEL_TYPE: 50024,
+ INVALID_OAUTH_TOKEN: 50025,
+ MISSING_OAUTH_SCOPE: 50026,
+ INVALID_WEBHOOK_TOKEN: 50027,
+ INVALID_ROLE: 50028,
+ INVALID_RECIPIENTS: 50033,
+ BULK_DELETE_MESSAGE_TOO_OLD: 50034,
+ INVALID_FORM_BODY: 50035,
+ INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036,
+ INVALID_API_VERSION: 50041,
+ FILE_UPLOADED_EXCEEDS_MAXIMUM_SIZE: 50045,
+ INVALID_FILE_UPLOADED: 50046,
+ CANNOT_SELF_REDEEM_GIFT: 50054,
+ INVALID_GUILD: 50055,
+ PAYMENT_SOURCE_REQUIRED: 50070,
+ CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074,
+ INVALID_STICKER_SENT: 50081,
+ INVALID_OPERATION_ON_ARCHIVED_THREAD: 50083,
+ INVALID_THREAD_NOTIFICATION_SETTINGS: 50084,
+ PARAMETER_EARLIER_THAN_CREATION: 50085,
+ GUILD_NOT_AVAILABLE_IN_LOCATION: 50095,
+ GUILD_MONETIZATION_REQUIRED: 50097,
+ INSUFFICIENT_BOOSTS: 50101,
+ INVALID_JSON: 50109,
+ TWO_FACTOR_REQUIRED: 60003,
+ NO_USERS_WITH_DISCORDTAG_EXIST: 80004,
+ REACTION_BLOCKED: 90001,
+ RESOURCE_OVERLOADED: 130000,
+ STAGE_ALREADY_OPEN: 150006,
+ CANNOT_REPLY_WITHOUT_READ_MESSAGE_HISTORY_PERMISSION: 160002,
+ MESSAGE_ALREADY_HAS_THREAD: 160004,
+ THREAD_LOCKED: 160005,
+ MAXIMUM_ACTIVE_THREADS: 160006,
+ MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007,
+ INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001,
+ UPLOADED_LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: 170002,
+ STICKER_MAXIMUM_FRAMERATE_EXCEEDED: 170003,
+ STICKER_FRAME_COUNT_EXCEEDS_MAXIMUM_OF_1000_FRAMES: 170004,
+ LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005,
+ STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE: 170006,
+ STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS: 170007,
+ CANNOT_UPDATE_A_FINISHED_EVENT: 180000,
+ FAILED_TO_CREATE_STAGE_NEEDED_FOR_STAGE_EVENT: 180002,
+};
+
+/**
+ * The value set for a guild's default message notifications, e.g. `ALL_MESSAGES`. Here are the available types:
+ * * ALL_MESSAGES
+ * * ONLY_MENTIONS
+ * @typedef {string} DefaultMessageNotificationLevel
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level}
+ */
+exports.DefaultMessageNotificationLevels = createEnum(['ALL_MESSAGES', 'ONLY_MENTIONS']);
+
+/**
+ * The value set for a team member's membership state:
+ * * INVITED
+ * * ACCEPTED
+ * @typedef {string} MembershipState
+ * @see {@link https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum}
+ */
+exports.MembershipStates = createEnum([null, 'INVITED', 'ACCEPTED']);
+
+/**
+ * The value set for a webhook's type:
+ * * Incoming
+ * * Channel Follower
+ * * Application
+ * @typedef {string} WebhookType
+ * @see {@link https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types}
+ */
+exports.WebhookTypes = createEnum([null, 'Incoming', 'Channel Follower', 'Application']);
+
+/**
+ * The value set for a sticker's type:
+ * * STANDARD
+ * * GUILD
+ * @typedef {string} StickerType
+ * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types}
+ */
+exports.StickerTypes = createEnum([null, 'STANDARD', 'GUILD']);
+
+/**
+ * The value set for a sticker's format type:
+ * * PNG
+ * * APNG
+ * * LOTTIE
+ * @typedef {string} StickerFormatType
+ * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types}
+ */
+exports.StickerFormatTypes = createEnum([null, 'PNG', 'APNG', 'LOTTIE']);
+
+/**
+ * An overwrite type:
+ * * role
+ * * member
+ * @typedef {string} OverwriteType
+ * @see {@link https://discord.com/developers/docs/resources/channel#overwrite-object-overwrite-structure}
+ */
+exports.OverwriteTypes = createEnum(['role', 'member']);
+
+/* eslint-disable max-len */
+/**
+ * The type of an {@link ApplicationCommand} object:
+ * * CHAT_INPUT
+ * * USER
+ * * MESSAGE
+ * @typedef {string} ApplicationCommandType
+ * @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types}
+ */
+exports.ApplicationCommandTypes = createEnum([null, 'CHAT_INPUT', 'USER', 'MESSAGE']);
+
+/**
+ * The type of an {@link ApplicationCommandOption} object:
+ * * SUB_COMMAND
+ * * SUB_COMMAND_GROUP
+ * * STRING
+ * * INTEGER
+ * * BOOLEAN
+ * * USER
+ * * CHANNEL
+ * * ROLE
+ * * MENTIONABLE
+ * * NUMBER
+ * @typedef {string} ApplicationCommandOptionType
+ * @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type}
+ */
+exports.ApplicationCommandOptionTypes = createEnum([
+ null,
+ 'SUB_COMMAND',
+ 'SUB_COMMAND_GROUP',
+ 'STRING',
+ 'INTEGER',
+ 'BOOLEAN',
+ 'USER',
+ 'CHANNEL',
+ 'ROLE',
+ 'MENTIONABLE',
+ 'NUMBER',
+]);
+
+/**
+ * The type of an {@link ApplicationCommandPermissions} object:
+ * * ROLE
+ * * USER
+ * @typedef {string} ApplicationCommandPermissionType
+ * @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type}
+ */
+exports.ApplicationCommandPermissionTypes = createEnum([null, 'ROLE', 'USER']);
+
+/**
+ * The type of an {@link Interaction} object:
+ * * PING
+ * * APPLICATION_COMMAND
+ * * MESSAGE_COMPONENT
+ * * APPLICATION_COMMAND_AUTOCOMPLETE
+ * @typedef {string} InteractionType
+ * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-type}
+ */
+exports.InteractionTypes = createEnum([
+ null,
+ 'PING',
+ 'APPLICATION_COMMAND',
+ 'MESSAGE_COMPONENT',
+ 'APPLICATION_COMMAND_AUTOCOMPLETE',
+]);
+
+/**
+ * The type of an interaction response:
+ * * PONG
+ * * CHANNEL_MESSAGE_WITH_SOURCE
+ * * DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
+ * * DEFERRED_MESSAGE_UPDATE
+ * * UPDATE_MESSAGE
+ * * APPLICATION_COMMAND_AUTOCOMPLETE_RESULT
+ * @typedef {string} InteractionResponseType
+ * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type}
+ */
+exports.InteractionResponseTypes = createEnum([
+ null,
+ 'PONG',
+ null,
+ null,
+ 'CHANNEL_MESSAGE_WITH_SOURCE',
+ 'DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE',
+ 'DEFERRED_MESSAGE_UPDATE',
+ 'UPDATE_MESSAGE',
+ 'APPLICATION_COMMAND_AUTOCOMPLETE_RESULT',
+]);
+
+/**
+ * The type of a message component
+ * * ACTION_ROW
+ * * BUTTON
+ * * SELECT_MENU
+ * @typedef {string} MessageComponentType
+ * @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types}
+ */
+exports.MessageComponentTypes = createEnum([null, 'ACTION_ROW', 'BUTTON', 'SELECT_MENU']);
+
+/**
+ * The style of a message button
+ * * PRIMARY
+ * * SECONDARY
+ * * SUCCESS
+ * * DANGER
+ * * LINK
+ * @typedef {string} MessageButtonStyle
+ * @see {@link https://discord.com/developers/docs/interactions/message-components#button-object-button-styles}
+ */
+exports.MessageButtonStyles = createEnum([null, 'PRIMARY', 'SECONDARY', 'SUCCESS', 'DANGER', 'LINK']);
+
+/**
+ * The required MFA level for a guild
+ * * NONE
+ * * ELEVATED
+ * @typedef {string} MFALevel
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-mfa-level}
+ */
+exports.MFALevels = createEnum(['NONE', 'ELEVATED']);
+
+/**
+ * NSFW level of a Guild:
+ * * DEFAULT
+ * * EXPLICIT
+ * * SAFE
+ * * AGE_RESTRICTED
+ * @typedef {string} NSFWLevel
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-guild-nsfw-level}
+ */
+exports.NSFWLevels = createEnum(['DEFAULT', 'EXPLICIT', 'SAFE', 'AGE_RESTRICTED']);
+
+/**
+ * Privacy level of a {@link StageInstance} object:
+ * * PUBLIC
+ * * GUILD_ONLY
+ * @typedef {string} PrivacyLevel
+ * @see {@link https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level}
+ */
+exports.PrivacyLevels = createEnum([null, 'PUBLIC', 'GUILD_ONLY']);
+
+/**
+ * Privacy level of a {@link GuildScheduledEvent} object:
+ * * GUILD_ONLY
+ * @typedef {string} GuildScheduledEventPrivacyLevel
+ * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level}
+ */
+exports.GuildScheduledEventPrivacyLevels = createEnum([null, null, 'GUILD_ONLY']);
+
+/**
+ * The premium tier (Server Boost level) of a guild:
+ * * NONE
+ * * TIER_1
+ * * TIER_2
+ * * TIER_3
+ * @typedef {string} PremiumTier
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-premium-tier}
+ */
+exports.PremiumTiers = createEnum(['NONE', 'TIER_1', 'TIER_2', 'TIER_3']);
+
+/**
+ * The status of a {@link GuildScheduledEvent}:
+ * * SCHEDULED
+ * * ACTIVE
+ * * COMPLETED
+ * * CANCELED
+ * @typedef {string} GuildScheduledEventStatus
+ * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status}
+ */
+exports.GuildScheduledEventStatuses = createEnum([null, 'SCHEDULED', 'ACTIVE', 'COMPLETED', 'CANCELED']);
+
+/**
+ * The entity type of a {@link GuildScheduledEvent}:
+ * * NONE
+ * * STAGE_INSTANCE
+ * * VOICE
+ * * EXTERNAL
+ * @typedef {string} GuildScheduledEventEntityType
+ * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types}
+ */
+exports.GuildScheduledEventEntityTypes = createEnum([null, 'STAGE_INSTANCE', 'VOICE', 'EXTERNAL']);
+/* eslint-enable max-len */
+
+exports._cleanupSymbol = Symbol('djsCleanup');
+
+function keyMirror(arr) {
+ let tmp = Object.create(null);
+ for (const value of arr) tmp[value] = value;
+ return tmp;
+}
+
+function createEnum(keys) {
+ const obj = {};
+ for (const [index, key] of keys.entries()) {
+ if (key === null) continue;
+ obj[key] = index;
+ obj[index] = key;
+ }
+ return obj;
}
/**
- * Options for Image URLs.
- * @typedef {StaticImageURLOptions} ImageURLOptions
- * @property {boolean} [dynamic=false] If true, the format will dynamically change to `gif` for animated avatars.
+ * @typedef {Object} Constants Constants that can be used in an enum or object-like way.
+ * @property {ActivityType} ActivityTypes The type of an activity of a users presence.
+ * @property {APIError} APIErrors An error encountered while performing an API request.
+ * @property {ApplicationCommandOptionType} ApplicationCommandOptionTypes
+ * The type of an {@link ApplicationCommandOption} object.
+ * @property {ApplicationCommandPermissionType} ApplicationCommandPermissionTypes
+ * The type of an {@link ApplicationCommandPermissions} object.
+ * @property {ChannelType} ChannelTypes All available channel types.
+ * @property {DefaultMessageNotificationLevel} DefaultMessageNotificationLevels
+ * The value set for a guild's default message notifications.
+ * @property {ExplicitContentFilterLevel} ExplicitContentFilterLevels
+ * The value set for the explicit content filter levels for a guild.
+ * @property {GuildScheduledEventStatus} GuildScheduledEventStatuses The status of a {@link GuildScheduledEvent} object.
+ * @property {GuildScheduledEventEntityType} GuildScheduledEventEntityTypes The entity type of a
+ * {@link GuildScheduledEvent} object.
+ * @property {GuildScheduledEventPrivacyLevel} GuildScheduledEventPrivacyLevels Privacy level of a
+ * {@link GuildScheduledEvent} object.
+ * @property {InteractionResponseType} InteractionResponseTypes The type of an interaction response.
+ * @property {InteractionType} InteractionTypes The type of an {@link Interaction} object.
+ * @property {MembershipState} MembershipStates The value set for a team member's membership state.
+ * @property {MessageButtonStyle} MessageButtonStyles The style of a message button.
+ * @property {MessageComponentType} MessageComponentTypes The type of a message component.
+ * @property {MFALevel} MFALevels The required MFA level for a guild.
+ * @property {NSFWLevel} NSFWLevels NSFW level of a guild.
+ * @property {OverwriteType} OverwriteTypes An overwrite type.
+ * @property {PartialType} PartialTypes The type of Structure allowed to be a partial.
+ * @property {PremiumTier} PremiumTiers The premium tier (Server Boost level) of a guild.
+ * @property {PrivacyLevel} PrivacyLevels Privacy level of a {@link StageInstance} object.
+ * @property {Status} Status The available statuses of the client.
+ * @property {StickerFormatType} StickerFormatTypes The value set for a sticker's format type.
+ * @property {StickerType} StickerTypes The value set for a sticker's type.
+ * @property {VerificationLevel} VerificationLevels The value set for the verification levels for a guild.
+ * @property {WebhookType} WebhookTypes The value set for a webhook's type.
+ * @property {WSEventType} WSEvents The type of a WebSocket message event.
*/
-
-/**
- * Options for static Image URLs.
- * @typedef {Object} StaticImageURLOptions
- * @property {string} [format='webp'] One of `webp`, `png`, `jpg`, `jpeg`.
- * @property {number} [size] One of `16`, `32`, `56`, `64`, `96`, `128`, `256`, `300`, `512`, `600`, `1024`, `2048`,
- * `4096`
- */
-
-// https://discord.com/developers/docs/reference#image-formatting-cdn-endpoints
-exports.Endpoints = {
- CDN(root) {
- return {
- Emoji: (emojiId, format = 'webp') => `${root}/emojis/${emojiId}.${format}`,
- Asset: name => `${root}/assets/${name}`,
- DefaultAvatar: discriminator => `${root}/embed/avatars/${discriminator}.png`,
- Avatar: (userId, hash, format, size, dynamic = false) => {
- if (dynamic && hash.startsWith('a_')) format = 'gif';
- return makeImageUrl(`${root}/avatars/${userId}/${hash}`, { format, size });
- },
- GuildMemberAvatar: (guildId, memberId, hash, format = 'webp', size, dynamic = false) => {
- if (dynamic && hash.startsWith('a_')) format = 'gif';
- return makeImageUrl(`${root}/guilds/${guildId}/users/${memberId}/avatars/${hash}`, { format, size });
- },
- Banner: (id, hash, format, size, dynamic = false) => {
- if (dynamic && hash.startsWith('a_')) format = 'gif';
- return makeImageUrl(`${root}/banners/${id}/${hash}`, { format, size });
- },
- Icon: (guildId, hash, format, size, dynamic = false) => {
- if (dynamic && hash.startsWith('a_')) format = 'gif';
- return makeImageUrl(`${root}/icons/${guildId}/${hash}`, { format, size });
- },
- AppIcon: (appId, hash, options) => makeImageUrl(`${root}/app-icons/${appId}/${hash}`, options),
- AppAsset: (appId, hash, options) => makeImageUrl(`${root}/app-assets/${appId}/${hash}`, options),
- StickerPackBanner: (bannerId, format, size) =>
- makeImageUrl(`${root}/app-assets/710982414301790216/store/${bannerId}`, { size, format }),
- GDMIcon: (channelId, hash, format, size) =>
- makeImageUrl(`${root}/channel-icons/${channelId}/${hash}`, { size, format }),
- Splash: (guildId, hash, format, size) => makeImageUrl(`${root}/splashes/${guildId}/${hash}`, { size, format }),
- DiscoverySplash: (guildId, hash, format, size) =>
- makeImageUrl(`${root}/discovery-splashes/${guildId}/${hash}`, { size, format }),
- TeamIcon: (teamId, hash, options) => makeImageUrl(`${root}/team-icons/${teamId}/${hash}`, options),
- Sticker: (stickerId, stickerFormat) =>
- `${root}/stickers/${stickerId}.${stickerFormat === 'LOTTIE' ? 'json' : 'png'}`,
- RoleIcon: (roleId, hash, format = 'webp', size) =>
- makeImageUrl(`${root}/role-icons/${roleId}/${hash}`, { size, format }),
- };
- },
- invite: (root, code, eventId) => (eventId ? `${root}/${code}?event=${eventId}` : `${root}/${code}`),
- scheduledEvent: (root, guildId, eventId) => `${root}/${guildId}/${eventId}`,
- botGateway: '/gateway/bot',
-};
\ No newline at end of file
diff --git a/src/util/DataResolver.js b/src/util/DataResolver.js
index e3ab586..d7d9eb7 100644
--- a/src/util/DataResolver.js
+++ b/src/util/DataResolver.js
@@ -1,10 +1,10 @@
'use strict';
const { Buffer } = require('node:buffer');
-const fs = require('node:fs/promises');
+const fs = require('node:fs');
const path = require('node:path');
const stream = require('node:stream');
-const { fetch } = require('undici');
+const fetch = require('node-fetch');
const { Error: DiscordError, TypeError } = require('../errors');
const Invite = require('../structures/Invite');
@@ -66,8 +66,8 @@ class DataResolver extends null {
if (typeof image === 'string' && image.startsWith('data:')) {
return image;
}
- const file = await this.resolveFile(image);
- return this.resolveBase64(file);
+ const file = await this.resolveFileAsBuffer(image);
+ return DataResolver.resolveBase64(file);
}
/**
@@ -102,34 +102,44 @@ class DataResolver extends null {
*/
/**
- * Resolves a BufferResolvable to a Buffer.
+ * Resolves a BufferResolvable to a Buffer or a Stream.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
- * @returns {Promise}
+ * @returns {Promise}
*/
static async resolveFile(resource) {
- if (Buffer.isBuffer(resource)) return resource;
-
- if (resource instanceof stream.Readable) {
- const buffers = [];
- for await (const data of resource) buffers.push(data);
- return Buffer.concat(buffers);
- }
-
+ if (Buffer.isBuffer(resource) || resource instanceof stream.Readable) return resource;
if (typeof resource === 'string') {
if (/^https?:\/\//.test(resource)) {
const res = await fetch(resource);
- return Buffer.from(await res.arrayBuffer());
+ return res.body;
}
- const file = path.resolve(resource);
-
- const stats = await fs.stat(file);
- if (!stats.isFile()) throw new DiscordError('FILE_NOT_FOUND', file);
- return fs.readFile(file);
+ return new Promise((resolve, reject) => {
+ const file = path.resolve(resource);
+ fs.stat(file, (err, stats) => {
+ if (err) return reject(err);
+ if (!stats.isFile()) return reject(new DiscordError('FILE_NOT_FOUND', file));
+ return resolve(fs.createReadStream(file));
+ });
+ });
}
throw new TypeError('REQ_RESOURCE_TYPE');
}
+
+ /**
+ * Resolves a BufferResolvable to a Buffer.
+ * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
+ * @returns {Promise}
+ */
+ static async resolveFileAsBuffer(resource) {
+ const file = await this.resolveFile(resource);
+ if (Buffer.isBuffer(file)) return file;
+
+ const buffers = [];
+ for await (const data of file) buffers.push(data);
+ return Buffer.concat(buffers);
+ }
}
module.exports = DataResolver;
diff --git a/src/util/Embeds.js b/src/util/Embeds.js
deleted file mode 100644
index d6294b2..00000000
--- a/src/util/Embeds.js
+++ /dev/null
@@ -1,48 +0,0 @@
-'use strict';
-
-/**
- * @typedef {Object} EmbedData
- * @property {?string} title
- * @property {?EmbedType} type
- * @property {?string} description
- * @property {?string} url
- * @property {?string} timestamp
- * @property {?number} color
- * @property {?EmbedFooterData} footer
- * @property {?EmbedImageData} image
- * @property {?EmbedImageData} thumbnail
- * @property {?EmbedProviderData} provider
- * @property {?EmbedAuthorData} author
- * @property {?EmbedFieldData[]} fields
- */
-
-/**
- * @typedef {Object} EmbedFooterData
- * @property {string} text
- * @property {?string} iconURL
- */
-
-/**
- * @typedef {Object} EmbedImageData
- * @property {?string} url
- */
-
-/**
- * @typedef {Object} EmbedProviderData
- * @property {?string} name
- * @property {?string} url
- */
-
-/**
- * @typedef {Object} EmbedAuthorData
- * @property {string} name
- * @property {?string} url
- * @property {?string} iconURL
- */
-
-/**
- * @typedef {Object} EmbedFieldData
- * @property {string} name
- * @property {string} value
- * @property {?boolean} inline
- */
diff --git a/src/util/EnumResolvers.js b/src/util/EnumResolvers.js
deleted file mode 100644
index 92684af..00000000
--- a/src/util/EnumResolvers.js
+++ /dev/null
@@ -1,819 +0,0 @@
-'use strict';
-
-const {
- ApplicationCommandType,
- InteractionType,
- ComponentType,
- ButtonStyle,
- ApplicationCommandOptionType,
- ChannelType,
- ApplicationCommandPermissionType,
- MessageType,
- GuildNSFWLevel,
- GuildVerificationLevel,
- GuildDefaultMessageNotifications,
- GuildExplicitContentFilter,
- GuildPremiumTier,
- GuildScheduledEventStatus,
- StageInstancePrivacyLevel,
- GuildMFALevel,
- TeamMemberMembershipState,
- GuildScheduledEventEntityType,
- IntegrationExpireBehavior,
- AuditLogEvent,
-} = require('discord-api-types/v9');
-
-function unknownKeyStrategy(val) {
- throw new Error(`Could not resolve enum value for ${val}`);
-}
-
-/**
- * Holds a bunch of methods to resolve enum values to readable strings.
- */
-class EnumResolvers extends null {
- /**
- * A string that can be resolved to a {@link ChannelType} enum value. Here are the available types:
- * * GUILD_TEXT
- * * DM
- * * GUILD_VOICE
- * * GROUP_DM
- * * GUILD_CATEGORY
- * * GUILD_NEWS
- * * GUILD_NEWS_THREAD
- * * GUILD_PUBLIC_THREAD
- * * GUILD_PRIVATE_THREAD
- * * GUILD_STAGE_VOICE
- * @typedef {string} ChannelTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link ChannelType} enum value
- * @param {ChannelTypeEnumResolvable|ChannelType} key The key to resolve
- * @returns {ChannelType}
- */
- static resolveChannelType(key) {
- switch (key) {
- case 'GUILD_TEXT':
- return ChannelType.GuildText;
- case 'DM':
- return ChannelType.DM;
- case 'GUILD_VOICE':
- return ChannelType.GuildVoice;
- case 'GROUP_DM':
- return ChannelType.GroupDM;
- case 'GUILD_CATEGORY':
- return ChannelType.GuildCategory;
- case 'GUILD_NEWS':
- return ChannelType.GuildNews;
- case 'GUILD_NEWS_THREAD':
- return ChannelType.GuildNewsThread;
- case 'GUILD_PUBLIC_THREAD':
- return ChannelType.GuildPublicThread;
- case 'GUILD_PRIVATE_THREAD':
- return ChannelType.GuildPrivateThread;
- case 'GUILD_STAGE_VOICE':
- return ChannelType.GuildStageVoice;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to an {@link InteractionType} enum value. Here are the available types:
- * * PING
- * * APPLICATION_COMMAND
- * * MESSAGE_COMPONENT
- * * APPLICATION_COMMAND_AUTOCOMPLETE
- * @typedef {string} InteractionTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link InteractionType} enum value
- * @param {InteractionTypeEnumResolvable|InteractionType} key The key to resolve
- * @returns {InteractionType}
- */
- static resolveInteractionType(key) {
- switch (key) {
- case 'PING':
- return InteractionType.Ping;
- case 'APPLICATION_COMMAND':
- return InteractionType.ApplicationCommand;
- case 'MESSAGE_COMPONENT':
- return InteractionType.MessageComponent;
- case 'APPLICATION_COMMAND_AUTOCOMPLETE':
- return InteractionType.ApplicationCommandAutocomplete;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to an {@link ApplicationCommandType} enum value. Here are the available types:
- * * CHAT_INPUT
- * * USER
- * * MESSAGE
- * @typedef {string} ApplicationCommandTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link ApplicationCommandType} enum value
- * @param {ApplicationCommandTypeEnumResolvable|ApplicationCommandType} key The key to resolve
- * @returns {ApplicationCommandType}
- */
- static resolveApplicationCommandType(key) {
- switch (key) {
- case 'CHAT_INPUT':
- return ApplicationCommandType.ChatInput;
- case 'USER':
- return ApplicationCommandType.User;
- case 'MESSAGE':
- return ApplicationCommandType.Message;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to an {@link ApplicationCommandOptionType} enum value. Here are the available types:
- * * SUB_COMMAND
- * * SUB_COMMAND_GROUP
- * * STRING
- * * INTEGER
- * * BOOLEAN
- * * USER
- * * CHANNEL
- * * ROLE
- * * NUMBER
- * * MENTIONABLE
- * @typedef {string} ApplicationCommandOptionTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link ApplicationCommandOptionType} enum value
- * @param {ApplicationCommandOptionTypeEnumResolvable|ApplicationCommandOptionType} key The key to resolve
- * @returns {ApplicationCommandOptionType}
- */
- static resolveApplicationCommandOptionType(key) {
- switch (key) {
- case 'SUB_COMMAND':
- return ApplicationCommandOptionType.Subcommand;
- case 'SUB_COMMAND_GROUP':
- return ApplicationCommandOptionType.SubcommandGroup;
- case 'STRING':
- return ApplicationCommandOptionType.String;
- case 'INTEGER':
- return ApplicationCommandOptionType.Integer;
- case 'BOOLEAN':
- return ApplicationCommandOptionType.Boolean;
- case 'USER':
- return ApplicationCommandOptionType.User;
- case 'CHANNEL':
- return ApplicationCommandOptionType.Channel;
- case 'ROLE':
- return ApplicationCommandOptionType.Role;
- case 'NUMBER':
- return ApplicationCommandOptionType.Number;
- case 'MENTIONABLE':
- return ApplicationCommandOptionType.Mentionable;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to an {@link ApplicationCommandPermissionType} enum value.
- * Here are the available types:
- * * ROLE
- * * USER
- * @typedef {string} ApplicationCommandPermissionTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link ApplicationCommandPermissionType} enum value
- * @param {ApplicationCommandPermissionTypeEnumResolvable|ApplicationCommandPermissionType} key The key to resolve
- * @returns {ApplicationCommandPermissionType}
- */
- static resolveApplicationCommandPermissionType(key) {
- switch (key) {
- case 'ROLE':
- return ApplicationCommandPermissionType.Role;
- case 'USER':
- return ApplicationCommandPermissionType.User;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link ComponentType} enum value. Here are the available types:
- * * ACTION_ROW
- * * BUTTON
- * * SELECT_MENU
- * @typedef {string} ComponentTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link ComponentType} enum value
- * @param {ComponentTypeEnumResolvable|ComponentType} key The key to resolve
- * @returns {ComponentType}
- */
- static resolveComponentType(key) {
- switch (key) {
- case 'ACTION_ROW':
- return ComponentType.ActionRow;
- case 'BUTTON':
- return ComponentType.Button;
- case 'SELECT_MENU':
- return ComponentType.SelectMenu;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link ButtonStyle} enum value. Here are the available types:
- * * PRIMARY
- * * SECONDARY
- * * SUCCESS
- * * DANGER
- * * LINK
- * @typedef {string} ButtonStyleEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link ButtonStyle} enum value
- * @param {ButtonStyleEnumResolvable|ButtonStyle} key The key to resolve
- * @returns {ButtonStyle}
- */
- static resolveButtonStyle(key) {
- switch (key) {
- case 'PRIMARY':
- return ButtonStyle.Primary;
- case 'SECONDARY':
- return ButtonStyle.Secondary;
- case 'SUCCESS':
- return ButtonStyle.Success;
- case 'DANGER':
- return ButtonStyle.Danger;
- case 'LINK':
- return ButtonStyle.Link;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link MessageType} enum value. Here are the available types:
- * * DEFAULT
- * * RECIPIENT_ADD
- * * RECIPIENT_REMOVE
- * * CALL
- * * CHANNEL_NAME_CHANGE
- * * CHANNEL_ICON_CHANGE
- * * CHANNEL_PINNED_MESSAGE
- * * GUILD_MEMBER_JOIN
- * * USER_PREMIUM_GUILD_SUBSCRIPTION
- * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1
- * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2
- * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3
- * * CHANNEL_FOLLOW_ADD
- * * GUILD_DISCOVERY_DISQUALIFIED
- * * GUILD_DISCOVERY_REQUALIFIED
- * * GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING
- * * GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING
- * * THREAD_CREATED
- * * REPLY
- * * CHAT_INPUT_COMMAND
- * * THREAD_STARTER_MESSAGE
- * * GUILD_INVITE_REMINDER
- * * CONTEXT_MENU_COMMAND
- * @typedef {string} MessageTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link MessageType} enum value
- * @param {MessageTypeEnumResolvable|MessageType} key The key to lookup
- * @returns {MessageType}
- */
- static resolveMessageType(key) {
- switch (key) {
- case 'DEFAULT':
- return MessageType.Default;
- case 'RECIPIENT_ADD':
- return MessageType.RecipientAdd;
- case 'RECIPIENT_REMOVE':
- return MessageType.RecipientRemove;
- case 'CALL':
- return MessageType.Call;
- case 'CHANNEL_NAME_CHANGE':
- return MessageType.ChannelNameChange;
- case 'CHANNEL_ICON_CHANGE':
- return MessageType.ChannelIconChange;
- case 'CHANNEL_PINNED_MESSAGE':
- return MessageType.ChannelPinnedMessage;
- case 'GUILD_MEMBER_JOIN':
- return MessageType.GuildMemberJoin;
- case 'USER_PREMIUM_GUILD_SUBSCRIPTION':
- return MessageType.UserPremiumGuildSubscription;
- case 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1':
- return MessageType.UserPremiumGuildSubscriptionTier1;
- case 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2':
- return MessageType.UserPremiumGuildSubscriptionTier2;
- case 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3':
- return MessageType.UserPremiumGuildSubscriptionTier3;
- case 'CHANNEL_FOLLOW_ADD':
- return MessageType.ChannelFollowAdd;
- case 'GUILD_DISCOVERY_DISQUALIFIED':
- return MessageType.GuildDiscoveryDisqualified;
- case 'GUILD_DISCOVERY_REQUALIFIED':
- return MessageType.GuildDiscoveryRequalified;
- case 'GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING':
- return MessageType.GuildDiscoveryGracePeriodInitialWarning;
- case 'GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING':
- return MessageType.GuildDiscoveryGracePeriodFinalWarning;
- case 'THREAD_CREATED':
- return MessageType.ThreadCreated;
- case 'REPLY':
- return MessageType.Reply;
- case 'CHAT_INPUT_COMMAND':
- return MessageType.ChatInputCommand;
- case 'THREAD_STARTER_MESSAGE':
- return MessageType.ThreadStarterMessage;
- case 'GUILD_INVITE_REMINDER':
- return MessageType.GuildInviteReminder;
- case 'CONTEXT_MENU_COMMAND':
- return MessageType.ContextMenuCommand;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildNSFWLevel} enum value. Here are the available types:
- * * DEFAULT
- * * EXPLICIT
- * * SAFE
- * * AGE_RESTRICTED
- * @typedef {string} GuildNSFWLevelEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildNSFWLevel} enum value
- * @param {GuildNSFWLevelEnumResolvable|GuildNSFWLevel} key The key to lookup
- * @returns {GuildNSFWLevel}
- */
- static resolveGuildNSFWLevel(key) {
- switch (key) {
- case 'DEFAULT':
- return GuildNSFWLevel.Default;
- case 'EXPLICIT':
- return GuildNSFWLevel.Explicit;
- case 'SAFE':
- return GuildNSFWLevel.Safe;
- case 'AGE_RESTRICTED':
- return GuildNSFWLevel.AgeRestricted;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildVerificationLevel} enum value. Here are the available types:
- * * NONE
- * * LOW
- * * MEDIUM
- * * HIGH
- * * VERY_HIGH
- * @typedef {string} GuildVerificationLevelEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildVerificationLevel} enum value
- * @param {GuildVerificationLevelEnumResolvable|GuildVerificationLevel} key The key to lookup
- * @returns {GuildVerificationLevel}
- */
- static resolveGuildVerificationLevel(key) {
- switch (key) {
- case 'NONE':
- return GuildVerificationLevel.None;
- case 'LOW':
- return GuildVerificationLevel.Low;
- case 'MEDIUM':
- return GuildVerificationLevel.Medium;
- case 'HIGH':
- return GuildVerificationLevel.High;
- case 'VERY_HIGH':
- return GuildVerificationLevel.VeryHigh;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildDefaultMessageNotifications} enum value.
- * Here are the available types:
- * * ALL_MESSAGES
- * * ONLY_MENTIONS
- * @typedef {string} GuildDefaultMessageNotificationsEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildDefaultMessageNotifications} enum value
- * @param {GuildDefaultMessageNotificationsEnumResolvable|GuildDefaultMessageNotifications} key The key to lookup
- * @returns {GuildDefaultMessageNotifications}
- */
- static resolveGuildDefaultMessageNotifications(key) {
- switch (key) {
- case 'ALL_MESSAGES':
- return GuildDefaultMessageNotifications.AllMessages;
- case 'ONLY_MENTIONS':
- return GuildDefaultMessageNotifications.OnlyMentions;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildExplicitContentFilter} enum value. Here are the available types:
- * * DISABLED
- * * MEMBERS_WITHOUT_ROLES
- * * ALL_MEMBERS
- * @typedef {string} GuildExplicitContentFilterEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildExplicitContentFilter} enum value
- * @param {GuildExplicitContentFilterEnumResolvable|GuildExplicitContentFilter} key The key to lookup
- * @returns {GuildExplicitContentFilter}
- */
- static resolveGuildExplicitContentFilter(key) {
- switch (key) {
- case 'DISABLED':
- return GuildExplicitContentFilter.Disabled;
- case 'MEMBERS_WITHOUT_ROLES':
- return GuildExplicitContentFilter.MembersWithoutRoles;
- case 'ALL_MEMBERS':
- return GuildExplicitContentFilter.AllMembers;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildPremiumTier} enum value. Here are the available types:
- * * NONE
- * * TIER_1
- * * TIER_2
- * * TIER_3
- * @typedef {string} GuildPremiumTierEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildPremiumTier} enum value
- * @param {GuildPremiumTierEnumResolvable|GuildPremiumTier} key The key to lookup
- * @returns {GuildPremiumTier}
- */
- static resolveGuildPremiumTier(key) {
- switch (key) {
- case 'NONE':
- return GuildPremiumTier.None;
- case 'TIER_1':
- return GuildPremiumTier.Tier1;
- case 'TIER_2':
- return GuildPremiumTier.Tier2;
- case 'TIER_3':
- return GuildPremiumTier.Tier3;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildScheduledEventStatus} enum value. Here are the available types:
- * * SCHEDULED
- * * ACTIVE
- * * COMPLETED
- * * CANCELED
- * @typedef {string} GuildScheduledEventStatusEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildScheduledEventStatus} enum value
- * @param {GuildScheduledEventStatusEnumResolvable|GuildScheduledEventStatus} key The key to lookup
- * @returns {GuildScheduledEventStatus}
- */
- static resolveGuildScheduledEventStatus(key) {
- switch (key) {
- case 'SCHEDULED':
- return GuildScheduledEventStatus.Scheduled;
- case 'ACTIVE':
- return GuildScheduledEventStatus.Active;
- case 'COMPLETED':
- return GuildScheduledEventStatus.Completed;
- case 'CANCELED':
- return GuildScheduledEventStatus.Canceled;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link StageInstancePrivacyLevel} enum value. Here are the available types:
- * * PUBLIC
- * * GUILD_ONLY
- * @typedef {string} StageInstancePrivacyLevelEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link StageInstancePrivacyLevel} enum value
- * @param {StageInstancePrivacyLevelEnumResolvable|StageInstancePrivacyLevel} key The key to lookup
- * @returns {StageInstancePrivacyLevel}
- */
- static resolveStageInstancePrivacyLevel(key) {
- switch (key) {
- case 'PUBLIC':
- return StageInstancePrivacyLevel.Public;
- case 'GUILD_ONLY':
- return StageInstancePrivacyLevel.GuildOnly;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildMFALevel} enum value. Here are the available types:
- * * NONE
- * * ELEVATED
- * @typedef {string} GuildMFALevelEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildMFALevel} enum value
- * @param {GuildMFALevelEnumResolvable|GuildMFALevel} key The key to lookup
- * @returns {GuildMFALevel}
- */
- static resolveGuildMFALevel(key) {
- switch (key) {
- case 'NONE':
- return GuildMFALevel.None;
- case 'ELEVATED':
- return GuildMFALevel.Elevated;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link TeamMemberMembershipState} enum value. Here are the available types:
- * * INVITED
- * * ACCEPTED
- * @typedef {string} TeamMemberMembershipStateEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link TeamMemberMembershipState} enum value
- * @param {TeamMemberMembershipStateEnumResolvable|TeamMemberMembershipState} key The key to lookup
- * @returns {TeamMemberMembershipState}
- */
- static resolveTeamMemberMembershipState(key) {
- switch (key) {
- case 'INVITED':
- return TeamMemberMembershipState.Invited;
- case 'ACCEPTED':
- return TeamMemberMembershipState.Accepted;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link GuildScheduledEventEntityType} enum value. Here are the available types:
- * * STAGE_INSTANCE
- * * VOICE
- * * EXTERNAL
- * @typedef {string} GuildScheduledEventEntityTypeEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link GuildScheduledEventEntityType} enum value
- * @param {GuildScheduledEventEntityTypeEnumResolvable|GuildScheduledEventEntityType} key The key to lookup
- * @returns {GuildScheduledEventEntityType}
- */
- static resolveGuildScheduledEventEntityType(key) {
- switch (key) {
- case 'STAGE_INSTANCE':
- return GuildScheduledEventEntityType.StageInstance;
- case 'VOICE':
- return GuildScheduledEventEntityType.Voice;
- case 'EXTERNAL':
- return GuildScheduledEventEntityType.External;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link IntegrationExpireBehavior} enum value. Here are the available types:
- * * REMOVE_ROLE
- * * KICK
- * @typedef {string} IntegrationExpireBehaviorEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link IntegrationExpireBehavior} enum value
- * @param {IntegrationExpireBehaviorEnumResolvable|IntegrationExpireBehavior} key The key to lookup
- * @returns {IntegrationExpireBehavior}
- */
- static resolveIntegrationExpireBehavior(key) {
- switch (key) {
- case 'REMOVE_ROLE':
- return IntegrationExpireBehavior.RemoveRole;
- case 'KICK':
- return IntegrationExpireBehavior.Kick;
- default:
- return unknownKeyStrategy(key);
- }
- }
-
- /**
- * A string that can be resolved to a {@link AuditLogEvent} enum value. Here are the available types:
- * * GUILD_UPDATE
- * * CHANNEL_CREATE
- * * CHANNEL_UPDATE
- * * CHANNEL_DELETE
- * * CHANNEL_OVERWRITE_CREATE
- * * CHANNEL_OVERWRITE_UPDATE
- * * CHANNEL_OVERWRITE_DELETE
- * * MEMBER_KICK
- * * MEMBER_PRUNE
- * * MEMBER_BAN_ADD
- * * MEMBER_BAN_REMOVE
- * * MEMBER_UPDATE
- * * MEMBER_ROLE_UPDATE
- * * MEMBER_MOVE
- * * MEMBER_DISCONNECT
- * * BOT_ADD
- * * ROLE_CREATE
- * * ROLE_UPDATE
- * * ROLE_DELETE
- * * INVITE_CREATE
- * * INVITE_UPDATE
- * * INVITE_DELETE
- * * WEBHOOK_CREATE
- * * WEBHOOK_UPDATE
- * * WEBHOOK_DELETE
- * * INTEGRATION_CREATE
- * * INTEGRATION_UPDATE
- * * INTEGRATION_DELETE
- * * STAGE_INSTANCE_CREATE
- * * STAGE_INSTANCE_UPDATE
- * * STAGE_INSTANCE_DELETE
- * * STICKER_CREATE
- * * STICKER_UPDATE
- * * STICKER_DELETE
- * * GUILD_SCHEDULED_EVENT_CREATE
- * * GUILD_SCHEDULED_EVENT_UPDATE
- * * GUILD_SCHEDULED_EVENT_DELETE
- * * THREAD_CREATE
- * * THREAD_UPDATE
- * * THREAD_DELETE
- * @typedef {string} AuditLogEventEnumResolvable
- */
-
- /**
- * Resolves enum key to {@link AuditLogEvent} enum value
- * @param {AuditLogEventEnumResolvable|AuditLogEvent} key The key to lookup
- * @returns {AuditLogEvent}
- */
- static resolveAuditLogEvent(key) {
- switch (key) {
- case 'GUILD_UPDATE':
- return AuditLogEvent.GuildUpdate;
- case 'CHANNEL_CREATE':
- return AuditLogEvent.ChannelCreate;
- case 'CHANNEL_UPDATE':
- return AuditLogEvent.ChannelUpdate;
- case 'CHANNEL_DELETE':
- return AuditLogEvent.ChannelDelete;
- case 'CHANNEL_OVERWRITE_CREATE':
- return AuditLogEvent.ChannelOverwriteCreate;
- case 'CHANNEL_OVERWRITE_UPDATE':
- return AuditLogEvent.ChannelOverwriteUpdate;
- case 'CHANNEL_OVERWRITE_DELETE':
- return AuditLogEvent.ChannelOverwriteDelete;
- case 'MEMBER_KICK':
- return AuditLogEvent.MemberKick;
- case 'MEMBER_PRUNE':
- return AuditLogEvent.MemberPrune;
- case 'MEMBER_BAN_ADD':
- return AuditLogEvent.MemberBanAdd;
- case 'MEMBER_BAN_REMOVE':
- return AuditLogEvent.MemberBanRemove;
- case 'MEMBER_UPDATE':
- return AuditLogEvent.MemberUpdate;
- case 'MEMBER_ROLE_UPDATE':
- return AuditLogEvent.MemberRoleUpdate;
- case 'MEMBER_MOVE':
- return AuditLogEvent.MemberMove;
- case 'MEMBER_DISCONNECT':
- return AuditLogEvent.MemberDisconnect;
- case 'BOT_ADD':
- return AuditLogEvent.BotAdd;
- case 'ROLE_CREATE':
- return AuditLogEvent.RoleCreate;
- case 'ROLE_UPDATE':
- return AuditLogEvent.RoleUpdate;
- case 'ROLE_DELETE':
- return AuditLogEvent.RoleDelete;
- case 'INVITE_CREATE':
- return AuditLogEvent.InviteCreate;
- case 'INVITE_UPDATE':
- return AuditLogEvent.InviteUpdate;
- case 'INVITE_DELETE':
- return AuditLogEvent.InviteDelete;
- case 'WEBHOOK_CREATE':
- return AuditLogEvent.WebhookCreate;
- case 'WEBHOOK_UPDATE':
- return AuditLogEvent.WebhookUpdate;
- case 'WEBHOOK_DELETE':
- return AuditLogEvent.WebhookDelete;
- case 'EMOJI_CREATE':
- return AuditLogEvent.EmojiCreate;
- case 'EMOJI_UPDATE':
- return AuditLogEvent.EmojiUpdate;
- case 'EMOJI_DELETE':
- return AuditLogEvent.EmojiDelete;
- case 'MESSAGE_DELETE':
- return AuditLogEvent.MessageDelete;
- case 'MESSAGE_BULK_DELETE':
- return AuditLogEvent.MessageBulkDelete;
- case 'MESSAGE_PIN':
- return AuditLogEvent.MessagePin;
- case 'MESSAGE_UNPIN':
- return AuditLogEvent.MessageUnpin;
- case 'INTEGRATION_CREATE':
- return AuditLogEvent.IntegrationCreate;
- case 'INTEGRATION_UPDATE':
- return AuditLogEvent.IntegrationUpdate;
- case 'INTEGRATION_DELETE':
- return AuditLogEvent.IntegrationDelete;
- case 'STAGE_INSTANCE_CREATE':
- return AuditLogEvent.StageInstanceCreate;
- case 'STAGE_INSTANCE_UPDATE':
- return AuditLogEvent.StageInstanceUpdate;
- case 'STAGE_INSTANCE_DELETE':
- return AuditLogEvent.StageInstanceDelete;
- case 'STICKER_CREATE':
- return AuditLogEvent.StickerCreate;
- case 'STICKER_UPDATE':
- return AuditLogEvent.StickerUpdate;
- case 'STICKER_DELETE':
- return AuditLogEvent.StickerDelete;
- case 'GUILD_SCHEDULED_EVENT_CREATE':
- return AuditLogEvent.GuildScheduledEventCreate;
- case 'GUILD_SCHEDULED_EVENT_UPDATE':
- return AuditLogEvent.GuildScheduledEventUpdate;
- case 'GUILD_SCHEDULED_EVENT_DELETE':
- return AuditLogEvent.GuildScheduledEventDelete;
- case 'THREAD_CREATE':
- return AuditLogEvent.ThreadCreate;
- case 'THREAD_UPDATE':
- return AuditLogEvent.ThreadUpdate;
- case 'THREAD_DELETE':
- return AuditLogEvent.ThreadDelete;
- default:
- return unknownKeyStrategy(key);
- }
- }
-}
-
-// Precondition logic wrapper
-function preconditioner(func) {
- return key => {
- if (typeof key !== 'string' && typeof key !== 'number') {
- throw new Error('Enum value must be string or number');
- }
-
- if (typeof key === 'number') {
- return key;
- }
-
- return func(key);
- };
-}
-
-// Injects wrapper into class static methods.
-function applyPreconditioner(obj) {
- for (const name in Object.getOwnPropertyNames(obj)) {
- if (typeof obj[name] !== 'function') {
- return;
- }
-
- obj[name] = preconditioner(obj[name]);
- }
-}
-
-// Apply precondition logic
-applyPreconditioner(EnumResolvers);
-
-module.exports = EnumResolvers;
diff --git a/src/util/Enums.js b/src/util/Enums.js
deleted file mode 100644
index e3e5cac..00000000
--- a/src/util/Enums.js
+++ /dev/null
@@ -1,13 +0,0 @@
-'use strict';
-
-function createEnum(keys) {
- const obj = {};
- for (const [index, key] of keys.entries()) {
- if (key === null) continue;
- obj[key] = index;
- obj[index] = key;
- }
- return obj;
-}
-
-module.exports = { createEnum };
diff --git a/src/util/Events.js b/src/util/Events.js
deleted file mode 100644
index 11d980d..00000000
--- a/src/util/Events.js
+++ /dev/null
@@ -1,72 +0,0 @@
-'use strict';
-
-module.exports = {
- ClientReady: 'ready',
- GuildCreate: 'guildCreate',
- GuildDelete: 'guildDelete',
- GuildUpdate: 'guildUpdate',
- GuildUnavailable: 'guildUnavailable',
- GuildMemberAdd: 'guildMemberAdd',
- GuildMemberRemove: 'guildMemberRemove',
- GuildMemberUpdate: 'guildMemberUpdate',
- GuildMemberAvailable: 'guildMemberAvailable',
- GuildMembersChunk: 'guildMembersChunk',
- GuildIntegrationsUpdate: 'guildIntegrationsUpdate',
- GuildRoleCreate: 'roleCreate',
- GuildRoleDelete: 'roleDelete',
- InviteCreate: 'inviteCreate',
- InviteDelete: 'inviteDelete',
- GuildRoleUpdate: 'roleUpdate',
- GuildEmojiCreate: 'emojiCreate',
- GuildEmojiDelete: 'emojiDelete',
- GuildEmojiUpdate: 'emojiUpdate',
- GuildBanAdd: 'guildBanAdd',
- GuildBanRemove: 'guildBanRemove',
- ChannelCreate: 'channelCreate',
- ChannelDelete: 'channelDelete',
- ChannelUpdate: 'channelUpdate',
- ChannelPinsUpdate: 'channelPinsUpdate',
- MessageCreate: 'messageCreate',
- MessageDelete: 'messageDelete',
- MessageUpdate: 'messageUpdate',
- MessageBulkDelete: 'messageDeleteBulk',
- MessageReactionAdd: 'messageReactionAdd',
- MessageReactionRemove: 'messageReactionRemove',
- MessageReactionRemoveAll: 'messageReactionRemoveAll',
- MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji',
- ThreadCreate: 'threadCreate',
- ThreadDelete: 'threadDelete',
- ThreadUpdate: 'threadUpdate',
- ThreadListSync: 'threadListSync',
- ThreadMemberUpdate: 'threadMemberUpdate',
- ThreadMembersUpdate: 'threadMembersUpdate',
- UserUpdate: 'userUpdate',
- PresenceUpdate: 'presenceUpdate',
- VoiceServerUpdate: 'voiceServerUpdate',
- VoiceStateUpdate: 'voiceStateUpdate',
- TypingStart: 'typingStart',
- WebhooksUpdate: 'webhookUpdate',
- InteractionCreate: 'interactionCreate',
- Error: 'error',
- Warn: 'warn',
- Debug: 'debug',
- CacheSweep: 'cacheSweep',
- ShardDisconnect: 'shardDisconnect',
- ShardError: 'shardError',
- ShardReconnecting: 'shardReconnecting',
- ShardReady: 'shardReady',
- ShardResume: 'shardResume',
- Invalidated: 'invalidated',
- Raw: 'raw',
- StageInstanceCreate: 'stageInstanceCreate',
- StageInstanceUpdate: 'stageInstanceUpdate',
- StageInstanceDelete: 'stageInstanceDelete',
- GuildStickerCreate: 'stickerCreate',
- GuildStickerDelete: 'stickerDelete',
- GuildStickerUpdate: 'stickerUpdate',
- GuildScheduledEventCreate: 'guildScheduledEventCreate',
- GuildScheduledEventUpdate: 'guildScheduledEventUpdate',
- GuildScheduledEventDelete: 'guildScheduledEventDelete',
- GuildScheduledEventUserAdd: 'guildScheduledEventUserAdd',
- GuildScheduledEventUserRemove: 'guildScheduledEventUserRemove',
-};
diff --git a/src/util/Intents.js b/src/util/Intents.js
new file mode 100644
index 00000000..359e10b
--- /dev/null
+++ b/src/util/Intents.js
@@ -0,0 +1,66 @@
+'use strict';
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to calculate intents.
+ * @extends {BitField}
+ */
+class Intents extends BitField {}
+
+/**
+ * @name Intents
+ * @kind constructor
+ * @memberof Intents
+ * @param {IntentsResolvable} [bits=0] Bit(s) to read from
+ */
+
+/**
+ * Data that can be resolved to give a permission number. This can be:
+ * * A string (see {@link Intents.FLAGS})
+ * * An intents flag
+ * * An instance of Intents
+ * * An array of IntentsResolvable
+ * @typedef {string|number|Intents|IntentsResolvable[]} IntentsResolvable
+ */
+
+/**
+ * Numeric WebSocket intents. All available properties:
+ * * `GUILDS`
+ * * `GUILD_MEMBERS`
+ * * `GUILD_BANS`
+ * * `GUILD_EMOJIS_AND_STICKERS`
+ * * `GUILD_INTEGRATIONS`
+ * * `GUILD_WEBHOOKS`
+ * * `GUILD_INVITES`
+ * * `GUILD_VOICE_STATES`
+ * * `GUILD_PRESENCES`
+ * * `GUILD_MESSAGES`
+ * * `GUILD_MESSAGE_REACTIONS`
+ * * `GUILD_MESSAGE_TYPING`
+ * * `DIRECT_MESSAGES`
+ * * `DIRECT_MESSAGE_REACTIONS`
+ * * `DIRECT_MESSAGE_TYPING`
+ * * `GUILD_SCHEDULED_EVENTS`
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/topics/gateway#list-of-intents}
+ */
+Intents.FLAGS = {
+ GUILDS: 1 << 0,
+ GUILD_MEMBERS: 1 << 1,
+ GUILD_BANS: 1 << 2,
+ GUILD_EMOJIS_AND_STICKERS: 1 << 3,
+ GUILD_INTEGRATIONS: 1 << 4,
+ GUILD_WEBHOOKS: 1 << 5,
+ GUILD_INVITES: 1 << 6,
+ GUILD_VOICE_STATES: 1 << 7,
+ GUILD_PRESENCES: 1 << 8,
+ GUILD_MESSAGES: 1 << 9,
+ GUILD_MESSAGE_REACTIONS: 1 << 10,
+ GUILD_MESSAGE_TYPING: 1 << 11,
+ DIRECT_MESSAGES: 1 << 12,
+ DIRECT_MESSAGE_REACTIONS: 1 << 13,
+ DIRECT_MESSAGE_TYPING: 1 << 14,
+ GUILD_SCHEDULED_EVENTS: 1 << 16,
+};
+
+module.exports = Intents;
diff --git a/src/util/IntentsBitField.js b/src/util/IntentsBitField.js
deleted file mode 100644
index a173176..00000000
--- a/src/util/IntentsBitField.js
+++ /dev/null
@@ -1,33 +0,0 @@
-'use strict';
-const { GatewayIntentBits } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to calculate intents.
- * @extends {BitField}
- */
-class IntentsBitField extends BitField {}
-
-/**
- * @name IntentsBitField
- * @kind constructor
- * @memberof IntentsBitField
- * @param {IntentsResolvable} [bits=0] Bit(s) to read from
- */
-
-/**
- * Data that can be resolved to give a permission number. This can be:
- * * A string (see {@link IntentsBitField.Flags})
- * * An intents flag
- * * An instance of {@link IntentsBitField}
- * * An array of IntentsResolvable
- * @typedef {string|number|IntentsBitField|IntentsResolvable[]} IntentsResolvable
- */
-
-/**
- * Numeric WebSocket intents
- * @type {GatewayIntentBits}
- */
-IntentsBitField.Flags = GatewayIntentBits;
-
-module.exports = IntentsBitField;
diff --git a/src/util/LimitedCollection.js b/src/util/LimitedCollection.js
index 1fa6798..8ebda0e 100644
--- a/src/util/LimitedCollection.js
+++ b/src/util/LimitedCollection.js
@@ -1,18 +1,35 @@
'use strict';
+const { setInterval } = require('node:timers');
const { Collection } = require('@discordjs/collection');
+const { _cleanupSymbol } = require('./Constants.js');
+const Sweepers = require('./Sweepers.js');
const { TypeError } = require('../errors/DJSError.js');
+/**
+ * @typedef {Function} SweepFilter
+ * @param {LimitedCollection} collection The collection being swept
+ * @returns {Function|null} Return `null` to skip sweeping, otherwise a function passed to `sweep()`,
+ * See {@link [Collection#sweep](https://discord.js.org/#/docs/collection/main/class/Collection?scrollTo=sweep)}
+ * for the definition of this function.
+ */
+
/**
* Options for defining the behavior of a LimitedCollection
* @typedef {Object} LimitedCollectionOptions
* @property {?number} [maxSize=Infinity] The maximum size of the Collection
* @property {?Function} [keepOverLimit=null] A function, which is passed the value and key of an entry, ran to decide
* to keep an entry past the maximum size
+ * @property {?SweepFilter} [sweepFilter=null] DEPRECATED: There is no direct alternative to this,
+ * however most of its purpose is fulfilled by {@link Client#sweepers}
+ * A function ran every `sweepInterval` to determine how to sweep
+ * @property {?number} [sweepInterval=0] DEPRECATED: There is no direct alternative to this,
+ * however most of its purpose is fulfilled by {@link Client#sweepers}
+ * How frequently, in seconds, to sweep the collection.
*/
/**
- * A Collection which holds a max amount of entries.
+ * A Collection which holds a max amount of entries and sweeps periodically.
* @extends {Collection}
* @param {LimitedCollectionOptions} [options={}] Options for constructing the Collection.
* @param {Iterable} [iterable=null] Optional entries passed to the Map constructor.
@@ -22,7 +39,7 @@ class LimitedCollection extends Collection {
if (typeof options !== 'object' || options === null) {
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
}
- const { maxSize = Infinity, keepOverLimit = null } = options;
+ const { maxSize = Infinity, keepOverLimit = null, sweepInterval = 0, sweepFilter = null } = options;
if (typeof maxSize !== 'number') {
throw new TypeError('INVALID_TYPE', 'maxSize', 'number');
@@ -30,6 +47,12 @@ class LimitedCollection extends Collection {
if (keepOverLimit !== null && typeof keepOverLimit !== 'function') {
throw new TypeError('INVALID_TYPE', 'keepOverLimit', 'function');
}
+ if (typeof sweepInterval !== 'number') {
+ throw new TypeError('INVALID_TYPE', 'sweepInterval', 'number');
+ }
+ if (sweepFilter !== null && typeof sweepFilter !== 'function') {
+ throw new TypeError('INVALID_TYPE', 'sweepFilter', 'function');
+ }
super(iterable);
@@ -44,6 +67,28 @@ class LimitedCollection extends Collection {
* @type {?Function}
*/
this.keepOverLimit = keepOverLimit;
+
+ /**
+ * A function called every sweep interval that returns a function passed to `sweep`.
+ * @deprecated in favor of {@link Client#sweepers}
+ * @type {?SweepFilter}
+ */
+ this.sweepFilter = sweepFilter;
+
+ /**
+ * The id of the interval being used to sweep.
+ * @deprecated in favor of {@link Client#sweepers}
+ * @type {?Timeout}
+ */
+ this.interval =
+ sweepInterval > 0 && sweepInterval !== Infinity && sweepFilter
+ ? setInterval(() => {
+ const sweepFn = this.sweepFilter(this);
+ if (sweepFn === null) return;
+ if (typeof sweepFn !== 'function') throw new TypeError('SWEEP_FILTER_RETURN');
+ this.sweep(sweepFn);
+ }, sweepInterval * 1_000).unref()
+ : null;
}
set(key, value) {
@@ -60,6 +105,24 @@ class LimitedCollection extends Collection {
return super.set(key, value);
}
+ /**
+ * Create a sweepFilter function that uses a lifetime to determine sweepability.
+ * @param {LifetimeFilterOptions} [options={}] The options used to generate the filter function
+ * @deprecated Use {@link Sweepers.filterByLifetime} instead
+ * @returns {SweepFilter}
+ */
+ static filterByLifetime({
+ lifetime = 14400,
+ getComparisonTimestamp = e => e?.createdTimestamp,
+ excludeFromSweep = () => false,
+ } = {}) {
+ return Sweepers.filterByLifetime({ lifetime, getComparisonTimestamp, excludeFromSweep });
+ }
+
+ [_cleanupSymbol]() {
+ return this.interval ? () => clearInterval(this.interval) : null;
+ }
+
static get [Symbol.species]() {
return Collection;
}
diff --git a/src/util/MessageFlags.js b/src/util/MessageFlags.js
new file mode 100644
index 00000000..b91a1fc
--- /dev/null
+++ b/src/util/MessageFlags.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with a {@link Message#flags} bitfield.
+ * @extends {BitField}
+ */
+class MessageFlags extends BitField {}
+
+/**
+ * @name MessageFlags
+ * @kind constructor
+ * @memberof MessageFlags
+ * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
+ */
+
+/**
+ * Bitfield of the packed bits
+ * @type {number}
+ * @name MessageFlags#bitfield
+ */
+
+/**
+ * Numeric message flags. All available properties:
+ * * `CROSSPOSTED`
+ * * `IS_CROSSPOST`
+ * * `SUPPRESS_EMBEDS`
+ * * `SOURCE_MESSAGE_DELETED`
+ * * `URGENT`
+ * * `HAS_THREAD`
+ * * `EPHEMERAL`
+ * * `LOADING`
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/resources/channel#message-object-message-flags}
+ */
+MessageFlags.FLAGS = {
+ CROSSPOSTED: 1 << 0,
+ IS_CROSSPOST: 1 << 1,
+ SUPPRESS_EMBEDS: 1 << 2,
+ SOURCE_MESSAGE_DELETED: 1 << 3,
+ URGENT: 1 << 4,
+ HAS_THREAD: 1 << 5,
+ EPHEMERAL: 1 << 6,
+ LOADING: 1 << 7,
+};
+
+module.exports = MessageFlags;
diff --git a/src/util/MessageFlagsBitField.js b/src/util/MessageFlagsBitField.js
deleted file mode 100644
index a9f1e7f..00000000
--- a/src/util/MessageFlagsBitField.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-
-const { MessageFlags } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to interact with a {@link Message#flags} bitfield.
- * @extends {BitField}
- */
-class MessageFlagsBitField extends BitField {}
-
-/**
- * @name MessageFlagsBitField
- * @kind constructor
- * @memberof MessageFlagsBitField
- * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
- */
-
-/**
- * Bitfield of the packed bits
- * @type {number}
- * @name MessageFlagsBitField#bitfield
- */
-
-/**
- * Numeric message flags.
- * @type {MessageFlags}
- */
-MessageFlagsBitField.Flags = MessageFlags;
-
-module.exports = MessageFlagsBitField;
diff --git a/src/util/Options.js b/src/util/Options.js
index f92c14d..4a3c9cc 100644
--- a/src/util/Options.js
+++ b/src/util/Options.js
@@ -1,8 +1,24 @@
'use strict';
const process = require('node:process');
-const Transformers = require('./Transformers');
const JSONBig = require('json-bigint');
+/**
+ * Rate limit data
+ * @typedef {Object} RateLimitData
+ * @property {number} timeout Time until this rate limit ends, in ms
+ * @property {number} limit The maximum amount of requests of this endpoint
+ * @property {string} method The HTTP method of this request
+ * @property {string} path The path of the request relative to the HTTP endpoint
+ * @property {string} route The route of the request relative to the HTTP endpoint
+ * @property {boolean} global Whether this is a global rate limit
+ */
+
+/**
+ * Whether this rate limit should throw an Error
+ * @typedef {Function} RateLimitQueueFilter
+ * @param {RateLimitData} rateLimitData The data of this rate limit
+ * @returns {boolean|Promise}
+ */
/**
* @typedef {Function} CacheFactory
@@ -23,20 +39,44 @@ const JSONBig = require('json-bigint');
* You can use your own function, or the {@link Options} class to customize the Collection used for the cache.
* Overriding the cache used in `GuildManager`, `ChannelManager`, `GuildChannelManager`, `RoleManager`,
* and `PermissionOverwriteManager` is unsupported and **will** break functionality
+ * @property {number} [messageCacheLifetime=0] DEPRECATED: Pass `lifetime` to `sweepers.messages` instead.
+ * How long a message should stay in the cache until it is considered sweepable (in seconds, 0 for forever)
+ * @property {number} [messageSweepInterval=0] DEPRECATED: Pass `interval` to `sweepers.messages` instead.
+ * How frequently to remove messages from the cache that are older than the message cache lifetime
+ * (in seconds, 0 for never)
* @property {MessageMentionOptions} [allowedMentions] Default value for {@link MessageOptions#allowedMentions}
- * @property {Partials[]} [partials] Structures allowed to be partial. This means events can be emitted even when
+ * @property {number} [invalidRequestWarningInterval=0] The number of invalid REST requests (those that return
+ * 401, 403, or 429) in a 10 minute window between emitted warnings (0 for no warnings). That is, if set to 500,
+ * warnings will be emitted at invalid request number 500, 1000, 1500, and so on.
+ * @property {PartialType[]} [partials] Structures allowed to be partial. This means events can be emitted even when
* they're missing all the data for a particular structure. See the "Partial Structures" topic on the
* [guide](https://discordjs.guide/popular-topics/partials.html) for some
* important usage information, as partials require you to put checks in place when handling data.
+ * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their
+ * corresponding WebSocket events
+ * @property {number} [restTimeOffset=500] Extra time in milliseconds to wait before continuing to make REST
+ * requests (higher values will reduce rate-limiting errors on bad connections)
+ * @property {number} [restRequestTimeout=15000] Time to wait before cancelling a REST request, in milliseconds
+ * @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds
+ * (or 0 for never)
+ * @property {number} [restGlobalRateLimit=0] How many requests to allow sending per second (0 for unlimited, 50 for
+ * the standard global limit used by Discord)
+ * @property {string[]|RateLimitQueueFilter} [rejectOnRateLimit] Decides how rate limits and pre-emptive throttles
+ * should be handled. If this option is an array containing the prefix of the request route (e.g. /channels to match any
+ * route starting with /channels, such as /channels/222197033908436994/messages) or a function returning true, a
+ * {@link RateLimitError} will be thrown. Otherwise the request will be queued for later
+ * @property {number} [retryLimit=1] How many times to retry on 5XX errors
+ * (Infinity for an indefinite amount of retries)
* @property {boolean} [failIfNotExists=true] Default value for {@link ReplyMessageOptions#failIfNotExists}
+ * @property {string[]} [userAgentSuffix] An array of additional bot info to be appended to the end of the required
+ * [User Agent](https://discord.com/developers/docs/reference#user-agent) header
* @property {PresenceData} [presence={}] Presence data to use upon login
* @property {IntentsResolvable} intents Intents to enable for this connection
* @property {number} [waitGuildTimeout=15_000] Time in milliseconds that Clients with the GUILDS intent should wait for
- * missing guilds to be received before starting the bot. If not specified, the default is 15 seconds.
+ * missing guilds to be recieved before starting the bot. If not specified, the default is 15 seconds.
* @property {SweeperOptions} [sweepers={}] Options for cache sweeping
* @property {WebsocketOptions} [ws] Options for the WebSocket
- * @property {RESTOptions} [rest] Options for the REST manager
- * @property {Function} [jsonTransformer] A function used to transform outgoing json data
+ * @property {HTTPOptions} [http] HTTP options
*/
/**
@@ -62,6 +102,26 @@ const JSONBig = require('json-bigint');
* sent in the initial guild member list, must be between 50 and 250
*/
+/**
+ * HTTPS Agent options.
+ * @typedef {Object} AgentOptions
+ * @see {@link https://nodejs.org/api/https.html#https_class_https_agent}
+ * @see {@link https://nodejs.org/api/http.html#http_new_agent_options}
+ */
+
+/**
+ * HTTP options
+ * @typedef {Object} HTTPOptions
+ * @property {number} [version=9] API version to use
+ * @property {AgentOptions} [agent={}] HTTPS Agent options
+ * @property {string} [api='https://discord.com/api'] Base URL of the API
+ * @property {string} [cdn='https://cdn.discordapp.com'] Base URL of the CDN
+ * @property {string} [invite='https://discord.gg'] Base URL of invites
+ * @property {string} [template='https://discord.new'] Base URL of templates
+ * @property {Object} [headers] Additional headers to send for all API requests
+ * @property {string} [scheduledEvent='https://discord.com/events'] Base URL of guild scheduled events
+ */
+
/**
* Contains various utilities for client options.
*/
@@ -72,25 +132,28 @@ class Options extends null {
*/
static createDefault() {
return {
- waitGuildTimeout: 15_000,
- shardCount: 1,
- makeCache: this.cacheWithLimits(this.defaultMakeCacheSettings),
- messageCacheLifetime: 0,
- messageSweepInterval: 0,
- invalidRequestWarningInterval: 0,
- intents: 32767,
- partials: [],
- restWsBridgeTimeout: 5_000,
- restRequestTimeout: 15_000,
- restGlobalRateLimit: 0,
- retryLimit: 1,
- restTimeOffset: 500,
- restSweepInterval: 60,
- failIfNotExists: true,
- userAgentSuffix: [],
- presence: {},
- sweepers: {},
- ws: {
+ jsonTransformer: (object) => JSONBig.stringify(object),
+ checkUpdate: true,
+ readyStatus: false,
+ waitGuildTimeout: 15_000,
+ shardCount: 1,
+ makeCache: this.cacheWithLimits(this.defaultMakeCacheSettings),
+ messageCacheLifetime: 0,
+ messageSweepInterval: 0,
+ invalidRequestWarningInterval: 0,
+ intents: 65535,
+ partials: [],
+ restWsBridgeTimeout: 5_000,
+ restRequestTimeout: 15_000,
+ restGlobalRateLimit: 0,
+ retryLimit: 1,
+ restTimeOffset: 500,
+ restSweepInterval: 60,
+ failIfNotExists: true,
+ userAgentSuffix: [],
+ presence: {},
+ sweepers: {},
+ ws: {
large_threshold: 50,
compress: false,
properties: {
@@ -121,6 +184,7 @@ class Options extends null {
'X-Debug-Options': 'bugReporterEnabled',
'X-Discord-Locale': 'en-US',
Origin: 'https://discord.com',
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36',
},
agent: {},
version: 10,
@@ -130,10 +194,7 @@ class Options extends null {
template: 'https://discord.new',
scheduledEvent: 'https://discord.com/events',
},
- jsonTransformer: (object) => JSONBig.stringify(object),
- checkUpdate: true,
- readyStatus: false,
- };
+ };
}
/**
@@ -144,14 +205,32 @@ class Options extends null {
* If LimitedCollectionOptions are provided for a manager, it uses those settings to form a LimitedCollection.
* @returns {CacheFactory}
* @example
- * // Store up to 200 messages per channel and 200 members per guild, always keeping the client member.
+ * // Store up to 200 messages per channel and discard archived threads if they were archived more than 4 hours ago.
+ * // Note archived threads will remain in the guild and client caches with these settings
* Options.cacheWithLimits({
* MessageManager: 200,
- * GuildMemberManager: {
- * maxSize: 200,
- * keepOverLimit: (member) => member.id === client.user.id,
+ * ThreadManager: {
+ * sweepInterval: 3600,
+ * sweepFilter: LimitedCollection.filterByLifetime({
+ * getComparisonTimestamp: e => e.archiveTimestamp,
+ * excludeFromSweep: e => !e.archived,
+ * }),
* },
* });
+ * @example
+ * // Sweep messages every 5 minutes, removing messages that have not been edited or created in the last 30 minutes
+ * Options.cacheWithLimits({
+ * // Keep default thread sweeping behavior
+ * ...Options.defaultMakeCacheSettings,
+ * // Override MessageManager
+ * MessageManager: {
+ * sweepInterval: 300,
+ * sweepFilter: LimitedCollection.filterByLifetime({
+ * lifetime: 1800,
+ * getComparisonTimestamp: e => e.editedTimestamp ?? e.createdTimestamp,
+ * })
+ * }
+ * });
*/
static cacheWithLimits(settings = {}) {
const { Collection } = require('@discordjs/collection');
@@ -169,9 +248,15 @@ class Options extends null {
}
return new LimitedCollection({ maxSize: setting });
}
- /* eslint-disable-next-line eqeqeq */
+ /* eslint-disable eqeqeq */
+ const noSweeping =
+ setting.sweepFilter == null ||
+ setting.sweepInterval == null ||
+ setting.sweepInterval <= 0 ||
+ setting.sweepInterval === Infinity;
const noLimit = setting.maxSize == null || setting.maxSize === Infinity;
- if (noLimit) {
+ /* eslint-enable eqeqeq */
+ if (noSweeping && noLimit) {
return new Collection();
}
return new LimitedCollection(setting);
@@ -201,6 +286,20 @@ class Options extends null {
static get defaultMakeCacheSettings() {
return {
MessageManager: 200,
+ /*
+ ChannelManager: {
+ sweepInterval: 3600,
+ sweepFilter: require('./Util').archivedThreadSweepFilter(),
+ },
+ GuildChannelManager: {
+ sweepInterval: 3600,
+ sweepFilter: require('./Util').archivedThreadSweepFilter(),
+ },
+ ThreadManager: {
+ sweepInterval: 3600,
+ sweepFilter: require('./Util').archivedThreadSweepFilter(),
+ },
+ */
};
}
}
@@ -221,8 +320,3 @@ Options.defaultSweeperSettings = {
};
module.exports = Options;
-
-/**
- * @external RESTOptions
- * @see {@link https://discord.js.org/#/docs/rest/main/typedef/RESTOptions}
- */
diff --git a/src/util/Partials.js b/src/util/Partials.js
deleted file mode 100644
index 7bbe517..00000000
--- a/src/util/Partials.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-
-const { createEnum } = require('./Enums');
-
-module.exports = createEnum(['User', 'Channel', 'GuildMember', 'Message', 'Reaction', 'GuildScheduledEvent']);
diff --git a/src/util/Permissions.js b/src/util/Permissions.js
new file mode 100644
index 00000000..c7a9134
--- /dev/null
+++ b/src/util/Permissions.js
@@ -0,0 +1,182 @@
+'use strict';
+
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of
+ * permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member
+ * that override their default permissions.
+ * @extends {BitField}
+ */
+class Permissions extends BitField {
+ /**
+ * Bitfield of the packed bits
+ * @type {bigint}
+ * @name Permissions#bitfield
+ */
+
+ /**
+ * Data that can be resolved to give a permission number. This can be:
+ * * A string (see {@link Permissions.FLAGS})
+ * * A permission number
+ * * An instance of Permissions
+ * * An Array of PermissionResolvable
+ * @typedef {string|bigint|Permissions|PermissionResolvable[]} PermissionResolvable
+ */
+
+ /**
+ * Gets all given bits that are missing from the bitfield.
+ * @param {BitFieldResolvable} bits Bit(s) to check for
+ * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
+ * @returns {string[]}
+ */
+ missing(bits, checkAdmin = true) {
+ return checkAdmin && this.has(this.constructor.FLAGS.ADMINISTRATOR) ? [] : super.missing(bits);
+ }
+
+ /**
+ * Checks whether the bitfield has a permission, or any of multiple permissions.
+ * @param {PermissionResolvable} permission Permission(s) to check for
+ * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
+ * @returns {boolean}
+ */
+ any(permission, checkAdmin = true) {
+ return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.any(permission);
+ }
+
+ /**
+ * Checks whether the bitfield has a permission, or multiple permissions.
+ * @param {PermissionResolvable} permission Permission(s) to check for
+ * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
+ * @returns {boolean}
+ */
+ has(permission, checkAdmin = true) {
+ return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.has(permission);
+ }
+
+ /**
+ * Gets an {@link Array} of bitfield names based on the permissions available.
+ * @returns {string[]}
+ */
+ toArray() {
+ return super.toArray(false);
+ }
+}
+
+/**
+ * Numeric permission flags. All available properties:
+ * * `CREATE_INSTANT_INVITE` (create invitations to the guild)
+ * * `KICK_MEMBERS`
+ * * `BAN_MEMBERS`
+ * * `ADMINISTRATOR` (implicitly has *all* permissions, and bypasses all channel overwrites)
+ * * `MANAGE_CHANNELS` (edit and reorder channels)
+ * * `MANAGE_GUILD` (edit the guild information, region, etc.)
+ * * `ADD_REACTIONS` (add new reactions to messages)
+ * * `VIEW_AUDIT_LOG`
+ * * `PRIORITY_SPEAKER`
+ * * `STREAM`
+ * * `VIEW_CHANNEL`
+ * * `SEND_MESSAGES`
+ * * `SEND_TTS_MESSAGES`
+ * * `MANAGE_MESSAGES` (delete messages and reactions)
+ * * `EMBED_LINKS` (links posted will have a preview embedded)
+ * * `ATTACH_FILES`
+ * * `READ_MESSAGE_HISTORY` (view messages that were posted prior to opening Discord)
+ * * `MENTION_EVERYONE`
+ * * `USE_EXTERNAL_EMOJIS` (use emojis from different guilds)
+ * * `VIEW_GUILD_INSIGHTS`
+ * * `CONNECT` (connect to a voice channel)
+ * * `SPEAK` (speak in a voice channel)
+ * * `MUTE_MEMBERS` (mute members across all voice channels)
+ * * `DEAFEN_MEMBERS` (deafen members across all voice channels)
+ * * `MOVE_MEMBERS` (move members between voice channels)
+ * * `USE_VAD` (use voice activity detection)
+ * * `CHANGE_NICKNAME`
+ * * `MANAGE_NICKNAMES` (change other members' nicknames)
+ * * `MANAGE_ROLES`
+ * * `MANAGE_WEBHOOKS`
+ * * `MANAGE_EMOJIS_AND_STICKERS`
+ * * `USE_APPLICATION_COMMANDS`
+ * * `REQUEST_TO_SPEAK`
+ * * `MANAGE_EVENTS`
+ * * `MANAGE_THREADS`
+ * * `USE_PUBLIC_THREADS` (deprecated)
+ * * `CREATE_PUBLIC_THREADS`
+ * * `USE_PRIVATE_THREADS` (deprecated)
+ * * `CREATE_PRIVATE_THREADS`
+ * * `USE_EXTERNAL_STICKERS` (use stickers from different guilds)
+ * * `SEND_MESSAGES_IN_THREADS`
+ * * `START_EMBEDDED_ACTIVITIES`
+ * * `MODERATE_MEMBERS`
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
+ */
+Permissions.FLAGS = {
+ CREATE_INSTANT_INVITE: 1n << 0n,
+ KICK_MEMBERS: 1n << 1n,
+ BAN_MEMBERS: 1n << 2n,
+ ADMINISTRATOR: 1n << 3n,
+ MANAGE_CHANNELS: 1n << 4n,
+ MANAGE_GUILD: 1n << 5n,
+ ADD_REACTIONS: 1n << 6n,
+ VIEW_AUDIT_LOG: 1n << 7n,
+ PRIORITY_SPEAKER: 1n << 8n,
+ STREAM: 1n << 9n,
+ VIEW_CHANNEL: 1n << 10n,
+ SEND_MESSAGES: 1n << 11n,
+ SEND_TTS_MESSAGES: 1n << 12n,
+ MANAGE_MESSAGES: 1n << 13n,
+ EMBED_LINKS: 1n << 14n,
+ ATTACH_FILES: 1n << 15n,
+ READ_MESSAGE_HISTORY: 1n << 16n,
+ MENTION_EVERYONE: 1n << 17n,
+ USE_EXTERNAL_EMOJIS: 1n << 18n,
+ VIEW_GUILD_INSIGHTS: 1n << 19n,
+ CONNECT: 1n << 20n,
+ SPEAK: 1n << 21n,
+ MUTE_MEMBERS: 1n << 22n,
+ DEAFEN_MEMBERS: 1n << 23n,
+ MOVE_MEMBERS: 1n << 24n,
+ USE_VAD: 1n << 25n,
+ CHANGE_NICKNAME: 1n << 26n,
+ MANAGE_NICKNAMES: 1n << 27n,
+ MANAGE_ROLES: 1n << 28n,
+ MANAGE_WEBHOOKS: 1n << 29n,
+ MANAGE_EMOJIS_AND_STICKERS: 1n << 30n,
+ USE_APPLICATION_COMMANDS: 1n << 31n,
+ REQUEST_TO_SPEAK: 1n << 32n,
+ MANAGE_EVENTS: 1n << 33n,
+ MANAGE_THREADS: 1n << 34n,
+ // TODO: Remove deprecated USE_*_THREADS flags in v14
+ USE_PUBLIC_THREADS: 1n << 35n,
+ CREATE_PUBLIC_THREADS: 1n << 35n,
+ USE_PRIVATE_THREADS: 1n << 36n,
+ CREATE_PRIVATE_THREADS: 1n << 36n,
+ USE_EXTERNAL_STICKERS: 1n << 37n,
+ SEND_MESSAGES_IN_THREADS: 1n << 38n,
+ START_EMBEDDED_ACTIVITIES: 1n << 39n,
+ MODERATE_MEMBERS: 1n << 40n,
+};
+
+/**
+ * Bitfield representing every permission combined
+ * @type {bigint}
+ */
+Permissions.ALL = Object.values(Permissions.FLAGS).reduce((all, p) => all | p, 0n);
+
+/**
+ * Bitfield representing the default permissions for users
+ * @type {bigint}
+ */
+Permissions.DEFAULT = BigInt(104324673);
+
+/**
+ * Bitfield representing the permissions required for moderators of stage channels
+ * @type {bigint}
+ */
+Permissions.STAGE_MODERATOR =
+ Permissions.FLAGS.MANAGE_CHANNELS | Permissions.FLAGS.MUTE_MEMBERS | Permissions.FLAGS.MOVE_MEMBERS;
+
+Permissions.defaultBit = BigInt(0);
+
+module.exports = Permissions;
diff --git a/src/util/PermissionsBitField.js b/src/util/PermissionsBitField.js
deleted file mode 100644
index ff101c3..00000000
--- a/src/util/PermissionsBitField.js
+++ /dev/null
@@ -1,95 +0,0 @@
-'use strict';
-
-const { PermissionFlagsBits } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of
- * permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member
- * that override their default permissions.
- * @extends {BitField}
- */
-class PermissionsBitField extends BitField {
- /**
- * Bitfield of the packed bits
- * @type {bigint}
- * @name Permissions#bitfield
- */
-
- /**
- * Data that can be resolved to give a permission number. This can be:
- * * A string (see {@link PermissionsBitField.Flags})
- * * A permission number
- * * An instance of {@link PermissionsBitField}
- * * An Array of PermissionResolvable
- * @typedef {string|bigint|PermissionsBitField|PermissionResolvable[]} PermissionResolvable
- */
-
- /**
- * Gets all given bits that are missing from the bitfield.
- * @param {BitFieldResolvable} bits Bit(s) to check for
- * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
- * @returns {string[]}
- */
- missing(bits, checkAdmin = true) {
- return checkAdmin && this.has(PermissionFlagsBits.Administrator) ? [] : super.missing(bits);
- }
-
- /**
- * Checks whether the bitfield has a permission, or any of multiple permissions.
- * @param {PermissionResolvable} permission Permission(s) to check for
- * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
- * @returns {boolean}
- */
- any(permission, checkAdmin = true) {
- return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.any(permission);
- }
-
- /**
- * Checks whether the bitfield has a permission, or multiple permissions.
- * @param {PermissionResolvable} permission Permission(s) to check for
- * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
- * @returns {boolean}
- */
- has(permission, checkAdmin = true) {
- return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.has(permission);
- }
-
- /**
- * Gets an {@link Array} of bitfield names based on the permissions available.
- * @returns {string[]}
- */
- toArray() {
- return super.toArray(false);
- }
-}
-
-/**
- * Numeric permission flags.
- * @type {PermissionFlagsBits}
- * @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
- */
-PermissionsBitField.Flags = PermissionFlagsBits;
-
-/**
- * Bitfield representing every permission combined
- * @type {bigint}
- */
-PermissionsBitField.All = Object.values(PermissionFlagsBits).reduce((all, p) => all | p, 0n);
-
-/**
- * Bitfield representing the default permissions for users
- * @type {bigint}
- */
-PermissionsBitField.Default = BigInt(104324673);
-
-/**
- * Bitfield representing the permissions required for moderators of stage channels
- * @type {bigint}
- */
-PermissionsBitField.StageModerator =
- PermissionFlagsBits.ManageChannels | PermissionFlagsBits.MuteMembers | PermissionFlagsBits.MoveMembers;
-
-PermissionsBitField.defaultBit = BigInt(0);
-
-module.exports = PermissionsBitField;
diff --git a/src/util/ShardEvents.js b/src/util/ShardEvents.js
deleted file mode 100644
index 102c722..00000000
--- a/src/util/ShardEvents.js
+++ /dev/null
@@ -1,10 +0,0 @@
-'use strict';
-
-module.exports = {
- Close: 'close',
- Destroyed: 'destroyed',
- InvalidSession: 'invalidSession',
- Ready: 'ready',
- Resumed: 'resumed',
- AllReady: 'allReady',
-};
diff --git a/src/util/SnowflakeUtil.js b/src/util/SnowflakeUtil.js
new file mode 100644
index 00000000..3e89efd
--- /dev/null
+++ b/src/util/SnowflakeUtil.js
@@ -0,0 +1,92 @@
+'use strict';
+
+// Discord epoch (2015-01-01T00:00:00.000Z)
+const EPOCH = 1_420_070_400_000;
+let INCREMENT = BigInt(0);
+
+/**
+ * A container for useful snowflake-related methods.
+ */
+class SnowflakeUtil extends null {
+ /**
+ * 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 ms since Discord epoch worker pid increment
+ * ```
+ * @typedef {string} Snowflake
+ */
+
+ /**
+ * Generates a Discord snowflake.
+ * This hardcodes the worker's id as 1 and the process's id as 0.
+ * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate
+ * @returns {Snowflake} The generated snowflake
+ */
+ static generate(timestamp = Date.now()) {
+ if (timestamp instanceof Date) timestamp = timestamp.getTime();
+ if (typeof timestamp !== 'number' || isNaN(timestamp)) {
+ throw new TypeError(
+ `"timestamp" argument must be a number (received ${isNaN(timestamp) ? 'NaN' : typeof timestamp})`,
+ );
+ }
+ if (INCREMENT >= 4095n) INCREMENT = BigInt(0);
+
+ // Assign WorkerId as 1 and ProcessId as 0:
+ return ((BigInt(timestamp - EPOCH) << 22n) | (1n << 17n) | INCREMENT++).toString();
+ }
+
+ /**
+ * A deconstructed snowflake.
+ * @typedef {Object} DeconstructedSnowflake
+ * @property {number} timestamp Timestamp the snowflake was created
+ * @property {Date} date Date the snowflake was created
+ * @property {number} workerId The worker's id in the snowflake
+ * @property {number} processId The process's id in the snowflake
+ * @property {number} increment Increment in the snowflake
+ * @property {string} binary Binary representation of the snowflake
+ */
+
+ /**
+ * Deconstructs a Discord snowflake.
+ * @param {Snowflake} snowflake Snowflake to deconstruct
+ * @returns {DeconstructedSnowflake}
+ */
+ static deconstruct(snowflake) {
+ const bigIntSnowflake = BigInt(snowflake);
+ return {
+ timestamp: Number(bigIntSnowflake >> 22n) + EPOCH,
+ get date() {
+ return new Date(this.timestamp);
+ },
+ workerId: Number((bigIntSnowflake >> 17n) & 0b11111n),
+ processId: Number((bigIntSnowflake >> 12n) & 0b11111n),
+ increment: Number(bigIntSnowflake & 0b111111111111n),
+ binary: bigIntSnowflake.toString(2).padStart(64, '0'),
+ };
+ }
+
+ /**
+ * Retrieves the timestamp field's value from a Discord snowflake.
+ * @param {Snowflake} snowflake Snowflake to get the timestamp value from
+ * @returns {number}
+ */
+ static timestampFrom(snowflake) {
+ return Number(BigInt(snowflake) >> 22n) + EPOCH;
+ }
+
+ /**
+ * Discord's epoch value (2015-01-01T00:00:00.000Z).
+ * @type {number}
+ * @readonly
+ */
+ static get EPOCH() {
+ return EPOCH;
+ }
+}
+
+module.exports = SnowflakeUtil;
diff --git a/src/util/Status.js b/src/util/Status.js
deleted file mode 100644
index d614c72..00000000
--- a/src/util/Status.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict';
-
-const { createEnum } = require('./Enums');
-
-module.exports = createEnum([
- 'Ready',
- 'Connecting',
- 'Reconnecting',
- 'Idle',
- 'Nearly',
- 'Disconnected',
- 'WaitingForGuilds',
- 'Identifying',
- 'Resuming',
-]);
diff --git a/src/util/Sweepers.js b/src/util/Sweepers.js
index bcd7df0..ab91bf9 100644
--- a/src/util/Sweepers.js
+++ b/src/util/Sweepers.js
@@ -1,8 +1,7 @@
'use strict';
-const { setInterval, clearInterval } = require('node:timers');
-const { ThreadChannelTypes, SweeperKeys } = require('./Constants');
-const Events = require('./Events');
+const { setInterval } = require('node:timers');
+const { Events, ThreadChannelTypes, SweeperKeys } = require('./Constants');
const { TypeError } = require('../errors/DJSError.js');
/**
@@ -72,7 +71,7 @@ class Sweepers {
const globalCommands = this.client.application?.commands.cache.sweep(filter) ?? 0;
this.client.emit(
- Events.CacheSweep,
+ Events.CACHE_SWEEP,
`Swept ${globalCommands} global application commands and ${guildCommands} guild commands in ${guilds} guilds.`,
);
return guildCommands + globalCommands;
@@ -137,12 +136,12 @@ class Sweepers {
let messages = 0;
for (const channel of this.client.channels.cache.values()) {
- if (!channel.isTextBased()) continue;
+ if (!channel.isText()) continue;
channels++;
messages += channel.messages.cache.sweep(filter);
}
- this.client.emit(Events.CacheSweep, `Swept ${messages} messages in ${channels} text-based channels.`);
+ this.client.emit(Events.CACHE_SWEEP, `Swept ${messages} messages in ${channels} text-based channels.`);
return messages;
}
@@ -169,7 +168,7 @@ class Sweepers {
let reactions = 0;
for (const channel of this.client.channels.cache.values()) {
- if (!channel.isTextBased()) continue;
+ if (!channel.isText()) continue;
channels++;
for (const message of channel.messages.cache.values()) {
@@ -178,7 +177,7 @@ class Sweepers {
}
}
this.client.emit(
- Events.CacheSweep,
+ Events.CACHE_SWEEP,
`Swept ${reactions} reactions on ${messages} messages in ${channels} text-based channels.`,
);
return reactions;
@@ -193,15 +192,6 @@ class Sweepers {
return this._sweepGuildDirectProp('stageInstances', filter, { outputName: 'stage instances' }).items;
}
- /**
- * Sweeps all guild stickers and removes the ones which are indicated by the filter.
- * @param {Function} filter The function used to determine which stickers will be removed from the caches.
- * @returns {number} Amount of stickers that were removed from the caches
- */
- sweepStickers(filter) {
- return this._sweepGuildDirectProp('stickers', filter).items;
- }
-
/**
* Sweeps all thread members and removes the ones which are indicated by the filter.
* It is highly recommended to keep the client thread member cached
@@ -220,7 +210,7 @@ class Sweepers {
threads++;
members += channel.members.cache.sweep(filter);
}
- this.client.emit(Events.CacheSweep, `Swept ${members} thread members in ${threads} threads.`);
+ this.client.emit(Events.CACHE_SWEEP, `Swept ${members} thread members in ${threads} threads.`);
return members;
}
@@ -251,7 +241,7 @@ class Sweepers {
this.client.channels._remove(key);
}
}
- this.client.emit(Events.CacheSweep, `Swept ${threads} threads.`);
+ this.client.emit(Events.CACHE_SWEEP, `Swept ${threads} threads.`);
return threads;
}
@@ -267,7 +257,7 @@ class Sweepers {
const users = this.client.users.cache.sweep(filter);
- this.client.emit(Events.CacheSweep, `Swept ${users} users.`);
+ this.client.emit(Events.CACHE_SWEEP, `Swept ${users} users.`);
return users;
}
@@ -405,7 +395,7 @@ class Sweepers {
}
if (emit) {
- this.client.emit(Events.CacheSweep, `Swept ${items} ${outputName ?? key} in ${guilds} guilds.`);
+ this.client.emit(Events.CACHE_SWEEP, `Swept ${items} ${outputName ?? key} in ${guilds} guilds.`);
}
return { guilds, items };
diff --git a/src/util/SystemChannelFlags.js b/src/util/SystemChannelFlags.js
new file mode 100644
index 00000000..bf5a56a
--- /dev/null
+++ b/src/util/SystemChannelFlags.js
@@ -0,0 +1,51 @@
+'use strict';
+
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with a {@link Guild#systemChannelFlags} bitfield.
+ * Note that all event message types are enabled by default,
+ * and by setting their corresponding flags you are disabling them
+ * @extends {BitField}
+ */
+class SystemChannelFlags extends BitField {}
+
+/**
+ * @name SystemChannelFlags
+ * @kind constructor
+ * @memberof SystemChannelFlags
+ * @param {SystemChannelFlagsResolvable} [bits=0] Bit(s) to read from
+ */
+
+/**
+ * Bitfield of the packed bits
+ * @type {number}
+ * @name SystemChannelFlags#bitfield
+ */
+
+/**
+ * Data that can be resolved to give a system channel flag bitfield. This can be:
+ * * A string (see {@link SystemChannelFlags.FLAGS})
+ * * A system channel flag
+ * * An instance of SystemChannelFlags
+ * * An Array of SystemChannelFlagsResolvable
+ * @typedef {string|number|SystemChannelFlags|SystemChannelFlagsResolvable[]} SystemChannelFlagsResolvable
+ */
+
+/**
+ * Numeric system channel flags. All available properties:
+ * * `SUPPRESS_JOIN_NOTIFICATIONS` (Suppress member join notifications)
+ * * `SUPPRESS_PREMIUM_SUBSCRIPTIONS` (Suppress server boost notifications)
+ * * `SUPPRESS_GUILD_REMINDER_NOTIFICATIONS` (Suppress server setup tips)
+ * * `SUPPRESS_JOIN_NOTIFICATION_REPLIES` (Hide member join sticker reply buttons)
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags}
+ */
+SystemChannelFlags.FLAGS = {
+ SUPPRESS_JOIN_NOTIFICATIONS: 1 << 0,
+ SUPPRESS_PREMIUM_SUBSCRIPTIONS: 1 << 1,
+ SUPPRESS_GUILD_REMINDER_NOTIFICATIONS: 1 << 2,
+ SUPPRESS_JOIN_NOTIFICATION_REPLIES: 1 << 3,
+};
+
+module.exports = SystemChannelFlags;
diff --git a/src/util/SystemChannelFlagsBitField.js b/src/util/SystemChannelFlagsBitField.js
deleted file mode 100644
index 90f2cca..00000000
--- a/src/util/SystemChannelFlagsBitField.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict';
-
-const { GuildSystemChannelFlags } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to interact with a {@link Guild#systemChannelFlags} bitfield.
- * Note that all event message types are enabled by default,
- * and by setting their corresponding flags you are disabling them
- * @extends {BitField}
- */
-class SystemChannelFlagsBitField extends BitField {}
-
-/**
- * @name SystemChannelFlagsBitField
- * @kind constructor
- * @memberof SystemChannelFlagsBitField
- * @param {SystemChannelFlagsResolvable} [bits=0] Bit(s) to read from
- */
-
-/**
- * Bitfield of the packed bits
- * @type {number}
- * @name SystemChannelFlagsBitField#bitfield
- */
-
-/**
- * Data that can be resolved to give a system channel flag bitfield. This can be:
- * * A string (see {@link SystemChannelFlagsBitField.Flags})
- * * A system channel flag
- * * An instance of SystemChannelFlagsBitField
- * * An Array of SystemChannelFlagsResolvable
- * @typedef {string|number|SystemChannelFlagsBitField|SystemChannelFlagsResolvable[]} SystemChannelFlagsResolvable
- */
-
-/**
- * Numeric system channel flags.
- * @type {GuildSystemChannelFlags}
- */
-SystemChannelFlagsBitField.Flags = GuildSystemChannelFlags;
-
-module.exports = SystemChannelFlagsBitField;
diff --git a/src/util/ThreadMemberFlagsBitField.js b/src/util/ThreadMemberFlags.js
similarity index 64%
rename from src/util/ThreadMemberFlagsBitField.js
rename to src/util/ThreadMemberFlags.js
index 4f94b52..309ea59 100644
--- a/src/util/ThreadMemberFlagsBitField.js
+++ b/src/util/ThreadMemberFlags.js
@@ -6,25 +6,25 @@ const BitField = require('./BitField');
* Data structure that makes it easy to interact with a {@link ThreadMember#flags} bitfield.
* @extends {BitField}
*/
-class ThreadMemberFlagsBitField extends BitField {}
+class ThreadMemberFlags extends BitField {}
/**
- * @name ThreadMemberFlagsBitField
+ * @name ThreadMemberFlags
* @kind constructor
- * @memberof ThreadMemberFlagsBitField
+ * @memberof ThreadMemberFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
- * @name ThreadMemberFlagsBitField#bitfield
+ * @name ThreadMemberFlags#bitfield
*/
/**
* Numeric thread member flags. There are currently no bitflags relevant to bots for this.
* @type {Object}
*/
-ThreadMemberFlagsBitField.Flags = {};
+ThreadMemberFlags.FLAGS = {};
-module.exports = ThreadMemberFlagsBitField;
+module.exports = ThreadMemberFlags;
diff --git a/src/util/Transformers.js b/src/util/Transformers.js
deleted file mode 100644
index f7eed82..00000000
--- a/src/util/Transformers.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-const snakeCase = require('lodash.snakecase');
-
-class Transformers extends null {
- /**
- * Transforms camel-cased keys into snake cased keys
- * @param {*} obj The object to transform
- * @returns {*}
- */
- static toSnakeCase(obj) {
- if (typeof obj !== 'object' || !obj) return obj;
- if (Array.isArray(obj)) return obj.map(Transformers.toSnakeCase);
- return Object.fromEntries(
- Object.entries(obj).map(([key, value]) => [snakeCase(key), Transformers.toSnakeCase(value)]),
- );
- }
-}
-
-module.exports = Transformers;
diff --git a/src/util/UserFlags.js b/src/util/UserFlags.js
new file mode 100644
index 00000000..5849750
--- /dev/null
+++ b/src/util/UserFlags.js
@@ -0,0 +1,59 @@
+'use strict';
+const BitField = require('./BitField');
+
+/**
+ * Data structure that makes it easy to interact with a {@link User#flags} bitfield.
+ * @extends {BitField}
+ */
+class UserFlags extends BitField {}
+
+/**
+ * @name UserFlags
+ * @kind constructor
+ * @memberof UserFlags
+ * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
+ */
+
+/**
+ * Bitfield of the packed bits
+ * @type {number}
+ * @name UserFlags#bitfield
+ */
+
+/**
+ * Numeric user flags. All available properties:
+ * * `DISCORD_EMPLOYEE`
+ * * `PARTNERED_SERVER_OWNER`
+ * * `HYPESQUAD_EVENTS`
+ * * `BUGHUNTER_LEVEL_1`
+ * * `HOUSE_BRAVERY`
+ * * `HOUSE_BRILLIANCE`
+ * * `HOUSE_BALANCE`
+ * * `EARLY_SUPPORTER`
+ * * `TEAM_USER`
+ * * `BUGHUNTER_LEVEL_2`
+ * * `VERIFIED_BOT`
+ * * `EARLY_VERIFIED_BOT_DEVELOPER`
+ * * `DISCORD_CERTIFIED_MODERATOR`
+ * * `BOT_HTTP_INTERACTIONS`
+ * @type {Object}
+ * @see {@link https://discord.com/developers/docs/resources/user#user-object-user-flags}
+ */
+UserFlags.FLAGS = {
+ DISCORD_EMPLOYEE: 1 << 0,
+ PARTNERED_SERVER_OWNER: 1 << 1,
+ HYPESQUAD_EVENTS: 1 << 2,
+ BUGHUNTER_LEVEL_1: 1 << 3,
+ HOUSE_BRAVERY: 1 << 6,
+ HOUSE_BRILLIANCE: 1 << 7,
+ HOUSE_BALANCE: 1 << 8,
+ EARLY_SUPPORTER: 1 << 9,
+ TEAM_USER: 1 << 10,
+ BUGHUNTER_LEVEL_2: 1 << 14,
+ VERIFIED_BOT: 1 << 16,
+ EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17,
+ DISCORD_CERTIFIED_MODERATOR: 1 << 18,
+ BOT_HTTP_INTERACTIONS: 1 << 19,
+};
+
+module.exports = UserFlags;
diff --git a/src/util/UserFlagsBitField.js b/src/util/UserFlagsBitField.js
deleted file mode 100644
index dc2a943..00000000
--- a/src/util/UserFlagsBitField.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-
-const { UserFlags } = require('discord-api-types/v9');
-const BitField = require('./BitField');
-
-/**
- * Data structure that makes it easy to interact with a {@link User#flags} bitfield.
- * @extends {BitField}
- */
-class UserFlagsBitField extends BitField {}
-
-/**
- * @name UserFlagsBitField
- * @kind constructor
- * @memberof UserFlagsBitField
- * @param {BitFieldResolvable} [bits=0] Bit(s) to read from
- */
-
-/**
- * Bitfield of the packed bits
- * @type {number}
- * @name UserFlagsBitField#bitfield
- */
-
-/**
- * Numeric user flags.
- * @type {UserFlags}
- */
-UserFlagsBitField.Flags = UserFlags;
-
-module.exports = UserFlagsBitField;
diff --git a/src/util/Util.js b/src/util/Util.js
index 2ab39b4..7901f77 100644
--- a/src/util/Util.js
+++ b/src/util/Util.js
@@ -1,13 +1,17 @@
'use strict';
const { parse } = require('node:path');
+const process = require('node:process');
const { Collection } = require('@discordjs/collection');
-const { ChannelType, RouteBases, Routes } = require('discord-api-types/v9');
-const { fetch } = require('undici');
-const Colors = require('./Colors');
+const fetch = require('node-fetch');
+const { Colors, Endpoints } = require('./Constants');
+const Options = require('./Options');
const { Error: DiscordError, RangeError, TypeError } = require('../errors');
+const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
const isObject = d => typeof d === 'object' && d !== null;
+let deprecationEmittedForRemoveMentions = false;
+
/**
* Contains various general-purpose utility methods.
*/
@@ -268,7 +272,8 @@ class Util extends null {
*/
static async fetchRecommendedShards(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) {
if (!token) throw new DiscordError('TOKEN_MISSING');
- const response = await fetch(RouteBases.api + Routes.gatewayBot(), {
+ const defaults = Options.createDefault();
+ const response = await fetch(`${defaults.http.api}/v${defaults.http.version}${Endpoints.botGateway}`, {
method: 'GET',
headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` },
});
@@ -330,7 +335,7 @@ class Util extends null {
static mergeDefault(def, given) {
if (!given) return def;
for (const key in def) {
- if (!Object.hasOwn(given, key) || given[key] === undefined) {
+ if (!has(given, key) || given[key] === undefined) {
given[key] = def[key];
} else if (given[key] === Object(given[key])) {
given[key] = Util.mergeDefault(def[key], given[key]);
@@ -419,37 +424,37 @@ class Util extends null {
* [255, 0, 255] // purple
* ```
* or one of the following strings:
- * - `Default`
- * - `White`
- * - `Aqua`
- * - `Green`
- * - `Blue`
- * - `Yellow`
- * - `Purple`
- * - `LuminousVividPink`
- * - `Fuchsia`
- * - `Gold`
- * - `Orange`
- * - `Red`
- * - `Grey`
- * - `Navy`
- * - `DarkAqua`
- * - `DarkGreen`
- * - `DarkBlue`
- * - `DarkPurple`
- * - `DarkVividPink`
- * - `DarkGold`
- * - `DarkOrange`
- * - `DarkRed`
- * - `DarkGrey`
- * - `DarkerGrey`
- * - `LightGrey`
- * - `DarkNavy`
- * - `Blurple`
- * - `Greyple`
- * - `DarkButNotBlack`
- * - `NotQuiteBlack`
- * - `Random`
+ * - `DEFAULT`
+ * - `WHITE`
+ * - `AQUA`
+ * - `GREEN`
+ * - `BLUE`
+ * - `YELLOW`
+ * - `PURPLE`
+ * - `LUMINOUS_VIVID_PINK`
+ * - `FUCHSIA`
+ * - `GOLD`
+ * - `ORANGE`
+ * - `RED`
+ * - `GREY`
+ * - `NAVY`
+ * - `DARK_AQUA`
+ * - `DARK_GREEN`
+ * - `DARK_BLUE`
+ * - `DARK_PURPLE`
+ * - `DARK_VIVID_PINK`
+ * - `DARK_GOLD`
+ * - `DARK_ORANGE`
+ * - `DARK_RED`
+ * - `DARK_GREY`
+ * - `DARKER_GREY`
+ * - `LIGHT_GREY`
+ * - `DARK_NAVY`
+ * - `BLURPLE`
+ * - `GREYPLE`
+ * - `DARK_BUT_NOT_BLACK`
+ * - `NOT_QUITE_BLACK`
+ * - `RANDOM`
* @typedef {string|number|number[]} ColorResolvable
*/
@@ -460,8 +465,8 @@ class Util extends null {
*/
static resolveColor(color) {
if (typeof color === 'string') {
- if (color === 'Random') return Math.floor(Math.random() * (0xffffff + 1));
- if (color === 'Default') return 0;
+ if (color === 'RANDOM') return Math.floor(Math.random() * (0xffffff + 1));
+ if (color === 'DEFAULT') return 0;
color = Colors[color] ?? parseInt(color.replace('#', ''), 16);
} else if (Array.isArray(color)) {
color = (color[0] << 16) + (color[1] << 8) + color[2];
@@ -493,17 +498,16 @@ class Util extends null {
* @param {number} position New position for the object
* @param {boolean} relative Whether `position` is relative to its current position
* @param {Collection} sorted A collection of the objects sorted properly
- * @param {Client} client The client to use to patch the data
- * @param {string} route Route to call PATCH on
+ * @param {APIRouter} route Route to call PATCH on
* @param {string} [reason] Reason for the change
* @returns {Promise} Updated item list, with `id` and `position` properties
* @private
*/
- static async setPosition(item, position, relative, sorted, client, route, reason) {
+ static async setPosition(item, position, relative, sorted, route, reason) {
let updatedItems = [...sorted.values()];
Util.moveElementInArray(updatedItems, item, position, relative);
updatedItems = updatedItems.map((r, i) => ({ id: r.id, position: i }));
- await client.rest.patch(route, { body: updatedItems, reason });
+ await route.patch({ data: updatedItems, reason });
return updatedItems;
}
@@ -518,8 +522,34 @@ class Util extends null {
const res = parse(path);
return ext && res.ext.startsWith(ext) ? res.name : res.base.split('?')[0];
}
+
+ /**
+ * Breaks user, role and everyone/here mentions by adding a zero width space after every @ character
+ * @param {string} str The string to sanitize
+ * @returns {string}
+ * @deprecated Use {@link BaseMessageOptions#allowedMentions} instead.
+ */
+ static removeMentions(str) {
+ if (!deprecationEmittedForRemoveMentions) {
+ process.emitWarning(
+ 'The Util.removeMentions method is deprecated. Use MessageOptions#allowedMentions instead.',
+ 'DeprecationWarning',
+ );
+
+ deprecationEmittedForRemoveMentions = true;
+ }
+
+ return Util._removeMentions(str);
+ }
+
+ static _removeMentions(str) {
+ return str.replaceAll('@', '@\u200b');
+ }
+
/**
* The content to have all mentions replaced by the equivalent text.
+ * When {@link Util.removeMentions} is removed, this method will no longer sanitize mentions.
+ * Use {@link BaseMessageOptions#allowedMentions} instead to prevent mentions when sending a message.
* @param {string} str The string to be converted
* @param {TextBasedChannels} channel The channel the string was sent in
* @returns {string}
@@ -528,17 +558,17 @@ class Util extends null {
str = str
.replace(/<@!?[0-9]+>/g, input => {
const id = input.replace(/<|!|>|@/g, '');
- if (channel.type === ChannelType.DM) {
+ if (channel.type === 'DM') {
const user = channel.client.users.cache.get(id);
- return user ? `@${user.username}` : input;
+ return user ? Util._removeMentions(`@${user.username}`) : input;
}
const member = channel.guild.members.cache.get(id);
if (member) {
- return `@${member.displayName}`;
+ return Util._removeMentions(`@${member.displayName}`);
} else {
const user = channel.client.users.cache.get(id);
- return user ? `@${user.username}` : input;
+ return user ? Util._removeMentions(`@${user.username}`) : input;
}
})
.replace(/<#[0-9]+>/g, input => {
@@ -546,7 +576,7 @@ class Util extends null {
return mentionedChannel ? `#${mentionedChannel.name}` : input;
})
.replace(/<@&[0-9]+>/g, input => {
- if (channel.type === ChannelType.DM) return input;
+ if (channel.type === 'DM') return input;
const role = channel.guild.roles.cache.get(input.replace(/<|@|>|&/g, ''));
return role ? `@${role.name}` : input;
});
@@ -561,6 +591,18 @@ class Util extends null {
static cleanCodeBlockContent(text) {
return text.replaceAll('```', '`\u200b``');
}
+
+ /**
+ * Creates a sweep filter that sweeps archived threads
+ * @param {number} [lifetime=14400] How long a thread has to be archived to be valid for sweeping
+ * @deprecated When not using with `makeCache` use `Sweepers.archivedThreadSweepFilter` instead
+ * @returns {SweepFilter}
+ */
+ static archivedThreadSweepFilter(lifetime = 14400) {
+ const filter = require('./Sweepers').archivedThreadSweepFilter(lifetime);
+ filter.isDefault = true;
+ return filter;
+ }
}
module.exports = Util;
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 899ce76..8990f3e 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -144,6 +144,7 @@ import {
} from './rawDataTypes';
//#region Classes
+
export class Activity {
private constructor(presence: Presence, data?: RawActivityData);
public applicationId: Snowflake | null;
@@ -537,13 +538,15 @@ export class Client extends BaseClient {
private actions: unknown;
private presence: ClientPresence;
private _eval(script: string): unknown;
+ private _validateOptions(options: ClientOptions): void;
public application: If;
- public bot: Boolean;
- public channels: ChannelManager;
// Added
public setting: ClientUserSettingManager;
+ public friends: FriendsManager;
+ public blocked: BlockedManager;
// End
+ public channels: ChannelManager;
public readonly emojis: BaseGuildEmojiManager;
public guilds: GuildManager;
public options: ClientOptions;
@@ -552,12 +555,9 @@ export class Client extends BaseClient {
public sweepers: Sweepers;
public shard: ShardClientUtil | null;
public token: If;
- public session_id: String;
public uptime: If;
public user: If;
public users: UserManager;
- public friends: FriendsManager;
- public blocked: BlockedManager;
public voice: ClientVoiceManager;
public ws: WebSocketManager;
public destroy(): void;
@@ -569,7 +569,8 @@ export class Client extends BaseClient {
public fetchPremiumStickerPacks(): Promise>;
public fetchWebhook(id: Snowflake, token?: string): Promise;
public fetchGuildWidget(guild: GuildResolvable): Promise;
- public login(token?: string, bot?: Boolean): Promise;
+ public generateInvite(options?: InviteGenerationOptions): string;
+ public login(token?: string): Promise;
public isReady(): this is Client;
/** @deprecated Use {@link Sweepers#sweepMessages} instead */
public sweepMessages(lifetime?: number): number;
@@ -1127,6 +1128,10 @@ export class GuildMember extends PartialTextBasedChannel(Base) {
public deleteDM(): Promise;
public displayAvatarURL(options?: ImageURLOptions): string;
public edit(data: GuildMemberEditData, reason?: string): Promise;
+ public isCommunicationDisabled(): this is GuildMember & {
+ communicationDisabledUntilTimestamp: number;
+ readonly communicationDisabledUntil: Date;
+ };
public kick(reason?: string): Promise;
public permissionsIn(channel: GuildChannelResolvable): Readonly;
public setNickname(nickname: string | null, reason?: string): Promise;
@@ -1330,6 +1335,8 @@ export class Interaction extends Base {
public user: User;
public version: number;
public memberPermissions: CacheTypeReducer>;
+ public locale: string;
+ public guildLocale: CacheTypeReducer;
public inGuild(): this is Interaction<'present'>;
public inCachedGuild(): this is Interaction<'cached'>;
public inRawGuild(): this is Interaction<'raw'>;
@@ -1694,11 +1701,13 @@ export class MessageEmbed {
public addField(name: string, value: string, inline?: boolean): this;
public addFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): this;
public setFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): this;
- public setAuthor(options: string | EmbedAuthorData | null): this;
- /** @deprecated Supply a lone object of interface {@link EmbedAuthorData} instead of more parameters. */
+ public setAuthor(options: EmbedAuthorData | null): this;
+ /** @deprecated Supply a lone object of interface {@link EmbedAuthorData} instead. */
public setAuthor(name: string, iconURL?: string, url?: string): this;
public setColor(color: ColorResolvable): this;
public setDescription(description: string): this;
+ public setFooter(options: EmbedFooterData | null): this;
+ /** @deprecated Supply a lone object of interface {@link EmbedFooterData} instead. */
public setFooter(text: string, iconURL?: string): this;
public setImage(url: string): this;
public setThumbnail(url: string): this;
@@ -1856,7 +1865,7 @@ export class Permissions extends BitField {
public has(permission: PermissionResolvable, checkAdmin?: boolean): boolean;
public missing(bits: BitFieldResolvable, checkAdmin?: boolean): PermissionString[];
public serialize(checkAdmin?: boolean): Record;
- public toArray(checkAdmin?: boolean): PermissionString[];
+ public toArray(): PermissionString[];
public static ALL: bigint;
public static DEFAULT: bigint;
@@ -2389,12 +2398,6 @@ export class User extends PartialTextBasedChannel(Base) {
public system: boolean;
public readonly tag: string;
public username: string;
- public readonly friended: Boolean;
- public readonly blocked: Boolean;
- public readonly connectedAccounts: Readonly;
- public readonly premiumSince: number | null;
- public readonly premiumGuildSince: number | null;
- public readonly mutualGuilds: Collection;
public avatarURL(options?: ImageURLOptions): string | null;
public bannerURL(options?: ImageURLOptions): string | null;
public createDM(force?: boolean): Promise;
@@ -2433,6 +2436,7 @@ export class Util extends null {
public static cleanContent(str: string, channel: TextBasedChannel): string;
/** @deprecated Use {@link MessageOptions.allowedMentions} to control mentions in a message instead. */
public static removeMentions(str: string): string;
+ private static _removeMentions(str: string): string;
public static cloneObject(obj: unknown): unknown;
public static discordSort(
collection: Collection,
@@ -4049,6 +4053,7 @@ export interface ClientOptions {
userAgentSuffix?: string[];
presence?: PresenceData;
intents: BitFieldResolvable;
+ waitGuildTimeout?: number;
sweepers?: SweeperOptions;
ws?: WebSocketOptions;
http?: HTTPOptions;
@@ -4356,6 +4361,11 @@ export interface EmbedFieldData {
inline?: boolean;
}
+export interface EmbedFooterData {
+ text: string;
+ iconURL?: string;
+}
+
export type EmojiIdentifierResolvable = string | EmojiResolvable;
export type EmojiResolvable = Snowflake | GuildEmoji | ReactionEmoji;
@@ -5407,7 +5417,6 @@ export type PresenceStatus = PresenceStatusData | 'offline';
export type PrivacyLevel = keyof typeof PrivacyLevels;
-export type LocaleStrings = "DANISH" | "GERMAN" | "ENGLISH_UK" | "ENGLISH_US" | "SPANISH" | "FRENCH" | "CROATIAN" | "ITALIAN" | "LITHUANIAN" | "HUNGARIAN" | "DUTCH" | "NORWEGIAN" | "POLISH" | "BRAZILIAN_PORTUGUESE" | "ROMANIA_ROMANIAN" | "FINNISH" | "SWEDISH" | "VIETNAMESE" | "TURKISH" | "CZECH" | "GREEK" | "BULGARIAN" | "RUSSIAN" | "UKRAINIAN" | "HINDI" | "THAI" | "CHINA_CHINESE" | "JAPANESE" | "TAIWAN_CHINESE" | "KOREAN"
export interface RateLimitData {
timeout: number;
limit: number;
diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts
index ccfbc3a..51c9900 100644
--- a/typings/index.test-d.ts
+++ b/typings/index.test-d.ts
@@ -949,12 +949,17 @@ client.on('interactionCreate', async interaction => {
expectAssignable(interaction.member);
expectNotType>(interaction);
expectAssignable(interaction);
+ expectType(interaction.guildLocale);
} else if (interaction.inRawGuild()) {
expectAssignable(interaction.member);
expectNotAssignable>(interaction);
+ expectType(interaction.guildLocale);
+ } else if (interaction.inGuild()) {
+ expectType(interaction.guildLocale);
} else {
expectType(interaction.member);
expectNotAssignable>(interaction);
+ expectType(interaction.guildId);
}
if (interaction.isContextMenu()) {
diff --git a/typings/rawDataTypes.d.ts b/typings/rawDataTypes.d.ts
index d7a9ec7..885566d 100644
--- a/typings/rawDataTypes.d.ts
+++ b/typings/rawDataTypes.d.ts
@@ -75,7 +75,7 @@ import {
RESTPostAPIInteractionFollowupJSONBody,
RESTPostAPIWebhookWithTokenJSONBody,
Snowflake,
- APIGuildScheduledEvent
+ APIGuildScheduledEvent,
} from 'discord-api-types/v9';
import { GuildChannel, Guild, PermissionOverwrites } from '.';