feat: Update (see description)

```diff
+ fix: Discord Ban (invalid headers)
+ fix: acceptInvite not working (invalid captcha data)
+ feat: automod update
- feat: remove `nopecha`
- feat: remove Client#updateCookie & ClientOptions#autoCookie
```
This commit is contained in:
March 7th 2022-12-27 17:27:34 +07:00
parent e407700865
commit affcf5e166
12 changed files with 118 additions and 126 deletions

View File

@ -48,7 +48,6 @@
### Optional packages ### Optional packages
- [2captcha](https://www.npmjs.com/package/2captcha) for solving captcha (`npm install 2captcha`) - [2captcha](https://www.npmjs.com/package/2captcha) for solving captcha (`npm install 2captcha`)
- [nopecha](https://www.npmjs.com/package/nopecha) for solving captcha (`npm install nopecha`)
## Installation ## Installation

View File

@ -312,48 +312,6 @@ class Client extends BaseClient {
return getVoiceConnection(null); return getVoiceConnection(null);
} }
/**
* Update Cloudflare Cookie and Discord Fingerprint
*/
async updateCookie() {
/* Auto find fingerprint and add Cookie */
let cookie = '';
await require('axios')({
method: 'get',
url: 'https://discord.com/api/v9/experiments',
headers: this.options.http.headers,
})
.then(res => {
if (!('set-cookie' in res.headers)) return;
res.headers['set-cookie'].map(line => {
line.split('; ').map(arr => {
if (
arr.startsWith('Expires') ||
arr.startsWith('Path') ||
arr.startsWith('Domain') ||
arr.startsWith('HttpOnly') ||
arr.startsWith('Secure') ||
arr.startsWith('Max-Age') ||
arr.startsWith('SameSite')
) {
return null;
} else {
cookie += `${arr}; `;
return true;
}
});
return true;
});
this.options.http.headers.Cookie = `${cookie}locale=en`;
this.options.http.headers['x-fingerprint'] = res.data.fingerprint;
this.emit(Events.DEBUG, `Added Cookie: ${cookie}`);
this.emit(Events.DEBUG, `Added Fingerprint: ${res.data.fingerprint}`);
})
.catch(err => {
this.emit(Events.DEBUG, `Update Cookie and Fingerprint failed: ${err.message}`);
});
}
/** /**
* Logs the client in, establishing a WebSocket connection to Discord. * Logs the client in, establishing a WebSocket connection to Discord.
* @param {string} [token=this.token] Token of the account to log in with * @param {string} [token=this.token] Token of the account to log in with
@ -381,10 +339,6 @@ class Client extends BaseClient {
.join('.')}`, .join('.')}`,
); );
if (this.options.autoCookie) {
await this.updateCookie();
}
if (this.options.presence) { if (this.options.presence) {
this.options.ws.presence = this.presence._parse(this.options.presence); this.options.ws.presence = this.presence._parse(this.options.presence);
} }
@ -522,7 +476,7 @@ class Client extends BaseClient {
/** /**
* Create a new token based on the current token * Create a new token based on the current token
* @returns {Promise<string>} Discord Token * @returns {Promise<string>} New Discord Token
*/ */
createToken() { createToken() {
return new Promise(resolve => { return new Promise(resolve => {
@ -537,12 +491,12 @@ class Client extends BaseClient {
wsProperties: this.options.ws.properties, wsProperties: this.options.ws.properties,
}); });
// Step 2: Add event // Step 2: Add event
QR.on('ready', async (_, url) => { QR.once('ready', async (_, url) => {
await this.remoteAuth(url, true); await this.remoteAuth(url, true);
}).on('finish', (user, token) => { }).once('finish', (user, token) => {
resolve(token); resolve(token);
}); });
// Step 3: Connect
QR.connect(); QR.connect();
}); });
} }
@ -637,6 +591,24 @@ class Client extends BaseClient {
return new Invite(this, data); return new Invite(this, data);
} }
/**
* Join this Guild using this invite (Use with caution)
* @param {InviteResolvable} invite Invite code or URL
* @deprecated
* @returns {Promise<undefined>}
*/
async acceptInvite(invite) {
const code = DataResolver.resolveInviteCode(invite);
if (!code) throw new Error('INVITE_RESOLVE_CODE');
if (invite instanceof Invite) {
await invite.acceptInvite();
} else {
await this.api.invites(code).post({
data: {},
});
}
}
/** /**
* Automatically Redeem Nitro from raw message. * Automatically Redeem Nitro from raw message.
* @param {Message} message Discord Message * @param {Message} message Discord Message
@ -1005,9 +977,6 @@ class Client extends BaseClient {
if (options && typeof options.readyStatus !== 'boolean') { if (options && typeof options.readyStatus !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'readyStatus', 'a boolean'); throw new TypeError('CLIENT_INVALID_OPTION', 'readyStatus', 'a boolean');
} }
if (options && typeof options.autoCookie !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'autoCookie', 'a boolean');
}
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');
} }
@ -1025,12 +994,6 @@ class Client extends BaseClient {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 32 character string'); throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 32 character string');
} }
break; break;
case 'nopecha': {
if (options.captchaKey.length !== 16) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 16 character string');
}
break;
}
} }
} }
if (options && typeof options.DMSync !== 'boolean') { if (options && typeof options.DMSync !== 'boolean') {

View File

@ -25,6 +25,24 @@ class AutoModerationRuleManager extends CachedManager {
this.guild = guild; this.guild = guild;
} }
/**
* Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object.
* @method resolve
* @memberof AutoModerationRuleManager
* @instance
* @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve
* @returns {?AutoModerationRule}
*/
/**
* Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id.
* @method resolveId
* @memberof AutoModerationRuleManager
* @instance
* @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve
* @returns {?Snowflake}
*/
_add(data, cache) { _add(data, cache) {
return super._add(data, cache, { extras: [this.guild] }); return super._add(data, cache, { extras: [this.guild] });
} }
@ -265,24 +283,6 @@ class AutoModerationRuleManager extends CachedManager {
const autoModerationRuleId = this.resolveId(autoModerationRule); const autoModerationRuleId = this.resolveId(autoModerationRule);
await this.client.api.guilds(this.guild.id)('auto-moderation').rules(autoModerationRuleId).delete({ reason }); await this.client.api.guilds(this.guild.id)('auto-moderation').rules(autoModerationRuleId).delete({ reason });
} }
/**
* Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object.
* @method resolve
* @memberof AutoModerationRuleManager
* @instance
* @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve
* @returns {?AutoModerationRule}
*/
/**
* Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id.
* @method resolveId
* @memberof AutoModerationRuleManager
* @instance
* @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve
* @returns {?Snowflake}
*/
} }
module.exports = AutoModerationRuleManager; module.exports = AutoModerationRuleManager;

