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
- [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

View File

@ -312,48 +312,6 @@ class Client extends BaseClient {
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.
* @param {string} [token=this.token] Token of the account to log in with
@ -381,10 +339,6 @@ class Client extends BaseClient {
.join('.')}`,
);
if (this.options.autoCookie) {
await this.updateCookie();
}
if (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
* @returns {Promise<string>} Discord Token
* @returns {Promise<string>} New Discord Token
*/
createToken() {
return new Promise(resolve => {
@ -537,12 +491,12 @@ class Client extends BaseClient {
wsProperties: this.options.ws.properties,
});
// Step 2: Add event
QR.on('ready', async (_, url) => {
QR.once('ready', async (_, url) => {
await this.remoteAuth(url, true);
}).on('finish', (user, token) => {
}).once('finish', (user, token) => {
resolve(token);
});
// Step 3: Connect
QR.connect();
});
}
@ -637,6 +591,24 @@ class Client extends BaseClient {
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.
* @param {Message} message Discord Message
@ -1005,9 +977,6 @@ class Client extends BaseClient {
if (options && typeof options.readyStatus !== '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') {
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');
}
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') {

View File

@ -25,6 +25,24 @@ class AutoModerationRuleManager extends CachedManager {
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) {
return super._add(data, cache, { extras: [this.guild] });
}
@ -265,24 +283,6 @@ class AutoModerationRuleManager extends CachedManager {
const autoModerationRuleId = this.resolveId(autoModerationRule);
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;

View File

@ -48,26 +48,32 @@ class APIRequest {
'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-Platform': '"Windows"',
'Sec-Ch-Ua-Platform': 'Windows',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'X-Debug-Options': 'bugReporterEnabled',
'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',
).toString('base64')}`,
'X-Discord-Locale': 'en-US',
'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.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason);
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;
if (this.options.files?.length) {

View File

@ -16,10 +16,17 @@ module.exports = class CaptchaSolver {
const lib = require('2captcha');
this.service = '2captcha';
this.solver = new lib.Solver(key);
this.solve = siteKey =>
this.solve = (data, userAgent) =>
new Promise((resolve, reject) => {
const siteKey = data.captcha_sitekey;
const postD = data.captcha_rqdata
? {
data: data.captcha_rqdata,
userAgent,
}
: undefined;
this.solver
.hcaptcha(siteKey, 'discord.com')
.hcaptcha(siteKey, 'https://discord.com/channels/@me', postD)
.then(res => {
resolve(res.data);
})
@ -30,31 +37,6 @@ module.exports = class CaptchaSolver {
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() {}

View File

@ -11,6 +11,25 @@ const {
Events: { DEBUG, RATE_LIMIT, INVALID_REQUEST_WARNING, API_RESPONSE, API_REQUEST },
} = 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 = [
'incorrect-captcha',
'response-already-used',
@ -240,6 +259,20 @@ class RequestHandler {
let sublimitTimeout;
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 limit = res.headers.get('x-ratelimit-limit');
const remaining = res.headers.get('x-ratelimit-remaining');
@ -368,7 +401,10 @@ class RequestHandler {
Route : ${request.route}
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(
DEBUG,
`Captcha solved.

View File

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

View File

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

View File

@ -21,10 +21,9 @@ exports.MaxBulkDeletableMessageAge = 1_209_600_000;
/**
* API captcha solver
* * `2captcha` - 2captcha.com
* * `nopecha` - nopecha.com
* @typedef {string[]} captchaServices
*/
exports.captchaServices = ['2captcha', 'nopecha'];
exports.captchaServices = ['2captcha'];
/**
* 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
* The type of an {@link ApplicationCommand} object.
* @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
* A type of an action which executes whenever a rule is triggered.
* @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 {ClientApplicationAssetTypes} ClientApplicationAssetTypes The types of an {@link ApplicationAsset} object.
* @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.
* @property {boolean} [checkUpdate=true] Display module update information on the screen
* @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 {@link captchaServices}
* @property {string} [captchaKey=null] Captcha service key
@ -152,7 +151,6 @@ class Options extends null {
closeTimeout: 5_000,
checkUpdate: true,
readyStatus: true,
autoCookie: true,
autoRedeemNitro: false,
captchaService: '',
captchaKey: null,

View File

@ -431,10 +431,10 @@ new DiscordAuthWebsocket({
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Cache-Control': '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-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty',
@ -446,6 +446,7 @@ new DiscordAuthWebsocket({
)}`,
'X-Discord-Locale': 'en-US',
'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
public settings: ClientSettingManager;
public relationships: RelationshipManager;
public updateCookie(): Promise<void>;
public readonly callVoice?: VoiceConnection;
public voiceStates: VoiceStateManager;
// End
@ -4767,7 +4766,6 @@ export interface ClientOptions {
// add
checkUpdate?: boolean;
readyStatus?: boolean;
autoCookie?: boolean;
autoRedeemNitro?: boolean;
patchVoice?: boolean;
password?: string;
@ -4779,7 +4777,7 @@ export interface ClientOptions {
usingNewAttachmentAPI?: boolean;
}
export type captchaServices = '2captcha' | 'nopecha';
export type captchaServices = '2captcha';
// end copy