feat(CaptchaHandler): Must not use due to safety concerns

This commit is contained in:
March 7th 2022-11-04 18:40:19 +07:00
parent 9616ef19a7
commit b3eedc34be
9 changed files with 112 additions and 12 deletions

View File

@ -34,16 +34,21 @@
### <strong>[Extend Document (With Example)](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/tree/main/Document)</strong> ### <strong>[Extend Document (With Example)](https://github.com/aiko-chan-ai/discord.js-selfbot-v13/tree/main/Document)</strong>
## Features ## Features (User)
- [x] Message: Send, Receive, Delete, Edit, Pin, Reaction Emoji, Attachments, Embeds (WebEmbed), Mentions, Webhooks, etc. - [x] Message: Embeds (WebEmbed)
- [x] User: Settings, Status, Activity, DeveloperPortal, RemoteAuth, etc. - [x] User: Settings, Status, Activity, DeveloperPortal, RemoteAuth, etc.
- [X] Guild: Fetch Members, Join / Leave, Roles, Channels, 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] Voice: Connect, Disconnect, Mute, Deafen, Call, Play Audio, etc.
- [X] Captcha Handler (Must not use due to safety concerns)
- [X] Documentation - [X] Documentation
- [ ] Video stream - [ ] Video stream
- [ ] Everything - [ ] Everything
### Optional packages
- [2captcha](https://www.npmjs.com/package/2captcha) for solving captcha (`npm install 2captcha`)
## Installation ## Installation
**Node.js 16.6.0 or newer is required** **Node.js 16.6.0 or newer is required**
@ -51,6 +56,7 @@
```sh-session ```sh-session
npm install discord.js-selfbot-v13@latest npm install discord.js-selfbot-v13@latest
``` ```
## Example ## Example
```js ```js

View File

@ -850,11 +850,17 @@ class Client extends BaseClient {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async authorizeURL(url, options = {}) { async authorizeURL(url, options = {}) {
const reg = /^https:\/\/discord.com\/(api\/)*oauth2\/authorize(\W+)/gim; const reg = /(api\/)*oauth2\/authorize/gim;
let searchParams = {};
const checkURL = () => { const checkURL = () => {
try { try {
// eslint-disable-next-line no-new // 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; return true;
} catch (e) { } catch (e) {
return false; return false;
@ -867,10 +873,11 @@ class Client extends BaseClient {
}, },
options, options,
); );
if (!url || !checkURL() || !reg.test(url)) { if (!url || !checkURL()) {
throw new Error('INVALID_URL', url); 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, data: options,
}); });
return true; return true;
@ -885,6 +892,7 @@ class Client extends BaseClient {
* @private * @private
*/ */
_validateOptions(options = this.options) { _validateOptions(options = this.options) {
const captchaService = ['2captcha'];
if (typeof options.intents === 'undefined') { if (typeof options.intents === 'undefined') {
throw new TypeError('CLIENT_MISSING_INTENTS'); throw new TypeError('CLIENT_MISSING_INTENTS');
} else { } else {
@ -902,6 +910,12 @@ class Client extends BaseClient {
if (options && typeof options.autoRedeemNitro !== 'boolean') { if (options && typeof options.autoRedeemNitro !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'autoRedeemNitro', 'a 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') { if (options && typeof options.DMSync !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'DMSync', 'a boolean'); throw new TypeError('CLIENT_INVALID_OPTION', 'DMSync', 'a boolean');
} }

View File

@ -34,7 +34,7 @@ class APIRequest {
this.path = `${path}${queryString && `?${queryString}`}`; this.path = `${path}${queryString && `?${queryString}`}`;
} }
make() { make(captchaKey = undefined) {
agent ??= agent ??=
typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0 typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0
? new proxy(this.client.options.proxy) ? new proxy(this.client.options.proxy)
@ -101,6 +101,12 @@ class APIRequest {
headers = Object.assign(headers, body.getHeaders()); 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 controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.client.options.restRequestTimeout).unref(); const timeout = setTimeout(() => controller.abort(), this.client.options.restRequestTimeout).unref();
return fetch(url, { return fetch(url, {

33
src/rest/CaptchaSolver.js Normal file
View File

@ -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() {}
};

View File

@ -38,7 +38,7 @@ class DiscordAPIError extends Error {
/** /**
* @typedef {Object} Captcha * @typedef {Object} Captcha
* @property {Array<string>} captcha_key ['message'] * @property {Array<string>} captcha_key ['message']
* @property {string} captcha_sitekey Captcha code ??? * @property {string} captcha_sitekey Captcha sitekey (hcaptcha)
* @property {string} captcha_service hcaptcha * @property {string} captcha_service hcaptcha
* @property {string} [captcha_rqdata] * @property {string} [captcha_rqdata]
* @property {string} [captcha_rqtoken] * @property {string} [captcha_rqtoken]

View File

@ -4,6 +4,7 @@ const { setInterval } = require('node:timers');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const APIRequest = require('./APIRequest'); const APIRequest = require('./APIRequest');
const routeBuilder = require('./APIRouter'); const routeBuilder = require('./APIRouter');
const CaptchaSolver = require('./CaptchaSolver');
const RequestHandler = require('./RequestHandler'); const RequestHandler = require('./RequestHandler');
const { Error } = require('../errors'); const { Error } = require('../errors');
const { Endpoints } = require('../util/Constants'); const { Endpoints } = require('../util/Constants');
@ -22,6 +23,12 @@ class RESTManager {
this.handlers.sweep(handler => handler._inactive); this.handlers.sweep(handler => handler._inactive);
}, client.options.restSweepInterval * 1_000).unref(); }, 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() { get api() {

View File

@ -103,7 +103,7 @@ class RequestHandler {
} }
} }
async execute(request) { async execute(request, captchaKey) {
/* /*
* After calculations have been done, pre-emptively stop further requests * 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 * 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 // Perform the request
let res; let res;
try { try {
res = await request.make(); res = await request.make(captchaKey);
} catch (error) { } catch (error) {
// Retry the specified number of times for request abortions // Retry the specified number of times for request abortions
if (request.retries === this.manager.client.options.retryLimit) { if (request.retries === this.manager.client.options.retryLimit) {
@ -343,6 +343,27 @@ class RequestHandler {
let data; let data;
try { try {
data = await parseResponse(res); 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) { } catch (err) {
throw new HTTPError(err.message, err.constructor.name, err.status, request); throw new HTTPError(err.message, err.constructor.name, err.status, request);
} }

View File

@ -39,6 +39,8 @@ const { randomUA } = require('../util/Constants');
* @property {boolean} [readyStatus=true] Sync state with Discord Client * @property {boolean} [readyStatus=true] Sync state with Discord Client
* @property {boolean} [autoCookie=true] Automatically add Cookies to Request on startup * @property {boolean} [autoCookie=true] Automatically add Cookies to Request on startup
* @property {boolean} [patchVoice=false] Automatically patch @discordjs/voice module (support for call) * @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 <NOTE: there is no cooldown on the auto redeem> * @property {boolean} [autoRedeemNitro=false] Automaticlly redeems nitro codes <NOTE: there is no cooldown on the auto redeem>
* @property {string} [proxy] Proxy to use for the WebSocket + REST connection (proxy-agent uri type) {@link https://www.npmjs.com/package/proxy-agent}. * @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) * @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, readyStatus: true,
autoCookie: true, autoCookie: true,
autoRedeemNitro: false, autoRedeemNitro: false,
captchaService: '',
captchaKey: null,
DMSync: false, DMSync: false,
patchVoice: false, patchVoice: false,
waitGuildTimeout: 15_000, waitGuildTimeout: 15_000,

11
typings/index.d.ts vendored
View File

@ -1179,10 +1179,17 @@ export class DataResolver extends null {
public static resolveGuildTemplateCode(data: GuildTemplateResolvable): string; 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 { export class DiscordAPIError extends Error {
private constructor(error: unknown, status: number, request: unknown); private constructor(error: unknown, status: number, request: unknown);
private static flattenErrors(obj: unknown, key: string): string[]; private static flattenErrors(obj: unknown, key: string): string[];
public captcha?: Captcha;
public code: number; public code: number;
public method: string; public method: string;
public path: string; public path: string;
@ -4680,6 +4687,8 @@ export interface ClientOptions {
patchVoice?: boolean; patchVoice?: boolean;
DMSync?: boolean; DMSync?: boolean;
proxy?: string; proxy?: string;
captchaService?: string;
captchaKey?: string;
} }
// end copy // end copy