This commit is contained in:
Elysia
2024-01-14 12:33:11 +07:00
parent e15b9ab7fe
commit 039dd34cf2
27 changed files with 1250 additions and 5297 deletions

View File

@@ -1,46 +1,40 @@
'use strict';
const process = require('node:process');
const { setInterval, setTimeout } = require('node:timers');
const { setInterval } = require('node:timers');
const { setTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { getVoiceConnection } = require('@discordjs/voice');
const chalk = require('chalk');
const fetch = require('node-fetch');
const BaseClient = require('./BaseClient');
const ActionsManager = require('./actions/ActionsManager');
const ClientVoiceManager = require('./voice/ClientVoiceManager');
const WebSocketManager = require('./websocket/WebSocketManager');
const { Error, TypeError, RangeError } = require('../errors');
const Discord = require('../index');
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
const BillingManager = require('../managers/BillingManager');
const ChannelManager = require('../managers/ChannelManager');
const ClientUserSettingManager = require('../managers/ClientUserSettingManager');
const DeveloperPortalManager = require('../managers/DeveloperPortalManager');
const GuildManager = require('../managers/GuildManager');
const PresenceManager = require('../managers/PresenceManager');
const RelationshipManager = require('../managers/RelationshipManager');
const SessionManager = require('../managers/SessionManager');
const UserManager = require('../managers/UserManager');
const UserNoteManager = require('../managers/UserNoteManager');
const VoiceStateManager = require('../managers/VoiceStateManager');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const ClientPresence = require('../structures/ClientPresence');
const GuildPreview = require('../structures/GuildPreview');
const GuildTemplate = require('../structures/GuildTemplate');
const Invite = require('../structures/Invite');
const { CustomStatus } = require('../structures/RichPresence');
const { Sticker } = require('../structures/Sticker');
const StickerPack = require('../structures/StickerPack');
const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook');
const Widget = require('../structures/Widget');
const { Events, InviteScopes, Status, captchaServices } = require('../util/Constants');
const { Events, Status } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Intents = require('../util/Intents');
const Options = require('../util/Options');
const Permissions = require('../util/Permissions');
const DiscordAuthWebsocket = require('../util/RemoteAuth');
const Sweepers = require('../util/Sweepers');
const { getProxyObject } = require('../util/Util');
/**
* The main hub for interacting with the Discord API, and the starting point for any bot.
@@ -50,7 +44,7 @@ class Client extends BaseClient {
/**
* @param {ClientOptions} options Options for the client
*/
constructor(options = {}) {
constructor(options) {
super(options);
const data = require('node:worker_threads').workerData ?? process.env;
@@ -141,17 +135,6 @@ class Client extends BaseClient {
*/
this.users = new UserManager(this);
// Patch
/**
* All of the relationships {@link User}
* @type {RelationshipManager}
*/
this.relationships = new RelationshipManager(this);
/**
* All of the settings {@link Object}
* @type {ClientUserSettingManager}
*/
this.settings = new ClientUserSettingManager(this);
/**
* All of the guilds the client is currently handling, mapped by their ids -
* as long as sharding isn't being used, this will be *every* guild the bot is a member of
@@ -159,18 +142,6 @@ class Client extends BaseClient {
*/
this.guilds = new GuildManager(this);
/**
* Manages the API methods
* @type {BillingManager}
*/
this.billing = new BillingManager(this);
/**
* All of the sessions of the client
* @type {SessionManager}
*/
this.sessions = new SessionManager(this);
/**
* All of the {@link Channel}s that the client is currently handling, mapped by their ids -
* as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
@@ -186,12 +157,6 @@ class Client extends BaseClient {
*/
this.sweepers = new Sweepers(this, this.options.sweepers);
/**
* The developer portal manager of the client
* @type {DeveloperPortalManager}
*/
this.developerPortal = new DeveloperPortalManager(this);
/**
* The presence of the Client
* @private
@@ -199,6 +164,30 @@ class Client extends BaseClient {
*/
this.presence = new ClientPresence(this, this.options.presence);
/**
* A manager of the presences belonging to this client
* @type {PresenceManager}
*/
this.presences = new PresenceManager(this);
/**
* All of the note that have been cached at any point, mapped by their ids
* @type {UserManager}
*/
this.notes = new UserNoteManager(this);
/**
* All of the relationships {@link User}
* @type {RelationshipManager}
*/
this.relationships = new RelationshipManager(this);
/**
* Manages the API methods
* @type {BillingManager}
*/
this.billing = new BillingManager(this);
Object.defineProperty(this, 'token', { writable: true });
if (!this.token && 'DISCORD_TOKEN' in process.env) {
/**
@@ -212,20 +201,12 @@ class Client extends BaseClient {
this.token = null;
}
this._interactionCache = new Collection();
/**
* User that the client is logged in as
* @type {?ClientUser}
*/
this.user = null;
/**
* The application of this bot
* @type {?ClientApplication}
*/
this.application = null;
/**
* Time at which the client was last regarded as being in the `READY` state
* (each time the client disconnects and successfully reconnects, this will be overwritten)
@@ -233,12 +214,6 @@ class Client extends BaseClient {
*/
this.readyAt = null;
/**
* Password cache
* @type {?string}
*/
this.password = this.options.password;
if (this.options.messageSweepInterval > 0) {
process.emitWarning(
'The message sweeping client options are deprecated, use the global sweepers instead.',
@@ -251,15 +226,6 @@ class Client extends BaseClient {
}
}
/**
* Session ID
* @type {?string}
* @readonly
*/
get sessionId() {
return this.ws.shards.first()?.sessionId;
}
/**
* All custom emojis that the client has access to, mapped by their ids
* @type {BaseGuildEmojiManager}
@@ -291,19 +257,6 @@ class Client extends BaseClient {
return this.readyAt ? Date.now() - this.readyAt : null;
}
/**
* @external VoiceConnection
* @see {@link https://discord.js.org/#/docs/voice/main/class/VoiceConnection}
*/
/**
* Get connection to current call
* @type {?VoiceConnection}
* @readonly
*/
get callVoice() {
return getVoiceConnection(null);
}
/**
* Logs the client in, establishing a WebSocket connection to Discord.
* @param {string} [token=this.token] Token of the account to log in with
@@ -320,8 +273,7 @@ class Client extends BaseClient {
Logging on with a user token is unfortunately against the Discord
\`Terms of Service\` <https://support.discord.com/hc/en-us/articles/115002192352>
and doing so might potentially get your account banned.
Use this at your own risk.
`,
Use this at your own risk.`,
);
this.emit(
Events.DEBUG,
@@ -346,178 +298,10 @@ class Client extends BaseClient {
}
}
/**
* Login Discord with Username and Password
* @param {string} username Email or Phone Number
* @param {?string} password Password
* @param {?string} mfaCode 2FA Code / Backup Code
* @returns {Promise<string>}
*/
async normalLogin(username, password = this.password, mfaCode) {
if (!username || !password || typeof username !== 'string' || typeof password !== 'string') {
throw new Error('NORMAL_LOGIN');
}
this.emit(
Events.DEBUG,
`Connecting to Discord with:
username: ${username}
password: ${password.replace(/./g, '*')}`,
);
const data = await this.api.auth.login.post({
data: {
login: username,
password: password,
undelete: false,
captcha_key: null,
login_source: null,
gift_code_sku_id: null,
},
auth: false,
});
this.password = password;
if (!data.token && data.ticket && data.mfa) {
this.emit(Events.DEBUG, `Using 2FA Code: ${mfaCode}`);
const normal2fa = /(\d{6})/g;
const backupCode = /([a-z0-9]{4})-([a-z0-9]{4})/g;
if (!mfaCode || typeof mfaCode !== 'string') {
throw new Error('LOGIN_FAILED_2FA');
}
if (normal2fa.test(mfaCode) || backupCode.test(mfaCode)) {
const data2 = await this.api.auth.mfa.totp.post({
data: {
code: mfaCode,
ticket: data.ticket,
login_source: null,
gift_code_sku_id: null,
},
auth: false,
});
return this.login(data2.token);
} else {
throw new Error('LOGIN_FAILED_2FA');
}
} else if (data.token) {
return this.login(data.token);
} else {
throw new Error('LOGIN_FAILED_UNKNOWN');
}
}
/**
* Switch the user
* @param {string} token User Token
* @returns {Promise<string>}
*/
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);
}
/**
* Sign in with the QR code on your phone.
* @param {DiscordAuthWebsocketOptions} options Options
* @returns {DiscordAuthWebsocket}
* @example
* client.QRLogin();
*/
QRLogin(options = {}) {
const QR = new DiscordAuthWebsocket({ ...options, autoLogin: true });
this.emit(Events.DEBUG, `Preparing to connect to the gateway (QR Login)`, QR);
return QR.connect(this);
}
/**
* Implement `remoteAuth`, like using your phone to scan a QR code
* @param {string} url URL from QR code
* @returns {Promise<void>}
*/
async remoteAuth(url) {
if (!this.isReady()) throw new Error('CLIENT_NOT_READY', 'Remote Auth');
// Step 1: Parse URL
url = new URL(url);
if (
!['discordapp.com', 'discord.com'].includes(url.hostname) ||
!url.pathname.startsWith('/ra/') ||
url.pathname.length <= 4
) {
throw new Error('INVALID_REMOTE_AUTH_URL');
}
const hash = url.pathname.replace('/ra/', '');
// Step 2: Post > Get handshake_token
const res = await this.api.users['@me']['remote-auth'].post({
data: {
fingerprint: hash,
},
});
const handshake_token = res.handshake_token;
// Step 3: Post
return this.api.users['@me']['remote-auth'].finish.post({ data: { handshake_token, temporary_token: false } });
// Cancel
// this.api.users['@me']['remote-auth'].cancel.post({ data: { handshake_token } });
}
/**
* Create a new token based on the current token
* @returns {Promise<string>} New Discord Token
*/
createToken() {
return new Promise((resolve, reject) => {
// Step 1: Create DiscordAuthWebsocket
const QR = new DiscordAuthWebsocket({
hiddenLog: true,
generateQR: false,
autoLogin: false,
debug: false,
failIfError: false,
userAgent: this.options.http.headers['User-Agent'],
wsProperties: this.options.ws.properties,
});
// Step 2: Add event
QR.once('ready', async (_, url) => {
try {
await this.remoteAuth(url);
} catch (e) {
reject(e);
}
}).once('finish', (user, token) => {
resolve(token);
});
// Step 3: Connect
QR.connect();
});
}
/**
* Emitted whenever clientOptions.checkUpdate = false
* @event Client#update
* @param {string} oldVersion Current version
* @param {string} newVersion Latest version
*/
/**
* Check for updates
* @returns {Promise<Client>}
*/
async checkUpdate() {
const res_ = await (
await fetch(`https://registry.npmjs.com/${encodeURIComponent('discord.js-selfbot-v13')}`)
).json();
try {
const latest_tag = res_['dist-tags'].latest;
this.emit('update', Discord.version, latest_tag);
this.emit('debug', `${chalk.greenBright('[OK]')} Check Update success`);
} catch {
this.emit('debug', `${chalk.redBright('[Fail]')} Check Update error`);
this.emit('update', Discord.version, false);
}
return this;
QRLogin() {
const ws = new DiscordAuthWebsocket();
ws.once('ready', () => ws.generateQR());
return ws.connect(this);
}
/**
@@ -544,7 +328,6 @@ class Client extends BaseClient {
this.sweepers.destroy();
this.ws.destroy();
this.token = null;
this.password = null;
}
/**
@@ -558,7 +341,7 @@ class Client extends BaseClient {
voip_provider: null,
},
});
await this.destroy();
return this.destroy();
}
/**
@@ -586,51 +369,6 @@ class Client extends BaseClient {
return new Invite(this, data);
}
/**
* Join this Guild using this invite (fast)
* @param {InviteResolvable} invite Invite code or URL
* @returns {Promise<void>}
* @example
* await client.acceptInvite('https://discord.gg/genshinimpact')
*/
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({
headers: {
'X-Context-Properties': 'eyJsb2NhdGlvbiI6Ik1hcmtkb3duIExpbmsifQ==', // Markdown Link
},
data: {
session_id: this.sessionId,
},
});
}
}
/**
* Redeem nitro from code or url.
* @param {string} nitro Nitro url or code
* @param {TextChannelResolvable} channel Channel that the code was sent in
* @param {Snowflake} [paymentSourceId] Payment source id
* @returns {Promise<any>}
*/
redeemNitro(nitro, channel, paymentSourceId) {
if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
const nitroCode =
nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
if (!nitroCode) return false;
const code = nitroCode[2];
channel = this.channels.resolveId(channel);
return this.api.entitlements['gift-codes'](code).redeem.post({
auth: true,
data: { channel_id: channel || null, payment_source_id: paymentSourceId || null },
});
}
/**
* Obtains a template from Discord.
* @param {GuildTemplateResolvable} template Template code or URL
@@ -721,16 +459,6 @@ class Client extends BaseClient {
}
}
/**
* Clear a cache
* @param {Collection} cache The cache to clear
* @returns {number} The number of removed entries
* @private
*/
_clearCache(cache) {
return cache.sweep(() => true);
}
/**
* Sweeps all text-based channels' messages and removes the ones older than the max message lifetime.
* If the message has been edited, the time of the edit is used rather than the time of the original message.
@@ -791,65 +519,13 @@ class Client extends BaseClient {
*/
/**
* Generates a link that can be used to invite the bot to a guild.
* @param {InviteGenerationOptions} [options={}] Options for the invite
* @returns {string}
* @example
* const link = client.generateInvite({
* scopes: ['applications.commands'],
* });
* console.log(`Generated application invite link: ${link}`);
* @example
* const link = client.generateInvite({
* permissions: [
* Permissions.FLAGS.SEND_MESSAGES,
* Permissions.FLAGS.MANAGE_GUILD,
* Permissions.FLAGS.MENTION_EVERYONE,
* ],
* scopes: ['bot'],
* });
* console.log(`Generated bot invite link: ${link}`);
* The sleep function in JavaScript returns a promise that resolves after a specified timeout.
* @param {number} timeout - The timeout parameter is the amount of time, in milliseconds, that the sleep
* function will wait before resolving the promise and continuing execution.
* @returns {void} The `sleep` function is returning a Promise.
*/
generateInvite(options = {}) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
if (!this.application) throw new Error('CLIENT_NOT_READY', 'generate an invite link');
const query = new URLSearchParams({
client_id: this.application.id,
});
const { scopes } = options;
if (typeof scopes === 'undefined') {
throw new TypeError('INVITE_MISSING_SCOPES');
}
if (!Array.isArray(scopes)) {
throw new TypeError('INVALID_TYPE', 'scopes', 'Array of Invite Scopes', true);
}
if (!scopes.some(scope => ['bot', 'applications.commands'].includes(scope))) {
throw new TypeError('INVITE_MISSING_SCOPES');
}
const invalidScope = scopes.find(scope => !InviteScopes.includes(scope));
if (invalidScope) {
throw new TypeError('INVALID_ELEMENT', 'Array', 'scopes', invalidScope);
}
query.set('scope', scopes.join(' '));
if (options.permissions) {
const permissions = Permissions.resolve(options.permissions);
if (permissions) query.set('permissions', permissions);
}
if (options.disableGuildSelect) {
query.set('disable_guild_select', true);
}
if (options.guild) {
const guildId = this.guilds.resolveId(options.guild);
if (!guildId) throw new TypeError('INVALID_TYPE', 'options.guild', 'GuildResolvable');
query.set('guild_id', guildId);
}
return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
sleep(timeout) {
return new Promise(r => setTimeout(r, timeout));
}
toJSON() {
@@ -859,41 +535,46 @@ class Client extends BaseClient {
}
/**
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
* with the client as `this`.
* @param {string} script Script to eval
* @returns {*}
* @private
* Join this Guild using this invite (fast)
* @param {InviteResolvable} invite Invite code or URL
* @returns {Promise<void>}
* @example
* await client.acceptInvite('https://discord.gg/genshinimpact')
*/
_eval(script) {
return eval(script);
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({
DiscordContext: { location: 'Markdown Link' },
data: {
session_id: this.ws.shards.first()?.sessionId,
},
});
}
}
/**
* Sets the client's presence. (Sync Setting).
* @param {Client} client Discord Client
* @private
* Redeem nitro from code or url.
* @param {string} nitro Nitro url or code
* @param {TextChannelResolvable} [channel] Channel that the code was sent in
* @param {Snowflake} [paymentSourceId] Payment source id
* @returns {Promise<any>}
*/
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) {
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.toJSON(), ...this.presence.activities.filter(a => a.type !== 'CUSTOM')],
status: client.settings.rawSetting.status ?? 'invisible',
});
}
redeemNitro(nitro, channel, paymentSourceId) {
if (typeof nitro !== 'string') throw new Error('INVALID_NITRO');
const nitroCode =
nitro.match(/(discord.gift|discord.com|discordapp.com\/gifts)\/(\w{16,25})/) ||
nitro.match(/(discord\.gift\/|discord\.com\/gifts\/|discordapp\.com\/gifts\/)(\w+)/);
if (!nitroCode) return false;
const code = nitroCode[2];
channel = this.channels.resolveId(channel);
return this.api.entitlements['gift-codes'](code).redeem.post({
auth: true,
data: { channel_id: channel || null, payment_source_id: paymentSourceId || null },
});
}
/**
@@ -909,7 +590,7 @@ class Client extends BaseClient {
* Authorize an application.
* @param {string} url Discord Auth URL
* @param {OAuth2AuthorizeOptions} options Oauth2 options
* @returns {Promise<Object>}
* @returns {Promise<any>}
* @example
* client.authorizeURL(`https://discord.com/api/oauth2/authorize?client_id=botID&permissions=8&scope=applications.commands%20bot`, {
guild_id: "guildID",
@@ -937,12 +618,14 @@ class Client extends BaseClient {
}
/**
* Makes waiting time for Client.
* @param {number} miliseconds Sleeping time as milliseconds.
* @returns {Promise<void> | null}
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
* with the client as `this`.
* @param {string} script Script to eval
* @returns {*}
* @private
*/
sleep(miliseconds) {
return typeof miliseconds === 'number' ? new Promise(r => setTimeout(r, miliseconds).unref()) : null;
_eval(script) {
return eval(script);
}
/**
@@ -956,73 +639,8 @@ class Client extends BaseClient {
} else {
options.intents = Intents.resolve(options.intents);
}
if (options && typeof options.checkUpdate !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'checkUpdate', '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');
}
if (options && options.captchaService && !captchaServices.includes(options.captchaService)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaService', captchaServices.join(', '));
}
// Parse captcha key
if (options && captchaServices.includes(options.captchaService) && options.captchaService !== 'custom') {
if (typeof options.captchaKey !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a string');
}
switch (options.captchaService) {
case '2captcha':
if (options.captchaKey.length !== 32) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaKey', 'a 32 character string');
}
break;
case 'capmonster':
if (options.captchaKey.length !== 32) {
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 (typeof options.captchaRetryLimit !== 'number' || isNaN(options.captchaRetryLimit)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaRetryLimit', 'a number');
}
if (options && typeof options.captchaSolver !== 'function') {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaSolver', 'a function');
}
if (options && typeof options.captchaWithProxy !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'captchaWithProxy', 'a boolean');
}
if (options && typeof options.DMSync !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'DMSync', 'a boolean');
}
if (options && typeof options.patchVoice !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'patchVoice', 'a boolean');
}
if (options && options.password && typeof options.password !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'password', 'a string');
}
if (options && options.usingNewAttachmentAPI && typeof options.usingNewAttachmentAPI !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'usingNewAttachmentAPI', 'a boolean');
}
if (options && options.interactionTimeout && typeof options.interactionTimeout !== 'number') {
throw new TypeError('CLIENT_INVALID_OPTION', 'interactionTimeout', 'a number');
}
if (options && typeof options.proxy !== 'string') {
throw new TypeError('CLIENT_INVALID_OPTION', 'proxy', 'a string');
} else if (options && options.proxy && typeof options.proxy === 'string') {
getProxyObject(options.proxy);
}
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');
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount !== 1) {
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number equal to 1');
}
if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) {
throw new TypeError('CLIENT_INVALID_OPTION', 'shards', "'auto', a number or array of numbers");
@@ -1046,12 +664,15 @@ class Client extends BaseClient {
if (!Array.isArray(options.partials)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
}
if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number');
}
if (typeof options.messageCreateEventGuildTimeout !== 'number' || isNaN(options.messageCreateEventGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'messageCreateEventGuildTimeout', 'a number');
}
if (typeof options.DMChannelVoiceStatusSync !== 'number' || isNaN(options.DMChannelVoiceStatusSync)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'DMChannelVoiceStatusSync', 'a number');
}
if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'waitGuildTimeout', 'a number');
}
if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
}

View File

@@ -1,105 +1,13 @@
'use strict';
const USER_REQUIRED_ACTION = require('./USER_REQUIRED_ACTION_UPDATE');
const { Opcodes } = require('../../../util/Constants');
let ClientUser;
const { VoiceConnection } = require('@discordjs/voice');
const chalk = require('chalk');
const { Events, Opcodes } = require('../../../util/Constants');
const Util = require('../../../util/Util');
const { VoiceConnection: VoiceConnection_patch } = require('../../../util/Voice');
let firstReady = false;
function patchVoice(client) {
try {
/* eslint-disable */
VoiceConnection.prototype.configureNetworking = VoiceConnection_patch.prototype.configureNetworking;
client.emit(
'debug',
`${chalk.greenBright('[OK]')} Patched ${chalk.cyanBright(
'VoiceConnection.prototype.configureNetworking',
)} [${chalk.bgMagentaBright('@discordjs/voice')} - ${chalk.redBright('v0.16.0')}]`,
);
/* eslint-enable */
} catch (e) {
client.emit(
'debug',
`${chalk.redBright('[Fail]')} Patched ${chalk.cyanBright(
'VoiceConnection.prototype.configureNetworking',
)} [${chalk.bgMagentaBright('@discordjs/voice')} - ${chalk.redBright('v0.16.0')}]\n${e.stack}`,
);
client.emit(
Events.ERROR,
`${chalk.redBright('[Fail]')} Patched ${chalk.cyanBright(
'VoiceConnection.prototype.configureNetworking',
)} [${chalk.bgMagentaBright('@discordjs/voice')} - ${chalk.redBright('v0.16.0')}]`,
);
client.emit(
Events.ERROR,
`${chalk.redBright('[Error]')} Please install ${chalk.bgMagentaBright(
'@discordjs/voice',
)} version ${chalk.redBright('v0.16.0')}`,
);
}
}
module.exports = async (client, { d: data }, shard) => {
Util.clientRequiredAction(client, data.required_action);
if (!firstReady) {
if (client.options.checkUpdate) {
client.once('update', (currentVersion, newVersion) => {
if (!newVersion) {
console.log(`
${chalk.redBright('[WARNING]')} Cannot check new Discord.js-selfbot-v13 version.
Current: ${chalk.blueBright(currentVersion)}
If you don't want to show this message, set ${chalk.cyanBright('checkUpdate')} to false
const client = new Client({
checkUpdate: false,
});
and using event update
https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-update\n`);
} else if (currentVersion !== newVersion && !currentVersion.includes('-')) {
console.log(`
${chalk.yellowBright('[WARNING]')} New Discord.js-selfbot-v13 version.
Current: ${chalk.redBright(currentVersion)} => Latest: ${chalk.greenBright(newVersion)}
If you don't want to show this message, set ${chalk.cyanBright('checkUpdate')} to false
const client = new Client({
checkUpdate: false,
});
and using event update
https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-update\n`);
} else {
console.log(
`
${chalk.greenBright('[OK]')} Discord.js-selfbot-v13 is up to date. Current: ${chalk.blueBright(currentVersion)}
If you don't want to show this message, set ${chalk.cyanBright('checkUpdate')} to false
const client = new Client({
checkUpdate: false,
});
and using event update
https://discordjs-self-v13.netlify.app/#/docs/docs/main/class/Client?scrollTo=e-update\n`,
);
}
});
client.checkUpdate();
}
if (client.options.patchVoice) {
patchVoice(client);
}
if (client.options.syncStatus) {
client.customStatusAuto(client);
}
firstReady = true;
}
module.exports = (client, { d: data }, shard) => {
// Check
USER_REQUIRED_ACTION(client, { d: data });
if (client.user) {
client.user._patch(data.user);
@@ -109,31 +17,8 @@ module.exports = async (client, { d: data }, shard) => {
client.users.cache.set(client.user.id, client.user);
}
client.settings._patch(data.user_settings);
client.user.connectedAccounts = data.connected_accounts ?? [];
client.relationships._setup(data.relationships);
client.user._patchNote(data.notes);
const syncTime = Date.now();
for (const private_channel of data.private_channels) {
const channel = client.channels._add(private_channel);
// Rate limit warning
if (client.options.DMSync) {
client.ws.broadcast({
op: Opcodes.DM_UPDATE,
d: {
channel_id: channel.id,
},
});
}
}
if (client.options.DMSync) {
console.warn(
`Gateway Rate Limit Warning: Sending ${data.private_channels.length} Requests / ${Date.now() - syncTime || 1} ms`,
);
client.channels._add(private_channel);
}
for (const guild of data.guilds) {
@@ -141,31 +26,41 @@ module.exports = async (client, { d: data }, shard) => {
client.guilds._add(guild);
}
for (const gSetting of Array.isArray(data.user_guild_settings) ? data.user_guild_settings : []) {
const guild = client.guilds.cache.get(gSetting.guild_id);
if (guild) guild.settings._patch(gSetting);
}
const largeGuilds = data.guilds.filter(g => g.large);
client.emit('debug', `[READY] Received ${data.guilds.length} guilds, ${largeGuilds.length} large guilds`);
// Receive messages in large guilds
for (const guild of largeGuilds) {
await client.sleep(client.options.messageCreateEventGuildTimeout);
client.ws.broadcast({
op: Opcodes.GUILD_SUBSCRIPTIONS,
d: {
guild_id: guild.id,
typing: true,
threads: true,
activities: true,
thread_member_lists: [],
members: [],
channels: {},
},
});
}
// User Notes
client.notes._reload(data.notes);
shard.checkReady();
// Relationship
client.relationships._setup(data.relationships);
Promise.all(
largeGuilds.map(async (guild, index) => {
client.ws.broadcast({
op: Opcodes.GUILD_SUBSCRIPTIONS,
d: {
guild_id: guild.id,
typing: true,
threads: true,
activities: true,
thread_member_lists: [],
members: [],
channels: {},
},
});
client.emit('debug', `[READY] Register guild ${guild.id}`);
await client.sleep(client.options.messageCreateEventGuildTimeout * index);
}),
data.private_channels.map(async (c, index) => {
if (client.options.DMChannelVoiceStatusSync < 1) return;
client.ws.broadcast({
op: Opcodes.DM_UPDATE,
d: {
channel_id: c.id,
},
});
await client.sleep(client.options.DMChannelVoiceStatusSync * index);
}),
).then(() => shard.checkReady());
};

View File

@@ -1,12 +0,0 @@
'use strict';
const { Events } = require('../../../util/Constants');
module.exports = (client, { d: data }) => {
const guild = client.guilds.cache.get(data.guild_id);
guild?.settings._patch(data);
/**
* Emitted whenever guild settings are updated
* @event Client#userGuildSettingsUpdate
* @param {Guild} guild Guild
*/
return client.emit(Events.USER_GUILD_SETTINGS_UPDATE, guild);
};

View File

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

View File

@@ -3,10 +3,6 @@
const handlers = Object.fromEntries([
['READY', require('./READY')],
['RESUMED', require('./RESUMED')],
['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')],
['RELATIONSHIP_REMOVE', require('./RELATIONSHIP_REMOVE')],
['RELATIONSHIP_UPDATE', require('./RELATIONSHIP_UPDATE')],
['APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE', require('./APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE')],
['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')],
['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')],
['APPLICATION_COMMAND_UPDATE', require('./APPLICATION_COMMAND_UPDATE')],
@@ -15,9 +11,6 @@ const handlers = Object.fromEntries([
['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')],
['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')],
['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')],
['CALL_CREATE', require('./CALL_CREATE')],
['CALL_UPDATE', require('./CALL_UPDATE')],
['CALL_DELETE', require('./CALL_DELETE')],
['GUILD_CREATE', require('./GUILD_CREATE')],
['GUILD_DELETE', require('./GUILD_DELETE')],
['GUILD_UPDATE', require('./GUILD_UPDATE')],
@@ -27,8 +20,6 @@ const handlers = Object.fromEntries([
['GUILD_MEMBER_REMOVE', require('./GUILD_MEMBER_REMOVE')],
['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')],
['GUILD_MEMBERS_CHUNK', require('./GUILD_MEMBERS_CHUNK')],
['GUILD_MEMBER_LIST_UPDATE', require('./GUILD_MEMBER_LIST_UPDATE.js')],
['GUILD_APPLICATION_COMMANDS_UPDATE', require('./GUILD_APPLICATION_COMMANDS_UPDATE.js')],
['GUILD_INTEGRATIONS_UPDATE', require('./GUILD_INTEGRATIONS_UPDATE')],
['GUILD_ROLE_CREATE', require('./GUILD_ROLE_CREATE')],
['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')],
@@ -40,9 +31,6 @@ const handlers = Object.fromEntries([
['CHANNEL_DELETE', require('./CHANNEL_DELETE')],
['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')],
['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')],
['CHANNEL_RECIPIENT_ADD', require('./CHANNEL_RECIPIENT_ADD')],
['CHANNEL_RECIPIENT_REMOVE', require('./CHANNEL_RECIPIENT_REMOVE')],
['MESSAGE_ACK', require('./MESSAGE_ACK')],
['MESSAGE_CREATE', require('./MESSAGE_CREATE')],
['MESSAGE_DELETE', require('./MESSAGE_DELETE')],
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
@@ -57,21 +45,12 @@ const handlers = Object.fromEntries([
['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')],
['THREAD_MEMBER_UPDATE', require('./THREAD_MEMBER_UPDATE')],
['THREAD_MEMBERS_UPDATE', require('./THREAD_MEMBERS_UPDATE')],
['USER_SETTINGS_UPDATE', require('./USER_SETTINGS_UPDATE')], // Opcode 0
['USER_GUILD_SETTINGS_UPDATE', require('./USER_GUILD_SETTINGS_UPDATE')],
// USER_SETTINGS_PROTO_UPDATE // opcode 0
['USER_NOTE_UPDATE', require('./USER_NOTE_UPDATE')],
['USER_REQUIRED_ACTION_UPDATE', require('./USER_REQUIRED_ACTION_UPDATE')],
['USER_UPDATE', require('./USER_UPDATE')],
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
['TYPING_START', require('./TYPING_START')],
['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')],
['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')],
['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')],
['INTERACTION_CREATE', require('./INTERACTION_CREATE')],
['INTERACTION_SUCCESS', require('./INTERACTION_SUCCESS')],
['INTERACTION_MODAL_CREATE', require('./INTERACTION_MODAL_CREATE')],
['INTERACTION_FAILURE', require('./INTERACTION_FAILURE')],
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
@@ -82,6 +61,18 @@ const handlers = Object.fromEntries([
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')],
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')],
// Selfbot
['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')],
['RELATIONSHIP_REMOVE', require('./RELATIONSHIP_REMOVE')],
['RELATIONSHIP_UPDATE', require('./RELATIONSHIP_UPDATE')],
['USER_NOTE_UPDATE', require('./USER_NOTE_UPDATE')],
['CHANNEL_RECIPIENT_ADD', require('./CHANNEL_RECIPIENT_ADD')],
['CHANNEL_RECIPIENT_REMOVE', require('./CHANNEL_RECIPIENT_REMOVE')],
['INTERACTION_MODAL_CREATE', require('./INTERACTION_MODAL_CREATE')],
['USER_REQUIRED_ACTION_UPDATE', require('./USER_REQUIRED_ACTION_UPDATE')],
['CALL_CREATE', require('./CALL_CREATE')],
['CALL_UPDATE', require('./CALL_UPDATE')],
['CALL_DELETE', require('./CALL_DELETE')],
]);
module.exports = handlers;