Compare commits

...

2 Commits

Author SHA1 Message Date
MythEclipse
66e212677c fix: update TypeScript module settings to Node16 and adjust root directory
Some checks failed
Lint / ESLint (push) Has been cancelled
2026-05-15 07:16:20 +07:00
MythEclipse
6e947f299e feat: update rawDataTypes with new API components and types; add biome configuration file
Some checks failed
Lint / ESLint (push) Has been cancelled
2026-05-15 06:26:08 +07:00
206 changed files with 8330 additions and 3077 deletions

View File

@@ -1,290 +0,0 @@
{
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"plugins": [
"import"
],
"parserOptions": {
"ecmaVersion": 13
},
"env": {
"es2021": true,
"node": true
},
"ignorePatterns": [
"**/src/util/Voice.js",
"**/examples/**"
],
"rules": {
"import/order": [
"error",
{
"groups": [
"builtin",
"external",
"internal",
"index",
"sibling",
"parent"
],
"alphabetize": {
"order": "asc"
}
}
],
"prettier/prettier": [
2,
{
"printWidth": 120,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"endOfLine": "lf",
"arrowParens": "avoid"
}
],
"strict": [
"error",
"global"
],
// "no-await-in-loop": "warn",
"no-compare-neg-zero": "error",
"no-template-curly-in-string": "error",
"no-unsafe-negation": "error",
"valid-jsdoc": [
"error",
{
"requireReturn": false,
"requireReturnDescription": false,
"prefer": {
"return": "returns",
"arg": "param"
},
"preferType": {
"String": "string",
"Number": "number",
"Boolean": "boolean",
"Symbol": "symbol",
"object": "Object",
"function": "Function",
"array": "Array",
"date": "Date",
"error": "Error",
"null": "void"
}
}
],
"accessor-pairs": "warn",
"array-callback-return": "error",
"consistent-return": "error",
"curly": [
"error",
"multi-line",
"consistent"
],
"dot-location": [
"error",
"property"
],
"dot-notation": "error",
"eqeqeq": "off",
// "no-empty-function": "error",
"no-floating-decimal": "error",
"no-implied-eval": "error",
"no-invalid-this": "error",
"no-lone-blocks": "error",
"no-multi-spaces": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-new": "error",
"no-octal-escape": "error",
"no-return-assign": "error",
"no-return-await": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unmodified-loop-condition": "error",
"no-unused-expressions": "error",
"no-useless-call": "error",
"no-useless-concat": "error",
"no-useless-escape": "error",
"no-useless-return": "error",
"no-void": "error",
// "no-warning-comments": "warn",
"prefer-promise-reject-errors": "error",
"require-await": "off",
"wrap-iife": "error",
"yoda": "error",
"no-label-var": "error",
// "no-shadow": "error",
"no-undef-init": "error",
"callback-return": "error",
"getter-return": "off",
"handle-callback-err": "error",
"no-mixed-requires": "error",
"no-new-require": "error",
"no-path-concat": "error",
"array-bracket-spacing": "error",
"block-spacing": "error",
"brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
"capitalized-comments": [
"error",
"always",
{
"ignoreConsecutiveComments": true
}
],
"comma-dangle": [
"error",
"always-multiline"
],
"comma-spacing": "error",
"comma-style": "error",
"computed-property-spacing": "error",
"consistent-this": [
"error",
"$this"
],
"eol-last": "error",
"func-names": "error",
"func-name-matching": "error",
"func-style": [
"error",
"declaration",
{
"allowArrowFunctions": true
}
],
"key-spacing": "error",
"keyword-spacing": "error",
"max-depth": "error",
"max-len": [
"off",
120,
2
],
"max-nested-callbacks": [
"error",
{
"max": 4
}
],
"max-statements-per-line": [
"error",
{
"max": 2
}
],
"new-cap": "off",
"newline-per-chained-call": [
"error",
{
"ignoreChainWithDepth": 4
}
],
"no-array-constructor": "error",
"no-inline-comments": "off",
"no-lonely-if": "error",
"no-multiple-empty-lines": [
"error",
{
"max": 2,
"maxEOF": 1,
"maxBOF": 0
}
],
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-unneeded-ternary": "error",
"no-whitespace-before-property": "error",
"nonblock-statement-body-position": "error",
"object-curly-spacing": [
"error",
"always"
],
"operator-assignment": "error",
"padded-blocks": [
"error",
"never"
],
"quote-props": [
"error",
"as-needed"
],
"quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"semi-spacing": "error",
"semi": "error",
"space-before-blocks": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"template-tag-spacing": "error",
"unicode-bom": "error",
"arrow-body-style": "error",
"arrow-parens": [
"error",
"as-needed"
],
"arrow-spacing": "error",
"no-duplicate-imports": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"prefer-arrow-callback": "error",
"prefer-numeric-literals": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"rest-spread-spacing": "error",
"template-curly-spacing": "error",
"yield-star-spacing": "error",
"no-restricted-globals": [
"error",
{
"name": "Buffer",
"message": "Import Buffer from `node:buffer` instead"
},
{
"name": "process",
"message": "Import process from `node:process` instead"
},
{
"name": "setTimeout",
"message": "Import setTimeout from `node:timers` instead"
},
{
"name": "setInterval",
"message": "Import setInterval from `node:timers` instead"
},
{
"name": "setImmediate",
"message": "Import setImmediate from `node:timers` instead"
}
],
"linebreak-style": 0
}
}

View File

@@ -1,7 +0,0 @@
{
"singleQuote": true,
"printWidth": 120,
"trailingComma": "all",
"endOfLine": "lf",
"arrowParens": "avoid"
}

29
biome.json Normal file
View File

@@ -0,0 +1,29 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"files": {
"includes": ["src/**/*.js", "typings/**/*.ts", "*.json"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": false,
"style": {
"useNodejsImportProtocol": "warn"
},
"suspicious": {
"noExplicitAny": "warn"
}
}
}
}

View File

@@ -6,18 +6,14 @@
"types": "./typings/index.d.ts", "types": "./typings/index.d.ts",
"scripts": { "scripts": {
"all": "npm run build && npm publish", "all": "npm run build && npm publish",
"test": "npm run lint:all && npm run docs:test && npm run test:typescript", "test": "npm run lint && npm run test:typescript && npm run docs:test",
"fix:all": "npm run lint:fix && npm run lint:typings:fix && npm run format", "fix:all": "npm run format",
"test:typescript": "tsc --noEmit && tsd", "test:typescript": "tsc --noEmit && tsd",
"lint": "eslint .", "lint": "biome check . --diagnostic-level=error",
"lint:fix": "eslint . --fix", "format": "biome format --write .",
"lint:typings": "tslint typings/index.d.ts",
"lint:typings:fix": "tslint typings/index.d.ts --fix",
"format": "prettier --write src/**/*.js typings/**/*.ts",
"lint:all": "npm run lint && npm run lint:typings",
"docs": "docgen --source src --custom docs/index.yml --output docs/main.json", "docs": "docgen --source src --custom docs/index.yml --output docs/main.json",
"docs:test": "docgen --source src --custom docs/index.yml", "docs:test": "docgen --source src --custom docs/index.yml",
"build": "npm run lint:fix && npm run lint:typings:fix && npm run format && npm run docs" "build": "npm run format && npm run docs"
}, },
"files": [ "files": [
"src", "src",
@@ -51,39 +47,33 @@
}, },
"homepage": "https://github.com/aiko-chan-ai/discord.js-selfbot-v13#readme", "homepage": "https://github.com/aiko-chan-ai/discord.js-selfbot-v13#readme",
"dependencies": { "dependencies": {
"@discordjs/builders": "^1.6.3", "@discordjs/builders": "^1.13.0",
"@discordjs/collection": "^2.1.1", "@discordjs/collection": "^2.1.1",
"@sapphire/async-queue": "^1.5.5", "@sapphire/async-queue": "^1.5.5",
"@sapphire/shapeshift": "^4.0.0", "@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.38.15", "discord-api-types": "^0.38.38",
"fetch-cookie": "^3.1.0", "fetch-cookie": "^3.1.0",
"find-process": "^2.0.0", "find-process": "^2.0.0",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"prism-media": "^1.3.5", "prism-media": "^2.0.0-alpha.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"tough-cookie": "^5.1.2", "tough-cookie": "^5.1.2",
"tree-kill": "^1.2.2", "tree-kill": "^1.2.2",
"undici": "^7.11.0", "undici": "^7.16.0",
"werift-rtp": "^0.8.4", "werift-rtp": "^0.8.4",
"ws": "^8.16.0" "ws": "^8.20.0"
}, },
"engines": { "engines": {
"node": ">=20.18" "node": ">=20.18"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "latest",
"@discordjs/docgen": "^0.11.1", "@discordjs/docgen": "^0.11.1",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/node": "^22.10.7", "@types/node": "^25.8.0",
"@types/ws": "^8.5.10", "@types/ws": "^8.18.1",
"dtslint": "^4.2.1", "patch-package": "^8.0.1",
"eslint": "^8.39.0", "tsd": "^0.33.0",
"eslint-config-prettier": "^8.8.0", "typescript": "^5.9.3"
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"patch-package": "^8.0.0",
"prettier": "^2.8.8",
"tsd": "^0.32.0",
"tslint": "^6.1.3",
"typescript": "^5.5.4"
} }
} }

View File

@@ -36,4 +36,5 @@ exports.create = (gateway, query = {}, ...args) => {
return ws; return ws;
}; };
for (const state of ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']) exports[state] = exports.WebSocket[state]; for (const state of ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'])
exports[state] = exports.WebSocket[state];

View File