View File

@ -48,26 +48,32 @@ class APIRequest {
'Accept-Language': 'en-US,en;q=0.9', 'Accept-Language': 'en-US,en;q=0.9',
'Sec-Ch-Ua': `"Not?A_Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`, 'Sec-Ch-Ua': `"Not?A_Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`,
'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"', 'Sec-Ch-Ua-Platform': 'Windows',
'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Site': 'same-origin',
'X-Debug-Options': 'bugReporterEnabled', 'X-Debug-Options': 'bugReporterEnabled',
'X-Super-Properties': `${Buffer.from( 'X-Super-Properties': `${Buffer.from(
this.client.options.jsonTransformer(this.client.options.ws.properties), this.client.options.jsonTransformer({
...this.client.options.ws.properties,
browser_user_agent: this.client.options.http.headers['User-Agent'],
}),
'ascii', 'ascii',
).toString('base64')}`, ).toString('base64')}`,
'X-Discord-Locale': 'en-US', 'X-Discord-Locale': 'en-US',
'User-Agent': this.client.options.http.headers['User-Agent'], 'User-Agent': this.client.options.http.headers['User-Agent'],
Origin: 'https://discord.com',
Connection: 'keep-alive',
}; };
/* Remove
this.client.options.http.headers['User-Agent'] = this.fullUserAgent;
*/
if (this.options.auth !== false) headers.Authorization = this.rest.getAuth(); if (this.options.auth !== false) headers.Authorization = this.rest.getAuth();
if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason); if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason);
if (this.options.headers) headers = Object.assign(headers, this.options.headers); if (this.options.headers) headers = Object.assign(headers, this.options.headers);
if (this.options.webhook === true) {
headers = {
'User-Agent': this.client.options.http.headers['User-Agent'],
};
}
let body; let body;
if (this.options.files?.length) { if (this.options.files?.length) {

View File

@ -16,10 +16,17 @@ module.exports = class CaptchaSolver {
const lib = require('2captcha'); const lib = require('2captcha');
this.service = '2captcha'; this.service = '2captcha';
this.solver = new lib.Solver(key); this.solver = new lib.Solver(key);
this.solve = siteKey => this.solve = (data, userAgent) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const siteKey = data.captcha_sitekey;
const postD = data.captcha_rqdata
? {
data: data.captcha_rqdata,
userAgent,
}
: undefined;
this.solver this.solver
.hcaptcha(siteKey, 'discord.com') .hcaptcha(siteKey, 'https://discord.com/channels/@me', postD)
.then(res => { .then(res => {
resolve(res.data); resolve(res.data);
}) })
@ -30,31 +37,6 @@ module.exports = class CaptchaSolver {
throw this._missingModule('2captcha'); throw this._missingModule('2captcha');
} }
} }
case 'nopecha': {
if (!key || typeof key !== 'string') throw new Error('NopeCHA key is not provided');
try {
const { Configuration, NopeCHAApi } = require('nopecha');
const configuration = new Configuration({
apiKey: key,
});
this.service = 'nopecha';
this.solver = new NopeCHAApi(configuration);
this.solve = sitekey =>
new Promise((resolve, reject) => {
this.solver
.solveToken({
type: 'hcaptcha',
sitekey,
url: 'https://discord.com',
})
.then(res => (res ? resolve(res) : reject(new Error('Captcha could not be solved'))))
.catch(reject);
});
break;
} catch (e) {
throw this._missingModule('nopecha');
}
}
} }
} }
solve() {} solve() {}

