Update
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
@@ -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());
|
||||
};
|
||||
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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;
|
||||
|
@@ -11,8 +11,6 @@ const Messages = {
|
||||
TOKEN_INVALID: 'An invalid token was provided.',
|
||||
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
|
||||
|
||||
MFA_INVALID: 'An invalid mfa code was provided',
|
||||
|
||||
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
|
||||
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
||||
WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`,
|
||||
@@ -20,8 +18,8 @@ const Messages = {
|
||||
|
||||
BITFIELD_INVALID: bit => `Invalid bitfield flag or number: ${bit}.`,
|
||||
|
||||
SHARDING_INVALID: 'Invalid shard settings were provided.',
|
||||
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
||||
SHARDING_INVALID: '[Bot Token] Invalid shard settings were provided.',
|
||||
SHARDING_REQUIRED: '[Bot Token] This session would have handled too many guilds - Sharding is required.',
|
||||
INVALID_INTENTS: '[Bot Token] Invalid intent provided for WebSocket intents.',
|
||||
DISALLOWED_INTENTS: '[Bot Token] Privileged intent provided is not enabled or whitelisted.',
|
||||
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
||||
@@ -49,12 +47,6 @@ const Messages = {
|
||||
EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.',
|
||||
EMBED_DESCRIPTION: 'MessageEmbed description must be a string.',
|
||||
EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.',
|
||||
/* Add */
|
||||
MISSING_PERMISSIONS: (...permission) => `You can't do this action [Missing Permission(s): ${permission.join(', ')}]`,
|
||||
EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.',
|
||||
INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
|
||||
INVALID_RANGE_QUERY_MEMBER: 'Invalid range query member. (0<x<=100)',
|
||||
MUST_SPECIFY_BOT: 'You must specify a bot to use this command.',
|
||||
|
||||
BUTTON_LABEL: 'MessageButton label must be a string',
|
||||
BUTTON_URL: 'MessageButton URL must be a string',
|
||||
@@ -66,16 +58,22 @@ const Messages = {
|
||||
SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string',
|
||||
SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string',
|
||||
|
||||
TEXT_INPUT_CUSTOM_ID: 'TextInputComponent customId must be a string',
|
||||
TEXT_INPUT_LABEL: 'TextInputComponent label must be a string',
|
||||
TEXT_INPUT_PLACEHOLDER: 'TextInputComponent placeholder must be a string',
|
||||
TEXT_INPUT_VALUE: 'TextInputComponent value must be a string',
|
||||
|
||||
MODAL_CUSTOM_ID: 'Modal customId must be a string',
|
||||
MODAL_TITLE: 'Modal title must be a string',
|
||||
|
||||
INTERACTION_COLLECTOR_ERROR: reason => `Collector received no interactions before ending with reason: ${reason}`,
|
||||
|
||||
FILE_NOT_FOUND: file => `File could not be found: ${file}`,
|
||||
|
||||
USER_BANNER_NOT_FETCHED: "You must fetch this user's banner before trying to generate its URL!",
|
||||
USER_NO_DM_CHANNEL: 'No DM Channel exists!',
|
||||
CLIENT_NO_CALL: 'No call exists!',
|
||||
|
||||
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',
|
||||
VOICE_NOT_IN_GUILD: 'You are only allowed to do this in guild channels.',
|
||||
|
||||
VOICE_STATE_NOT_OWN:
|
||||
'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.',
|
||||
@@ -103,17 +101,11 @@ const Messages = {
|
||||
GUILD_CHANNEL_UNOWNED: "The fetched channel does not belong to this manager's guild.",
|
||||
GUILD_OWNED: 'Guild is owned by the client.',
|
||||
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
|
||||
GUILD_APPLICATION_COMMANDS_SEARCH_TIMEOUT: "Application commands didn't arrive in time.",
|
||||
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.',
|
||||
CHANNEL_NOT_CACHED: 'Could not find the channel where this message came from in the cache!',
|
||||
STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.',
|
||||
GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.',
|
||||
|
||||
REQUIRE_PASSWORD: 'You must provide a password.',
|
||||
INVALIDATE_MEMBER: range => `Invalid member range: [${range[0]}, ${range[1]}]`,
|
||||
|
||||
MISSING_VALUE: (where, type) => `Missing value for ${where} (${type})`,
|
||||
|
||||
INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
|
||||
INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`,
|
||||
|
||||
@@ -141,10 +133,6 @@ const Messages = {
|
||||
|
||||
INVITE_NOT_FOUND: 'Could not find the requested invite.',
|
||||
|
||||
NOT_OWNER_GROUP_DM_CHANNEL: "You can't do this action [Missing Permission]",
|
||||
USER_ALREADY_IN_GROUP_DM_CHANNEL: 'User is already in the channel.',
|
||||
USER_NOT_IN_GROUP_DM_CHANNEL: 'User is not in the channel.',
|
||||
|
||||
DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them",
|
||||
FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them",
|
||||
|
||||
@@ -169,60 +157,28 @@ const Messages = {
|
||||
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.',
|
||||
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.',
|
||||
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
|
||||
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
|
||||
|
||||
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite',
|
||||
|
||||
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
|
||||
|
||||
SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function',
|
||||
|
||||
INVALID_BOT_METHOD: 'Bot accounts cannot use this method',
|
||||
INVALID_USER_METHOD: 'User accounts cannot use this method',
|
||||
BOT_ONLY: 'This method only for bots',
|
||||
USER_ONLY: 'This method only for users',
|
||||
|
||||
INTERACTION_SEND_FAILURE: msg => `${msg}`,
|
||||
|
||||
INVALID_LOCALE: 'Unable to select this location',
|
||||
FOLDER_NOT_FOUND: 'Server directory not found',
|
||||
FOLDER_POSITION_INVALID: 'The server index in the directory is invalid',
|
||||
APPLICATION_ID_INVALID: "The application isn't BOT",
|
||||
INVALID_NITRO: 'Invalid Nitro Code',
|
||||
MESSAGE_ID_NOT_FOUND: 'Message ID not found',
|
||||
MESSAGE_EMBED_LINK_LENGTH: 'Message content with embed link length is too long',
|
||||
GUILD_MEMBERS_FETCH: msg => `${msg}`,
|
||||
USER_NOT_STREAMING: 'User is not streaming',
|
||||
// Djs v13.7
|
||||
TEXT_INPUT_CUSTOM_ID: 'TextInputComponent customId must be a string',
|
||||
TEXT_INPUT_LABEL: 'TextInputComponent label must be a string',
|
||||
TEXT_INPUT_PLACEHOLDER: 'TextInputComponent placeholder must be a string',
|
||||
TEXT_INPUT_VALUE: 'TextInputComponent value must be a string',
|
||||
|
||||
MODAL_CUSTOM_ID: 'Modal customId must be a string',
|
||||
MODAL_TITLE: 'Modal title must be a string',
|
||||
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
|
||||
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
|
||||
|
||||
INVALID_REMOTE_AUTH_URL: 'Invalid remote auth URL (https://discord.com/ra/{hash})',
|
||||
INVALID_URL: url =>
|
||||
`Invalid URL: ${url}.\nMake sure you are using a valid URL (https://discord.com/oauth2/authorize?...)`,
|
||||
|
||||
NITRO_REQUIRED: 'This feature is only available for Nitro users.',
|
||||
NITRO_BOOST_REQUIRED: feature => `This feature (${feature}) is only available for Nitro Boost users.`,
|
||||
ONLY_ME: 'This feature is only available for self.',
|
||||
MISSING_CAPTCHA_SERVICE: 'This feature is only available for enabled captcha handler.',
|
||||
|
||||
GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread',
|
||||
NORMAL_LOGIN: 'Username and password are required for normal login',
|
||||
LOGIN_FAILED_UNKNOWN: 'Login failed',
|
||||
LOGIN_FAILED_2FA: 'Login failed, 2FA code is required',
|
||||
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.`,
|
||||
// Selfbot
|
||||
INVALID_USER_API: 'User accounts cannot use this endpoint',
|
||||
INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
|
||||
INVALID_SLASH_COMMAND_CHOICES: (parentOptions, value) =>
|
||||
`${value} is not a valid choice for this option (${parentOptions})`,
|
||||
SLASH_COMMAND_REQUIRED_OPTIONS_MISSING: (req, opt) => `Value required (${req}) missing (Options: ${opt})`,
|
||||
SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID: n => `${n} is not a valid sub command group`,
|
||||
SLASH_COMMAND_SUB_COMMAND_INVALID: n => `${n} is not a valid sub command`,
|
||||
INTERACTION_FAILED: 'No responsed from Application Command',
|
||||
USER_NOT_STREAMING: 'User is not streaming',
|
||||
};
|
||||
|
||||
for (const [name, message] of Object.entries(Messages)) register(name, message);
|
||||
|
53
src/index.js
53
src/index.js
@@ -1,11 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const tls = require('tls');
|
||||
// Cipher
|
||||
tls.DEFAULT_CIPHERS = tls.DEFAULT_CIPHERS.split(':')
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.join(':');
|
||||
|
||||
// "Root" classes (starting points)
|
||||
exports.BaseClient = require('./client/BaseClient');
|
||||
exports.Client = require('./client/Client');
|
||||
@@ -15,7 +9,6 @@ exports.ShardingManager = require('./sharding/ShardingManager');
|
||||
exports.WebhookClient = require('./client/WebhookClient');
|
||||
|
||||
// Utilities
|
||||
exports.DiscordRPCServer = require('./util/arRPC/index');
|
||||
exports.ActivityFlags = require('./util/ActivityFlags');
|
||||
exports.ApplicationFlags = require('./util/ApplicationFlags');
|
||||
exports.AttachmentFlags = require('./util/AttachmentFlags');
|
||||
@@ -42,8 +35,6 @@ exports.ThreadMemberFlags = require('./util/ThreadMemberFlags');
|
||||
exports.UserFlags = require('./util/UserFlags');
|
||||
exports.Util = require('./util/Util');
|
||||
exports.version = require('../package.json').version;
|
||||
exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
|
||||
exports.PurchasedFlags = require('./util/PurchasedFlags');
|
||||
|
||||
// Managers
|
||||
exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager');
|
||||
@@ -71,7 +62,6 @@ exports.PresenceManager = require('./managers/PresenceManager');
|
||||
exports.ReactionManager = require('./managers/ReactionManager');
|
||||
exports.ReactionUserManager = require('./managers/ReactionUserManager');
|
||||
exports.RoleManager = require('./managers/RoleManager');
|
||||
exports.SessionManager = require('./managers/SessionManager');
|
||||
exports.StageInstanceManager = require('./managers/StageInstanceManager');
|
||||
exports.ThreadManager = require('./managers/ThreadManager');
|
||||
exports.ThreadMemberManager = require('./managers/ThreadMemberManager');
|
||||
@@ -80,6 +70,7 @@ exports.VoiceStateManager = require('./managers/VoiceStateManager');
|
||||
exports.WebSocketManager = require('./client/websocket/WebSocketManager');
|
||||
exports.WebSocketShard = require('./client/websocket/WebSocketShard');
|
||||
exports.RelationshipManager = require('./managers/RelationshipManager');
|
||||
exports.UserNoteManager = require('./managers/UserNoteManager');
|
||||
|
||||
// Structures
|
||||
exports.Activity = require('./structures/Presence').Activity;
|
||||
@@ -88,26 +79,18 @@ exports.Application = require('./structures/interfaces/Application');
|
||||
exports.ApplicationCommand = require('./structures/ApplicationCommand');
|
||||
exports.ApplicationRoleConnectionMetadata =
|
||||
require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata;
|
||||
exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction');
|
||||
exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution');
|
||||
exports.AutoModerationRule = require('./structures/AutoModerationRule');
|
||||
exports.Base = require('./structures/Base');
|
||||
exports.BaseCommandInteraction = require('./structures/BaseCommandInteraction');
|
||||
exports.BaseGuild = require('./structures/BaseGuild');
|
||||
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
|
||||
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
|
||||
exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel');
|
||||
exports.BaseMessageComponent = require('./structures/BaseMessageComponent');
|
||||
exports.ButtonInteraction = require('./structures/ButtonInteraction');
|
||||
exports.CategoryChannel = require('./structures/CategoryChannel');
|
||||
exports.Channel = require('./structures/Channel').Channel;
|
||||
exports.ClientApplication = require('./structures/ClientApplication');
|
||||
exports.ClientPresence = require('./structures/ClientPresence');
|
||||
exports.ClientUser = require('./structures/ClientUser');
|
||||
exports.Collector = require('./structures/interfaces/Collector');
|
||||
exports.CommandInteraction = require('./structures/CommandInteraction');
|
||||
exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
|
||||
exports.ContextMenuInteraction = require('./structures/ContextMenuInteraction');
|
||||
exports.DMChannel = require('./structures/DMChannel');
|
||||
exports.Emoji = require('./structures/Emoji').Emoji;
|
||||
exports.Guild = require('./structures/Guild').Guild;
|
||||
@@ -123,49 +106,27 @@ exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildS
|
||||
exports.GuildTemplate = require('./structures/GuildTemplate');
|
||||
exports.Integration = require('./structures/Integration');
|
||||
exports.IntegrationApplication = require('./structures/IntegrationApplication');
|
||||
exports.Interaction = require('./structures/Interaction');
|
||||
exports.InteractionCollector = require('./structures/InteractionCollector');
|
||||
exports.InteractionWebhook = require('./structures/InteractionWebhook');
|
||||
exports.Invite = require('./structures/Invite');
|
||||
exports.InviteStageInstance = require('./structures/InviteStageInstance');
|
||||
exports.InviteGuild = require('./structures/InviteGuild');
|
||||
exports.Message = require('./structures/Message').Message;
|
||||
exports.MessageActionRow = require('./structures/MessageActionRow');
|
||||
exports.MessageAttachment = require('./structures/MessageAttachment');
|
||||
exports.MessageButton = require('./structures/MessageButton');
|
||||
exports.MessageCollector = require('./structures/MessageCollector');
|
||||
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
|
||||
exports.MessageContextMenuInteraction = require('./structures/MessageContextMenuInteraction');
|
||||
exports.MessageEmbed = require('./structures/MessageEmbed');
|
||||
exports.WebEmbed = require('./structures/WebEmbed');
|
||||
exports.MessageMentions = require('./structures/MessageMentions');
|
||||
exports.MessagePayload = require('./structures/MessagePayload');
|
||||
exports.MessageReaction = require('./structures/MessageReaction');
|
||||
exports.MessageSelectMenu = require('./structures/MessageSelectMenu');
|
||||
exports.Modal = require('./structures/Modal');
|
||||
exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction');
|
||||
exports.NewsChannel = require('./structures/NewsChannel');
|
||||
exports.OAuth2Guild = require('./structures/OAuth2Guild');
|
||||
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
|
||||
exports.GroupDMChannel = require('./structures/GroupDMChannel');
|
||||
exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
|
||||
exports.Presence = require('./structures/Presence').Presence;
|
||||
exports.ReactionCollector = require('./structures/ReactionCollector');
|
||||
exports.ReactionEmoji = require('./structures/ReactionEmoji');
|
||||
exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
|
||||
exports.Role = require('./structures/Role').Role;
|
||||
exports.Session = require('./structures/Session');
|
||||
// RPC
|
||||
exports.getUUID = require('./structures/RichPresence').getUUID;
|
||||
exports.CustomStatus = require('./structures/RichPresence').CustomStatus;
|
||||
exports.RichPresence = require('./structures/RichPresence').RichPresence;
|
||||
exports.SpotifyRPC = require('./structures/RichPresence').SpotifyRPC;
|
||||
// SelectMenu
|
||||
exports.ChannelSelectInteraction = require('./structures/SelectMenuInteraction').ChannelSelectInteraction;
|
||||
exports.MentionableSelectInteraction = require('./structures/SelectMenuInteraction').MentionableSelectInteraction;
|
||||
exports.RoleSelectInteraction = require('./structures/SelectMenuInteraction').RoleSelectInteraction;
|
||||
exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction').SelectMenuInteraction;
|
||||
exports.UserSelectInteraction = require('./structures/SelectMenuInteraction').UserSelectInteraction;
|
||||
//
|
||||
exports.StageChannel = require('./structures/StageChannel');
|
||||
exports.StageInstance = require('./structures/StageInstance').StageInstance;
|
||||
exports.Sticker = require('./structures/Sticker').Sticker;
|
||||
@@ -179,7 +140,6 @@ exports.ThreadChannel = require('./structures/ThreadChannel');
|
||||
exports.ThreadMember = require('./structures/ThreadMember');
|
||||
exports.Typing = require('./structures/Typing');
|
||||
exports.User = require('./structures/User');
|
||||
exports.UserContextMenuInteraction = require('./structures/UserContextMenuInteraction');
|
||||
exports.VoiceChannel = require('./structures/VoiceChannel');
|
||||
exports.VoiceRegion = require('./structures/VoiceRegion');
|
||||
exports.VoiceState = require('./structures/VoiceState');
|
||||
@@ -188,7 +148,12 @@ exports.Widget = require('./structures/Widget');
|
||||
exports.WidgetMember = require('./structures/WidgetMember');
|
||||
exports.WelcomeChannel = require('./structures/WelcomeChannel');
|
||||
exports.WelcomeScreen = require('./structures/WelcomeScreen');
|
||||
|
||||
exports.WebSocket = require('./WebSocket');
|
||||
|
||||
// DiscordJSVoice Patch
|
||||
exports.DiscordJSVoice = require('./util/Voice');
|
||||
exports.CustomStatus = require('./structures/RichPresence').CustomStatus;
|
||||
exports.RichPresence = require('./structures/RichPresence').RichPresence;
|
||||
exports.SpotifyRPC = require('./structures/RichPresence').SpotifyRPC;
|
||||
exports.WebEmbed = require('./structures/WebEmbed');
|
||||
exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
|
||||
exports.PurchasedFlags = require('./util/PurchasedFlags');
|
||||
|
@@ -1,490 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
const GuildFolderManager = require('./GuildFolderManager');
|
||||
const { Error, TypeError } = require('../errors/DJSError');
|
||||
const GuildFolder = require('../structures/GuildFolder');
|
||||
const { CustomStatus } = require('../structures/RichPresence');
|
||||
const { localeSetting, DMScanLevel, stickerAnimationMode } = require('../util/Constants');
|
||||
/**
|
||||
* Manages API methods for users and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/user_settings.html}
|
||||
*/
|
||||
class ClientUserSettingManager extends BaseManager {
|
||||
constructor(client) {
|
||||
super(client);
|
||||
/**
|
||||
* Raw data
|
||||
* @type {Object}
|
||||
*/
|
||||
this.rawSetting = {};
|
||||
/**
|
||||
* Language
|
||||
* @type {?string}
|
||||
*/
|
||||
this.locale = null;
|
||||
/**
|
||||
* From: Setting => ACTIVITY SETTINGS => Activity Status => Display current activity as a status message
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.activityDisplay = null;
|
||||
/**
|
||||
* Disable Direct Message from servers
|
||||
* @type {Collection<Snowflake, boolean>}
|
||||
*/
|
||||
this.disableDMfromServer = new Collection();
|
||||
/**
|
||||
* Allow direct messages from server members
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.DMfromServerMode = null;
|
||||
/**
|
||||
* Display images
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.displayImage = null;
|
||||
/**
|
||||
* Display linked images
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.linkedImageDisplay = null;
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Accessibility => Automatically play GIFs when Discord is focused.
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.autoplayGIF = null;
|
||||
/**
|
||||
* Show embeds and preview website links pasted into chat
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.previewLink = null;
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Accessibility => Play Animated Emojis
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.animatedEmojis = null;
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Accessibility => Text-to-speech => Allow playback
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.allowTTS = null;
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Appearance => Message Display => Compact Mode
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.compactMode = null;
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Text & Images => Emoji => Convert Emoticons
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.convertEmoticons = null;
|
||||
/**
|
||||
* SAFE DIRECT MESSAGING
|
||||
* @type {?DMScanLevel}
|
||||
*/
|
||||
this.DMScanLevel = null;
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Appearance => Theme
|
||||
* @type {'dark' | 'light' | null}
|
||||
*/
|
||||
this.theme = '';
|
||||
/**
|
||||
* Developer Mode (Copy ID, etc.)
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.developerMode = null;
|
||||
/**
|
||||
* AFK timeout (receives notifications)
|
||||
* @type {?number}
|
||||
*/
|
||||
this.afkTimeout = null;
|
||||
/**
|
||||
* Sticker animation mode
|
||||
* @type {?stickerAnimationMode}
|
||||
*/
|
||||
this.stickerAnimationMode = null;
|
||||
/**
|
||||
* WHO CAN ADD YOU AS A FRIEND ?
|
||||
* @type {?object}
|
||||
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/user_settings.html#friend-source-flags-structure}
|
||||
*/
|
||||
this.addFriendFrom = {
|
||||
all: null,
|
||||
mutual_friends: null,
|
||||
mutual_guilds: null,
|
||||
};
|
||||
/**
|
||||
* From: Setting => APP SETTINGS => Text & Images => Emoji => Show emoji reactions
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.showEmojiReactions = null;
|
||||
/**
|
||||
* Custom Stauts
|
||||
* @type {?object}
|
||||
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/custom_status.html#customstatus-structure}
|
||||
*/
|
||||
this.customStatus = null;
|
||||
/**
|
||||
* Guild folder and position
|
||||
* @type {GuildFolderManager}
|
||||
*/
|
||||
this.guildFolder = new GuildFolderManager(client);
|
||||
// Todo: add new method from Discum
|
||||
}
|
||||
/**
|
||||
* Patch data file
|
||||
* https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/discum/user/user.py
|
||||
* @private
|
||||
* @param {Object} data Raw Data to patch
|
||||
*/
|
||||
_patch(data = {}) {
|
||||
this.rawSetting = Object.assign(this.rawSetting, data);
|
||||
if ('locale' in data) {
|
||||
this.locale = localeSetting[data.locale];
|
||||
}
|
||||
if ('show_current_game' in data) {
|
||||
this.activityDisplay = data.show_current_game;
|
||||
}
|
||||
if ('default_guilds_restricted' in data) {
|
||||
this.DMfromServerMode = data.default_guilds_restricted;
|
||||
}
|
||||
if ('inline_attachment_media' in data) {
|
||||
this.displayImage = data.inline_attachment_media;
|
||||
}
|
||||
if ('inline_embed_media' in data) {
|
||||
this.linkedImageDisplay = data.inline_embed_media;
|
||||
}
|
||||
if ('gif_auto_play' in data) {
|
||||
this.autoplayGIF = data.gif_auto_play;
|
||||
}
|
||||
if ('render_embeds' in data) {
|
||||
this.previewLink = data.render_embeds;
|
||||
}
|
||||
if ('animate_emoji' in data) {
|
||||
this.animatedEmojis = data.animate_emoji;
|
||||
}
|
||||
if ('enable_tts_command' in data) {
|
||||
this.allowTTS = data.enable_tts_command;
|
||||
}
|
||||
if ('message_display_compact' in data) {
|
||||
this.compactMode = data.message_display_compact;
|
||||
}
|
||||
if ('convert_emoticons' in data) {
|
||||
this.convertEmoticons = data.convert_emoticons;
|
||||
}
|
||||
if ('explicit_content_filter' in data) {
|
||||
this.DMScanLevel = DMScanLevel[data.explicit_content_filter];
|
||||
}
|
||||
if ('theme' in data) {
|
||||
this.theme = data.theme;
|
||||
}
|
||||
if ('developer_mode' in data) {
|
||||
this.developerMode = data.developer_mode;
|
||||
}
|
||||
if ('afk_timeout' in data) {
|
||||
this.afkTimeout = data.afk_timeout * 1000; // Second => milisecond
|
||||
}
|
||||
if ('animate_stickers' in data) {
|
||||
this.stickerAnimationMode = stickerAnimationMode[data.animate_stickers];
|
||||
}
|
||||
if ('render_reactions' in data) {
|
||||
this.showEmojiReactions = data.render_reactions;
|
||||
}
|
||||
if ('custom_status' in data) {
|
||||
this.customStatus = data.custom_status || {}; // Thanks PinkDuwc._#3443 reported this issue
|
||||
this.customStatus.status = data.status;
|
||||
}
|
||||
if ('friend_source_flags' in data) {
|
||||
this.addFriendFrom = {
|
||||
all: data.friend_source_flags.all || false,
|
||||
mutual_friends: data.friend_source_flags.all ? true : data.friend_source_flags.mutual_friends,
|
||||
mutual_guilds: data.friend_source_flags.all ? true : data.friend_source_flags.mutual_guilds,
|
||||
};
|
||||
}
|
||||
if ('guild_folders' in data) {
|
||||
data.guild_folders.map((folder, index) =>
|
||||
this.guildFolder.cache.set(index, new GuildFolder(this.client, folder)),
|
||||
);
|
||||
}
|
||||
if ('restricted_guilds' in data) {
|
||||
this.disableDMfromServer = new Collection(data.restricted_guilds.map(guildId => [guildId, true]));
|
||||
}
|
||||
}
|
||||
async fetch() {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
const data = await this.client.api.users('@me').settings.get();
|
||||
this._patch(data);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Edit data
|
||||
* @param {Object} data Data to edit
|
||||
* @private
|
||||
*/
|
||||
async edit(data) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
const res = await this.client.api.users('@me').settings.patch({ data });
|
||||
this._patch(res);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set compact mode
|
||||
* @param {boolean | null} value Compact mode enable or disable
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async setDisplayCompactMode(value) {
|
||||
if (typeof value !== 'boolean' && value !== null) {
|
||||
throw new TypeError('INVALID_TYPE', 'value', 'boolean | null', true);
|
||||
}
|
||||
if (!value) value = !this.compactMode;
|
||||
if (value !== this.compactMode) {
|
||||
await this.edit({ message_display_compact: value });
|
||||
}
|
||||
return this.compactMode;
|
||||
}
|
||||
/**
|
||||
* Discord Theme
|
||||
* @param {null |dark |light} value Theme to set
|
||||
* @returns {theme}
|
||||
*/
|
||||
async setTheme(value) {
|
||||
const validValues = ['dark', 'light'];
|
||||
if (typeof value !== 'string' && value !== null) {
|
||||
throw new TypeError('INVALID_TYPE', 'value', 'string | null', true);
|
||||
}
|
||||
if (!validValues.includes(value)) {
|
||||
if (value == validValues[0]) value = validValues[1];
|
||||
else value = validValues[0];
|
||||
}
|
||||
if (value !== this.theme) {
|
||||
await this.edit({ theme: value });
|
||||
}
|
||||
return this.theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* CustomStatus Object
|
||||
* @typedef {Object} CustomStatusOption
|
||||
* @property {string | null} text Text to set
|
||||
* @property {string | null} status The status to set: 'online', 'idle', 'dnd', 'invisible' or null.
|
||||
* @property {EmojiResolvable | null} emoji UnicodeEmoji, DiscordEmoji, or null.
|
||||
* @property {number | null} expires The number of seconds until the status expires, or null.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set custom status
|
||||
* @param {?CustomStatus | CustomStatusOption} options CustomStatus
|
||||
*/
|
||||
setCustomStatus(options) {
|
||||
if (typeof options !== 'object') {
|
||||
this.edit({ custom_status: null });
|
||||
} else if (options instanceof CustomStatus) {
|
||||
options = options.toJSON();
|
||||
let data = {
|
||||
emoji_name: null,
|
||||
expires_at: null,
|
||||
text: null,
|
||||
};
|
||||
if (typeof options.state === 'string') {
|
||||
data.text = options.state;
|
||||
}
|
||||
if (options.emoji) {
|
||||
if (options.emoji?.id) {
|
||||
data.emoji_name = options.emoji?.name;
|
||||
data.emoji_id = options.emoji?.id;
|
||||
} else {
|
||||
data.emoji_name = typeof options.emoji?.name === 'string' ? options.emoji?.name : null;
|
||||
}
|
||||
}
|
||||
this.edit({ custom_status: data });
|
||||
} else {
|
||||
let data = {
|
||||
emoji_name: null,
|
||||
expires_at: null,
|
||||
text: null,
|
||||
};
|
||||
if (typeof options.text === 'string') {
|
||||
if (options.text.length > 128) {
|
||||
throw new RangeError('[INVALID_VALUE] Custom status text must be less than 128 characters');
|
||||
}
|
||||
data.text = options.text;
|
||||
}
|
||||
if (options.emoji) {
|
||||
const emoji = this.client.emojis.resolve(options.emoji);
|
||||
if (emoji) {
|
||||
data.emoji_name = emoji.name;
|
||||
data.emoji_id = emoji.id;
|
||||
} else {
|
||||
data.emoji_name = typeof options.emoji === 'string' ? options.emoji : null;
|
||||
}
|
||||
}
|
||||
if (typeof options.expires === 'number') {
|
||||
if (options.expires < Date.now()) {
|
||||
throw new RangeError(`[INVALID_VALUE] Custom status expiration must be greater than ${Date.now()}`);
|
||||
}
|
||||
data.expires_at = new Date(options.expires).toISOString();
|
||||
}
|
||||
if (['online', 'idle', 'dnd', 'invisible'].includes(options.status)) this.edit({ status: options.status });
|
||||
this.edit({ custom_status: data });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * Locale Setting, must be one of:
|
||||
* * `DANISH`
|
||||
* * `GERMAN`
|
||||
* * `ENGLISH_UK`
|
||||
* * `ENGLISH_US`
|
||||
* * `SPANISH`
|
||||
* * `FRENCH`
|
||||
* * `CROATIAN`
|
||||
* * `ITALIAN`
|
||||
* * `LITHUANIAN`
|
||||
* * `HUNGARIAN`
|
||||
* * `DUTCH`
|
||||
* * `NORWEGIAN`
|
||||
* * `POLISH`
|
||||
* * `BRAZILIAN_PORTUGUESE`
|
||||
* * `ROMANIA_ROMANIAN`
|
||||
* * `FINNISH`
|
||||
* * `SWEDISH`
|
||||
* * `VIETNAMESE`
|
||||
* * `TURKISH`
|
||||
* * `CZECH`
|
||||
* * `GREEK`
|
||||
* * `BULGARIAN`
|
||||
* * `RUSSIAN`
|
||||
* * `UKRAINIAN`
|
||||
* * `HINDI`
|
||||
* * `THAI`
|
||||
* * `CHINA_CHINESE`
|
||||
* * `JAPANESE`
|
||||
* * `TAIWAN_CHINESE`
|
||||
* * `KOREAN`
|
||||
* @param {localeSetting} value Locale to set
|
||||
* @returns {locale}
|
||||
*/
|
||||
async setLocale(value) {
|
||||
if (typeof value !== 'string') {
|
||||
throw new TypeError('INVALID_TYPE', 'value', 'string', true);
|
||||
}
|
||||
if (!localeSetting[value]) throw new Error('INVALID_LOCALE');
|
||||
if (localeSetting[value] !== this.locale) {
|
||||
await this.edit({ locale: localeSetting[value] });
|
||||
}
|
||||
return this.locale;
|
||||
}
|
||||
// TODO: Guild positions & folders
|
||||
// Change Index in Array [Hidden]
|
||||
/**
|
||||
*
|
||||
* @param {Array} array Array
|
||||
* @param {number} from Index1
|
||||
* @param {number} to Index2
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
_move(array, from, to) {
|
||||
array.splice(to, 0, array.splice(from, 1)[0]);
|
||||
return array;
|
||||
}
|
||||
// TODO: Move Guild
|
||||
// folder to folder
|
||||
// folder to home
|
||||
// home to home
|
||||
// home to folder
|
||||
/**
|
||||
* Change Guild Position (from * to Folder or Home)
|
||||
* @param {GuildIDResolve} guildId guild.id
|
||||
* @param {number} newPosition Guild Position
|
||||
* * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder.
|
||||
* @param {number} type Move to folder or home
|
||||
* * `FOLDER`: 1
|
||||
* * `HOME`: 2
|
||||
* @param {FolderID} folderId If you want to move to folder
|
||||
* @private
|
||||
*/
|
||||
guildChangePosition(guildId, newPosition, type, folderId) {
|
||||
// Get Guild default position
|
||||
// Escape
|
||||
const oldGuildFolderPosition = this.rawSetting.guild_folders.findIndex(value => value.guild_ids.includes(guildId));
|
||||
const newGuildFolderPosition = this.rawSetting.guild_folders.findIndex(value =>
|
||||
value.guild_ids.includes(this.rawSetting.guild_positions[newPosition]),
|
||||
);
|
||||
if (type == 2 || `${type}`.toUpperCase() == 'HOME') {
|
||||
// Delete GuildID from Folder and create new Folder
|
||||
// Check it is folder
|
||||
const folder = this.rawSetting.guild_folders[oldGuildFolderPosition];
|
||||
if (folder.id) {
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids = this.rawSetting.guild_folders[
|
||||
oldGuildFolderPosition
|
||||
].guild_ids.filter(v => v !== guildId);
|
||||
}
|
||||
this.rawSetting.guild_folders = this._move(
|
||||
this.rawSetting.guild_folders,
|
||||
oldGuildFolderPosition,
|
||||
newGuildFolderPosition,
|
||||
);
|
||||
this.rawSetting.guild_folders[newGuildFolderPosition].id = null;
|
||||
} else if (type == 1 || `${type}`.toUpperCase() == 'FOLDER') {
|
||||
// Delete GuildID from oldFolder
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids = this.rawSetting.guild_folders[
|
||||
oldGuildFolderPosition
|
||||
].guild_ids.filter(v => v !== guildId);
|
||||
// Index new Folder
|
||||
const folderIndex = this.rawSetting.guild_folders.findIndex(value => value.id == folderId);
|
||||
const folder = this.rawSetting.guild_folders[folderIndex];
|
||||
folder.guild_ids.push(guildId);
|
||||
folder.guild_ids = [...new Set(folder.guild_ids)];
|
||||
folder.guild_ids = this._move(
|
||||
folder.guild_ids,
|
||||
folder.guild_ids.findIndex(v => v == guildId),
|
||||
newPosition,
|
||||
);
|
||||
}
|
||||
this.edit({ guild_folders: this.rawSetting.guild_folders });
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricted guilds setting
|
||||
* @param {boolean} status Restricted status
|
||||
* @returns {Promise}
|
||||
*/
|
||||
restrictedGuilds(status) {
|
||||
if (typeof status !== 'boolean') {
|
||||
throw new TypeError('INVALID_TYPE', 'status', 'boolean', true);
|
||||
}
|
||||
return this.edit({
|
||||
default_guilds_restricted: status,
|
||||
restricted_guilds: status ? this.client.guilds.cache.map(v => v.id) : [],
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add a guild to the list of restricted guilds.
|
||||
* @param {GuildIDResolve} guildId The guild to add
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addRestrictedGuild(guildId) {
|
||||
const temp = Object.assign(
|
||||
[],
|
||||
this.disableDMfromServer.map((v, k) => k),
|
||||
);
|
||||
if (temp.includes(guildId)) throw new Error('Guild is already restricted');
|
||||
temp.push(guildId);
|
||||
return this.edit({ restricted_guilds: temp });
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a guild from the list of restricted guilds.
|
||||
* @param {GuildIDResolve} guildId The guild to remove
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeRestrictedGuild(guildId) {
|
||||
if (!this.disableDMfromServer.delete(guildId)) throw new Error('Guild is already restricted');
|
||||
return this.edit({ restricted_guilds: this.disableDMfromServer.map((v, k) => k) });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUserSettingManager;
|
@@ -1,104 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
const DeveloperPortalApplication = require('../structures/DeveloperPortalApplication');
|
||||
const Team = require('../structures/Team');
|
||||
|
||||
/**
|
||||
* Manages API methods for users and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class DeveloperPortalManager extends BaseManager {
|
||||
constructor(client) {
|
||||
super(client);
|
||||
/**
|
||||
* A collection of all the applications the client has.
|
||||
* @type {Collection<Snowflake, DeveloperPortalApplication>}
|
||||
* @readonly
|
||||
*/
|
||||
this.applications = new Collection();
|
||||
/**
|
||||
* A collection of all the teams the client has.
|
||||
* @type {Collection<Snowflake, Team>}
|
||||
* @readonly
|
||||
*/
|
||||
this.teams = new Collection(); // Collection<Snowflake, Team>
|
||||
}
|
||||
/**
|
||||
* Fetches all the applications & teams the client has.
|
||||
* @returns {Promise<DeveloperPortalManager>}
|
||||
*/
|
||||
async fetch() {
|
||||
const promise1 = this.client.api.applications.get({
|
||||
query: {
|
||||
with_team_applications: true,
|
||||
},
|
||||
});
|
||||
const promise2 = this.client.api.teams.get();
|
||||
const [applications, teams] = await Promise.all([promise1, promise2]);
|
||||
for (const team of teams) {
|
||||
this.teams.set(team.id, new Team(this.client, team));
|
||||
}
|
||||
for (const application of applications) {
|
||||
this.applications.set(application.id, new DeveloperPortalApplication(this.client, application));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Team.
|
||||
* @param {string} name Name of the team
|
||||
* @returns {Promise<Team>}
|
||||
*/
|
||||
async createTeam(name) {
|
||||
const team = await this.client.api.teams.post({
|
||||
data: {
|
||||
name: name,
|
||||
},
|
||||
});
|
||||
|
||||
this.teams.set(team.id, new Team(this.client, team));
|
||||
return this.teams.get(team.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new application.
|
||||
* @param {string} name Name of the application
|
||||
* @param {?Snowflake | Team} teamId The team to create the application in
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async createApplication(name, teamId = null) {
|
||||
teamId = teamId instanceof Team ? teamId.id : teamId;
|
||||
const application = await this.client.api.applications.post({
|
||||
data: {
|
||||
name,
|
||||
team_id: teamId,
|
||||
},
|
||||
});
|
||||
this.applications.set(application.id, new DeveloperPortalApplication(this.client, application));
|
||||
return this.applications.get(application.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an application.
|
||||
* @param {Snowflake} id Application ID
|
||||
* @param {?number} MFACode 2FA code (if 2FA is enabled)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async deleteApplication(id, MFACode) {
|
||||
if (MFACode) {
|
||||
await this.client.api.applications[`${id}/delete`].post({
|
||||
query: {
|
||||
code: MFACode,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await this.client.api.applications[`${id}/delete`].post();
|
||||
}
|
||||
this.applications.delete(id);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeveloperPortalManager;
|
@@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
|
||||
/**
|
||||
* Manages API methods for users and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class GuildFolderManager extends BaseManager {
|
||||
constructor(client) {
|
||||
super(client);
|
||||
/**
|
||||
* The guild folder cache (Index, GuildFolder)
|
||||
* @type {Collection<number, GuildFolder>}
|
||||
*/
|
||||
this.cache = new Collection();
|
||||
}
|
||||
_refresh() {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildFolderManager;
|
@@ -1,148 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const BaseManager = require('./BaseManager');
|
||||
/**
|
||||
* Manages API methods for users and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
* @see {@link https://luna.gitlab.io/discord-unofficial-docs/user_settings.html}
|
||||
*/
|
||||
class GuildSettingManager extends BaseManager {
|
||||
constructor(client, guildId = null) {
|
||||
super(client);
|
||||
/**
|
||||
* Raw data
|
||||
* @type {Object}
|
||||
*/
|
||||
this.rawSetting = {};
|
||||
/**
|
||||
* Guild Id
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = guildId;
|
||||
}
|
||||
/**
|
||||
* Get the guild
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.cache.get(this.guildId);
|
||||
}
|
||||
/**
|
||||
* Patch data file
|
||||
* @private
|
||||
* @param {Object} data Raw Data to patch
|
||||
*/
|
||||
_patch(data = {}) {
|
||||
this.rawSetting = Object.assign(this.rawSetting, data);
|
||||
if ('suppress_everyone' in data) {
|
||||
/**
|
||||
* Notification setting > Suppress `@everyone` and `@here`
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.suppressEveryone = data.suppress_everyone;
|
||||
}
|
||||
if ('suppress_roles' in data) {
|
||||
/**
|
||||
* Notification setting > Suppress all role `@mention`
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.suppressRoles = data.suppress_roles;
|
||||
}
|
||||
if ('mute_scheduled_events' in data) {
|
||||
/**
|
||||
* Notification setting > Mute new events
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.muteScheduledEvents = data.mute_scheduled_events;
|
||||
}
|
||||
if ('message_notifications' in data) {
|
||||
/**
|
||||
* Notification setting > Message notifications
|
||||
* * `0` = All messages
|
||||
* * `1` = Only @mentions
|
||||
* * `2` = Nothing
|
||||
* @type {?number}
|
||||
*/
|
||||
this.messageNotifications = data.message_notifications;
|
||||
}
|
||||
if ('flags' in data) {
|
||||
/**
|
||||
* Flags (unknown)
|
||||
* @type {?number}
|
||||
*/
|
||||
this.flags = data.flags;
|
||||
}
|
||||
if ('mobile_push' in data) {
|
||||
/**
|
||||
* Notification setting > Mobile push notifications
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.mobilePush = data.mobile_push;
|
||||
}
|
||||
if ('muted' in data) {
|
||||
/**
|
||||
* Mute server
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.muted = data.muted;
|
||||
}
|
||||
if ('mute_config' in data && data.mute_config !== null) {
|
||||
/**
|
||||
* Mute config (muted = true)
|
||||
* * `muteConfig.endTime`: End time (Date)
|
||||
* * `muteConfig.selectedTimeWindow`: Selected time window (seconds) (number)
|
||||
* @type {?Object}
|
||||
*/
|
||||
this.muteConfig = {
|
||||
endTime: new Date(data.mute_config.end_time),
|
||||
selectedTimeWindow: data.mute_config.selected_time_window,
|
||||
};
|
||||
} else {
|
||||
this.muteConfig = null;
|
||||
}
|
||||
if ('hide_muted_channels' in data) {
|
||||
/**
|
||||
* Hide muted channels
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.hideMutedChannels = data.hide_muted_channels;
|
||||
}
|
||||
if ('channel_overrides' in data) {
|
||||
/**
|
||||
* Channel overrides (unknown)
|
||||
* @type {?Array}
|
||||
*/
|
||||
this.channelOverrides = data.channel_overrides;
|
||||
}
|
||||
if ('notify_highlights' in data) {
|
||||
/**
|
||||
* Notification setting > Suppress highlights
|
||||
* * `0` = ??? (unknown)
|
||||
* * `1` = Enable
|
||||
* * `2` = Disable
|
||||
* @type {?number}
|
||||
*/
|
||||
this.notifyHighlights = data.notify_highlights;
|
||||
}
|
||||
if ('version' in data) {
|
||||
/**
|
||||
* Version (unknown)
|
||||
* @type {?number}
|
||||
*/
|
||||
this.version = data.version;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Edit guild settings
|
||||
* @param {Object} data Data to edit
|
||||
* @returns {Promise<GuildSettingManager>}
|
||||
*/
|
||||
async edit(data) {
|
||||
const data_ = await this.client.api.users('@me').settings.patch(data);
|
||||
this._patch(data_);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildSettingManager;
|
@@ -1,57 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { Error } = require('../errors/DJSError');
|
||||
const Session = require('../structures/Session');
|
||||
/**
|
||||
* Manages API methods for users and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class SessionManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, Session, iterable);
|
||||
}
|
||||
/**
|
||||
* The cache of Sessions
|
||||
* @type {Collection<string, Session>}
|
||||
* @name SessionManager#cache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch all sessions of the client.
|
||||
* @returns {Promise<SessionManager>}
|
||||
*/
|
||||
fetch() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.api.auth.sessions
|
||||
.get()
|
||||
.then(data => {
|
||||
const allData = data.user_sessions;
|
||||
this.cache.clear();
|
||||
for (const session of allData) {
|
||||
this._add(new Session(this.client, session), true, { id: session.id_hash });
|
||||
}
|
||||
resolve(this);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the client (remote).
|
||||
* @param {string | null} mfaCode MFA code (if 2FA is enabled)
|
||||
* @returns {Promise<undefined>}
|
||||
*/
|
||||
logoutAllDevices(mfaCode) {
|
||||
if (typeof this.client.password !== 'string') throw new Error('REQUIRE_PASSWORD');
|
||||
return this.client.api.auth.sessions.logout({
|
||||
data: {
|
||||
session_id_hashes: this.cache.map(session => session.id),
|
||||
password: this.client.password,
|
||||
code: typeof mfaCode === 'string' ? mfaCode : undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SessionManager;
|
@@ -1,520 +0,0 @@
|
||||
'use strict';
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata');
|
||||
const Base = require('./Base');
|
||||
const ApplicationFlags = require('../util/ApplicationFlags');
|
||||
const { ClientApplicationAssetTypes, Endpoints, ApplicationRoleConnectionMetadataTypes } = require('../util/Constants');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
|
||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||
|
||||
/**
|
||||
* Represents an OAuth2 Application.
|
||||
* @extends {Base}
|
||||
* @abstract
|
||||
*/
|
||||
class DeveloperPortalApplication extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
this._patch(data);
|
||||
}
|
||||
_patch(data) {
|
||||
/**
|
||||
* The application's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The name of the application
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
} else {
|
||||
this.name ??= null;
|
||||
}
|
||||
|
||||
if ('description' in data) {
|
||||
/**
|
||||
* The application's description
|
||||
* @type {?string}
|
||||
*/
|
||||
this.description = data.description;
|
||||
} else {
|
||||
this.description ??= null;
|
||||
}
|
||||
|
||||
if ('icon' in data) {
|
||||
/**
|
||||
* The application's icon hash
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
} else {
|
||||
this.icon ??= null;
|
||||
}
|
||||
|
||||
if ('bot' in data) {
|
||||
/**
|
||||
* Bot application
|
||||
* @type {User}
|
||||
*/
|
||||
this.bot = this.client.users._add(data.bot);
|
||||
}
|
||||
|
||||
/**
|
||||
* The tags this application has (max of 5)
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.tags = data.tags ?? [];
|
||||
|
||||
if ('install_params' in data) {
|
||||
/**
|
||||
* Settings for this application's default in-app authorization
|
||||
* @type {?ClientApplicationInstallParams}
|
||||
*/
|
||||
this.installParams = {
|
||||
scopes: data.install_params.scopes,
|
||||
permissions: new Permissions(data.install_params.permissions).freeze(),
|
||||
};
|
||||
} else {
|
||||
this.installParams ??= null;
|
||||
}
|
||||
|
||||
if ('custom_install_url' in data) {
|
||||
/**
|
||||
* This application's custom installation URL
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customInstallURL = data.custom_install_url;
|
||||
} else {
|
||||
this.customInstallURL = null;
|
||||
}
|
||||
|
||||
if ('flags' in data) {
|
||||
/**
|
||||
* The flags this application has
|
||||
* @type {ApplicationFlags}
|
||||
*/
|
||||
this.flags = new ApplicationFlags(data.flags).freeze();
|
||||
}
|
||||
|
||||
if ('cover_image' in data) {
|
||||
/**
|
||||
* The hash of the application's cover image
|
||||
* @type {?string}
|
||||
*/
|
||||
this.cover = data.cover_image;
|
||||
} else {
|
||||
this.cover ??= null;
|
||||
}
|
||||
|
||||
if ('rpc_origins' in data) {
|
||||
/**
|
||||
* The application's RPC origins, if enabled
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.rpcOrigins = data.rpc_origins;
|
||||
} else {
|
||||
this.rpcOrigins ??= [];
|
||||
}
|
||||
|
||||
if ('bot_require_code_grant' in data) {
|
||||
/**
|
||||
* If this application's bot requires a code grant when using the OAuth2 flow
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.botRequireCodeGrant = data.bot_require_code_grant;
|
||||
} else {
|
||||
this.botRequireCodeGrant ??= null;
|
||||
}
|
||||
|
||||
if ('bot_public' in data) {
|
||||
/**
|
||||
* If this application's bot is public
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.botPublic = data.bot_public;
|
||||
} else {
|
||||
this.botPublic ??= null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The owner of this OAuth application
|
||||
* @type {?(User|Team)}
|
||||
*/
|
||||
this.owner = null;
|
||||
if (data.owner.username == `team${data.owner.id}` && data.owner.discriminator == '0000') {
|
||||
this.owner = this.client.developerPortal.teams.get(data.owner.id);
|
||||
} else {
|
||||
this.owner = data.owner ? this.client.users._add(data.owner) : this.owner ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect URIs for this application
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.redirectURIs = data.redirect_uris ?? [];
|
||||
|
||||
/**
|
||||
* BOT_HTTP_INTERACTIONS feature flag
|
||||
* @type {?string}
|
||||
*/
|
||||
this.interactionEndpointURL = data.interactions_endpoint_url ?? null;
|
||||
|
||||
/**
|
||||
* Public key
|
||||
* @type {?string}
|
||||
*/
|
||||
this.publicKey = data.verify_key ?? null;
|
||||
|
||||
/**
|
||||
* @typedef {Object} Tester
|
||||
* @property {number} state The state of the tester (2: Accepted, 1: Pending)
|
||||
* @property {User} user The user that the tester is
|
||||
*/
|
||||
/**
|
||||
* User tester
|
||||
* @type {Collection<Snowflake, Tester>}
|
||||
*/
|
||||
this.testers = new Collection(); // <Snowflake, User>
|
||||
|
||||
/**
|
||||
* Terms of service URL
|
||||
* @type {?string}
|
||||
*/
|
||||
this.TermsOfService = data.terms_of_service_url ?? null;
|
||||
|
||||
/**
|
||||
* Privacy policy URL
|
||||
* @type {?string}
|
||||
*/
|
||||
this.PrivacyPolicy = data.privacy_policy_url ?? null;
|
||||
|
||||
if ('role_connections_verification_url' in data) {
|
||||
/**
|
||||
* This application's role connection verification entry point URL
|
||||
* @type {?string}
|
||||
*/
|
||||
this.roleConnectionsVerificationURL = data.role_connections_verification_url;
|
||||
} else {
|
||||
this.roleConnectionsVerificationURL ??= null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The timestamp the application was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the application was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the application's icon.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
iconURL({ format, size } = {}) {
|
||||
if (!this.icon) return null;
|
||||
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to this application's cover image.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
coverURL({ format, size } = {}) {
|
||||
if (!this.cover) return null;
|
||||
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset data.
|
||||
* @typedef {Object} ApplicationAsset
|
||||
* @property {Snowflake} id The asset's id
|
||||
* @property {string} name The asset's name
|
||||
* @property {string} type The asset's type
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the application's rich presence assets.
|
||||
* @returns {Promise<Array<ApplicationAsset>>}
|
||||
* @deprecated This will be removed in the next major as it is unsupported functionality.
|
||||
*/
|
||||
async fetchAssets() {
|
||||
const assets = await this.client.api.oauth2.applications(this.id).assets.get();
|
||||
return assets.map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
type: AssetTypes[a.type - 1],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this application is partial
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return !this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains this application from Discord.
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async fetch() {
|
||||
const app = await this.client.api.applications[this.id].get();
|
||||
this._patch(app);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all testers for this application.
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async fetchTesters() {
|
||||
const app = await this.client.api.applications[this.id].allowlist.get();
|
||||
this.testers = new Collection();
|
||||
for (const tester of app || []) {
|
||||
this.testers.set(tester.user.id, {
|
||||
state: tester.state,
|
||||
user: this.client.users._add(tester.user),
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to this application's allowlist.
|
||||
* @param {string} username Username of the user to add
|
||||
* @param {string} discriminator Discriminator of the user to add
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async addTester(username, discriminator) {
|
||||
const app = await this.client.api.applications[this.id].allowlist.post({
|
||||
data: {
|
||||
username,
|
||||
discriminator,
|
||||
},
|
||||
});
|
||||
this.testers.set(app.user.id, {
|
||||
state: app.state,
|
||||
user: this.client.users._add(app.user),
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user from this application's allowlist.
|
||||
* @param {UserResolvable} user User
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async deleteTester(user) {
|
||||
const userId = this.client.users.resolveId(user);
|
||||
await this.client.api.applications[this.id].allowlist[userId].delete();
|
||||
this.testers.delete(userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for editing a application.
|
||||
* @typedef {Object} ApplicationEditData
|
||||
* @property {string} [name] The name of the app
|
||||
* @property {string} [description] The description of the app
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the app
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [cover] The application's default rich presence invite
|
||||
* @property {boolean} [botPublic] When false only app owner can join the app's bot to guilds
|
||||
* @property {boolean} [botRequireCodeGrant] When true the app's bot will only join upon completion of the full oauth2 code grant flow
|
||||
* @property {?string} [TermsOfService] ToS URL
|
||||
* @property {?string} [PrivacyPolicy] Privacy policy URL
|
||||
* @property {number} [flags] The application's public flags
|
||||
* @property {Array<string>} [redirectURIs] Redirect URIs (OAuth2 only)
|
||||
* @property {Array<string>} [tags] Up to 5 tags describing the content and functionality of the application
|
||||
*/
|
||||
/**
|
||||
* Edits this application.
|
||||
* @param {ApplicationEditData} data Edit data for the application
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async edit(data) {
|
||||
const _data = {};
|
||||
if (data.name) _data.name = data.name;
|
||||
if (typeof data.icon !== 'undefined') {
|
||||
_data.icon = await DataResolver.resolveImage(data.icon);
|
||||
}
|
||||
if (data.description) _data.description = data.description;
|
||||
if (typeof data.cover !== 'undefined') {
|
||||
_data.cover = await DataResolver.resolveImage(data.cover);
|
||||
}
|
||||
if (data.botPublic) _data.bot_public = data.botPublic;
|
||||
if (data.botRequireCodeGrant) _data.bot_require_code_grant = data.botRequireCodeGrant;
|
||||
if (data.TermsOfService) _data.terms_of_service_url = data.TermsOfService;
|
||||
if (data.PrivacyPolicy) _data.privacy_policy_url = data.PrivacyPolicy;
|
||||
if (data.flags) _data.flags = data.flags;
|
||||
if (data.redirectURIs) _data.redirect_uris = data.redirectURIs;
|
||||
if (data.tags) _data.tags = data.tags;
|
||||
//
|
||||
const app = await this.client.api.applications[this.id].patch({ data: _data });
|
||||
this._patch(app);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new bot for this application.
|
||||
* @returns {Promise<DeveloperPortalApplication>}
|
||||
*/
|
||||
async createBot() {
|
||||
if (this.bot) throw new Error('Application already has a bot.');
|
||||
await this.client.api.applications[this.id].bot.post();
|
||||
const app = await this.fetch();
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset CLient Secret for this application.
|
||||
* @param {number} MFACode The MFA code (if required)
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async resetClientSecret(MFACode) {
|
||||
const app = MFACode
|
||||
? await this.client.api.applications[this.id].reset.post({
|
||||
data: {
|
||||
code: MFACode,
|
||||
},
|
||||
})
|
||||
: await this.client.api.applications[this.id].reset.post();
|
||||
return app.secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Bot Token for this application.
|
||||
* @param {number} MFACode The MFA code (if required)
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async resetBotToken(MFACode) {
|
||||
const app = MFACode
|
||||
? await this.client.api.applications[this.id].bot.reset.post({
|
||||
data: {
|
||||
code: MFACode,
|
||||
},
|
||||
})
|
||||
: await this.client.api.applications[this.id].bot.reset.post();
|
||||
return app.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this application.
|
||||
* @param {number} MFACode The MFA code (if required)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
delete(MFACode) {
|
||||
return this.client.developerPortal.deleteApplication(this.id, MFACode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new image to this application. (RPC)
|
||||
* @param {BufferResolvable|Base64Resolvable} image Image Resolvable
|
||||
* @param {string} name Name of the image
|
||||
* @returns {ApplicationAsset}
|
||||
*/
|
||||
async addAsset(image, name) {
|
||||
const data = await DataResolver.resolveImage(image);
|
||||
const asset = await this.client.api.applications[this.id].assets.post({
|
||||
data: {
|
||||
type: 1,
|
||||
name,
|
||||
image: data,
|
||||
},
|
||||
});
|
||||
return {
|
||||
id: asset.id,
|
||||
name: asset.name,
|
||||
type: AssetTypes[asset.type - 1],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image from this application. (RPC)
|
||||
* @param {Snowflake} id ID of the image
|
||||
* @returns {Promise<undefined>}
|
||||
*/
|
||||
async deleteAsset(id) {
|
||||
await this.client.api.applications[this.id].assets[id].delete();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this application's role connection metadata records
|
||||
* @returns {Promise<ApplicationRoleConnectionMetadata[]>}
|
||||
*/
|
||||
async fetchRoleConnectionMetadataRecords() {
|
||||
const metadata = await this.client.api.applications(this.id)('role-connections').metadata.get();
|
||||
return metadata.map(data => new ApplicationRoleConnectionMetadata(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for creating or editing an application role connection metadata.
|
||||
* @typedef {Object} ApplicationRoleConnectionMetadataEditOptions
|
||||
* @property {string} name The name of the metadata field
|
||||
* @property {?Object<Locale, string>} [nameLocalizations] The name localizations for the metadata field
|
||||
* @property {string} description The description of the metadata field
|
||||
* @property {?Object<Locale, string>} [descriptionLocalizations] The description localizations for the metadata field
|
||||
* @property {string} key The dictionary key of the metadata field
|
||||
* @property {ApplicationRoleConnectionMetadataType} type The type of the metadata field
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updates this application's role connection metadata records
|
||||
* @param {ApplicationRoleConnectionMetadataEditOptions[]} records The new role connection metadata records
|
||||
* @returns {Promise<ApplicationRoleConnectionMetadata[]>}
|
||||
*/
|
||||
async editRoleConnectionMetadataRecords(records) {
|
||||
const newRecords = await this.client.api
|
||||
.applications(this.client.user.id)('role-connections')
|
||||
.metadata.put({
|
||||
data: records.map(record => ({
|
||||
type: typeof record.type === 'string' ? ApplicationRoleConnectionMetadataTypes[record.type] : record.type,
|
||||
key: record.key,
|
||||
name: record.name,
|
||||
name_localizations: record.nameLocalizations,
|
||||
description: record.description,
|
||||
description_localizations: record.descriptionLocalizations,
|
||||
})),
|
||||
});
|
||||
|
||||
return newRecords.map(data => new ApplicationRoleConnectionMetadata(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the application's name instead of the
|
||||
* Application object.
|
||||
* @returns {?string}
|
||||
* @example
|
||||
* // Logs: Application name: My App
|
||||
* console.log(`Application name: ${application}`);
|
||||
*/
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return super.toJSON({ createdTimestamp: true });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeveloperPortalApplication;
|
@@ -1,75 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Base = require('./Base');
|
||||
|
||||
/**
|
||||
* Guild Folder.
|
||||
* @abstract
|
||||
*/
|
||||
class GuildFolder extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
this._patch(data);
|
||||
}
|
||||
_patch(data) {
|
||||
if ('id' in data) {
|
||||
/**
|
||||
* The guild folder's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
}
|
||||
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The guild folder's name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
}
|
||||
|
||||
if ('color' in data) {
|
||||
/**
|
||||
* The base 10 color of the folder
|
||||
* @type {number}
|
||||
*/
|
||||
this.color = data.color;
|
||||
}
|
||||
|
||||
if ('guild_ids' in data) {
|
||||
/**
|
||||
* The guild folder's guild ids
|
||||
* @type {Snowflake[]}
|
||||
*/
|
||||
this.guild_ids = data.guild_ids;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The hexadecimal version of the folder color, with a leading hashtag
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get hexColor() {
|
||||
return `#${this.color.toString(16).padStart(6, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guilds in the folder
|
||||
* @type {Collection<Snowflake, Guild>}
|
||||
* @readonly
|
||||
*/
|
||||
get guilds() {
|
||||
return this.client.guilds.cache.filter(guild => this.guild_ids.includes(guild.id));
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
color: this.color,
|
||||
guild_ids: this.guild_ids,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildFolder;
|
@@ -1,81 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Base = require('./Base');
|
||||
|
||||
/**
|
||||
* @typedef {Object} SessionClientInfo
|
||||
* @property {string} location Location of the client (using IP address)
|
||||
* @property {string} platform Platform of the client
|
||||
* @property {string} os Operating system of the client
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a Client OAuth2 Application Team.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Session extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('id_hash' in data) {
|
||||
/**
|
||||
* The session hash id
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = data.id_hash;
|
||||
}
|
||||
if ('approx_last_used_time' in data) {
|
||||
this.approxLastUsedTime = data.approx_last_used_time;
|
||||
}
|
||||
if ('client_info' in data) {
|
||||
/**
|
||||
* The client info
|
||||
* @type {SessionClientInfo}
|
||||
*/
|
||||
this.clientInfo = data.client_info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the client was last used at.
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return this.createdAt.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the client was last used at.
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.approxLastUsedTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the client (remote).
|
||||
* @param {string | null} mfaCode MFA code (if 2FA is enabled)
|
||||
* @returns {Promise<undefined>}
|
||||
*/
|
||||
logout(mfaCode) {
|
||||
if (typeof this.client.password !== 'string') throw new Error('REQUIRE_PASSWORD', 'You must provide a password.');
|
||||
return this.client.api.auth.sessions.logout({
|
||||
data: {
|
||||
session_id_hashes: [this.id],
|
||||
password: this.client.password,
|
||||
code: typeof mfaCode === 'string' ? mfaCode : undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Session;
|
@@ -1,14 +1,13 @@
|
||||
'use strict';
|
||||
const { Buffer } = require('buffer');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const { Buffer } = require('node:buffer');
|
||||
const crypto = require('node:crypto');
|
||||
const EventEmitter = require('node:events');
|
||||
const { StringDecoder } = require('node:string_decoder');
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { StringDecoder } = require('string_decoder');
|
||||
const chalk = require('chalk');
|
||||
const fetch = require('node-fetch');
|
||||
const { encode: urlsafe_b64encode } = require('safe-base64');
|
||||
const WebSocket = require('ws');
|
||||
const { defaultUA } = require('./Constants');
|
||||
const { UserAgent } = require('./Constants');
|
||||
const Options = require('./Options');
|
||||
|
||||
const defaultClientOptions = Options.createDefault();
|
||||
@@ -22,9 +21,9 @@ const receiveEvent = {
|
||||
NONCE_PROOF: 'nonce_proof',
|
||||
PENDING_REMOTE_INIT: 'pending_remote_init',
|
||||
HEARTBEAT_ACK: 'heartbeat_ack',
|
||||
PENDING_LOGIN: 'pending_ticket',
|
||||
PENDING_TICKET: 'pending_ticket',
|
||||
CANCEL: 'cancel',
|
||||
SUCCESS: 'pending_login',
|
||||
PENDING_LOGIN: 'pending_login',
|
||||
};
|
||||
|
||||
const sendEvent = {
|
||||
@@ -37,266 +36,167 @@ const Event = {
|
||||
READY: 'ready',
|
||||
ERROR: 'error',
|
||||
CANCEL: 'cancel',
|
||||
WAIT: 'pending',
|
||||
SUCCESS: 'success',
|
||||
WAIT_SCAN: 'pending',
|
||||
FINISH: 'finish',
|
||||
CLOSED: 'closed',
|
||||
DEBUG: 'debug',
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} DiscordAuthWebsocketOptions
|
||||
* @property {?boolean} [debug=false] Log debug info
|
||||
* @property {?boolean} [hiddenLog=false] Hide log ?
|
||||
* @property {?boolean} [autoLogin=false] Automatically login (DiscordJS.Client Login) ?
|
||||
* @property {?boolean} [failIfError=true] Throw error ?
|
||||
* @property {?boolean} [generateQR=true] Create QR Code ?
|
||||
* @property {?number} [apiVersion=9] API Version
|
||||
* @property {?string} [userAgent] User Agent
|
||||
* @property {?Object.<string,string>} [wsProperties] Web Socket Properties
|
||||
*/
|
||||
|
||||
/**
|
||||
* Discord Auth QR (Discord.RemoteAuth will be removed in the future, v13.9.0 release)
|
||||
* Discord Auth QR
|
||||
* @extends {EventEmitter}
|
||||
* @abstract
|
||||
*/
|
||||
class DiscordAuthWebsocket extends EventEmitter {
|
||||
#ws = null;
|
||||
#heartbeatInterval = null;
|
||||
#expire = null;
|
||||
#publicKey = null;
|
||||
#privateKey = null;
|
||||
#ticket = null;
|
||||
#fingerprint = '';
|
||||
#userDecryptString = '';
|
||||
|
||||
/**
|
||||
* Creates a new DiscordAuthWebsocket instance.
|
||||
* @param {?DiscordAuthWebsocketOptions} options Options
|
||||
*/
|
||||
constructor(options) {
|
||||
constructor() {
|
||||
super();
|
||||
/**
|
||||
* WebSocket
|
||||
* @type {?WebSocket}
|
||||
*/
|
||||
this.ws = null;
|
||||
/**
|
||||
* Heartbeat Interval
|
||||
* @type {?number}
|
||||
*/
|
||||
this.heartbeatInterval = NaN;
|
||||
this._expire = NaN;
|
||||
this.key = null;
|
||||
/**
|
||||
* User (Scan QR Code)
|
||||
* @type {?Object}
|
||||
*/
|
||||
this.user = null;
|
||||
/**
|
||||
* Temporary Token (Scan QR Code)
|
||||
* @type {?string}
|
||||
*/
|
||||
this.token = undefined;
|
||||
/**
|
||||
* Real Token (Login)
|
||||
* @type {?string}
|
||||
*/
|
||||
this.realToken = undefined;
|
||||
/**
|
||||
* Fingerprint (QR Code)
|
||||
* @type {?string}
|
||||
*/
|
||||
this.fingerprint = null;
|
||||
|
||||
/**
|
||||
* Captcha Handler
|
||||
* @type {Function}
|
||||
* @param {Captcha} data hcaptcha data
|
||||
* @returns {Promise<string>} Captcha token
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this.captchaSolver = data =>
|
||||
new Promise((resolve, reject) => {
|
||||
reject(
|
||||
new Error(`
|
||||
Captcha Handler not found - Please set captchaSolver option
|
||||
Example captchaSolver function:
|
||||
|
||||
new DiscordAuthWebsocket({
|
||||
captchaSolver: async (data) => {
|
||||
const token = await hcaptchaSolver(data.captcha_sitekey, 'discord.com');
|
||||
return token;
|
||||
this.token = '';
|
||||
}
|
||||
});
|
||||
|
||||
`),
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Captcha Cache
|
||||
* @type {?Captcha}
|
||||
*/
|
||||
this.captchaCache = null;
|
||||
|
||||
this._validateOptions(options);
|
||||
|
||||
this.callFindRealTokenCount = 0;
|
||||
}
|
||||
/**
|
||||
* Get expire time
|
||||
* @type {string} Expire time
|
||||
* @readonly
|
||||
* @type {string}
|
||||
*/
|
||||
get exprireTime() {
|
||||
return this._expire.toLocaleString('en-US');
|
||||
get AuthURL() {
|
||||
return baseURL + this.#fingerprint;
|
||||
}
|
||||
_validateOptions(options = {}) {
|
||||
/**
|
||||
* Options
|
||||
* @type {?DiscordAuthWebsocketOptions}
|
||||
*/
|
||||
this.options = {
|
||||
debug: false,
|
||||
hiddenLog: false,
|
||||
autoLogin: false,
|
||||
failIfError: true,
|
||||
generateQR: true,
|
||||
apiVersion: 9,
|
||||
userAgent: defaultUA,
|
||||
wsProperties: defaultClientOptions.ws.properties,
|
||||
captchaSolver: () => new Error('Captcha Handler not found. Please set captchaSolver option.'),
|
||||
};
|
||||
if (typeof options == 'object') {
|
||||
if (typeof options.debug == 'boolean') this.options.debug = options.debug;
|
||||
if (typeof options.hiddenLog == 'boolean') this.options.hiddenLog = options.hiddenLog;
|
||||
if (typeof options.autoLogin == 'boolean') this.options.autoLogin = options.autoLogin;
|
||||
if (typeof options.failIfError == 'boolean') this.options.failIfError = options.failIfError;
|
||||
if (typeof options.generateQR == 'boolean') this.options.generateQR = options.generateQR;
|
||||
if (typeof options.apiVersion == 'number') this.options.apiVersion = options.apiVersion;
|
||||
if (typeof options.userAgent == 'string') this.options.userAgent = options.userAgent;
|
||||
if (typeof options.wsProperties == 'object') this.options.wsProperties = options.wsProperties;
|
||||
if (typeof options.captchaSolver == 'function') this.captchaSolver = options.captchaSolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Date}
|
||||
*/
|
||||
get exprire() {
|
||||
return this.#expire;
|
||||
}
|
||||
_createWebSocket(url) {
|
||||
this.ws = new WebSocket(url, {
|
||||
|
||||
/**
|
||||
* @type {UserRaw}
|
||||
*/
|
||||
get user() {
|
||||
return DiscordAuthWebsocket.decryptUser(this.#userDecryptString);
|
||||
}
|
||||
|
||||
#createWebSocket(url) {
|
||||
this.#ws = new WebSocket(url, {
|
||||
headers: {
|
||||
Origin: 'https://discord.com',
|
||||
'User-Agent': this.options.userAgent,
|
||||
'User-Agent': UserAgent,
|
||||
},
|
||||
});
|
||||
this._handleWebSocket();
|
||||
this.#handleWebSocket();
|
||||
}
|
||||
_handleWebSocket() {
|
||||
this.ws.on('error', error => {
|
||||
this._logger('error', error);
|
||||
|
||||
#handleWebSocket() {
|
||||
this.#ws.on('error', error => {
|
||||
/**
|
||||
* WS Error
|
||||
* @event DiscordAuthWebsocket#error
|
||||
* @param {Error} error Error
|
||||
*/
|
||||
this.emit(Event.ERROR, error);
|
||||
});
|
||||
this.ws.on('open', () => {
|
||||
this._logger('debug', 'Client Connected');
|
||||
this.#ws.on('open', () => {
|
||||
/**
|
||||
* Debug Event
|
||||
* @event DiscordAuthWebsocket#debug
|
||||
* @param {string} msg Debug msg
|
||||
*/
|
||||
this.emit(Event.DEBUG, '[WS] Client Connected');
|
||||
});
|
||||
this.ws.on('close', () => {
|
||||
this._logger('debug', 'Connection closed.');
|
||||
});
|
||||
this.ws.on('message', message => {
|
||||
this._handleMessage(JSON.parse(message));
|
||||
this.#ws.on('close', () => {
|
||||
this.emit(Event.DEBUG, '[WS] Connection closed');
|
||||
});
|
||||
this.#ws.on('message', this.#handleMessage.bind(this));
|
||||
}
|
||||
_handleMessage(message) {
|
||||
|
||||
#handleMessage(message) {
|
||||
message = JSON.parse(message);
|
||||
switch (message.op) {
|
||||
case receiveEvent.HELLO: {
|
||||
this._ready(message);
|
||||
this.#ready(message);
|
||||
break;
|
||||
}
|
||||
|
||||
case receiveEvent.NONCE_PROOF: {
|
||||
this._receiveNonceProof(message);
|
||||
this.#receiveNonceProof(message);
|
||||
break;
|
||||
}
|
||||
|
||||
case receiveEvent.PENDING_REMOTE_INIT: {
|
||||
this._pendingRemoteInit(message);
|
||||
break;
|
||||
}
|
||||
case receiveEvent.HEARTBEAT_ACK: {
|
||||
this._logger('debug', 'Heartbeat acknowledged.');
|
||||
this._heartbeatAck();
|
||||
break;
|
||||
}
|
||||
case receiveEvent.PENDING_LOGIN: {
|
||||
this._pendingLogin(message);
|
||||
break;
|
||||
}
|
||||
case receiveEvent.CANCEL: {
|
||||
this._logger('debug', 'Cancel login.');
|
||||
this.#fingerprint = message.fingerprint;
|
||||
/**
|
||||
* Emitted whenever a user cancels the login process.
|
||||
* @event DiscordAuthWebsocket#cancel
|
||||
* @param {object} user User (Raw)
|
||||
* Ready Event
|
||||
* @event DiscordAuthWebsocket#ready
|
||||
* @param {DiscordAuthWebsocket} client WS
|
||||
*/
|
||||
this.emit(Event.CANCEL, this.user);
|
||||
this.emit(Event.READY, this);
|
||||
break;
|
||||
}
|
||||
|
||||
case receiveEvent.HEARTBEAT_ACK: {
|
||||
this.emit(Event.DEBUG, `Heartbeat acknowledged.`);
|
||||
this.#heartbeatAck();
|
||||
break;
|
||||
}
|
||||
|
||||
case receiveEvent.PENDING_TICKET: {
|
||||
this.#pendingLogin(message);
|
||||
break;
|
||||
}
|
||||
|
||||
case receiveEvent.CANCEL: {
|
||||
/**
|
||||
* Cancel
|
||||
* @event DiscordAuthWebsocket#cancel
|
||||
* @param {DiscordAuthWebsocket} client WS
|
||||
*/
|
||||
this.emit(Event.CANCEL, this);
|
||||
this.destroy();
|
||||
break;
|
||||
}
|
||||
case receiveEvent.SUCCESS: {
|
||||
this._logger('debug', 'Receive Token - Login Success.', message.ticket);
|
||||
/**
|
||||
* Emitted whenever a token is created. (Fake token)
|
||||
* @event DiscordAuthWebsocket#success
|
||||
* @param {object} user Discord User
|
||||
* @param {string} token Discord Token (Fake)
|
||||
*/
|
||||
this.emit(Event.SUCCESS, this.user, message.ticket);
|
||||
this.token = message.ticket;
|
||||
this._findRealToken();
|
||||
this._logger('default', 'Get token success.');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this._logger('debug', `Unknown op: ${message.op}`, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
_logger(type = 'default', ...message) {
|
||||
if (this.options.hiddenLog) return;
|
||||
switch (type.toLowerCase()) {
|
||||
case 'error': {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.options.failIfError
|
||||
? this._throwError(new Error(message[0]))
|
||||
: console.error(chalk.red(`[DiscordRemoteAuth] ERROR`), ...message);
|
||||
break;
|
||||
}
|
||||
case 'default': {
|
||||
console.log(chalk.green(`[DiscordRemoteAuth]`), ...message);
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
if (this.options.debug) console.log(chalk.yellow(`[DiscordRemoteAuth] DEBUG`), ...message);
|
||||
|
||||
case receiveEvent.PENDING_LOGIN: {
|
||||
this.#ticket = message.ticket;
|
||||
this.#findRealToken();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_throwError(error) {
|
||||
console.log(chalk.red(`[DiscordRemoteAuth] ERROR`), error);
|
||||
throw error;
|
||||
}
|
||||
_send(op, data) {
|
||||
if (!this.ws) this._throwError(new Error('WebSocket is not connected.'));
|
||||
|
||||
#send(op, data) {
|
||||
if (!this.#ws) return;
|
||||
let payload = { op: op };
|
||||
if (data !== null) payload = { ...payload, ...data };
|
||||
this._logger('debug', `Send Data:`, payload);
|
||||
this.ws.send(JSON.stringify(payload));
|
||||
this.#ws.send(JSON.stringify(payload));
|
||||
}
|
||||
_heartbeat() {
|
||||
this._send(sendEvent.HEARTBEAT);
|
||||
}
|
||||
_heartbeatAck() {
|
||||
|
||||
#heartbeatAck() {
|
||||
setTimeout(() => {
|
||||
this._heartbeat();
|
||||
}, this.heartbeatInterval).unref();
|
||||
this.#send(sendEvent.HEARTBEAT);
|
||||
}, this.#heartbeatInterval).unref();
|
||||
}
|
||||
_ready(data) {
|
||||
this._logger('debug', 'Attempting server handshake...');
|
||||
this._expire = new Date(Date.now() + data.timeout_ms);
|
||||
this.heartbeatInterval = data.heartbeat_interval;
|
||||
this._createKey();
|
||||
this._heartbeatAck();
|
||||
this._init();
|
||||
|
||||
#ready(data) {
|
||||
this.emit(Event.DEBUG, 'Attempting server handshake...');
|
||||
this.#expire = new Date(Date.now() + data.timeout_ms);
|
||||
this.#heartbeatInterval = data.heartbeat_interval;
|
||||
this.#createKey();
|
||||
this.#heartbeatAck();
|
||||
this.#init();
|
||||
}
|
||||
_createKey() {
|
||||
if (this.key) this._throwError(new Error('Key is already created.'));
|
||||
this.key = crypto.generateKeyPairSync('rsa', {
|
||||
|
||||
#createKey() {
|
||||
const key = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
@@ -307,35 +207,41 @@ new DiscordAuthWebsocket({
|
||||
format: 'pem',
|
||||
},
|
||||
});
|
||||
this.#privateKey = key.privateKey;
|
||||
this.#publicKey = key.publicKey;
|
||||
}
|
||||
_createPublicKey() {
|
||||
if (!this.key) this._throwError(new Error('Key is not created.'));
|
||||
this._logger('debug', 'Generating public key...');
|
||||
|
||||
#encodePublicKey() {
|
||||
const decoder = new StringDecoder('utf-8');
|
||||
let pub_key = decoder.write(this.key.publicKey);
|
||||
let pub_key = decoder.write(this.#publicKey);
|
||||
pub_key = pub_key.split('\n').slice(1, -2).join('');
|
||||
this._logger('debug', 'Public key generated.', pub_key);
|
||||
return pub_key;
|
||||
}
|
||||
_init() {
|
||||
const public_key = this._createPublicKey();
|
||||
this._send(sendEvent.INIT, { encoded_public_key: public_key });
|
||||
|
||||
#init() {
|
||||
const public_key = this.#encodePublicKey();
|
||||
this.#send(sendEvent.INIT, { encoded_public_key: public_key });
|
||||
}
|
||||
_receiveNonceProof(data) {
|
||||
|
||||
#receiveNonceProof(data) {
|
||||
const nonce = data.encrypted_nonce;
|
||||
const decrypted_nonce = this._decryptPayload(nonce);
|
||||
let proof = crypto.createHash('sha256').update(decrypted_nonce).digest();
|
||||
proof = urlsafe_b64encode(proof);
|
||||
proof = proof.replace(/\s+$/, '');
|
||||
this._send(sendEvent.NONCE_PROOF, { proof: proof });
|
||||
this._logger('debug', `Nonce proof decrypted:`, proof);
|
||||
const decrypted_nonce = this.#decryptPayload(nonce);
|
||||
const proof = crypto
|
||||
.createHash('sha256')
|
||||
.update(decrypted_nonce)
|
||||
.digest()
|
||||
.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+/, '')
|
||||
.replace(/\s+$/, '');
|
||||
this.#send(sendEvent.NONCE_PROOF, { proof: proof });
|
||||
}
|
||||
_decryptPayload(encrypted_payload) {
|
||||
if (!this.key) this._throwError(new Error('Key is not created.'));
|
||||
|
||||
#decryptPayload(encrypted_payload) {
|
||||
const payload = Buffer.from(encrypted_payload, 'base64');
|
||||
this._logger('debug', `Encrypted Payload (Buffer):`, payload);
|
||||
const decoder = new StringDecoder('utf-8');
|
||||
const private_key = decoder.write(this.key.privateKey);
|
||||
const private_key = decoder.write(this.#privateKey);
|
||||
const data = crypto.privateDecrypt(
|
||||
{
|
||||
key: private_key,
|
||||
@@ -344,170 +250,129 @@ new DiscordAuthWebsocket({
|
||||
},
|
||||
payload,
|
||||
);
|
||||
this._logger('debug', `Decrypted Payload:`, data.toString());
|
||||
return data;
|
||||
}
|
||||
_pendingLogin(data) {
|
||||
const user_data = this._decryptPayload(data.encrypted_user_payload);
|
||||
const user = new User(user_data.toString());
|
||||
this.user = user;
|
||||
|
||||
#pendingLogin(data) {
|
||||
const user_data = this.#decryptPayload(data.encrypted_user_payload);
|
||||
this.#userDecryptString = user_data.toString();
|
||||
|
||||
/**
|
||||
* @typedef {Object} UserRaw
|
||||
* @property {Snowflake} id
|
||||
* @property {string} username
|
||||
* @property {number} discriminator
|
||||
* @property {string} avatar
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted whenever a user is scan QR Code.
|
||||
* @event DiscordAuthWebsocket#pending
|
||||
* @param {object} user Discord User Raw
|
||||
* @param {UserRaw} user Discord User Raw
|
||||
*/
|
||||
this.emit(Event.WAIT, user);
|
||||
this._logger('debug', 'Waiting for user to finish login...');
|
||||
this.user.prettyPrint(this);
|
||||
this._logger('default', 'Please check your phone again to confirm login.');
|
||||
this.emit(Event.WAIT_SCAN, this.user);
|
||||
}
|
||||
_pendingRemoteInit(data) {
|
||||
this._logger('debug', `Pending Remote Init:`, data);
|
||||
/**
|
||||
* Emitted whenever a url is created.
|
||||
* @event DiscordAuthWebsocket#ready
|
||||
* @param {string} fingerprint Fingerprint
|
||||
* @param {string} url DiscordAuthWebsocket
|
||||
*/
|
||||
this.emit(Event.READY, data.fingerprint, `${baseURL}${data.fingerprint}`);
|
||||
this.fingerprint = data.fingerprint;
|
||||
if (this.options.generateQR) this.generateQR();
|
||||
}
|
||||
_awaitLogin(client) {
|
||||
this.once(Event.FINISH, (user, token) => {
|
||||
this._logger('debug', 'Create login state...', user, token);
|
||||
client.login(token);
|
||||
|
||||
#awaitLogin(client) {
|
||||
return new Promise(r => {
|
||||
this.once(Event.FINISH, token => {
|
||||
r(client.login(token));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to DiscordAuthWebsocket.
|
||||
* @param {?Client} client Using only for auto login.
|
||||
* @returns {undefined}
|
||||
* Connect WS
|
||||
* @param {Client} [client] DiscordJS Client
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
connect(client) {
|
||||
this._createWebSocket(wsURL);
|
||||
if (client && this.options.autoLogin) this._awaitLogin(client);
|
||||
this.#createWebSocket(wsURL);
|
||||
if (client) {
|
||||
return this.#awaitLogin(client);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from DiscordAuthWebsocket.
|
||||
* @returns {undefined}
|
||||
* Destroy client
|
||||
* @returns {void}
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.ws) this._throwError(new Error('WebSocket is not connected.'));
|
||||
if (!this.ws) return;
|
||||
this.ws.close();
|
||||
this.emit(Event.DEBUG, 'WebSocket closed.');
|
||||
/**
|
||||
* Emitted whenever a connection is closed.
|
||||
* @event DiscordAuthWebsocket#closed
|
||||
* @param {boolean} loginState Login state
|
||||
*/
|
||||
this.emit(Event.CLOSED, this.token);
|
||||
this._logger('debug', 'WebSocket closed.');
|
||||
this.emit(Event.CLOSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate QR code for user to scan (Terminal)
|
||||
* @returns {undefined}
|
||||
* @returns {void}
|
||||
*/
|
||||
generateQR() {
|
||||
if (!this.fingerprint) this._throwError(new Error('Fingerprint is not created.'));
|
||||
require('@aikochan2k6/qrcode-terminal').generate(`${baseURL}${this.fingerprint}`, {
|
||||
if (!this.#fingerprint) return;
|
||||
require('@aikochan2k6/qrcode-terminal').generate(this.AuthURL, {
|
||||
small: true,
|
||||
});
|
||||
this._logger('default', `Please scan the QR code to continue.\nQR Code will expire in ${this.exprireTime}`);
|
||||
}
|
||||
|
||||
async _findRealToken(captchaSolveData) {
|
||||
this.callFindRealTokenCount++;
|
||||
if (!this.token) return this._throwError(new Error('Token is not created.'));
|
||||
if (!captchaSolveData && this.captchaCache) return this._throwError(new Error('Captcha is not solved.'));
|
||||
if (this.callFindRealTokenCount > 5) {
|
||||
return this._throwError(
|
||||
new Error(
|
||||
`Failed to find real token (${this.callFindRealTokenCount} times) ${this.captchaCache ? '[Captcha]' : ''}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
this._logger('debug', 'Find real token...');
|
||||
const res = await (
|
||||
await fetch(`https://discord.com/api/v${this.options.apiVersion}/users/@me/remote-auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Accept-Language': 'en-US',
|
||||
'Content-Type': 'application/json',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'X-Debug-Options': 'bugReporterEnabled',
|
||||
'X-Super-Properties': `${Buffer.from(JSON.stringify(this.options.wsProperties), 'ascii').toString('base64')}`,
|
||||
'X-Discord-Locale': 'en-US',
|
||||
'User-Agent': this.options.userAgent,
|
||||
Referer: 'https://discord.com/channels/@me',
|
||||
Connection: 'keep-alive',
|
||||
Origin: 'https://discord.com',
|
||||
},
|
||||
body: JSON.stringify(
|
||||
captchaSolveData
|
||||
? {
|
||||
ticket: this.token,
|
||||
captcha_rqtoken: this.captchaCache.captcha_rqtoken,
|
||||
captcha_key: captchaSolveData,
|
||||
}
|
||||
: {
|
||||
ticket: this.token,
|
||||
},
|
||||
),
|
||||
#findRealToken() {
|
||||
return fetch(`https://discord.com/api/v9/users/@me/remote-auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Accept-Language': 'en-US',
|
||||
'Content-Type': 'application/json',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'X-Debug-Options': 'bugReporterEnabled',
|
||||
'X-Super-Properties': `${Buffer.from(JSON.stringify(defaultClientOptions.ws.properties), 'ascii').toString(
|
||||
'base64',
|
||||
)}`,
|
||||
'X-Discord-Locale': 'en-US',
|
||||
'User-Agent': UserAgent,
|
||||
Referer: 'https://discord.com/channels/@me',
|
||||
Connection: 'keep-alive',
|
||||
Origin: 'https://discord.com',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ticket: this.#ticket,
|
||||
}),
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(res => {
|
||||
if (res.encrypted_token) {
|
||||
this.token = this.#decryptPayload(res.encrypted_token).toString();
|
||||
}
|
||||
/**
|
||||
* Emitted whenever a real token is found.
|
||||
* @event DiscordAuthWebsocket#finish
|
||||
* @param {string} token Discord Token
|
||||
*/
|
||||
this.emit(Event.FINISH, this.token);
|
||||
this.destroy();
|
||||
})
|
||||
).json();
|
||||
if (res?.captcha_key) {
|
||||
this.captchaCache = res;
|
||||
} else if (!res.encrypted_token) {
|
||||
this._throwError(new Error('Request failed. Please try again.', res));
|
||||
this.captchaCache = null;
|
||||
}
|
||||
if (!res && this.captchaCache) {
|
||||
this._logger('default', 'Captcha is detected. Please solve the captcha to continue.');
|
||||
this._logger('debug', 'Try call captchaSolver()', this.captchaCache);
|
||||
const token = await this.options.captchaSolver(this.captchaCache);
|
||||
return this._findRealToken(token);
|
||||
}
|
||||
this.realToken = this._decryptPayload(res.encrypted_token).toString();
|
||||
/**
|
||||
* Emitted whenever a real token is found.
|
||||
* @event DiscordAuthWebsocket#finish
|
||||
* @param {object} user User
|
||||
* @param {string} token Real token
|
||||
*/
|
||||
this.emit(Event.FINISH, this.user, this.realToken);
|
||||
return this;
|
||||
.catch(() => false);
|
||||
}
|
||||
}
|
||||
|
||||
class User {
|
||||
constructor(payload) {
|
||||
static decryptUser(payload) {
|
||||
const values = payload.split(':');
|
||||
this.id = values[0];
|
||||
this.username = values[3];
|
||||
this.discriminator = values[1];
|
||||
this.avatar = values[2];
|
||||
return this;
|
||||
}
|
||||
get avatarURL() {
|
||||
return `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.${
|
||||
this.avatar.startsWith('a_') ? 'gif' : 'png'
|
||||
}`;
|
||||
}
|
||||
get tag() {
|
||||
return `${this.username}#${this.discriminator}`;
|
||||
}
|
||||
prettyPrint(RemoteAuth) {
|
||||
let string = `\n`;
|
||||
string += ` ${chalk.bgBlue('User:')} `;
|
||||
string += `${this.tag} (${this.id})\n`;
|
||||
string += ` ${chalk.bgGreen('Avatar URL:')} `;
|
||||
string += chalk.cyan(`${this.avatarURL}\n`);
|
||||
string += ` ${chalk.bgMagenta('Token:')} `;
|
||||
string += chalk.red(`${this.token ? this.token : 'Unknown'}`);
|
||||
RemoteAuth._logger('default', string);
|
||||
const id = values[0];
|
||||
const username = values[3];
|
||||
const discriminator = values[1];
|
||||
const avatar = values[2];
|
||||
return {
|
||||
id,
|
||||
username,
|
||||
discriminator,
|
||||
avatar,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
259
src/util/Util.js
259
src/util/Util.js
@@ -5,12 +5,13 @@ const process = require('node:process');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const fetch = require('node-fetch');
|
||||
const { Colors } = require('./Constants');
|
||||
const { RangeError, TypeError, Error: DJSError } = require('../errors');
|
||||
const { Error: DiscordError, RangeError, TypeError } = require('../errors');
|
||||
const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
||||
const isObject = d => typeof d === 'object' && d !== null;
|
||||
|
||||
let deprecationEmittedForSplitMessage = false;
|
||||
let deprecationEmittedForRemoveMentions = false;
|
||||
let deprecationEmittedForResolveAutoArchiveMaxLimit = false;
|
||||
|
||||
const TextSortableGroupTypes = ['GUILD_TEXT', 'GUILD_ANNOUCMENT', 'GUILD_FORUM'];
|
||||
const VoiceSortableGroupTypes = ['GUILD_VOICE', 'GUILD_STAGE_VOICE'];
|
||||
@@ -138,6 +139,7 @@ class Util extends null {
|
||||
* @property {boolean} [numberedList=false] Whether to escape numbered lists
|
||||
* @property {boolean} [maskedLink=false] Whether to escape masked links
|
||||
*/
|
||||
|
||||
/**
|
||||
* Escapes any Discord-flavour markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -220,6 +222,7 @@ class Util extends null {
|
||||
if (maskedLink) text = Util.escapeMaskedLink(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes code block markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -228,6 +231,7 @@ class Util extends null {
|
||||
static escapeCodeBlock(text) {
|
||||
return text.replaceAll('```', '\\`\\`\\`');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes inline code markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -236,6 +240,7 @@ class Util extends null {
|
||||
static escapeInlineCode(text) {
|
||||
return text.replace(/(?<=^|[^`])``?(?=[^`]|$)/g, match => (match.length === 2 ? '\\`\\`' : '\\`'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes italic markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -253,6 +258,7 @@ class Util extends null {
|
||||
return `\\_${match}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes bold markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -265,6 +271,7 @@ class Util extends null {
|
||||
return '\\*\\*';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes underline markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -277,6 +284,7 @@ class Util extends null {
|
||||
return '\\_\\_';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes strikethrough markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -285,6 +293,7 @@ class Util extends null {
|
||||
static escapeStrikethrough(text) {
|
||||
return text.replaceAll('~~', '\\~\\~');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes spoiler markdown in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -293,6 +302,7 @@ class Util extends null {
|
||||
static escapeSpoiler(text) {
|
||||
return text.replaceAll('||', '\\|\\|');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes escape characters in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -301,6 +311,7 @@ class Util extends null {
|
||||
static escapeEscape(text) {
|
||||
return text.replaceAll('\\', '\\\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes heading characters in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -309,6 +320,7 @@ class Util extends null {
|
||||
static escapeHeading(text) {
|
||||
return text.replaceAll(/^( {0,2}[*-] +)?(#{1,3} )/gm, '$1\\$2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes bulleted list characters in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -317,6 +329,7 @@ class Util extends null {
|
||||
static escapeBulletedList(text) {
|
||||
return text.replaceAll(/^( *)[*-]( +)/gm, '$1\\-$2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes numbered list characters in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -325,6 +338,7 @@ class Util extends null {
|
||||
static escapeNumberedList(text) {
|
||||
return text.replaceAll(/^( *\d+)\./gm, '$1\\.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes masked link characters in a string.
|
||||
* @param {string} text Content to escape
|
||||
@@ -334,6 +348,16 @@ class Util extends null {
|
||||
return text.replaceAll(/\[.+\]\(.+\)/gm, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} FetchRecommendedShardsOptions
|
||||
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
|
||||
* @property {number} [multipleOf=1] The multiple the shard count should round up to. (16 for large bot sharding)
|
||||
*/
|
||||
|
||||
static fetchRecommendedShards() {
|
||||
throw new DiscordError('INVALID_USER_API');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses emoji info out of a string. The string must be one of:
|
||||
* * A UTF-8 emoji (no id)
|
||||
@@ -623,26 +647,22 @@ class Util extends null {
|
||||
|
||||
/**
|
||||
* Resolves the maximum time a guild's thread channels should automatically archive in case of no recent activity.
|
||||
* @deprecated
|
||||
* @param {Guild} guild The guild to resolve this limit from.
|
||||
* @deprecated This will be removed in the next major version.
|
||||
* @returns {number}
|
||||
*/
|
||||
static resolveAutoArchiveMaxLimit() {
|
||||
if (!deprecationEmittedForResolveAutoArchiveMaxLimit) {
|
||||
process.emitWarning(
|
||||
// eslint-disable-next-line max-len
|
||||
"The Util.resolveAutoArchiveMaxLimit method and the 'MAX' option are deprecated and will be removed in the next major version.",
|
||||
'DeprecationWarning',
|
||||
);
|
||||
deprecationEmittedForResolveAutoArchiveMaxLimit = true;
|
||||
}
|
||||
return 10080;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily evaluates a callback function (yea it's v14 :yay:)
|
||||
* @param {Function} cb The callback to lazily evaluate
|
||||
* @returns {Function}
|
||||
* @example
|
||||
* const User = lazy(() => require('./User'));
|
||||
* const user = new (User())(client, data);
|
||||
*/
|
||||
static lazy(cb) {
|
||||
let defaultValue;
|
||||
return () => (defaultValue ??= cb());
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an API guild forum tag to camel-cased guild forum tag.
|
||||
* @param {APIGuildForumTag} tag The tag to transform
|
||||
@@ -708,95 +728,6 @@ class Util extends null {
|
||||
};
|
||||
}
|
||||
|
||||
static async getAttachments(client, channelId, ...files) {
|
||||
files = files.flat(2);
|
||||
if (!files.length) return [];
|
||||
files = files.map((file, i) => ({
|
||||
filename: file.name ?? file.attachment?.name ?? file.attachment?.filename ?? 'file.jpg',
|
||||
// 25MB = 26_214_400bytes
|
||||
file_size: Math.floor((26_214_400 / 10) * Math.random()),
|
||||
id: `${i}`,
|
||||
}));
|
||||
const { attachments } = await client.api.channels[channelId].attachments.post({
|
||||
data: {
|
||||
files,
|
||||
},
|
||||
});
|
||||
return attachments;
|
||||
}
|
||||
|
||||
static uploadFile(data, url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, {
|
||||
method: 'PUT',
|
||||
body: data,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
resolve(res);
|
||||
} else {
|
||||
reject(res);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
static testImportModule(name) {
|
||||
try {
|
||||
require.resolve(name);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static getProxyObject(proxy) {
|
||||
const protocol = new URL(proxy).protocol.slice(0, -1);
|
||||
const mapObject = {
|
||||
http: 'https', // Cuz we can't use http for discord
|
||||
https: 'https',
|
||||
socks4: 'socks',
|
||||
socks5: 'socks',
|
||||
'pac+http': 'pac',
|
||||
'pac+https': 'pac',
|
||||
};
|
||||
const proxyType = mapObject[protocol];
|
||||
switch (proxyType) {
|
||||
case 'https': {
|
||||
if (!Util.testImportModule('https-proxy-agent')) {
|
||||
throw new DJSError('MISSING_MODULE', 'https-proxy-agent', 'npm install https-proxy-agent');
|
||||
}
|
||||
const httpsProxyAgent = require('https-proxy-agent');
|
||||
return new httpsProxyAgent.HttpsProxyAgent(proxy);
|
||||
}
|
||||
|
||||
case 'socks': {
|
||||
if (!Util.testImportModule('socks-proxy-agent')) {
|
||||
throw new DJSError('MISSING_MODULE', 'socks-proxy-agent', 'npm install socks-proxy-agent');
|
||||
}
|
||||
const socksProxyAgent = require('socks-proxy-agent');
|
||||
return new socksProxyAgent.SocksProxyAgent(proxy);
|
||||
}
|
||||
|
||||
case 'pac': {
|
||||
if (!Util.testImportModule('pac-proxy-agent')) {
|
||||
throw new DJSError('MISSING_MODULE', 'pac-proxy-agent', 'npm install pac-proxy-agent');
|
||||
}
|
||||
const pacProxyAgent = require('pac-proxy-agent');
|
||||
return new pacProxyAgent.PacProxyAgent(proxy);
|
||||
}
|
||||
|
||||
default: {
|
||||
if (!Util.testImportModule('proxy-agent')) {
|
||||
throw new DJSError('MISSING_MODULE', 'proxy-agent', 'npm install proxy-agent@5');
|
||||
}
|
||||
const proxyAgent = require('proxy-agent');
|
||||
return new proxyAgent(proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of the channel types that can be moved in the channel group. For example, a GuildText channel would
|
||||
* return an array containing the types that can be ordered within the text channels (always at the top), and a voice
|
||||
@@ -831,94 +762,38 @@ class Util extends null {
|
||||
return Number(BigInt(userId) >> 22n) % 6;
|
||||
}
|
||||
|
||||
static clientRequiredAction(client, code) {
|
||||
let msg = '';
|
||||
let stopClient = false;
|
||||
switch (code) {
|
||||
case null: {
|
||||
msg = 'All required actions have been completed.';
|
||||
break;
|
||||
}
|
||||
case 'AGREEMENTS': {
|
||||
msg = 'You need to accept the new Terms of Service and Privacy Policy.';
|
||||
// https://discord.com/api/v9/users/@me/agreements
|
||||
client.api
|
||||
.users('@me')
|
||||
.agreements.patch({
|
||||
data: {
|
||||
terms: true,
|
||||
privacy: true,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
client.emit(
|
||||
'debug',
|
||||
'[USER_REQUIRED_ACTION] Successfully accepted the new Terms of Service and Privacy Policy.',
|
||||
);
|
||||
})
|
||||
.catch(e => {
|
||||
client.emit(
|
||||
'debug',
|
||||
`[USER_REQUIRED_ACTION] Failed to accept the new Terms of Service and Privacy Policy: ${e}`,
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_CAPTCHA': {
|
||||
msg = 'You need to complete a captcha.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_EMAIL': {
|
||||
msg = 'You need to verify your email.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_EMAIL': {
|
||||
msg = 'You need to reverify your email.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_PHONE': {
|
||||
msg = 'You need to verify your phone number.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_PHONE': {
|
||||
msg = 'You need to reverify your phone number.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE': {
|
||||
msg = 'You need to verify your email or verify your phone number.';
|
||||
stopClient = true; // Maybe not
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE': {
|
||||
msg = 'You need to reverify your email or verify your phone number.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE': {
|
||||
msg = 'You need to verify your email or reverify your phone number.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
case 'REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE': {
|
||||
msg = 'You need to reverify your email or reverify your phone number.';
|
||||
stopClient = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
msg = `Unknown required action: ${code}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stopClient) {
|
||||
client.emit('error', new Error(`[USER_REQUIRED_ACTION] ${msg}`));
|
||||
} else {
|
||||
client.emit('debug', `[USER_REQUIRED_ACTION] ${msg}`);
|
||||
}
|
||||
static async getUploadURL(client, channelId, files) {
|
||||
if (!files.length) return [];
|
||||
files = files.map((file, i) => ({
|
||||
filename: file.name,
|
||||
// 25MB = 26_214_400bytes
|
||||
file_size: Math.floor((26_214_400 / 10) * Math.random()),
|
||||
id: `${i}`,
|
||||
}));
|
||||
const { attachments } = await client.api.channels[channelId].attachments.post({
|
||||
data: {
|
||||
files,
|
||||
},
|
||||
});
|
||||
return attachments;
|
||||
}
|
||||
|
||||
static uploadFile(data, url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, {
|
||||
method: 'PUT',
|
||||
body: data,
|
||||
duplex: 'half', // Node.js v20
|
||||
})
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
resolve(res);
|
||||
} else {
|
||||
reject(res);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user