feat(CaptchaHandler): Must not use due to safety concerns
This commit is contained in:
parent
9616ef19a7
commit
b3eedc34be
12
README.md
12
README.md
@ -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
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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
33
src/rest/CaptchaSolver.js
Normal 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() {}
|
||||||
|
};
|
@ -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]
|
||||||
|
@ -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() {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
11
typings/index.d.ts
vendored
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user