View File

@ -11,6 +11,25 @@ const {
Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST }, Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST },
} = require('../util/Constants'); } = require('../util/Constants');
const cookieFilter = str => {
const blackList = ['expires', 'path', 'domain', 'httponly', 'secure', 'max-age', 'samesite'];
if (blackList.some(s => str.toLowerCase().includes(`${s}`))) return false;
return true;
};
function parseCookie(str, old) {
const oldProps = old.split(';').filter(cookieFilter);
const allProps = str.split(';').filter(cookieFilter);
// Update data from all to old
allProps.forEach(prop => {
const key = prop.split('=')[0];
const index = oldProps.findIndex(s => s.startsWith(key));
if (index !== -1) oldProps[index] = prop;
else oldProps.push(prop);
});
return oldProps.filter(s => s).join('; ');
}
const captchaMessage = [ const captchaMessage = [
'incorrect-captcha', 'incorrect-captcha',
'response-already-used', 'response-already-used',
@ -240,6 +259,20 @@ class RequestHandler {
let sublimitTimeout; let sublimitTimeout;
if (res.headers) { if (res.headers) {
// Cookie:
const cookie = res.headers.get('set-cookie');
if (cookie) {
if (typeof cookie == 'string') {
this.manager.client.options.http.headers.Cookie = parseCookie(
cookie,
this.manager.client.options.http.headers.Cookie || '',
);
this.manager.client.emit(
'debug',
`[REST] Set new cookie: ${this.manager.client.options.http.headers.Cookie}`,
);
}
}
const serverDate = res.headers.get('date'); const serverDate = res.headers.get('date');
const limit = res.headers.get('x-ratelimit-limit'); const limit = res.headers.get('x-ratelimit-limit');
const remaining = res.headers.get('x-ratelimit-remaining'); const remaining = res.headers.get('x-ratelimit-remaining');
@ -368,7 +401,10 @@ class RequestHandler {
Route : ${request.route} Route : ${request.route}
Info : ${inspect(data, { depth: null })}`, Info : ${inspect(data, { depth: null })}`,
); );
const captcha = await this.manager.captchaService.solve(data.captcha_sitekey); const captcha = await this.manager.captchaService.solve(
data,
this.manager.client.options.http.headers['User-Agent'],
);
this.manager.client.emit( this.manager.client.emit(
DEBUG, DEBUG,
`Captcha solved. `Captcha solved.

View File

@ -343,10 +343,12 @@ class Invite extends Base {
}, },
}); });
const guild = this.client.guilds.cache.get(this.guild.id); const guild = this.client.guilds.cache.get(this.guild.id);
/*
//
if (autoVerify) { if (autoVerify) {
console.warn('Feature is under maintenance - Invite#acceptInvite(true)'); console.warn('Feature is under maintenance - Invite#acceptInvite(true)');
} }
/* Disabled */
if (autoVerify) { if (autoVerify) {
const getForm = await this.client.api const getForm = await this.client.api
.guilds(this.guild.id) .guilds(this.guild.id)
@ -358,7 +360,6 @@ class Invite extends Base {
// https://discord.com/api/v9/guilds/:id/requests/@me // https://discord.com/api/v9/guilds/:id/requests/@me
await this.client.api.guilds(this.guild.id).requests['@me'].put({ data: { form_fields: [form] } }); await this.client.api.guilds(this.guild.id).requests['@me'].put({ data: { form_fields: [form] } });
} }
*/
return guild; return guild;
} }
} }

