From affcf5e1666acfb82febe80ebea77eb004522143 Mon Sep 17 00:00:00 2001 From: March 7th <71698422+aiko-chan-ai@users.noreply.github.com> Date: Tue, 27 Dec 2022 17:27:34 +0700 Subject: [PATCH] feat: Update (see description) ```diff + fix: Discord Ban (invalid headers) + fix: acceptInvite not working (invalid captcha data) + feat: automod update - feat: remove `nopecha` - feat: remove Client#updateCookie & ClientOptions#autoCookie ``` --- README.md | 1 - src/client/Client.js | 81 ++++++----------------- src/managers/AutoModerationRuleManager.js | 36 +++++----- src/rest/APIRequest.js | 18 +++-- src/rest/CaptchaSolver.js | 36 +++------- src/rest/RequestHandler.js | 38 ++++++++++- src/structures/Invite.js | 5 +- src/structures/Webhook.js | 8 ++- src/util/Constants.js | 10 +-- src/util/Options.js | 2 - src/util/RemoteAuth.js | 5 +- typings/index.d.ts | 4 +- 12 files changed, 118 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index aae814a..210f579 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ ### Optional packages - [2captcha](https://www.npmjs.com/package/2captcha) for solving captcha (`npm install 2captcha`) -- [nopecha](https://www.npmjs.com/package/nopecha) for solving captcha (`npm install nopecha`) ## Installation diff --git a/src/client/Client.js b/src/client/Client.js index c81c81b..0f1cdb7 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -312,48 +312,6 @@ class Client extends BaseClient { return getVoiceConnection(null); } - /** - * Update Cloudflare Cookie and Discord Fingerprint - */ - async updateCookie() { - /* Auto find fingerprint and add Cookie */ - let cookie = ''; - await require('axios')({ - method: 'get', - url: 'https://discord.com/api/v9/experiments', - headers: this.options.http.headers, - }) - .then(res => { - if (!('set-cookie' in res.headers)) return; - res.headers['set-cookie'].map(line => { - line.split('; ').map(arr => { - if ( - arr.startsWith('Expires') || - arr.startsWith('Path') || - arr.startsWith('Domain') || - arr.startsWith('HttpOnly') || - arr.startsWith('Secure') || - arr.startsWith('Max-Age') || - arr.startsWith('SameSite') - ) { - return null; - } else { - cookie += `${arr}; `; - return true; - } - }); - return true; - }); - this.options.http.headers.Cookie = `${cookie}locale=en`; - this.options.http.headers['x-fingerprint'] = res.data.fingerprint; - this.emit(Events.DEBUG, `Added Cookie: ${cookie}`); - this.emit(Events.DEBUG, `Added Fingerprint: ${res.data.fingerprint}`); - }) - .catch(err => { - this.emit(Events.DEBUG, `Update Cookie and Fingerprint failed: ${err.message}`); - }); - } - /** * Logs the client in, establishing a WebSocket connection to Discord. * @param {string} [token=this.token] Token of the account to log in with @@ -381,10 +339,6 @@ class Client extends BaseClient { .join('.')}`, ); - if (this.options.autoCookie) { - await this.updateCookie(); - } - if (this.options.presence) { this.options.ws.presence = this.presence._parse(this.options.presence); } @@ -522,7 +476,7 @@ class Client extends BaseClient { /** * Create a new token based on the current token - * @returns {Promise} Discord Token + * @returns {Promise} New Discord Token */ createToken() { return new Promise(resolve => { @@ -537,12 +491,12 @@ class Client extends BaseClient { wsProperties: this.options.ws.properties, }); // Step 2: Add event - QR.on('ready', async (_, url) => { + QR.once('ready', async (_, url) => { await this.remoteAuth(url, true); - }).on('finish', (user, token) => { + }).once('finish', (user, token) => { resolve(token); }); - + // Step 3: Connect QR.connect(); }); } @@ -637,6 +591,24 @@ class Client extends BaseClient { return new Invite(this, data); } + /** + * Join this Guild using this invite (Use with caution) + * @param {InviteResolvable} invite Invite code or URL + * @deprecated + * @returns {Promise} + */ + async acceptInvite(invite) { + const code = DataResolver.resolveInviteCode(invite); + if (!code) throw new Error('INVITE_RESOLVE_CODE'); + if (invite instanceof Invite) { + await invite.acceptInvite(); + } else { + await this.api.invites(code).post({ + data: {}, + }); + } + } + /** * Automatically Redeem Nitro from raw message. * @param {Message} message Discord Message @@ -1005,9 +977,6 @@ class Client extends BaseClient { if (options && typeof options.readyStatus !== 'boolean') { throw new TypeError('CLIENT_INVALID_OPTION', 'readyStatus', 'a boolean'); } - if (options && typeof options.autoCookie !== 'boolean') { - throw new TypeError('CLIENT_INVALID_OPTION', 'autoCookie', 'a boolean'); - } if (options && typeof options.autoRedeemNitro !== 'boolean') { throw new TypeError('CLIENT_INVALID_OPTION', 'autoRedeemNitro', 'a boolean'); } @@ -1025,12 +994,6 @@ class Client extends BaseClient { throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 32 character string'); } break; - case 'nopecha': { - if (options.captchaKey.length !== 16) { - throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 16 character string'); - } - break; - } } } if (options && typeof options.DMSync !== 'boolean') { diff --git a/src/managers/AutoModerationRuleManager.js b/src/managers/AutoModerationRuleManager.js index 2090c9e..ff886c7 100644 --- a/src/managers/AutoModerationRuleManager.js +++ b/src/managers/AutoModerationRuleManager.js @@ -25,6 +25,24 @@ class AutoModerationRuleManager extends CachedManager { this.guild = guild; } + /** + * Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object. + * @method resolve + * @memberof AutoModerationRuleManager + * @instance + * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve + * @returns {?AutoModerationRule} + */ + + /** + * Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id. + * @method resolveId + * @memberof AutoModerationRuleManager + * @instance + * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve + * @returns {?Snowflake} + */ + _add(data, cache) { return super._add(data, cache, { extras: [this.guild] }); } @@ -265,24 +283,6 @@ class AutoModerationRuleManager extends CachedManager { const autoModerationRuleId = this.resolveId(autoModerationRule); await this.client.api.guilds(this.guild.id)('auto-moderation').rules(autoModerationRuleId).delete({ reason }); } - - /** - * Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object. - * @method resolve - * @memberof AutoModerationRuleManager - * @instance - * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve - * @returns {?AutoModerationRule} - */ - - /** - * Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id. - * @method resolveId - * @memberof AutoModerationRuleManager - * @instance - * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve - * @returns {?Snowflake} - */ } module.exports = AutoModerationRuleManager; diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index 3207f95..e54ad05 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -48,26 +48,32 @@ class APIRequest { '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"', + 'Sec-Ch-Ua-Platform': 'Windows', '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), + this.client.options.jsonTransformer({ + ...this.client.options.ws.properties, + browser_user_agent: this.client.options.http.headers['User-Agent'], + }), 'ascii', ).toString('base64')}`, 'X-Discord-Locale': 'en-US', 'User-Agent': this.client.options.http.headers['User-Agent'], + Origin: 'https://discord.com', + Connection: 'keep-alive', }; - /* Remove - this.client.options.http.headers['User-Agent'] = this.fullUserAgent; - */ - if (this.options.auth !== false) headers.Authorization = this.rest.getAuth(); if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason); if (this.options.headers) headers = Object.assign(headers, this.options.headers); + if (this.options.webhook === true) { + headers = { + 'User-Agent': this.client.options.http.headers['User-Agent'], + }; + } let body; if (this.options.files?.length) { diff --git a/src/rest/CaptchaSolver.js b/src/rest/CaptchaSolver.js index 08ffc96..dc2c4a2 100644 --- a/src/rest/CaptchaSolver.js +++ b/src/rest/CaptchaSolver.js @@ -16,10 +16,17 @@ module.exports = class CaptchaSolver { const lib = require('2captcha'); this.service = '2captcha'; this.solver = new lib.Solver(key); - this.solve = siteKey => + this.solve = (data, userAgent) => new Promise((resolve, reject) => { + const siteKey = data.captcha_sitekey; + const postD = data.captcha_rqdata + ? { + data: data.captcha_rqdata, + userAgent, + } + : undefined; this.solver - .hcaptcha(siteKey, 'discord.com') + .hcaptcha(siteKey, 'https://discord.com/channels/@me', postD) .then(res => { resolve(res.data); }) @@ -30,31 +37,6 @@ module.exports = class CaptchaSolver { throw this._missingModule('2captcha'); } } - case 'nopecha': { - if (!key || typeof key !== 'string') throw new Error('NopeCHA key is not provided'); - try { - const { Configuration, NopeCHAApi } = require('nopecha'); - const configuration = new Configuration({ - apiKey: key, - }); - this.service = 'nopecha'; - this.solver = new NopeCHAApi(configuration); - this.solve = sitekey => - new Promise((resolve, reject) => { - this.solver - .solveToken({ - type: 'hcaptcha', - sitekey, - url: 'https://discord.com', - }) - .then(res => (res ? resolve(res) : reject(new Error('Captcha could not be solved')))) - .catch(reject); - }); - break; - } catch (e) { - throw this._missingModule('nopecha'); - } - } } } solve() {} diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index 7648c02..d9d3e3d 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -11,6 +11,25 @@ const { Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST }, } = require('../util/Constants'); +const cookieFilter = str => { + const blackList = ['expires', 'path', 'domain', 'httponly', 'secure', 'max-age', 'samesite']; + if (blackList.some(s => str.toLowerCase().includes(`${s}`))) return false; + return true; +}; + +function parseCookie(str, old) { + const oldProps = old.split(';').filter(cookieFilter); + const allProps = str.split(';').filter(cookieFilter); + // Update data from all to old + allProps.forEach(prop => { + const key = prop.split('=')[0]; + const index = oldProps.findIndex(s => s.startsWith(key)); + if (index !== -1) oldProps[index] = prop; + else oldProps.push(prop); + }); + return oldProps.filter(s => s).join('; '); +} + const captchaMessage = [ 'incorrect-captcha', 'response-already-used', @@ -240,6 +259,20 @@ class RequestHandler { let sublimitTimeout; if (res.headers) { + // Cookie: + const cookie = res.headers.get('set-cookie'); + if (cookie) { + if (typeof cookie == 'string') { + this.manager.client.options.http.headers.Cookie = parseCookie( + cookie, + this.manager.client.options.http.headers.Cookie || '', + ); + this.manager.client.emit( + 'debug', + `[REST] Set new cookie: ${this.manager.client.options.http.headers.Cookie}`, + ); + } + } const serverDate = res.headers.get('date'); const limit = res.headers.get('x-ratelimit-limit'); const remaining = res.headers.get('x-ratelimit-remaining'); @@ -368,7 +401,10 @@ class RequestHandler { Route : ${request.route} Info : ${inspect(data, { depth: null })}`, ); - const captcha = await this.manager.captchaService.solve(data.captcha_sitekey); + const captcha = await this.manager.captchaService.solve( + data, + this.manager.client.options.http.headers['User-Agent'], + ); this.manager.client.emit( DEBUG, `Captcha solved. diff --git a/src/structures/Invite.js b/src/structures/Invite.js index b0026b6..35e1731 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -343,10 +343,12 @@ class Invite extends Base { }, }); const guild = this.client.guilds.cache.get(this.guild.id); + /* + // if (autoVerify) { console.warn('Feature is under maintenance - Invite#acceptInvite(true)'); } - /* Disabled + */ if (autoVerify) { const getForm = await this.client.api .guilds(this.guild.id) @@ -358,7 +360,6 @@ class Invite extends Base { // https://discord.com/api/v9/guilds/:id/requests/@me await this.client.api.guilds(this.guild.id).requests['@me'].put({ data: { form_fields: [form] } }); } - */ return guild; } } diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 5745211..814f504 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -201,6 +201,7 @@ class Webhook { query: { thread_id: messagePayload.options.threadId, wait: true }, auth: false, versioned: true, + webhook: true, }); return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? d; } @@ -230,6 +231,7 @@ class Webhook { query: { wait: true }, auth: false, data: body, + webhook: true, }); return data.toString() === 'ok'; } @@ -257,6 +259,7 @@ class Webhook { data: { name, avatar, channel_id: channel }, reason, auth: !this.token || Boolean(channel), + webhook: true, }); this.name = data.name; @@ -305,6 +308,7 @@ class Webhook { thread_id: cacheOrOptions.threadId, }, auth: false, + webhook: true, }); return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cacheOrOptions.cache) ?? data; } @@ -337,6 +341,7 @@ class Webhook { thread_id: messagePayload.options.threadId, }, auth: false, + webhook: true, }); const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages; @@ -356,7 +361,7 @@ class Webhook { * @returns {Promise} */ async delete(reason) { - await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token }); + await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token, webhook: true }); } /** @@ -376,6 +381,7 @@ class Webhook { thread_id: threadId, }, auth: false, + webhook: true, }); } diff --git a/src/util/Constants.js b/src/util/Constants.js index 07a4ad9..16ac995 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -21,10 +21,9 @@ exports.MaxBulkDeletableMessageAge = 1_209_600_000; /** * API captcha solver * * `2captcha` - 2captcha.com - * * `nopecha` - nopecha.com * @typedef {string[]} captchaServices */ -exports.captchaServices = ['2captcha', 'nopecha']; +exports.captchaServices = ['2captcha']; /** * Automatically scan and delete direct messages you receive that contain explicit media content. @@ -1770,10 +1769,13 @@ function createEnum(keys) { * @property {Object} ApplicationCommandTypes * The type of an {@link ApplicationCommand} object. * @property {Object} AutoModerationRuleTriggerTypes Characterizes the type - * of contentwhich can trigger the rule. + * of content which can trigger the rule. * @property {Object} AutoModerationActionTypes + * A type of an action which executes whenever a rule is triggered. * @property {Object} AutoModerationRuleKeywordPresetTypes - * @property {Object} AutoModerationRuleEventTypes + * The internally pre-defined wordsetswhich will be searched for in content + * @property {Object} AutoModerationRuleEventTypes Indicates in what event context + * a rule should be checked. * @property {Object} ChannelTypes All available channel types. * @property {ClientApplicationAssetTypes} ClientApplicationAssetTypes The types of an {@link ApplicationAsset} object. * @property {Object} Colors An object with regularly used colors. diff --git a/src/util/Options.js b/src/util/Options.js index 25b6d6d..ce4b6ff 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -38,7 +38,6 @@ const { randomUA } = require('../util/Constants'); * from the WebSocket. Don't have this too high/low. Its best to have it between 2_000-6_000 ms. * @property {boolean} [checkUpdate=true] Display module update information on the screen * @property {boolean} [readyStatus=true] Sync state with Discord Client - * @property {boolean} [autoCookie=true] Automatically add Cookies to Request on startup * @property {boolean} [patchVoice=false] Automatically patch @discordjs/voice module (support for call) * @property {string} [captchaService=null] Captcha service to use for solving captcha {@link captchaServices} * @property {string} [captchaKey=null] Captcha service key @@ -152,7 +151,6 @@ class Options extends null { closeTimeout: 5_000, checkUpdate: true, readyStatus: true, - autoCookie: true, autoRedeemNitro: false, captchaService: '', captchaKey: null, diff --git a/src/util/RemoteAuth.js b/src/util/RemoteAuth.js index ad28d5d..4391979 100644 --- a/src/util/RemoteAuth.js +++ b/src/util/RemoteAuth.js @@ -431,10 +431,10 @@ new DiscordAuthWebsocket({ headers: { Accept: '*/*', 'Content-Type': 'application/json', - 'Accept-Language': 'en-US,en;q=0.9', 'Cache-Control': 'no-cache', Pragma: 'no-cache', - 'Sec-Ch-Ua': `"Google Chrome";v="${chromeVersion}", "Chromium";v="${chromeVersion}", "Not=A?Brand";v="24"`, + '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"', 'Sec-Fetch-Dest': 'empty', @@ -446,6 +446,7 @@ new DiscordAuthWebsocket({ )}`, 'X-Discord-Locale': 'en-US', 'User-Agent': this.options.userAgent, + Origin: 'https://discord.com', }, }, ) diff --git a/typings/index.d.ts b/typings/index.d.ts index 6e79eba..c8febc9 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -856,7 +856,6 @@ export class Client extends BaseClient { // Added public settings: ClientSettingManager; public relationships: RelationshipManager; - public updateCookie(): Promise; public readonly callVoice?: VoiceConnection; public voiceStates: VoiceStateManager; // End @@ -4767,7 +4766,6 @@ export interface ClientOptions { // add checkUpdate?: boolean; readyStatus?: boolean; - autoCookie?: boolean; autoRedeemNitro?: boolean; patchVoice?: boolean; password?: string; @@ -4779,7 +4777,7 @@ export interface ClientOptions { usingNewAttachmentAPI?: boolean; } -export type captchaServices = '2captcha' | 'nopecha'; +export type captchaServices = '2captcha'; // end copy