From b3eedc34be5c8173b9e35fb5ad80f9cf19c7e75b Mon Sep 17 00:00:00 2001 From: March 7th <71698422+aiko-chan-ai@users.noreply.github.com> Date: Fri, 4 Nov 2022 18:40:19 +0700 Subject: [PATCH] feat(CaptchaHandler): Must not use due to safety concerns --- README.md | 12 +++++++++--- src/client/Client.js | 22 ++++++++++++++++++---- src/rest/APIRequest.js | 8 +++++++- src/rest/CaptchaSolver.js | 33 +++++++++++++++++++++++++++++++++ src/rest/DiscordAPIError.js | 2 +- src/rest/RESTManager.js | 7 +++++++ src/rest/RequestHandler.js | 25 +++++++++++++++++++++++-- src/util/Options.js | 4 ++++ typings/index.d.ts | 11 ++++++++++- 9 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 src/rest/CaptchaSolver.js diff --git a/README.md b/README.md index ca1cc11..dc248f8 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,21 @@ ### [Extend Document (With Example)](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/tree/main/Document) -## Features -- [x] Message: Send, Receive, Delete, Edit, Pin, Reaction Emoji, Attachments, Embeds (WebEmbed), Mentions, Webhooks, etc. +## Features (User) +- [x] Message: Embeds (WebEmbed) - [x] User: Settings, Status, Activity, DeveloperPortal, RemoteAuth, etc. - [X] Guild: Fetch Members, Join / Leave, Roles, Channels, etc. -- [X] Interactions: Slash Commands, Click Buttons, Using Menu, Modal, Context Menu, etc. +- [X] Interactions: Slash Commands, Click Buttons, Menu (classic), Modal, Context Menu, etc. - [X] Voice: Connect, Disconnect, Mute, Deafen, Call, Play Audio, etc. +- [X] Captcha Handler (Must not use due to safety concerns) - [X] Documentation - [ ] Video stream - [ ] Everything +### Optional packages + +- [2captcha](https://www.npmjs.com/package/2captcha) for solving captcha (`npm install 2captcha`) + ## Installation **Node.js 16.6.0 or newer is required** @@ -51,6 +56,7 @@ ```sh-session npm install discord.js-selfbot-v13@latest ``` + ## Example ```js diff --git a/src/client/Client.js b/src/client/Client.js index 5bcd1ef..166ee2b 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -850,11 +850,17 @@ class Client extends BaseClient { * @returns {Promise} */ async authorizeURL(url, options = {}) { - const reg = /^https:\/\/discord.com\/(api\/)*oauth2\/authorize(\W+)/gim; + const reg = /(api\/)*oauth2\/authorize/gim; + let searchParams = {}; const checkURL = () => { try { // eslint-disable-next-line no-new - new URL(url); + const url_ = new URL(url); + if (!['discord.com', 'canary.discord.com', 'ptb.discord.com'].includes(url_.hostname)) return false; + if (!reg.test(url_.pathname)) return false; + for (const [key, value] of url_.searchParams.entries()) { + searchParams[key] = value; + } return true; } catch (e) { return false; @@ -867,10 +873,11 @@ class Client extends BaseClient { }, options, ); - if (!url || !checkURL() || !reg.test(url)) { + if (!url || !checkURL()) { throw new Error('INVALID_URL', url); } - await this.api.oauth2.authorize[`?${url.replace(reg, '')}`].post({ + await this.api.oauth2.authorize.post({ + query: searchParams, data: options, }); return true; @@ -885,6 +892,7 @@ class Client extends BaseClient { * @private */ _validateOptions(options = this.options) { + const captchaService = ['2captcha']; if (typeof options.intents === 'undefined') { throw new TypeError('CLIENT_MISSING_INTENTS'); } else { @@ -902,6 +910,12 @@ class Client extends BaseClient { if (options && typeof options.autoRedeemNitro !== 'boolean') { throw new TypeError('CLIENT_INVALID_OPTION', 'autoRedeemNitro', 'a boolean'); } + if (options && options.captchaService && !captchaService.includes(options.captchaService)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'captchaService', captchaService.join(', ')); + } + if (options && captchaService.includes(options.captchaService) && typeof options.captchaKey !== 'string') { + throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a string'); + } if (options && typeof options.DMSync !== 'boolean') { throw new TypeError('CLIENT_INVALID_OPTION', 'DMSync', 'a boolean'); } diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index b863100..043e0d3 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -34,7 +34,7 @@ class APIRequest { this.path = `${path}${queryString && `?${queryString}`}`; } - make() { + make(captchaKey = undefined) { agent ??= typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0 ? new proxy(this.client.options.proxy) @@ -101,6 +101,12 @@ class APIRequest { headers = Object.assign(headers, body.getHeaders()); } + if (headers['Content-Type'] === 'application/json' && captchaKey && typeof captchaKey == 'string' && body) { + body = JSON.parse(body); + body.captcha_key = captchaKey; + body = JSON.stringify(body); + } + const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), this.client.options.restRequestTimeout).unref(); return fetch(url, { diff --git a/src/rest/CaptchaSolver.js b/src/rest/CaptchaSolver.js new file mode 100644 index 00000000..32f4616 --- /dev/null +++ b/src/rest/CaptchaSolver.js @@ -0,0 +1,33 @@ +'use strict'; +module.exports = class CaptchaSolver { + constructor(service, key) { + this.service = ''; + this.solver = undefined; + this._setup(service, key); + } + _setup(service, key) { + switch (service) { + case '2captcha': { + if (!key || typeof key !== 'string') throw new Error('2captcha key is not provided'); + try { + const lib = require('2captcha'); + this.service = '2captcha'; + this.solver = new lib.Solver(key); + this.solve = siteKey => + new Promise((resolve, reject) => { + this.solver + .hcaptcha(siteKey, 'discord.com') + .then(res => { + resolve(res.data); + }) + .catch(reject); + }); + break; + } catch (e) { + throw new Error('2captcha module not found, please install it with `npm i 2captcha`'); + } + } + } + } + solve() {} +}; diff --git a/src/rest/DiscordAPIError.js b/src/rest/DiscordAPIError.js index 32c1b16..9f633db 100644 --- a/src/rest/DiscordAPIError.js +++ b/src/rest/DiscordAPIError.js @@ -38,7 +38,7 @@ class DiscordAPIError extends Error { /** * @typedef {Object} Captcha * @property {Array} captcha_key ['message'] - * @property {string} captcha_sitekey Captcha code ??? + * @property {string} captcha_sitekey Captcha sitekey (hcaptcha) * @property {string} captcha_service hcaptcha * @property {string} [captcha_rqdata] * @property {string} [captcha_rqtoken] diff --git a/src/rest/RESTManager.js b/src/rest/RESTManager.js index ed6775b..c742cc5 100644 --- a/src/rest/RESTManager.js +++ b/src/rest/RESTManager.js @@ -4,6 +4,7 @@ const { setInterval } = require('node:timers'); const { Collection } = require('@discordjs/collection'); const APIRequest = require('./APIRequest'); const routeBuilder = require('./APIRouter'); +const CaptchaSolver = require('./CaptchaSolver'); const RequestHandler = require('./RequestHandler'); const { Error } = require('../errors'); const { Endpoints } = require('../util/Constants'); @@ -22,6 +23,12 @@ class RESTManager { this.handlers.sweep(handler => handler._inactive); }, client.options.restSweepInterval * 1_000).unref(); } + this.captchaService = null; + this.setup(); + } + + setup() { + this.captchaService = new CaptchaSolver(this.client.options.captchaService, this.client.options.captchaKey); } get api() { diff --git a/src/rest/RequestHandler.js b/src/rest/RequestHandler.js index 3f02709..fcec6ad 100644 --- a/src/rest/RequestHandler.js +++ b/src/rest/RequestHandler.js @@ -103,7 +103,7 @@ class RequestHandler { } } - async execute(request) { + async execute(request, captchaKey) { /* * After calculations have been done, pre-emptively stop further requests * Potentially loop until this task can run if e.g. the global rate limit is hit twice @@ -194,7 +194,7 @@ class RequestHandler { // Perform the request let res; try { - res = await request.make(); + res = await request.make(captchaKey); } catch (error) { // Retry the specified number of times for request abortions if (request.retries === this.manager.client.options.retryLimit) { @@ -343,6 +343,27 @@ class RequestHandler { let data; try { data = await parseResponse(res); + if (data?.captcha_service && this.manager.client.options.captchaService) { + // Retry the request after a captcha is solved + this.manager.client.emit( + DEBUG, + `Hit a captcha while executing a request. Solving captcha ... + Method : ${request.method} + Path : ${request.path} + Route : ${request.route} + Sitekey : ${data.captcha_sitekey}`, + ); + const captcha = await this.manager.captchaService.solve(data.captcha_sitekey); + this.manager.client.emit( + DEBUG, + `Captcha solved. + Method : ${request.method} + Path : ${request.path} + Route : ${request.route} + Key : ${captcha}`, + ); + return this.execute(request, captcha); + } } catch (err) { throw new HTTPError(err.message, err.constructor.name, err.status, request); } diff --git a/src/util/Options.js b/src/util/Options.js index ce7df9c..20e84da 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -39,6 +39,8 @@ const { randomUA } = require('../util/Constants'); * @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 + * @property {string} [captchaKey=null] Captcha service key * @property {boolean} [autoRedeemNitro=false] Automaticlly redeems nitro codes * @property {string} [proxy] Proxy to use for the WebSocket + REST connection (proxy-agent uri type) {@link https://www.npmjs.com/package/proxy-agent}. * @property {boolean} [DMSync=false] Automatically synchronize call status (DM and group) at startup (event synchronization) [Warning: May cause rate limit to gateway) @@ -148,6 +150,8 @@ class Options extends null { readyStatus: true, autoCookie: true, autoRedeemNitro: false, + captchaService: '', + captchaKey: null, DMSync: false, patchVoice: false, waitGuildTimeout: 15_000, diff --git a/typings/index.d.ts b/typings/index.d.ts index fb981e4..be7b37b 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1179,10 +1179,17 @@ export class DataResolver extends null { public static resolveGuildTemplateCode(data: GuildTemplateResolvable): string; } +export interface Captcha { + captcha_key: string[]; + captcha_service: string; + captcha_sitekey: string; + captcha_rqdata?: string; + captcha_rqtoken?: string; +} export class DiscordAPIError extends Error { private constructor(error: unknown, status: number, request: unknown); private static flattenErrors(obj: unknown, key: string): string[]; - + public captcha?: Captcha; public code: number; public method: string; public path: string; @@ -4680,6 +4687,8 @@ export interface ClientOptions { patchVoice?: boolean; DMSync?: boolean; proxy?: string; + captchaService?: string; + captchaKey?: string; } // end copy