View File

@ -201,6 +201,7 @@ class Webhook {
query: { thread_id: messagePayload.options.threadId, wait: true }, query: { thread_id: messagePayload.options.threadId, wait: true },
auth: false, auth: false,
versioned: true, versioned: true,
webhook: true,
}); });
return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? d; return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? d;
} }
@ -230,6 +231,7 @@ class Webhook {
query: { wait: true }, query: { wait: true },
auth: false, auth: false,
data: body, data: body,
webhook: true,
}); });
return data.toString() === 'ok'; return data.toString() === 'ok';
} }
@ -257,6 +259,7 @@ class Webhook {
data: { name, avatar, channel_id: channel }, data: { name, avatar, channel_id: channel },
reason, reason,
auth: !this.token || Boolean(channel), auth: !this.token || Boolean(channel),
webhook: true,
}); });
this.name = data.name; this.name = data.name;
@ -305,6 +308,7 @@ class Webhook {
thread_id: cacheOrOptions.threadId, thread_id: cacheOrOptions.threadId,
}, },
auth: false, auth: false,
webhook: true,
}); });
return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cacheOrOptions.cache) ?? data; return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cacheOrOptions.cache) ?? data;
} }
@ -337,6 +341,7 @@ class Webhook {
thread_id: messagePayload.options.threadId, thread_id: messagePayload.options.threadId,
}, },
auth: false, auth: false,
webhook: true,
}); });
const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages; const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages;
@ -356,7 +361,7 @@ class Webhook {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async delete(reason) { async delete(reason) {
await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token }); await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token, webhook: true });
} }
/** /**
@ -376,6 +381,7 @@ class Webhook {
thread_id: threadId, thread_id: threadId,
}, },
auth: false, auth: false,
webhook: true,
}); });
} }

View File

@ -21,10 +21,9 @@ exports.MaxBulkDeletableMessageAge = 1_209_600_000;
/** /**
* API captcha solver * API captcha solver
* * `2captcha` - 2captcha.com * * `2captcha` - 2captcha.com
* * `nopecha` - nopecha.com
* @typedef {string[]} captchaServices * @typedef {string[]} captchaServices
*/ */
exports.captchaServices = ['2captcha', 'nopecha']; exports.captchaServices = ['2captcha'];
/** /**
* Automatically scan and delete direct messages you receive that contain explicit media content. * Automatically scan and delete direct messages you receive that contain explicit media content.
@ -1770,10 +1769,13 @@ function createEnum(keys) {
* @property {Object<ApplicationCommandType, number>} ApplicationCommandTypes * @property {Object<ApplicationCommandType, number>} ApplicationCommandTypes
* The type of an {@link ApplicationCommand} object. * The type of an {@link ApplicationCommand} object.
* @property {Object<AutoModerationRuleTriggerType, number>} AutoModerationRuleTriggerTypes Characterizes the type * @property {Object<AutoModerationRuleTriggerType, number>} AutoModerationRuleTriggerTypes Characterizes the type
* of contentwhich can trigger the rule. * of content which can trigger the rule.
* @property {Object<AutoModerationActionType, number>} AutoModerationActionTypes * @property {Object<AutoModerationActionType, number>} AutoModerationActionTypes
* A type of an action which executes whenever a rule is triggered.
* @property {Object<AutoModerationRuleKeywordPresetType, number>} AutoModerationRuleKeywordPresetTypes * @property {Object<AutoModerationRuleKeywordPresetType, number>} AutoModerationRuleKeywordPresetTypes
* @property {Object<AutoModerationRuleEventType, number>} AutoModerationRuleEventTypes * The internally pre-defined wordsetswhich will be searched for in content
* @property {Object<AutoModerationRuleEventType, number>} AutoModerationRuleEventTypes Indicates in what event context
* a rule should be checked.
* @property {Object<ChannelType, number>} ChannelTypes All available channel types. * @property {Object<ChannelType, number>} ChannelTypes All available channel types.
* @property {ClientApplicationAssetTypes} ClientApplicationAssetTypes The types of an {@link ApplicationAsset} object. * @property {ClientApplicationAssetTypes} ClientApplicationAssetTypes The types of an {@link ApplicationAsset} object.
* @property {Object<Color, number>} Colors An object with regularly used colors. * @property {Object<Color, number>} Colors An object with regularly used colors.

View File

@ -38,7 +38,6 @@ const { randomUA } = require('../util/Constants');
* from the WebSocket. Don't have this too high/low. Its best to have it between 2_000-6_000 ms. * from the WebSocket. Don't have this too high/low. Its best to have it between 2_000-6_000 ms.
* @property {boolean} [checkUpdate=true] Display module update information on the screen * @property {boolean} [checkUpdate=true] Display module update information on the screen
* @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} [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 {@link captchaServices} * @property {string} [captchaService=null] Captcha service to use for solving captcha {@link captchaServices}
* @property {string} [captchaKey=null] Captcha service key * @property {string} [captchaKey=null] Captcha service key
@ -152,7 +151,6 @@ class Options extends null {
closeTimeout: 5_000, closeTimeout: 5_000,
checkUpdate: true, checkUpdate: true,
readyStatus: true, readyStatus: true,
autoCookie: true,
autoRedeemNitro: false, autoRedeemNitro: false,
captchaService: '', captchaService: '',
captchaKey: null, captchaKey: null,

View File

@ -431,10 +431,10 @@ new DiscordAuthWebsocket({
headers: { headers: {
Accept: '*/*', Accept: '*/*',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
Pragma: 'no-cache', Pragma: 'no-cache',
'Sec-Ch-Ua': `"Google Chrome";v="${chromeVersion}", "Chromium";v="${chromeVersion}", "Not=A?Brand";v="24"`, 'Accept-Language': 'en-US,en;q=0.9',
'Sec-Ch-Ua': `"Not?A_Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`,
'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"', 'Sec-Ch-Ua-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Dest': 'empty',
@ -446,6 +446,7 @@ new DiscordAuthWebsocket({
)}`, )}`,
'X-Discord-Locale': 'en-US', 'X-Discord-Locale': 'en-US',
'User-Agent': this.options.userAgent, 'User-Agent': this.options.userAgent,
Origin: 'https://discord.com',
}, },
}, },
) )

4
typings/index.d.ts vendored
View File

@ -856,7 +856,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
// Added // Added
public settings: ClientSettingManager; public settings: ClientSettingManager;
public relationships: RelationshipManager; public relationships: RelationshipManager;
public updateCookie(): Promise<void>;
public readonly callVoice?: VoiceConnection; public readonly callVoice?: VoiceConnection;
public voiceStates: VoiceStateManager; public voiceStates: VoiceStateManager;
// End // End
@ -4767,7 +4766,6 @@ export interface ClientOptions {
// add // add
checkUpdate?: boolean; checkUpdate?: boolean;
readyStatus?: boolean; readyStatus?: boolean;
autoCookie?: boolean;
autoRedeemNitro?: boolean; autoRedeemNitro?: boolean;
patchVoice?: boolean; patchVoice?: boolean;
password?: string; password?: string;
@ -4779,7 +4777,7 @@ export interface ClientOptions {
usingNewAttachmentAPI?: boolean; usingNewAttachmentAPI?: boolean;
} }
export type captchaServices = '2captcha' | 'nopecha'; export type captchaServices = '2captcha';
// end copy // end copy