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