diff --git a/Document/RichPresence.md b/Document/RichPresence.md index 75e6f2f..448c967 100644 --- a/Document/RichPresence.md +++ b/Document/RichPresence.md @@ -1,7 +1,7 @@ ## Setup ```js const client = new Client({ - readyStatus: false, + syncStatus: false, }); ``` diff --git a/package.json b/package.json index 0aa75e0..09f1480 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/client/Client.js b/src/client/Client.js index 7c616c4..821acc9 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -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} */ - 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'); diff --git a/src/client/websocket/WebSocketShard.js b/src/client/websocket/WebSocketShard.js index bc9109b..cf98637 100644 --- a/src/client/websocket/WebSocketShard.js +++ b/src/client/websocket/WebSocketShard.js @@ -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(); } /** diff --git a/src/client/websocket/handlers/READY.js b/src/client/websocket/handlers/READY.js index ad4b86e..beec814 100644 --- a/src/client/websocket/handlers/READY.js +++ b/src/client/websocket/handlers/READY.js @@ -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; diff --git a/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js b/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js index 9a3cbc9..7f90916 100644 --- a/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js +++ b/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js @@ -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); diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 5dad37e..fbf5a3d 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -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); diff --git a/src/managers/GuildSettingManager.js b/src/managers/GuildSettingManager.js index bb7e30f..dd0131d 100644 --- a/src/managers/GuildSettingManager.js +++ b/src/managers/GuildSettingManager.js @@ -139,8 +139,8 @@ class GuildSettingManager extends BaseManager { * @returns {Promise} */ 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; } } diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index e54ad05..a07ed59 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -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 diff --git a/src/structures/interfaces/InteractionResponses.js b/src/structures/interfaces/InteractionResponses.js index 6d99964..a73f582 100644 --- a/src/structures/interfaces/InteractionResponses.js +++ b/src/structures/interfaces/InteractionResponses.js @@ -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; } diff --git a/src/util/Constants.js b/src/util/Constants.js index 480b03c..373df35 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -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', ]; /** diff --git a/src/util/Options.js b/src/util/Options.js index fb02dc3..5c339ac 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -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, diff --git a/src/util/Util.js b/src/util/Util.js index 50ca7a3..8acc1e9 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -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; diff --git a/typings/index.d.ts b/typings/index.d.ts index 3433540..d4635a0 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -867,13 +867,6 @@ export interface remoteAuthConfrim { yes(): Promise; no(): Promise; } - -export interface switchUserOptions { - username: string; - password: string; - mfaCode?: number; -} - export class Client extends BaseClient { public constructor(options?: ClientOptions); /* Bug report by Mavri#0001 [721347809667973141] */ private actions: unknown; @@ -921,7 +914,7 @@ export class Client extends BaseClient { public generateInvite(options?: InviteGenerationOptions): string; public login(token?: string): Promise; public normalLogin(username: string, password?: string, mfaCode?: string): Promise; - public switchUser(options: string | switchUserOptions): void; + public switchUser(token: string): void; public QRLogin(debug?: boolean): DiscordAuthWebsocket; public remoteAuth(url: string, forceAccept?: boolean): Promise; public createToken(): Promise; @@ -932,7 +925,7 @@ export class Client extends BaseClient { public customStatusAuto(client?: this): undefined; public authorizeURL(url: string, options?: object): Promise; public sleep(milliseconds: number): Promise | null; - public clearCache(cache: Collection): void; + private _clearCache(cache: Collection): void; public toJSON(): unknown; public on(event: K, listener: (...args: ClientEvents[K]) => Awaitable): this; @@ -4870,7 +4863,7 @@ export interface ClientOptions { rejectOnRateLimit?: string[] | ((data: RateLimitData) => boolean | Promise); // add checkUpdate?: boolean; - readyStatus?: boolean; + syncStatus?: boolean; autoRedeemNitro?: boolean; patchVoice?: boolean; password?: string;