@@ -228,7 +228,9 @@ class Client extends BaseClient {
get emojis() { get emojis() {
const emojis = new BaseGuildEmojiManager(this); const emojis = new BaseGuildEmojiManager(this);
for (const guild of this.guilds.cache.values()) { for (const guild of this.guilds.cache.values()) {
if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji); if (guild.available)
for (const emoji of guild.emojis.cache.values())
emojis.cache.set(emoji.id, emoji);
} }
return emojis; return emojis;
} }
@@ -312,7 +314,13 @@ class Client extends BaseClient {
const initial = await this.api.auth.login.post({ const initial = await this.api.auth.login.post({
auth: false, auth: false,
versioned: true, versioned: true,
data: { gift_code_sku_id: null, login_source: null, undelete: false, login: email, password }, data: {
gift_code_sku_id: null,
login_source: null,
undelete: false,
login: email,
password,
},
}); });
if ('token' in initial) { if ('token' in initial) {
@@ -323,7 +331,12 @@ class Client extends BaseClient {
const totp = await this.api.auth.mfa.totp.post({ const totp = await this.api.auth.mfa.totp.post({
auth: false, auth: false,
versioned: true, versioned: true,
data: { gift_code_sku_id: null, login_source: null, code: otp, ticket: initial.ticket }, data: {
gift_code_sku_id: null,
login_source: null,
code: otp,
ticket: initial.ticket,
},
}); });
if ('token' in totp) { if ('token' in totp) {
return this.login(totp.token); return this.login(totp.token);
@@ -393,7 +406,10 @@ class Client extends BaseClient {
async fetchInvite(invite, options) { async fetchInvite(invite, options) {
const code = DataResolver.resolveInviteCode(invite); const code = DataResolver.resolveInviteCode(invite);
const data = await this.api.invites(code).get({ const data = await this.api.invites(code).get({
query: { with_counts: true, guild_scheduled_event_id: options?.guildScheduledEventId }, query: {
with_counts: true,
guild_scheduled_event_id: options?.guildScheduledEventId,
},
}); });
return new Invite(this, data); return new Invite(this, data);
} }
@@ -439,7 +455,8 @@ class Client extends BaseClient {
async fetchVoiceRegions() { async fetchVoiceRegions() {
const apiRegions = await this.api.voice.regions.get(); const apiRegions = await this.api.voice.regions.get();
const regions = new Collection(); const regions = new Collection();
for (const region of apiRegions) regions.set(region.id, new VoiceRegion(region)); for (const region of apiRegions)
regions.set(region.id, new VoiceRegion(region));
return regions; return regions;
} }
@@ -467,7 +484,9 @@ class Client extends BaseClient {
*/ */
async fetchPremiumStickerPacks() { async fetchPremiumStickerPacks() {
const data = await this.api('sticker-packs').get(); const data = await this.api('sticker-packs').get();
return new Collection(data.sticker_packs.map(p => [p.id, new StickerPack(this, p)])); return new Collection(
data.sticker_packs.map((p) => [p.id, new StickerPack(this, p)]),
);
} }
/** /**
* A last ditch cleanup function for garbage collection. * A last ditch cleanup function for garbage collection.
@@ -484,7 +503,10 @@ class Client extends BaseClient {
this.emit(Events.DEBUG, message); this.emit(Events.DEBUG, message);
} }
} catch { } catch {
this.emit(Events.DEBUG, `Garbage collection failed on ${name ?? 'an unknown item'}.`); this.emit(
Events.DEBUG,
`Garbage collection failed on ${name ?? 'an unknown item'}.`,
);
} }
} }
@@ -509,8 +531,13 @@ class Client extends BaseClient {
return -1; return -1;
} }
const messages = this.sweepers.sweepMessages(Sweepers.outdatedMessageSweepFilter(lifetime)()); const messages = this.sweepers.sweepMessages(
this.emit(Events.DEBUG, `Swept ${messages} messages older than ${lifetime} seconds`); Sweepers.outdatedMessageSweepFilter(lifetime)(),
);
this.emit(
Events.DEBUG,
`Swept ${messages} messages older than ${lifetime} seconds`,
);
return messages; return messages;
} }
@@ -545,7 +572,7 @@ class Client extends BaseClient {
*/ */
async refreshAttachmentURL(...urls) { async refreshAttachmentURL(...urls) {
// Clean up the URLs // Clean up the URLs
urls = urls.map(url => { urls = urls.map((url) => {
const urlObject = new URL(url); const urlObject = new URL(url);
// Clean query // Clean query
urlObject.search = ''; urlObject.search = '';
@@ -583,7 +610,7 @@ class Client extends BaseClient {
* @returns {void} The `sleep` function is returning a Promise. * @returns {void} The `sleep` function is returning a Promise.
*/ */
sleep(timeout) { sleep(timeout) {
return new Promise(r => setTimeout(r, timeout)); return new Promise((r) => setTimeout(r, timeout));
} }
toJSON() { toJSON() {
@@ -615,13 +642,18 @@ class Client extends BaseClient {
* @example * @example
* await client.acceptInvite('https://discord.gg/genshinimpact', { bypassOnboarding: true, bypassVerify: true }) * await client.acceptInvite('https://discord.gg/genshinimpact', { bypassOnboarding: true, bypassVerify: true })
*/ */
async acceptInvite(invite, options = { bypassOnboarding: true, bypassVerify: true }) { async acceptInvite(
invite,
options = { bypassOnboarding: true, bypassVerify: true },
) {
// ! throw new Error('METHOD_WARNING'); // ! throw new Error('METHOD_WARNING');
const code = DataResolver.resolveInviteCode(invite); const code = DataResolver.resolveInviteCode(invite);
if (!code) throw new Error('INVITE_RESOLVE_CODE'); if (!code) throw new Error('INVITE_RESOLVE_CODE');
const i = await this.fetchInvite(code); const i = await this.fetchInvite(code);
if (i.guild?.id && this.guilds.cache.has(i.guild?.id)) return this.guilds.cache.get(i.guild?.id); if (i.guild?.id && this.guilds.cache.has(i.guild?.id))
if (this.channels.cache.has(i.channelId)) return this.channels.cache.get(i.channelId); return this.guilds.cache.get(i.guild?.id);
if (this.channels.cache.has(i.channelId))
return this.channels.cache.get(i.channelId);
const data = await this.api.invites(code).post({ const data = await this.api.invites(code).post({
DiscordContext: { location: 'Markdown Link' }, DiscordContext: { location: 'Markdown Link' },
data: { data: {
@@ -637,10 +669,11 @@ class Client extends BaseClient {
return guild; return guild;
} }
if (options.bypassOnboarding) { if (options.bypassOnboarding) {
const onboardingData = await this.api.guilds[i.guild?.id].onboarding.get(); const onboardingData =
await this.api.guilds[i.guild?.id].onboarding.get();
// Onboarding // Onboarding
if (onboardingData.enabled) { if (onboardingData.enabled) {
const prompts = onboardingData.prompts.filter(o => o.in_onboarding); const prompts = onboardingData.prompts.filter((o) => o.in_onboarding);
if (prompts.length) { if (prompts.length) {
const onboarding_prompts_seen = {}; const onboarding_prompts_seen = {};
const onboarding_responses = []; const onboarding_responses = [];
@@ -648,10 +681,11 @@ class Client extends BaseClient {
const currentDate = Date.now(); const currentDate = Date.now();
prompts.forEach(prompt => { prompts.forEach((prompt) => {
onboarding_prompts_seen[prompt.id] = currentDate; onboarding_prompts_seen[prompt.id] = currentDate;
if (prompt.required) onboarding_responses.push(prompt.options[0].id); if (prompt.required)
prompt.options.forEach(option => { onboarding_responses.push(prompt.options[0].id);
prompt.options.forEach((option) => {
onboarding_responses_seen[option.id] = currentDate; onboarding_responses_seen[option.id] = currentDate;
}); });
}); });
@@ -663,7 +697,10 @@ class Client extends BaseClient {
onboarding_responses_seen, onboarding_responses_seen,
}, },
}); });
this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Bypassed onboarding`); this.emit(
Events.DEBUG,
`[Invite > Guild ${i.guild?.id}] Bypassed onboarding`,
);
} }
} }
} }
@@ -671,23 +708,36 @@ class Client extends BaseClient {
if (data.show_verification_form && options.bypassVerify) { if (data.show_verification_form && options.bypassVerify) {
// Check Guild // Check Guild
if (i.guild.verificationLevel == 'VERY_HIGH' && !this.user.phone) { if (i.guild.verificationLevel == 'VERY_HIGH' && !this.user.phone) {
this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Cannot bypass verify (Phone required)`); this.emit(
Events.DEBUG,
`[Invite > Guild ${i.guild?.id}] Cannot bypass verify (Phone required)`,
);
return this.guilds.cache.get(i.guild?.id); return this.guilds.cache.get(i.guild?.id);
} }
if (i.guild.verificationLevel !== 'NONE' && !this.user.email) { if (i.guild.verificationLevel !== 'NONE' && !this.user.email) {
this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Cannot bypass verify (Email required)`); this.emit(
Events.DEBUG,
`[Invite > Guild ${i.guild?.id}] Cannot bypass verify (Email required)`,
);
return this.guilds.cache.get(i.guild?.id); return this.guilds.cache.get(i.guild?.id);
} }
const getForm = await this.api const getForm = await this.api
.guilds(i.guild?.id) .guilds(i.guild?.id)
['member-verification'].get({ query: { with_guild: false, invite_code: this.code } }) ['member-verification'].get({
query: { with_guild: false, invite_code: this.code },
})
.catch(() => {}); .catch(() => {});
if (getForm && getForm.form_fields[0]) { if (getForm && getForm.form_fields[0]) {
const form = Object.assign(getForm.form_fields[0], { response: true }); const form = Object.assign(getForm.form_fields[0], {
await this.api response: true,
.guilds(i.guild?.id) });
.requests['@me'].put({ data: { form_fields: [form], version: getForm.version } }); await this.api.guilds(i.guild?.id).requests['@me'].put({
this.emit(Events.DEBUG, `[Invite > Guild ${i.guild?.id}] Bypassed verify`); data: { form_fields: [form], version: getForm.version },
});
this.emit(
Events.DEBUG,
`[Invite > Guild ${i.guild?.id}] Bypassed verify`,
);
} }
} }
return guild; return guild;
@@ -706,14 +756,21 @@ class Client extends BaseClient {
redeemNitro(nitro, channel, paymentSourceId) { redeemNitro(nitro, channel, paymentSourceId) {
if (typeof nitro !== 'string') throw new Error('INVALID_NITRO'); if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
const nitroCode = const nitroCode =
nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) || nitro.match(
nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/); /(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/,
) ||
nitro.match(
/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/,
);
if (!nitroCode) return false; if (!nitroCode) return false;
const code = nitroCode[2]; const code = nitroCode[2];
channel = this.channels.resolveId(channel); channel = this.channels.resolveId(channel);
return this.api.entitlements['gift-codes'](code).redeem.post({ return this.api.entitlements['gift-codes'](code).redeem.post({
auth: true, auth: true,
data: { channel_id: channel || null, payment_source_id: paymentSourceId || null }, data: {
channel_id: channel || null,
payment_source_id: paymentSourceId || null,
},
}); });
} }
@@ -739,7 +796,11 @@ class Client extends BaseClient {
authorizeURL(urlOAuth2, options = {}) { authorizeURL(urlOAuth2, options = {}) {
// ! throw new Error('METHOD_WARNING'); // ! throw new Error('METHOD_WARNING');
const url = new URL(urlOAuth2); const url = new URL(urlOAuth2);
if (!/^https:\/\/(?:canary\.|ptb\.)?discord\.com(?:\/api(?:\/v\d{1,2})?)?\/oauth2\/authorize\?/.test(urlOAuth2)) { if (
!/^https:\/\/(?:canary\.|ptb\.)?discord\.com(?:\/api(?:\/v\d{1,2})?)?\/oauth2\/authorize\?/.test(
urlOAuth2,
)
) {
throw new Error('INVALID_URL', urlOAuth2); throw new Error('INVALID_URL', urlOAuth2);
} }
const searchParams = Object.fromEntries(url.searchParams); const searchParams = Object.fromEntries(url.searchParams);
@@ -778,7 +839,7 @@ class Client extends BaseClient {
with_guild: false, with_guild: false,
}, },
}) })
.then(rawData => { .then((rawData) => {
const installTypes = rawData.integration_types_config['1']; const installTypes = rawData.integration_types_config['1'];
if (installTypes) { if (installTypes) {
return this.api.oauth2.authorize.post({ return this.api.oauth2.authorize.post({
@@ -808,8 +869,8 @@ class Client extends BaseClient {
if (type === 'application') { if (type === 'application') {
return this.api.oauth2.tokens return this.api.oauth2.tokens
.get() .get()
.then(data => data.find(o => o.application.id == id)) .then((data) => data.find((o) => o.application.id == id))
.then(o => this.api.oauth2.tokens(o.id).delete()); .then((o) => this.api.oauth2.tokens(o.id).delete());
} else { } else {
return this.api.oauth2.tokens(id).delete(); return this.api.oauth2.tokens(id).delete();
} }
@@ -828,7 +889,7 @@ class Client extends BaseClient {
* @returns {Promise<Collection<Snowflake, AuthorizedApplicationData>>} * @returns {Promise<Collection<Snowflake, AuthorizedApplicationData>>}
*/ */
authorizedApplications() { authorizedApplications() {
return this.api.oauth2.tokens.get().then(data => { return this.api.oauth2.tokens.get().then((data) => {
const results = new Collection(); const results = new Collection();
for (const o of data) { for (const o of data) {
const application = new Application(this, o.application); const application = new Application(this, o.application);
@@ -864,50 +925,124 @@ class Client extends BaseClient {
if (typeof options.makeCache !== 'function') { if (typeof options.makeCache !== 'function') {
throw new TypeError('CLIENT_INVALID_OPTION', 'makeCache', 'a function'); throw new TypeError('CLIENT_INVALID_OPTION', 'makeCache', 'a function');
} }
if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number'); 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)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number'); typeof options.messageSweepInterval !== 'number' ||
isNaN(options.messageSweepInterval)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'messageSweepInterval',
'a number',
);
} }
if (typeof options.sweepers !== 'object' || options.sweepers === null) { if (typeof options.sweepers !== 'object' || options.sweepers === null) {
throw new TypeError('CLIENT_INVALID_OPTION', 'sweepers', 'an object'); throw new TypeError('CLIENT_INVALID_OPTION', 'sweepers', 'an object');
} }
if (typeof options.invalidRequestWarningInterval !== 'number' || isNaN(options.invalidRequestWarningInterval)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'invalidRequestWarningInterval', 'a number'); typeof options.invalidRequestWarningInterval !== 'number' ||
isNaN(options.invalidRequestWarningInterval)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'invalidRequestWarningInterval',
'a number',
);
} }
if (!Array.isArray(options.partials)) { if (!Array.isArray(options.partials)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array'); throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
} }
if (typeof options.DMChannelVoiceStatusSync !== 'number' || isNaN(options.DMChannelVoiceStatusSync)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'DMChannelVoiceStatusSync', 'a number'); typeof options.DMChannelVoiceStatusSync !== 'number' ||
isNaN(options.DMChannelVoiceStatusSync)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'DMChannelVoiceStatusSync',
'a number',
);
} }
if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number'); typeof options.waitGuildTimeout !== 'number' ||
isNaN(options.waitGuildTimeout)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'waitGuildTimeout',
'a number',
);
} }
if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number'); typeof options.restWsBridgeTimeout !== 'number' ||
isNaN(options.restWsBridgeTimeout)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'restWsBridgeTimeout',
'a number',
);
} }
if (typeof options.restRequestTimeout !== 'number' || isNaN(options.restRequestTimeout)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'restRequestTimeout', 'a number'); typeof options.restRequestTimeout !== 'number' ||
isNaN(options.restRequestTimeout)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'restRequestTimeout',
'a number',
);
} }
if (typeof options.restGlobalRateLimit !== 'number' || isNaN(options.restGlobalRateLimit)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'restGlobalRateLimit', 'a number'); typeof options.restGlobalRateLimit !== 'number' ||
isNaN(options.restGlobalRateLimit)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'restGlobalRateLimit',
'a number',
);
} }
if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) { if (
throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number'); typeof options.restSweepInterval !== 'number' ||
isNaN(options.restSweepInterval)
) {
throw new TypeError(
'CLIENT_INVALID_OPTION',
'restSweepInterval',
'a number',
);
} }
if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) { if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number'); throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number');
} }
if (typeof options.failIfNotExists !== 'boolean') { if (typeof options.failIfNotExists !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'failIfNotExists', 'a boolean'); throw new TypeError(
'CLIENT_INVALID_OPTION',
'failIfNotExists',
'a boolean',
);
} }
if ( if (
typeof options.rejectOnRateLimit !== 'undefined' && typeof options.rejectOnRateLimit !== 'undefined' &&
!(typeof options.rejectOnRateLimit === 'function' || Array.isArray(options.rejectOnRateLimit)) !(
typeof options.rejectOnRateLimit === 'function' ||
Array.isArray(options.rejectOnRateLimit)
)
) { ) {
throw new TypeError('CLIENT_INVALID_OPTION', 'rejectOnRateLimit', 'an array or a function'); throw new TypeError(
'CLIENT_INVALID_OPTION',
'rejectOnRateLimit',
'an array or a function',
);
} }
if (typeof options.TOTPKey === 'string') { if (typeof options.TOTPKey === 'string') {
// Convert to base32 if not already // Convert to base32 if not already

View File

@@ -39,7 +39,11 @@ class WebhookClient extends BaseClient {
} }
this.id = id; this.id = id;
Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); Object.defineProperty(this, 'token', {
value: token,
writable: true,
configurable: true,
});
} }
// These are here only for documentation purposes - they are implemented by Webhook // These are here only for documentation purposes - they are implemented by Webhook

View File

@@ -38,14 +38,20 @@ class GenericAction {
if (!('recipients' in data)) { if (!('recipients' in data)) {
// Try to resolve the recipient, but do not add the client user. // Try to resolve the recipient, but do not add the client user.
const recipient = data.author ?? data.user ?? { id: data.user_id }; const recipient = data.author ?? data.user ?? { id: data.user_id };
if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient]; if (recipient.id !== this.client.user.id)
payloadData.recipients = [recipient];
} }
if (id !== undefined) payloadData.id = id; if (id !== undefined) payloadData.id = id;
return ( return (
data[this.client.actions.injectedChannel] ?? data[this.client.actions.injectedChannel] ??
this.getPayload({ ...data, ...payloadData }, this.client.channels, id, PartialTypes.CHANNEL) this.getPayload(
{ ...data, ...payloadData },
this.client.channels,
id,
PartialTypes.CHANNEL,
)
); );
} }
@@ -82,12 +88,20 @@ class GenericAction {
} }
getMember(data, guild) { getMember(data, guild) {
return this.getPayload(data, guild.members, data.user.id, PartialTypes.GUILD_MEMBER); return this.getPayload(
data,
guild.members,
data.user.id,
PartialTypes.GUILD_MEMBER,
);
} }
getUser(data) { getUser(data) {
const id = data.user_id; const id = data.user_id;
return data[this.client.actions.injectedUser] ?? this.getPayload({ id }, this.client.users, id, PartialTypes.USER); return (
data[this.client.actions.injectedUser] ??
this.getPayload({ id }, this.client.users, id, PartialTypes.USER)
);
} }
getUserFromMember(data) { getUserFromMember(data) {

View File

@@ -17,7 +17,10 @@ class AutoModerationActionExecutionAction extends Action {
* @param {AutoModerationActionExecution} autoModerationActionExecution The data of the execution * @param {AutoModerationActionExecution} autoModerationActionExecution The data of the execution
* @deprecated This event is not received by user accounts. * @deprecated This event is not received by user accounts.
*/ */
client.emit(Events.AUTO_MODERATION_ACTION_EXECUTION, new AutoModerationActionExecution(data, guild)); client.emit(
Events.AUTO_MODERATION_ACTION_EXECUTION,
new AutoModerationActionExecution(data, guild),
);
} }
return {}; return {};

View File

@@ -9,7 +9,8 @@ class AutoModerationRuleUpdateAction extends Action {
const guild = client.guilds.cache.get(data.guild_id); const guild = client.guilds.cache.get(data.guild_id);
if (guild) { if (guild) {
const oldAutoModerationRule = guild.autoModerationRules.cache.get(data.id)?._clone() ?? null; const oldAutoModerationRule =
guild.autoModerationRules.cache.get(data.id)?._clone() ?? null;
const newAutoModerationRule = guild.autoModerationRules._add(data); const newAutoModerationRule = guild.autoModerationRules._add(data);
/** /**
@@ -20,7 +21,11 @@ class AutoModerationRuleUpdateAction extends Action {
* @param {AutoModerationRule} newAutoModerationRule The auto moderation rule after the update * @param {AutoModerationRule} newAutoModerationRule The auto moderation rule after the update
* @deprecated This event is not received by user accounts. * @deprecated This event is not received by user accounts.
*/ */
client.emit(Events.AUTO_MODERATION_RULE_UPDATE, oldAutoModerationRule, newAutoModerationRule); client.emit(
Events.AUTO_MODERATION_RULE_UPDATE,
oldAutoModerationRule,
newAutoModerationRule,
);
} }
return {}; return {};

View File

@@ -21,7 +21,8 @@ class ChannelUpdateAction extends Action {
} }
if (channel.isText() && newChannel.isText()) { if (channel.isText() && newChannel.isText()) {
for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message); for (const [id, message] of channel.messages.cache)
newChannel.messages.cache.set(id, message);
} }
channel = newChannel; channel = newChannel;

View File

@@ -15,7 +15,8 @@ class GuildBanRemove extends Action {
* @param {GuildBan} ban The ban that was removed * @param {GuildBan} ban The ban that was removed
*/ */
if (guild) { if (guild) {
const ban = guild.bans.cache.get(data.user.id) ?? new GuildBan(client, data, guild); const ban =
guild.bans.cache.get(data.user.id) ?? new GuildBan(client, data, guild);
guild.bans.cache.delete(ban.user.id); guild.bans.cache.delete(ban.user.id);
client.emit(Events.GUILD_BAN_REMOVE, ban); client.emit(Events.GUILD_BAN_REMOVE, ban);
} }

View File

@@ -34,7 +34,8 @@ class GuildDeleteAction extends Action {
}; };
} }
for (const channel of guild.channels.cache.values()) this.client.channels._remove(channel.id); for (const channel of guild.channels.cache.values())
this.client.channels._remove(channel.id);
client.voice.adapters.get(data.id)?.destroy(); client.voice.adapters.get(data.id)?.destroy();
// Delete guild // Delete guild
@@ -58,7 +59,10 @@ class GuildDeleteAction extends Action {
} }
scheduleForDeletion(id) { scheduleForDeletion(id) {
setTimeout(() => this.deleted.delete(id), this.client.options.restWsBridgeTimeout).unref(); setTimeout(
() => this.deleted.delete(id),
this.client.options.restWsBridgeTimeout,
).unref();
} }
} }

View File

@@ -21,7 +21,8 @@ class GuildMemberRemoveAction extends Action {
* @param {GuildMember} member The member that has left/been kicked from the guild * @param {GuildMember} member The member that has left/been kicked from the guild
* @deprecated See {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/197 this issue} for more information. * @deprecated See {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/197 this issue} for more information.
*/ */
if (shard.status === Status.READY) client.emit(Events.GUILD_MEMBER_REMOVE, member); if (shard.status === Status.READY)
client.emit(Events.GUILD_MEMBER_REMOVE, member);
} }
guild.presences.cache.delete(data.user.id); guild.presences.cache.delete(data.user.id);
guild.voiceStates.cache.delete(data.user.id); guild.voiceStates.cache.delete(data.user.id);

View File

@@ -27,7 +27,8 @@ class GuildMemberUpdateAction extends Action {
* @param {GuildMember} newMember The member after the update * @param {GuildMember} newMember The member after the update
* @deprecated See {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/197 this issue} for more information. * @deprecated See {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/197 this issue} for more information.
*/ */
if (shard.status === Status.READY && !member.equals(old)) client.emit(Events.GUILD_MEMBER_UPDATE, old, member); if (shard.status === Status.READY && !member.equals(old))
client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
} else { } else {
const newMember = guild.members._add(data); const newMember = guild.members._add(data);
/** /**

View File

@@ -9,7 +9,8 @@ class GuildScheduledEventUpdateAction extends Action {
const guild = client.guilds.cache.get(data.guild_id); const guild = client.guilds.cache.get(data.guild_id);
if (guild) { if (guild) {
const oldGuildScheduledEvent = guild.scheduledEvents.cache.get(data.id)?._clone() ?? null; const oldGuildScheduledEvent =
guild.scheduledEvents.cache.get(data.id)?._clone() ?? null;
const newGuildScheduledEvent = guild.scheduledEvents._add(data); const newGuildScheduledEvent = guild.scheduledEvents._add(data);
/** /**
@@ -18,7 +19,11 @@ class GuildScheduledEventUpdateAction extends Action {
* @param {?GuildScheduledEvent} oldGuildScheduledEvent The guild scheduled event object before the update * @param {?GuildScheduledEvent} oldGuildScheduledEvent The guild scheduled event object before the update
* @param {GuildScheduledEvent} newGuildScheduledEvent The guild scheduled event object after the update * @param {GuildScheduledEvent} newGuildScheduledEvent The guild scheduled event object after the update
*/ */
client.emit(Events.GUILD_SCHEDULED_EVENT_UPDATE, oldGuildScheduledEvent, newGuildScheduledEvent); client.emit(
Events.GUILD_SCHEDULED_EVENT_UPDATE,
oldGuildScheduledEvent,
newGuildScheduledEvent,
);
return { oldGuildScheduledEvent, newGuildScheduledEvent }; return { oldGuildScheduledEvent, newGuildScheduledEvent };
} }

View File

@@ -19,7 +19,11 @@ class GuildScheduledEventUserAddAction extends Action {
* @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event * @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event
* @param {User} user The user who subscribed * @param {User} user The user who subscribed
*/ */
client.emit(Events.GUILD_SCHEDULED_EVENT_USER_ADD, guildScheduledEvent, user); client.emit(
Events.GUILD_SCHEDULED_EVENT_USER_ADD,
guildScheduledEvent,
user,
);
return { guildScheduledEvent, user }; return { guildScheduledEvent, user };
} }

View File

@@ -19,7 +19,11 @@ class GuildScheduledEventUserRemoveAction extends Action {
* @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event * @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event
* @param {User} user The user who unsubscribed * @param {User} user The user who unsubscribed
*/ */
client.emit(Events.GUILD_SCHEDULED_EVENT_USER_REMOVE, guildScheduledEvent, user); client.emit(
Events.GUILD_SCHEDULED_EVENT_USER_REMOVE,
guildScheduledEvent,
user,
);
return { guildScheduledEvent, user }; return { guildScheduledEvent, user };
} }

View File

@@ -18,7 +18,8 @@ class MessageCreateAction extends Action {
if (!channel.isText()) return {}; if (!channel.isText()) return {};
const existing = channel.messages.cache.get(data.id); const existing = channel.messages.cache.get(data.id);
if (existing && existing.author?.id !== this.client.user.id) return { message: existing }; if (existing && existing.author?.id !== this.client.user.id)
return { message: existing };
const message = existing ?? channel.messages._add(data); const message = existing ?? channel.messages._add(data);
channel.lastMessageId = data.id; channel.lastMessageId = data.id;
@@ -37,7 +38,10 @@ class MessageCreateAction extends Action {
*/ */
if (client.emit('message', message) && !deprecationEmitted) { if (client.emit('message', message) && !deprecationEmitted) {
deprecationEmitted = true; deprecationEmitted = true;
process.emitWarning('The message event is deprecated. Use messageCreate instead', 'DeprecationWarning'); process.emitWarning(
'The message event is deprecated. Use messageCreate instead',
'DeprecationWarning',
);
} }
return { message }; return { message };

View File

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

View File

@@ -35,7 +35,9 @@ class MessageReactionAdd extends Action {
if (!message) return false; if (!message) return false;
// Verify reaction // Verify reaction
const includePartial = this.client.options.partials.includes(PartialTypes.REACTION); const includePartial = this.client.options.partials.includes(
PartialTypes.REACTION,
);
if (message.partial && !includePartial) return false; if (message.partial && !includePartial) return false;
const reaction = message.reactions._add({ const reaction = message.reactions._add({
emoji: data.emoji, emoji: data.emoji,
@@ -59,7 +61,10 @@ class MessageReactionAdd extends Action {
* @param {User} user The user that applied the guild or reaction emoji * @param {User} user The user that applied the guild or reaction emoji
* @param {MessageReactionEventDetails} details Details of adding the reaction * @param {MessageReactionEventDetails} details Details of adding the reaction
*/ */
this.client.emit(Events.MESSAGE_REACTION_ADD, reaction, user, { type: data.type, burst: data.burst }); this.client.emit(Events.MESSAGE_REACTION_ADD, reaction, user, {
type: data.type,
burst: data.burst,
});
return { message, reaction, user }; return { message, reaction, user };
} }

View File

@@ -41,7 +41,10 @@ class MessageReactionRemove extends Action {
* @param {User} user The user whose emoji or reaction emoji was removed * @param {User} user The user whose emoji or reaction emoji was removed
* @param {MessageReactionEventDetails} details Details of removing the reaction * @param {MessageReactionEventDetails} details Details of removing the reaction
*/ */
this.client.emit(Events.MESSAGE_REACTION_REMOVE, reaction, user, { type: data.type, burst: data.burst }); this.client.emit(Events.MESSAGE_REACTION_REMOVE, reaction, user, {
type: data.type,
burst: data.burst,
});
return { message, reaction, user }; return { message, reaction, user };
} }

View File

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

View File

@@ -5,7 +5,10 @@ const { Events } = require('../../util/Constants');
class MessageReactionRemoveEmoji extends Action { class MessageReactionRemoveEmoji extends Action {
handle(data) { handle(data) {
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) }); const channel = this.getChannel({
id: data.channel_id,
...('guild_id' in data && { guild_id: data.guild_id }),
});
if (!channel || !channel.isText()) return false; if (!channel || !channel.isText()) return false;
const message = this.getMessage(data, channel); const message = this.getMessage(data, channel);
@@ -13,7 +16,8 @@ class MessageReactionRemoveEmoji extends Action {
const reaction = this.getReaction(data, message); const reaction = this.getReaction(data, message);
if (!reaction) return false; if (!reaction) return false;
if (!message.partial) message.reactions.cache.delete(reaction.emoji.id ?? reaction.emoji.name); if (!message.partial)
message.reactions.cache.delete(reaction.emoji.id ?? reaction.emoji.name);
/** /**
* Emitted when a bot removes an emoji reaction from a cached message. * Emitted when a bot removes an emoji reaction from a cached message.

View File

@@ -4,12 +4,18 @@ const Action = require('./Action');
class MessageUpdateAction extends Action { class MessageUpdateAction extends Action {
handle(data) { handle(data) {
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) }); const channel = this.getChannel({
id: data.channel_id,
...('guild_id' in data && { guild_id: data.guild_id }),
});
if (channel) { if (channel) {
if (!channel.isText()) return {}; if (!channel.isText()) return {};
const { id, channel_id, guild_id, author, timestamp, type } = data; const { id, channel_id, guild_id, author, timestamp, type } = data;
const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel); const message = this.getMessage(
{ id, channel_id, guild_id, author, timestamp, type },
channel,
);
if (message) { if (message) {
const old = message._update(data); const old = message._update(data);
return { return {

View File

@@ -8,13 +8,18 @@ class PresenceUpdateAction extends Action {
handle(data) { handle(data) {
let user = this.client.users.cache.get(data.user.id); 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 && ('username' in data.user || this.client.options.partials.includes(PartialTypes.USER))) { if (
!user &&
('username' in data.user ||
this.client.options.partials.includes(PartialTypes.USER))
) {
user = this.client.users._add(data.user); user = this.client.users._add(data.user);
} }
if (!user) return; if (!user) return;
if (data.user?.username) { if (data.user?.username) {
if (!user._equals(data.user)) this.client.actions.UserUpdate.handle(data.user); if (!user._equals(data.user))
this.client.actions.UserUpdate.handle(data.user);
} }
const guild = this.client.guilds.cache.get(data.guild_id); const guild = this.client.guilds.cache.get(data.guild_id);
@@ -31,11 +36,17 @@ class PresenceUpdateAction extends Action {
} }
} }
const oldPresence = (guild || this.client).presences.cache.get(user.id)?._clone() ?? null; const oldPresence =
(guild || this.client).presences.cache.get(user.id)?._clone() ?? null;
const newPresence = (guild || this.client).presences._add(Object.assign(data, { guild })); const newPresence = (guild || this.client).presences._add(
Object.assign(data, { guild }),
);
if (this.client.listenerCount(Events.PRESENCE_UPDATE) && !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. * Emitted whenever a guild member's presence (e.g. status, activity) is changed.
* @event Client#presenceUpdate * @event Client#presenceUpdate

View File

@@ -6,7 +6,10 @@ const { Events } = require('../../util/Constants');
class StageInstanceCreateAction extends Action { class StageInstanceCreateAction extends Action {
handle(data) { handle(data) {
const client = this.client; const client = this.client;
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id }); const channel = this.getChannel({
id: data.channel_id,
guild_id: data.guild_id,
});
if (channel) { if (channel) {
const stageInstance = channel.guild.stageInstances._add(data); const stageInstance = channel.guild.stageInstances._add(data);

View File

@@ -7,7 +7,10 @@ const { Events } = require('../../util/Constants');
class StageInstanceDeleteAction extends Action { class StageInstanceDeleteAction extends Action {
handle(data) { handle(data) {
const client = this.client; const client = this.client;
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id }); const channel = this.getChannel({
id: data.channel_id,
guild_id: data.guild_id,
});
if (channel) { if (channel) {
const stageInstance = channel.guild.stageInstances._add(data); const stageInstance = channel.guild.stageInstances._add(data);

View File

@@ -6,10 +6,14 @@ const { Events } = require('../../util/Constants');
class StageInstanceUpdateAction extends Action { class StageInstanceUpdateAction extends Action {
handle(data) { handle(data) {
const client = this.client; const client = this.client;
const channel = this.getChannel({ id: data.channel_id, guild_id: data.guild_id }); const channel = this.getChannel({
id: data.channel_id,
guild_id: data.guild_id,
});
if (channel) { if (channel) {
const oldStageInstance = channel.guild.stageInstances.cache.get(data.id)?._clone() ?? null; const oldStageInstance =
channel.guild.stageInstances.cache.get(data.id)?._clone() ?? null;
const newStageInstance = channel.guild.stageInstances._add(data); const newStageInstance = channel.guild.stageInstances._add(data);
/** /**
@@ -18,7 +22,11 @@ class StageInstanceUpdateAction extends Action {
* @param {?StageInstance} oldStageInstance The stage instance before the update * @param {?StageInstance} oldStageInstance The stage instance before the update
* @param {StageInstance} newStageInstance The stage instance after the update * @param {StageInstance} newStageInstance The stage instance after the update
*/ */
client.emit(Events.STAGE_INSTANCE_UPDATE, oldStageInstance, newStageInstance); client.emit(
Events.STAGE_INSTANCE_UPDATE,
oldStageInstance,
newStageInstance,
);
return { oldStageInstance, newStageInstance }; return { oldStageInstance, newStageInstance };
} }

View File

@@ -48,7 +48,7 @@ class ThreadListSyncAction extends Action {
} }
removeStale(channel) { removeStale(channel) {
channel.threads?.cache.forEach(thread => { channel.threads?.cache.forEach((thread) => {
if (!thread.archived) { if (!thread.archived) {
this.client.channels._remove(thread.id); this.client.channels._remove(thread.id);
} }

View File

@@ -11,11 +11,11 @@ class ThreadMembersUpdateAction extends Action {
const old = thread.members.cache.clone(); const old = thread.members.cache.clone();
thread.memberCount = data.member_count; thread.memberCount = data.member_count;
data.added_members?.forEach(rawMember => { data.added_members?.forEach((rawMember) => {
thread.members._add(rawMember); thread.members._add(rawMember);
}); });
data.removed_member_ids?.forEach(memberId => { data.removed_member_ids?.forEach((memberId) => {
thread.members.cache.delete(memberId); thread.members.cache.delete(memberId);
}); });

View File

@@ -6,11 +6,17 @@ const { Events } = require('../../util/Constants');
class TypingStart extends Action { class TypingStart extends Action {
handle(data) { handle(data) {
const channel = this.getChannel({ id: data.channel_id, ...('guild_id' in data && { guild_id: data.guild_id }) }); const channel = this.getChannel({
id: data.channel_id,
...('guild_id' in data && { guild_id: data.guild_id }),
});
if (!channel) return; if (!channel) return;
if (!channel.isText()) { if (!channel.isText()) {
this.client.emit(Events.WARN, `Discord sent a typing packet to a ${channel.type} channel ${channel.id}`); this.client.emit(
Events.WARN,
`Discord sent a typing packet to a ${channel.type} channel ${channel.id}`,
);
return; return;
} }

View File

@@ -7,7 +7,10 @@ class UserUpdateAction extends Action {
handle(data) { handle(data) {
const client = this.client; const client = this.client;
const newUser = data.id === client.user.id ? client.user : client.users.cache.get(data.id); const newUser =
data.id === client.user.id
? client.user
: client.users.cache.get(data.id);
const oldUser = newUser._update(data); const oldUser = newUser._update(data);
if (!oldUser.equals(newUser)) { if (!oldUser.equals(newUser)) {

View File

@@ -11,7 +11,8 @@ class VoiceStateUpdate extends Action {
if (guild) { if (guild) {
// Update the state // Update the state
const oldState = const oldState =
guild.voiceStates.cache.get(data.user_id)?._clone() ?? new VoiceState(guild, { user_id: data.user_id }); guild.voiceStates.cache.get(data.user_id)?._clone() ??
new VoiceState(guild, { user_id: data.user_id });
const newState = guild.voiceStates._add(data); const newState = guild.voiceStates._add(data);
@@ -33,7 +34,8 @@ class VoiceStateUpdate extends Action {
} else { } else {
// Update the state // Update the state
const oldState = const oldState =
client.voiceStates.cache.get(data.user_id)?._clone() ?? new VoiceState({ client }, { user_id: data.user_id }); client.voiceStates.cache.get(data.user_id)?._clone() ??
new VoiceState({ client }, { user_id: data.user_id });
const newState = client.voiceStates._add(data); const newState = client.voiceStates._add(data);
@@ -41,7 +43,10 @@ class VoiceStateUpdate extends Action {
} }
// Emit event // Emit event
if (data.user_id === client.user?.id) { if (data.user_id === client.user?.id) {
client.emit('debug', `[VOICE] received voice state update: ${JSON.stringify(data)}`); client.emit(
'debug',
`[VOICE] received voice state update: ${JSON.stringify(data)}`,
);
client.voice.onVoiceStateUpdate(data); client.voice.onVoiceStateUpdate(data);
} }
} }

View File

@@ -61,14 +61,25 @@ class ClientVoiceManager {
onVoiceStateUpdate(payload) { onVoiceStateUpdate(payload) {
const { guild_id, session_id, channel_id } = payload; const { guild_id, session_id, channel_id } = payload;
// @discordjs/voice // @discordjs/voice
if (payload.guild_id && payload.session_id && payload.user_id === this.client.user?.id) { if (
payload.guild_id &&
payload.session_id &&
payload.user_id === this.client.user?.id
) {
this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload); this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload);
} else if (payload.channel_id && payload.session_id && payload.user_id === this.client.user?.id) { } else if (
payload.channel_id &&
payload.session_id &&
payload.user_id === this.client.user?.id
) {
this.adapters.get(payload.channel_id)?.onVoiceStateUpdate(payload); this.adapters.get(payload.channel_id)?.onVoiceStateUpdate(payload);
} }
// Main lib // Main lib
const connection = this.connection; const connection = this.connection;
this.client.emit('debug', `[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`); this.client.emit(
'debug',
`[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`,
);
if (!connection) return; if (!connection) return;
if (!channel_id) { if (!channel_id) {
connection._disconnect(); connection._disconnect();
@@ -80,7 +91,10 @@ class ClientVoiceManager {
connection.channel = channel; connection.channel = channel;
connection.setSessionId(session_id); connection.setSessionId(session_id);
} else { } else {
this.client.emit('debug', `[VOICE] disconnecting from guild ${guild_id} as channel ${channel_id} is uncached`); this.client.emit(
'debug',
`[VOICE] disconnecting from guild ${guild_id} as channel ${channel_id} is uncached`,
);
connection.disconnect(); connection.disconnect();
} }
} }
@@ -117,8 +131,11 @@ class ClientVoiceManager {
} else { } else {
connection = new VoiceConnection(this, channel); connection = new VoiceConnection(this, channel);
if (config?.videoCodec) connection.setVideoCodec(config.videoCodec); if (config?.videoCodec) connection.setVideoCodec(config.videoCodec);
connection.on('debug', msg => connection.on('debug', (msg) =>
this.client.emit('debug', `[VOICE (${channel.guild?.id || channel.id}:${connection.status})]: ${msg}`), this.client.emit(
'debug',
`[VOICE (${channel.guild?.id || channel.id}:${connection.status})]: ${msg}`,
),
); );
connection.authenticate({ connection.authenticate({
self_mute: Boolean(config.selfMute), self_mute: Boolean(config.selfMute),
@@ -128,7 +145,7 @@ class ClientVoiceManager {
this.connection = connection; this.connection = connection;
} }
connection.once('failed', reason => { connection.once('failed', (reason) => {
this.connection = null; this.connection = null;
reject(reason); reject(reason);
}); });

View File

@@ -12,7 +12,12 @@ const { parseStreamKey } = require('./util/Function');
const PlayInterface = require('./util/PlayInterface'); const PlayInterface = require('./util/PlayInterface');
const Silence = require('./util/Silence'); const Silence = require('./util/Silence');
const { Error } = require('../../errors'); const { Error } = require('../../errors');
const { Opcodes, VoiceOpcodes, VoiceStatus, Events } = require('../../util/Constants'); const {
Opcodes,
VoiceOpcodes,
VoiceStatus,
Events,
} = require('../../util/Constants');
const Speaking = require('../../util/Speaking'); const Speaking = require('../../util/Speaking');
const Util = require('../../util/Util'); const Util = require('../../util/Util');
@@ -90,9 +95,12 @@ class VoiceConnection extends EventEmitter {
* The audio player for this voice connection * The audio player for this voice connection
* @type {MediaPlayer} * @type {MediaPlayer}
*/ */
this.player = new MediaPlayer(this, this.constructor.name === 'StreamConnection'); this.player = new MediaPlayer(
this,
this.constructor.name === 'StreamConnection',
);
this.player.on('debug', m => { this.player.on('debug', (m) => {
/** /**
* Debug info from the connection. * Debug info from the connection.
* @event VoiceConnection#debug * @event VoiceConnection#debug
@@ -101,7 +109,7 @@ class VoiceConnection extends EventEmitter {
this.emit('debug', `media player - ${m}`); this.emit('debug', `media player - ${m}`);
}); });
this.player.on('error', e => { this.player.on('error', (e) => {
/** /**
* Warning info from the connection. * Warning info from the connection.
* @event VoiceConnection#warn * @event VoiceConnection#warn
@@ -212,7 +220,7 @@ class VoiceConnection extends EventEmitter {
ssrc: this.authentication.ssrc, ssrc: this.authentication.ssrc,
}, },
}) })
.catch(e => { .catch((e) => {
this.emit('debug', e); this.emit('debug', e);
}); });
} }
@@ -223,7 +231,8 @@ class VoiceConnection extends EventEmitter {
* @returns {VoiceConnection} * @returns {VoiceConnection}
*/ */
setVideoCodec(value) { setVideoCodec(value) {
if (!SUPPORTED_CODECS.includes(value)) throw new Error('INVALID_VIDEO_CODEC', SUPPORTED_CODECS); if (!SUPPORTED_CODECS.includes(value))
throw new Error('INVALID_VIDEO_CODEC', SUPPORTED_CODECS);
this.videoCodec = value; this.videoCodec = value;
return this; return this;
} }
@@ -247,7 +256,7 @@ class VoiceConnection extends EventEmitter {
streams: [], streams: [],
}, },
}) })
.catch(e => { .catch((e) => {
this.emit('debug', e); this.emit('debug', e);
}); });
} else { } else {
@@ -277,7 +286,7 @@ class VoiceConnection extends EventEmitter {
], ],
}, },
}) })
.catch(e => { .catch((e) => {
this.emit('debug', e); this.emit('debug', e);
}); });
} }
@@ -310,7 +319,10 @@ class VoiceConnection extends EventEmitter {
options, options,
); );
this.emit('debug', `Sending voice state update: ${JSON.stringify(options)}`); this.emit(
'debug',
`Sending voice state update: ${JSON.stringify(options)}`,
);
return this.channel.client.ws.broadcast({ return this.channel.client.ws.broadcast({
op: Opcodes.VOICE_STATE_UPDATE, op: Opcodes.VOICE_STATE_UPDATE,
@@ -349,7 +361,10 @@ class VoiceConnection extends EventEmitter {
this.authentication.token = token; this.authentication.token = token;
this.authentication.endpoint = endpoint; this.authentication.endpoint = endpoint;
this.checkAuthenticated(); this.checkAuthenticated();
} else if (token !== this.authentication.token || endpoint !== this.authentication.endpoint) { } else if (
token !== this.authentication.token ||
endpoint !== this.authentication.endpoint
) {
this.reconnect(token, endpoint); this.reconnect(token, endpoint);
} }
} }
@@ -360,7 +375,10 @@ class VoiceConnection extends EventEmitter {
* @private * @private
*/ */
setSessionId(sessionId) { setSessionId(sessionId) {
this.emit('debug', `Setting sessionId ${sessionId} (stored as "${this.authentication.sessionId}")`); this.emit(
'debug',
`Setting sessionId ${sessionId} (stored as "${this.authentication.sessionId}")`,
);
if (!sessionId) { if (!sessionId) {
this.authenticateFailed('VOICE_SESSION_ABSENT'); this.authenticateFailed('VOICE_SESSION_ABSENT');
return; return;
@@ -441,7 +459,10 @@ class VoiceConnection extends EventEmitter {
*/ */
authenticate(options = {}) { authenticate(options = {}) {
this.sendVoiceStateUpdate(options); this.sendVoiceStateUpdate(options);
this.connectTimeout = setTimeout(() => this.authenticateFailed('VOICE_CONNECTION_TIMEOUT'), 15_000).unref(); this.connectTimeout = setTimeout(
() => this.authenticateFailed('VOICE_CONNECTION_TIMEOUT'),
15_000,
).unref();
} }
/** /**
@@ -537,10 +558,10 @@ class VoiceConnection extends EventEmitter {
const { ws, udp } = this.sockets; const { ws, udp } = this.sockets;
ws.on('debug', msg => this.emit('debug', msg)); ws.on('debug', (msg) => this.emit('debug', msg));
udp.on('debug', msg => this.emit('debug', msg)); udp.on('debug', (msg) => this.emit('debug', msg));
ws.on('error', err => this.emit('error', err)); ws.on('error', (err) => this.emit('error', err));
udp.on('error', err => this.emit('error', err)); udp.on('error', (err) => this.emit('error', err));
ws.on('ready', this.onReady.bind(this)); ws.on('ready', this.onReady.bind(this));
ws.on('sessionDescription', this.onSessionDescription.bind(this)); ws.on('sessionDescription', this.onSessionDescription.bind(this));
ws.on('startSpeaking', this.onStartSpeaking.bind(this)); ws.on('startSpeaking', this.onStartSpeaking.bind(this));
@@ -576,7 +597,10 @@ class VoiceConnection extends EventEmitter {
this.status = VoiceStatus.CONNECTED; this.status = VoiceStatus.CONNECTED;
const ready = () => { const ready = () => {
clearTimeout(this.connectTimeout); clearTimeout(this.connectTimeout);
this.emit('debug', `Ready with authentication details: ${JSON.stringify(this.authentication)}`); this.emit(
'debug',
`Ready with authentication details: ${JSON.stringify(this.authentication)}`,
);
/** /**
* Emitted once the connection is ready, when a promise to join a voice channel resolves, * Emitted once the connection is ready, when a promise to join a voice channel resolves,
* the connection will already be ready. * the connection will already be ready.
@@ -588,7 +612,10 @@ class VoiceConnection extends EventEmitter {
ready(); ready();
} else { } else {
// This serves to provide support for voice receive, sending audio is required to receive it. // This serves to provide support for voice receive, sending audio is required to receive it.
const dispatcher = this.playAudio(new SingleSilence(), { type: 'opus', volume: false }); const dispatcher = this.playAudio(new SingleSilence(), {
type: 'opus',
volume: false,
});
dispatcher.once('finish', ready); dispatcher.once('finish', ready);
} }
} }
@@ -678,13 +705,22 @@ class VoiceConnection extends EventEmitter {
if (this.streamConnection) { if (this.streamConnection) {
return resolve(this.streamConnection); return resolve(this.streamConnection);
} else { } else {
const connection = (this.streamConnection = new StreamConnection(this.voiceManager, this.channel, this)); const connection = (this.streamConnection = new StreamConnection(
this.voiceManager,
this.channel,
this,
));
connection.setVideoCodec(this.videoCodec); // Sync :? connection.setVideoCodec(this.videoCodec); // Sync :?
// Setup event... // Setup event...
if (!this.eventHook) { if (!this.eventHook) {
this.eventHook = true; // Dont listen this event two times :/ this.eventHook = true; // Dont listen this event two times :/
this.channel.client.on('raw', packet => { this.channel.client.on('raw', (packet) => {
if (typeof packet !== 'object' || !packet.t || !packet.d || !packet.d?.stream_key) { if (
typeof packet !== 'object' ||
!packet.t ||
!packet.d ||
!packet.d?.stream_key
) {
return; return;
} }
const { t: event, d: data } = packet; const { t: event, d: data } = packet;
@@ -697,12 +733,17 @@ class VoiceConnection extends EventEmitter {
// Current user stream // Current user stream
switch (event) { switch (event) {
case 'STREAM_CREATE': { case 'STREAM_CREATE': {
this.streamConnection.setSessionId(this.authentication.sessionId); this.streamConnection.setSessionId(
this.authentication.sessionId,
);
this.streamConnection.serverId = data.rtc_server_id; this.streamConnection.serverId = data.rtc_server_id;
break; break;
} }
case 'STREAM_SERVER_UPDATE': { case 'STREAM_SERVER_UPDATE': {
this.streamConnection.setTokenAndEndpoint(data.token, data.endpoint); this.streamConnection.setTokenAndEndpoint(
data.token,
data.endpoint,
);
break; break;
} }
case 'STREAM_DELETE': { case 'STREAM_DELETE': {
@@ -715,8 +756,13 @@ class VoiceConnection extends EventEmitter {
} }
} }
} }
if (this.streamWatchConnection.has(StreamKey.userId) && this.channel.id == StreamKey.channelId) { if (
const streamConnection = this.streamWatchConnection.get(StreamKey.userId); this.streamWatchConnection.has(StreamKey.userId) &&
this.channel.id == StreamKey.channelId
) {
const streamConnection = this.streamWatchConnection.get(
StreamKey.userId,
);
// Watch user stream // Watch user stream
switch (event) { switch (event) {
case 'STREAM_CREATE': { case 'STREAM_CREATE': {
@@ -725,7 +771,10 @@ class VoiceConnection extends EventEmitter {
break; break;
} }
case 'STREAM_SERVER_UPDATE': { case 'STREAM_SERVER_UPDATE': {
streamConnection.setTokenAndEndpoint(data.token, data.endpoint); streamConnection.setTokenAndEndpoint(
data.token,
data.endpoint,
);
break; break;
} }
case 'STREAM_DELETE': { case 'STREAM_DELETE': {
@@ -745,13 +794,13 @@ class VoiceConnection extends EventEmitter {
connection.sendSignalScreenshare(); connection.sendSignalScreenshare();
connection.sendScreenshareState(true); connection.sendScreenshareState(true);
connection.on('debug', msg => connection.on('debug', (msg) =>
this.channel.client.emit( this.channel.client.emit(
'debug', 'debug',
`[VOICE STREAM (${this.channel.guild?.id || this.channel.id}:${connection.status})]: ${msg}`, `[VOICE STREAM (${this.channel.guild?.id || this.channel.id}:${connection.status})]: ${msg}`,
), ),
); );
connection.once('failed', reason => { connection.once('failed', (reason) => {
this.streamConnection = null; this.streamConnection = null;
reject(reason); reject(reason);
}); });
@@ -782,7 +831,9 @@ class VoiceConnection extends EventEmitter {
if (!userId) { if (!userId) {
throw new Error('VOICE_USER_MISSING'); throw new Error('VOICE_USER_MISSING');
} }
const voiceState = this.channel.guild?.voiceStates.cache.get(userId) || this.client.voiceStates.cache.get(userId); const voiceState =
this.channel.guild?.voiceStates.cache.get(userId) ||
this.client.voiceStates.cache.get(userId);
if (!voiceState || !voiceState.streaming) { if (!voiceState || !voiceState.streaming) {
throw new Error('VOICE_USER_NOT_STREAMING'); throw new Error('VOICE_USER_NOT_STREAMING');
} }
@@ -791,14 +842,24 @@ class VoiceConnection extends EventEmitter {
if (this.streamWatchConnection.has(userId)) { if (this.streamWatchConnection.has(userId)) {
return resolve(this.streamWatchConnection.get(userId)); return resolve(this.streamWatchConnection.get(userId));
} else { } else {
const connection = new StreamConnectionReadonly(this.voiceManager, this.channel, this, userId); const connection = new StreamConnectionReadonly(
this.voiceManager,
this.channel,
this,
userId,
);
this.streamWatchConnection.set(userId, connection); this.streamWatchConnection.set(userId, connection);
connection.setVideoCodec(this.videoCodec); connection.setVideoCodec(this.videoCodec);
// Setup event... // Setup event...
if (!this.eventHook) { if (!this.eventHook) {
this.eventHook = true; // Dont listen this event two times :/ this.eventHook = true; // Dont listen this event two times :/
this.channel.client.on('raw', packet => { this.channel.client.on('raw', (packet) => {
if (typeof packet !== 'object' || !packet.t || !packet.d || !packet.d?.stream_key) { if (
typeof packet !== 'object' ||
!packet.t ||
!packet.d ||
!packet.d?.stream_key
) {
return; return;
} }
const { t: event, d: data } = packet; const { t: event, d: data } = packet;
@@ -811,12 +872,17 @@ class VoiceConnection extends EventEmitter {
// Current user stream // Current user stream
switch (event) { switch (event) {
case 'STREAM_CREATE': { case 'STREAM_CREATE': {
this.streamConnection.setSessionId(this.authentication.sessionId); this.streamConnection.setSessionId(
this.authentication.sessionId,
);
this.streamConnection.serverId = data.rtc_server_id; this.streamConnection.serverId = data.rtc_server_id;
break; break;
} }
case 'STREAM_SERVER_UPDATE': { case 'STREAM_SERVER_UPDATE': {
this.streamConnection.setTokenAndEndpoint(data.token, data.endpoint); this.streamConnection.setTokenAndEndpoint(
data.token,
data.endpoint,
);
break; break;
} }
case 'STREAM_DELETE': { case 'STREAM_DELETE': {
@@ -829,8 +895,13 @@ class VoiceConnection extends EventEmitter {
} }
} }
} }
if (this.streamWatchConnection.has(StreamKey.userId) && this.channel.id == StreamKey.channelId) { if (
const streamConnection = this.streamWatchConnection.get(StreamKey.userId); this.streamWatchConnection.has(StreamKey.userId) &&
this.channel.id == StreamKey.channelId
) {
const streamConnection = this.streamWatchConnection.get(
StreamKey.userId,
);
// Watch user stream // Watch user stream
switch (event) { switch (event) {
case 'STREAM_CREATE': { case 'STREAM_CREATE': {
@@ -839,7 +910,10 @@ class VoiceConnection extends EventEmitter {
break; break;
} }
case 'STREAM_SERVER_UPDATE': { case 'STREAM_SERVER_UPDATE': {
streamConnection.setTokenAndEndpoint(data.token, data.endpoint); streamConnection.setTokenAndEndpoint(
data.token,
data.endpoint,
);
break; break;
} }
case 'STREAM_DELETE': { case 'STREAM_DELETE': {
@@ -858,7 +932,7 @@ class VoiceConnection extends EventEmitter {
connection.sendSignalScreenshare(); connection.sendSignalScreenshare();
connection.on('debug', msg => connection.on('debug', (msg) =>
this.channel.client.emit( this.channel.client.emit(
'debug', 'debug',
`[VOICE STREAM WATCH (${userId}>${this.channel.guild?.id || this.channel.id}:${ `[VOICE STREAM WATCH (${userId}>${this.channel.guild?.id || this.channel.id}:${
@@ -866,7 +940,7 @@ class VoiceConnection extends EventEmitter {
})]: ${msg}`, })]: ${msg}`,
), ),
); );
connection.once('failed', reason => { connection.once('failed', (reason) => {
this.streamWatchConnection.delete(userId); this.streamWatchConnection.delete(userId);
reject(reason); reject(reason);
}); });
@@ -988,7 +1062,8 @@ class StreamConnection extends VoiceConnection {
this.emit('closing'); this.emit('closing');
this.emit('debug', 'Stream: disconnect() triggered'); this.emit('debug', 'Stream: disconnect() triggered');
clearTimeout(this.connectTimeout); clearTimeout(this.connectTimeout);
if (this.voiceConnection.streamConnection === this) this.voiceConnection.streamConnection = null; if (this.voiceConnection.streamConnection === this)
this.voiceConnection.streamConnection = null;
this.sendStopScreenshare(); this.sendStopScreenshare();
this._disconnect(); this._disconnect();
} }

View File

@@ -20,10 +20,20 @@ Please use the @dank074/discord-video-stream library for the best support.
const { Buffer } = require('buffer'); const { Buffer } = require('buffer');
const VideoDispatcher = require('./VideoDispatcher'); const VideoDispatcher = require('./VideoDispatcher');
const Util = require('../../../util/Util'); const Util = require('../../../util/Util');
const { H264Helpers, H265Helpers } = require('../player/processing/AnnexBNalSplitter'); const {
H264Helpers,
H265Helpers,
} = require('../player/processing/AnnexBNalSplitter');
class AnnexBDispatcher extends VideoDispatcher { class AnnexBDispatcher extends VideoDispatcher {
constructor(player, highWaterMark = 12, streams, fps, nalFunctions, payloadType) { constructor(
player,
highWaterMark = 12,
streams,
fps,
nalFunctions,
payloadType,
) {
super(player, highWaterMark, streams, fps, payloadType); super(player, highWaterMark, streams, fps, payloadType);
this._nalFunctions = nalFunctions; this._nalFunctions = nalFunctions;
} }
@@ -40,12 +50,19 @@ class AnnexBDispatcher extends VideoDispatcher {
const isLastNal = offset + naluSize >= accessUnit.length; const isLastNal = offset + naluSize >= accessUnit.length;
if (nalu.length <= this.mtu) { if (nalu.length <= this.mtu) {
// Send as Single NAL Unit Packet. // Send as Single NAL Unit Packet.
this._playChunk(Buffer.concat([this.createPayloadExtension(), nalu]), isLastNal); this._playChunk(
Buffer.concat([this.createPayloadExtension(), nalu]),
isLastNal,
);
} else { } else {
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu); const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
const dataFragments = this.partitionMtu(naluData); const dataFragments = this.partitionMtu(naluData);
// Send as Fragmentation Unit A (FU-A): // Send as Fragmentation Unit A (FU-A):
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) { for (
let fragmentIndex = 0;
fragmentIndex < dataFragments.length;
fragmentIndex++
) {
const data = dataFragments[fragmentIndex]; const data = dataFragments[fragmentIndex];
const isFirstPacket = fragmentIndex === 0; const isFirstPacket = fragmentIndex === 0;
const isFinalPacket = fragmentIndex === dataFragments.length - 1; const isFinalPacket = fragmentIndex === dataFragments.length - 1;
@@ -53,7 +70,11 @@ class AnnexBDispatcher extends VideoDispatcher {
this._playChunk( this._playChunk(
Buffer.concat([ Buffer.concat([
this.createPayloadExtension(), this.createPayloadExtension(),
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader), this.makeFragmentationUnitHeader(
isFirstPacket,
isFinalPacket,
naluHeader,
),
data, data,
]), ]),
isLastNal && isFinalPacket, isLastNal && isFinalPacket,
@@ -67,7 +88,14 @@ class AnnexBDispatcher extends VideoDispatcher {
class H264Dispatcher extends AnnexBDispatcher { class H264Dispatcher extends AnnexBDispatcher {
constructor(player, highWaterMark = 12, streams, fps) { constructor(player, highWaterMark = 12, streams, fps) {
super(player, highWaterMark, streams, fps, H264Helpers, Util.getPayloadType('H264')); super(
player,
highWaterMark,
streams,
fps,
H264Helpers,
Util.getPayloadType('H264'),
);
} }
makeFragmentationUnitHeader(isFirstPacket, isLastPacket, naluHeader) { makeFragmentationUnitHeader(isFirstPacket, isLastPacket, naluHeader) {
@@ -92,7 +120,14 @@ class H264Dispatcher extends AnnexBDispatcher {
class H265Dispatcher extends AnnexBDispatcher { class H265Dispatcher extends AnnexBDispatcher {
constructor(player, highWaterMark = 12, streams, fps) { constructor(player, highWaterMark = 12, streams, fps) {
super(player, highWaterMark, streams, fps, H265Helpers, Util.getPayloadType('H265')); super(
player,
highWaterMark,
streams,
fps,
H265Helpers,
Util.getPayloadType('H265'),
);
} }
makeFragmentationUnitHeader(isFirstPacket, isLastPacket, naluHeader) { makeFragmentationUnitHeader(isFirstPacket, isLastPacket, naluHeader) {

View File

@@ -25,7 +25,11 @@ const CHANNELS = 2;
* @extends {BaseDispatcher} * @extends {BaseDispatcher}
*/ */
class AudioDispatcher extends BaseDispatcher { class AudioDispatcher extends BaseDispatcher {
constructor(player, { seek = 0, volume = 1, fec, plp, bitrate = 96, highWaterMark = 12 } = {}, streams) { constructor(
player,
{ seek = 0, volume = 1, fec, plp, bitrate = 96, highWaterMark = 12 } = {},
streams,
) {
const streamOptions = { seek, volume, fec, plp, bitrate, highWaterMark }; const streamOptions = { seek, volume, fec, plp, bitrate, highWaterMark };
super(player, highWaterMark, Util.getPayloadType('opus'), false, streams); super(player, highWaterMark, Util.getPayloadType('opus'), false, streams);
@@ -63,7 +67,8 @@ class AudioDispatcher extends BaseDispatcher {
*/ */
setBitrate(value) { setBitrate(value) {
if (!value || !this.bitrateEditable) return false; if (!value || !this.bitrateEditable) return false;
const bitrate = value === 'auto' ? this.player.voiceConnection.channel.bitrate : value; const bitrate =
value === 'auto' ? this.player.voiceConnection.channel.bitrate : value;
this.streams.opus.setBitrate(bitrate * 1000); this.streams.opus.setBitrate(bitrate * 1000);
return true; return true;
} }

View File

@@ -20,7 +20,13 @@ const extensions = [{ id: 5, length: 2, value: 0 }];
* @extends {Writable} * @extends {Writable}
*/ */
class BaseDispatcher extends Writable { class BaseDispatcher extends Writable {
constructor(player, highWaterMark = 12, payloadType, extensionEnabled, streams = {}) { constructor(
player,
highWaterMark = 12,
payloadType,
extensionEnabled,
streams = {},
) {
super({ super({
highWaterMark, highWaterMark,
}); });
@@ -63,10 +69,14 @@ class BaseDispatcher extends Writable {
}; };
this.on('error', () => streamError()); this.on('error', () => streamError());
if (this.streams.input) this.streams.input.on('error', err => streamError('input', err)); if (this.streams.input)
if (this.streams.ffmpeg) this.streams.ffmpeg.on('error', err => streamError('ffmpeg', err)); this.streams.input.on('error', (err) => streamError('input', err));
if (this.streams.opus) this.streams.opus.on('error', err => streamError('opus', err)); if (this.streams.ffmpeg)
if (this.streams.volume) this.streams.volume.on('error', err => streamError('volume', err)); this.streams.ffmpeg.on('error', (err) => streamError('ffmpeg', err));
if (this.streams.opus)
this.streams.opus.on('error', (err) => streamError('opus', err));
if (this.streams.volume)
this.streams.volume.on('error', (err) => streamError('volume', err));
this.on('finish', () => { this.on('finish', () => {
this._cleanup(); this._cleanup();
@@ -82,7 +92,8 @@ class BaseDispatcher extends Writable {
resetNonceBuffer() { resetNonceBuffer() {
this._nonceBuffer = this._nonceBuffer =
this.player.voiceConnection.authentication.mode === 'aead_aes256_gcm_rtpsize' this.player.voiceConnection.authentication.mode ===
'aead_aes256_gcm_rtpsize'
? Buffer.alloc(12) ? Buffer.alloc(12)
: Buffer.alloc(24); : Buffer.alloc(24);
} }
@@ -179,7 +190,11 @@ class BaseDispatcher extends Writable {
* @readonly * @readonly
*/ */
get pausedTime() { get pausedTime() {
return this._silentPausedTime + this._pausedTime + (this.paused ? performance.now() - this.pausedSince : 0); return (
this._silentPausedTime +
this._pausedTime +
(this.paused ? performance.now() - this.pausedSince : 0)
);
} }
/** /**
@@ -217,9 +232,12 @@ class BaseDispatcher extends Writable {
this._writeCallback = null; this._writeCallback = null;
done(); done();
}; };
const next = (this.count + 1) * this.FRAME_LENGTH - (performance.now() - this.startTime - this._pausedTime); const next =
(this.count + 1) * this.FRAME_LENGTH -
(performance.now() - this.startTime - this._pausedTime);
setTimeout(() => { setTimeout(() => {
if ((!this.pausedSince || this._silence) && this._writeCallback) this._writeCallback(); if ((!this.pausedSince || this._silence) && this._writeCallback)
this._writeCallback();
}, next).unref(); }, next).unref();
this.timestamp += this.TIMESTAMP_INC; this.timestamp += this.TIMESTAMP_INC;
if (this.timestamp > MAX_UINT_32) this.timestamp = 0; if (this.timestamp > MAX_UINT_32) this.timestamp = 0;
@@ -234,7 +252,8 @@ class BaseDispatcher extends Writable {
_playChunk(chunk, isLastPacket = false) { _playChunk(chunk, isLastPacket = false) {
if ( if (
(this.player.dispatcher !== this && this.player.videoDispatcher !== this) || (this.player.dispatcher !== this &&
this.player.videoDispatcher !== this) ||
!this.player.voiceConnection.authentication.secret_key !this.player.voiceConnection.authentication.secret_key
) { ) {
return; return;
@@ -321,15 +340,24 @@ class BaseDispatcher extends Writable {
switch (mode) { switch (mode) {
case 'aead_aes256_gcm_rtpsize': { case 'aead_aes256_gcm_rtpsize': {
const cipher = crypto.createCipheriv('aes-256-gcm', secret_key, this._nonceBuffer); const cipher = crypto.createCipheriv(
'aes-256-gcm',
secret_key,
this._nonceBuffer,
);
cipher.setAAD(additionalData); cipher.setAAD(additionalData);
encrypted = Buffer.concat([cipher.update(buffer), cipher.final(), cipher.getAuthTag()]); encrypted = Buffer.concat([
cipher.update(buffer),
cipher.final(),
cipher.getAuthTag(),
]);
return [encrypted, noncePadding]; return [encrypted, noncePadding];
} }
case 'aead_xchacha20_poly1305_rtpsize': { case 'aead_xchacha20_poly1305_rtpsize': {
encrypted = secretbox.methods.crypto_aead_xchacha20poly1305_ietf_encrypt( encrypted =
secretbox.methods.crypto_aead_xchacha20poly1305_ietf_encrypt(
buffer, buffer,
additionalData, additionalData,
this._nonceBuffer, this._nonceBuffer,
@@ -385,7 +413,8 @@ class BaseDispatcher extends Writable {
rtpHeader.writeUIntBE(this.getNewSequence(), 2, 2); rtpHeader.writeUIntBE(this.getNewSequence(), 2, 2);
rtpHeader.writeUIntBE(this.timestamp, 4, 4); rtpHeader.writeUIntBE(this.timestamp, 4, 4);
rtpHeader.writeUIntBE( rtpHeader.writeUIntBE(
this.player.voiceConnection.authentication.ssrc + Number(this.getTypeDispatcher() === 'video'), this.player.voiceConnection.authentication.ssrc +
Number(this.getTypeDispatcher() === 'video'),
8, 8,
4, 4,
); );
@@ -408,7 +437,7 @@ class BaseDispatcher extends Writable {
this.emit('debug', 'Failed to send a packet - no UDP socket'); this.emit('debug', 'Failed to send a packet - no UDP socket');
return; return;
} }
this.player.voiceConnection.sockets.udp.send(packet).catch(e => { this.player.voiceConnection.sockets.udp.send(packet).catch((e) => {
if (this.getTypeDispatcher() === 'audio') { if (this.getTypeDispatcher() === 'audio') {
this._setSpeaking(this._setSpeaking(0)); this._setSpeaking(this._setSpeaking(0));
} else if (this.getTypeDispatcher() === 'video') { } else if (this.getTypeDispatcher() === 'video') {
@@ -444,7 +473,9 @@ class BaseDispatcher extends Writable {
} }
_setStreamStatus(value) { _setStreamStatus(value) {
if (typeof this.player.voiceConnection?.sendScreenshareState !== 'undefined') { if (
typeof this.player.voiceConnection?.sendScreenshareState !== 'undefined'
) {
this.player.voiceConnection.sendScreenshareState(value); this.player.voiceConnection.sendScreenshareState(value);
} }
/** /**

View File

@@ -38,11 +38,18 @@ class VP8Dispatcher extends VideoDispatcher {
const pictureIdBuf = Buffer.alloc(2); const pictureIdBuf = Buffer.alloc(2);
pictureIdBuf.writeUintBE(this.count, 0, 2); pictureIdBuf.writeUintBE(this.count, 0, 2);
pictureIdBuf[0] |= 0x80; pictureIdBuf[0] |= 0x80;
return Buffer.concat([this.createPayloadExtension(), payloadDescriptorBuf, pictureIdBuf, buffer]); return Buffer.concat([
this.createPayloadExtension(),
payloadDescriptorBuf,
pictureIdBuf,
buffer,
]);
} }
_codecCallback(chunk) { _codecCallback(chunk) {
const chunkSplit = this.partitionMtu(chunk).map((c, i) => this.makeChunk(c, i === 0)); const chunkSplit = this.partitionMtu(chunk).map((c, i) =>
this.makeChunk(c, i === 0),
);
for (let i = 0; i < chunkSplit.length; i++) { for (let i = 0; i < chunkSplit.length; i++) {
this._playChunk(chunkSplit[i], i + 1 === chunkSplit.length); this._playChunk(chunkSplit[i], i + 1 === chunkSplit.length);
} }

View File

@@ -79,22 +79,30 @@ class VoiceConnectionUDPClient extends EventEmitter {
send(packet) { send(packet) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.socket) throw new Error('UDP_SEND_FAIL'); if (!this.socket) throw new Error('UDP_SEND_FAIL');
if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED'); if (!this.discordAddress || !this.discordPort)
this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => { throw new Error('UDP_ADDRESS_MALFORMED');
this.socket.send(
packet,
0,
packet.length,
this.discordPort,
this.discordAddress,
(error) => {
if (error) { if (error) {
this.emit('debug', `[UDP] >> ERROR: ${error}`); this.emit('debug', `[UDP] >> ERROR: ${error}`);
reject(error); reject(error);
} else { } else {
resolve(packet); resolve(packet);
} }
}); },
);
}); });
} }
async createUDPSocket(address) { async createUDPSocket(address) {
this.discordAddress = address; this.discordAddress = address;
const socket = (this.socket = udp.createSocket('udp4')); const socket = (this.socket = udp.createSocket('udp4'));
socket.on('error', e => { socket.on('error', (e) => {
this.emit('debug', `[UDP] Error: ${e}`); this.emit('debug', `[UDP] Error: ${e}`);
this.emit('error', e); this.emit('error', e);
}); });
@@ -102,7 +110,7 @@ class VoiceConnectionUDPClient extends EventEmitter {
this.emit('debug', '[UDP] socket closed'); this.emit('debug', '[UDP] socket closed');
}); });
this.emit('debug', `[UDP] created socket`); this.emit('debug', `[UDP] created socket`);
socket.once('message', message => { socket.once('message', (message) => {
this.emit('debug', `[UDP] message: [${[...message]}] (${message})`); this.emit('debug', `[UDP] message: [${[...message]}] (${message})`);
if (message.readUInt16BE(0) !== 2) { if (message.readUInt16BE(0) !== 2) {
throw new Error('UDP_WRONG_HANDSHAKE'); throw new Error('UDP_WRONG_HANDSHAKE');
@@ -117,7 +125,10 @@ class VoiceConnectionUDPClient extends EventEmitter {
return; return;
} }
this.emit('debug', `[UDP] Parse local packet: ${packet.address}:${packet.port}`); this.emit(
'debug',
`[UDP] Parse local packet: ${packet.address}:${packet.port}`,
);
this.localAddress = packet.address; this.localAddress = packet.address;
this.localPort = packet.port; this.localPort = packet.port;
@@ -143,7 +154,9 @@ class VoiceConnectionUDPClient extends EventEmitter {
this.emit('debug', `[UDP] << ${JSON.stringify(packet)}`); this.emit('debug', `[UDP] << ${JSON.stringify(packet)}`);
socket.on('message', buffer => this.voiceConnection.receiver.packets.push(buffer)); socket.on('message', (buffer) =>
this.voiceConnection.receiver.packets.push(buffer),
);
}); });
const blankMessage = Buffer.alloc(74); const blankMessage = Buffer.alloc(74);

View File

@@ -67,7 +67,10 @@ class VoiceWebSocket extends EventEmitter {
if (this.dead) return; if (this.dead) return;
if (this.ws) this.reset(); if (this.ws) this.reset();
if (this.attempts >= 5) { if (this.attempts >= 5) {
this.emit('debug', new Error('VOICE_CONNECTION_ATTEMPTS_EXCEEDED', this.attempts)); this.emit(
'debug',
new Error('VOICE_CONNECTION_ATTEMPTS_EXCEEDED', this.attempts),
);
return; return;
} }
@@ -77,8 +80,14 @@ class VoiceWebSocket extends EventEmitter {
* The actual WebSocket used to connect to the Voice WebSocket Server. * The actual WebSocket used to connect to the Voice WebSocket Server.
* @type {WebSocket} * @type {WebSocket}
*/ */
this.ws = WebSocket.create(`wss://${this.connection.authentication.endpoint}/`, { v: 8 }); this.ws = WebSocket.create(
this.emit('debug', `[WS] connecting, ${this.attempts} attempts, ${this.ws.url}`); `wss://${this.connection.authentication.endpoint}/`,
{ v: 8 },
);
this.emit(
'debug',
`[WS] connecting, ${this.attempts} attempts, ${this.ws.url}`,
);
this.ws.onopen = this.onOpen.bind(this); this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this); this.ws.onmessage = this.onMessage.bind(this);
this.ws.onclose = this.onClose.bind(this); this.ws.onclose = this.onClose.bind(this);
@@ -93,8 +102,9 @@ class VoiceWebSocket extends EventEmitter {
send(data) { send(data) {
this.emit('debug', `[WS] >> ${data}`); this.emit('debug', `[WS] >> ${data}`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WS_NOT_OPEN', data); if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
this.ws.send(data, null, error => { throw new Error('WS_NOT_OPEN', data);
this.ws.send(data, null, (error) => {
if (error) reject(error); if (error) reject(error);
else resolve(data); else resolve(data);
}); });
@@ -115,11 +125,17 @@ class VoiceWebSocket extends EventEmitter {
* Called whenever the WebSocket opens. * Called whenever the WebSocket opens.
*/ */
onOpen() { onOpen() {
this.emit('debug', `[WS] opened at gateway ${this.connection.authentication.endpoint}`); this.emit(
'debug',
`[WS] opened at gateway ${this.connection.authentication.endpoint}`,
);
this.sendPacket({ this.sendPacket({
op: Opcodes.DISPATCH, op: Opcodes.DISPATCH,
d: { d: {
server_id: this.connection.serverId || this.connection.channel.guild?.id || this.connection.channel.id, server_id:
this.connection.serverId ||
this.connection.channel.guild?.id ||
this.connection.channel.id,
user_id: this.client.user.id, user_id: this.client.user.id,
token: this.connection.authentication.token, token: this.connection.authentication.token,
session_id: this.connection.authentication.sessionId, session_id: this.connection.authentication.sessionId,
@@ -149,8 +165,12 @@ class VoiceWebSocket extends EventEmitter {
* @param {CloseEvent} event The WebSocket close event * @param {CloseEvent} event The WebSocket close event
*/ */
onClose(event) { onClose(event) {
this.emit('debug', `[WS] closed with code ${event.code} and reason: ${event.reason}`); this.emit(
if (!this.dead) setTimeout(this.connect.bind(this), this.attempts * 1000).unref(); 'debug',
`[WS] closed with code ${event.code} and reason: ${event.reason}`,
);
if (!this.dead)
setTimeout(this.connect.bind(this), this.attempts * 1000).unref();
} }
/** /**
@@ -200,7 +220,9 @@ class VoiceWebSocket extends EventEmitter {
}); });
break; break;
case VoiceOpcodes.CLIENT_DISCONNECT: case VoiceOpcodes.CLIENT_DISCONNECT:
const streamInfo = this.connection.receiver && this.connection.receiver.packets.streams.get(packet.d.user_id); const streamInfo =
this.connection.receiver &&
this.connection.receiver.packets.streams.get(packet.d.user_id);
if (streamInfo) { if (streamInfo) {
this.connection.receiver.packets.streams.delete(packet.d.user_id); this.connection.receiver.packets.streams.delete(packet.d.user_id);
streamInfo.stream.push(null); streamInfo.stream.push(null);
@@ -251,7 +273,10 @@ class VoiceWebSocket extends EventEmitter {
this.emit('warn', 'A voice heartbeat interval is being overwritten'); this.emit('warn', 'A voice heartbeat interval is being overwritten');
clearInterval(this.heartbeatInterval); clearInterval(this.heartbeatInterval);
} }
this.heartbeatInterval = setInterval(this.sendHeartbeat.bind(this), interval).unref(); this.heartbeatInterval = setInterval(
this.sendHeartbeat.bind(this),
interval,
).unref();
} }
/** /**
@@ -259,7 +284,10 @@ class VoiceWebSocket extends EventEmitter {
*/ */
clearHeartbeat() { clearHeartbeat() {
if (!this.heartbeatInterval) { if (!this.heartbeatInterval) {
this.emit('warn', 'Tried to clear a heartbeat interval that does not exist'); this.emit(
'warn',
'Tried to clear a heartbeat interval that does not exist',
);
return; return;
} }
clearInterval(this.heartbeatInterval); clearInterval(this.heartbeatInterval);

View File

@@ -20,13 +20,22 @@ Please use the @dank074/discord-video-stream library for the best support.
const EventEmitter = require('events'); const EventEmitter = require('events');
const { Readable: ReadableStream } = require('stream'); const { Readable: ReadableStream } = require('stream');
const prism = require('prism-media'); const prism = require('prism-media');
const { H264NalSplitter, H265NalSplitter } = require('./processing/AnnexBNalSplitter'); const {
H264NalSplitter,
H265NalSplitter,
} = require('./processing/AnnexBNalSplitter');
const { IvfTransformer } = require('./processing/IvfSplitter'); const { IvfTransformer } = require('./processing/IvfSplitter');
const { H264Dispatcher } = require('../dispatcher/AnnexBDispatcher'); const { H264Dispatcher } = require('../dispatcher/AnnexBDispatcher');
const AudioDispatcher = require('../dispatcher/AudioDispatcher'); const AudioDispatcher = require('../dispatcher/AudioDispatcher');
const { VP8Dispatcher } = require('../dispatcher/VPxDispatcher'); const { VP8Dispatcher } = require('../dispatcher/VPxDispatcher');
const FFMPEG_OUTPUT_PREFIX = ['-use_wallclock_as_timestamps', '1', '-copyts', '-analyzeduration', '0']; const FFMPEG_OUTPUT_PREFIX = [
'-use_wallclock_as_timestamps',
'1',
'-copyts',
'-analyzeduration',
'0',
];
const FFMPEG_INPUT_PREFIX = [ const FFMPEG_INPUT_PREFIX = [
'-reconnect', '-reconnect',
'1', '1',
@@ -38,8 +47,15 @@ const FFMPEG_INPUT_PREFIX = [
'4294', '4294',
]; ];
const FFMPEG_PCM_ARGUMENTS = ['-f', 's16le', '-ar', '48000', '-ac', '2']; const FFMPEG_PCM_ARGUMENTS = ['-f', 's16le', '-ar', '48000', '-ac', '2'];
const FFMPEG_VP8_ARGUMENTS = ['-f', 'ivf', '-deadline', 'realtime', '-c:v', 'libvpx']; const FFMPEG_VP8_ARGUMENTS = [
const FFMPEG_H264_ARGUMENTS = options => [ '-f',
'ivf',
'-deadline',
'realtime',
'-c:v',
'libvpx',
];
const FFMPEG_H264_ARGUMENTS = (options) => [
'-c:v', '-c:v',
'libx264', 'libx264',
'-f', '-f',
@@ -62,7 +78,7 @@ const FFMPEG_H264_ARGUMENTS = options => [
'h264_metadata=aud=insert', 'h264_metadata=aud=insert',
]; ];
const FFMPEG_H265_ARGUMENTS = options => [ const FFMPEG_H265_ARGUMENTS = (options) => [
'-c:v', '-c:v',
'libx265', 'libx265',
'-f', '-f',
@@ -127,9 +143,12 @@ class MediaPlayer extends EventEmitter {
} }
const ffmpeg = new prism.FFmpeg({ args }); const ffmpeg = new prism.FFmpeg({ args });
this.emit('debug', `[ffmpeg-audio_process] Spawn process with args:\n${args.join(' ')}`); this.emit(
'debug',
`[ffmpeg-audio_process] Spawn process with args:\n${args.join(' ')}`,
);
ffmpeg.process.stderr.on('data', data => { ffmpeg.process.stderr.on('data', (data) => {
this.emit('debug', `[ffmpeg-audio_process]: ${data.toString()}`); this.emit('debug', `[ffmpeg-audio_process]: ${data.toString()}`);
}); });
@@ -143,12 +162,19 @@ class MediaPlayer extends EventEmitter {
playPCMStream(stream, options, streams = {}) { playPCMStream(stream, options, streams = {}) {
this.destroyDispatcher(); this.destroyDispatcher();
const opus = (streams.opus = new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 })); const opus = (streams.opus = new prism.opus.Encoder({
channels: 2,
rate: 48000,
frameSize: 960,
}));
if (options && options.volume === false) { if (options && options.volume === false) {
stream.pipe(opus); stream.pipe(opus);
return this.playOpusStream(opus, options, streams); return this.playOpusStream(opus, options, streams);
} }
streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 }); streams.volume = new prism.VolumeTransformer({
type: 's16le',
volume: options ? options.volume : 1,
});
stream.pipe(streams.volume).pipe(opus); stream.pipe(streams.volume).pipe(opus);
return this.playOpusStream(opus, options, streams); return this.playOpusStream(opus, options, streams);
} }
@@ -158,12 +184,21 @@ class MediaPlayer extends EventEmitter {
streams.opus = stream; streams.opus = stream;
if (options.volume !== false && !streams.input) { if (options.volume !== false && !streams.input) {
streams.input = stream; streams.input = stream;
const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 }); const decoder = new prism.opus.Decoder({
streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 }); channels: 2,
rate: 48000,
frameSize: 960,
});
streams.volume = new prism.VolumeTransformer({
type: 's16le',
volume: options ? options.volume : 1,
});
streams.opus = stream streams.opus = stream
.pipe(decoder) .pipe(decoder)
.pipe(streams.volume) .pipe(streams.volume)
.pipe(new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 })); .pipe(
new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 }),
);
} }
const dispatcher = this.createDispatcher(options, streams); const dispatcher = this.createDispatcher(options, streams);
streams.opus.pipe(dispatcher); streams.opus.pipe(dispatcher);
@@ -232,9 +267,12 @@ class MediaPlayer extends EventEmitter {
input.pipe(ffmpeg); input.pipe(ffmpeg);
} }
this.emit('debug', `[ffmpeg-video_process] Spawn process with args:\n${args.join(' ')}`); this.emit(
'debug',
`[ffmpeg-video_process] Spawn process with args:\n${args.join(' ')}`,
);
ffmpeg.process.stderr.on('data', data => { ffmpeg.process.stderr.on('data', (data) => {
this.emit('debug', `[ffmpeg-video_process]: ${data.toString()}`); this.emit('debug', `[ffmpeg-video_process]: ${data.toString()}`);
}); });
@@ -279,7 +317,11 @@ class MediaPlayer extends EventEmitter {
createDispatcher(options, streams) { createDispatcher(options, streams) {
this.destroyDispatcher(); this.destroyDispatcher();
const dispatcher = (this.dispatcher = new AudioDispatcher(this, options, streams)); const dispatcher = (this.dispatcher = new AudioDispatcher(
this,
options,
streams,
));
return dispatcher; return dispatcher;
} }

View File

@@ -166,7 +166,9 @@ class AnnexBNalSplitter extends Transform {
findNalStart(buf) { findNalStart(buf) {
const pos = buf.indexOf(nalSuffix); const pos = buf.indexOf(nalSuffix);
if (pos === -1) return null; if (pos === -1) return null;
return pos > 0 && buf[pos - 1] === 0 ? { index: pos - 1, length: 4 } : { index: pos, length: 3 }; return pos > 0 && buf[pos - 1] === 0
? { index: pos - 1, length: 4 }
: { index: pos, length: 3 };
} }
processFrame(frame) { processFrame(frame) {
@@ -174,11 +176,14 @@ class AnnexBNalSplitter extends Transform {
const unitType = this._nalFunctions.getUnitType(frame); const unitType = this._nalFunctions.getUnitType(frame);
if (this._nalFunctions.isAUD(unitType) && this._accessUnit.length > 0) { if (this._nalFunctions.isAUD(unitType) && this._accessUnit.length > 0) {
const sizeOfAccessUnit = this._accessUnit.reduce((acc, nalu) => acc + nalu.length + 4, 0); const sizeOfAccessUnit = this._accessUnit.reduce(
(acc, nalu) => acc + nalu.length + 4,
0,
);
const accessUnitBuf = Buffer.allocUnsafe(sizeOfAccessUnit); const accessUnitBuf = Buffer.allocUnsafe(sizeOfAccessUnit);
let offset = 0; let offset = 0;
this._accessUnit.forEach(nalu => { this._accessUnit.forEach((nalu) => {
accessUnitBuf.writeUint32BE(nalu.length, offset); accessUnitBuf.writeUint32BE(nalu.length, offset);
offset += 4; offset += 4;
nalu.copy(accessUnitBuf, offset); nalu.copy(accessUnitBuf, offset);
@@ -220,7 +225,10 @@ class H264NalSplitter extends AnnexBNalSplitter {
} }
removeEpbs(frame, unitType) { removeEpbs(frame, unitType) {
return unitType === H264NalUnitTypes.SPS || unitType === H264NalUnitTypes.SEI ? this.rbsp(frame) : frame; return unitType === H264NalUnitTypes.SPS ||
unitType === H264NalUnitTypes.SEI
? this.rbsp(frame)
: frame;
} }
} }

View File

@@ -28,7 +28,8 @@ class IvfTransformer extends Transform {
this.header = null; this.header = null;
this.buf = null; this.buf = null;
this.retFullFrame = options && options.fullframe ? options.fullframe : false; this.retFullFrame =
options && options.fullframe ? options.fullframe : false;
} }
_parseHeader(header) { _parseHeader(header) {
@@ -69,7 +70,8 @@ class IvfTransformer extends Transform {
} }
_updateBufLen(size) { _updateBufLen(size) {
if (this.buf.length > size) this.buf = this.buf.subarray(size, this.buf.length); if (this.buf.length > size)
this.buf = this.buf.subarray(size, this.buf.length);
else this.buf = null; else this.buf = null;
} }

View File

@@ -33,7 +33,8 @@ class PacketHandler extends EventEmitter {
} }
getNonceBuffer() { getNonceBuffer() {
return this.receiver.connection.authentication.mode === 'aead_aes256_gcm_rtpsize' return this.receiver.connection.authentication.mode ===
'aead_aes256_gcm_rtpsize'
? Buffer.alloc(12) ? Buffer.alloc(12)
: Buffer.alloc(24); : Buffer.alloc(24);
} }
@@ -88,7 +89,10 @@ class PacketHandler extends EventEmitter {
const header = buffer.slice(0, headerSize); const header = buffer.slice(0, headerSize);
// Encrypted contains the extension, if any, the opus packet, and the auth tag // Encrypted contains the extension, if any, the opus packet, and the auth tag
const encrypted = buffer.slice(headerSize, buffer.length - AUTH_TAG_LENGTH - UNPADDED_NONCE_LENGTH); const encrypted = buffer.slice(
headerSize,
buffer.length - AUTH_TAG_LENGTH - UNPADDED_NONCE_LENGTH,
);
const authTag = buffer.slice( const authTag = buffer.slice(
buffer.length - AUTH_TAG_LENGTH - UNPADDED_NONCE_LENGTH, buffer.length - AUTH_TAG_LENGTH - UNPADDED_NONCE_LENGTH,
buffer.length - UNPADDED_NONCE_LENGTH, buffer.length - UNPADDED_NONCE_LENGTH,
@@ -97,11 +101,18 @@ class PacketHandler extends EventEmitter {
let packet; let packet;
switch (mode) { switch (mode) {
case 'aead_aes256_gcm_rtpsize': { case 'aead_aes256_gcm_rtpsize': {
const decipheriv = crypto.createDecipheriv('aes-256-gcm', secret_key, nonce); const decipheriv = crypto.createDecipheriv(
'aes-256-gcm',
secret_key,
nonce,
);
decipheriv.setAAD(header); decipheriv.setAAD(header);
decipheriv.setAuthTag(authTag); decipheriv.setAuthTag(authTag);
packet = Buffer.concat([decipheriv.update(encrypted), decipheriv.final()]); packet = Buffer.concat([
decipheriv.update(encrypted),
decipheriv.final(),
]);
break; break;
} }
case 'aead_xchacha20_poly1305_rtpsize': { case 'aead_xchacha20_poly1305_rtpsize': {
@@ -163,10 +174,18 @@ class PacketHandler extends EventEmitter {
if (userStat.speaking === 0) { if (userStat.speaking === 0) {
userStat.speaking = Speaking.FLAGS.SPEAKING; userStat.speaking = Speaking.FLAGS.SPEAKING;
} }
this.connection.onSpeaking({ user_id: userStat.userId, ssrc: ssrc, speaking: userStat.speaking }); this.connection.onSpeaking({
user_id: userStat.userId,
ssrc: ssrc,
speaking: userStat.speaking,
});
speakingTimeout = setTimeout(() => { speakingTimeout = setTimeout(() => {
try { try {
this.connection.onSpeaking({ user_id: userStat.userId, ssrc: ssrc, speaking: 0 }); this.connection.onSpeaking({
user_id: userStat.userId,
ssrc: ssrc,
speaking: 0,
});
clearTimeout(speakingTimeout); clearTimeout(speakingTimeout);
this.speakingTimeouts.delete(ssrc); this.speakingTimeouts.delete(ssrc);
} catch { } catch {
@@ -241,7 +260,8 @@ class PacketHandler extends EventEmitter {
packet = this.parseBuffer(buffer); packet = this.parseBuffer(buffer);
this.videoReceiver(ssrc, userStat, packet); this.videoReceiver(ssrc, userStat, packet);
} }
if (userStat && !(packet instanceof Error)) this.receiver.emit('receiverData', userStat, packet); if (userStat && !(packet instanceof Error))
this.receiver.emit('receiverData', userStat, packet);
} }
// When udp connection is closed (STREAM_DELETE), destroy all streams (Memory leak) // When udp connection is closed (STREAM_DELETE), destroy all streams (Memory leak)

View File

@@ -23,7 +23,7 @@ class VoiceReceiver extends EventEmitter {
* @event VoiceReceiver#debug * @event VoiceReceiver#debug
* @param {Error|string} error The error or message to debug * @param {Error|string} error The error or message to debug
*/ */
this.packets.on('error', err => this.emit('debug', err)); this.packets.on('error', (err) => this.emit('debug', err));
} }
/** /**
@@ -45,24 +45,39 @@ class VoiceReceiver extends EventEmitter {
* @param {ReceiveStreamOptions} options Options. * @param {ReceiveStreamOptions} options Options.
* @returns {ReadableStream} * @returns {ReadableStream}
*/ */
createStream(user, { mode = 'opus', end = 'silence', paddingSilence = false } = {}) { createStream(
user,
{ mode = 'opus', end = 'silence', paddingSilence = false } = {},
) {
user = this.connection.client.users.resolve(user); user = this.connection.client.users.resolve(user);
if (end === 'silence') paddingSilence = false; if (end === 'silence') paddingSilence = false;
if (!user) throw new Error('VOICE_USER_MISSING'); if (!user) throw new Error('VOICE_USER_MISSING');
const stream = this.packets.makeStream(user.id, end); // Opus stream const stream = this.packets.makeStream(user.id, end); // Opus stream
if (paddingSilence) { if (paddingSilence) {
const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 }); const decoder = new prism.opus.Decoder({
channels: 2,
rate: 48000,
frameSize: 960,
});
const pcmTransformer = new PCMInsertSilence(); const pcmTransformer = new PCMInsertSilence();
stream.pipe(decoder).pipe(pcmTransformer); stream.pipe(decoder).pipe(pcmTransformer);
if (mode === 'opus') { if (mode === 'opus') {
const encoder = new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 }); const encoder = new prism.opus.Encoder({
channels: 2,
rate: 48000,
frameSize: 960,
});
pcmTransformer.pipe(encoder); pcmTransformer.pipe(encoder);
return encoder; return encoder;
} }
return pcmTransformer; return pcmTransformer;
} else { } else {
if (mode === 'pcm') { if (mode === 'pcm') {
const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 }); const decoder = new prism.opus.Decoder({
channels: 2,
rate: 48000,
frameSize: 960,
});
stream.pipe(decoder); stream.pipe(decoder);
return decoder; return decoder;
} }

View File

@@ -35,8 +35,8 @@ class Recorder extends EventEmitter {
this.promise = null; this.promise = null;
if (!portUdpH264 || !portUdpOpus) { if (!portUdpH264 || !portUdpOpus) {
this.promise = randomPorts(6, 'udp4').then(ports => { this.promise = randomPorts(6, 'udp4').then((ports) => {
ports = ports.filter(port => port % 2 === 0); ports = ports.filter((port) => port % 2 === 0);
this.portUdpH264 ??= ports[0]; this.portUdpH264 ??= ports[0];
this.portUdpOpus ??= ports[1]; this.portUdpOpus ??= ports[1];
}); });
@@ -60,7 +60,11 @@ class Recorder extends EventEmitter {
} }
async init(output) { async init(output) {
await this.promise; await this.promise;
const sdpData = Util.getSDPCodecName(this.portUdpH264, this.portUdpH265, this.portUdpOpus); const sdpData = Util.getSDPCodecName(
this.portUdpH264,
this.portUdpH265,
this.portUdpOpus,
);
const isStream = output instanceof Writable; const isStream = output instanceof Writable;
if (isStream) { if (isStream) {
this.outputStream = StreamOutput(output); this.outputStream = StreamOutput(output);
@@ -109,7 +113,7 @@ class Recorder extends EventEmitter {
this.stream = stream; this.stream = stream;
this.stream.stdin.write(sdpData); this.stream.stdin.write(sdpData);
this.stream.stdin.end(); this.stream.stdin.end();
this.stream.stderr.once('data', data => { this.stream.stderr.once('data', (data) => {
this.emit('debug', `stderr: ${data}`); this.emit('debug', `stderr: ${data}`);
this.ready = true; this.ready = true;
this.emit('ready'); this.emit('ready');
@@ -122,14 +126,16 @@ class Recorder extends EventEmitter {
*/ */
feed( feed(
payload, payload,
callback = e => { callback = (e) => {
if (e) { if (e) {
console.error('Error sending packet:', e); console.error('Error sending packet:', e);
} }
}, },
) { ) {
if (!(payload instanceof RtpPacket)) { if (!(payload instanceof RtpPacket)) {
payload = RtpPacket.deSerialize(Buffer.isBuffer(payload) ? payload : Buffer.from(payload)); payload = RtpPacket.deSerialize(
Buffer.isBuffer(payload) ? payload : Buffer.from(payload),
);
} }
const message = payload.serialize(); const message = payload.serialize();
// Get port from payloadType // Get port from payloadType
@@ -149,8 +155,11 @@ class Recorder extends EventEmitter {
destroy() { destroy() {
const ffmpegPid = this.stream.pid; // But it is ppid ;-; const ffmpegPid = this.stream.pid; // But it is ppid ;-;
const args = this.stream.spawnargs.slice(1).join(' '); // Skip ffmpeg const args = this.stream.spawnargs.slice(1).join(' '); // Skip ffmpeg
find('name', 'ffmpeg', true).then(list => { find('name', 'ffmpeg', true).then((list) => {
let process = list.find(o => o.pid === ffmpegPid || o.ppid === ffmpegPid || o.cmd.includes(args)); let process = list.find(
(o) =>
o.pid === ffmpegPid || o.ppid === ffmpegPid || o.cmd.includes(args),
);
if (process) { if (process) {
kill(process.pid); kill(process.pid);
this.receiver?.videoStreams?.delete(this.userId); this.receiver?.videoStreams?.delete(this.userId);

View File

@@ -41,7 +41,7 @@ async function randomPort(protocol = 'udp4', interfaceAddresses) {
}); });
const port = socket.address()?.port; const port = socket.address()?.port;
await new Promise(resolve => socket.close(resolve)); await new Promise((resolve) => socket.close(resolve));
return port; return port;
} }
@@ -53,7 +53,11 @@ async function randomPort(protocol = 'udp4', interfaceAddresses) {
* @returns {Promise<number[]>} An array of assigned random ports. * @returns {Promise<number[]>} An array of assigned random ports.
*/ */
async function randomPorts(num, protocol = 'udp4', interfaceAddresses) { async function randomPorts(num, protocol = 'udp4', interfaceAddresses) {
return Promise.all(Array.from({ length: num }).map(() => randomPort(protocol, interfaceAddresses))); return Promise.all(
Array.from({ length: num }).map(() =>
randomPort(protocol, interfaceAddresses),
),
);
} }
/** /**
@@ -78,12 +82,12 @@ async function findPort(min, max, protocol = 'udp4', interfaceAddresses) {
}), }),
); );
const error = await new Promise(resolve => { const error = await new Promise((resolve) => {
socket.once('error', resolve); socket.once('error', resolve);
socket.once('listening', () => resolve(null)); socket.once('listening', () => resolve(null));
}); });
await new Promise(resolve => socket.close(resolve)); await new Promise((resolve) => socket.close(resolve));
if (error) continue; if (error) continue;

View File

@@ -64,11 +64,19 @@ class PlayInterface {
} else if (type === 'opus') { } else if (type === 'opus') {
return this.player.playOpusStream(resource, options); return this.player.playOpusStream(resource, options);
} else if (type === 'ogg/opus') { } else if (type === 'ogg/opus') {
if (!(resource instanceof Readable)) throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM'); if (!(resource instanceof Readable))
return this.player.playOpusStream(resource.pipe(new prism.opus.OggDemuxer()), options); throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM');
return this.player.playOpusStream(
resource.pipe(new prism.opus.OggDemuxer()),
options,
);
} else if (type === 'webm/opus') { } else if (type === 'webm/opus') {
if (!(resource instanceof Readable)) throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM'); if (!(resource instanceof Readable))
return this.player.playOpusStream(resource.pipe(new prism.opus.WebmDemuxer()), options); throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM');
return this.player.playOpusStream(
resource.pipe(new prism.opus.WebmDemuxer()),
options,
);
} }
} }
throw new Error('VOICE_PLAY_INTERFACE_BAD_TYPE'); throw new Error('VOICE_PLAY_INTERFACE_BAD_TYPE');
@@ -114,7 +122,11 @@ class PlayInterface {
static applyToClass(structure) { static applyToClass(structure) {
for (const prop of ['playAudio', 'playVideo']) { for (const prop of ['playAudio', 'playVideo']) {
Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(PlayInterface.prototype, prop)); Object.defineProperty(
structure.prototype,
prop,
Object.getOwnPropertyDescriptor(PlayInterface.prototype, prop),
);
} }
} }
} }

View File

@@ -1,24 +1,78 @@
'use strict'; 'use strict';
const libs = { const libs = {
sodium: sodium => ({ sodium: (sodium) => ({
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) => crypto_aead_xchacha20poly1305_ietf_encrypt: (
sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key), plaintext,
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) => additionalData,
sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key), nonce,
key,
) =>
sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(
plaintext,
additionalData,
null,
nonce,
key,
),
crypto_aead_xchacha20poly1305_ietf_decrypt: (
plaintext,
additionalData,
nonce,
key,
) =>
sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(
plaintext,
additionalData,
null,
nonce,
key,
),
}), }),
'libsodium-wrappers': sodium => ({ 'libsodium-wrappers': (sodium) => ({
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) => crypto_aead_xchacha20poly1305_ietf_encrypt: (
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key), plaintext,
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) => additionalData,
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, plaintext, additionalData, nonce, key), nonce,
key,
) =>
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
plaintext,
additionalData,
null,
nonce,
key,
),
crypto_aead_xchacha20poly1305_ietf_decrypt: (
plaintext,
additionalData,
nonce,
key,
) =>
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null,
plaintext,
additionalData,
nonce,
key,
),
}), }),
'@stablelib/xchacha20poly1305': stablelib => ({ '@stablelib/xchacha20poly1305': (stablelib) => ({
crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, additionalData, nonce, key) { crypto_aead_xchacha20poly1305_ietf_encrypt(
cipherText,
additionalData,
nonce,
key,
) {
const crypto = new stablelib.XChaCha20Poly1305(key); const crypto = new stablelib.XChaCha20Poly1305(key);
return crypto.seal(nonce, cipherText, additionalData); return crypto.seal(nonce, cipherText, additionalData);
}, },
crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, nonce, key) { crypto_aead_xchacha20poly1305_ietf_decrypt(
plaintext,
additionalData,
nonce,
key,
) {
const crypto = new stablelib.XChaCha20Poly1305(key); const crypto = new stablelib.XChaCha20Poly1305(key);
return crypto.open(nonce, plaintext, additionalData); return crypto.open(nonce, plaintext, additionalData);
}, },

View File

@@ -52,11 +52,11 @@ class UnixStream {
} }
function StreamInput(stream) { function StreamInput(stream) {
return new UnixStream(stream, socket => stream.pipe(socket)); return new UnixStream(stream, (socket) => stream.pipe(socket));
} }
function StreamOutput(stream) { function StreamOutput(stream) {
return new UnixStream(stream, socket => socket.pipe(stream)); return new UnixStream(stream, (socket) => socket.pipe(stream));
} }
module.exports = { StreamOutput, StreamInput, UnixStream }; module.exports = { StreamOutput, StreamInput, UnixStream };

View File

@@ -56,7 +56,10 @@ class VolumeInterface extends EventEmitter {
const out = Buffer.alloc(buffer.length); const out = Buffer.alloc(buffer.length);
for (let i = 0; i < buffer.length; i += 2) { for (let i = 0; i < buffer.length; i += 2) {
if (i >= buffer.length - 1) break; if (i >= buffer.length - 1) break;
const uint = Math.min(32767, Math.max(-32767, Math.floor(volume * buffer.readInt16LE(i)))); const uint = Math.min(
32767,
Math.max(-32767, Math.floor(volume * buffer.readInt16LE(i))),
);
out.writeInt16LE(uint, i); out.writeInt16LE(uint, i);
} }
@@ -95,10 +98,19 @@ class VolumeInterface extends EventEmitter {
} }
} }
const props = ['volumeDecibels', 'volumeLogarithmic', 'setVolumeDecibels', 'setVolumeLogarithmic']; const props = [
'volumeDecibels',
'volumeLogarithmic',
'setVolumeDecibels',
'setVolumeLogarithmic',
];
exports.applyToClass = function applyToClass(structure) { exports.applyToClass = function applyToClass(structure) {
for (const prop of props) { for (const prop of props) {
Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(VolumeInterface.prototype, prop)); Object.defineProperty(
structure.prototype,
prop,
Object.getOwnPropertyDescriptor(VolumeInterface.prototype, prop),
);
} }
}; };

View File

@@ -8,7 +8,13 @@ const { RPCErrorCodes } = require('discord-api-types/v10');
const WebSocketShard = require('./WebSocketShard'); const WebSocketShard = require('./WebSocketShard');
const PacketHandlers = require('./handlers'); const PacketHandlers = require('./handlers');
const { Error } = require('../../errors'); const { Error } = require('../../errors');
const { Events, ShardEvents, Status, WSCodes, WSEvents } = require('../../util/Constants'); const {
Events,
ShardEvents,
Status,
WSCodes,
WSEvents,
} = require('../../util/Constants');
const BeforeReadyWhitelist = [ const BeforeReadyWhitelist = [
WSEvents.READY, WSEvents.READY,
@@ -70,7 +76,10 @@ class WebSocketManager extends EventEmitter {
* @private * @private
* @name WebSocketManager#shardQueue * @name WebSocketManager#shardQueue
*/ */
Object.defineProperty(this, 'shardQueue', { value: new Set(), writable: true }); Object.defineProperty(this, 'shardQueue', {
value: new Set(),
writable: true,
});
/** /**
* An array of queued events before this WebSocketManager became ready * An array of queued events before this WebSocketManager became ready
@@ -118,7 +127,10 @@ class WebSocketManager extends EventEmitter {
* @private * @private
*/ */
debug(message, shard) { 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}`,
);
} }
/** /**
@@ -129,7 +141,7 @@ class WebSocketManager extends EventEmitter {
let gatewayURL = 'wss://gateway.discord.gg'; let gatewayURL = 'wss://gateway.discord.gg';
await this.client.api.gateway await this.client.api.gateway
.get({ auth: false }) .get({ auth: false })
.then(r => (gatewayURL = r.url)) .then((r) => (gatewayURL = r.url))
.catch(() => {}); .catch(() => {});
const total = Infinity; const total = Infinity;
@@ -149,14 +161,19 @@ class WebSocketManager extends EventEmitter {
let { shards } = this.client.options; let { shards } = this.client.options;
if (shards === 'auto') { if (shards === 'auto') {
this.debug(`Using the recommended shard count provided by Discord: ${recommendedShards}`); this.debug(
`Using the recommended shard count provided by Discord: ${recommendedShards}`,
);
this.totalShards = this.client.options.shardCount = recommendedShards; this.totalShards = this.client.options.shardCount = recommendedShards;
shards = this.client.options.shards = Array.from({ length: recommendedShards }, (_, i) => i); shards = this.client.options.shards = Array.from(
{ length: recommendedShards },
(_, i) => i,
);
} }
this.totalShards = shards.length; this.totalShards = shards.length;
this.debug(`Spawning shards: ${shards.join(', ')}`); this.debug(`Spawning shards: ${shards.join(', ')}`);
this.shardQueue = new Set(shards.map(id => new WebSocketShard(this, id))); this.shardQueue = new Set(shards.map((id) => new WebSocketShard(this, id)));
return this.createShards(); return this.createShards();
} }
@@ -175,7 +192,7 @@ class WebSocketManager extends EventEmitter {
this.shardQueue.delete(shard); this.shardQueue.delete(shard);
if (!shard.eventsAttached) { if (!shard.eventsAttached) {
shard.on(ShardEvents.ALL_READY, unavailableGuilds => { shard.on(ShardEvents.ALL_READY, (unavailableGuilds) => {
/** /**
* Emitted when a shard turns ready. * Emitted when a shard turns ready.
* @event Client#shardReady * @event Client#shardReady
@@ -188,8 +205,12 @@ class WebSocketManager extends EventEmitter {
this.checkShardsReady(); 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)) { 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. * Emitted when a shard's WebSocket disconnects and will no longer reconnect.
* @event Client#shardDisconnect * @event Client#shardDisconnect
@@ -215,7 +236,11 @@ class WebSocketManager extends EventEmitter {
this.shardQueue.add(shard); this.shardQueue.add(shard);
if (shard.sessionId) this.debug(`Session id is present, attempting an immediate reconnect...`, shard); if (shard.sessionId)
this.debug(
`Session id is present, attempting an immediate reconnect...`,
shard,
);
this.reconnect(); this.reconnect();
}); });
@@ -224,7 +249,10 @@ class WebSocketManager extends EventEmitter {
}); });
shard.on(ShardEvents.DESTROYED, () => { shard.on(ShardEvents.DESTROYED, () => {
this.debug('Shard was destroyed but no WebSocket connection was present! Reconnecting...', shard); this.debug(
'Shard was destroyed but no WebSocket connection was present! Reconnecting...',
shard,
);
this.client.emit(Events.SHARD_RECONNECTING, shard.id); this.client.emit(Events.SHARD_RECONNECTING, shard.id);
@@ -252,7 +280,9 @@ class WebSocketManager extends EventEmitter {
} }
// If we have more shards, add a 5s delay // If we have more shards, add a 5s delay
if (this.shardQueue.size) { if (this.shardQueue.size) {
this.debug(`Shard Queue Size: ${this.shardQueue.size}; continuing in 5 seconds...`); this.debug(
`Shard Queue Size: ${this.shardQueue.size}; continuing in 5 seconds...`,
);
await sleep(5_000); await sleep(5_000);
return this.createShards(); return this.createShards();
} }
@@ -271,7 +301,9 @@ class WebSocketManager extends EventEmitter {
try { try {
await this.createShards(); await this.createShards();
} catch (error) { } catch (error) {
this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`); this.debug(
`Couldn't reconnect or fetch information about the gateway. ${error}`,
);
if (error.httpStatus !== 401) { if (error.httpStatus !== 401) {
this.debug(`Possible network error occurred. Retrying in 5s...`); this.debug(`Possible network error occurred. Retrying in 5s...`);
await sleep(5_000); await sleep(5_000);
@@ -313,10 +345,13 @@ class WebSocketManager extends EventEmitter {
*/ */
destroy() { destroy() {
if (this.destroyed) return; if (this.destroyed) return;
this.debug(`Manager was destroyed. Called by:\n${new Error('MANAGER_DESTROYED').stack}`); this.debug(
`Manager was destroyed. Called by:\n${new Error('MANAGER_DESTROYED').stack}`,
);
this.destroyed = true; this.destroyed = true;
this.shardQueue.clear(); this.shardQueue.clear();
for (const shard of this.shards.values()) shard.destroy({ closeCode: 1_000, reset: true, emit: false, log: false }); for (const shard of this.shards.values())
shard.destroy({ closeCode: 1_000, reset: true, emit: false, log: false });
} }
/** /**
@@ -362,7 +397,10 @@ class WebSocketManager extends EventEmitter {
*/ */
checkShardsReady() { checkShardsReady() {
if (this.status === Status.READY) return; if (this.status === Status.READY) return;
if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.READY)) { if (
this.shards.size !== this.totalShards ||
this.shards.some((s) => s.status !== Status.READY)
) {
return; return;
} }

View File

@@ -3,7 +3,14 @@
const EventEmitter = require('node:events'); const EventEmitter = require('node:events');
const { setTimeout, setInterval, clearTimeout } = require('node:timers'); const { setTimeout, setInterval, clearTimeout } = require('node:timers');
const WebSocket = require('../../WebSocket'); const WebSocket = require('../../WebSocket');
const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants'); const {
Status,
Events,
ShardEvents,
Opcodes,
WSEvents,
WSCodes,
} = require('../../util/Constants');
const Intents = require('../../util/Intents'); const Intents = require('../../util/Intents');
const Util = require('../../util/Util'); const Util = require('../../util/Util');
@@ -140,7 +147,10 @@ class WebSocketShard extends EventEmitter {
* @type {?NodeJS.Timeout} * @type {?NodeJS.Timeout}
* @private * @private
*/ */
Object.defineProperty(this, 'helloTimeout', { value: null, writable: true }); Object.defineProperty(this, 'helloTimeout', {
value: null,
writable: true,
});
/** /**
* The WebSocket timeout. * The WebSocket timeout.
@@ -148,7 +158,10 @@ class WebSocketShard extends EventEmitter {
* @type {?NodeJS.Timeout} * @type {?NodeJS.Timeout}
* @private * @private
*/ */
Object.defineProperty(this, 'wsCloseTimeout', { value: null, writable: true }); Object.defineProperty(this, 'wsCloseTimeout', {
value: null,
writable: true,
});
/** /**
* If the manager attached its event handlers on the shard * If the manager attached its event handlers on the shard
@@ -156,7 +169,10 @@ class WebSocketShard extends EventEmitter {
* @type {boolean} * @type {boolean}
* @private * @private
*/ */
Object.defineProperty(this, 'eventsAttached', { value: false, writable: true }); Object.defineProperty(this, 'eventsAttached', {
value: false,
writable: true,
});
/** /**
* A set of guild ids this shard expects to receive * A set of guild ids this shard expects to receive
@@ -164,7 +180,10 @@ class WebSocketShard extends EventEmitter {
* @type {?Set<string>} * @type {?Set<string>}
* @private * @private
*/ */
Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true }); Object.defineProperty(this, 'expectedGuilds', {
value: null,
writable: true,
});
/** /**
* The ready timeout * The ready timeout
@@ -172,7 +191,10 @@ class WebSocketShard extends EventEmitter {
* @type {?NodeJS.Timeout} * @type {?NodeJS.Timeout}
* @private * @private
*/ */
Object.defineProperty(this, 'readyTimeout', { value: null, writable: true }); Object.defineProperty(this, 'readyTimeout', {
value: null,
writable: true,
});
/** /**
* Time when the WebSocket connection was opened * Time when the WebSocket connection was opened
@@ -201,7 +223,10 @@ class WebSocketShard extends EventEmitter {
connect() { connect() {
const { client } = this.manager; const { 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 Promise.resolve();
} }
@@ -226,7 +251,7 @@ class WebSocketShard extends EventEmitter {
resolve(); resolve();
}; };
const onClose = event => { const onClose = (event) => {
cleanup(); cleanup();
reject(event); reject(event);
}; };
@@ -244,7 +269,9 @@ class WebSocketShard extends EventEmitter {
this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed); this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed);
if (this.connection?.readyState === WebSocket.OPEN) { if (this.connection?.readyState === WebSocket.OPEN) {
this.debug('An open connection was found, attempting an immediate identify.'); this.debug(
'An open connection was found, attempting an immediate identify.',
);
this.identify(); this.identify();
return; return;
} }
@@ -275,7 +302,10 @@ class WebSocketShard extends EventEmitter {
Agent : ${Util.verifyProxyAgent(client.options.ws.agent)}`, Agent : ${Util.verifyProxyAgent(client.options.ws.agent)}`,
); );
this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING; this.status =
this.status === Status.DISCONNECTED
? Status.RECONNECTING
: Status.CONNECTING;
this.setHelloTimeout(); this.setHelloTimeout();
this.setWsCloseTimeout(-1); this.setWsCloseTimeout(-1);
this.connectedAt = Date.now(); this.connectedAt = Date.now();
@@ -283,7 +313,9 @@ class WebSocketShard extends EventEmitter {
// Adding a handshake timeout to just make sure no zombie connection appears. // Adding a handshake timeout to just make sure no zombie connection appears.
const ws = (this.connection = WebSocket.create(gateway, wsQuery, { const ws = (this.connection = WebSocket.create(gateway, wsQuery, {
handshakeTimeout: 30_000, handshakeTimeout: 30_000,
agent: Util.verifyProxyAgent(client.options.ws.agent) ? client.options.ws.agent : undefined, agent: Util.verifyProxyAgent(client.options.ws.agent)
? client.options.ws.agent
: undefined,
})); }));
ws.onopen = this.onOpen.bind(this); ws.onopen = this.onOpen.bind(this);
ws.onmessage = this.onMessage.bind(this); ws.onmessage = this.onMessage.bind(this);
@@ -312,7 +344,11 @@ class WebSocketShard extends EventEmitter {
if (zlib) { if (zlib) {
const l = data.length; const l = data.length;
const flush = const flush =
l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff; l >= 4 &&
data[l - 4] === 0x00 &&
data[l - 3] === 0x00 &&
data[l - 2] === 0xff &&
data[l - 1] === 0xff;
this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH); this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH);
if (!flush) return; if (!flush) return;
@@ -328,7 +364,8 @@ class WebSocketShard extends EventEmitter {
return; return;
} }
this.manager.client.emit(Events.RAW, packet, 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); if (packet.op === Opcodes.DISPATCH)
this.manager.emit(packet.t, packet.d, this.id);
this.onPacket(packet); this.onPacket(packet);
} }
@@ -434,9 +471,15 @@ class WebSocketShard extends EventEmitter {
this.resumeURL = packet.d.resume_gateway_url; this.resumeURL = packet.d.resume_gateway_url;
this.sessionId = packet.d.session_id; this.sessionId = packet.d.session_id;
this.expectedGuilds = new Set(packet.d.guilds.filter(d => d?.unavailable == true).map(d => d.id)); this.expectedGuilds = new Set(
packet.d.guilds
.filter((d) => d?.unavailable == true)
.map((d) => d.id),
);
this.status = Status.WAITING_FOR_GUILDS; this.status = Status.WAITING_FOR_GUILDS;
this.debug(`[READY] Session ${this.sessionId} | Resume url ${this.resumeURL}.`); this.debug(
`[READY] Session ${this.sessionId} | Resume url ${this.resumeURL}.`,
);
this.lastHeartbeatAcked = true; this.lastHeartbeatAcked = true;
this.sendHeartbeat('ReadyHeartbeat'); this.sendHeartbeat('ReadyHeartbeat');
break; break;
@@ -449,7 +492,9 @@ class WebSocketShard extends EventEmitter {
this.status = Status.READY; this.status = Status.READY;
const replayed = packet.s - this.closeSequence; const replayed = packet.s - this.closeSequence;
this.debug(`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`); this.debug(
`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`,
);
this.lastHeartbeatAcked = true; this.lastHeartbeatAcked = true;
this.sendHeartbeat('ResumeHeartbeat'); this.sendHeartbeat('ResumeHeartbeat');
break; break;
@@ -496,7 +541,10 @@ class WebSocketShard extends EventEmitter {
break; break;
default: default:
this.manager.handlePacket(packet, this); this.manager.handlePacket(packet, this);
if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) { if (
this.status === Status.WAITING_FOR_GUILDS &&
packet.t === WSEvents.GUILD_CREATE
) {
this.expectedGuilds.delete(packet.d.id); this.expectedGuilds.delete(packet.d.id);
this.checkReady(); this.checkReady();
} }
@@ -529,7 +577,9 @@ class WebSocketShard extends EventEmitter {
this.emit(ShardEvents.ALL_READY); this.emit(ShardEvents.ALL_READY);
return; return;
} }
const hasGuildsIntent = new Intents(this.manager.client.options.intents).has(Intents.FLAGS.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 // 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 // * The timeout is 15 seconds by default
// * This can be optionally changed in the client options via the `waitGuildTimeout` option // * This can be optionally changed in the client options via the `waitGuildTimeout` option
@@ -572,7 +622,9 @@ class WebSocketShard extends EventEmitter {
} }
this.debug('Setting a HELLO timeout for 20s.'); this.debug('Setting a HELLO timeout for 20s.');
this.helloTimeout = setTimeout(() => { this.helloTimeout = setTimeout(() => {
this.debug('Did not receive HELLO in time. Destroying and connecting again.'); this.debug(
'Did not receive HELLO in time. Destroying and connecting again.',
);
this.destroy({ reset: true, closeCode: 4009 }); this.destroy({ reset: true, closeCode: 4009 });
}, 20_000).unref(); }, 20_000).unref();
} }
@@ -597,7 +649,9 @@ class WebSocketShard extends EventEmitter {
// Check if close event was emitted. // Check if close event was emitted.
if (this.closeEmitted) { if (this.closeEmitted) {
this.debug(`[WebSocket] close was already emitted, assuming the connection was closed properly.`); this.debug(
`[WebSocket] close was already emitted, assuming the connection was closed properly.`,
);
// Setting the variable false to check for zombie connections. // Setting the variable false to check for zombie connections.
this.closeEmitted = false; this.closeEmitted = false;
return; return;
@@ -635,7 +689,10 @@ class WebSocketShard extends EventEmitter {
this.debug(`Setting a heartbeat interval for ${time}ms.`); this.debug(`Setting a heartbeat interval for ${time}ms.`);
// Sanity checks // Sanity checks
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval); if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), time).unref(); this.heartbeatInterval = setInterval(
() => this.sendHeartbeat(),
time,
).unref();
} }
/** /**
@@ -647,10 +704,16 @@ class WebSocketShard extends EventEmitter {
*/ */
sendHeartbeat( sendHeartbeat(
tag = 'HeartbeatTimer', tag = 'HeartbeatTimer',
ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status), ignoreHeartbeatAck = [
Status.WAITING_FOR_GUILDS,
Status.IDENTIFYING,
Status.RESUMING,
].includes(this.status),
) { ) {
if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) { if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`); this.debug(
`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`,
);
} else if (!this.lastHeartbeatAcked) { } else if (!this.lastHeartbeatAcked) {
this.debug( this.debug(
`[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting. `[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.
@@ -703,9 +766,10 @@ class WebSocketShard extends EventEmitter {
// Patch something // Patch something
Object.keys(client.options.ws.properties) Object.keys(client.options.ws.properties)
.filter(k => k.startsWith('$')) .filter((k) => k.startsWith('$'))
.forEach(k => { .forEach((k) => {
client.options.ws.properties[k.slice(1)] = client.options.ws.properties[k]; client.options.ws.properties[k.slice(1)] =
client.options.ws.properties[k];
delete client.options.ws.properties[k]; delete client.options.ws.properties[k];
}); });
@@ -728,14 +792,18 @@ class WebSocketShard extends EventEmitter {
*/ */
identifyResume() { identifyResume() {
if (!this.sessionId) { if (!this.sessionId) {
this.debug('[RESUME] No session id was present; identifying as a new session.'); this.debug(
'[RESUME] No session id was present; identifying as a new session.',
);
this.identifyNew(); this.identifyNew();
return; return;
} }
this.status = Status.RESUMING; this.status = Status.RESUMING;
this.debug(`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`); this.debug(
`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`,
);
const d = { const d = {
token: this.manager.client.token, token: this.manager.client.token,
@@ -767,13 +835,15 @@ class WebSocketShard extends EventEmitter {
*/ */
_send(data) { _send(data) {
if (this.connection?.readyState !== WebSocket.OPEN) { if (this.connection?.readyState !== WebSocket.OPEN) {
this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`); this.debug(
`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`,
);
this.destroy({ closeCode: 4_000 }); this.destroy({ closeCode: 4_000 });
return; return;
} }
this.debug(`[WebSocketShard] send packet '${JSON.stringify(data)}'`); this.debug(`[WebSocketShard] send packet '${JSON.stringify(data)}'`);
this.connection.send(WebSocket.pack(data), err => { this.connection.send(WebSocket.pack(data), (err) => {
if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id); if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
}); });
} }
@@ -827,7 +897,9 @@ class WebSocketShard extends EventEmitter {
// If the connection is currently opened, we will (hopefully) receive close // If the connection is currently opened, we will (hopefully) receive close
if (this.connection.readyState === WebSocket.OPEN) { if (this.connection.readyState === WebSocket.OPEN) {
this.connection.close(closeCode); this.connection.close(closeCode);
this.debug(`[WebSocket] Close: Tried closing. | WS State: ${CONNECTION_STATE[this.connection.readyState]}`); this.debug(
`[WebSocket] Close: Tried closing. | WS State: ${CONNECTION_STATE[this.connection.readyState]}`,
);
} else { } else {
// Connection is not OPEN // Connection is not OPEN
this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`); this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
@@ -886,7 +958,10 @@ class WebSocketShard extends EventEmitter {
* @private * @private
*/ */
_cleanupConnection() { _cleanupConnection() {
this.connection.onopen = this.connection.onclose = this.connection.onmessage = null; this.connection.onopen =
this.connection.onclose =
this.connection.onmessage =
null;
this.connection.onerror = () => null; this.connection.onerror = () => null;
} }

View File

@@ -3,10 +3,15 @@
const { Events } = require('../../../util/Constants'); const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => { module.exports = (client, { d: data }) => {
const commandManager = data.guild_id ? client.guilds.cache.get(data.guild_id)?.commands : client.application.commands; const commandManager = data.guild_id
? client.guilds.cache.get(data.guild_id)?.commands
: client.application.commands;
if (!commandManager) return; if (!commandManager) return;
const command = commandManager._add(data, data.application_id === client.application.id); const command = commandManager._add(
data,
data.application_id === client.application.id,
);
/** /**
* Emitted when a guild application command is created. * Emitted when a guild application command is created.

View File

@@ -3,7 +3,9 @@
const { Events } = require('../../../util/Constants'); const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => { module.exports = (client, { d: data }) => {
const commandManager = data.guild_id ? client.guilds.cache.get(data.guild_id)?.commands : client.application.commands; const commandManager = data.guild_id
? client.guilds.cache.get(data.guild_id)?.commands
: client.application.commands;
if (!commandManager) return; if (!commandManager) return;
const isOwn = data.application_id === client.application.id; const isOwn = data.application_id === client.application.id;

View File

@@ -3,11 +3,16 @@
const { Events } = require('../../../util/Constants'); const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => { module.exports = (client, { d: data }) => {
const commandManager = data.guild_id ? client.guilds.cache.get(data.guild_id)?.commands : client.application.commands; const commandManager = data.guild_id
? client.guilds.cache.get(data.guild_id)?.commands
: client.application.commands;
if (!commandManager) return; if (!commandManager) return;
const oldCommand = commandManager.cache.get(data.id)?._clone() ?? null; const oldCommand = commandManager.cache.get(data.id)?._clone() ?? null;
const newCommand = commandManager._add(data, data.application_id === client.application.id); const newCommand = commandManager._add(
data,
data.application_id === client.application.id,
);
/** /**
* Emitted when a guild application command is updated. * Emitted when a guild application command is updated.

View File

@@ -4,7 +4,9 @@ const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => { module.exports = (client, { d: data }) => {
const channel = client.channels.cache.get(data.channel_id); const channel = client.channels.cache.get(data.channel_id);
const time = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null; const time = data.last_pin_timestamp
? new Date(data.last_pin_timestamp).getTime()
: null;
if (channel) { if (channel) {
// Discord sends null for last_pin_timestamp if the last pinned message was removed // Discord sends null for last_pin_timestamp if the last pinned message was removed

View File

@@ -4,13 +4,19 @@ module.exports = (client, packet) => {
const channel = client.channels.cache.get(packet.d.channel_id); const channel = client.channels.cache.get(packet.d.channel_id);
if (channel) { if (channel) {
if (!channel._recipients) channel._recipients = []; if (!channel._recipients) channel._recipients = [];
channel._recipients = channel._recipients.filter(u => u.id !== packet.d.user.id); channel._recipients = channel._recipients.filter(
(u) => u.id !== packet.d.user.id,
);
/** /**
* Emitted whenever a recipient is removed from a group DM. * Emitted whenever a recipient is removed from a group DM.
* @event Client#channelRecipientRemove * @event Client#channelRecipientRemove
* @param {GroupDMChannel} channel Group DM channel * @param {GroupDMChannel} channel Group DM channel
* @param {User} user User * @param {User} user User
*/ */
client.emit(Events.CHANNEL_RECIPIENT_REMOVE, channel, client.users._add(packet.d.user)); client.emit(
Events.CHANNEL_RECIPIENT_REMOVE,
channel,
client.users._add(packet.d.user),
);
} }
}; };

View File

@@ -8,9 +8,11 @@ module.exports = (client, { d: data }) => {
if (!guild) return; if (!guild) return;
const members = new Collection(); const members = new Collection();
for (const member of data.members) members.set(member.user.id, guild.members._add(member)); for (const member of data.members)
members.set(member.user.id, guild.members._add(member));
if (data.presences) { if (data.presences) {
for (const presence of data.presences) guild.presences._add(Object.assign(presence, { guild })); for (const presence of data.presences)
guild.presences._add(Object.assign(presence, { guild }));
} }
/** /**

View File

@@ -39,7 +39,9 @@ module.exports = (client, { d: data }, shard) => {
client.settings._patch(data.user_settings); client.settings._patch(data.user_settings);
// GuildSetting // GuildSetting
for (const gSetting of Array.isArray(data.user_guild_settings) ? data.user_guild_settings : []) { for (const gSetting of Array.isArray(data.user_guild_settings)
? data.user_guild_settings
: []) {
const guild = client.guilds.cache.get(gSetting.guild_id); const guild = client.guilds.cache.get(gSetting.guild_id);
if (guild) guild.settings._patch(gSetting); if (guild) guild.settings._patch(gSetting);
} }

View File

@@ -22,8 +22,10 @@ module.exports = (client, { d: data }) => {
const oldNickname = client.relationships.friendNicknames.get(data.id); const oldNickname = client.relationships.friendNicknames.get(data.id);
// Update // Update
if (data.type) client.relationships.cache.set(data.id, data.type); if (data.type) client.relationships.cache.set(data.id, data.type);
if (data.nickname) client.relationships.friendNicknames.set(data.id, data.nickname); if (data.nickname)
if (data.since) client.relationships.sinceCache.set(data.id, new Date(data.since || 0)); client.relationships.friendNicknames.set(data.id, data.nickname);
if (data.since)
client.relationships.sinceCache.set(data.id, new Date(data.since || 0));
client.emit( client.emit(
Events.RELATIONSHIP_UPDATE, Events.RELATIONSHIP_UPDATE,
data.id, data.id,

View File

@@ -25,7 +25,7 @@ module.exports = (client, { d: data }) => {
'[USER_REQUIRED_ACTION] Successfully accepted the new Terms of Service and Privacy Policy.', '[USER_REQUIRED_ACTION] Successfully accepted the new Terms of Service and Privacy Policy.',
); );
}) })
.catch(e => { .catch((e) => {
client.emit( client.emit(
'debug', 'debug',
`[USER_REQUIRED_ACTION] Failed to accept the new Terms of Service and Privacy Policy: ${e}`, `[USER_REQUIRED_ACTION] Failed to accept the new Terms of Service and Privacy Policy: ${e}`,

View File

@@ -12,5 +12,8 @@ module.exports = (client, { d: data }) => {
* @event Client#voiceChannelEffectSend * @event Client#voiceChannelEffectSend
* @param {VoiceChannelEffect} voiceChannelEffect The sent voice channel effect * @param {VoiceChannelEffect} voiceChannelEffect The sent voice channel effect
*/ */
client.emit(Events.VOICE_CHANNEL_EFFECT_SEND, new VoiceChannelEffect(data, guild)); client.emit(
Events.VOICE_CHANNEL_EFFECT_SEND,
new VoiceChannelEffect(data, guild),
);
}; };

View File

@@ -1,6 +1,9 @@
'use strict'; 'use strict';
module.exports = (client, packet) => { module.exports = (client, packet) => {
client.emit('debug', `[VOICE] received voice server: ${JSON.stringify(packet)}`); client.emit(
'debug',
`[VOICE] received voice server: ${JSON.stringify(packet)}`,
);
client.voice.onVoiceServer(packet.d); client.voice.onVoiceServer(packet.d);
}; };

View File

@@ -6,8 +6,14 @@ const handlers = Object.fromEntries([
['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')], ['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')],
['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')], ['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')],
['APPLICATION_COMMAND_UPDATE', require('./APPLICATION_COMMAND_UPDATE')], ['APPLICATION_COMMAND_UPDATE', require('./APPLICATION_COMMAND_UPDATE')],
['APPLICATION_COMMAND_PERMISSIONS_UPDATE', require('./APPLICATION_COMMAND_PERMISSIONS_UPDATE')], [
['AUTO_MODERATION_ACTION_EXECUTION', require('./AUTO_MODERATION_ACTION_EXECUTION')], 'APPLICATION_COMMAND_PERMISSIONS_UPDATE',
require('./APPLICATION_COMMAND_PERMISSIONS_UPDATE'),
],
[
'AUTO_MODERATION_ACTION_EXECUTION',
require('./AUTO_MODERATION_ACTION_EXECUTION'),
],
['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')], ['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')],
['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')], ['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')],
['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')], ['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')],
@@ -59,8 +65,14 @@ const handlers = Object.fromEntries([
['GUILD_SCHEDULED_EVENT_CREATE', require('./GUILD_SCHEDULED_EVENT_CREATE')], ['GUILD_SCHEDULED_EVENT_CREATE', require('./GUILD_SCHEDULED_EVENT_CREATE')],
['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')], ['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')],
['GUILD_SCHEDULED_EVENT_DELETE', require('./GUILD_SCHEDULED_EVENT_DELETE')], ['GUILD_SCHEDULED_EVENT_DELETE', require('./GUILD_SCHEDULED_EVENT_DELETE')],
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')], [
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')], 'GUILD_SCHEDULED_EVENT_USER_ADD',
require('./GUILD_SCHEDULED_EVENT_USER_ADD'),
],
[
'GUILD_SCHEDULED_EVENT_USER_REMOVE',
require('./GUILD_SCHEDULED_EVENT_USER_REMOVE'),
],
['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')], ['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')],
// Selfbot // Selfbot
['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')], ['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')],

View File

@@ -15,7 +15,8 @@ function makeDiscordjsError(Base) {
constructor(key, ...args) { constructor(key, ...args) {
super(message(key, args)); super(message(key, args));
this[kCode] = key; this[kCode] = key;
if (Error.captureStackTrace) Error.captureStackTrace(this, DiscordjsError); if (Error.captureStackTrace)
Error.captureStackTrace(this, DiscordjsError);
} }
get name() { get name() {
@@ -35,7 +36,8 @@ function makeDiscordjsError(Base) {
* @returns {string} Formatted string * @returns {string} Formatted string
*/ */
function message(key, args) { function message(key, args) {
if (typeof key !== 'string') throw new Error('Error message key must be a string'); if (typeof key !== 'string')
throw new Error('Error message key must be a string');
const msg = messages.get(key); const msg = messages.get(key);
if (!msg) throw new Error(`An invalid error message key was used: ${key}.`); if (!msg) throw new Error(`An invalid error message key was used: ${key}.`);
if (typeof msg === 'function') return msg(...args); if (typeof msg === 'function') return msg(...args);

View File

@@ -6,41 +6,51 @@ const Messages = {
CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`, CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`,
CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.', CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.',
CLIENT_MISSING_INTENTS: 'Valid intents must be provided for the Client.', 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_NOT_READY: (action) =>
`The client needs to be logged in to ${action}.`,
TOKEN_INVALID: 'An invalid token was provided.', TOKEN_INVALID: 'An invalid token was provided.',
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.', TOKEN_MISSING:
TOTPKEY_MISSING: 'Request to use mfa, but TOTPKey was not set in client options.', 'Request to use token, but token was unavailable to the client.',
TOTPKEY_MISSING:
'Request to use mfa, but TOTPKey was not set in client options.',
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.', WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.', WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`, WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`,
MANAGER_DESTROYED: 'Manager was destroyed.', 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: '[Bot Token] Invalid shard settings were provided.', SHARDING_INVALID: '[Bot Token] Invalid shard settings were provided.',
SHARDING_REQUIRED: '[Bot Token] This session would have handled too many guilds - Sharding is required.', SHARDING_REQUIRED:
'[Bot Token] This session would have handled too many guilds - Sharding is required.',
INVALID_INTENTS: '[Bot Token] Invalid intent provided for WebSocket intents.', INVALID_INTENTS: '[Bot Token] Invalid intent provided for WebSocket intents.',
DISALLOWED_INTENTS: '[Bot Token] Privileged intent provided is not enabled or whitelisted.', DISALLOWED_INTENTS:
'[Bot Token] Privileged intent provided is not enabled or whitelisted.',
SHARDING_NO_SHARDS: 'No shards have been spawned.', SHARDING_NO_SHARDS: 'No shards have been spawned.',
SHARDING_IN_PROCESS: 'Shards are still being spawned.', SHARDING_IN_PROCESS: 'Shards are still being spawned.',
SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function', SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function',
SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`, SHARDING_SHARD_NOT_FOUND: (id) => `Shard ${id} could not be found.`,
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`, SHARDING_ALREADY_SPAWNED: (count) => `Already spawned ${count} shards.`,
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`, SHARDING_PROCESS_EXISTS: (id) => `Shard ${id} already has an active process.`,
SHARDING_WORKER_EXISTS: id => `Shard ${id} already has an active worker.`, 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_TIMEOUT: (id) =>
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`, `Shard ${id}'s Client took too long to become ready.`,
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`, SHARDING_READY_DISCONNECTED: (id) =>
SHARDING_NO_CHILD_EXISTS: id => `Shard ${id} has no active process or worker.`, `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) => SHARDING_SHARD_MISCALCULATION: (shard, guild, count) =>
`Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`, `Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`,
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
COLOR_CONVERT: 'Unable to convert color to a number.', 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.',
EMBED_TITLE: 'MessageEmbed title 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_NAME: 'MessageEmbed field names must be non-empty strings.',
@@ -67,66 +77,82 @@ const Messages = {
MODAL_CUSTOM_ID: 'Modal customId must be a string', MODAL_CUSTOM_ID: 'Modal customId must be a string',
MODAL_TITLE: 'Modal title must be a string', MODAL_TITLE: 'Modal title must be a string',
INTERACTION_COLLECTOR_ERROR: reason => `Collector received no interactions before ending with reason: ${reason}`, INTERACTION_COLLECTOR_ERROR: (reason) =>
`Collector received no interactions before ending with reason: ${reason}`,
FILE_NOT_FOUND: file => `File could not be found: ${file}`, FILE_NOT_FOUND: (file) => `File could not be found: ${file}`,
USER_BANNER_NOT_FETCHED: "You must fetch this user's banner before trying to generate its URL!", 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!', USER_NO_DM_CHANNEL: 'No DM Channel exists!',
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.', VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',
VOICE_STATE_NOT_OWN: VOICE_STATE_NOT_OWN:
'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.', '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_STATE_INVALID_TYPE: (name) => `${name} must be a boolean.`,
REQ_RESOURCE_TYPE: 'The resource must be a string, Buffer or a valid file stream.', REQ_RESOURCE_TYPE:
'The resource must be a string, Buffer or a valid file stream.',
IMAGE_FORMAT: format => `Invalid image format: ${format}`, IMAGE_FORMAT: (format) => `Invalid image format: ${format}`,
IMAGE_SIZE: size => `Invalid image size: ${size}`, IMAGE_SIZE: (size) => `Invalid image size: ${size}`,
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.', 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_NONCE_TYPE: 'Message nonce must be an integer or a string.',
MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.', MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.',
SPLIT_MAX_LEN: 'Chunk exceeds the max length and contains no split characters.', SPLIT_MAX_LEN:
'Chunk exceeds the max length and contains no split characters.',
BAN_RESOLVE_ID: (ban = false) => `Couldn't resolve the user id to ${ban ? 'ban' : 'unban'}.`, 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.", FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user id to fetch the ban.",
PRUNE_DAYS_TYPE: 'Days must be a number', PRUNE_DAYS_TYPE: 'Days must be a number',
GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.', 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_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_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_CHANNEL_UNOWNED:
"The fetched channel does not belong to this manager's guild.",
GUILD_OWNED: 'Guild is owned by the client.', GUILD_OWNED: 'Guild is owned by the client.',
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.", GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', 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!', 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.', STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.',
GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.', GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.',
INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, INVALID_TYPE: (name, expected, an = false) =>
INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`, `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
INVALID_ELEMENT: (type, name, elem) =>
`Supplied ${type} ${name} includes an invalid element: ${elem}`,
MESSAGE_THREAD_PARENT: 'The message was not sent in a guild text or news channel', MESSAGE_THREAD_PARENT:
'The message was not sent in a guild text or news channel',
MESSAGE_EXISTING_THREAD: 'The message already has a thread', MESSAGE_EXISTING_THREAD: 'The message already has a thread',
THREAD_INVITABLE_TYPE: type => `Invitable cannot be edited on ${type}`, THREAD_INVITABLE_TYPE: (type) => `Invitable cannot be edited on ${type}`,
WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', WEBHOOK_MESSAGE: 'The message was not sent by a webhook.',
WEBHOOK_TOKEN_UNAVAILABLE: 'This action requires a webhook token, but none is available.', WEBHOOK_TOKEN_UNAVAILABLE:
'This action requires a webhook token, but none is available.',
WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.', WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.',
WEBHOOK_APPLICATION: 'This message webhook belongs to an application and cannot be fetched.', WEBHOOK_APPLICATION:
'This message webhook belongs to an application and cannot be fetched.',
MESSAGE_REFERENCE_MISSING: 'The message does not reference another message', MESSAGE_REFERENCE_MISSING: 'The message does not reference another message',
EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji', EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji',
EMOJI_MANAGED: 'Emoji is managed and has no Author.', EMOJI_MANAGED: 'Emoji is managed and has no Author.',
MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: guild => MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: (guild) =>
`Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, `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.', NOT_GUILD_STICKER:
'Sticker is a standard (non-guild) sticker and has no author.',
REACTION_RESOLVE_USER: "Couldn't resolve the user id to remove from the reaction.", REACTION_RESOLVE_USER:
"Couldn't resolve the user id to remove from the reaction.",
VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.', VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.',
@@ -134,55 +160,73 @@ const Messages = {
INVITE_NOT_FOUND: 'Could not find the requested invite.', INVITE_NOT_FOUND: 'Could not find the requested invite.',
DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them", DELETE_GROUP_DM_CHANNEL:
FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them", "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",
MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.', MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.',
GLOBAL_COMMAND_PERMISSIONS: GLOBAL_COMMAND_PERMISSIONS:
'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' + 'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' +
"or from a guild's application command manager.", "or from a guild's application command manager.",
GUILD_UNCACHED_ROLE_RESOLVE: 'Cannot resolve roles from an arbitrary guild, provide an id instead', GUILD_UNCACHED_ROLE_RESOLVE:
'Cannot resolve roles from an arbitrary guild, provide an id instead',
INTERACTION_ALREADY_REPLIED: 'The reply to this interaction has already been sent or deferred.', INTERACTION_ALREADY_REPLIED:
INTERACTION_NOT_REPLIED: 'The reply to this interaction has not been sent or deferred.', '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.',
COMMAND_INTERACTION_OPTION_NOT_FOUND: name => `Required option "${name}" not found.`, COMMAND_INTERACTION_OPTION_NOT_FOUND: (name) =>
`Required option "${name}" not found.`,
COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) => COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) =>
`Option "${name}" is of type: ${type}; expected ${expected}.`, `Option "${name}" is of type: ${type}; expected ${expected}.`,
COMMAND_INTERACTION_OPTION_EMPTY: (name, type) => COMMAND_INTERACTION_OPTION_EMPTY: (name, type) =>
`Required option "${name}" is of type: ${type}; expected a non-empty value.`, `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:
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.', 'No subcommand specified for interaction.',
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete 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.',
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`, MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: (customId) =>
`Required field with custom id "${customId}" not found.`,
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) => MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`, `Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite', INVITE_MISSING_SCOPES:
'At least one valid scope must be provided for the invite',
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`, NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function', SWEEP_FILTER_RETURN:
'The return value of the sweepFilter function was not false or a Function',
GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread', GUILD_FORUM_MESSAGE_REQUIRED:
'You must provide a message to create a guild forum thread',
// Selfbot // Selfbot
INVALID_USER_API: 'User accounts cannot use this endpoint', INVALID_USER_API: 'User accounts cannot use this endpoint',
INVALID_APPLICATION_COMMAND: id => `Could not find a valid command for this bot: ${id}`, INVALID_APPLICATION_COMMAND: (id) =>
INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`, `Could not find a valid command for this bot: ${id}`,
INVALID_COMMAND_NAME: (allCMD) =>
`Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
INVALID_SLASH_COMMAND_CHOICES: (parentOptions, value) => INVALID_SLASH_COMMAND_CHOICES: (parentOptions, value) =>
`${value} is not a valid choice for this option (${parentOptions})`, `${value} is not a valid choice for this option (${parentOptions})`,
SLASH_COMMAND_REQUIRED_OPTIONS_MISSING: (req, opt) => `Value required (${req}) missing (Options: ${opt})`, SLASH_COMMAND_REQUIRED_OPTIONS_MISSING: (req, opt) =>
SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID: n => `${n} is not a valid sub command group`, `Value required (${req}) missing (Options: ${opt})`,
SLASH_COMMAND_SUB_COMMAND_INVALID: n => `${n} is not a valid sub command`, SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID: (n) =>
`${n} is not a valid sub command group`,
SLASH_COMMAND_SUB_COMMAND_INVALID: (n) => `${n} is not a valid sub command`,
INTERACTION_FAILED: 'No responsed from Application', INTERACTION_FAILED: 'No responsed from Application',
USER_NOT_STREAMING: 'User is not streaming', USER_NOT_STREAMING: 'User is not streaming',
BULK_BAN_USERS_OPTION_EMPTY: 'Option "users" array or collection is empty', BULK_BAN_USERS_OPTION_EMPTY: 'Option "users" array or collection is empty',
// Djs v12 // Djs v12
VOICE_INVALID_HEARTBEAT: 'Tried to set voice heartbeat but no valid interval was specified.', VOICE_INVALID_HEARTBEAT:
'Tried to set voice heartbeat but no valid interval was specified.',
VOICE_USER_MISSING: "Couldn't resolve the user to create stream.", VOICE_USER_MISSING: "Couldn't resolve the user to create stream.",
VOICE_JOIN_CHANNEL: (full = false) => VOICE_JOIN_CHANNEL: (full = false) =>
`You do not have permission to join this voice channel${full ? '; it is full.' : '.'}`, `You do not have permission to join this voice channel${full ? '; it is full.' : '.'}`,
@@ -191,20 +235,26 @@ const Messages = {
VOICE_SESSION_ABSENT: 'Session ID not supplied.', VOICE_SESSION_ABSENT: 'Session ID not supplied.',
VOICE_INVALID_ENDPOINT: 'Invalid endpoint received.', VOICE_INVALID_ENDPOINT: 'Invalid endpoint received.',
VOICE_NO_BROWSER: 'Voice connections are not available in browsers.', VOICE_NO_BROWSER: 'Voice connections are not available in browsers.',
VOICE_CONNECTION_ATTEMPTS_EXCEEDED: attempts => `Too many connection attempts (${attempts}).`, VOICE_CONNECTION_ATTEMPTS_EXCEEDED: (attempts) =>
VOICE_JOIN_SOCKET_CLOSED: 'Tried to send join packet, but the WebSocket is not open.', `Too many connection attempts (${attempts}).`,
VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.', VOICE_JOIN_SOCKET_CLOSED:
'Tried to send join packet, but the WebSocket is not open.',
VOICE_PLAY_INTERFACE_NO_BROADCAST:
'A broadcast cannot be played in this context.',
VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type', VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type',
VOICE_PRISM_DEMUXERS_NEED_STREAM: 'To play a webm/ogg stream, you need to pass a ReadableStream.', VOICE_PRISM_DEMUXERS_NEED_STREAM:
'To play a webm/ogg stream, you need to pass a ReadableStream.',
VOICE_STATE_UNCACHED_MEMBER: 'The member of this voice state is uncached.', VOICE_STATE_UNCACHED_MEMBER: 'The member of this voice state is uncached.',
UDP_SEND_FAIL: 'Tried to send a UDP packet, but there is no socket available.', UDP_SEND_FAIL:
'Tried to send a UDP packet, but there is no socket available.',
UDP_ADDRESS_MALFORMED: 'Malformed UDP address or port.', UDP_ADDRESS_MALFORMED: 'Malformed UDP address or port.',
UDP_CONNECTION_EXISTS: 'There is already an existing UDP connection.', UDP_CONNECTION_EXISTS: 'There is already an existing UDP connection.',
UDP_WRONG_HANDSHAKE: 'Wrong handshake packet for UDP', UDP_WRONG_HANDSHAKE: 'Wrong handshake packet for UDP',
INVALID_VIDEO_CODEC: codecs => `Only these codecs are supported: ${codecs.join(', ')}`, INVALID_VIDEO_CODEC: (codecs) =>
`Only these codecs are supported: ${codecs.join(', ')}`,
STREAM_CONNECTION_READONLY: 'Cannot send data to a read-only stream', STREAM_CONNECTION_READONLY: 'Cannot send data to a read-only stream',
STREAM_CANNOT_JOIN: 'Cannot join a stream to itself', STREAM_CANNOT_JOIN: 'Cannot join a stream to itself',

View File

@@ -107,7 +107,8 @@ exports.GuildEmoji = require('./structures/GuildEmoji');
exports.GuildMember = require('./structures/GuildMember').GuildMember; exports.GuildMember = require('./structures/GuildMember').GuildMember;
exports.GuildPreview = require('./structures/GuildPreview'); exports.GuildPreview = require('./structures/GuildPreview');
exports.GuildPreviewEmoji = require('./structures/GuildPreviewEmoji'); exports.GuildPreviewEmoji = require('./structures/GuildPreviewEmoji');
exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildScheduledEvent; exports.GuildScheduledEvent =
require('./structures/GuildScheduledEvent').GuildScheduledEvent;
exports.GuildTemplate = require('./structures/GuildTemplate'); exports.GuildTemplate = require('./structures/GuildTemplate');
exports.Integration = require('./structures/Integration'); exports.Integration = require('./structures/Integration');
exports.IntegrationApplication = require('./structures/IntegrationApplication'); exports.IntegrationApplication = require('./structures/IntegrationApplication');
@@ -132,7 +133,8 @@ exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
exports.Presence = require('./structures/Presence').Presence; exports.Presence = require('./structures/Presence').Presence;
exports.ReactionCollector = require('./structures/ReactionCollector'); exports.ReactionCollector = require('./structures/ReactionCollector');
exports.ReactionEmoji = require('./structures/ReactionEmoji'); exports.ReactionEmoji = require('./structures/ReactionEmoji');
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets; exports.RichPresenceAssets =
require('./structures/Presence').RichPresenceAssets;
exports.Role = require('./structures/Role').Role; exports.Role = require('./structures/Role').Role;
exports.Session = require('./structures/Session'); exports.Session = require('./structures/Session');
exports.StageChannel = require('./structures/StageChannel'); exports.StageChannel = require('./structures/StageChannel');

View File

@@ -93,7 +93,10 @@ class ApplicationCommandManager extends CachedManager {
* .then(commands => console.log(`Fetched ${commands.size} commands`)) * .then(commands => console.log(`Fetched ${commands.size} commands`))
* .catch(console.error); * .catch(console.error);
*/ */
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) { async fetch(
id,
{ guildId, cache = true, force = false, locale, withLocalizations } = {},
) {
if (typeof id === 'object') { if (typeof id === 'object') {
({ guildId, cache = true, locale, withLocalizations } = id); ({ guildId, cache = true, locale, withLocalizations } = id);
} else if (id) { } else if (id) {
@@ -109,9 +112,16 @@ class ApplicationCommandManager extends CachedManager {
headers: { headers: {
'X-Discord-Locale': locale, 'X-Discord-Locale': locale,
}, },
query: typeof withLocalizations === 'boolean' ? { with_localizations: withLocalizations } : undefined, query:
typeof withLocalizations === 'boolean'
? { with_localizations: withLocalizations }
: undefined,
}); });
return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection()); return data.reduce(
(coll, command) =>
coll.set(command.id, this._add(command, cache, guildId)),
new Collection(),
);
} }
/** /**
@@ -160,9 +170,13 @@ class ApplicationCommandManager extends CachedManager {
*/ */
async set(commands, guildId) { async set(commands, guildId) {
const data = await this.commandPath({ guildId }).put({ const data = await this.commandPath({ guildId }).put({
data: commands.map(c => this.constructor.transformCommand(c)), data: commands.map((c) => this.constructor.transformCommand(c)),
}); });
return data.reduce((coll, command) => coll.set(command.id, this._add(command, true, guildId)), new Collection()); return data.reduce(
(coll, command) =>
coll.set(command.id, this._add(command, true, guildId)),
new Collection(),
);
} }
/** /**
@@ -182,7 +196,12 @@ class ApplicationCommandManager extends CachedManager {
*/ */
async edit(command, data, guildId) { async edit(command, data, guildId) {
const id = this.resolveId(command); const id = this.resolveId(command);
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable'); if (!id)
throw new TypeError(
'INVALID_TYPE',
'command',
'ApplicationCommandResolvable',
);
const patched = await this.commandPath({ id, guildId }).patch({ const patched = await this.commandPath({ id, guildId }).patch({
data: this.constructor.transformCommand(data), data: this.constructor.transformCommand(data),
@@ -204,7 +223,12 @@ class ApplicationCommandManager extends CachedManager {
*/ */
async delete(command, guildId) { async delete(command, guildId) {
const id = this.resolveId(command); const id = this.resolveId(command);
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable'); if (!id)
throw new TypeError(
'INVALID_TYPE',
'command',
'ApplicationCommandResolvable',
);
await this.commandPath({ id, guildId }).delete(); await this.commandPath({ id, guildId }).delete();
@@ -226,25 +250,37 @@ class ApplicationCommandManager extends CachedManager {
if ('default_member_permissions' in command) { if ('default_member_permissions' in command) {
default_member_permissions = command.default_member_permissions default_member_permissions = command.default_member_permissions
? new Permissions(BigInt(command.default_member_permissions)).bitfield.toString() ? new Permissions(
BigInt(command.default_member_permissions),
).bitfield.toString()
: command.default_member_permissions; : command.default_member_permissions;
} }
if ('defaultMemberPermissions' in command) { if ('defaultMemberPermissions' in command) {
default_member_permissions = default_member_permissions =
command.defaultMemberPermissions !== null command.defaultMemberPermissions !== null
? new Permissions(command.defaultMemberPermissions).bitfield.toString() ? new Permissions(
command.defaultMemberPermissions,
).bitfield.toString()
: command.defaultMemberPermissions; : command.defaultMemberPermissions;
} }
return { return {
name: command.name, name: command.name,
name_localizations: command.nameLocalizations ?? command.name_localizations, name_localizations:
command.nameLocalizations ?? command.name_localizations,
description: command.description, description: command.description,
description_localizations: command.descriptionLocalizations ?? command.description_localizations, description_localizations:
type: typeof command.type === 'number' ? command.type : ApplicationCommandTypes[command.type], command.descriptionLocalizations ?? command.description_localizations,
options: command.options?.map(o => ApplicationCommand.transformOption(o)), type:
default_permission: command.defaultPermission ?? command.default_permission, typeof command.type === 'number'
? command.type
: ApplicationCommandTypes[command.type],
options: command.options?.map((o) =>
ApplicationCommand.transformOption(o),
),
default_permission:
command.defaultPermission ?? command.default_permission,
default_member_permissions, default_member_permissions,
dm_permission: command.dmPermission ?? command.dm_permission, dm_permission: command.dmPermission ?? command.dm_permission,
}; };

View File

@@ -3,7 +3,10 @@
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const BaseManager = require('./BaseManager'); const BaseManager = require('./BaseManager');
const { Error, TypeError } = require('../errors'); const { Error, TypeError } = require('../errors');
const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Constants'); const {
ApplicationCommandPermissionTypes,
APIErrors,
} = require('../util/Constants');
/** /**
* Manages API methods for permissions of Application Commands. * Manages API methods for permissions of Application Commands.
@@ -47,7 +50,10 @@ class ApplicationCommandPermissionsManager extends BaseManager {
* @private * @private
*/ */
permissionsPath(guildId, commandId) { permissionsPath(guildId, commandId) {
return this.client.api.applications(this.client.application.id).guilds(guildId).commands(commandId).permissions; return this.client.api
.applications(this.client.application.id)
.guilds(guildId)
.commands(commandId).permissions;
} }
/** /**
@@ -96,7 +102,9 @@ class ApplicationCommandPermissionsManager extends BaseManager {
const { guildId, commandId } = this._validateOptions(guild, command); const { guildId, commandId } = this._validateOptions(guild, command);
if (commandId) { if (commandId) {
const data = await this.permissionsPath(guildId, commandId).get(); const data = await this.permissionsPath(guildId, commandId).get();
return data.permissions.map(perm => this.constructor.transformPermissions(perm, true)); return data.permissions.map((perm) =>
this.constructor.transformPermissions(perm, true),
);
} }
const data = await this.permissionsPath(guildId).get(); const data = await this.permissionsPath(guildId).get();
@@ -104,7 +112,9 @@ class ApplicationCommandPermissionsManager extends BaseManager {
(coll, perm) => (coll, perm) =>
coll.set( coll.set(
perm.id, perm.id,
perm.permissions.map(p => this.constructor.transformPermissions(p, true)), perm.permissions.map((p) =>
this.constructor.transformPermissions(p, true),
),
), ),
new Collection(), new Collection(),
); );
@@ -163,24 +173,48 @@ class ApplicationCommandPermissionsManager extends BaseManager {
if (commandId) { if (commandId) {
if (!Array.isArray(permissions)) { if (!Array.isArray(permissions)) {
throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true); throw new TypeError(
'INVALID_TYPE',
'permissions',
'Array of ApplicationCommandPermissionData',
true,
);
} }
const data = await this.permissionsPath(guildId, commandId).put({ const data = await this.permissionsPath(guildId, commandId).put({
data: { permissions: permissions.map(perm => this.constructor.transformPermissions(perm)) }, data: {
permissions: permissions.map((perm) =>
this.constructor.transformPermissions(perm),
),
},
}); });
return data.permissions.map(perm => this.constructor.transformPermissions(perm, true)); return data.permissions.map((perm) =>
this.constructor.transformPermissions(perm, true),
);
} }
if (!Array.isArray(fullPermissions)) { if (!Array.isArray(fullPermissions)) {
throw new TypeError('INVALID_TYPE', 'fullPermissions', 'Array of GuildApplicationCommandPermissionData', true); throw new TypeError(
'INVALID_TYPE',
'fullPermissions',
'Array of GuildApplicationCommandPermissionData',
true,
);
} }
const APIPermissions = []; const APIPermissions = [];
for (const perm of fullPermissions) { for (const perm of fullPermissions) {
if (!Array.isArray(perm.permissions)) throw new TypeError('INVALID_ELEMENT', 'Array', 'fullPermissions', perm); if (!Array.isArray(perm.permissions))
throw new TypeError(
'INVALID_ELEMENT',
'Array',
'fullPermissions',
perm,
);
APIPermissions.push({ APIPermissions.push({
id: perm.id, id: perm.id,
permissions: perm.permissions.map(p => this.constructor.transformPermissions(p)), permissions: perm.permissions.map((p) =>
this.constructor.transformPermissions(p),
),
}); });
} }
const data = await this.permissionsPath(guildId).put({ const data = await this.permissionsPath(guildId).put({
@@ -190,7 +224,9 @@ class ApplicationCommandPermissionsManager extends BaseManager {
(coll, perm) => (coll, perm) =>
coll.set( coll.set(
perm.id, perm.id,
perm.permissions.map(p => this.constructor.transformPermissions(p, true)), perm.permissions.map((p) =>
this.constructor.transformPermissions(p, true),
),
), ),
new Collection(), new Collection(),
); );
@@ -221,26 +257,41 @@ class ApplicationCommandPermissionsManager extends BaseManager {
*/ */
async add({ guild, command, permissions }) { async add({ guild, command, permissions }) {
const { guildId, commandId } = this._validateOptions(guild, command); const { guildId, commandId } = this._validateOptions(guild, command);
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable'); if (!commandId)
throw new TypeError(
'INVALID_TYPE',
'command',
'ApplicationCommandResolvable',
);
if (!Array.isArray(permissions)) { if (!Array.isArray(permissions)) {
throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true); throw new TypeError(
'INVALID_TYPE',
'permissions',
'Array of ApplicationCommandPermissionData',
true,
);
} }
let existing = []; let existing = [];
try { try {
existing = await this.fetch({ guild: guildId, command: commandId }); existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) { } catch (error) {
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error; if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS)
throw error;
} }
const newPermissions = permissions.slice(); const newPermissions = permissions.slice();
for (const perm of existing) { for (const perm of existing) {
if (!newPermissions.some(x => x.id === perm.id)) { if (!newPermissions.some((x) => x.id === perm.id)) {
newPermissions.push(perm); newPermissions.push(perm);
} }
} }
return this.set({ guild: guildId, command: commandId, permissions: newPermissions }); return this.set({
guild: guildId,
command: commandId,
permissions: newPermissions,
});
} }
/** /**
@@ -272,15 +323,27 @@ class ApplicationCommandPermissionsManager extends BaseManager {
*/ */
async remove({ guild, command, users, roles }) { async remove({ guild, command, users, roles }) {
const { guildId, commandId } = this._validateOptions(guild, command); const { guildId, commandId } = this._validateOptions(guild, command);
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable'); if (!commandId)
throw new TypeError(
'INVALID_TYPE',
'command',
'ApplicationCommandResolvable',
);
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true); if (!users && !roles)
throw new TypeError(
'INVALID_TYPE',
'users OR roles',
'Array or Resolvable',
true,
);
let resolvedIds = []; let resolvedIds = [];
if (Array.isArray(users)) { if (Array.isArray(users)) {
users.forEach(user => { users.forEach((user) => {
const userId = this.client.users.resolveId(user); const userId = this.client.users.resolveId(user);
if (!userId) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', user); if (!userId)
throw new TypeError('INVALID_ELEMENT', 'Array', 'users', user);
resolvedIds.push(userId); resolvedIds.push(userId);
}); });
} else if (users) { } else if (users) {
@@ -292,14 +355,15 @@ class ApplicationCommandPermissionsManager extends BaseManager {
} }
if (Array.isArray(roles)) { if (Array.isArray(roles)) {
roles.forEach(role => { roles.forEach((role) => {
if (typeof role === 'string') { if (typeof role === 'string') {
resolvedIds.push(role); resolvedIds.push(role);
return; return;
} }
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE'); if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
const roleId = this.guild.roles.resolveId(role); const roleId = this.guild.roles.resolveId(role);
if (!roleId) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', role); if (!roleId)
throw new TypeError('INVALID_ELEMENT', 'Array', 'users', role);
resolvedIds.push(roleId); resolvedIds.push(roleId);
}); });
} else if (roles) { } else if (roles) {
@@ -309,7 +373,11 @@ class ApplicationCommandPermissionsManager extends BaseManager {
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE'); if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
const roleId = this.guild.roles.resolveId(roles); const roleId = this.guild.roles.resolveId(roles);
if (!roleId) { if (!roleId) {
throw new TypeError('INVALID_TYPE', 'users', 'Array or RoleResolvable'); throw new TypeError(
'INVALID_TYPE',
'users',
'Array or RoleResolvable',
);
} }
resolvedIds.push(roleId); resolvedIds.push(roleId);
} }
@@ -319,10 +387,13 @@ class ApplicationCommandPermissionsManager extends BaseManager {
try { try {
existing = await this.fetch({ guild: guildId, command: commandId }); existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) { } catch (error) {
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error; if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS)
throw error;
} }
const permissions = existing.filter(perm => !resolvedIds.includes(perm.id)); const permissions = existing.filter(
(perm) => !resolvedIds.includes(perm.id),
);
return this.set({ guild: guildId, command: commandId, permissions }); return this.set({ guild: guildId, command: commandId, permissions });
} }
@@ -347,9 +418,19 @@ class ApplicationCommandPermissionsManager extends BaseManager {
*/ */
async has({ guild, command, permissionId }) { async has({ guild, command, permissionId }) {
const { guildId, commandId } = this._validateOptions(guild, command); const { guildId, commandId } = this._validateOptions(guild, command);
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable'); if (!commandId)
throw new TypeError(
'INVALID_TYPE',
'command',
'ApplicationCommandResolvable',
);
if (!permissionId) throw new TypeError('INVALID_TYPE', 'permissionId', 'UserResolvable or RoleResolvable'); if (!permissionId)
throw new TypeError(
'INVALID_TYPE',
'permissionId',
'UserResolvable or RoleResolvable',
);
let resolvedId = permissionId; let resolvedId = permissionId;
if (typeof permissionId !== 'string') { if (typeof permissionId !== 'string') {
resolvedId = this.client.users.resolveId(permissionId); resolvedId = this.client.users.resolveId(permissionId);
@@ -358,7 +439,11 @@ class ApplicationCommandPermissionsManager extends BaseManager {
resolvedId = this.guild.roles.resolveId(permissionId); resolvedId = this.guild.roles.resolveId(permissionId);
} }
if (!resolvedId) { if (!resolvedId) {
throw new TypeError('INVALID_TYPE', 'permissionId', 'UserResolvable or RoleResolvable'); throw new TypeError(
'INVALID_TYPE',
'permissionId',
'UserResolvable or RoleResolvable',
);
} }
} }
@@ -366,10 +451,11 @@ class ApplicationCommandPermissionsManager extends BaseManager {
try { try {
existing = await this.fetch({ guild: guildId, command: commandId }); existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) { } catch (error) {
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error; if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS)
throw error;
} }
return existing.some(perm => perm.id === resolvedId); return existing.some((perm) => perm.id === resolvedId);
} }
_validateOptions(guild, command) { _validateOptions(guild, command) {
@@ -383,7 +469,12 @@ class ApplicationCommandPermissionsManager extends BaseManager {
} }
commandId ??= this.client.application?.commands.resolveId(command); commandId ??= this.client.application?.commands.resolveId(command);
if (!commandId) { if (!commandId) {
throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable', true); throw new TypeError(
'INVALID_TYPE',
'command',
'ApplicationCommandResolvable',
true,
);
} }
} }
return { guildId, commandId }; return { guildId, commandId };

View File

@@ -116,32 +116,52 @@ class AutoModerationRuleManager extends CachedManager {
exemptChannels, exemptChannels,
reason, reason,
}) { }) {
const data = await this.client.api.guilds(this.guild.id)['auto-moderation'].rules.post({ const data = await this.client.api
.guilds(this.guild.id)
['auto-moderation'].rules.post({
data: { data: {
name, name,
event_type: typeof eventType === 'number' ? eventType : AutoModerationRuleEventTypes[eventType], event_type:
trigger_type: typeof triggerType === 'number' ? triggerType : AutoModerationRuleTriggerTypes[triggerType], typeof eventType === 'number'
? eventType
: AutoModerationRuleEventTypes[eventType],
trigger_type:
typeof triggerType === 'number'
? triggerType
: AutoModerationRuleTriggerTypes[triggerType],
trigger_metadata: triggerMetadata && { trigger_metadata: triggerMetadata && {
keyword_filter: triggerMetadata.keywordFilter, keyword_filter: triggerMetadata.keywordFilter,
regex_patterns: triggerMetadata.regexPatterns, regex_patterns: triggerMetadata.regexPatterns,
presets: triggerMetadata.presets?.map(preset => presets: triggerMetadata.presets?.map((preset) =>
typeof preset === 'number' ? preset : AutoModerationRuleKeywordPresetTypes[preset], typeof preset === 'number'
? preset
: AutoModerationRuleKeywordPresetTypes[preset],
), ),
allow_list: triggerMetadata.allowList, allow_list: triggerMetadata.allowList,
mention_total_limit: triggerMetadata.mentionTotalLimit, mention_total_limit: triggerMetadata.mentionTotalLimit,
mention_raid_protection_enabled: triggerMetadata.mentionRaidProtectionEnabled, mention_raid_protection_enabled:
triggerMetadata.mentionRaidProtectionEnabled,
}, },
actions: actions.map(action => ({ actions: actions.map((action) => ({
type: typeof action.type === 'number' ? action.type : AutoModerationActionTypes[action.type], type:
typeof action.type === 'number'
? action.type
: AutoModerationActionTypes[action.type],
metadata: { metadata: {
duration_seconds: action.metadata?.durationSeconds, duration_seconds: action.metadata?.durationSeconds,
channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel), channel_id:
action.metadata?.channel &&
this.guild.channels.resolveId(action.metadata.channel),
custom_message: action.metadata?.customMessage, custom_message: action.metadata?.customMessage,
}, },
})), })),
enabled, enabled,
exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)), exempt_roles: exemptRoles?.map((exemptRole) =>
exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)), this.guild.roles.resolveId(exemptRole),
),
exempt_channels: exemptChannels?.map((exemptChannel) =>
this.guild.channels.resolveId(exemptChannel),
),
}, },
reason, reason,
}); });
@@ -173,7 +193,16 @@ class AutoModerationRuleManager extends CachedManager {
*/ */
async edit( async edit(
autoModerationRule, autoModerationRule,
{ name, eventType, triggerMetadata, actions, enabled, exemptRoles, exemptChannels, reason }, {
name,
eventType,
triggerMetadata,
actions,
enabled,
exemptRoles,
exemptChannels,
reason,
},
) { ) {
const autoModerationRuleId = this.resolveId(autoModerationRule); const autoModerationRuleId = this.resolveId(autoModerationRule);
@@ -183,28 +212,43 @@ class AutoModerationRuleManager extends CachedManager {
.patch({ .patch({
data: { data: {
name, name,
event_type: typeof eventType === 'number' ? eventType : AutoModerationRuleEventTypes[eventType], event_type:
typeof eventType === 'number'
? eventType
: AutoModerationRuleEventTypes[eventType],
trigger_metadata: triggerMetadata && { trigger_metadata: triggerMetadata && {
keyword_filter: triggerMetadata.keywordFilter, keyword_filter: triggerMetadata.keywordFilter,
regex_patterns: triggerMetadata.regexPatterns, regex_patterns: triggerMetadata.regexPatterns,
presets: triggerMetadata.presets?.map(preset => presets: triggerMetadata.presets?.map((preset) =>
typeof preset === 'number' ? preset : AutoModerationRuleKeywordPresetTypes[preset], typeof preset === 'number'
? preset
: AutoModerationRuleKeywordPresetTypes[preset],
), ),
allow_list: triggerMetadata.allowList, allow_list: triggerMetadata.allowList,
mention_total_limit: triggerMetadata.mentionTotalLimit, mention_total_limit: triggerMetadata.mentionTotalLimit,
mention_raid_protection_enabled: triggerMetadata.mentionRaidProtectionEnabled, mention_raid_protection_enabled:
triggerMetadata.mentionRaidProtectionEnabled,
}, },
actions: actions?.map(action => ({ actions: actions?.map((action) => ({
type: typeof action.type === 'number' ? action.type : AutoModerationActionTypes[action.type], type:
typeof action.type === 'number'
? action.type
: AutoModerationActionTypes[action.type],
metadata: { metadata: {
duration_seconds: action.metadata?.durationSeconds, duration_seconds: action.metadata?.durationSeconds,
channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel), channel_id:
action.metadata?.channel &&
this.guild.channels.resolveId(action.metadata.channel),
custom_message: action.metadata?.customMessage, custom_message: action.metadata?.customMessage,
}, },
})), })),
enabled, enabled,
exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)), exempt_roles: exemptRoles?.map((exemptRole) =>
exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)), this.guild.roles.resolveId(exemptRole),
),
exempt_channels: exemptChannels?.map((exemptChannel) =>
this.guild.channels.resolveId(exemptChannel),
),
}, },
reason, reason,
}); });
@@ -255,9 +299,15 @@ class AutoModerationRuleManager extends CachedManager {
fetch(options) { fetch(options) {
if (!options) return this._fetchMany(); if (!options) return this._fetchMany();
const { autoModerationRule, cache, force } = options; const { autoModerationRule, cache, force } = options;
const resolvedAutoModerationRule = this.resolveId(autoModerationRule ?? options); const resolvedAutoModerationRule = this.resolveId(
autoModerationRule ?? options,
);
if (resolvedAutoModerationRule) { if (resolvedAutoModerationRule) {
return this._fetchSingle({ autoModerationRule: resolvedAutoModerationRule, cache, force }); return this._fetchSingle({
autoModerationRule: resolvedAutoModerationRule,
cache,
force,
});
} }
return this._fetchMany(options); return this._fetchMany(options);
} }
@@ -268,15 +318,24 @@ class AutoModerationRuleManager extends CachedManager {
if (existing) return existing; if (existing) return existing;
} }
const data = await this.client.api.guilds(this.guild.id)('auto-moderation').rules(autoModerationRule).get(); const data = await this.client.api
.guilds(this.guild.id)('auto-moderation')
.rules(autoModerationRule)
.get();
return this._add(data, cache); return this._add(data, cache);
} }
async _fetchMany(options = {}) { async _fetchMany(options = {}) {
const data = await this.client.api.guilds(this.guild.id)('auto-moderation').rules.get(); const data = await this.client.api
.guilds(this.guild.id)('auto-moderation')
.rules.get();
return data.reduce( return data.reduce(
(col, autoModerationRule) => col.set(autoModerationRule.id, this._add(autoModerationRule, options.cache)), (col, autoModerationRule) =>
col.set(
autoModerationRule.id,
this._add(autoModerationRule, options.cache),
),
new Collection(), new Collection(),
); );
} }
@@ -289,7 +348,10 @@ class AutoModerationRuleManager extends CachedManager {
*/ */
async delete(autoModerationRule, reason) { async delete(autoModerationRule, reason) {
const autoModerationRuleId = this.resolveId(autoModerationRule); const autoModerationRuleId = this.resolveId(autoModerationRule);
await this.client.api.guilds(this.guild.id)('auto-moderation').rules(autoModerationRuleId).delete({ reason }); await this.client.api
.guilds(this.guild.id)('auto-moderation')
.rules(autoModerationRuleId)
.delete({ reason });
} }
} }

View File

@@ -34,9 +34,11 @@ class BillingManager extends BaseManager {
*/ */
async fetchPaymentSources() { async fetchPaymentSources() {
// https://discord.com/api/v9/users/@me/billing/payment-sources // https://discord.com/api/v9/users/@me/billing/payment-sources
const d = await this.client.api.users('@me').billing['payment-sources'].get(); const d = await this.client.api
.users('@me')
.billing['payment-sources'].get();
// ! TODO: Create a PaymentSource class // ! TODO: Create a PaymentSource class
this.paymentSources = new Collection(d.map(s => [s.id, s])); this.paymentSources = new Collection(d.map((s) => [s.id, s]));
return this.paymentSources; return this.paymentSources;
} }
@@ -46,8 +48,12 @@ class BillingManager extends BaseManager {
*/ */
async fetchGuildBoosts() { async fetchGuildBoosts() {
// https://discord.com/api/v9/users/@me/guilds/premium/subscription-slots // https://discord.com/api/v9/users/@me/guilds/premium/subscription-slots
const d = await this.client.api.users('@me').guilds.premium['subscription-slots'].get(); const d = await this.client.api
this.guildBoosts = new Collection(d.map(s => [s.id, new GuildBoost(this.client, s)])); .users('@me')
.guilds.premium['subscription-slots'].get();
this.guildBoosts = new Collection(
d.map((s) => [s.id, new GuildBoost(this.client, s)]),
);
return this.guildBoosts; return this.guildBoosts;
} }
@@ -58,7 +64,7 @@ class BillingManager extends BaseManager {
async fetchCurrentSubscription() { async fetchCurrentSubscription() {
// https://discord.com/api/v9/users/@me/billing/subscriptions // https://discord.com/api/v9/users/@me/billing/subscriptions
const d = await this.client.api.users('@me').billing.subscriptions.get(); const d = await this.client.api.users('@me').billing.subscriptions.get();
this.currentSubscription = new Collection(d.map(s => [s.id, s])); this.currentSubscription = new Collection(d.map((s) => [s.id, s]));
return this.currentSubscription; return this.currentSubscription;
} }
} }

View File

@@ -19,7 +19,9 @@ class CachedManager extends DataManager {
* @readonly * @readonly
* @name CachedManager#_cache * @name CachedManager#_cache
*/ */
Object.defineProperty(this, '_cache', { value: this.client.options.makeCache(this.constructor, this.holds) }); Object.defineProperty(this, '_cache', {
value: this.client.options.makeCache(this.constructor, this.holds),
});
let cleanup = this._cache[_cleanupSymbol]?.(); let cleanup = this._cache[_cleanupSymbol]?.();
if (cleanup) { if (cleanup) {
@@ -62,7 +64,9 @@ class CachedManager extends DataManager {
return clone; return clone;
} }
const entry = this.holds ? new this.holds(this.client, data, ...extras) : data; const entry = this.holds
? new this.holds(this.client, data, ...extras)
: data;
if (cache) this.cache.set(id ?? entry.id, entry); if (cache) this.cache.set(id ?? entry.id, entry);
return entry; return entry;
} }

View File

@@ -3,7 +3,11 @@
const process = require('node:process'); const process = require('node:process');
const CachedManager = require('./CachedManager'); const CachedManager = require('./CachedManager');
const { Channel } = require('../structures/Channel'); const { Channel } = require('../structures/Channel');
const { Events, ThreadChannelTypes, RelationshipTypes } = require('../util/Constants'); const {
Events,
ThreadChannelTypes,
RelationshipTypes,
} = require('../util/Constants');
let cacheWarningEmitted = false; let cacheWarningEmitted = false;
@@ -16,8 +20,10 @@ class ChannelManager extends CachedManager {
super(client, Channel, iterable); super(client, Channel, iterable);
const defaultCaching = const defaultCaching =
this._cache.constructor.name === 'Collection' || this._cache.constructor.name === 'Collection' ||
((this._cache.maxSize === undefined || this._cache.maxSize === Infinity) && ((this._cache.maxSize === undefined ||
(this._cache.sweepFilter === undefined || this._cache.sweepFilter.isDefault)); this._cache.maxSize === Infinity) &&
(this._cache.sweepFilter === undefined ||
this._cache.sweepFilter.isDefault));
if (!cacheWarningEmitted && !defaultCaching) { if (!cacheWarningEmitted && !defaultCaching) {
cacheWarningEmitted = true; cacheWarningEmitted = true;
process.emitWarning( process.emitWarning(
@@ -44,10 +50,15 @@ class ChannelManager extends CachedManager {
return existing; return existing;
} }
const channel = Channel.create(this.client, data, guild, { allowUnknownGuild }); const channel = Channel.create(this.client, data, guild, {
allowUnknownGuild,
});
if (!channel) { 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; return null;
} }
@@ -115,7 +126,10 @@ class ChannelManager extends CachedManager {
* .then(channel => console.log(channel.name)) * .then(channel => console.log(channel.name))
* .catch(console.error); * .catch(console.error);
*/ */
async fetch(id, { allowUnknownGuild = false, cache = true, force = false } = {}) { async fetch(
id,
{ allowUnknownGuild = false, cache = true, force = false } = {},
) {
if (!force) { if (!force) {
const existing = this.cache.get(id); const existing = this.cache.get(id);
if (existing && !existing.partial) return existing; if (existing && !existing.partial) return existing;
@@ -133,11 +147,19 @@ class ChannelManager extends CachedManager {
* client.channels.createGroupDM(); * client.channels.createGroupDM();
*/ */
async createGroupDM(recipients = []) { async createGroupDM(recipients = []) {
if (!Array.isArray(recipients)) throw new Error(`Expected an array of recipients (got ${typeof recipients})`); if (!Array.isArray(recipients))
throw new Error(
`Expected an array of recipients (got ${typeof recipients})`,
);
recipients = recipients recipients = recipients
.map(r => this.client.users.resolveId(r)) .map((r) => this.client.users.resolveId(r))
.filter(r => r && this.client.relationships.cache.get(r) == RelationshipTypes.FRIEND); .filter(
if (recipients.length == 1 || recipients.length > 9) throw new Error('Invalid Users length (max=9)'); (r) =>
r &&
this.client.relationships.cache.get(r) == RelationshipTypes.FRIEND,
);
if (recipients.length == 1 || recipients.length > 9)
throw new Error('Invalid Users length (max=9)');
const data = await this.client.api.users['@me'].channels.post({ const data = await this.client.api.users['@me'].channels.post({
data: { recipients }, data: { recipients },
}); });

View File

@@ -186,7 +186,7 @@ class ClientUserSettingManager extends BaseManager {
if ('custom_status' in data) { if ('custom_status' in data) {
this.customStatus = data.custom_status; this.customStatus = data.custom_status;
const activities = this.client.presence.activities.filter( const activities = this.client.presence.activities.filter(
a => ![ActivityTypes.CUSTOM, 'CUSTOM'].includes(a.type), (a) => ![ActivityTypes.CUSTOM, 'CUSTOM'].includes(a.type),
); );
if (data.custom_status) { if (data.custom_status) {
const custom = new CustomStatus(this.client); const custom = new CustomStatus(this.client);
@@ -200,7 +200,10 @@ class ClientUserSettingManager extends BaseManager {
if (emoji) custom.setEmoji(emoji); if (emoji) custom.setEmoji(emoji);
activities.push(custom); activities.push(custom);
} }
this.client.emit('debug', '[SETTING > ClientUser] Sync activities & status'); this.client.emit(
'debug',
'[SETTING > ClientUser] Sync activities & status',
);
this.client.user.setPresence({ activities }); this.client.user.setPresence({ activities });
} }
if ('friend_source_flags' in data) { if ('friend_source_flags' in data) {
@@ -212,7 +215,10 @@ class ClientUserSettingManager extends BaseManager {
* @type {Collection<Snowflake, Guild>} * @type {Collection<Snowflake, Guild>}
*/ */
this.disableDMfromGuilds = new Collection( this.disableDMfromGuilds = new Collection(
data.restricted_guilds.map(guildId => [guildId, this.client.guilds.cache.get(guildId)]), data.restricted_guilds.map((guildId) => [
guildId,
this.client.guilds.cache.get(guildId),
]),
); );
} }
} }
@@ -293,7 +299,10 @@ class ClientUserSettingManager extends BaseManager {
data.emoji_name = options.emoji?.name; data.emoji_name = options.emoji?.name;
data.emoji_id = options.emoji?.id; data.emoji_id = options.emoji?.id;
} else { } else {
data.emoji_name = typeof options.emoji?.name === 'string' ? options.emoji?.name : null; data.emoji_name =
typeof options.emoji?.name === 'string'
? options.emoji?.name
: null;
} }
} }
return this.edit({ custom_status: data }); return this.edit({ custom_status: data });
@@ -305,7 +314,9 @@ class ClientUserSettingManager extends BaseManager {
}; };
if (typeof options.text === 'string') { if (typeof options.text === 'string') {
if (options.text.length > 128) { if (options.text.length > 128) {
throw new RangeError('[INVALID_VALUE] Custom status text must be less than 128 characters'); throw new RangeError(
'[INVALID_VALUE] Custom status text must be less than 128 characters',
);
} }
data.text = options.text; data.text = options.text;
} }
@@ -315,16 +326,20 @@ class ClientUserSettingManager extends BaseManager {
data.emoji_name = emoji.name; data.emoji_name = emoji.name;
data.emoji_id = emoji.id; data.emoji_id = emoji.id;
} else { } else {
data.emoji_name = typeof options.emoji === 'string' ? options.emoji : null; data.emoji_name =
typeof options.emoji === 'string' ? options.emoji : null;
} }
} }
if (typeof options.expires === 'number') { if (typeof options.expires === 'number') {
if (options.expires < Date.now()) { if (options.expires < Date.now()) {
throw new RangeError(`[INVALID_VALUE] Custom status expiration must be greater than ${Date.now()}`); throw new RangeError(
`[INVALID_VALUE] Custom status expiration must be greater than ${Date.now()}`,
);
} }
data.expires_at = new Date(options.expires).toISOString(); data.expires_at = new Date(options.expires).toISOString();
} }
if (['online', 'idle', 'dnd', 'invisible'].includes(options.status)) this.edit({ status: options.status }); if (['online', 'idle', 'dnd', 'invisible'].includes(options.status))
this.edit({ status: options.status });
return this.edit({ custom_status: data }); return this.edit({ custom_status: data });
} }
} }
@@ -340,7 +355,9 @@ class ClientUserSettingManager extends BaseManager {
} }
return this.edit({ return this.edit({
default_guilds_restricted: status, default_guilds_restricted: status,
restricted_guilds: status ? this.client.guilds.cache.map(v => v.id) : [], restricted_guilds: status
? this.client.guilds.cache.map((v) => v.id)
: [],
}); });
} }
/** /**
@@ -364,8 +381,11 @@ class ClientUserSettingManager extends BaseManager {
* @returns {Promise} * @returns {Promise}
*/ */
removeRestrictedGuild(guildId) { removeRestrictedGuild(guildId) {
if (!this.disableDMfromServer.delete(guildId)) throw new Error('Guild is already restricted'); if (!this.disableDMfromServer.delete(guildId))
return this.edit({ restricted_guilds: this.disableDMfromServer.map((v, k) => k) }); throw new Error('Guild is already restricted');
return this.edit({
restricted_guilds: this.disableDMfromServer.map((v, k) => k),
});
} }
} }

View File

@@ -38,7 +38,8 @@ class DataManager extends BaseManager {
*/ */
resolve(idOrInstance) { resolve(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance; if (idOrInstance instanceof this.holds) return idOrInstance;
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null; if (typeof idOrInstance === 'string')
return this.cache.get(idOrInstance) ?? null;
return null; return null;
} }

View File

@@ -47,7 +47,9 @@ class GuildBanManager extends CachedManager {
* @returns {?GuildBan} * @returns {?GuildBan}
*/ */
resolve(ban) { resolve(ban) {
return super.resolve(ban) ?? super.resolve(this.client.users.resolveId(ban)); return (
super.resolve(ban) ?? super.resolve(this.client.users.resolveId(ban))
);
} }
/** /**
@@ -99,7 +101,8 @@ class GuildBanManager extends CachedManager {
if (!options) return this._fetchMany(); if (!options) return this._fetchMany();
const { user, cache, force, limit, before, after } = options; const { user, cache, force, limit, before, after } = options;
const resolvedUser = this.client.users.resolveId(user ?? options); const resolvedUser = this.client.users.resolveId(user ?? options);
if (resolvedUser) return this._fetchSingle({ user: resolvedUser, cache, force }); if (resolvedUser)
return this._fetchSingle({ user: resolvedUser, cache, force });
if (!before && !after && !limit && typeof cache === 'undefined') { if (!before && !after && !limit && typeof cache === 'undefined') {
throw new Error('FETCH_BAN_RESOLVE_ID'); throw new Error('FETCH_BAN_RESOLVE_ID');
@@ -123,7 +126,10 @@ class GuildBanManager extends CachedManager {
query: options, query: options,
}); });
return data.reduce((col, ban) => col.set(ban.user.id, this._add(ban, options.cache)), new Collection()); return data.reduce(
(col, ban) => col.set(ban.user.id, this._add(ban, options.cache)),
new Collection(),
);
} }
/** /**
* Options used to ban a user from a guild. * Options used to ban a user from a guild.
@@ -149,7 +155,8 @@ class GuildBanManager extends CachedManager {
* .catch(console.error); * .catch(console.error);
*/ */
async create(user, options = {}) { async create(user, options = {}) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); if (typeof options !== 'object')
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
const id = this.client.users.resolveId(user); const id = this.client.users.resolveId(user);
if (!id) throw new Error('BAN_RESOLVE_ID', true); if (!id) throw new Error('BAN_RESOLVE_ID', true);
@@ -232,18 +239,32 @@ class GuildBanManager extends CachedManager {
*/ */
async bulkCreate(users, options = {}) { async bulkCreate(users, options = {}) {
if (!users || !(Array.isArray(users) || users instanceof Collection)) { if (!users || !(Array.isArray(users) || users instanceof Collection)) {
throw new TypeError('INVALID_TYPE', 'users', 'Array or Collection of UserResolvable', true); throw new TypeError(
'INVALID_TYPE',
'users',
'Array or Collection of UserResolvable',
true,
);
} }
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); if (typeof options !== 'object')
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
const userIds = users.map(user => this.client.users.resolveId(user)); const userIds = users.map((user) => this.client.users.resolveId(user));
if (userIds.length === 0) throw new Error('BULK_BAN_USERS_OPTION_EMPTY'); if (userIds.length === 0) throw new Error('BULK_BAN_USERS_OPTION_EMPTY');
const result = await this.client.api.guilds(this.guild.id)['bulk-ban'].post({ const result = await this.client.api
data: { delete_message_days: options.deleteMessageSeconds, user_ids: userIds }, .guilds(this.guild.id)
['bulk-ban'].post({
data: {
delete_message_days: options.deleteMessageSeconds,
user_ids: userIds,
},
reason: options.reason, reason: options.reason,
}); });
return { bannedUsers: result.banned_users, failedUsers: result.failed_users }; return {
bannedUsers: result.banned_users,
failedUsers: result.failed_users,
};
} }
} }

View File

@@ -18,7 +18,11 @@ const {
} = require('../util/Constants'); } = require('../util/Constants');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util'); const Util = require('../util/Util');
const { resolveAutoArchiveMaxLimit, transformGuildForumTag, transformGuildDefaultReaction } = require('../util/Util'); const {
resolveAutoArchiveMaxLimit,
transformGuildForumTag,
transformGuildDefaultReaction,
} = require('../util/Util');
let cacheWarningEmitted = false; let cacheWarningEmitted = false;
let storeChannelDeprecationEmitted = false; let storeChannelDeprecationEmitted = false;
@@ -32,8 +36,10 @@ class GuildChannelManager extends CachedManager {
super(guild.client, GuildChannel, iterable); super(guild.client, GuildChannel, iterable);
const defaultCaching = const defaultCaching =
this._cache.constructor.name === 'Collection' || this._cache.constructor.name === 'Collection' ||
((this._cache.maxSize === undefined || this._cache.maxSize === Infinity) && ((this._cache.maxSize === undefined ||
(this._cache.sweepFilter === undefined || this._cache.sweepFilter.isDefault)); this._cache.maxSize === Infinity) &&
(this._cache.sweepFilter === undefined ||
this._cache.sweepFilter.isDefault));
if (!cacheWarningEmitted && !defaultCaching) { if (!cacheWarningEmitted && !defaultCaching) {
cacheWarningEmitted = true; cacheWarningEmitted = true;
process.emitWarning( process.emitWarning(
@@ -89,7 +95,8 @@ class GuildChannelManager extends CachedManager {
* @returns {?(GuildChannel|ThreadChannel)} * @returns {?(GuildChannel|ThreadChannel)}
*/ */
resolve(channel) { resolve(channel) {
if (channel instanceof ThreadChannel) return this.cache.get(channel.id) ?? null; if (channel instanceof ThreadChannel)
return this.cache.get(channel.id) ?? null;
return super.resolve(channel); return super.resolve(channel);
} }
@@ -154,17 +161,33 @@ class GuildChannelManager extends CachedManager {
} = {}, } = {},
) { ) {
parent &&= this.client.channels.resolveId(parent); parent &&= this.client.channels.resolveId(parent);
permissionOverwrites &&= permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild)); permissionOverwrites &&= permissionOverwrites.map((o) =>
const intType = typeof type === 'number' ? type : ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT; PermissionOverwrites.resolve(o, this.guild),
);
const intType =
typeof type === 'number'
? type
: (ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT);
const videoMode = typeof videoQualityMode === 'number' ? videoQualityMode : VideoQualityModes[videoQualityMode]; const videoMode =
typeof videoQualityMode === 'number'
? videoQualityMode
: VideoQualityModes[videoQualityMode];
const sortMode = typeof defaultSortOrder === 'number' ? defaultSortOrder : SortOrderTypes[defaultSortOrder]; const sortMode =
typeof defaultSortOrder === 'number'
? defaultSortOrder
: SortOrderTypes[defaultSortOrder];
const layoutMode = const layoutMode =
typeof defaultForumLayout === 'number' ? defaultForumLayout : ForumLayoutTypes[defaultForumLayout]; typeof defaultForumLayout === 'number'
? defaultForumLayout
: ForumLayoutTypes[defaultForumLayout];
if (intType === ChannelTypes.GUILD_STORE && !storeChannelDeprecationEmitted) { if (
intType === ChannelTypes.GUILD_STORE &&
!storeChannelDeprecationEmitted
) {
storeChannelDeprecationEmitted = true; storeChannelDeprecationEmitted = true;
process.emitWarning( process.emitWarning(
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
@@ -187,8 +210,12 @@ class GuildChannelManager extends CachedManager {
rate_limit_per_user: rateLimitPerUser, rate_limit_per_user: rateLimitPerUser,
rtc_region: rtcRegion, rtc_region: rtcRegion,
video_quality_mode: videoMode, video_quality_mode: videoMode,
available_tags: availableTags?.map(availableTag => transformGuildForumTag(availableTag)), available_tags: availableTags?.map((availableTag) =>
default_reaction_emoji: defaultReactionEmoji && transformGuildDefaultReaction(defaultReactionEmoji), transformGuildForumTag(availableTag),
),
default_reaction_emoji:
defaultReactionEmoji &&
transformGuildDefaultReaction(defaultReactionEmoji),
default_sort_order: sortMode, default_sort_order: sortMode,
default_forum_layout: layoutMode, default_forum_layout: layoutMode,
default_thread_rate_limit_per_user: defaultThreadRateLimitPerUser, default_thread_rate_limit_per_user: defaultThreadRateLimitPerUser,
@@ -215,7 +242,8 @@ class GuildChannelManager extends CachedManager {
*/ */
async createWebhook(channel, name, { avatar, reason } = {}) { async createWebhook(channel, name, { avatar, reason } = {}) {
const id = this.resolveId(channel); const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable'); if (!id)
throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const resolvedImage = await DataResolver.resolveImage(avatar); const resolvedImage = await DataResolver.resolveImage(avatar);
@@ -239,8 +267,11 @@ class GuildChannelManager extends CachedManager {
async addFollower(channel, targetChannel, reason) { async addFollower(channel, targetChannel, reason) {
const channelId = this.resolveId(channel); const channelId = this.resolveId(channel);
const targetChannelId = this.resolveId(targetChannel); const targetChannelId = this.resolveId(targetChannel);
if (!channelId || !targetChannelId) throw new Error('GUILD_CHANNEL_RESOLVE'); if (!channelId || !targetChannelId)
const { webhook_id } = await this.client.api.channels[channelId].followers.post({ throw new Error('GUILD_CHANNEL_RESOLVE');
const { webhook_id } = await this.client.api.channels[
channelId
].followers.post({
data: { webhook_channel_id: targetChannelId }, data: { webhook_channel_id: targetChannelId },
reason, reason,
}); });
@@ -288,31 +319,36 @@ class GuildChannelManager extends CachedManager {
*/ */
async edit(channel, data, reason) { async edit(channel, data, reason) {
channel = this.resolve(channel); channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable'); if (!channel)
throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const parentId = data.parent && this.client.channels.resolveId(data.parent); const parentId = data.parent && this.client.channels.resolveId(data.parent);
if (typeof data.position !== 'undefined') await this.setPosition(channel, data.position, { reason }); if (typeof data.position !== 'undefined')
await this.setPosition(channel, data.position, { reason });
let permission_overwrites = data.permissionOverwrites?.map(o => PermissionOverwrites.resolve(o, this.guild)); let permission_overwrites = data.permissionOverwrites?.map((o) =>
PermissionOverwrites.resolve(o, this.guild),
);
if (data.lockPermissions) { if (data.lockPermissions) {
if (parentId) { if (parentId) {
const newParent = this.cache.get(parentId); const newParent = this.cache.get(parentId);
if (newParent?.type === 'GUILD_CATEGORY') { if (newParent?.type === 'GUILD_CATEGORY') {
permission_overwrites = newParent.permissionOverwrites.cache.map(o => permission_overwrites = newParent.permissionOverwrites.cache.map(
PermissionOverwrites.resolve(o, this.guild), (o) => PermissionOverwrites.resolve(o, this.guild),
); );
} }
} else if (channel.parent) { } else if (channel.parent) {
permission_overwrites = channel.parent.permissionOverwrites.cache.map(o => permission_overwrites = channel.parent.permissionOverwrites.cache.map(
PermissionOverwrites.resolve(o, this.guild), (o) => PermissionOverwrites.resolve(o, this.guild),
); );
} }
} }
let defaultAutoArchiveDuration = data.defaultAutoArchiveDuration; let defaultAutoArchiveDuration = data.defaultAutoArchiveDuration;
if (defaultAutoArchiveDuration === 'MAX') defaultAutoArchiveDuration = resolveAutoArchiveMaxLimit(this.guild); if (defaultAutoArchiveDuration === 'MAX')
defaultAutoArchiveDuration = resolveAutoArchiveMaxLimit(this.guild);
const newData = await this.client.api.channels(channel.id).patch({ const newData = await this.client.api.channels(channel.id).patch({
data: { data: {
@@ -324,18 +360,26 @@ class GuildChannelManager extends CachedManager {
user_limit: data.userLimit ?? channel.userLimit, user_limit: data.userLimit ?? channel.userLimit,
rtc_region: 'rtcRegion' in data ? data.rtcRegion : channel.rtcRegion, rtc_region: 'rtcRegion' in data ? data.rtcRegion : channel.rtcRegion,
video_quality_mode: video_quality_mode:
typeof data.videoQualityMode === 'string' ? VideoQualityModes[data.videoQualityMode] : data.videoQualityMode, typeof data.videoQualityMode === 'string'
? VideoQualityModes[data.videoQualityMode]
: data.videoQualityMode,
parent_id: parentId, parent_id: parentId,
lock_permissions: data.lockPermissions, lock_permissions: data.lockPermissions,
rate_limit_per_user: data.rateLimitPerUser, rate_limit_per_user: data.rateLimitPerUser,
default_auto_archive_duration: defaultAutoArchiveDuration, default_auto_archive_duration: defaultAutoArchiveDuration,
permission_overwrites, permission_overwrites,
available_tags: data.availableTags?.map(availableTag => transformGuildForumTag(availableTag)), available_tags: data.availableTags?.map((availableTag) =>
default_reaction_emoji: data.defaultReactionEmoji && transformGuildDefaultReaction(data.defaultReactionEmoji), transformGuildForumTag(availableTag),
),
default_reaction_emoji:
data.defaultReactionEmoji &&
transformGuildDefaultReaction(data.defaultReactionEmoji),
default_thread_rate_limit_per_user: data.defaultThreadRateLimitPerUser, default_thread_rate_limit_per_user: data.defaultThreadRateLimitPerUser,
flags: 'flags' in data ? ChannelFlags.resolve(data.flags) : undefined, flags: 'flags' in data ? ChannelFlags.resolve(data.flags) : undefined,
default_sort_order: default_sort_order:
typeof data.defaultSortOrder === 'string' ? SortOrderTypes[data.defaultSortOrder] : data.defaultSortOrder, typeof data.defaultSortOrder === 'string'
? SortOrderTypes[data.defaultSortOrder]
: data.defaultSortOrder,
}, },
reason, reason,
}); });
@@ -357,7 +401,8 @@ class GuildChannelManager extends CachedManager {
*/ */
async setPosition(channel, position, { relative, reason } = {}) { async setPosition(channel, position, { relative, reason } = {}) {
channel = this.resolve(channel); channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable'); if (!channel)
throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const updatedChannels = await Util.setPosition( const updatedChannels = await Util.setPosition(
channel, channel,
position, position,
@@ -399,13 +444,18 @@ class GuildChannelManager extends CachedManager {
if (id) { if (id) {
const data = await this.client.api.channels(id).get(); const data = await this.client.api.channels(id).get();
// Since this is the guild manager, throw if on a different guild // Since this is the guild manager, throw if on a different guild
if (this.guild.id !== data.guild_id) throw new Error('GUILD_CHANNEL_UNOWNED'); if (this.guild.id !== data.guild_id)
throw new Error('GUILD_CHANNEL_UNOWNED');
return this.client.channels._add(data, this.guild, { cache }); return this.client.channels._add(data, this.guild, { cache });
} }
const data = await this.client.api.guilds(this.guild.id).channels.get(); const data = await this.client.api.guilds(this.guild.id).channels.get();
const channels = new Collection(); const channels = new Collection();
for (const channel of data) channels.set(channel.id, this.client.channels._add(channel, this.guild, { cache })); for (const channel of data)
channels.set(
channel.id,
this.client.channels._add(channel, this.guild, { cache }),
);
return channels; return channels;
} }
@@ -421,9 +471,13 @@ class GuildChannelManager extends CachedManager {
*/ */
async fetchWebhooks(channel) { async fetchWebhooks(channel) {
const id = this.resolveId(channel); const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable'); if (!id)
throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const data = await this.client.api.channels[id].webhooks.get(); 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()); return data.reduce(
(hooks, hook) => hooks.set(hook.id, new Webhook(this.client, hook)),
new Collection(),
);
} }
/** /**
@@ -453,14 +507,17 @@ class GuildChannelManager extends CachedManager {
* .catch(console.error); * .catch(console.error);
*/ */
async setPositions(channelPositions) { async setPositions(channelPositions) {
channelPositions = channelPositions.map(r => ({ channelPositions = channelPositions.map((r) => ({
id: this.client.channels.resolveId(r.channel), id: this.client.channels.resolveId(r.channel),
position: r.position, position: r.position,
lock_permissions: r.lockPermissions, lock_permissions: r.lockPermissions,
parent_id: typeof r.parent !== 'undefined' ? this.resolveId(r.parent) : undefined, parent_id:
typeof r.parent !== 'undefined' ? this.resolveId(r.parent) : undefined,
})); }));
await this.client.api.guilds(this.guild.id).channels.patch({ data: channelPositions }); await this.client.api
.guilds(this.guild.id)
.channels.patch({ data: channelPositions });
return this.client.actions.GuildChannelsPositionUpdate.handle({ return this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.guild.id, guild_id: this.guild.id,
channels: channelPositions, channels: channelPositions,
@@ -480,7 +537,8 @@ class GuildChannelManager extends CachedManager {
*/ */
async delete(channel, reason) { async delete(channel, reason) {
const id = this.resolveId(channel); const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable'); if (!id)
throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
await this.client.api.channels(id).delete({ reason }); await this.client.api.channels(id).delete({ reason });
} }
} }

View File

@@ -56,17 +56,30 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
const data = { image: attachment, name }; const data = { image: attachment, name };
if (roles) { if (roles) {
if (!Array.isArray(roles) && !(roles instanceof Collection)) { if (!Array.isArray(roles) && !(roles instanceof Collection)) {
throw new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true); throw new TypeError(
'INVALID_TYPE',
'options.roles',
'Array or Collection of Roles or Snowflakes',
true,
);
} }
data.roles = []; data.roles = [];
for (const role of roles.values()) { for (const role of roles.values()) {
const resolvedRole = this.guild.roles.resolveId(role); const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'options.roles', role); if (!resolvedRole)
throw new TypeError(
'INVALID_ELEMENT',
'Array or Collection',
'options.roles',
role,
);
data.roles.push(resolvedRole); data.roles.push(resolvedRole);
} }
} }
const emoji = await this.client.api.guilds(this.guild.id).emojis.post({ data, 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; return this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji;
} }
@@ -92,7 +105,10 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
const existing = this.cache.get(id); const existing = this.cache.get(id);
if (existing) return existing; if (existing) return existing;
} }
const emoji = await this.client.api.guilds(this.guild.id).emojis(id).get(); const emoji = await this.client.api
.guilds(this.guild.id)
.emojis(id)
.get();
return this._add(emoji, cache); return this._add(emoji, cache);
} }
@@ -110,7 +126,8 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
*/ */
async delete(emoji, reason) { async delete(emoji, reason) {
const id = this.resolveId(emoji); const id = this.resolveId(emoji);
if (!id) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true); if (!id)
throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
await this.client.api.guilds(this.guild.id).emojis(id).delete({ reason }); await this.client.api.guilds(this.guild.id).emojis(id).delete({ reason });
} }
@@ -123,8 +140,9 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
*/ */
async edit(emoji, data, reason) { async edit(emoji, data, reason) {
const id = this.resolveId(emoji); const id = this.resolveId(emoji);
if (!id) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true); if (!id)
const roles = data.roles?.map(r => this.guild.roles.resolveId(r)); 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 const newData = await this.client.api
.guilds(this.guild.id) .guilds(this.guild.id)
.emojis(id) .emojis(id)
@@ -151,7 +169,8 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
*/ */
async fetchAuthor(emoji) { async fetchAuthor(emoji) {
emoji = this.resolve(emoji); emoji = this.resolve(emoji);
if (!emoji) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true); if (!emoji)
throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
if (emoji.managed) { if (emoji.managed) {
throw new Error('EMOJI_MANAGED'); throw new Error('EMOJI_MANAGED');
} }
@@ -159,10 +178,16 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
const { me } = this.guild.members; const { me } = this.guild.members;
if (!me) throw new Error('GUILD_UNCACHED_ME'); if (!me) throw new Error('GUILD_UNCACHED_ME');
if (!me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS)) { if (!me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS)) {
throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild); 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(); const data = await this.client.api
.guilds(this.guild.id)
.emojis(emoji.id)
.get();
emoji._patch(data); emoji._patch(data);
return emoji.author; return emoji.author;
} }

View File

@@ -31,7 +31,9 @@ class GuildEmojiRoleManager extends DataManager {
* @readonly * @readonly
*/ */
get cache() { get cache() {
return this.guild.roles.cache.filter(role => this.emoji._roles.includes(role.id)); return this.guild.roles.cache.filter((role) =>
this.emoji._roles.includes(role.id),
);
} }
/** /**
@@ -40,13 +42,19 @@ class GuildEmojiRoleManager extends DataManager {
* @returns {Promise<GuildEmoji>} * @returns {Promise<GuildEmoji>}
*/ */
async add(roleOrRoles) { async add(roleOrRoles) {
if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles]; if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection))
roleOrRoles = [roleOrRoles];
const resolvedRoles = []; const resolvedRoles = [];
for (const role of roleOrRoles.values()) { for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role); const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) { if (!resolvedRole) {
throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'roles', role); throw new TypeError(
'INVALID_ELEMENT',
'Array or Collection',
'roles',
role,
);
} }
resolvedRoles.push(resolvedRole); resolvedRoles.push(resolvedRole);
} }
@@ -61,18 +69,26 @@ class GuildEmojiRoleManager extends DataManager {
* @returns {Promise<GuildEmoji>} * @returns {Promise<GuildEmoji>}
*/ */
async remove(roleOrRoles) { async remove(roleOrRoles) {
if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles]; if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection))
roleOrRoles = [roleOrRoles];
const resolvedRoleIds = []; const resolvedRoleIds = [];
for (const role of roleOrRoles.values()) { for (const role of roleOrRoles.values()) {
const roleId = this.guild.roles.resolveId(role); const roleId = this.guild.roles.resolveId(role);
if (!roleId) { if (!roleId) {
throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'roles', role); throw new TypeError(
'INVALID_ELEMENT',
'Array or Collection',
'roles',
role,
);
} }
resolvedRoleIds.push(roleId); resolvedRoleIds.push(roleId);
} }
const newRoles = [...this.cache.keys()].filter(id => !resolvedRoleIds.includes(id)); const newRoles = [...this.cache.keys()].filter(
(id) => !resolvedRoleIds.includes(id),
);
return this.set(newRoles); return this.set(newRoles);
} }

View File

@@ -3,7 +3,11 @@
const ThreadManager = require('./ThreadManager'); const ThreadManager = require('./ThreadManager');
const { TypeError } = require('../errors'); const { TypeError } = require('../errors');
const MessagePayload = require('../structures/MessagePayload'); const MessagePayload = require('../structures/MessagePayload');
const { resolveAutoArchiveMaxLimit, getUploadURL, uploadFile } = require('../util/Util'); const {
resolveAutoArchiveMaxLimit,
getUploadURL,
uploadFile,
} = require('../util/Util');
/** /**
* Manages API methods for threads in forum channels and stores their cache. * Manages API methods for threads in forum channels and stores their cache.
@@ -72,7 +76,7 @@ class GuildForumThreadManager extends ThreadManager {
// New API // New API
const attachments = await getUploadURL(this.client, this.channel.id, files); const attachments = await getUploadURL(this.client, this.channel.id, files);
const requestPromises = attachments.map(async attachment => { const requestPromises = attachments.map(async (attachment) => {
await uploadFile(files[attachment.id].file, attachment.upload_url); await uploadFile(files[attachment.id].file, attachment.upload_url);
return { return {
id: attachment.id, id: attachment.id,
@@ -86,9 +90,12 @@ class GuildForumThreadManager extends ThreadManager {
const attachmentsData = await Promise.all(requestPromises); const attachmentsData = await Promise.all(requestPromises);
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id)); attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild); if (autoArchiveDuration === 'MAX')
autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
const post_data = await this.client.api.channels(this.channel.id).threads.post({ const post_data = await this.client.api
.channels(this.channel.id)
.threads.post({
data: { data: {
name, name,
auto_archive_duration: autoArchiveDuration, auto_archive_duration: autoArchiveDuration,

View File

@@ -156,12 +156,18 @@ class GuildInviteManager extends CachedManager {
async _fetchMany(cache) { async _fetchMany(cache) {
const data = await this.client.api.guilds(this.guild.id).invites.get(); const data = await this.client.api.guilds(this.guild.id).invites.get();
return data.reduce((col, invite) => col.set(invite.code, this._add(invite, cache)), new Collection()); return data.reduce(
(col, invite) => col.set(invite.code, this._add(invite, cache)),
new Collection(),
);
} }
async _fetchChannelMany(channelId, cache) { async _fetchChannelMany(channelId, cache) {
const data = await this.client.api.channels(channelId).invites.get(); const data = await this.client.api.channels(channelId).invites.get();
return data.reduce((col, invite) => col.set(invite.code, this._add(invite, cache)), new Collection()); return data.reduce(
(col, invite) => col.set(invite.code, this._add(invite, cache)),
new Collection(),
);
} }
/** /**
@@ -177,7 +183,16 @@ class GuildInviteManager extends CachedManager {
*/ */
async create( async create(
channel, channel,
{ temporary = false, maxAge = 86400, maxUses = 0, unique, targetUser, targetApplication, targetType, reason } = {}, {
temporary = false,
maxAge = 86400,
maxUses = 0,
unique,
targetUser,
targetApplication,
targetType,
reason,
} = {},
) { ) {
const id = this.guild.channels.resolveId(channel); const id = this.guild.channels.resolveId(channel);
if (!id) throw new Error('GUILD_CHANNEL_RESOLVE'); if (!id) throw new Error('GUILD_CHANNEL_RESOLVE');
@@ -189,7 +204,10 @@ class GuildInviteManager extends CachedManager {
max_uses: maxUses, max_uses: maxUses,
unique, unique,
target_user_id: this.client.users.resolveId(targetUser), target_user_id: this.client.users.resolveId(targetUser),
target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication, target_application_id:
targetApplication?.id ??
targetApplication?.applicationId ??
targetApplication,
target_type: targetType, target_type: targetType,
}, },
reason, reason,

View File

@@ -187,13 +187,18 @@ class GuildManager extends CachedManager {
verificationLevel = VerificationLevels[verificationLevel]; verificationLevel = VerificationLevels[verificationLevel];
} }
if (typeof defaultMessageNotifications === 'string') { if (typeof defaultMessageNotifications === 'string') {
defaultMessageNotifications = DefaultMessageNotificationLevels[defaultMessageNotifications]; defaultMessageNotifications =
DefaultMessageNotificationLevels[defaultMessageNotifications];
} }
if (typeof explicitContentFilter === 'string') { if (typeof explicitContentFilter === 'string') {
explicitContentFilter = ExplicitContentFilterLevels[explicitContentFilter]; explicitContentFilter =
ExplicitContentFilterLevels[explicitContentFilter];
} }
for (const channel of channels) { for (const channel of channels) {
channel.type &&= typeof channel.type === 'number' ? channel.type : ChannelTypes[channel.type]; channel.type &&=
typeof channel.type === 'number'
? channel.type
: ChannelTypes[channel.type];
channel.parent_id = channel.parentId; channel.parent_id = channel.parentId;
delete channel.parentId; delete channel.parentId;
channel.user_limit = channel.userLimit; channel.user_limit = channel.userLimit;
@@ -242,10 +247,11 @@ class GuildManager extends CachedManager {
}, },
}); });
if (this.client.guilds.cache.has(data.id)) return this.client.guilds.cache.get(data.id); if (this.client.guilds.cache.has(data.id))
return this.client.guilds.cache.get(data.id);
return new Promise(resolve => { return new Promise((resolve) => {
const handleGuild = guild => { const handleGuild = (guild) => {
if (guild.id === data.id) { if (guild.id === data.id) {
clearTimeout(timeout); clearTimeout(timeout);
this.client.removeListener(Events.GUILD_CREATE, handleGuild); this.client.removeListener(Events.GUILD_CREATE, handleGuild);
@@ -293,12 +299,19 @@ class GuildManager extends CachedManager {
if (existing) return existing; if (existing) return existing;
} }
const data = await this.client.api.guilds(id).get({ query: { 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); return this._add(data, options.cache);
} }
const data = await this.client.api.users('@me').guilds.get({ query: options }); const data = await this.client.api
return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection()); .users('@me')
.guilds.get({ query: options });
return data.reduce(
(coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)),
new Collection(),
);
} }
/** /**
@@ -319,8 +332,10 @@ class GuildManager extends CachedManager {
const data = await this.client.api.guilds(guildId)['incident-actions'].put({ const data = await this.client.api.guilds(guildId)['incident-actions'].put({
data: { data: {
invites_disabled_until: invitesDisabledUntil && new Date(invitesDisabledUntil).toISOString(), invites_disabled_until:
dms_disabled_until: dmsDisabledUntil && new Date(dmsDisabledUntil).toISOString(), invitesDisabledUntil && new Date(invitesDisabledUntil).toISOString(),
dms_disabled_until:
dmsDisabledUntil && new Date(dmsDisabledUntil).toISOString(),
}, },
}); });

View File

@@ -106,20 +106,41 @@ class GuildMemberManager extends CachedManager {
deaf: options.deaf, deaf: options.deaf,
}; };
if (options.roles) { if (options.roles) {
if (!Array.isArray(options.roles) && !(options.roles instanceof Collection)) { if (
throw new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true); !Array.isArray(options.roles) &&
!(options.roles instanceof Collection)
) {
throw new TypeError(
'INVALID_TYPE',
'options.roles',
'Array or Collection of Roles or Snowflakes',
true,
);
} }
const resolvedRoles = []; const resolvedRoles = [];
for (const role of options.roles.values()) { for (const role of options.roles.values()) {
const resolvedRole = this.guild.roles.resolveId(role); const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'options.roles', role); if (!resolvedRole)
throw new TypeError(
'INVALID_ELEMENT',
'Array or Collection',
'options.roles',
role,
);
resolvedRoles.push(resolvedRole); resolvedRoles.push(resolvedRole);
} }
resolvedOptions.roles = resolvedRoles; resolvedOptions.roles = resolvedRoles;
} }
const data = await this.client.api.guilds(this.guild.id).members(userId).put({ data: 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. // 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); return data instanceof Buffer
? options.fetchWhenExisting === false
? null
: this.fetch(userId)
: this._add(data);
} }
/** /**
@@ -207,12 +228,13 @@ class GuildMemberManager extends CachedManager {
if (user) return this._fetchSingle({ user, cache: true }); if (user) return this._fetchSingle({ user, cache: true });
if (options.user) { if (options.user) {
if (Array.isArray(options.user)) { if (Array.isArray(options.user)) {
options.user = options.user.map(u => this.client.users.resolveId(u)); options.user = options.user.map((u) => this.client.users.resolveId(u));
return this._fetchMany(options); return this._fetchMany(options);
} else { } else {
options.user = this.client.users.resolveId(options.user); options.user = this.client.users.resolveId(options.user);
} }
if (!options.limit && !options.withPresences) return this._fetchSingle(options); if (!options.limit && !options.withPresences)
return this._fetchSingle(options);
} }
return this._fetchMany(options); return this._fetchMany(options);
} }
@@ -240,8 +262,13 @@ class GuildMemberManager extends CachedManager {
* @returns {Promise<Collection<Snowflake, GuildMember>>} * @returns {Promise<Collection<Snowflake, GuildMember>>}
*/ */
async search({ query, limit = 1, cache = true } = {}) { async search({ query, limit = 1, cache = true } = {}) {
const data = await this.client.api.guilds(this.guild.id).members.search.get({ query: { query, limit } }); const data = await this.client.api
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection()); .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(),
);
} }
/** /**
@@ -286,10 +313,13 @@ class GuildMemberManager extends CachedManager {
_data.channel_id = null; _data.channel_id = null;
_data.channel = undefined; _data.channel = undefined;
} }
_data.roles &&= _data.roles.map(role => (role instanceof Role ? role.id : role)); _data.roles &&= _data.roles.map((role) =>
role instanceof Role ? role.id : role,
);
_data.communication_disabled_until = _data.communication_disabled_until =
_data.communicationDisabledUntil && new Date(_data.communicationDisabledUntil).toISOString(); _data.communicationDisabledUntil &&
new Date(_data.communicationDisabledUntil).toISOString();
_data.flags = _data.flags && GuildMemberFlags.resolve(_data.flags); _data.flags = _data.flags && GuildMemberFlags.resolve(_data.flags);
@@ -304,7 +334,10 @@ class GuildMemberManager extends CachedManager {
let endpoint = this.client.api.guilds(this.guild.id); let endpoint = this.client.api.guilds(this.guild.id);
if (id === this.client.user.id) { if (id === this.client.user.id) {
const keys = Object.keys(data); const keys = Object.keys(data);
if (keys.length === 1 && ['nick', 'avatar', 'banner', 'bio'].includes(keys[0])) { if (
keys.length === 1 &&
['nick', 'avatar', 'banner', 'bio'].includes(keys[0])
) {
endpoint = endpoint.members('@me'); endpoint = endpoint.members('@me');
} else { } else {
endpoint = endpoint.members(id); endpoint = endpoint.members(id);
@@ -351,7 +384,13 @@ class GuildMemberManager extends CachedManager {
* .then(pruned => console.log(`I just pruned ${pruned} people!`)) * .then(pruned => console.log(`I just pruned ${pruned} people!`))
* .catch(console.error); * .catch(console.error);
*/ */
async prune({ days = 7, dry = false, count: compute_prune_count = true, 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'); if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE');
const query = { days }; const query = { days };
@@ -440,7 +479,10 @@ class GuildMemberManager extends CachedManager {
if (existing && !existing.partial) return existing; if (existing && !existing.partial) return existing;
} }
const data = await this.client.api.guilds(this.guild.id).members(user).get(); const data = await this.client.api
.guilds(this.guild.id)
.members(user)
.get();
return this._add(data, cache); return this._add(data, cache);
} }
@@ -455,7 +497,11 @@ class GuildMemberManager extends CachedManager {
const userId = this.resolveId(user); const userId = this.resolveId(user);
const roleId = this.guild.roles.resolveId(role); const roleId = this.guild.roles.resolveId(role);
await this.client.api.guilds(this.guild.id).members(userId).roles(roleId).put({ reason }); await this.client.api
.guilds(this.guild.id)
.members(userId)
.roles(roleId)
.put({ reason });
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId; return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
} }
@@ -471,7 +517,11 @@ class GuildMemberManager extends CachedManager {
const userId = this.resolveId(user); const userId = this.resolveId(user);
const roleId = this.guild.roles.resolveId(role); const roleId = this.guild.roles.resolveId(role);
await this.client.api.guilds(this.guild.id).members(userId).roles(roleId).delete({ reason }); await this.client.api
.guilds(this.guild.id)
.members(userId)
.roles(roleId)
.delete({ reason });
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId; return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
} }
@@ -483,7 +533,7 @@ class GuildMemberManager extends CachedManager {
* @returns {Promise<Collection<Snowflake, GuildMember>>} * @returns {Promise<Collection<Snowflake, GuildMember>>}
*/ */
fetchByMemberSafety(timeout = 15_000) { fetchByMemberSafety(timeout = 15_000) {
return new Promise(resolve => { return new Promise((resolve) => {
const nonce = SnowflakeUtil.generate(); const nonce = SnowflakeUtil.generate();
const fetchedMembers = new Collection(); const fetchedMembers = new Collection();
let timeout_ = setTimeout(() => { let timeout_ = setTimeout(() => {
@@ -556,12 +606,17 @@ class GuildMemberManager extends CachedManager {
for (const member of members.values()) { for (const member of members.values()) {
fetchedMembers.set(member.id, member); fetchedMembers.set(member.id, member);
} }
if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) { if (
members.size < 1_000 ||
(limit && fetchedMembers.size >= limit) ||
i === chunk.count
) {
clearTimeout(timeout); clearTimeout(timeout);
this.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler); this.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler);
this.client.decrementMaxListeners(); this.client.decrementMaxListeners();
let fetched = fetchedMembers; let fetched = fetchedMembers;
if (user_ids && !Array.isArray(user_ids) && fetched.size) fetched = fetched.first(); if (user_ids && !Array.isArray(user_ids) && fetched.size)
fetched = fetched.first();
resolve(fetched); resolve(fetched);
} }
}; };

View File

@@ -33,7 +33,9 @@ class GuildMemberRoleManager extends DataManager {
*/ */
get cache() { get cache() {
const everyone = this.guild.roles.everyone; const everyone = this.guild.roles.everyone;
return this.guild.roles.cache.filter(role => this.member._roles.includes(role.id)).set(everyone.id, everyone); return this.guild.roles.cache
.filter((role) => this.member._roles.includes(role.id))
.set(everyone.id, everyone);
} }
/** /**
@@ -42,9 +44,11 @@ class GuildMemberRoleManager extends DataManager {
* @readonly * @readonly
*/ */
get hoist() { get hoist() {
const hoistedRoles = this.cache.filter(role => role.hoist); const hoistedRoles = this.cache.filter((role) => role.hoist);
if (!hoistedRoles.size) return null; if (!hoistedRoles.size) return null;
return hoistedRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev)); return hoistedRoles.reduce((prev, role) =>
role.comparePositionTo(prev) > 0 ? role : prev,
);
} }
/** /**
@@ -53,9 +57,13 @@ class GuildMemberRoleManager extends DataManager {
* @readonly * @readonly
*/ */
get icon() { get icon() {
const iconRoles = this.cache.filter(role => role.icon || role.unicodeEmoji); const iconRoles = this.cache.filter(
(role) => role.icon || role.unicodeEmoji,
);
if (!iconRoles.size) return null; if (!iconRoles.size) return null;
return iconRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev)); return iconRoles.reduce((prev, role) =>
role.comparePositionTo(prev) > 0 ? role : prev,
);
} }
/** /**
@@ -64,9 +72,11 @@ class GuildMemberRoleManager extends DataManager {
* @readonly * @readonly
*/ */
get color() { get color() {
const coloredRoles = this.cache.filter(role => role.colors.primaryColor); const coloredRoles = this.cache.filter((role) => role.colors.primaryColor);
if (!coloredRoles.size) return null; if (!coloredRoles.size) return null;
return coloredRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev)); return coloredRoles.reduce((prev, role) =>
role.comparePositionTo(prev) > 0 ? role : prev,
);
} }
/** /**
@@ -75,7 +85,10 @@ class GuildMemberRoleManager extends DataManager {
* @readonly * @readonly
*/ */
get highest() { get highest() {
return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first()); return this.cache.reduce(
(prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev),
this.cache.first(),
);
} }
/** /**
@@ -84,7 +97,7 @@ class GuildMemberRoleManager extends DataManager {
* @readonly * @readonly
*/ */
get premiumSubscriberRole() { get premiumSubscriberRole() {
return this.cache.find(role => role.tags?.premiumSubscriberRole) ?? null; return this.cache.find((role) => role.tags?.premiumSubscriberRole) ?? null;
} }
/** /**
@@ -95,7 +108,10 @@ class GuildMemberRoleManager extends DataManager {
*/ */
get botRole() { get botRole() {
if (!this.member.user.bot) return null; if (!this.member.user.bot) return null;
return this.cache.find(role => role.tags?.botId === this.member.user.id) ?? null; return (
this.cache.find((role) => role.tags?.botId === this.member.user.id) ??
null
);
} }
/** /**
@@ -111,7 +127,13 @@ class GuildMemberRoleManager extends DataManager {
const resolvedRoles = []; const resolvedRoles = [];
for (const role of roleOrRoles.values()) { for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role); const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'roles', role); if (!resolvedRole)
throw new TypeError(
'INVALID_ELEMENT',
'Array or Collection',
'roles',
role,
);
resolvedRoles.push(resolvedRole); resolvedRoles.push(resolvedRole);
} }
@@ -120,10 +142,16 @@ class GuildMemberRoleManager extends DataManager {
} else { } else {
roleOrRoles = this.guild.roles.resolveId(roleOrRoles); roleOrRoles = this.guild.roles.resolveId(roleOrRoles);
if (roleOrRoles === null) { if (roleOrRoles === null) {
throw new TypeError('INVALID_TYPE', 'roles', 'Role, Snowflake or Array or Collection of Roles or Snowflakes'); throw new TypeError(
'INVALID_TYPE',
'roles',
'Role, Snowflake or Array or Collection of Roles or Snowflakes',
);
} }
await this.client.api.guilds[this.guild.id].members[this.member.id].roles[roleOrRoles].put({ reason }); await this.client.api.guilds[this.guild.id].members[this.member.id].roles[
roleOrRoles
].put({ reason });
const clone = this.member._clone(); const clone = this.member._clone();
clone._roles = [...this.cache.keys(), roleOrRoles]; clone._roles = [...this.cache.keys(), roleOrRoles];
@@ -144,22 +172,36 @@ class GuildMemberRoleManager extends DataManager {
const resolvedRoles = []; const resolvedRoles = [];
for (const role of roleOrRoles.values()) { for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role); const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) throw new TypeError('INVALID_ELEMENT', 'Array or Collection', 'roles', role); if (!resolvedRole)
throw new TypeError(
'INVALID_ELEMENT',
'Array or Collection',
'roles',
role,
);
resolvedRoles.push(resolvedRole); resolvedRoles.push(resolvedRole);
} }
const newRoles = this.cache.filter(role => !resolvedRoles.includes(role.id)); const newRoles = this.cache.filter(
(role) => !resolvedRoles.includes(role.id),
);
return this.set(newRoles, reason); return this.set(newRoles, reason);
} else { } else {
roleOrRoles = this.guild.roles.resolveId(roleOrRoles); roleOrRoles = this.guild.roles.resolveId(roleOrRoles);
if (roleOrRoles === null) { if (roleOrRoles === null) {
throw new TypeError('INVALID_TYPE', 'roles', 'Role, Snowflake or Array or Collection of Roles or Snowflakes'); throw new TypeError(
'INVALID_TYPE',
'roles',
'Role, Snowflake or Array or Collection of Roles or Snowflakes',
);
} }
await this.client.api.guilds[this.guild.id].members[this.member.id].roles[roleOrRoles].delete({ reason }); await this.client.api.guilds[this.guild.id].members[this.member.id].roles[
roleOrRoles
].delete({ reason });
const clone = this.member._clone(); const clone = this.member._clone();
const newRoles = this.cache.filter(role => role.id !== roleOrRoles); const newRoles = this.cache.filter((role) => role.id !== roleOrRoles);
clone._roles = [...newRoles.keys()]; clone._roles = [...newRoles.keys()];
return clone; return clone;
} }

View File

@@ -4,7 +4,11 @@ const { Collection } = require('@discordjs/collection');
const CachedManager = require('./CachedManager'); const CachedManager = require('./CachedManager');
const { TypeError, Error } = require('../errors'); const { TypeError, Error } = require('../errors');
const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent'); const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent');
const { PrivacyLevels, GuildScheduledEventEntityTypes, GuildScheduledEventStatuses } = require('../util/Constants'); const {
PrivacyLevels,
GuildScheduledEventEntityTypes,
GuildScheduledEventStatuses,
} = require('../util/Constants');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util'); const Util = require('../util/Util');
@@ -82,7 +86,8 @@ class GuildScheduledEventManager extends CachedManager {
* @returns {Promise<GuildScheduledEvent>} * @returns {Promise<GuildScheduledEvent>}
*/ */
async create(options) { async create(options) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); if (typeof options !== 'object')
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
let { let {
privacyLevel, privacyLevel,
entityType, entityType,
@@ -97,8 +102,10 @@ class GuildScheduledEventManager extends CachedManager {
recurrenceRule, recurrenceRule,
} = options; } = options;
if (typeof privacyLevel === 'string') privacyLevel = PrivacyLevels[privacyLevel]; if (typeof privacyLevel === 'string')
if (typeof entityType === 'string') entityType = GuildScheduledEventEntityTypes[entityType]; privacyLevel = PrivacyLevels[privacyLevel];
if (typeof entityType === 'string')
entityType = GuildScheduledEventEntityTypes[entityType];
let entity_metadata, channel_id; let entity_metadata, channel_id;
if (entityType === GuildScheduledEventEntityTypes.EXTERNAL) { if (entityType === GuildScheduledEventEntityTypes.EXTERNAL) {
@@ -107,21 +114,28 @@ class GuildScheduledEventManager extends CachedManager {
} else { } else {
channel_id = this.guild.channels.resolveId(channel); channel_id = this.guild.channels.resolveId(channel);
if (!channel_id) throw new Error('GUILD_VOICE_CHANNEL_RESOLVE'); if (!channel_id) throw new Error('GUILD_VOICE_CHANNEL_RESOLVE');
entity_metadata = typeof entityMetadata === 'undefined' ? entityMetadata : null; entity_metadata =
typeof entityMetadata === 'undefined' ? entityMetadata : null;
} }
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events').post({ const data = await this.client.api
.guilds(this.guild.id, 'scheduled-events')
.post({
data: { data: {
channel_id, channel_id,
name, name,
privacy_level: privacyLevel, privacy_level: privacyLevel,
scheduled_start_time: new Date(scheduledStartTime).toISOString(), scheduled_start_time: new Date(scheduledStartTime).toISOString(),
scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime, scheduled_end_time: scheduledEndTime
? new Date(scheduledEndTime).toISOString()
: scheduledEndTime,
description, description,
image: image && (await DataResolver.resolveImage(image)), image: image && (await DataResolver.resolveImage(image)),
entity_type: entityType, entity_type: entityType,
entity_metadata, entity_metadata,
recurrence_rule: recurrenceRule && Util.transformGuildScheduledEventRecurrenceRule(recurrenceRule), recurrence_rule:
recurrenceRule &&
Util.transformGuildScheduledEventRecurrenceRule(recurrenceRule),
}, },
reason, reason,
}); });
@@ -171,7 +185,10 @@ class GuildScheduledEventManager extends CachedManager {
return data.reduce( return data.reduce(
(coll, rawGuildScheduledEventData) => (coll, rawGuildScheduledEventData) =>
coll.set(rawGuildScheduledEventData.id, this._add(rawGuildScheduledEventData, options.cache)), coll.set(
rawGuildScheduledEventData.id,
this._add(rawGuildScheduledEventData, options.cache),
),
new Collection(), new Collection(),
); );
} }
@@ -204,9 +221,11 @@ class GuildScheduledEventManager extends CachedManager {
*/ */
async edit(guildScheduledEvent, options) { async edit(guildScheduledEvent, options) {
const guildScheduledEventId = this.resolveId(guildScheduledEvent); const guildScheduledEventId = this.resolveId(guildScheduledEvent);
if (!guildScheduledEventId) throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE'); if (!guildScheduledEventId)
throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE');
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); if (typeof options !== 'object')
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
let { let {
privacyLevel, privacyLevel,
entityType, entityType,
@@ -222,9 +241,12 @@ class GuildScheduledEventManager extends CachedManager {
recurrenceRule, recurrenceRule,
} = options; } = options;
if (typeof privacyLevel === 'string') privacyLevel = PrivacyLevels[privacyLevel]; if (typeof privacyLevel === 'string')
if (typeof entityType === 'string') entityType = GuildScheduledEventEntityTypes[entityType]; privacyLevel = PrivacyLevels[privacyLevel];
if (typeof status === 'string') status = GuildScheduledEventStatuses[status]; if (typeof entityType === 'string')
entityType = GuildScheduledEventEntityTypes[entityType];
if (typeof status === 'string')
status = GuildScheduledEventStatuses[status];
let entity_metadata; let entity_metadata;
if (entityMetadata) { if (entityMetadata) {
@@ -233,19 +255,30 @@ class GuildScheduledEventManager extends CachedManager {
}; };
} }
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).patch({ const data = await this.client.api
.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId)
.patch({
data: { data: {
channel_id: typeof channel === 'undefined' ? channel : this.guild.channels.resolveId(channel), channel_id:
typeof channel === 'undefined'
? channel
: this.guild.channels.resolveId(channel),
name, name,
privacy_level: privacyLevel, privacy_level: privacyLevel,
scheduled_start_time: scheduledStartTime ? new Date(scheduledStartTime).toISOString() : undefined, scheduled_start_time: scheduledStartTime
scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime, ? new Date(scheduledStartTime).toISOString()
: undefined,
scheduled_end_time: scheduledEndTime
? new Date(scheduledEndTime).toISOString()
: scheduledEndTime,
description, description,
entity_type: entityType, entity_type: entityType,
status, status,
image: image && (await DataResolver.resolveImage(image)), image: image && (await DataResolver.resolveImage(image)),
entity_metadata, entity_metadata,
recurrence_rule: recurrenceRule && Util.transformGuildScheduledEventRecurrenceRule(recurrenceRule), recurrence_rule:
recurrenceRule &&
Util.transformGuildScheduledEventRecurrenceRule(recurrenceRule),
}, },
reason, reason,
}); });
@@ -260,9 +293,12 @@ class GuildScheduledEventManager extends CachedManager {
*/ */
async delete(guildScheduledEvent) { async delete(guildScheduledEvent) {
const guildScheduledEventId = this.resolveId(guildScheduledEvent); const guildScheduledEventId = this.resolveId(guildScheduledEvent);
if (!guildScheduledEventId) throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE'); if (!guildScheduledEventId)
throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE');
await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).delete(); await this.client.api
.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId)
.delete();
} }
/** /**
@@ -291,11 +327,14 @@ class GuildScheduledEventManager extends CachedManager {
*/ */
async fetchSubscribers(guildScheduledEvent, options = {}) { async fetchSubscribers(guildScheduledEvent, options = {}) {
const guildScheduledEventId = this.resolveId(guildScheduledEvent); const guildScheduledEventId = this.resolveId(guildScheduledEvent);
if (!guildScheduledEventId) throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE'); if (!guildScheduledEventId)
throw new Error('GUILD_SCHEDULED_EVENT_RESOLVE');
let { limit, withMember, before, after } = options; let { limit, withMember, before, after } = options;
const data = await this.client.api.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId).users.get({ const data = await this.client.api
.guilds(this.guild.id, 'scheduled-events', guildScheduledEventId)
.users.get({
query: { limit, with_member: withMember, before, after }, query: { limit, with_member: withMember, before, after },
}); });
@@ -304,7 +343,9 @@ class GuildScheduledEventManager extends CachedManager {
coll.set(rawData.user.id, { coll.set(rawData.user.id, {
guildScheduledEventId: rawData.guild_scheduled_event_id, guildScheduledEventId: rawData.guild_scheduled_event_id,
user: this.client.users._add(rawData.user), user: this.client.users._add(rawData.user),
member: rawData.member ? this.guild.members._add({ ...rawData.member, user: rawData.user }) : null, member: rawData.member
? this.guild.members._add({ ...rawData.member, user: rawData.user })
: null,
}), }),
new Collection(), new Collection(),
); );

View File

@@ -66,7 +66,8 @@ class GuildStickerManager extends CachedManager {
const sticker = await this.client.api const sticker = await this.client.api
.guilds(this.guild.id) .guilds(this.guild.id)
.stickers.post({ data, files: [file], reason, dontUsePayloadJSON: true }); .stickers.post({ data, files: [file], reason, dontUsePayloadJSON: true });
return this.client.actions.GuildStickerCreate.handle(this.guild, sticker).sticker; return this.client.actions.GuildStickerCreate.handle(this.guild, sticker)
.sticker;
} }
/** /**
@@ -103,9 +104,13 @@ class GuildStickerManager extends CachedManager {
*/ */
async edit(sticker, data, reason) { async edit(sticker, data, reason) {
const stickerId = this.resolveId(sticker); const stickerId = this.resolveId(sticker);
if (!stickerId) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); if (!stickerId)
throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable');
const d = await this.client.api.guilds(this.guild.id).stickers(stickerId).patch({ const d = await this.client.api
.guilds(this.guild.id)
.stickers(stickerId)
.patch({
data, data,
reason, reason,
}); });
@@ -127,9 +132,13 @@ class GuildStickerManager extends CachedManager {
*/ */
async delete(sticker, reason) { async delete(sticker, reason) {
sticker = this.resolveId(sticker); sticker = this.resolveId(sticker);
if (!sticker) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); if (!sticker)
throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable');
await this.client.api.guilds(this.guild.id).stickers(sticker).delete({ reason }); await this.client.api
.guilds(this.guild.id)
.stickers(sticker)
.delete({ reason });
} }
/** /**
@@ -154,12 +163,17 @@ class GuildStickerManager extends CachedManager {
const existing = this.cache.get(id); const existing = this.cache.get(id);
if (existing) return existing; if (existing) return existing;
} }
const sticker = await this.client.api.guilds(this.guild.id).stickers(id).get(); const sticker = await this.client.api
.guilds(this.guild.id)
.stickers(id)
.get();
return this._add(sticker, cache); return this._add(sticker, cache);
} }
const data = await this.client.api.guilds(this.guild.id).stickers.get(); const data = await this.client.api.guilds(this.guild.id).stickers.get();
return new Collection(data.map(sticker => [sticker.id, this._add(sticker, cache)])); return new Collection(
data.map((sticker) => [sticker.id, this._add(sticker, cache)]),
);
} }
/** /**
@@ -169,8 +183,12 @@ class GuildStickerManager extends CachedManager {
*/ */
async fetchUser(sticker) { async fetchUser(sticker) {
sticker = this.resolve(sticker); sticker = this.resolve(sticker);
if (!sticker) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); if (!sticker)
const data = await this.client.api.guilds(this.guild.id).stickers(sticker.id).get(); throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable');
const data = await this.client.api
.guilds(this.guild.id)
.stickers(sticker.id)
.get();
sticker._patch(data); sticker._patch(data);
return sticker.user; return sticker.user;
} }

View File

@@ -66,26 +66,42 @@ class GuildTextThreadManager extends ThreadManager {
} = {}) { } = {}) {
let path = this.client.api.channels(this.channel.id); let path = this.client.api.channels(this.channel.id);
if (type && typeof type !== 'string' && typeof type !== 'number') { if (type && typeof type !== 'string' && typeof type !== 'number') {
throw new TypeError('INVALID_TYPE', 'type', 'ThreadChannelType or Number'); throw new TypeError(
'INVALID_TYPE',
'type',
'ThreadChannelType or Number',
);
} }
let resolvedType = let resolvedType =
this.channel.type === 'GUILD_NEWS' ? ChannelTypes.GUILD_NEWS_THREAD : ChannelTypes.GUILD_PUBLIC_THREAD; this.channel.type === 'GUILD_NEWS'
? ChannelTypes.GUILD_NEWS_THREAD
: ChannelTypes.GUILD_PUBLIC_THREAD;
if (startMessage) { if (startMessage) {
const startMessageId = this.channel.messages.resolveId(startMessage); const startMessageId = this.channel.messages.resolveId(startMessage);
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable'); if (!startMessageId)
throw new TypeError(
'INVALID_TYPE',
'startMessage',
'MessageResolvable',
);
path = path.messages(startMessageId); path = path.messages(startMessageId);
} else if (this.channel.type !== 'GUILD_NEWS') { } else if (this.channel.type !== 'GUILD_NEWS') {
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType; resolvedType =
typeof type === 'string' ? ChannelTypes[type] : (type ?? resolvedType);
} }
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild); if (autoArchiveDuration === 'MAX')
autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
const data = await path.threads.post({ const data = await path.threads.post({
data: { data: {
name, name,
auto_archive_duration: autoArchiveDuration, auto_archive_duration: autoArchiveDuration,
type: resolvedType, type: resolvedType,
invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined, invitable:
resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD
? invitable
: undefined,
rate_limit_per_user: rateLimitPerUser, rate_limit_per_user: rateLimitPerUser,
}, },
reason, reason,

View File

@@ -66,7 +66,9 @@ class MessageManager extends CachedManager {
* .catch(console.error); * .catch(console.error);
*/ */
fetch(message, { cache = true, force = false } = {}) { fetch(message, { cache = true, force = false } = {}) {
return typeof message === 'string' ? this._fetchId(message, cache, force) : this._fetchMany(message, cache); return typeof message === 'string'
? this._fetchId(message, cache, force)
: this._fetchMany(message, cache);
} }
/** /**
@@ -82,11 +84,14 @@ class MessageManager extends CachedManager {
* .catch(console.error); * .catch(console.error);
*/ */
async fetchPinned(cache = true) { async fetchPinned(cache = true) {
const data = await this.client.api.channels[this.channel.id].messages.pins.get({ const data = await this.client.api.channels[
this.channel.id
].messages.pins.get({
query: { limit: 50 }, query: { limit: 50 },
}); });
const messages = new Collection(); const messages = new Collection();
for (const message of data?.items || []) messages.set(message.id, this._add(message, cache)); for (const message of data?.items || [])
messages.set(message.id, this._add(message, cache));
return messages; return messages;
} }
@@ -123,18 +128,26 @@ class MessageManager extends CachedManager {
*/ */
async edit(message, options) { async edit(message, options) {
const messageId = this.resolveId(message); const messageId = this.resolveId(message);
if (!messageId) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!messageId)
throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
const { data, files } = await (options instanceof MessagePayload const { data, files } = await (options instanceof MessagePayload
? options ? options
: MessagePayload.create(message instanceof Message ? message : this, options) : MessagePayload.create(
message instanceof Message ? message : this,
options,
)
) )
.resolveData() .resolveData()
.resolveFiles(); .resolveFiles();
// New API // New API
const attachments = await Util.getUploadURL(this.client, this.channel.id, files); const attachments = await Util.getUploadURL(
const requestPromises = attachments.map(async attachment => { this.client,
this.channel.id,
files,
);
const requestPromises = attachments.map(async (attachment) => {
await Util.uploadFile(files[attachment.id].file, attachment.upload_url); await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
return { return {
id: attachment.id, id: attachment.id,
@@ -150,7 +163,9 @@ class MessageManager extends CachedManager {
data.attachments = attachmentsData; data.attachments = attachmentsData;
// Empty Files // Empty Files
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data }); const d = await this.client.api.channels[this.channel.id].messages[
messageId
].patch({ data });
const existing = this.cache.get(messageId); const existing = this.cache.get(messageId);
if (existing) { if (existing) {
@@ -168,9 +183,13 @@ class MessageManager extends CachedManager {
*/ */
async crosspost(message) { async crosspost(message) {
message = this.resolveId(message); message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!message)
throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
const data = await this.client.api.channels(this.channel.id).messages(message).crosspost.post(); const data = await this.client.api
.channels(this.channel.id)
.messages(message)
.crosspost.post();
return this.cache.get(data.id) ?? this._add(data); return this.cache.get(data.id) ?? this._add(data);
} }
@@ -182,9 +201,13 @@ class MessageManager extends CachedManager {
*/ */
async pin(message, reason) { async pin(message, reason) {
message = this.resolveId(message); message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!message)
throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).messages.pins(message).put({ reason }); await this.client.api
.channels(this.channel.id)
.messages.pins(message)
.put({ reason });
} }
/** /**
@@ -195,9 +218,13 @@ class MessageManager extends CachedManager {
*/ */
async unpin(message, reason) { async unpin(message, reason) {
message = this.resolveId(message); message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!message)
throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).messages.pins(message).delete({ reason }); await this.client.api
.channels(this.channel.id)
.messages.pins(message)
.delete({ reason });
} }
/** /**
@@ -209,10 +236,12 @@ class MessageManager extends CachedManager {
*/ */
async react(message, emoji, burst = false) { async react(message, emoji, burst = false) {
message = this.resolveId(message); message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!message)
throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
emoji = Util.resolvePartialEmoji(emoji); emoji = Util.resolvePartialEmoji(emoji);
if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable'); if (!emoji)
throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable');
const emojiId = emoji.id const emojiId = emoji.id
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}` ? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
@@ -237,7 +266,8 @@ class MessageManager extends CachedManager {
*/ */
async delete(message) { async delete(message) {
message = this.resolveId(message); message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!message)
throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
await this.client.api.channels(this.channel.id).messages(message).delete(); await this.client.api.channels(this.channel.id).messages(message).delete();
} }
@@ -257,8 +287,10 @@ class MessageManager extends CachedManager {
}, },
cache, cache,
) )
.then(data_ => .then((data_) =>
data_.has(messageId) ? resolve(data_.get(messageId)) : reject(new Error('MESSAGE_ID_NOT_FOUND')), data_.has(messageId)
? resolve(data_.get(messageId))
: reject(new Error('MESSAGE_ID_NOT_FOUND')),
) )
.catch(reject); .catch(reject);
}); });
@@ -296,8 +328,21 @@ class MessageManager extends CachedManager {
*/ */
async search(options = {}) { async search(options = {}) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
let { authors, content, mentions, has, maxId, minId, channels, pinned, nsfw, offset, limit, sortBy, sortOrder } = let {
Object.assign( authors,
content,
mentions,
has,
maxId,
minId,
channels,
pinned,
nsfw,
offset,
limit,
sortBy,
sortOrder,
} = Object.assign(
{ {
authors: [], authors: [],
content: '', content: '',
@@ -316,17 +361,20 @@ class MessageManager extends CachedManager {
options, options,
); );
// Validate // Validate
if (authors.length > 0) authors = authors.map(u => this.client.users.resolveId(u)); if (authors.length > 0)
if (mentions.length > 0) mentions = mentions.map(u => this.client.users.resolveId(u)); authors = authors.map((u) => this.client.users.resolveId(u));
if (mentions.length > 0)
mentions = mentions.map((u) => this.client.users.resolveId(u));
if (channels.length > 0) { if (channels.length > 0) {
channels = channels channels = channels
.map(c => this.client.channels.resolveId(c)) .map((c) => this.client.channels.resolveId(c))
.filter(id => { .filter((id) => {
if (this.channel.guildId) { if (this.channel.guildId) {
const c = this.channel.guild.channels.cache.get(id); const c = this.channel.guild.channels.cache.get(id);
if (!c || !c.messages) return false; if (!c || !c.messages) return false;
const perm = c.permissionsFor(this.client.user); const perm = c.permissionsFor(this.client.user);
if (!perm.has('READ_MESSAGE_HISTORY') || !perm.has('VIEW_CHANNEL')) return false; if (!perm.has('READ_MESSAGE_HISTORY') || !perm.has('VIEW_CHANNEL'))
return false;
return true; return true;
} else { } else {
return true; return true;
@@ -337,11 +385,18 @@ class MessageManager extends CachedManager {
let stringQuery = []; let stringQuery = [];
const result = new Collection(); const result = new Collection();
let data; let data;
if (authors.length > 0) stringQuery.push(authors.map(id => `author_id=${id}`).join('&')); if (authors.length > 0)
if (content && content.length) stringQuery.push(`content=${encodeURIComponent(content)}`); stringQuery.push(authors.map((id) => `author_id=${id}`).join('&'));
if (mentions.length > 0) stringQuery.push(mentions.map(id => `mentions=${id}`).join('&')); if (content && content.length)
has = has.filter(v => ['link', 'embed', 'file', 'video', 'image', 'sound', 'sticker'].includes(v)); stringQuery.push(`content=${encodeURIComponent(content)}`);
if (has.length > 0) stringQuery.push(has.map(v => `has=${v}`).join('&')); if (mentions.length > 0)
stringQuery.push(mentions.map((id) => `mentions=${id}`).join('&'));
has = has.filter((v) =>
['link', 'embed', 'file', 'video', 'image', 'sound', 'sticker'].includes(
v,
),
);
if (has.length > 0) stringQuery.push(has.map((v) => `has=${v}`).join('&'));
if (maxId) stringQuery.push(`max_id=${maxId}`); if (maxId) stringQuery.push(`max_id=${maxId}`);
if (minId) stringQuery.push(`min_id=${minId}`); if (minId) stringQuery.push(`min_id=${minId}`);
if (nsfw) stringQuery.push('include_nsfw=true'); if (nsfw) stringQuery.push('include_nsfw=true');
@@ -358,7 +413,7 @@ class MessageManager extends CachedManager {
stringQuery.push('sort_order=desc'); stringQuery.push('sort_order=desc');
} }
if (this.channel.guildId && channels.length > 0) { if (this.channel.guildId && channels.length > 0) {
stringQuery.push(channels.map(id => `channel_id=${id}`).join('&')); stringQuery.push(channels.map((id) => `channel_id=${id}`).join('&'));
} }
if (typeof pinned == 'boolean') stringQuery.push(`pinned=${pinned}`); if (typeof pinned == 'boolean') stringQuery.push(`pinned=${pinned}`);
// Main // Main
@@ -369,13 +424,22 @@ class MessageManager extends CachedManager {
}; };
} }
if (this.channel.guildId) { if (this.channel.guildId) {
data = await this.client.api.guilds[this.channel.guildId].messages[`search?${stringQuery.join('&')}`].get(); data =
await this.client.api.guilds[this.channel.guildId].messages[
`search?${stringQuery.join('&')}`
].get();
} else { } else {
stringQuery = stringQuery.filter(v => !v.startsWith('channel_id') && !v.startsWith('include_nsfw')); stringQuery = stringQuery.filter(
data = await this.client.api.channels[this.channel.id].messages[`search?${stringQuery.join('&')}`].get(); (v) => !v.startsWith('channel_id') && !v.startsWith('include_nsfw'),
);
data =
await this.client.api.channels[this.channel.id].messages[
`search?${stringQuery.join('&')}`
].get();
} }
for await (const message of data.messages) result.set(message[0].id, new Message(this.client, message[0])); for await (const message of data.messages)
result.set(message[0].id, new Message(this.client, message[0]));
return { return {
messages: result, messages: result,
total: data.total_results, total: data.total_results,
@@ -383,9 +447,12 @@ class MessageManager extends CachedManager {
} }
async _fetchMany(options = {}, cache) { async _fetchMany(options = {}, cache) {
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options }); const data = await this.client.api.channels[this.channel.id].messages.get({
query: options,
});
const messages = new Collection(); const messages = new Collection();
for (const message of data) messages.set(message.id, this._add(message, cache)); for (const message of data)
messages.set(message.id, this._add(message, cache));
return messages; return messages;
} }
@@ -395,7 +462,10 @@ class MessageManager extends CachedManager {
* @returns {Promise<Message>} * @returns {Promise<Message>}
*/ */
async endPoll(messageId) { async endPoll(messageId) {
const message = await this.client.api.channels(this.channel.id).polls(messageId).expire.post(); const message = await this.client.api
.channels(this.channel.id)
.polls(messageId)
.expire.post();
return this._add(message, false); return this._add(message, false);
} }
@@ -412,11 +482,18 @@ class MessageManager extends CachedManager {
* @returns {Promise<Collection<Snowflake, User>>} * @returns {Promise<Collection<Snowflake, User>>}
*/ */
async fetchPollAnswerVoters({ messageId, answerId, after, limit }) { async fetchPollAnswerVoters({ messageId, answerId, after, limit }) {
const voters = await this.client.channels(this.channel.id).polls(messageId).answers(answerId).get({ const voters = await this.client
.channels(this.channel.id)
.polls(messageId)
.answers(answerId)
.get({
query: { limit, after }, query: { limit, after },
}); });
return voters.users.reduce((acc, user) => acc.set(user.id, this.client.users._add(user, false)), new Collection()); return voters.users.reduce(
(acc, user) => acc.set(user.id, this.client.users._add(user, false)),
new Collection(),
);
} }
} }

View File

@@ -64,7 +64,12 @@ class PermissionOverwriteManager extends CachedManager {
*/ */
async set(overwrites, reason) { async set(overwrites, reason) {
if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) { if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) {
throw new TypeError('INVALID_TYPE', 'overwrites', 'Array or Collection of Permission Overwrites', true); throw new TypeError(
'INVALID_TYPE',
'overwrites',
'Array or Collection of Permission Overwrites',
true,
);
} }
return this.channel.edit({ permissionOverwrites: overwrites, reason }); return this.channel.edit({ permissionOverwrites: overwrites, reason });
} }
@@ -87,15 +92,26 @@ class PermissionOverwriteManager extends CachedManager {
* @private * @private
*/ */
async upsert(userOrRole, options, overwriteOptions = {}, existing) { async upsert(userOrRole, options, overwriteOptions = {}, existing) {
let userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole); let userOrRoleId =
this.channel.guild.roles.resolveId(userOrRole) ??
this.client.users.resolveId(userOrRole);
let { type, reason } = overwriteOptions; let { type, reason } = overwriteOptions;
if (typeof type !== 'number') { if (typeof type !== 'number') {
userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole); userOrRole =
if (!userOrRole) throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role'); this.channel.guild.roles.resolve(userOrRole) ??
type = userOrRole instanceof Role ? OverwriteTypes.role : OverwriteTypes.member; this.client.users.resolve(userOrRole);
if (!userOrRole)
throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role');
type =
userOrRole instanceof Role
? OverwriteTypes.role
: OverwriteTypes.member;
} }
const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options, existing); const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(
options,
existing,
);
await this.client.api await this.client.api
.channels(this.channel.id) .channels(this.channel.id)
@@ -141,7 +157,8 @@ class PermissionOverwriteManager extends CachedManager {
*/ */
edit(userOrRole, options, overwriteOptions) { edit(userOrRole, options, overwriteOptions) {
const existing = this.cache.get( const existing = this.cache.get(
this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole), this.channel.guild.roles.resolveId(userOrRole) ??
this.client.users.resolveId(userOrRole),
); );
return this.upsert(userOrRole, options, overwriteOptions, existing); return this.upsert(userOrRole, options, overwriteOptions, existing);
} }
@@ -153,10 +170,16 @@ class PermissionOverwriteManager extends CachedManager {
* @returns {Promise<GuildChannel>} * @returns {Promise<GuildChannel>}
*/ */
async delete(userOrRole, reason) { async delete(userOrRole, reason) {
const userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole); const userOrRoleId =
if (!userOrRoleId) throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role'); this.channel.guild.roles.resolveId(userOrRole) ??
this.client.users.resolveId(userOrRole);
if (!userOrRoleId)
throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role');
await this.client.api.channels(this.channel.id).permissions(userOrRoleId).delete({ reason }); await this.client.api
.channels(this.channel.id)
.permissions(userOrRoleId)
.delete({ reason });
return this.channel; return this.channel;
} }
} }

View File

@@ -61,7 +61,7 @@ class PresenceManager extends CachedManager {
async fetch() { async fetch() {
const data = await this.client.api.presences.get(); const data = await this.client.api.presences.get();
// https://docs.discord.food/resources/presence#endpoints // https://docs.discord.food/resources/presence#endpoints
data.presences.forEach(presence => { data.presences.forEach((presence) => {
this._add(presence, true); this._add(presence, true);
}); });
return this.cache; return this.cache;

View File

@@ -19,7 +19,10 @@ class ReactionManager extends CachedManager {
} }
_add(data, cache) { _add(data, cache) {
return super._add(data, cache, { id: data.emoji.id ?? data.emoji.name, extras: [this.message] }); return super._add(data, cache, {
id: data.emoji.id ?? data.emoji.name,
extras: [this.message],
});
} }
/** /**
@@ -59,7 +62,10 @@ class ReactionManager extends CachedManager {
* @returns {Promise<Message>} * @returns {Promise<Message>}
*/ */
async removeAll() { async removeAll() {
await this.client.api.channels(this.message.channelId).messages(this.message.id).reactions.delete(); await this.client.api
.channels(this.message.channelId)
.messages(this.message.id)
.reactions.delete();
return this.message; return this.message;
} }
} }

View File

@@ -42,9 +42,15 @@ class ReactionUserManager extends CachedManager {
*/ */
async fetch({ limit = 100, after, type = 'NORMAL' } = {}) { async fetch({ limit = 100, after, type = 'NORMAL' } = {}) {
const message = this.reaction.message; const message = this.reaction.message;
const data = await this.client.api.channels[message.channelId].messages[message.id].reactions[ const data = await this.client.api.channels[message.channelId].messages[
this.reaction.emoji.identifier message.id
].get({ query: { limit, after, type: typeof type == 'number' ? type : ReactionTypes[type] } }); ].reactions[this.reaction.emoji.identifier].get({
query: {
limit,
after,
type: typeof type == 'number' ? type : ReactionTypes[type],
},
});
const users = new Collection(); const users = new Collection();
for (const rawUser of data) { for (const rawUser of data) {
const user = this.client.users._add(rawUser); const user = this.client.users._add(rawUser);
@@ -63,7 +69,9 @@ class ReactionUserManager extends CachedManager {
const userId = this.client.users.resolveId(user); const userId = this.client.users.resolveId(user);
if (!userId) throw new Error('REACTION_RESOLVE_USER'); if (!userId) throw new Error('REACTION_RESOLVE_USER');
const message = this.reaction.message; const message = this.reaction.message;
await this.client.api.channels[message.channelId].messages[message.id].reactions[this.reaction.emoji.identifier][ await this.client.api.channels[message.channelId].messages[
message.id
].reactions[this.reaction.emoji.identifier][
userId === this.client.user.id ? '@me' : userId userId === this.client.user.id ? '@me' : userId
].delete(); ].delete();
return this.reaction; return this.reaction;

View File

@@ -37,7 +37,7 @@ class RelationshipManager extends BaseManager {
*/ */
get friendCache() { get friendCache() {
const users = this.cache const users = this.cache
.filter(value => value === RelationshipTypes.FRIEND) .filter((value) => value === RelationshipTypes.FRIEND)
.map((_, key) => [key, this.client.users.cache.get(key)]); .map((_, key) => [key, this.client.users.cache.get(key)]);
return new Collection(users); return new Collection(users);
} }
@@ -49,7 +49,7 @@ class RelationshipManager extends BaseManager {
*/ */
get blockedCache() { get blockedCache() {
const users = this.cache const users = this.cache
.filter(value => value === RelationshipTypes.BLOCKED) .filter((value) => value === RelationshipTypes.BLOCKED)
.map((_, key) => [key, this.client.users.cache.get(key)]); .map((_, key) => [key, this.client.users.cache.get(key)]);
return new Collection(users); return new Collection(users);
} }
@@ -61,7 +61,7 @@ class RelationshipManager extends BaseManager {
*/ */
get incomingCache() { get incomingCache() {
const users = this.cache const users = this.cache
.filter(value => value === RelationshipTypes.PENDING_INCOMING) .filter((value) => value === RelationshipTypes.PENDING_INCOMING)
.map((_, key) => [key, this.client.users.cache.get(key)]); .map((_, key) => [key, this.client.users.cache.get(key)]);
return new Collection(users); return new Collection(users);
} }
@@ -73,7 +73,7 @@ class RelationshipManager extends BaseManager {
*/ */
get outgoingCache() { get outgoingCache() {
const users = this.cache const users = this.cache
.filter(value => value === RelationshipTypes.PENDING_OUTGOING) .filter((value) => value === RelationshipTypes.PENDING_OUTGOING)
.map((_, key) => [key, this.client.users.cache.get(key)]); .map((_, key) => [key, this.client.users.cache.get(key)]);
return new Collection(users); return new Collection(users);
} }
@@ -172,9 +172,11 @@ class RelationshipManager extends BaseManager {
// eslint-disable-next-line no-unreachable // eslint-disable-next-line no-unreachable
const id = this.resolveId(user); const id = this.resolveId(user);
if ( if (
![RelationshipTypes.FRIEND, RelationshipTypes.BLOCKED, RelationshipTypes.PENDING_OUTGOING].includes( ![
this.cache.get(id), RelationshipTypes.FRIEND,
) RelationshipTypes.BLOCKED,
RelationshipTypes.PENDING_OUTGOING,
].includes(this.cache.get(id))
) { ) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@@ -222,9 +224,11 @@ class RelationshipManager extends BaseManager {
// eslint-disable-next-line no-unreachable // eslint-disable-next-line no-unreachable
const id = this.resolveId(user); const id = this.resolveId(user);
// Check if already friends // Check if already friends
if (this.cache.get(id) === RelationshipTypes.FRIEND) return Promise.resolve(false); if (this.cache.get(id) === RelationshipTypes.FRIEND)
return Promise.resolve(false);
// Check if outgoing request // Check if outgoing request
if (this.cache.get(id) === RelationshipTypes.PENDING_OUTGOING) return Promise.resolve(false); if (this.cache.get(id) === RelationshipTypes.PENDING_OUTGOING)
return Promise.resolve(false);
await this.client.api.users['@me'].relationships[id].put({ await this.client.api.users['@me'].relationships[id].put({
data: { confirm_stranger_request: true }, data: { confirm_stranger_request: true },
DiscordContext: { location: 'Friends' }, DiscordContext: { location: 'Friends' },
@@ -240,7 +244,8 @@ class RelationshipManager extends BaseManager {
*/ */
async setNickname(user, nickname = null) { async setNickname(user, nickname = null) {
const id = this.resolveId(user); const id = this.resolveId(user);
if (this.cache.get(id) !== RelationshipTypes.FRIEND) return Promise.resolve(false); if (this.cache.get(id) !== RelationshipTypes.FRIEND)
return Promise.resolve(false);
await this.client.api.users['@me'].relationships[id].patch({ await this.client.api.users['@me'].relationships[id].patch({
data: { data: {
nickname: typeof nickname === 'string' ? nickname : null, nickname: typeof nickname === 'string' ? nickname : null,
@@ -264,7 +269,8 @@ class RelationshipManager extends BaseManager {
// eslint-disable-next-line no-unreachable // eslint-disable-next-line no-unreachable
const id = this.resolveId(user); const id = this.resolveId(user);
// Check // Check
if (this.cache.get(id) === RelationshipTypes.BLOCKED) return Promise.resolve(false); if (this.cache.get(id) === RelationshipTypes.BLOCKED)
return Promise.resolve(false);
await this.client.api.users['@me'].relationships[id].put({ await this.client.api.users['@me'].relationships[id].put({
data: { data: {
type: RelationshipTypes.BLOCKED, type: RelationshipTypes.BLOCKED,

View File

@@ -72,7 +72,7 @@ class RoleManager extends CachedManager {
const data = await this.client.api.guilds(this.guild.id).roles.get(); const data = await this.client.api.guilds(this.guild.id).roles.get();
const roles = new Collection(); const roles = new Collection();
for (const role of data) roles.set(role.id, this._add(role, cache)); for (const role of data) roles.set(role.id, this._add(role, cache));
return id ? roles.get(id) ?? null : roles; return id ? (roles.get(id) ?? null) : roles;
} }
/** /**
@@ -80,7 +80,10 @@ class RoleManager extends CachedManager {
* @returns {Promise<Record<Snowflake, number>>} * @returns {Promise<Record<Snowflake, number>>}
*/ */
async fetchMemberCounts() { async fetchMemberCounts() {
const data = await this.client.api.guilds(this.guild.id).roles('member-counts').get(); const data = await this.client.api
.guilds(this.guild.id)
.roles('member-counts')
.get();
return data; return data;
} }
@@ -95,7 +98,10 @@ class RoleManager extends CachedManager {
const id = this.resolveId(role); const id = this.resolveId(role);
if (!id) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable'); if (!id) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');
const data = await this.client.api.guilds(this.guild.id).roles(id, 'member-ids').get(); const data = await this.client.api
.guilds(this.guild.id)
.roles(id, 'member-ids')
.get();
return data; return data;
} }
@@ -191,24 +197,34 @@ class RoleManager extends CachedManager {
*/ */
async create(options = {}) { async create(options = {}) {
let { permissions, icon } = options; let { permissions, icon } = options;
const { name, color, hoist, position, mentionable, reason, unicodeEmoji } = options; const { name, color, hoist, position, mentionable, reason, unicodeEmoji } =
options;
if (typeof permissions !== 'undefined') permissions = new Permissions(permissions); if (typeof permissions !== 'undefined')
permissions = new Permissions(permissions);
if (icon) { if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.url; const guildEmojiURL = this.guild.emojis.resolve(icon)?.url;
icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon); icon = guildEmojiURL
? await DataResolver.resolveImage(guildEmojiURL)
: await DataResolver.resolveImage(icon);
if (typeof icon !== 'string') icon = undefined; if (typeof icon !== 'string') icon = undefined;
} }
let colors = options.colors && { let colors = options.colors && {
primary_color: resolveColor(options.colors.primaryColor), primary_color: resolveColor(options.colors.primaryColor),
secondary_color: options.colors.secondaryColor && resolveColor(options.colors.secondaryColor), secondary_color:
tertiary_color: options.colors.tertiaryColor && resolveColor(options.colors.tertiaryColor), options.colors.secondaryColor &&
resolveColor(options.colors.secondaryColor),
tertiary_color:
options.colors.tertiaryColor &&
resolveColor(options.colors.tertiaryColor),
}; };
if (color !== undefined) { if (color !== undefined) {
if (!deprecationEmittedForCreate) { if (!deprecationEmittedForCreate) {
process.emitWarning(`Passing "color" to RoleManager#create() is deprecated. Use "colors" instead.`); process.emitWarning(
`Passing "color" to RoleManager#create() is deprecated. Use "colors" instead.`,
);
} }
deprecationEmittedForCreate = true; deprecationEmittedForCreate = true;
@@ -256,24 +272,31 @@ class RoleManager extends CachedManager {
role = this.resolve(role); role = this.resolve(role);
if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable'); if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');
if (typeof data.position === 'number') await this.setPosition(role, data.position, { reason }); if (typeof data.position === 'number')
await this.setPosition(role, data.position, { reason });
let icon = data.icon; let icon = data.icon;
if (icon) { if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.url; const guildEmojiURL = this.guild.emojis.resolve(icon)?.url;
icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon); icon = guildEmojiURL
? await DataResolver.resolveImage(guildEmojiURL)
: await DataResolver.resolveImage(icon);
if (typeof icon !== 'string') icon = undefined; if (typeof icon !== 'string') icon = undefined;
} }
let colors = data.colors && { let colors = data.colors && {
primary_color: resolveColor(data.colors.primaryColor), primary_color: resolveColor(data.colors.primaryColor),
secondary_color: data.colors.secondaryColor && resolveColor(data.colors.secondaryColor), secondary_color:
tertiary_color: data.colors.tertiaryColor && resolveColor(data.colors.tertiaryColor), data.colors.secondaryColor && resolveColor(data.colors.secondaryColor),
tertiary_color:
data.colors.tertiaryColor && resolveColor(data.colors.tertiaryColor),
}; };
if (data.color !== undefined) { if (data.color !== undefined) {
if (!deprecationEmittedForEdit) { if (!deprecationEmittedForEdit) {
process.emitWarning(`Passing "color" to RoleManager#edit() is deprecated. Use "colors" instead.`); process.emitWarning(
`Passing "color" to RoleManager#edit() is deprecated. Use "colors" instead.`,
);
} }
deprecationEmittedForEdit = true; deprecationEmittedForEdit = true;
@@ -289,13 +312,19 @@ class RoleManager extends CachedManager {
name: data.name, name: data.name,
colors, colors,
hoist: data.hoist, hoist: data.hoist,
permissions: typeof data.permissions === 'undefined' ? undefined : new Permissions(data.permissions), permissions:
typeof data.permissions === 'undefined'
? undefined
: new Permissions(data.permissions),
mentionable: data.mentionable, mentionable: data.mentionable,
icon, icon,
unicode_emoji: data.unicodeEmoji, unicode_emoji: data.unicodeEmoji,
}; };
const d = await this.client.api.guilds(this.guild.id).roles(role.id).patch({ data: _data, reason }); const d = await this.client.api
.guilds(this.guild.id)
.roles(role.id)
.patch({ data: _data, reason });
const clone = role._clone(); const clone = role._clone();
clone._patch(d); clone._patch(d);
@@ -316,7 +345,10 @@ class RoleManager extends CachedManager {
async delete(role, reason) { async delete(role, reason) {
const id = this.resolveId(role); 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 }); this.client.actions.GuildRoleDelete.handle({
guild_id: this.guild.id,
role_id: id,
});
} }
/** /**
@@ -368,7 +400,7 @@ class RoleManager extends CachedManager {
*/ */
async setPositions(rolePositions) { async setPositions(rolePositions) {
// Make sure rolePositions are prepared for API // Make sure rolePositions are prepared for API
rolePositions = rolePositions.map(o => ({ rolePositions = rolePositions.map((o) => ({
id: this.resolveId(o.role), id: this.resolveId(o.role),
position: o.position, position: o.position,
})); }));
@@ -393,7 +425,8 @@ class RoleManager extends CachedManager {
comparePositions(role1, role2) { comparePositions(role1, role2) {
const resolvedRole1 = this.resolve(role1); const resolvedRole1 = this.resolve(role1);
const resolvedRole2 = this.resolve(role2); const resolvedRole2 = this.resolve(role2);
if (!resolvedRole1 || !resolvedRole2) throw new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'); if (!resolvedRole1 || !resolvedRole2)
throw new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake');
const role1Position = resolvedRole1.position; const role1Position = resolvedRole1.position;
const role2Position = resolvedRole2.position; const role2Position = resolvedRole2.position;
@@ -414,7 +447,7 @@ class RoleManager extends CachedManager {
botRoleFor(user) { botRoleFor(user) {
const userId = this.client.users.resolveId(user); const userId = this.client.users.resolveId(user);
if (!userId) return null; if (!userId) return null;
return this.cache.find(role => role.tags?.botId === userId) ?? null; return this.cache.find((role) => role.tags?.botId === userId) ?? null;
} }
/** /**
@@ -432,7 +465,7 @@ class RoleManager extends CachedManager {
* @readonly * @readonly
*/ */
get premiumSubscriberRole() { get premiumSubscriberRole() {
return this.cache.find(role => role.tags?.premiumSubscriberRole) ?? null; return this.cache.find((role) => role.tags?.premiumSubscriberRole) ?? null;
} }
/** /**
@@ -441,7 +474,10 @@ class RoleManager extends CachedManager {
* @readonly * @readonly
*/ */
get highest() { get highest() {
return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first()); return this.cache.reduce(
(prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev),
this.cache.first(),
);
} }
} }

View File

@@ -28,7 +28,7 @@ class SessionManager extends CachedManager {
* @returns {Promise<Collection<string, Session>>} * @returns {Promise<Collection<string, Session>>}
*/ */
fetch() { fetch() {
return this.client.api.auth.sessions.get().then(data => { return this.client.api.auth.sessions.get().then((data) => {
const allData = data.user_sessions; const allData = data.user_sessions;
this.cache.clear(); this.cache.clear();
for (const session of allData) { for (const session of allData) {
@@ -45,7 +45,7 @@ class SessionManager extends CachedManager {
logoutAllDevices() { logoutAllDevices() {
return this.client.api.auth.sessions.logout({ return this.client.api.auth.sessions.logout({
data: { data: {
session_id_hashes: this.cache.map(session => session.id), session_id_hashes: this.cache.map((session) => session.id),
}, },
}); });
} }

View File

@@ -60,11 +60,17 @@ class StageInstanceManager extends CachedManager {
async create(channel, options) { async create(channel, options) {
const channelId = this.guild.channels.resolveId(channel); const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new Error('STAGE_CHANNEL_RESOLVE'); if (!channelId) throw new Error('STAGE_CHANNEL_RESOLVE');
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); if (typeof options !== 'object')
let { guildScheduledEvent, topic, privacyLevel, sendStartNotification } = options; throw new TypeError('INVALID_TYPE', 'options', 'object', true);
let { guildScheduledEvent, topic, privacyLevel, sendStartNotification } =
options;
privacyLevel &&= typeof privacyLevel === 'number' ? privacyLevel : PrivacyLevels[privacyLevel]; privacyLevel &&=
const guildScheduledEventId = guildScheduledEvent && this.resolveId(guildScheduledEvent); typeof privacyLevel === 'number'
? privacyLevel
: PrivacyLevels[privacyLevel];
const guildScheduledEventId =
guildScheduledEvent && this.resolveId(guildScheduledEvent);
const data = await this.client.api['stage-instances'].post({ const data = await this.client.api['stage-instances'].post({
data: { data: {
@@ -95,7 +101,9 @@ class StageInstanceManager extends CachedManager {
if (!channelId) throw new Error('STAGE_CHANNEL_RESOLVE'); if (!channelId) throw new Error('STAGE_CHANNEL_RESOLVE');
if (!force) { if (!force) {
const existing = this.cache.find(stageInstance => stageInstance.channelId === channelId); const existing = this.cache.find(
(stageInstance) => stageInstance.channelId === channelId,
);
if (existing) return existing; if (existing) return existing;
} }
@@ -122,13 +130,17 @@ class StageInstanceManager extends CachedManager {
* .catch(console.error); * .catch(console.error);
*/ */
async edit(channel, options) { async edit(channel, options) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); if (typeof options !== 'object')
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
const channelId = this.guild.channels.resolveId(channel); const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new Error('STAGE_CHANNEL_RESOLVE'); if (!channelId) throw new Error('STAGE_CHANNEL_RESOLVE');
let { topic, privacyLevel } = options; let { topic, privacyLevel } = options;
privacyLevel &&= typeof privacyLevel === 'number' ? privacyLevel : PrivacyLevels[privacyLevel]; privacyLevel &&=
typeof privacyLevel === 'number'
? privacyLevel
: PrivacyLevels[privacyLevel];
const data = await this.client.api('stage-instances', channelId).patch({ const data = await this.client.api('stage-instances', channelId).patch({
data: { data: {

View File

@@ -139,7 +139,9 @@ class ThreadManager extends CachedManager {
* @returns {Promise<FetchedThreads>} * @returns {Promise<FetchedThreads>}
*/ */
async fetchActive(cache = true, options = {}) { async fetchActive(cache = true, options = {}) {
const raw = await this.client.api.channels(this.channel.id).threads.search.get({ const raw = await this.client.api
.channels(this.channel.id)
.threads.search.get({
query: { query: {
archived: options?.archived ?? false, archived: options?.archived ?? false,
limit: options?.limit ?? 25, limit: options?.limit ?? 25,
@@ -149,17 +151,23 @@ class ThreadManager extends CachedManager {
}, },
}); });
return this.constructor._mapThreads(raw, this.client, { parent: this.channel, cache }); return this.constructor._mapThreads(raw, this.client, {
parent: this.channel,
cache,
});
} }
static _mapThreads(rawThreads, client, { parent, guild, cache }) { static _mapThreads(rawThreads, client, { parent, guild, cache }) {
const threads = rawThreads.threads.reduce((coll, raw) => { const threads = rawThreads.threads.reduce((coll, raw) => {
const thread = client.channels._add(raw, guild ?? parent?.guild, { cache }); const thread = client.channels._add(raw, guild ?? parent?.guild, {
cache,
});
if (parent && thread.parentId !== parent.id) return coll; if (parent && thread.parentId !== parent.id) return coll;
return coll.set(thread.id, thread); return coll.set(thread.id, thread);
}, new Collection()); }, new Collection());
// Discord sends the thread id as id in this object // Discord sends the thread id as id in this object
for (const rawMember of rawThreads.members) client.channels.cache.get(rawMember.id)?.members._add(rawMember); for (const rawMember of rawThreads.members)
client.channels.cache.get(rawMember.id)?.members._add(rawMember);
// Patch firstMessage // Patch firstMessage
// According to https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/1502, rawThreads.first_messages could be null. // According to https://github.com/aiko-chan-ai/discord.js-selfbot-v13/issues/1502, rawThreads.first_messages could be null.
for (const rawMessage of rawThreads?.first_messages || []) { for (const rawMessage of rawThreads?.first_messages || []) {

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