feat: Switch User

test #477
This commit is contained in:
March 7th 2023-01-26 13:40:51 +07:00
parent e8c3bcb0a0
commit 4c838a5e26
14 changed files with 79 additions and 60 deletions

View File

@ -1,7 +1,7 @@
## Setup ## Setup
```js ```js
const client = new Client({ const client = new Client({
readyStatus: false, syncStatus: false,
}); });
``` ```

View File

@ -65,7 +65,6 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"proxy-agent": "^5.0.0",
"safe-base64": "^2.0.1-0", "safe-base64": "^2.0.1-0",
"string_decoder": "^1.3.0", "string_decoder": "^1.3.0",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",

View File

@ -40,7 +40,7 @@ const Options = require('../util/Options');
const Permissions = require('../util/Permissions'); const Permissions = require('../util/Permissions');
const DiscordAuthWebsocket = require('../util/RemoteAuth'); const DiscordAuthWebsocket = require('../util/RemoteAuth');
const Sweepers = require('../util/Sweepers'); const Sweepers = require('../util/Sweepers');
const { lazy } = require('../util/Util'); const { lazy, testImportModule } = require('../util/Util');
const Message = lazy(() => require('../structures/Message').Message); const Message = lazy(() => require('../structures/Message').Message);
// Patch // Patch
@ -420,22 +420,19 @@ class Client extends BaseClient {
/** /**
* Switch the user * Switch the user
* @param {string | switchUserOptions} options Either the token or an object with the username, password, and mfaCode * @param {string} token User Token
* @returns {Promise<string>}
*/ */
async switchUser(options) { switchUser(token) {
await this.logout(); this._clearCache(this.emojis.cache);
// There is a better way to code this but it's a temp fix - TheDevYellowy this._clearCache(this.guilds.cache);
await this.clearCache(this.channels.cache); this._clearCache(this.channels.cache);
await this.clearCache(this.guilds.cache); this._clearCache(this.users.cache);
await this.clearCache(this.relationships.cache); this._clearCache(this.relationships.cache);
await this.clearCache(this.sessions.cache); this._clearCache(this.sessions.cache);
await this.clearCache(this.users.cache); this._clearCache(this.voiceStates.cache);
await this.clearCache(this.voiceStates.cache); this.ws.status = Status.IDLE;
if (typeof options == 'string') { return this.login(token);
await this.login(options);
} else {
await this.normalLogin(options.username, options.password, options.mfaCode);
}
} }
/** /**
@ -776,11 +773,11 @@ class Client extends BaseClient {
/** /**
* Clear a cache * Clear a cache
* @param {Collection} cache The cache to clear * @param {Collection} cache The cache to clear
* @returns {number} The number of removed entries
* @private
*/ */
async clearCache(cache) { _clearCache(cache) {
await cache.forEach(async (V, K) => { return cache.sweep(() => true);
await cache.delete(K);
});
} }
/** /**
@ -924,21 +921,26 @@ class Client extends BaseClient {
/** /**
* Sets the client's presence. (Sync Setting). * Sets the client's presence. (Sync Setting).
* @param {Client} client Discord Client * @param {Client} client Discord Client
* @private
*/ */
customStatusAuto(client) { customStatusAuto(client) {
client = client ?? this; client = client ?? this;
if (!client.user) return;
const custom_status = new CustomStatus(); const custom_status = new CustomStatus();
if (client.settings.rawSetting.custom_status?.text || client.settings.rawSetting.custom_status?.emoji_name) { if (!client.settings.rawSetting.custom_status?.text && !client.settings.rawSetting.custom_status?.emoji_name) {
client.user.setPresence({
activities: this.presence.activities.filter(a => a.type !== 'CUSTOM'),
status: client.settings.rawSetting.status ?? 'invisible',
});
} else {
custom_status.setEmoji({ custom_status.setEmoji({
name: client.settings.rawSetting.custom_status?.emoji_name, name: client.settings.rawSetting.custom_status?.emoji_name,
id: client.settings.rawSetting.custom_status?.emoji_id, id: client.settings.rawSetting.custom_status?.emoji_id,
}); });
custom_status.setState(client.settings.rawSetting.custom_status?.text); custom_status.setState(client.settings.rawSetting.custom_status?.text);
client.user.setPresence({ client.user.setPresence({
activities: custom_status activities: [custom_status.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')],
? [custom_status.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')] status: client.settings.rawSetting.status ?? 'invisible',
: this.presence.activities.filter(a => a.type !== 'CUSTOM'),
status: client.settings.rawSetting.status,
}); });
} }
} }
@ -1012,8 +1014,8 @@ class Client extends BaseClient {
if (options && typeof options.checkUpdate !== 'boolean') { if (options && typeof options.checkUpdate !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'checkUpdate', 'a boolean'); throw new TypeError('CLIENT_INVALID_OPTION', 'checkUpdate', 'a boolean');
} }
if (options && typeof options.readyStatus !== 'boolean') { if (options && typeof options.syncStatus !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'readyStatus', 'a boolean'); throw new TypeError('CLIENT_INVALID_OPTION', 'syncStatus', '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');
@ -1054,6 +1056,13 @@ class Client extends BaseClient {
} }
if (options && typeof options.proxy !== 'string') { if (options && typeof options.proxy !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'proxy', 'a string'); throw new TypeError('CLIENT_INVALID_OPTION', 'proxy', 'a string');
} else if (
options &&
options.proxy &&
typeof options.proxy === 'string' &&
testImportModule('proxy-agent') === false
) {
throw new Error('MISSING_MODULE', 'proxy-agent', 'npm install proxy-agent');
} }
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) { if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) {
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1'); throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1');

View File

@ -2,7 +2,6 @@
const EventEmitter = require('node:events'); const EventEmitter = require('node:events');
const { setTimeout, setInterval, clearTimeout } = require('node:timers'); const { setTimeout, setInterval, clearTimeout } = require('node:timers');
const proxy = require('proxy-agent');
const WebSocket = require('../../WebSocket'); const WebSocket = require('../../WebSocket');
const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants'); const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants');
const Intents = require('../../util/Intents'); const Intents = require('../../util/Intents');
@ -278,6 +277,7 @@ class WebSocketShard extends EventEmitter {
let args = { handshakeTimeout: 30_000 }; let args = { handshakeTimeout: 30_000 };
if (client.options.proxy.length > 0) { if (client.options.proxy.length > 0) {
const proxy = require('proxy-agent');
args.agent = new proxy(client.options.proxy); args.agent = new proxy(client.options.proxy);
this.debug(`Using proxy ${client.options.proxy}`, args); this.debug(`Using proxy ${client.options.proxy}`, args);
} }
@ -548,7 +548,8 @@ class WebSocketShard extends EventEmitter {
this.status = Status.READY; this.status = Status.READY;
this.emit(ShardEvents.ALL_READY, this.expectedGuilds); this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
}, hasGuildsIntent && waitGuildTimeout).unref(); // }, hasGuildsIntent && waitGuildTimeout).unref();
}, 1).unref();
} }
/** /**

View File

@ -94,7 +94,7 @@ module.exports = async (client, { d: data }, shard) => {
patchVoice(client); patchVoice(client);
} }
if (client.options.readyStatus) { if (client.options.syncStatus) {
client.customStatusAuto(client); client.customStatusAuto(client);
} }
firstReady = true; firstReady = true;

View File

@ -2,7 +2,7 @@
const { Events } = require('../../../util/Constants'); const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => { module.exports = (client, { d: data }) => {
client.settings._patch(data); client.settings._patch(data);
if (('status' in data || 'custom_status' in data) && client.options.readyStatus) { if (('status' in data || 'custom_status' in data) && client.options.syncStatus) {
client.customStatusAuto(client); client.customStatusAuto(client);
} }
return client.emit(Events.USER_SETTINGS_UPDATE, data); return client.emit(Events.USER_SETTINGS_UPDATE, data);

View File

@ -219,6 +219,9 @@ const Messages = {
GUILD_IS_LARGE: 'This guild is too large to fetch all members with this method', GUILD_IS_LARGE: 'This guild is too large to fetch all members with this method',
TEAM_MEMBER_FORMAT: 'The member provided is either not real or not of the User class', TEAM_MEMBER_FORMAT: 'The member provided is either not real or not of the User class',
MISSING_MODULE: (name, installCommand) =>
`The module "${name}" is missing. Please install it with "${installCommand}" and try again.`,
}; };
for (const [name, message] of Object.entries(Messages)) register(name, message); for (const [name, message] of Object.entries(Messages)) register(name, message);

View File

@ -139,8 +139,8 @@ class GuildSettingManager extends BaseManager {
* @returns {Promise<GuildSettingManager>} * @returns {Promise<GuildSettingManager>}
*/ */
async edit(data) { async edit(data) {
const data_ = await this.client.api.users('@me').settings.patch(data_); const data_ = await this.client.api.users('@me').settings.patch(data);
this._patch(data); this._patch(data_);
return this; return this;
} }
} }

