UNPKG

djs-selfbot-v11

Version:

Selfbot module Discord

213 lines (203 loc) 8.59 kB
const RequestHandler = require('./RequestHandler'); const DiscordAPIError = require('../DiscordAPIError'); const { Events: { RATE_LIMIT } } = require('../../../util/Constants'); var setCookie = require('set-cookie-parser'); const captchaMessage = [ 'incorrect-captcha', 'response-already-used', 'captcha-required', 'invalid-input-response', 'invalid-response', 'You need to update your app', 'response-already-used-error', ]; /** * Handles API Requests sequentially, i.e. we wait until the current request is finished before moving onto * the next. This plays a _lot_ nicer in terms of avoiding 429's when there is more than one session of the account, * but it can be slower. * @extends {RequestHandler} * @private */ class SequentialRequestHandler extends RequestHandler { /** * @param {RESTManager} restManager The REST manager to use * @param {string} endpoint The endpoint to handle */ constructor(restManager, endpoint) { super(restManager, endpoint); /** * The client that instantiated this handler * @type {Client} */ this.client = restManager.client; /** * The endpoint that this handler is handling * @type {string} */ this.endpoint = endpoint; /** * The time difference between Discord's Dates and the local computer's Dates. A positive number means the local * computer's time is ahead of Discord's * @type {number} */ this.timeDifference = 0; /** * Whether the queue is being processed or not * @type {boolean} */ this.busy = false; } push(request) { super.push(request); this.handle(); } /** * Performs a request then resolves a promise to indicate its readiness for a new request. * @param {APIRequest} item The item to execute * @returns {Promise<?Object|Error>} */ execute(item, captchaKey, captchaToken) { this.busy = true; return new Promise(resolve => { item.request.gen(captchaKey, captchaToken).end(async (err, res) => { if (res && res.headers) { this.requestLimit = Number(res.headers['x-ratelimit-limit']); this.requestResetTime = Number(res.headers['x-ratelimit-reset']) * 1000; this.requestRemaining = Number(res.headers['x-ratelimit-remaining']); this.timeDifference = Date.now() - new Date(res.headers.date).getTime(); /*if (this.client.cookie === null && item.request.path == "/users/@me/settings") { let cookie = ""; var cookies = setCookie.parse(res, { decodeValues: true }); const cs = cookies.values(); for (const c of cs) { const array = ["__dcfduid", "__sdcfduid", "__cfruid"]; if (array.includes(c.name)) { //console.log(c.name); cookie += `${c.name}=${c.value}; `; } } //console.log(cookie); this.client.cookie = cookie; }*/ } if (err) { if (err.status === 429) { this.queue.unshift(item); this.client.setTimeout(() => { this.globalLimit = false; resolve(); }, Number(res.headers['retry-after']) + this.client.options.restTimeOffset); if (res.headers['x-ratelimit-global']) this.globalLimit = true; //console.log(`rate limit hit retry after: ${Number(res.headers['retry-after'])} message: ${res.body.message} global: ${Boolean(res.body.global)}`); if (this.client.listenerCount(RATE_LIMIT)) { /** * Emitted when the client hits a rate limit while making a request * @event Client#rateLimit * @param {Object} rateLimitInfo Object containing the rate limit info * @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint * @param {number} rateLimitInfo.timeDifference Delta-T in ms between your system and Discord servers * @param {string} rateLimitInfo.path Path used for request that triggered this event * @param {string} rateLimitInfo.method HTTP method used for request that triggered this event */ this.client.emit(RATE_LIMIT, { limit: null, timeDifference: this.timeDifference, path: item.request.path, method: item.request.method, retry_after: Number(res.headers['retry-after']), global: Boolean(res.body.global) }); } } else if (err.status >= 500 && err.status < 600) { if (item.retries === this.client.options.retryLimit) { item.reject(err); resolve(); } else { item.retries++; this.queue.unshift(item); this.client.setTimeout(resolve, 1e3 + this.client.options.restTimeOffset); } } else { if (res.body?.captcha_service) { //console.log(res.body.captcha_service) } if ( res.body.captcha_service && this.client.options.captchaService && item.retries < this.client.options.captchaRetryLimit && captchaMessage.some(s => res.body.captcha_key[0].includes(s)) ) { // Retry the request after a captcha is solved const captcha = await this.restManager.captchaService.solve( res.body, `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9020 Chrome/108.0.5359.215 Electron/22.3.26 Safari/537.36`, ); item.retries++; return this.execute(item, captcha, res.body.captcha_rqtoken); } item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body, res.request.method) : err); resolve(err); } } else { this.globalLimit = false; const data = res && res.body ? res.body : {}; if (data?.captcha_service) { console.log(data.captcha_service) } if ( data?.captcha_service && this.client.options.captchaService && request.retries < this.client.options.captchaRetryLimit && captchaMessage.some(s => data.captcha_key[0].includes(s)) ) { // Retry the request after a captcha is solved const captcha = await this.captchaService.solve( data, `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9020 Chrome/108.0.5359.215 Electron/22.3.26 Safari/537.36`, ); item.retries++; return this.execute(item, captcha, data.captcha_rqtoken); } item.resolve(data); if (this.requestRemaining === 0) { if (this.client.listenerCount(RATE_LIMIT)) { /** * Emitted when the client hits a rate limit while making a request * @event Client#rateLimit * @param {Object} rateLimitInfo Object containing the rate limit info * @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint * @param {number} rateLimitInfo.timeDifference Delta-T in ms between your system and Discord servers * @param {string} rateLimitInfo.path Path used for request that triggered this event * @param {string} rateLimitInfo.method HTTP method used for request that triggered this event */ this.client.emit(RATE_LIMIT, { limit: this.requestLimit, timeDifference: this.timeDifference, path: item.request.path, method: item.request.method, }); } this.client.setTimeout( () => resolve(data), this.requestResetTime - Date.now() + this.timeDifference + this.client.options.restTimeOffset ); } else { resolve(data); } } }); }); } handle() { super.handle(); if (this.busy || this.remaining === 0 || this.queue.length === 0 || this.globalLimit) return; this.execute(this.queue.shift()).then(() => { this.busy = false; this.handle(); }); } } module.exports = SequentialRequestHandler;