update
This commit is contained in:
parent
b3cf4b7710
commit
59f254966c
@ -13,12 +13,15 @@ const Util = require('../util/Util');
|
|||||||
class BaseClient extends EventEmitter {
|
class BaseClient extends EventEmitter {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (options.intents) {
|
if (options.intents) {
|
||||||
process.emitWarning('Intents is not available.', 'DeprecationWarning');
|
process.emitWarning('Intents is not available.', 'DeprecationWarning');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options.captchaSolver === 'function') {
|
if (typeof options.captchaSolver === 'function') {
|
||||||
options.captchaService = 'custom';
|
options.captchaService = 'custom';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options the client was instantiated with
|
* The options the client was instantiated with
|
||||||
* @type {ClientOptions}
|
* @type {ClientOptions}
|
||||||
|
@ -126,36 +126,15 @@ class WebSocketManager extends EventEmitter {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async connect() {
|
async connect() {
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const invalidToken = new Error(WSCodes[4004]);
|
|
||||||
/*
|
|
||||||
BOT
|
|
||||||
const {
|
|
||||||
url: gatewayURL,
|
|
||||||
shards: recommendedShards,
|
|
||||||
session_start_limit: sessionStartLimit,
|
|
||||||
} = await this.client.api.gateway.bot.get().catch(error => {
|
|
||||||
throw error.httpStatus === 401 ? invalidToken : error;
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
let gatewayURL = 'wss://gateway.discord.gg';
|
let gatewayURL = 'wss://gateway.discord.gg';
|
||||||
const { url } = await this.client.api.gateway.get({ auth: false }).catch(() => ({ url: gatewayURL }));
|
await this.client.api.gateway
|
||||||
// eslint-disable-next-line no-unused-vars
|
.get({ auth: false })
|
||||||
/*
|
.then(r => (gatewayURL = r.url))
|
||||||
.catch(error => {
|
.catch(() => {});
|
||||||
// Never throw error :v
|
|
||||||
// throw error.httpStatus === 401 ? invalidToken : error;
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
if (url) gatewayURL = url;
|
|
||||||
const recommendedShards = 1;
|
|
||||||
const sessionStartLimit = {
|
|
||||||
total: Infinity,
|
|
||||||
remaining: Infinity,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { total, remaining } = sessionStartLimit;
|
const total = Infinity;
|
||||||
|
const remaining = Infinity;
|
||||||
|
const recommendedShards = 1;
|
||||||
|
|
||||||
this.debug(`Fetched Gateway Information
|
this.debug(`Fetched Gateway Information
|
||||||
URL: ${gatewayURL}
|
URL: ${gatewayURL}
|
||||||
@ -294,7 +273,7 @@ class WebSocketManager extends EventEmitter {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`);
|
this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`);
|
||||||
if (error.httpStatus !== 401) {
|
if (error.httpStatus !== 401) {
|
||||||
this.debug('Possible network error occurred. Retrying in 5s...');
|
this.debug(`Possible network error occurred. Retrying in 5s...`);
|
||||||
await sleep(5_000);
|
await sleep(5_000);
|
||||||
this.reconnecting = false;
|
this.reconnecting = false;
|
||||||
return this.reconnect();
|
return this.reconnect();
|
||||||
@ -368,11 +347,12 @@ class WebSocketManager extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Emitted whenever a packet isn't handled.
|
* Emitted whenever a packet isn't handled.
|
||||||
* @event Client#unhandledPacket
|
* @event Client#unhandledPacket
|
||||||
* @param {Object} packet The packet (t: Event name, d: Data)
|
* @param {Object} packet The packet (t: EVENT_NAME, d: any)
|
||||||
* @param {Number} shard The shard that received the packet (Auto = 0)
|
* @param {Number} shard The shard that received the packet (Shard 0)
|
||||||
*/
|
*/
|
||||||
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { isJSONEncodable } = require('@discordjs/builders');
|
||||||
const { Collection } = require('@discordjs/collection');
|
const { Collection } = require('@discordjs/collection');
|
||||||
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
|
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
|
||||||
const CachedManager = require('./CachedManager');
|
const CachedManager = require('./CachedManager');
|
||||||
@ -13,15 +14,14 @@ const Permissions = require('../util/Permissions');
|
|||||||
* @extends {CachedManager}
|
* @extends {CachedManager}
|
||||||
*/
|
*/
|
||||||
class ApplicationCommandManager extends CachedManager {
|
class ApplicationCommandManager extends CachedManager {
|
||||||
constructor(client, iterable, user) {
|
constructor(client, iterable) {
|
||||||
super(client, ApplicationCommand, iterable);
|
super(client, ApplicationCommand, iterable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The manager for permissions of arbitrary commands on arbitrary guilds
|
* The manager for permissions of arbitrary commands on arbitrary guilds
|
||||||
* @type {ApplicationCommandPermissionsManager}
|
* @type {ApplicationCommandPermissionsManager}
|
||||||
*/
|
*/
|
||||||
this.permissions = new ApplicationCommandPermissionsManager(this, user);
|
this.permissions = new ApplicationCommandPermissionsManager(this);
|
||||||
this.user = user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +43,7 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
commandPath({ id, guildId } = {}) {
|
commandPath({ id, guildId } = {}) {
|
||||||
let path = this.client.api.applications(this.user.id);
|
let path = this.client.api.applications(this.client.application.id);
|
||||||
if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
|
if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
|
||||||
return id ? path.commands(id) : path.commands;
|
return id ? path.commands(id) : path.commands;
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
/**
|
/**
|
||||||
* Data that resolves to the data of an ApplicationCommand
|
* Data that resolves to the data of an ApplicationCommand
|
||||||
* @typedef {ApplicationCommandDataResolvable|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
* @typedef {ApplicationCommandData|APIApplicationCommand|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
||||||
*/
|
*/
|
||||||
/* eslint-enable max-len */
|
/* eslint-enable max-len */
|
||||||
|
|
||||||
@ -94,7 +94,6 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
|
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
|
||||||
// Change from user.createDM to opcode (risky action)
|
|
||||||
if (typeof id === 'object') {
|
if (typeof id === 'object') {
|
||||||
({ guildId, cache = true, locale, withLocalizations } = id);
|
({ guildId, cache = true, locale, withLocalizations } = id);
|
||||||
} else if (id) {
|
} else if (id) {
|
||||||
@ -102,11 +101,10 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
const existing = this.cache.get(id);
|
const existing = this.cache.get(id);
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
}
|
}
|
||||||
await this.user.createDM().catch(() => {});
|
|
||||||
const command = await this.commandPath({ id, guildId }).get();
|
const command = await this.commandPath({ id, guildId }).get();
|
||||||
return this._add(command, cache);
|
return this._add(command, cache);
|
||||||
}
|
}
|
||||||
await this.user.createDM().catch(() => {});
|
|
||||||
const data = await this.commandPath({ guildId }).get({
|
const data = await this.commandPath({ guildId }).get({
|
||||||
headers: {
|
headers: {
|
||||||
'X-Discord-Locale': locale,
|
'X-Discord-Locale': locale,
|
||||||
@ -132,7 +130,6 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async create(command, guildId) {
|
async create(command, guildId) {
|
||||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const data = await this.commandPath({ guildId }).post({
|
const data = await this.commandPath({ guildId }).post({
|
||||||
data: this.constructor.transformCommand(command),
|
data: this.constructor.transformCommand(command),
|
||||||
});
|
});
|
||||||
@ -162,7 +159,6 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async set(commands, guildId) {
|
async set(commands, guildId) {
|
||||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const data = await this.commandPath({ guildId }).put({
|
const data = await this.commandPath({ guildId }).put({
|
||||||
data: commands.map(c => this.constructor.transformCommand(c)),
|
data: commands.map(c => this.constructor.transformCommand(c)),
|
||||||
});
|
});
|
||||||
@ -185,7 +181,6 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async edit(command, data, guildId) {
|
async edit(command, data, guildId) {
|
||||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const id = this.resolveId(command);
|
const id = this.resolveId(command);
|
||||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||||
|
|
||||||
@ -208,7 +203,6 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async delete(command, guildId) {
|
async delete(command, guildId) {
|
||||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const id = this.resolveId(command);
|
const id = this.resolveId(command);
|
||||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||||
|
|
||||||
@ -226,6 +220,8 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
static transformCommand(command) {
|
static transformCommand(command) {
|
||||||
|
if (isJSONEncodable(command)) return command.toJSON();
|
||||||
|
|
||||||
let default_member_permissions;
|
let default_member_permissions;
|
||||||
|
|
||||||
if ('default_member_permissions' in command) {
|
if ('default_member_permissions' in command) {
|
||||||
@ -240,6 +236,7 @@ class ApplicationCommandManager extends CachedManager {
|
|||||||
? new Permissions(command.defaultMemberPermissions).bitfield.toString()
|
? new Permissions(command.defaultMemberPermissions).bitfield.toString()
|
||||||
: command.defaultMemberPermissions;
|
: command.defaultMemberPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: command.name,
|
name: command.name,
|
||||||
name_localizations: command.nameLocalizations ?? command.name_localizations,
|
name_localizations: command.nameLocalizations ?? command.name_localizations,
|
||||||
|
@ -10,7 +10,7 @@ const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Consta
|
|||||||
* @extends {BaseManager}
|
* @extends {BaseManager}
|
||||||
*/
|
*/
|
||||||
class ApplicationCommandPermissionsManager extends BaseManager {
|
class ApplicationCommandPermissionsManager extends BaseManager {
|
||||||
constructor(manager, user) {
|
constructor(manager) {
|
||||||
super(manager.client);
|
super(manager.client);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,8 +37,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
|||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
*/
|
*/
|
||||||
this.commandId = manager.id ?? null;
|
this.commandId = manager.id ?? null;
|
||||||
|
|
||||||
this.user = user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,10 +47,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
permissionsPath(guildId, commandId) {
|
permissionsPath(guildId, commandId) {
|
||||||
return this.client.api
|
return this.client.api.applications(this.client.application.id).guilds(guildId).commands(commandId).permissions;
|
||||||
.applications(typeof this.user === 'string' ? this.user : this.user.id)
|
|
||||||
.guilds(guildId)
|
|
||||||
.commands(commandId).permissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,7 +159,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async set({ guild, command, permissions, fullPermissions } = {}) {
|
async set({ guild, command, permissions, fullPermissions } = {}) {
|
||||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||||
|
|
||||||
if (commandId) {
|
if (commandId) {
|
||||||
@ -226,7 +220,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async add({ guild, command, permissions }) {
|
async add({ guild, command, permissions }) {
|
||||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||||
if (!Array.isArray(permissions)) {
|
if (!Array.isArray(permissions)) {
|
||||||
@ -278,13 +271,12 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async remove({ guild, command, users, roles }) {
|
async remove({ guild, command, users, roles }) {
|
||||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||||
|
|
||||||
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
|
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
|
||||||
|
|
||||||
const resolvedIds = [];
|
let resolvedIds = [];
|
||||||
if (Array.isArray(users)) {
|
if (Array.isArray(users)) {
|
||||||
users.forEach(user => {
|
users.forEach(user => {
|
||||||
const userId = this.client.users.resolveId(user);
|
const userId = this.client.users.resolveId(user);
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
const { Collection } = require('@discordjs/collection');
|
const { Collection } = require('@discordjs/collection');
|
||||||
const CachedManager = require('./CachedManager');
|
const CachedManager = require('./CachedManager');
|
||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
const { lazy } = require('../util/Util');
|
const User = require('../structures/User');
|
||||||
const User = lazy(() => require('../structures/User'));
|
|
||||||
/**
|
/**
|
||||||
* Manages API methods for users who reacted to a reaction and stores their cache.
|
* Manages API methods for users who reacted to a reaction and stores their cache.
|
||||||
* @extends {CachedManager}
|
* @extends {CachedManager}
|
||||||
*/
|
*/
|
||||||
class ReactionUserManager extends CachedManager {
|
class ReactionUserManager extends CachedManager {
|
||||||
constructor(reaction, iterable) {
|
constructor(reaction, iterable) {
|
||||||
super(reaction.client, User(), iterable);
|
super(reaction.client, User, iterable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reaction that this manager belongs to
|
* The reaction that this manager belongs to
|
||||||
@ -22,7 +22,7 @@ class ReactionUserManager extends CachedManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The cache of this manager
|
* The cache of this manager
|
||||||
* @type {Collection<Snowflake, Discord.User>}
|
* @type {Collection<Snowflake, User>}
|
||||||
* @name ReactionUserManager#cache
|
* @name ReactionUserManager#cache
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ class ReactionUserManager extends CachedManager {
|
|||||||
/**
|
/**
|
||||||
* Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their ids.
|
* Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their ids.
|
||||||
* @param {FetchReactionUsersOptions} [options] Options for fetching the users
|
* @param {FetchReactionUsersOptions} [options] Options for fetching the users
|
||||||
* @returns {Promise<Collection<Snowflake, Discord.User>>}
|
* @returns {Promise<Collection<Snowflake, User>>}
|
||||||
*/
|
*/
|
||||||
async fetch({ limit = 100, after } = {}) {
|
async fetch({ limit = 100, after } = {}) {
|
||||||
const message = this.reaction.message;
|
const message = this.reaction.message;
|
||||||
|
@ -1,28 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { setTimeout } = require('node:timers');
|
|
||||||
const { findBestMatch } = require('string-similarity');
|
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
const MessagePayload = require('./MessagePayload');
|
|
||||||
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
|
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
|
||||||
const {
|
const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes } = require('../util/Constants');
|
||||||
ApplicationCommandOptionTypes,
|
|
||||||
ApplicationCommandTypes,
|
|
||||||
ChannelTypes,
|
|
||||||
Events,
|
|
||||||
InteractionTypes,
|
|
||||||
} = require('../util/Constants');
|
|
||||||
const Permissions = require('../util/Permissions');
|
const Permissions = require('../util/Permissions');
|
||||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||||
const { lazy, getAttachments, uploadFile } = require('../util/Util');
|
|
||||||
const Message = lazy(() => require('../structures/Message').Message);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an application command.
|
* Represents an application command.
|
||||||
* @extends {Base}
|
* @extends {Base}
|
||||||
*/
|
*/
|
||||||
class ApplicationCommand extends Base {
|
class ApplicationCommand extends Base {
|
||||||
constructor(client, data) {
|
constructor(client, data, guild, guildId) {
|
||||||
super(client);
|
super(client);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,11 +26,24 @@ class ApplicationCommand extends Base {
|
|||||||
*/
|
*/
|
||||||
this.applicationId = data.application_id;
|
this.applicationId = data.application_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guild this command is part of
|
||||||
|
* @type {?Guild}
|
||||||
|
*/
|
||||||
|
this.guild = guild ?? null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guild's id this command is part of, this may be non-null when `guild` is `null` if the command
|
||||||
|
* was fetched from the `ApplicationCommandManager`
|
||||||
|
* @type {?Snowflake}
|
||||||
|
*/
|
||||||
|
this.guildId = guild?.id ?? guildId ?? null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The manager for permissions of this command on its guild or arbitrary guilds when the command is global
|
* The manager for permissions of this command on its guild or arbitrary guilds when the command is global
|
||||||
* @type {ApplicationCommandPermissionsManager}
|
* @type {ApplicationCommandPermissionsManager}
|
||||||
*/
|
*/
|
||||||
this.permissions = new ApplicationCommandPermissionsManager(this, this.applicationId);
|
this.permissions = new ApplicationCommandPermissionsManager(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of this application command
|
* The type of this application command
|
||||||
@ -49,30 +51,10 @@ class ApplicationCommand extends Base {
|
|||||||
*/
|
*/
|
||||||
this.type = ApplicationCommandTypes[data.type];
|
this.type = ApplicationCommandTypes[data.type];
|
||||||
|
|
||||||
this.user = client.users.cache.get(this.applicationId);
|
|
||||||
|
|
||||||
this._patch(data);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The guild this command is part of
|
|
||||||
* @type {?Guild}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
get guild() {
|
|
||||||
return this.client.guilds.resolve(this.guildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
if ('guild_id' in data) {
|
|
||||||
/**
|
|
||||||
* The guild's id this command is part of, this may be non-null when `guild` is `null` if the command
|
|
||||||
* was fetched from the `ApplicationCommandManager`
|
|
||||||
* @type {?Snowflake}
|
|
||||||
*/
|
|
||||||
this.guildId = data.guild_id ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('name' in data) {
|
if ('name' in data) {
|
||||||
/**
|
/**
|
||||||
* The name of this command
|
* The name of this command
|
||||||
@ -148,7 +130,6 @@ class ApplicationCommand extends Base {
|
|||||||
*/
|
*/
|
||||||
this.defaultPermission = data.default_permission;
|
this.defaultPermission = data.default_permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
if ('default_member_permissions' in data) {
|
if ('default_member_permissions' in data) {
|
||||||
@ -336,7 +317,6 @@ class ApplicationCommand extends Base {
|
|||||||
setDefaultPermission(defaultPermission = true) {
|
setDefaultPermission(defaultPermission = true) {
|
||||||
return this.edit({ defaultPermission });
|
return this.edit({ defaultPermission });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-enable max-len */
|
/* eslint-enable max-len */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -391,6 +371,7 @@ class ApplicationCommand extends Base {
|
|||||||
equals(command, enforceOptionOrder = false) {
|
equals(command, enforceOptionOrder = false) {
|
||||||
// If given an id, check if the id matches
|
// If given an id, check if the id matches
|
||||||
if (command.id && this.id !== command.id) return false;
|
if (command.id && this.id !== command.id) return false;
|
||||||
|
|
||||||
let defaultMemberPermissions = null;
|
let defaultMemberPermissions = null;
|
||||||
let dmPermission = command.dmPermission ?? command.dm_permission;
|
let dmPermission = command.dmPermission ?? command.dm_permission;
|
||||||
|
|
||||||
@ -404,6 +385,7 @@ class ApplicationCommand extends Base {
|
|||||||
defaultMemberPermissions =
|
defaultMemberPermissions =
|
||||||
command.defaultMemberPermissions !== null ? new Permissions(command.defaultMemberPermissions).bitfield : null;
|
command.defaultMemberPermissions !== null ? new Permissions(command.defaultMemberPermissions).bitfield : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check top level parameters
|
// Check top level parameters
|
||||||
const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type];
|
const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type];
|
||||||
if (
|
if (
|
||||||
@ -446,9 +428,7 @@ class ApplicationCommand extends Base {
|
|||||||
const newOptions = new Map(options.map(option => [option.name, option]));
|
const newOptions = new Map(options.map(option => [option.name, option]));
|
||||||
for (const option of existing) {
|
for (const option of existing) {
|
||||||
const foundOption = newOptions.get(option.name);
|
const foundOption = newOptions.get(option.name);
|
||||||
if (!foundOption || !this._optionEquals(option, foundOption)) {
|
if (!foundOption || !this._optionEquals(option, foundOption)) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -597,423 +577,6 @@ class ApplicationCommand extends Base {
|
|||||||
[maxLengthKey]: option.maxLength ?? option.max_length,
|
[maxLengthKey]: option.maxLength ?? option.max_length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Send Slash command to channel
|
|
||||||
* @param {Message} message Discord Message
|
|
||||||
* @param {Array<string>} subCommandArray SubCommand Array
|
|
||||||
* @param {Array<any>} options The options to Slash Command
|
|
||||||
* @returns {Promise<InteractionResponse>}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line consistent-return
|
|
||||||
async sendSlashCommand(message, subCommandArray = [], options = []) {
|
|
||||||
// Todo: Refactor [Done]
|
|
||||||
const buildError = (type, value, array, msg) =>
|
|
||||||
new Error(`Invalid ${type}: ${value} ${msg}\nList of ${type}:\n${array}`);
|
|
||||||
// Check Options
|
|
||||||
if (!(message instanceof Message())) {
|
|
||||||
throw new TypeError('The message must be a Discord.Message');
|
|
||||||
}
|
|
||||||
if (!Array.isArray(options)) {
|
|
||||||
throw new TypeError('The options must be an array of strings');
|
|
||||||
}
|
|
||||||
if (this.type !== 'CHAT_INPUT') throw new Error('This command is not a chat input [/]');
|
|
||||||
const optionFormat = [];
|
|
||||||
const attachments = [];
|
|
||||||
const attachmentsBuffer = [];
|
|
||||||
const parseChoices = (list_choices, value) => {
|
|
||||||
if (value !== undefined) {
|
|
||||||
if (Array.isArray(list_choices) && list_choices.length) {
|
|
||||||
const choice = list_choices.find(c => c.name === value) || list_choices.find(c => c.value === value);
|
|
||||||
if (choice) {
|
|
||||||
return choice.value;
|
|
||||||
}
|
|
||||||
throw buildError(
|
|
||||||
'choice',
|
|
||||||
value,
|
|
||||||
list_choices.map((c, i) => ` #${i + 1} Name: ${c.name} Value: ${c.value}`).join('\n'),
|
|
||||||
'is not a valid choice for this option',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const parseOption = async (optionCommand, value) => {
|
|
||||||
const data = {
|
|
||||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
|
||||||
name: optionCommand.name,
|
|
||||||
};
|
|
||||||
if (value !== undefined) {
|
|
||||||
value = parseChoices(optionCommand.choices, value);
|
|
||||||
switch (optionCommand.type) {
|
|
||||||
case 'BOOLEAN': {
|
|
||||||
data.value = Boolean(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'INTEGER': {
|
|
||||||
data.value = Number(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ATTACHMENT': {
|
|
||||||
data.value = await addDataFromAttachment(value, this.client);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'SUB_COMMAND_GROUP': {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if (optionCommand.autocomplete) {
|
|
||||||
let optionsBuild;
|
|
||||||
switch (subCommandArray.length) {
|
|
||||||
case 0: {
|
|
||||||
optionsBuild = [
|
|
||||||
...optionFormat,
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
|
||||||
name: optionCommand.name,
|
|
||||||
value,
|
|
||||||
focused: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1: {
|
|
||||||
const subCommand = this.options.find(o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND');
|
|
||||||
optionsBuild = [
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
|
||||||
name: subCommand.name,
|
|
||||||
options: [
|
|
||||||
...optionFormat,
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
|
||||||
name: optionCommand.name,
|
|
||||||
value,
|
|
||||||
focused: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
const subGroup = this.options.find(
|
|
||||||
o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND_GROUP',
|
|
||||||
);
|
|
||||||
const subCommand = subGroup.options.find(
|
|
||||||
o => o.name == subCommandArray[1] && o.type == 'SUB_COMMAND',
|
|
||||||
);
|
|
||||||
optionsBuild = [
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionTypes[subGroup.type],
|
|
||||||
name: subGroup.name,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
|
||||||
name: subCommand.name,
|
|
||||||
options: [
|
|
||||||
...optionFormat,
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionTypes[optionCommand.type],
|
|
||||||
name: optionCommand.name,
|
|
||||||
value,
|
|
||||||
focused: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const autoValue = await getAutoResponse(optionsBuild, value);
|
|
||||||
data.value = autoValue;
|
|
||||||
} else {
|
|
||||||
data.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
optionFormat.push(data);
|
|
||||||
}
|
|
||||||
return optionFormat;
|
|
||||||
};
|
|
||||||
const parseSubCommand = async (subCommandName, options, subGroup) => {
|
|
||||||
const options_sub = subGroup ? subGroup.options : this.options;
|
|
||||||
const subCommand = options_sub.find(
|
|
||||||
o => (o.name == subCommandName || o.nameLocalized == subCommandName) && o.type == 'SUB_COMMAND',
|
|
||||||
);
|
|
||||||
if (!subCommand) {
|
|
||||||
throw buildError(
|
|
||||||
'SubCommand',
|
|
||||||
subCommandName,
|
|
||||||
options_sub.map((o, i) => ` #${i + 1} Name: ${o.name}`).join('\n'),
|
|
||||||
'is not a valid sub command',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const valueRequired = subCommand.options?.filter(o => o.required).length || 0;
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
|
||||||
const optionInput = subCommand.options[i];
|
|
||||||
const value = options[i];
|
|
||||||
await parseOption(optionInput, value);
|
|
||||||
}
|
|
||||||
if (valueRequired > options.length) {
|
|
||||||
throw new Error(`Value required missing\nDebug:
|
|
||||||
Required: ${valueRequired} - Options: ${optionFormat.length}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
|
||||||
name: subCommand.name,
|
|
||||||
options: optionFormat,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const parseSubGroupCommand = async (subGroupName, subName) => {
|
|
||||||
const subGroup = this.options.find(
|
|
||||||
o => (o.name == subGroupName || o.nameLocalized == subGroupName) && o.type == 'SUB_COMMAND_GROUP',
|
|
||||||
);
|
|
||||||
if (!subGroup) {
|
|
||||||
throw buildError(
|
|
||||||
'SubGroupCommand',
|
|
||||||
subGroupName,
|
|
||||||
this.options.map((o, i) => ` #${i + 1} Name: ${o.name}`).join('\n'),
|
|
||||||
'is not a valid sub group command',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const data = await parseSubCommand(subName, options, subGroup);
|
|
||||||
return {
|
|
||||||
type: ApplicationCommandOptionTypes[subGroup.type],
|
|
||||||
name: subGroup.name,
|
|
||||||
options: [data],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
async function addDataFromAttachment(data, client) {
|
|
||||||
const data_ = await MessagePayload.resolveFile(data);
|
|
||||||
if (!data_.file) {
|
|
||||||
throw new TypeError(
|
|
||||||
'The attachment data must be a BufferResolvable or Stream or FileOptions of MessageAttachment',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (client.options.usingNewAttachmentAPI === true) {
|
|
||||||
const attachments_ = await getAttachments(client, message.channelId, data_);
|
|
||||||
await uploadFile(data_.file, attachments_[0].upload_url);
|
|
||||||
const id = attachments.length;
|
|
||||||
attachments.push({
|
|
||||||
id: id,
|
|
||||||
filename: data_.name,
|
|
||||||
uploaded_filename: attachments_[0].upload_filename,
|
|
||||||
});
|
|
||||||
return id;
|
|
||||||
} else {
|
|
||||||
const id = attachments.length;
|
|
||||||
attachments.push({
|
|
||||||
id: id,
|
|
||||||
filename: data_.name,
|
|
||||||
});
|
|
||||||
attachmentsBuffer.push(data_);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const getDataPost = (dataAdd = [], nonce, autocomplete = false) => {
|
|
||||||
if (!Array.isArray(dataAdd) && typeof dataAdd == 'object') {
|
|
||||||
dataAdd = [dataAdd];
|
|
||||||
}
|
|
||||||
const data = {
|
|
||||||
type: autocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND,
|
|
||||||
application_id: this.applicationId,
|
|
||||||
guild_id: message.guildId,
|
|
||||||
channel_id: message.channelId,
|
|
||||||
session_id: this.client.sessionId,
|
|
||||||
data: {
|
|
||||||
version: this.version,
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
type: ApplicationCommandTypes[this.type],
|
|
||||||
options: dataAdd,
|
|
||||||
attachments: attachments,
|
|
||||||
},
|
|
||||||
nonce,
|
|
||||||
};
|
|
||||||
if (this.guildId) {
|
|
||||||
data.data.guild_id = message.guildId;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
const getAutoResponse = async (sendData, value) => {
|
|
||||||
let nonce = SnowflakeUtil.generate();
|
|
||||||
const data = getDataPost(sendData, nonce, true);
|
|
||||||
await this.client.api.interactions.post({
|
|
||||||
data,
|
|
||||||
files: attachmentsBuffer,
|
|
||||||
});
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const handler = data => {
|
|
||||||
timeout.refresh();
|
|
||||||
if (data.nonce !== nonce) return;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
if (data.choices.length > 1) {
|
|
||||||
// Find best match name
|
|
||||||
const bestMatch = findBestMatch(
|
|
||||||
value,
|
|
||||||
data.choices.map(c => c.name),
|
|
||||||
);
|
|
||||||
const result = data.choices.find(c => c.name == bestMatch.bestMatch.target);
|
|
||||||
resolve(result.value);
|
|
||||||
} else {
|
|
||||||
resolve(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
resolve(value);
|
|
||||||
}, this.client.options.interactionTimeout).unref();
|
|
||||||
this.client.incrementMaxListeners();
|
|
||||||
this.client.on(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const sendData = async (optionsData = []) => {
|
|
||||||
let nonce = SnowflakeUtil.generate();
|
|
||||||
const data = getDataPost(optionsData, nonce);
|
|
||||||
await this.client.api.interactions.post({
|
|
||||||
data,
|
|
||||||
useFormDataPayloadJSON: true,
|
|
||||||
files: attachmentsBuffer,
|
|
||||||
});
|
|
||||||
this.client._interactionCache.set(nonce, {
|
|
||||||
channelId: message.channelId,
|
|
||||||
guildId: message.guildId,
|
|
||||||
metadata: data,
|
|
||||||
});
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const handler = data => {
|
|
||||||
timeout.refresh();
|
|
||||||
if (data.metadata?.nonce !== nonce) return;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
this.client.removeListener('interactionResponse', handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
if (data.status) {
|
|
||||||
resolve(data.metadata);
|
|
||||||
} else {
|
|
||||||
reject(
|
|
||||||
new Error('INTERACTION_ERROR', {
|
|
||||||
cause: data,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.client.removeListener('interactionResponse', handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
reject(
|
|
||||||
new Error('INTERACTION_TIMEOUT', {
|
|
||||||
cause: data,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}, this.client.options.interactionTimeout).unref();
|
|
||||||
this.client.incrementMaxListeners();
|
|
||||||
this.client.on('interactionResponse', handler);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// SubCommandArray length max 2
|
|
||||||
// length = 0 => no sub command
|
|
||||||
// length = 1 => sub command
|
|
||||||
// length = 2 => sub command group + sub command
|
|
||||||
switch (subCommandArray.length) {
|
|
||||||
case 0: {
|
|
||||||
const valueRequired = this.options?.filter(o => o.required).length || 0;
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
|
||||||
const optionInput = this.options[i];
|
|
||||||
const value = options[i];
|
|
||||||
await parseOption(optionInput, value);
|
|
||||||
}
|
|
||||||
if (valueRequired > options.length) {
|
|
||||||
throw new Error(`Value required missing\nDebug:
|
|
||||||
Required: ${valueRequired} - Options: ${optionFormat.length}`);
|
|
||||||
}
|
|
||||||
return sendData(optionFormat);
|
|
||||||
}
|
|
||||||
case 1: {
|
|
||||||
const optionsData = await parseSubCommand(subCommandArray[0], options);
|
|
||||||
return sendData(optionsData);
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
const optionsData = await parseSubGroupCommand(subCommandArray[0], subCommandArray[1], options);
|
|
||||||
return sendData(optionsData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Message Context Menu
|
|
||||||
* @param {Message} message Discord Message
|
|
||||||
* @returns {Promise<InteractionResponse>}
|
|
||||||
*/
|
|
||||||
async sendContextMenu(message) {
|
|
||||||
if (!(message instanceof Message())) {
|
|
||||||
throw new TypeError('The message must be a Discord.Message');
|
|
||||||
}
|
|
||||||
if (this.type == 'CHAT_INPUT') return false;
|
|
||||||
const nonce = SnowflakeUtil.generate();
|
|
||||||
const data = {
|
|
||||||
type: InteractionTypes.APPLICATION_COMMAND,
|
|
||||||
application_id: this.applicationId,
|
|
||||||
guild_id: message.guildId,
|
|
||||||
channel_id: message.channelId,
|
|
||||||
session_id: this.client.sessionId,
|
|
||||||
data: {
|
|
||||||
version: this.version,
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
type: ApplicationCommandTypes[this.type],
|
|
||||||
target_id: ApplicationCommandTypes[this.type] == 1 ? message.author.id : message.id,
|
|
||||||
},
|
|
||||||
nonce,
|
|
||||||
};
|
|
||||||
if (this.guildId) {
|
|
||||||
data.data.guild_id = message.guildId;
|
|
||||||
}
|
|
||||||
await this.client.api.interactions.post({
|
|
||||||
data,
|
|
||||||
useFormDataPayloadJSON: true,
|
|
||||||
});
|
|
||||||
this.client._interactionCache.set(nonce, {
|
|
||||||
channelId: message.channelId,
|
|
||||||
guildId: message.guildId,
|
|
||||||
metadata: data,
|
|
||||||
});
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const handler = data => {
|
|
||||||
timeout.refresh();
|
|
||||||
if (data.metadata?.nonce !== nonce) return;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
this.client.removeListener('interactionResponse', handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
if (data.status) {
|
|
||||||
resolve(data.metadata);
|
|
||||||
} else {
|
|
||||||
reject(
|
|
||||||
new Error('INTERACTION_ERROR', {
|
|
||||||
cause: data,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.client.removeListener('interactionResponse', handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
reject(
|
|
||||||
new Error('INTERACTION_TIMEOUT', {
|
|
||||||
cause: data,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}, this.client.options.interactionTimeout).unref();
|
|
||||||
this.client.incrementMaxListeners();
|
|
||||||
this.client.on('interactionResponse', handler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ApplicationCommand;
|
module.exports = ApplicationCommand;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
const GuildChannel = require('./GuildChannel');
|
const GuildChannel = require('./GuildChannel');
|
||||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||||
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
|
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
|
||||||
const InteractionManager = require('../managers/InteractionManager');
|
|
||||||
const MessageManager = require('../managers/MessageManager');
|
const MessageManager = require('../managers/MessageManager');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,12 +20,6 @@ class BaseGuildTextChannel extends GuildChannel {
|
|||||||
*/
|
*/
|
||||||
this.messages = new MessageManager(this);
|
this.messages = new MessageManager(this);
|
||||||
|
|
||||||
/**
|
|
||||||
* A manager of the interactions sent to this channel
|
|
||||||
* @type {InteractionManager}
|
|
||||||
*/
|
|
||||||
this.interactions = new InteractionManager(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A manager of the threads belonging to this channel
|
* A manager of the threads belonging to this channel
|
||||||
* @type {GuildTextThreadManager}
|
* @type {GuildTextThreadManager}
|
||||||
@ -187,15 +180,10 @@ class BaseGuildTextChannel extends GuildChannel {
|
|||||||
sendTyping() {}
|
sendTyping() {}
|
||||||
createMessageCollector() {}
|
createMessageCollector() {}
|
||||||
awaitMessages() {}
|
awaitMessages() {}
|
||||||
createMessageComponentCollector() {}
|
|
||||||
awaitMessageComponent() {}
|
|
||||||
bulkDelete() {}
|
|
||||||
fetchWebhooks() {}
|
fetchWebhooks() {}
|
||||||
createWebhook() {}
|
createWebhook() {}
|
||||||
setRateLimitPerUser() {}
|
setRateLimitPerUser() {}
|
||||||
setNSFW() {}
|
setNSFW() {}
|
||||||
sendSlash() {}
|
|
||||||
searchInteraction() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
|
TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
const { Collection } = require('@discordjs/collection');
|
const { Collection } = require('@discordjs/collection');
|
||||||
const GuildChannel = require('./GuildChannel');
|
const GuildChannel = require('./GuildChannel');
|
||||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||||
const InteractionManager = require('../managers/InteractionManager');
|
|
||||||
const MessageManager = require('../managers/MessageManager');
|
const MessageManager = require('../managers/MessageManager');
|
||||||
const { VideoQualityModes } = require('../util/Constants');
|
const { VideoQualityModes } = require('../util/Constants');
|
||||||
const Permissions = require('../util/Permissions');
|
const Permissions = require('../util/Permissions');
|
||||||
@ -28,12 +27,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
*/
|
*/
|
||||||
this.nsfw = Boolean(data.nsfw);
|
this.nsfw = Boolean(data.nsfw);
|
||||||
|
|
||||||
/**
|
|
||||||
* A manager of the interactions sent to this channel
|
|
||||||
* @type {InteractionManager}
|
|
||||||
*/
|
|
||||||
this.interactions = new InteractionManager(this);
|
|
||||||
|
|
||||||
this._patch(data);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +88,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('nsfw' in data) {
|
if ('nsfw' in data) {
|
||||||
this.nsfw = Boolean(data.nsfw);
|
this.nsfw = data.nsfw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,11 +163,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
* Sets the bitrate of the channel.
|
* Sets the bitrate of the channel.
|
||||||
* @param {number} bitrate The new bitrate
|
* @param {number} bitrate The new bitrate
|
||||||
* @param {string} [reason] Reason for changing the channel's bitrate
|
* @param {string} [reason] Reason for changing the channel's bitrate
|
||||||
* @returns {Promise<VoiceChannel>}
|
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||||
* @example
|
* @example
|
||||||
* // Set the bitrate of a voice channel
|
* // Set the bitrate of a voice channel
|
||||||
* voiceChannel.setBitrate(48_000)
|
* channel.setBitrate(48_000)
|
||||||
* .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.name}`))
|
* .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
setBitrate(bitrate, reason) {
|
setBitrate(bitrate, reason) {
|
||||||
@ -201,11 +194,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
* Sets the user limit of the channel.
|
* Sets the user limit of the channel.
|
||||||
* @param {number} userLimit The new user limit
|
* @param {number} userLimit The new user limit
|
||||||
* @param {string} [reason] Reason for changing the user limit
|
* @param {string} [reason] Reason for changing the user limit
|
||||||
* @returns {Promise<VoiceChannel>}
|
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||||
* @example
|
* @example
|
||||||
* // Set the user limit of a voice channel
|
* // Set the user limit of a voice channel
|
||||||
* voiceChannel.setUserLimit(42)
|
* channel.setUserLimit(42)
|
||||||
* .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
|
* .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
setUserLimit(userLimit, reason) {
|
setUserLimit(userLimit, reason) {
|
||||||
@ -216,7 +209,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
* Sets the camera video quality mode of the channel.
|
* Sets the camera video quality mode of the channel.
|
||||||
* @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode.
|
* @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode.
|
||||||
* @param {string} [reason] Reason for changing the camera video quality mode.
|
* @param {string} [reason] Reason for changing the camera video quality mode.
|
||||||
* @returns {Promise<VoiceChannel>}
|
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||||
*/
|
*/
|
||||||
setVideoQualityMode(videoQualityMode, reason) {
|
setVideoQualityMode(videoQualityMode, reason) {
|
||||||
return this.edit({ videoQualityMode }, reason);
|
return this.edit({ videoQualityMode }, reason);
|
||||||
@ -229,9 +222,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
sendTyping() {}
|
sendTyping() {}
|
||||||
createMessageCollector() {}
|
createMessageCollector() {}
|
||||||
awaitMessages() {}
|
awaitMessages() {}
|
||||||
createMessageComponentCollector() {}
|
|
||||||
awaitMessageComponent() {}
|
|
||||||
bulkDelete() {}
|
|
||||||
fetchWebhooks() {}
|
fetchWebhooks() {}
|
||||||
createWebhook() {}
|
createWebhook() {}
|
||||||
setRateLimitPerUser() {}
|
setRateLimitPerUser() {}
|
||||||
|
@ -7,7 +7,7 @@ const Base = require('./Base');
|
|||||||
* Represents a call
|
* Represents a call
|
||||||
* @extends {Base}
|
* @extends {Base}
|
||||||
*/
|
*/
|
||||||
class Call extends Base {
|
class CallState extends Base {
|
||||||
constructor(client, data) {
|
constructor(client, data) {
|
||||||
super(client);
|
super(client);
|
||||||
/**
|
/**
|
||||||
@ -16,14 +16,11 @@ class Call extends Base {
|
|||||||
*/
|
*/
|
||||||
this.channelId = data.channel_id;
|
this.channelId = data.channel_id;
|
||||||
|
|
||||||
/**
|
this._ringing = [];
|
||||||
* The list of user ID who is ringing
|
|
||||||
* @type {Collection<Snowflake, User>}
|
|
||||||
*/
|
|
||||||
this.ringing = new Collection();
|
|
||||||
|
|
||||||
this._patch(data);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
if ('region' in data) {
|
if ('region' in data) {
|
||||||
/**
|
/**
|
||||||
@ -33,11 +30,10 @@ class Call extends Base {
|
|||||||
this.region = data.region;
|
this.region = data.region;
|
||||||
}
|
}
|
||||||
if ('ringing' in data) {
|
if ('ringing' in data) {
|
||||||
for (const userId of data.ringing) {
|
this._ringing = data.ringing;
|
||||||
this.ringing.set(userId, this.client.users.cache.get(userId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The channel of the call
|
* The channel of the call
|
||||||
* @type {?DMChannel|PartialGroupDMChannel}
|
* @type {?DMChannel|PartialGroupDMChannel}
|
||||||
@ -45,14 +41,23 @@ class Call extends Base {
|
|||||||
get channel() {
|
get channel() {
|
||||||
return this.client.channels.cache.get(this.channelId);
|
return this.client.channels.cache.get(this.channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the voice region of the call
|
* Sets the voice region of the call
|
||||||
* @param {string} region Region of the call
|
* @param {string} region Region of the call
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
setVoiceRegion(region) {
|
setRTCRegion(region) {
|
||||||
return this.client.api.channels(this.channelId).call.patch({ data: { region } });
|
return this.client.api.channels(this.channelId).call.patch({ data: { region } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of user ID who is ringing
|
||||||
|
* @type {Collection<Snowflake, User>}
|
||||||
|
*/
|
||||||
|
get ringing() {
|
||||||
|
return new Collection(this._ringing.map(id => [id, this.client.users.cache.get(id)]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Call;
|
module.exports = CallState;
|
@ -224,7 +224,6 @@ class GuildAuditLogs {
|
|||||||
this.applicationCommands.set(command.id, new ApplicationCommand(guild.client, command, guild));
|
this.applicationCommands.set(command.id, new ApplicationCommand(guild.client, command, guild));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached auto moderation rules.
|
* Cached auto moderation rules.
|
||||||
* @type {Collection<Snowflake, AutoModerationRule>}
|
* @type {Collection<Snowflake, AutoModerationRule>}
|
||||||
@ -487,7 +486,6 @@ class GuildAuditLogsEntry {
|
|||||||
count: Number(data.options.count),
|
count: Number(data.options.count),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Actions.MESSAGE_PIN:
|
case Actions.MESSAGE_PIN:
|
||||||
case Actions.MESSAGE_UNPIN:
|
case Actions.MESSAGE_UNPIN:
|
||||||
this.extra = {
|
this.extra = {
|
||||||
@ -533,13 +531,11 @@ class GuildAuditLogsEntry {
|
|||||||
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Actions.APPLICATION_COMMAND_PERMISSION_UPDATE:
|
case Actions.APPLICATION_COMMAND_PERMISSION_UPDATE:
|
||||||
this.extra = {
|
this.extra = {
|
||||||
applicationId: data.options.application_id,
|
applicationId: data.options.application_id,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Actions.AUTO_MODERATION_BLOCK_MESSAGE:
|
case Actions.AUTO_MODERATION_BLOCK_MESSAGE:
|
||||||
case Actions.AUTO_MODERATION_FLAG_TO_CHANNEL:
|
case Actions.AUTO_MODERATION_FLAG_TO_CHANNEL:
|
||||||
case Actions.AUTO_MODERATION_USER_COMMUNICATION_DISABLED:
|
case Actions.AUTO_MODERATION_USER_COMMUNICATION_DISABLED:
|
||||||
@ -549,7 +545,6 @@ class GuildAuditLogsEntry {
|
|||||||
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
const { Buffer } = require('node:buffer');
|
const { Buffer } = require('node:buffer');
|
||||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||||
const MessageEmbed = require('./MessageEmbed');
|
const MessageEmbed = require('./MessageEmbed');
|
||||||
const WebEmbed = require('./WebEmbed');
|
|
||||||
const { RangeError } = require('../errors');
|
const { RangeError } = require('../errors');
|
||||||
const ActivityFlags = require('../util/ActivityFlags');
|
const ActivityFlags = require('../util/ActivityFlags');
|
||||||
const DataResolver = require('../util/DataResolver');
|
const DataResolver = require('../util/DataResolver');
|
||||||
@ -42,7 +41,6 @@ class MessagePayload {
|
|||||||
* @property {Buffer|string|Stream} attachment The original attachment that generated this file
|
* @property {Buffer|string|Stream} attachment The original attachment that generated this file
|
||||||
* @property {string} name The name of this file
|
* @property {string} name The name of this file
|
||||||
* @property {Buffer|Stream} file The file to be sent to the API
|
* @property {Buffer|Stream} file The file to be sent to the API
|
||||||
* @extends {APIAttachment}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,15 +50,6 @@ class MessagePayload {
|
|||||||
this.files = null;
|
this.files = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not using new API to upload files
|
|
||||||
* @type {boolean}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
get usingNewAttachmentAPI() {
|
|
||||||
return Boolean(this.options?.usingNewAttachmentAPI);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
|
* Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@ -133,12 +122,12 @@ class MessagePayload {
|
|||||||
* Resolves data.
|
* Resolves data.
|
||||||
* @returns {MessagePayload}
|
* @returns {MessagePayload}
|
||||||
*/
|
*/
|
||||||
async resolveData() {
|
resolveData() {
|
||||||
if (this.data) return this;
|
if (this.data) return this;
|
||||||
const isInteraction = this.isInteraction;
|
const isInteraction = this.isInteraction;
|
||||||
const isWebhook = this.isWebhook;
|
const isWebhook = this.isWebhook;
|
||||||
|
|
||||||
let content = this.makeContent();
|
const content = this.makeContent();
|
||||||
const tts = Boolean(this.options.tts);
|
const tts = Boolean(this.options.tts);
|
||||||
|
|
||||||
let nonce;
|
let nonce;
|
||||||
@ -208,37 +197,6 @@ class MessagePayload {
|
|||||||
this.options.attachments = attachments;
|
this.options.attachments = attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.embeds) {
|
|
||||||
if (!Array.isArray(this.options.embeds)) {
|
|
||||||
this.options.embeds = [this.options.embeds];
|
|
||||||
}
|
|
||||||
|
|
||||||
const webembeds = this.options.embeds.filter(e => e instanceof WebEmbed);
|
|
||||||
this.options.embeds = this.options.embeds.filter(e => !(e instanceof WebEmbed));
|
|
||||||
|
|
||||||
if (webembeds.length > 0) {
|
|
||||||
if (!content) content = '';
|
|
||||||
// Add hidden embed link
|
|
||||||
content += `\n${WebEmbed.hiddenEmbed} \n`;
|
|
||||||
if (webembeds.length > 1) {
|
|
||||||
console.warn('[WARN] Multiple webembeds are not supported, this will be ignored.');
|
|
||||||
}
|
|
||||||
// Const embed = webembeds[0];
|
|
||||||
for (const webE of webembeds) {
|
|
||||||
const data = await webE.toMessage();
|
|
||||||
content += `\n${data}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check content
|
|
||||||
if (typeof content == 'string' && content.length > 2000) {
|
|
||||||
console.warn('[WARN] Content is longer than 2000 characters.');
|
|
||||||
}
|
|
||||||
if (typeof content == 'string' && content.length > 4000) {
|
|
||||||
// Max length if user has nitro boost
|
|
||||||
throw new RangeError('MESSAGE_EMBED_LINK_LENGTH');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activity
|
// Activity
|
||||||
let activity;
|
let activity;
|
||||||
if (
|
if (
|
||||||
@ -247,7 +205,7 @@ class MessagePayload {
|
|||||||
this.options.activity.type
|
this.options.activity.type
|
||||||
) {
|
) {
|
||||||
const type = ActivityFlags.resolve(this.options.activity.type);
|
const type = ActivityFlags.resolve(this.options.activity.type);
|
||||||
const sessionId = this.target.client.sessionId;
|
const sessionId = this.target.client.ws.shards.first()?.sessionId;
|
||||||
const partyId = this.options.activity.partyId;
|
const partyId = this.options.activity.partyId;
|
||||||
activity = {
|
activity = {
|
||||||
type,
|
type,
|
||||||
@ -348,7 +306,7 @@ module.exports = MessagePayload;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A target for a message.
|
* A target for a message.
|
||||||
* @typedef {TextBasedChannels|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
|
* @typedef {TextBasedChannels|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
|
||||||
* Message|MessageManager} MessageTarget
|
* Message|MessageManager} MessageTarget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
const { Collection } = require('@discordjs/collection');
|
const { Collection } = require('@discordjs/collection');
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
const TeamMember = require('./TeamMember');
|
const TeamMember = require('./TeamMember');
|
||||||
const User = require('./User');
|
|
||||||
const { Error } = require('../errors');
|
|
||||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,53 +98,6 @@ class Team extends Base {
|
|||||||
return this.client.rest.cdn.TeamIcon(this.id, this.icon, { format, size });
|
return this.client.rest.cdn.TeamIcon(this.id, this.icon, { format, size });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invite a team member to the team
|
|
||||||
* @param {User} user The user to invite to the team
|
|
||||||
* @param {number} MFACode The mfa code
|
|
||||||
* @returns {Promise<TeamMember>}
|
|
||||||
*/
|
|
||||||
async inviteMember(user, MFACode) {
|
|
||||||
if (!(user instanceof User)) return new Error('TEAM_MEMBER_FORMAT');
|
|
||||||
const regex = /([0-9]{6})/g;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
username: user.username,
|
|
||||||
discriminator: user.discriminator,
|
|
||||||
};
|
|
||||||
if (MFACode && !regex.test(MFACode)) return new Error('MFA_INVALID');
|
|
||||||
if (MFACode) payload.code = MFACode;
|
|
||||||
|
|
||||||
const member = await this.client.api.teams(this.id).members.post({
|
|
||||||
data: payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.members.set(member.user.id, new TeamMember(this, member));
|
|
||||||
return this.members.get(member.user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a member from the team
|
|
||||||
* @param {Snowflake} userID The ID of the user you want to remove
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
async removeMember(userID) {
|
|
||||||
await this.client.api.teams[this.id].members[userID].delete();
|
|
||||||
return this.members.delete(userID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete this team
|
|
||||||
* @param {number} MFACode The 2fa code
|
|
||||||
* @returns {Promise<boolean>}
|
|
||||||
*/
|
|
||||||
async delete(MFACode) {
|
|
||||||
const regex = /([0-9]{6})/g;
|
|
||||||
if (!regex.test(MFACode)) return new Error('MFA_INVALID');
|
|
||||||
await this.client.api.teams[this.id].delete({ data: { code: MFACode } });
|
|
||||||
return this.client.developerPortal.teams.delete(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically returns the Team's name instead of the
|
* When concatenated with a string, this automatically returns the Team's name instead of the
|
||||||
* Team object.
|
* Team object.
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const process = require('node:process');
|
const process = require('node:process');
|
||||||
|
const { ApplicationFlags } = require('../../util/ApplicationFlags');
|
||||||
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
||||||
const Permissions = require('../../util/Permissions');
|
const Permissions = require('../../util/Permissions');
|
||||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||||
|
const { ApplicationRoleConnectionMetadata } = require('../ApplicationRoleConnectionMetadata');
|
||||||
const Base = require('../Base');
|
const Base = require('../Base');
|
||||||
|
const Team = require('../Team');
|
||||||
|
|
||||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||||
|
|
||||||
@ -67,6 +70,132 @@ class Application extends Base {
|
|||||||
} else {
|
} else {
|
||||||
this.roleConnectionsVerificationURL ??= null;
|
this.roleConnectionsVerificationURL ??= null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientApplication
|
||||||
|
/**
|
||||||
|
* 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 ('approximate_guild_count' in data) {
|
||||||
|
/**
|
||||||
|
* An approximate amount of guilds this application is in.
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
this.approximateGuildCount = data.approximate_guild_count;
|
||||||
|
} else {
|
||||||
|
this.approximateGuildCount ??= null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('guild_id' in data) {
|
||||||
|
/**
|
||||||
|
* The id of the guild associated with this application.
|
||||||
|
* @type {?Snowflake}
|
||||||
|
*/
|
||||||
|
this.guildId = data.guild_id;
|
||||||
|
} else {
|
||||||
|
this.guildId ??= null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = data.team
|
||||||
|
? new Team(this.client, data.team)
|
||||||
|
: data.owner
|
||||||
|
? this.client.users._add(data.owner)
|
||||||
|
: this.owner ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guild associated with this application.
|
||||||
|
* @type {?Guild}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get guild() {
|
||||||
|
return this.client.guilds.cache.get(this.guildId) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this application is partial
|
||||||
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get partial() {
|
||||||
|
return !this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,35 +217,29 @@ class Application extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites this application to a guild / server
|
* Obtains this application from Discord.
|
||||||
* @param {Snowflake} guild_id The id of the guild that you want to invite the bot to
|
* @returns {Promise<Application>}
|
||||||
* @param {PermissionResolvable} [permissions] The permissions for the bot in number form (the default is 8 / Administrator)
|
|
||||||
* @param {string} [captcha] The captcha key to add
|
|
||||||
* @returns {Promise<void>} nothing :)
|
|
||||||
*/
|
*/
|
||||||
async invite(guild_id, permissions, captcha = null) {
|
async fetch() {
|
||||||
permissions = Permissions.resolve(permissions || 0n);
|
const app = await this.client.api.oauth2.authorize.get({
|
||||||
const postData = {
|
|
||||||
authorize: true,
|
|
||||||
guild_id,
|
|
||||||
permissions: '0',
|
|
||||||
};
|
|
||||||
if (permissions) {
|
|
||||||
postData.permissions = permissions;
|
|
||||||
}
|
|
||||||
if (captcha && typeof captcha === 'string' && captcha.length > 0) {
|
|
||||||
postData.captcha = captcha;
|
|
||||||
}
|
|
||||||
await this.client.api.oauth2.authorize.post({
|
|
||||||
query: {
|
query: {
|
||||||
client_id: this.id,
|
client_id: this.id,
|
||||||
scope: 'bot applications.commands',
|
scope: 'bot applications.commands',
|
||||||
},
|
},
|
||||||
data: postData,
|
|
||||||
headers: {
|
|
||||||
referer: `https://discord.com/oauth2/authorize?client_id=${this.id}&permissions=${permissions}&scope=bot`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
const user = this.client.users._add(app.bot);
|
||||||
|
user._partial = false;
|
||||||
|
this._patch(app.application);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* eslint-disable import/order */
|
/* eslint-disable import/order */
|
||||||
const InteractionManager = require('../../managers/InteractionManager');
|
|
||||||
const MessageCollector = require('../MessageCollector');
|
const MessageCollector = require('../MessageCollector');
|
||||||
const MessagePayload = require('../MessagePayload');
|
const MessagePayload = require('../MessagePayload');
|
||||||
|
const { InteractionTypes, ApplicationCommandOptionTypes, Events } = require('../../util/Constants');
|
||||||
|
const { Error } = require('../../errors');
|
||||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||||
const { Collection } = require('@discordjs/collection');
|
const { setTimeout } = require('node:timers');
|
||||||
const { InteractionTypes, MaxBulkDeletableMessageAge } = require('../../util/Constants');
|
|
||||||
const { TypeError, Error } = require('../../errors');
|
|
||||||
const InteractionCollector = require('../InteractionCollector');
|
|
||||||
const { lazy, getAttachments, uploadFile } = require('../../util/Util');
|
|
||||||
const Message = lazy(() => require('../Message').Message);
|
|
||||||
const { s } = require('@sapphire/shapeshift');
|
const { s } = require('@sapphire/shapeshift');
|
||||||
|
const Util = require('../../util/Util');
|
||||||
const validateName = stringName =>
|
const validateName = stringName =>
|
||||||
s.string
|
s.string
|
||||||
.lengthGreaterThanOrEqual(1)
|
.lengthGreaterThanOrEqual(1)
|
||||||
@ -32,12 +29,6 @@ class TextBasedChannel {
|
|||||||
*/
|
*/
|
||||||
this.messages = new MessageManager(this);
|
this.messages = new MessageManager(this);
|
||||||
|
|
||||||
/**
|
|
||||||
* A manager of the interactions sent to this channel
|
|
||||||
* @type {InteractionManager}
|
|
||||||
*/
|
|
||||||
this.interactions = new InteractionManager(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The channel's last message id, if one was sent
|
* The channel's last message id, if one was sent
|
||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
@ -76,7 +67,7 @@ class TextBasedChannel {
|
|||||||
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
||||||
* @property {string} [nonce=''] The nonce for the message
|
* @property {string} [nonce=''] The nonce for the message
|
||||||
* @property {string} [content=''] The content for the message
|
* @property {string} [content=''] The content for the message
|
||||||
* @property {Array<(MessageEmbed|APIEmbed|WebEmbed)>} [embeds] The embeds for the message
|
* @property {Array<(MessageEmbed|APIEmbed)>} [embeds] The embeds for the message
|
||||||
* (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
|
* (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
|
||||||
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
|
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
|
||||||
* (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
|
* (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
|
||||||
@ -84,7 +75,6 @@ class TextBasedChannel {
|
|||||||
* @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
|
* @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
|
||||||
* Action rows containing interactive components for the message (buttons, select menus)
|
* Action rows containing interactive components for the message (buttons, select menus)
|
||||||
* @property {MessageAttachment[]} [attachments] Attachments to send in the message
|
* @property {MessageAttachment[]} [attachments] Attachments to send in the message
|
||||||
* @property {boolean} [usingNewAttachmentAPI] Whether to use the new attachment API (`channels/:id/attachments`)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,21 +158,16 @@ class TextBasedChannel {
|
|||||||
let messagePayload;
|
let messagePayload;
|
||||||
|
|
||||||
if (options instanceof MessagePayload) {
|
if (options instanceof MessagePayload) {
|
||||||
messagePayload = await options.resolveData();
|
messagePayload = options.resolveData();
|
||||||
} else {
|
} else {
|
||||||
messagePayload = await MessagePayload.create(this, options).resolveData();
|
messagePayload = MessagePayload.create(this, options).resolveData();
|
||||||
}
|
}
|
||||||
|
|
||||||
let { data, files } = await messagePayload.resolveFiles();
|
const { data, files } = await messagePayload.resolveFiles();
|
||||||
|
// New API
|
||||||
if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
|
const attachments = await Util.getUploadURL(this.client, this.id, files);
|
||||||
options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.usingNewAttachmentAPI === true) {
|
|
||||||
const attachments = await getAttachments(this.client, this.id, ...files);
|
|
||||||
const requestPromises = attachments.map(async attachment => {
|
const requestPromises = attachments.map(async attachment => {
|
||||||
await uploadFile(files[attachment.id].file, attachment.upload_url);
|
await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
|
||||||
return {
|
return {
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
filename: files[attachment.id].name,
|
filename: files[attachment.id].name,
|
||||||
@ -195,14 +180,175 @@ class TextBasedChannel {
|
|||||||
const attachmentsData = await Promise.all(requestPromises);
|
const attachmentsData = await Promise.all(requestPromises);
|
||||||
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
||||||
data.attachments = attachmentsData;
|
data.attachments = attachmentsData;
|
||||||
files = [];
|
// Empty Files
|
||||||
}
|
const d = await this.client.api.channels[this.id].messages.post({ data });
|
||||||
|
|
||||||
const d = await this.client.api.channels[this.id].messages.post({ data, files });
|
|
||||||
|
|
||||||
return this.messages.cache.get(d.id) ?? this.messages._add(d);
|
return this.messages.cache.get(d.id) ?? this.messages._add(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchInteraction() {
|
||||||
|
// Support Slash / ContextMenu
|
||||||
|
// API https://canary.discord.com/api/v9/guilds/:id/application-command-index // Guild
|
||||||
|
// https://canary.discord.com/api/v9/channels/:id/application-command-index // DM Channel
|
||||||
|
// Updated: 07/01/2023
|
||||||
|
return this.client.api[this.guild ? 'guilds' : 'channels'][this.guild?.id || this.id][
|
||||||
|
'application-command-index'
|
||||||
|
].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendSlash(botOrApplicationId, commandNameString, ...args) {
|
||||||
|
// Parse commandName /role add user
|
||||||
|
const cmd = commandNameString.trim().split(' ');
|
||||||
|
// Ex: role add user => [role, add, user]
|
||||||
|
// Parse: name, subGr, sub
|
||||||
|
const commandName = validateName(cmd[0]);
|
||||||
|
// Parse: role
|
||||||
|
const sub = cmd.slice(1);
|
||||||
|
// Parse: [add, user]
|
||||||
|
for (let i = 0; i < sub.length; i++) {
|
||||||
|
if (sub.length > 2) {
|
||||||
|
throw new Error('INVALID_COMMAND_NAME', cmd);
|
||||||
|
}
|
||||||
|
validateName(sub[i]);
|
||||||
|
}
|
||||||
|
// Search all
|
||||||
|
const data = await this.searchInteraction();
|
||||||
|
// Find command...
|
||||||
|
const filterCommand = data.application_commands.filter(obj =>
|
||||||
|
// Filter: name | name_default
|
||||||
|
[obj.name, obj.name_default].includes(commandName),
|
||||||
|
);
|
||||||
|
// Filter Bot
|
||||||
|
botOrApplicationId = this.client.users.resolveId(botOrApplicationId);
|
||||||
|
const application = data.applications.find(
|
||||||
|
obj => obj.id == botOrApplicationId || obj.bot?.id == botOrApplicationId,
|
||||||
|
);
|
||||||
|
// Find Command with application
|
||||||
|
const command = filterCommand.find(command => command.application_id == application.id);
|
||||||
|
|
||||||
|
args = args.flat(2);
|
||||||
|
let optionFormat = [];
|
||||||
|
let attachments = [];
|
||||||
|
let optionsMaxdepth, subGroup, subCommand;
|
||||||
|
if (sub.length == 2) {
|
||||||
|
// Subcommand Group > Subcommand
|
||||||
|
// Find Sub group
|
||||||
|
subGroup = command.options.find(
|
||||||
|
obj =>
|
||||||
|
obj.type == ApplicationCommandOptionTypes.SUB_COMMAND_GROUP && [obj.name, obj.name_default].includes(sub[0]),
|
||||||
|
);
|
||||||
|
if (!subGroup) throw new Error('SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID', sub[0]);
|
||||||
|
// Find Sub
|
||||||
|
subCommand = subGroup.options.find(
|
||||||
|
obj => obj.type == ApplicationCommandOptionTypes.SUB_COMMAND && [obj.name, obj.name_default].includes(sub[1]),
|
||||||
|
);
|
||||||
|
if (!subCommand) throw new Error('SLASH_COMMAND_SUB_COMMAND_INVALID', sub[1]);
|
||||||
|
// Options
|
||||||
|
optionsMaxdepth = subCommand.options;
|
||||||
|
} else if (sub.length == 1) {
|
||||||
|
// Subcommand
|
||||||
|
subCommand = command.options.find(
|
||||||
|
obj => obj.type == ApplicationCommandOptionTypes.SUB_COMMAND && [obj.name, obj.name_default].includes(sub[0]),
|
||||||
|
);
|
||||||
|
if (!subCommand) throw new Error('SLASH_COMMAND_SUB_COMMAND_INVALID', sub[0]);
|
||||||
|
// Options
|
||||||
|
optionsMaxdepth = subCommand.options;
|
||||||
|
} else {
|
||||||
|
optionsMaxdepth = command.options;
|
||||||
|
}
|
||||||
|
const valueRequired = optionsMaxdepth?.filter(o => o.required).length || 0;
|
||||||
|
for (let i = 0; i < Math.min(args.length, optionsMaxdepth?.length || 0); i++) {
|
||||||
|
const optionInput = optionsMaxdepth[i];
|
||||||
|
const value = args[i];
|
||||||
|
const parseData = await parseOption(
|
||||||
|
this.client,
|
||||||
|
optionInput,
|
||||||
|
value,
|
||||||
|
optionFormat,
|
||||||
|
attachments,
|
||||||
|
command,
|
||||||
|
application.id,
|
||||||
|
this.guild?.id,
|
||||||
|
this.id,
|
||||||
|
subGroup,
|
||||||
|
subCommand,
|
||||||
|
);
|
||||||
|
optionFormat = parseData.optionFormat;
|
||||||
|
attachments = parseData.attachments;
|
||||||
|
}
|
||||||
|
if (valueRequired > args.length) {
|
||||||
|
throw new Error('SLASH_COMMAND_REQUIRED_OPTIONS_MISSING', valueRequired, optionFormat.length);
|
||||||
|
}
|
||||||
|
// Post
|
||||||
|
let postData;
|
||||||
|
if (subGroup) {
|
||||||
|
postData = [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionTypes.SUB_COMMAND_GROUP,
|
||||||
|
name: subGroup.name,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionTypes.SUB_COMMAND,
|
||||||
|
name: subCommand.name,
|
||||||
|
options: optionFormat,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (subCommand) {
|
||||||
|
postData = [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionTypes.SUB_COMMAND,
|
||||||
|
name: subCommand.name,
|
||||||
|
options: optionFormat,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
postData = optionFormat;
|
||||||
|
}
|
||||||
|
const nonce = SnowflakeUtil.generate();
|
||||||
|
const body = createPostData(
|
||||||
|
this.client,
|
||||||
|
false,
|
||||||
|
application.id,
|
||||||
|
nonce,
|
||||||
|
this.guild?.id,
|
||||||
|
Boolean(command.guild_id),
|
||||||
|
this.id,
|
||||||
|
command.version,
|
||||||
|
command.id,
|
||||||
|
command.name_default || command.name,
|
||||||
|
command.type,
|
||||||
|
postData,
|
||||||
|
attachments,
|
||||||
|
);
|
||||||
|
this.client.api.interactions.post({
|
||||||
|
data: body,
|
||||||
|
usePayloadJSON: true,
|
||||||
|
});
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timeoutMs = 5_000;
|
||||||
|
// Waiting for MsgCreate / ModalCreate
|
||||||
|
const handler = data => {
|
||||||
|
if (data.nonce !== nonce) return;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||||
|
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||||
|
this.client.decrementMaxListeners();
|
||||||
|
resolve(data);
|
||||||
|
};
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
this.client.removeListener(Events.MESSAGE_CREATE, handler);
|
||||||
|
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||||
|
this.client.decrementMaxListeners();
|
||||||
|
reject(new Error('INTERACTION_FAILED'));
|
||||||
|
}, timeoutMs).unref();
|
||||||
|
this.client.incrementMaxListeners();
|
||||||
|
this.client.on(Events.MESSAGE_CREATE, handler);
|
||||||
|
this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a typing indicator in the channel.
|
* Sends a typing indicator in the channel.
|
||||||
* @returns {Promise<void>} Resolves upon the typing status being sent
|
* @returns {Promise<void>} Resolves upon the typing status being sent
|
||||||
@ -261,101 +407,6 @@ class TextBasedChannel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a component interaction collector.
|
|
||||||
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
|
|
||||||
* @returns {InteractionCollector}
|
|
||||||
* @example
|
|
||||||
* // Create a button interaction collector
|
|
||||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
|
||||||
* const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
|
|
||||||
* collector.on('collect', i => console.log(`Collected ${i.customId}`));
|
|
||||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
|
||||||
*/
|
|
||||||
createMessageComponentCollector(options = {}) {
|
|
||||||
return new InteractionCollector(this.client, {
|
|
||||||
...options,
|
|
||||||
interactionType: InteractionTypes.MESSAGE_COMPONENT,
|
|
||||||
channel: this,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects a single component interaction that passes the filter.
|
|
||||||
* The Promise will reject if the time expires.
|
|
||||||
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
|
|
||||||
* @returns {Promise<MessageComponentInteraction>}
|
|
||||||
* @example
|
|
||||||
* // Collect a message component interaction
|
|
||||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
|
||||||
* channel.awaitMessageComponent({ filter, time: 15_000 })
|
|
||||||
* .then(interaction => console.log(`${interaction.customId} was clicked!`))
|
|
||||||
* .catch(console.error);
|
|
||||||
*/
|
|
||||||
awaitMessageComponent(options = {}) {
|
|
||||||
const _options = { ...options, max: 1 };
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const collector = this.createMessageComponentCollector(_options);
|
|
||||||
collector.once('end', (interactions, reason) => {
|
|
||||||
const interaction = interactions.first();
|
|
||||||
if (interaction) resolve(interaction);
|
|
||||||
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bulk deletes given messages that are newer than two weeks.
|
|
||||||
* @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
|
|
||||||
* Messages or number of messages to delete
|
|
||||||
* @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
|
|
||||||
* @returns {Promise<Collection<Snowflake, Message|undefined>>} Returns the deleted messages
|
|
||||||
* @example
|
|
||||||
* // Bulk delete messages
|
|
||||||
* channel.bulkDelete(5)
|
|
||||||
* .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
|
|
||||||
* .catch(console.error);
|
|
||||||
*/
|
|
||||||
async bulkDelete(messages, filterOld = false) {
|
|
||||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
|
||||||
if (Array.isArray(messages) || messages instanceof Collection) {
|
|
||||||
let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m);
|
|
||||||
if (filterOld) {
|
|
||||||
messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < MaxBulkDeletableMessageAge);
|
|
||||||
}
|
|
||||||
if (messageIds.length === 0) return new Collection();
|
|
||||||
if (messageIds.length === 1) {
|
|
||||||
await this.client.api.channels(this.id).messages(messageIds[0]).delete();
|
|
||||||
const message = this.client.actions.MessageDelete.getMessage(
|
|
||||||
{
|
|
||||||
message_id: messageIds[0],
|
|
||||||
},
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
return message ? new Collection([[message.id, message]]) : new Collection();
|
|
||||||
}
|
|
||||||
await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
|
|
||||||
return messageIds.reduce(
|
|
||||||
(col, id) =>
|
|
||||||
col.set(
|
|
||||||
id,
|
|
||||||
this.client.actions.MessageDeleteBulk.getMessage(
|
|
||||||
{
|
|
||||||
message_id: id,
|
|
||||||
},
|
|
||||||
this,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
new Collection(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!isNaN(messages)) {
|
|
||||||
const msgs = await this.messages.fetch({ limit: messages });
|
|
||||||
return this.bulkDelete(msgs, filterOld);
|
|
||||||
}
|
|
||||||
throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all webhooks for the channel.
|
* Fetches all webhooks for the channel.
|
||||||
* @returns {Promise<Collection<Snowflake, Webhook>>}
|
* @returns {Promise<Collection<Snowflake, Webhook>>}
|
||||||
@ -414,139 +465,21 @@ class TextBasedChannel {
|
|||||||
return this.edit({ nsfw }, reason);
|
return this.edit({ nsfw }, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Search Slash Command (return raw data)
|
|
||||||
* @param {Snowflake} applicationId Application ID
|
|
||||||
* @param {?ApplicationCommandType} type Command Type
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
searchInteraction(applicationId, type = 'CHAT_INPUT') {
|
|
||||||
switch (type) {
|
|
||||||
case 'USER':
|
|
||||||
case 2:
|
|
||||||
type = 2;
|
|
||||||
break;
|
|
||||||
case 'MESSAGE':
|
|
||||||
case 3:
|
|
||||||
type = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
type = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return this.client.api.channels[this.id]['application-commands'].search.get({
|
|
||||||
query: {
|
|
||||||
type,
|
|
||||||
application_id: applicationId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send Slash to this channel
|
|
||||||
* @param {UserResolvable} bot Bot user (BotID, not applicationID)
|
|
||||||
* @param {string} commandString Command name (and sub / group formats)
|
|
||||||
* @param {...?any|any[]} args Command arguments
|
|
||||||
* @returns {Promise<InteractionResponse>}
|
|
||||||
* @example
|
|
||||||
* // Send a basic slash
|
|
||||||
* channel.sendSlash('botid', 'ping')
|
|
||||||
* .then(console.log)
|
|
||||||
* .catch(console.error);
|
|
||||||
* @example
|
|
||||||
* // Send a remote file
|
|
||||||
* channel.sendSlash('botid', 'emoji upload', 'https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048', 'test')
|
|
||||||
* .then(console.log)
|
|
||||||
* .catch(console.error);
|
|
||||||
* @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/SlashCommand.md}
|
|
||||||
*/
|
|
||||||
async sendSlash(bot, commandString, ...args) {
|
|
||||||
const perms =
|
|
||||||
this.type != 'DM'
|
|
||||||
? this.permissionsFor(this.client.user).toArray()
|
|
||||||
: ['USE_APPLICATION_COMMANDS', `${this.recipient.relationships == 'BLOCKED' ? '' : 'SEND_MESSAGES'}`];
|
|
||||||
if (!perms.includes('SEND_MESSAGES')) {
|
|
||||||
throw new Error(
|
|
||||||
'INTERACTION_SEND_FAILURE',
|
|
||||||
`Cannot send Slash to ${this.toString()} ${
|
|
||||||
this.recipient ? 'because bot has been blocked' : 'due to missing SEND_MESSAGES permission'
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!perms.includes('USE_APPLICATION_COMMANDS')) {
|
|
||||||
throw new Error(
|
|
||||||
'INTERACTION_SEND_FAILURE',
|
|
||||||
`Cannot send Slash to ${this.toString()} due to missing USE_APPLICATION_COMMANDS permission`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args = args.flat(2);
|
|
||||||
const cmd = commandString.trim().split(' ');
|
|
||||||
// Validate CommandName
|
|
||||||
const commandName = validateName(cmd[0]);
|
|
||||||
const sub = cmd.slice(1);
|
|
||||||
for (let i = 0; i < sub.length; i++) {
|
|
||||||
if (sub.length > 2) {
|
|
||||||
throw new Error('INVALID_COMMAND_NAME', cmd);
|
|
||||||
}
|
|
||||||
validateName(sub[i]);
|
|
||||||
}
|
|
||||||
if (!bot) throw new Error('MUST_SPECIFY_BOT');
|
|
||||||
const botId = this.client.users.resolveId(bot);
|
|
||||||
const user = await this.client.users.fetch(botId).catch(() => {});
|
|
||||||
if (!user || !user.bot || !user.application) {
|
|
||||||
throw new Error('botId is not a bot or does not have an application slash command');
|
|
||||||
}
|
|
||||||
if (user._partial) await user.getProfile().catch(() => {});
|
|
||||||
if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required');
|
|
||||||
const data = await this.searchInteraction(user.application?.id ?? user.id, 'CHAT_INPUT');
|
|
||||||
for (const command of data.application_commands) {
|
|
||||||
if (user.id == command.application_id || user.application.id == command.application_id) {
|
|
||||||
user.application?.commands?._add(command, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remove
|
|
||||||
const commandTarget = user.application?.commands?.cache.find(
|
|
||||||
c => c.name === commandName && c.type === 'CHAT_INPUT',
|
|
||||||
);
|
|
||||||
if (!commandTarget) {
|
|
||||||
throw new Error(
|
|
||||||
'INTERACTION_SEND_FAILURE',
|
|
||||||
`SlashCommand ${commandName} is not found (With search)\nDebug:\n+ botId: ${botId} (ApplicationId: ${
|
|
||||||
user.application?.id
|
|
||||||
})\n+ args: ${args.join(' | ') || null}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return commandTarget.sendSlashCommand(
|
|
||||||
new (Message())(this.client, {
|
|
||||||
channel_id: this.id,
|
|
||||||
guild_id: this.guild?.id || null,
|
|
||||||
author: this.client.user,
|
|
||||||
content: '',
|
|
||||||
id: this.client.user.id,
|
|
||||||
}),
|
|
||||||
sub && sub.length > 0 ? sub : [],
|
|
||||||
args && args.length ? args : [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static applyToClass(structure, full = false, ignore = []) {
|
static applyToClass(structure, full = false, ignore = []) {
|
||||||
const props = ['send'];
|
const props = ['send'];
|
||||||
if (full) {
|
if (full) {
|
||||||
props.push(
|
props.push(
|
||||||
|
'sendSlash',
|
||||||
|
'searchInteraction',
|
||||||
'lastMessage',
|
'lastMessage',
|
||||||
'lastPinAt',
|
'lastPinAt',
|
||||||
'bulkDelete',
|
|
||||||
'sendTyping',
|
'sendTyping',
|
||||||
'createMessageCollector',
|
'createMessageCollector',
|
||||||
'awaitMessages',
|
'awaitMessages',
|
||||||
'createMessageComponentCollector',
|
|
||||||
'awaitMessageComponent',
|
|
||||||
'fetchWebhooks',
|
'fetchWebhooks',
|
||||||
'createWebhook',
|
'createWebhook',
|
||||||
'setRateLimitPerUser',
|
'setRateLimitPerUser',
|
||||||
'setNSFW',
|
'setNSFW',
|
||||||
'sendSlash',
|
|
||||||
'searchInteraction',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
@ -564,3 +497,225 @@ module.exports = TextBasedChannel;
|
|||||||
|
|
||||||
// Fixes Circular
|
// Fixes Circular
|
||||||
const MessageManager = require('../../managers/MessageManager');
|
const MessageManager = require('../../managers/MessageManager');
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
function parseChoices(parent, list_choices, value) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (Array.isArray(list_choices) && list_choices.length) {
|
||||||
|
const choice = list_choices.find(c => [c.name, c.value].includes(value));
|
||||||
|
if (choice) {
|
||||||
|
return choice.value;
|
||||||
|
} else {
|
||||||
|
throw new Error('INVALID_SLASH_COMMAND_CHOICES', parent, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addDataFromAttachment(value, client, channelId, attachments) {
|
||||||
|
value = await MessagePayload.resolveFile(value);
|
||||||
|
if (!value?.file) {
|
||||||
|
throw new TypeError('The attachment data must be a BufferResolvable or Stream or FileOptions of MessageAttachment');
|
||||||
|
}
|
||||||
|
const data = await Util.getUploadURL(client, channelId, [value]);
|
||||||
|
await Util.uploadFile(value.file, data[0].upload_url);
|
||||||
|
const id = attachments.length;
|
||||||
|
attachments.push({
|
||||||
|
id,
|
||||||
|
filename: value.name,
|
||||||
|
uploaded_filename: data[0].upload_filename,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
attachments,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseOption(
|
||||||
|
client,
|
||||||
|
optionCommand,
|
||||||
|
value,
|
||||||
|
optionFormat,
|
||||||
|
attachments,
|
||||||
|
command,
|
||||||
|
applicationId,
|
||||||
|
guildId,
|
||||||
|
channelId,
|
||||||
|
subGroup,
|
||||||
|
subCommand,
|
||||||
|
) {
|
||||||
|
const data = {
|
||||||
|
type: optionCommand.type,
|
||||||
|
name: optionCommand.name,
|
||||||
|
};
|
||||||
|
if (value !== undefined) {
|
||||||
|
switch (optionCommand.type) {
|
||||||
|
case ApplicationCommandOptionTypes.BOOLEAN:
|
||||||
|
case 'BOOLEAN': {
|
||||||
|
data.value = Boolean(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ApplicationCommandOptionTypes.INTEGER:
|
||||||
|
case 'INTEGER': {
|
||||||
|
data.value = Number(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ApplicationCommandOptionTypes.ATTACHMENT:
|
||||||
|
case 'ATTACHMENT': {
|
||||||
|
const parseData = await addDataFromAttachment(value, client, channelId, attachments);
|
||||||
|
data.value = parseData.id;
|
||||||
|
attachments = parseData.attachments;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ApplicationCommandOptionTypes.SUB_COMMAND_GROUP:
|
||||||
|
case 'SUB_COMMAND_GROUP': {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
value = parseChoices(optionCommand.name, optionCommand.choices, value);
|
||||||
|
if (optionCommand.autocomplete) {
|
||||||
|
const nonce = SnowflakeUtil.generate();
|
||||||
|
// Post
|
||||||
|
let postData;
|
||||||
|
if (subGroup) {
|
||||||
|
postData = [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionTypes.SUB_COMMAND_GROUP,
|
||||||
|
name: subGroup.name,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionTypes.SUB_COMMAND,
|
||||||
|
name: subCommand.name,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: optionCommand.type,
|
||||||
|
name: optionCommand.name,
|
||||||
|
value,
|
||||||
|
focused: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (subCommand) {
|
||||||
|
postData = [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionTypes.SUB_COMMAND,
|
||||||
|
name: subCommand.name,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: optionCommand.type,
|
||||||
|
name: optionCommand.name,
|
||||||
|
value,
|
||||||
|
focused: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
postData = [
|
||||||
|
{
|
||||||
|
type: optionCommand.type,
|
||||||
|
name: optionCommand.name,
|
||||||
|
value,
|
||||||
|
focused: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const body = createPostData(
|
||||||
|
client,
|
||||||
|
true,
|
||||||
|
applicationId,
|
||||||
|
nonce,
|
||||||
|
guildId,
|
||||||
|
Boolean(command.guild_id),
|
||||||
|
channelId,
|
||||||
|
command.version,
|
||||||
|
command.id,
|
||||||
|
command.name_default || command.name,
|
||||||
|
command.type,
|
||||||
|
postData,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
await client.api.interactions.post({
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
data.value = await awaitAutocomplete(client, nonce, value);
|
||||||
|
} else {
|
||||||
|
data.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
optionFormat.push(data);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
optionFormat,
|
||||||
|
attachments,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function awaitAutocomplete(client, nonce, defaultValue) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const handler = data => {
|
||||||
|
if (data.t !== 'APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE') return;
|
||||||
|
if (data.d?.nonce !== nonce) return;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||||
|
client.decrementMaxListeners();
|
||||||
|
if (data.d.choices.length >= 1) {
|
||||||
|
resolve(data.d.choices[0].value);
|
||||||
|
} else {
|
||||||
|
resolve(defaultValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
client.removeListener(Events.UNHANDLED_PACKET, handler);
|
||||||
|
client.decrementMaxListeners();
|
||||||
|
resolve(defaultValue);
|
||||||
|
}, 5_000).unref();
|
||||||
|
client.incrementMaxListeners();
|
||||||
|
client.on(Events.UNHANDLED_PACKET, handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPostData(
|
||||||
|
client,
|
||||||
|
isAutocomplete = false,
|
||||||
|
applicationId,
|
||||||
|
nonce,
|
||||||
|
guildId,
|
||||||
|
isGuildCommand,
|
||||||
|
channelId,
|
||||||
|
commandVersion,
|
||||||
|
commandId,
|
||||||
|
commandName,
|
||||||
|
commandType,
|
||||||
|
postData,
|
||||||
|
attachments = [],
|
||||||
|
) {
|
||||||
|
const data = {
|
||||||
|
type: isAutocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND,
|
||||||
|
application_id: applicationId,
|
||||||
|
guild_id: guildId,
|
||||||
|
channel_id: channelId,
|
||||||
|
session_id: client.ws.shards.first()?.sessionId,
|
||||||
|
data: {
|
||||||
|
version: commandVersion,
|
||||||
|
id: commandId,
|
||||||
|
name: commandName,
|
||||||
|
type: commandType,
|
||||||
|
options: postData,
|
||||||
|
attachments: attachments,
|
||||||
|
},
|
||||||
|
nonce,
|
||||||
|
};
|
||||||
|
if (isGuildCommand) {
|
||||||
|
data.data.guild_id = guildId;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user