View File

@ -5,7 +5,6 @@ const https = require('node:https');
const { setTimeout } = require('node:timers'); const { setTimeout } = require('node:timers');
const FormData = require('form-data'); const FormData = require('form-data');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const proxy = require('proxy-agent');
let agent = null; let agent = null;
@ -29,10 +28,14 @@ class APIRequest {
} }
make(captchaKey = undefined, captchaRqtoken = undefined) { make(captchaKey = undefined, captchaRqtoken = undefined) {
agent ??= if (agent === null) {
typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0 if (typeof this.client.options.proxy === 'string' && this.client.options.proxy.length > 0) {
? new proxy(this.client.options.proxy) const proxy = require('proxy-agent');
: new https.Agent({ ...this.client.options.http.agent, keepAlive: true }); agent = new proxy(this.client.options.proxy);
} else {
agent = new https.Agent({ ...this.client.options.http.agent, keepAlive: true });
}
}
const API = const API =
this.options.versioned === false this.options.versioned === false

View File

@ -147,7 +147,7 @@ class InteractionResponses {
*/ */
async editReply(options) { async editReply(options) {
if (!this.deferred && !this.replied) throw new Error('INTERACTION_NOT_REPLIED'); if (!this.deferred && !this.replied) throw new Error('INTERACTION_NOT_REPLIED');
const message = await this.webhook.editMessage(options.messsage ?? '@original', options); const message = await this.webhook.editMessage(options.message ?? '@original', options);
this.replied = true; this.replied = true;
return message; return message;
} }

View File

@ -6,10 +6,12 @@ const Package = (exports.Package = require('../../package.json'));
const { Error, RangeError, TypeError } = require('../errors'); const { Error, RangeError, TypeError } = require('../errors');
// #88: https://jnrbsn.github.io/user-agents/user-agents.json // #88: https://jnrbsn.github.io/user-agents/user-agents.json
const listUserAgent = [ const listUserAgent = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/107.0.1418.68', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.69',
]; ];
/** /**

View File

@ -37,7 +37,7 @@ const { randomUA } = require('../util/Constants');
* @property {number} [closeTimeout=5000] The amount of time in milliseconds to wait for the close frame to be received * @property {number} [closeTimeout=5000] The amount of time in milliseconds to wait for the close frame to be received
* 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} [syncStatus=true] Sync state with Discord Client
* @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
@ -160,7 +160,7 @@ class Options extends null {
captchaSolver: captcha => Promise.reject(new Error('CAPTCHA_SOLVER_NOT_IMPLEMENTED', captcha)), captchaSolver: captcha => Promise.reject(new Error('CAPTCHA_SOLVER_NOT_IMPLEMENTED', captcha)),
closeTimeout: 5_000, closeTimeout: 5_000,
checkUpdate: true, checkUpdate: true,
readyStatus: true, syncStatus: true,
autoRedeemNitro: false, autoRedeemNitro: false,
captchaService: '', captchaService: '',
captchaKey: null, captchaKey: null,
@ -197,14 +197,14 @@ class Options extends null {
browser: 'Chrome', browser: 'Chrome',
device: '', device: '',
system_locale: 'en-US', system_locale: 'en-US',
browser_version: '108.0.0.0', browser_version: '109.0.0.0',
os_version: '10', os_version: '10',
referrer: '', referrer: '',
referring_domain: '', referring_domain: '',
referrer_current: '', referrer_current: '',
referring_domain_current: '', referring_domain_current: '',
release_channel: 'stable', release_channel: 'stable',
client_build_number: 165485, client_build_number: 169617,
client_event_source: null, client_event_source: null,
}, },
// ! capabilities: 4093, // ! capabilities: 4093,

View File

@ -647,7 +647,7 @@ class Util extends null {
} }
/** /**
* Resolves the maximum time a guild's thread channels should automatcally archive in case of no recent activity. * Resolves the maximum time a guild's thread channels should automatically archive in case of no recent activity.
* @deprecated * @deprecated
* @returns {number} * @returns {number}
*/ */
@ -753,6 +753,15 @@ class Util extends null {
static uploadFile(data, url) { static uploadFile(data, url) {
return axios.put(url, data); return axios.put(url, data);
} }
static testImportModule(name) {
try {
require.resolve(name);
return true;
} catch {
return false;
}
}
} }
module.exports = Util; module.exports = Util;

13
typings/index.d.ts vendored
View File

@ -867,13 +867,6 @@ export interface remoteAuthConfrim {
yes(): Promise<undefined>; yes(): Promise<undefined>;
no(): Promise<undefined>; no(): Promise<undefined>;
} }
export interface switchUserOptions {
username: string;
password: string;
mfaCode?: number;
}
export class Client<Ready extends boolean = boolean> extends BaseClient { export class Client<Ready extends boolean = boolean> extends BaseClient {
public constructor(options?: ClientOptions); /* Bug report by Mavri#0001 [721347809667973141] */ public constructor(options?: ClientOptions); /* Bug report by Mavri#0001 [721347809667973141] */
private actions: unknown; private actions: unknown;
@ -921,7 +914,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public generateInvite(options?: InviteGenerationOptions): string; public generateInvite(options?: InviteGenerationOptions): string;
public login(token?: string): Promise<string>; public login(token?: string): Promise<string>;
public normalLogin(username: string, password?: string, mfaCode?: string): Promise<string>; public normalLogin(username: string, password?: string, mfaCode?: string): Promise<string>;
public switchUser(options: string | switchUserOptions): void; public switchUser(token: string): void;
public QRLogin(debug?: boolean): DiscordAuthWebsocket; public QRLogin(debug?: boolean): DiscordAuthWebsocket;
public remoteAuth(url: string, forceAccept?: boolean): Promise<remoteAuthConfrim | undefined>; public remoteAuth(url: string, forceAccept?: boolean): Promise<remoteAuthConfrim | undefined>;
public createToken(): Promise<string>; public createToken(): Promise<string>;
@ -932,7 +925,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public customStatusAuto(client?: this): undefined; public customStatusAuto(client?: this): undefined;
public authorizeURL(url: string, options?: object): Promise<boolean>; public authorizeURL(url: string, options?: object): Promise<boolean>;
public sleep(milliseconds: number): Promise<void> | null; public sleep(milliseconds: number): Promise<void> | null;
public clearCache(cache: Collection<any, any>): void; private _clearCache(cache: Collection<any, any>): void;
public toJSON(): unknown; public toJSON(): unknown;
public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): this; public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): this;
@ -4870,7 +4863,7 @@ export interface ClientOptions {
rejectOnRateLimit?: string[] | ((data: RateLimitData) => boolean | Promise<boolean>); rejectOnRateLimit?: string[] | ((data: RateLimitData) => boolean | Promise<boolean>);
// add // add
checkUpdate?: boolean; checkUpdate?: boolean;
readyStatus?: boolean; syncStatus?: boolean;
autoRedeemNitro?: boolean; autoRedeemNitro?: boolean;
patchVoice?: boolean; patchVoice?: boolean;
password?: string; password?: string;