From 756a2410519b358a5289a31ea4d3e0c618cea53a Mon Sep 17 00:00:00 2001 From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:26:39 +0700 Subject: [PATCH] fix: #523 i hate discord :sob: --- package.json | 10 +++++----- src/client/Client.js | 5 ++++- src/rest/APIRequest.js | 31 +++++++++++-------------------- src/rest/RequestHandler.js | 3 ++- src/sharding/ShardingManager.js | 2 +- src/util/Constants.js | 12 ++---------- src/util/DataResolver.js | 2 +- src/util/Options.js | 22 +++++++++------------- src/util/RemoteAuth.js | 4 ++-- src/util/Util.js | 33 ++------------------------------- typings/index.d.ts | 9 ++------- 11 files changed, 41 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index 7a57721..7aef536 100644 --- a/package.json +++ b/package.json @@ -61,10 +61,10 @@ "@types/ws": "^8.5.4", "axios": "1.1", "chalk": "^4.1.2", - "discord-api-types": "^0.37.29", + "discord-api-types": "^0.37.31", "form-data": "^4.0.0", "json-bigint": "^1.0.0", - "node-fetch": "^2.6.1", + "node-fetch": "^3.3.0", "safe-base64": "^2.0.1-0", "string_decoder": "^1.3.0", "string-similarity": "^4.0.4", @@ -82,7 +82,7 @@ "@types/node": "^16.11.12", "conventional-changelog-cli": "^2.2.2", "dtslint": "^4.2.1", - "eslint": "^8.32.0", + "eslint": "^8.33.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^4.2.1", @@ -90,9 +90,9 @@ "is-ci": "^3.0.1", "jest": "^28.1.3", "lint-staged": "^12.1.4", - "prettier": "^2.8.3", + "prettier": "^2.8.4", "tsd": "^0.25.0", "tslint": "^6.1.3", - "typescript": "^4.9.4" + "typescript": "^4.9.5" } } diff --git a/src/client/Client.js b/src/client/Client.js index 821acc9..ca3159c 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -619,7 +619,6 @@ class Client extends BaseClient { /** * Join this Guild using this invite (Use with caution) * @param {InviteResolvable} invite Invite code or URL - * @deprecated * @returns {Promise} */ async acceptInvite(invite) { @@ -629,6 +628,9 @@ class Client extends BaseClient { await invite.acceptInvite(); } else { await this.api.invites(code).post({ + headers: { + 'X-Context-Properties': 'eyJsb2NhdGlvbiI6Ik1hcmtkb3duIExpbmsifQ==', // Markdown Link + }, data: {}, }); } @@ -661,6 +663,7 @@ class Client extends BaseClient { if (!nitroArray) return false; const codeArray = nitroArray.map(code => code.replace(regex.url, '')); let redeem = false; + this.emit('debug', `${chalk.greenBright('[Nitro]')} Redeem Nitro: ${nitroArray.join(', ')}`); for await (const code of codeArray) { if (this.usedCodes.indexOf(code) > -1) continue; await this.api.entitlements['gift-codes'](code) diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index a07ed59..e577289 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -4,7 +4,7 @@ const Buffer = require('node:buffer').Buffer; const https = require('node:https'); const { setTimeout } = require('node:timers'); const FormData = require('form-data'); -const fetch = require('node-fetch'); +const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)); let agent = null; @@ -43,29 +43,21 @@ class APIRequest { : `${this.client.options.http.api}/v${this.client.options.http.version}`; const url = API + this.path; - const chromeVersion = this.client.options.ws.properties.browser_version.split('.')[0]; - let headers = { ...this.client.options.http.headers, Accept: '*/*', - 'Accept-Language': 'en-US,en;q=0.9', - 'Sec-Ch-Ua': `"Not?A_Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`, - 'Sec-Ch-Ua-Mobile': '?0', - 'Sec-Ch-Ua-Platform': 'Windows', + 'Accept-Language': 'en-US', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'X-Debug-Options': 'bugReporterEnabled', 'X-Super-Properties': `${Buffer.from( - this.client.options.jsonTransformer({ - ...this.client.options.ws.properties, - browser_user_agent: this.client.options.http.headers['User-Agent'], - }), + this.client.options.jsonTransformer(this.client.options.ws.properties), 'ascii', ).toString('base64')}`, 'X-Discord-Locale': 'en-US', 'User-Agent': this.client.options.http.headers['User-Agent'], - Origin: 'https://discord.com', + Referer: 'https://discord.com/channels/@me', Connection: 'keep-alive', }; @@ -100,23 +92,22 @@ class APIRequest { headers = Object.assign(headers, body.getHeaders()); // eslint-disable-next-line eqeqeq } else if (this.options.data != null) { - body = this.options.data ? JSON.stringify(this.options.data) : undefined; headers['Content-Type'] = 'application/json'; + if (captchaKey && typeof captchaKey == 'string') { + if (!this.options.data) this.options.data = {}; + this.options.data.captcha_key = captchaKey; + if (captchaRqtoken) this.options.data.captcha_rqtoken = captchaRqtoken; + } + body = this.options.data ? JSON.stringify(this.options.data) : undefined; } else if (this.options.body != null) { body = new FormData(); body.append('payload_json', JSON.stringify(this.options.body)); headers = Object.assign(headers, body.getHeaders()); } - if (headers['Content-Type'] === 'application/json' && captchaKey && typeof captchaKey == 'string') { - body = JSON.parse(body || '{}'); - body.captcha_key = captchaKey; - if (captchaRqtoken) body.captcha_rqtoken = captchaRqtoken; - body = JSON.stringify(body); - } - const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), this.client.options.restRequestTimeout).unref(); + return fetch(url, { method: this.method, headers, diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index d9d3e3d..1040c77 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -411,7 +411,8 @@ class RequestHandler { Method : ${request.method} Path : ${request.path} Route : ${request.route} - Key : ${captcha}`, + Key : ${captcha} + rqToken : ${data.captcha_rqtoken}`, ); request.retries++; return this.execute(request, captcha, data.captcha_rqtoken); diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index db8581c..2649b77 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -183,7 +183,7 @@ class ShardingManager extends EventEmitter { async spawn({ amount = this.totalShards, delay = 5500, timeout = 30_000 } = {}) { // Obtain/verify the number of shards to spawn if (amount === 'auto') { - amount = await Util.fetchRecommendedShards(this.token); + amount = 1; } else { if (typeof amount !== 'number' || isNaN(amount)) { throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.'); diff --git a/src/util/Constants.js b/src/util/Constants.js index e3997e0..e87a7db 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -5,14 +5,8 @@ const Package = (exports.Package = require('../../package.json')); */ const { Error, RangeError, TypeError } = require('../errors'); // #88: https://jnrbsn.github.io/user-agents/user-agents.json -const listUserAgent = [ - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.69', -]; +exports.defaultUA = + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9010 Chrome/91.0.4472.164 Electron/13.6.6 Safari/537.36'; /** * Max bulk deletable message age @@ -135,8 +129,6 @@ exports.localeSetting = { ko: 'KOREAN', }; -exports.randomUA = () => listUserAgent[Math.floor(Math.random() * listUserAgent.length)]; - /** * The types of WebSocket error codes: * * 1000: WS_CLOSE_REQUESTED diff --git a/src/util/DataResolver.js b/src/util/DataResolver.js index 1ea8aa6..63fa6f2 100644 --- a/src/util/DataResolver.js +++ b/src/util/DataResolver.js @@ -4,7 +4,7 @@ const { Buffer } = require('node:buffer'); const fs = require('node:fs'); const path = require('node:path'); const stream = require('node:stream'); -const fetch = require('node-fetch'); +const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)); const { Error: DiscordError, TypeError } = require('../errors'); const Invite = require('../structures/Invite'); diff --git a/src/util/Options.js b/src/util/Options.js index 5c339ac..a848fa1 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -2,7 +2,7 @@ const JSONBig = require('json-bigint'); const Intents = require('./Intents'); -const { randomUA } = require('../util/Constants'); +const { defaultUA } = require('../util/Constants'); /** * Rate limit data * @typedef {Object} RateLimitData @@ -191,20 +191,16 @@ class Options extends null { proxy: '', ws: { compress: false, - // https://discord-user-api.cf/api/v1/properties/web properties: { os: 'Windows', - browser: 'Chrome', - device: '', - system_locale: 'en-US', - browser_version: '109.0.0.0', - os_version: '10', - referrer: '', - referring_domain: '', - referrer_current: '', - referring_domain_current: '', + browser: 'Discord Client', release_channel: 'stable', - client_build_number: 169617, + client_version: '1.0.9010', + os_version: '10.0.22621', + os_arch: 'x64', + system_locale: 'en-US', + client_build_number: 172394, + native_build_number: 29128, client_event_source: null, }, // ! capabilities: 4093, @@ -222,7 +218,7 @@ class Options extends null { http: { agent: {}, headers: { - 'User-Agent': randomUA(), + 'User-Agent': defaultUA, }, version: 9, api: 'https://discord.com/api', diff --git a/src/util/RemoteAuth.js b/src/util/RemoteAuth.js index 4391979..9c9f290 100644 --- a/src/util/RemoteAuth.js +++ b/src/util/RemoteAuth.js @@ -8,7 +8,7 @@ const axios = require('axios'); const chalk = require('chalk'); const { encode: urlsafe_b64encode } = require('safe-base64'); const WebSocket = require('ws'); -const { randomUA } = require('./Constants'); +const { defaultUA } = require('./Constants'); const Options = require('./Options'); const defaultClientOptions = Options.createDefault(); @@ -153,7 +153,7 @@ new DiscordAuthWebsocket({ failIfError: true, generateQR: true, apiVersion: 9, - userAgent: randomUA(), + userAgent: defaultUA, wsProperties: defaultClientOptions.ws.properties, }; if (typeof options == 'object') { diff --git a/src/util/Util.js b/src/util/Util.js index 8acc1e9..e14f333 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -4,10 +4,8 @@ const { parse } = require('node:path'); const process = require('node:process'); const { Collection } = require('@discordjs/collection'); const axios = require('axios'); -const fetch = require('node-fetch'); -const { Colors, Endpoints } = require('./Constants'); -const Options = require('./Options'); -const { Error: DiscordError, RangeError, TypeError } = require('../errors'); +const { Colors } = require('./Constants'); +const { RangeError, TypeError } = require('../errors'); const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); const isObject = d => typeof d === 'object' && d !== null; @@ -332,33 +330,6 @@ class Util extends null { return text.replaceAll(/\[.+\]\(.+\)/gm, '\\$&'); } - /** - * @typedef {Object} FetchRecommendedShardsOptions - * @property {number} [guildsPerShard=1000] Number of guilds assigned per shard - * @property {number} [multipleOf=1] The multiple the shard count should round up to. (16 for large bot sharding) - */ - - /** - * Gets the recommended shard count from Discord. - * @param {string} token Discord auth token - * @param {FetchRecommendedShardsOptions} [options] Options for fetching the recommended shard count - * @returns {Promise} The recommended number of shards - */ - static async fetchRecommendedShards(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) { - if (!token) throw new DiscordError('TOKEN_MISSING'); - const defaults = Options.createDefault(); - const response = await fetch(`${defaults.http.api}/v${defaults.http.version}${Endpoints.botGateway}`, { - method: 'GET', - headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` }, - }); - if (!response.ok) { - if (response.status === 401) throw new DiscordError('TOKEN_INVALID'); - throw response; - } - const { shards } = await response.json(); - return Math.ceil((shards * (1_000 / guildsPerShard)) / multipleOf) * multipleOf; - } - /** * Parses emoji info out of a string. The string must be one of: * * A UTF-8 emoji (no id) diff --git a/typings/index.d.ts b/typings/index.d.ts index f5859b8..6fc3499 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -922,6 +922,7 @@ export class Client extends BaseClient { public logout(): Promise; public fetchGuildPreview(guild: GuildResolvable): Promise; public fetchInvite(invite: InviteResolvable, options?: ClientFetchInviteOptions): Promise; + public acceptInvite(invite: InviteResolvable): Promise; public fetchGuildTemplate(template: GuildTemplateResolvable): Promise; public fetchVoiceRegions(): Promise>; public fetchSticker(id: Snowflake): Promise; @@ -2770,11 +2771,6 @@ export class ShardingManager extends EventEmitter { public once(event: 'shardCreate', listener: (shard: Shard) => Awaitable): this; } -export interface FetchRecommendedShardsOptions { - guildsPerShard?: number; - multipleOf?: number; -} - export class SnowflakeUtil extends null { private constructor(); public static deconstruct(snowflake: Snowflake): DeconstructedSnowflake; @@ -3187,7 +3183,6 @@ export class Util extends null { public static escapeNumberedList(text: string): string; public static escapeMaskedLink(text: string): string; public static cleanCodeBlockContent(text: string): string; - public static fetchRecommendedShards(token: string, options?: FetchRecommendedShardsOptions): Promise; public static flatten(obj: unknown, ...props: Record[]): unknown; public static makeError(obj: MakeErrorOptions): Error; public static makePlainError(err: Error): MakeErrorOptions; @@ -3617,7 +3612,7 @@ export const Constants: { VideoQualityModes: EnumHolder; SweeperKeys: SweeperKey[]; // Add - randomUA: () => string; + defaultUA: string; captchaServices: captchaServices[]; DMScanLevel: EnumHolder; stickerAnimationMode: EnumHolder;