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
```js
const client = new Client({
readyStatus: false,
syncStatus: false,
});
```

View File

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

View File

@ -40,7 +40,7 @@ const Options = require('../util/Options');
const Permissions = require('../util/Permissions');
const DiscordAuthWebsocket = require('../util/RemoteAuth');
const Sweepers = require('../util/Sweepers');
const { lazy } = require('../util/Util');
const { lazy, testImportModule } = require('../util/Util');
const Message = lazy(() => require('../structures/Message').Message);
// Patch
@ -420,22 +420,19 @@ class Client extends BaseClient {
/**
* 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) {
await this.logout();
// There is a better way to code this but it's a temp fix - TheDevYellowy
await this.clearCache(this.channels.cache);
await this.clearCache(this.guilds.cache);
await this.clearCache(this.relationships.cache);
await this.clearCache(this.sessions.cache);
await this.clearCache(this.users.cache);
await this.clearCache(this.voiceStates.cache);
if (typeof options == 'string') {
await this.login(options);
} else {
await this.normalLogin(options.username, options.password, options.mfaCode);
}
switchUser(token) {
this._clearCache(this.emojis.cache);
this._clearCache(this.guilds.cache);
this._clearCache(this.channels.cache);
this._clearCache(this.users.cache);
this._clearCache(this.relationships.cache);
this._clearCache(this.sessions.cache);
this._clearCache(this.voiceStates.cache);
this.ws.status = Status.IDLE;
return this.login(token);
}
/**
@ -776,11 +773,11 @@ class Client extends BaseClient {
/**
* Clear a cache
* @param {Collection} cache The cache to clear
* @returns {number} The number of removed entries
* @private
*/
async clearCache(cache) {
await cache.forEach(async (V, K) => {
await cache.delete(K);
});
_clearCache(cache) {
return cache.sweep(() => true);
}
/**
@ -924,21 +921,26 @@ class Client extends BaseClient {
/**
* Sets the client's presence. (Sync Setting).
* @param {Client} client Discord Client
* @private
*/
customStatusAuto(client) {
client = client ?? this;
if (!client.user) return;
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({
name: client.settings.rawSetting.custom_status?.emoji_name,
id: client.settings.rawSetting.custom_status?.emoji_id,
});
custom_status.setState(client.settings.rawSetting.custom_status?.text);
client.user.setPresence({
activities: custom_status
? [custom_status.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')]
: this.presence.activities.filter(a => a.type !== 'CUSTOM'),
status: client.settings.rawSetting.status,
activities: [custom_status.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')],
status: client.settings.rawSetting.status ?? 'invisible',
});
}
}
@ -1012,8 +1014,8 @@ class Client extends BaseClient {
if (options && typeof options.checkUpdate !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'checkUpdate', 'a boolean');
}
if (options && typeof options.readyStatus !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'readyStatus', 'a boolean');
if (options && typeof options.syncStatus !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'syncStatus', 'a boolean');
}
if (options && typeof options.autoRedeemNitro !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'autoRedeemNitro', 'a boolean');
@ -1054,6 +1056,13 @@ class Client extends BaseClient {
}
if (options && typeof options.proxy !== '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) {
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 { setTimeout, setInterval, clearTimeout } = require('node:timers');
const proxy = require('proxy-agent');
const WebSocket = require('../../WebSocket');
const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants');
const Intents = require('../../util/Intents');
@ -278,6 +277,7 @@ class WebSocketShard extends EventEmitter {
let args = { handshakeTimeout: 30_000 };
if (client.options.proxy.length > 0) {
const proxy = require('proxy-agent');
args.agent = new proxy(client.options.proxy);
this.debug(`Using proxy ${client.options.proxy}`, args);
}
@ -548,7 +548,8 @@ class WebSocketShard extends EventEmitter {
this.status = Status.READY;
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);
}
if (client.options.readyStatus) {
if (client.options.syncStatus) {
client.customStatusAuto(client);
}
firstReady = true;

View File

@ -2,7 +2,7 @@
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: 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);
}
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',
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);

View File

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

View File

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

View File

@ -147,7 +147,7 @@ class InteractionResponses {
*/
async editReply(options) {
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;
return message;
}

View File

@ -6,10 +6,12 @@ const Package = (exports.Package = require('../../package.json'));
const { Error, RangeError, TypeError } = require('../errors');
// #88: https://jnrbsn.github.io/user-agents/user-agents.json
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; WOW64) 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/108.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) 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/109.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; 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
* 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} [syncStatus=true] Sync state with Discord Client
* @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
@ -160,7 +160,7 @@ class Options extends null {
captchaSolver: captcha => Promise.reject(new Error('CAPTCHA_SOLVER_NOT_IMPLEMENTED', captcha)),
closeTimeout: 5_000,
checkUpdate: true,
readyStatus: true,
syncStatus: true,
autoRedeemNitro: false,
captchaService: '',
captchaKey: null,
@ -197,14 +197,14 @@ class Options extends null {
browser: 'Chrome',
device: '',
system_locale: 'en-US',
browser_version: '108.0.0.0',
browser_version: '109.0.0.0',
os_version: '10',
referrer: '',
referring_domain: '',
referrer_current: '',
referring_domain_current: '',
release_channel: 'stable',
client_build_number: 165485,
client_build_number: 169617,
client_event_source: null,
},
// ! 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
* @returns {number}
*/
@ -753,6 +753,15 @@ class Util extends null {
static uploadFile(data, url) {
return axios.put(url, data);
}
static testImportModule(name) {
try {
require.resolve(name);
return true;
} catch {
return false;
}
}
}
module.exports = Util;

13
typings/index.d.ts vendored
View File

@ -867,13 +867,6 @@ export interface remoteAuthConfrim {
yes(): Promise<undefined>;
no(): Promise<undefined>;
}
export interface switchUserOptions {
username: string;
password: string;
mfaCode?: number;
}
export class Client<Ready extends boolean = boolean> extends BaseClient {
public constructor(options?: ClientOptions); /* Bug report by Mavri#0001 [721347809667973141] */
private actions: unknown;
@ -921,7 +914,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public generateInvite(options?: InviteGenerationOptions): string;
public login(token?: 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 remoteAuth(url: string, forceAccept?: boolean): Promise<remoteAuthConfrim | undefined>;
public createToken(): Promise<string>;
@ -932,7 +925,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public customStatusAuto(client?: this): undefined;
public authorizeURL(url: string, options?: object): Promise<boolean>;
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 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>);
// add
checkUpdate?: boolean;
readyStatus?: boolean;
syncStatus?: boolean;
autoRedeemNitro?: boolean;
patchVoice?: boolean;
password?: string;