update
This commit is contained in:
parent
b3cf4b7710
commit
59f254966c
@ -13,12 +13,15 @@ const Util = require('../util/Util');
|
||||
class BaseClient extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
if (options.intents) {
|
||||
process.emitWarning('Intents is not available.', 'DeprecationWarning');
|
||||
}
|
||||
|
||||
if (typeof options.captchaSolver === 'function') {
|
||||
options.captchaService = 'custom';
|
||||
}
|
||||
|
||||
/**
|
||||
* The options the client was instantiated with
|
||||
* @type {ClientOptions}
|
||||
|
@ -126,36 +126,15 @@ class WebSocketManager extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
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';
|
||||
const { url } = await this.client.api.gateway.get({ auth: false }).catch(() => ({ url: gatewayURL }));
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/*
|
||||
.catch(error => {
|
||||
// Never throw error :v
|
||||
// throw error.httpStatus === 401 ? invalidToken : error;
|
||||
});
|
||||
*/
|
||||
if (url) gatewayURL = url;
|
||||
const recommendedShards = 1;
|
||||
const sessionStartLimit = {
|
||||
total: Infinity,
|
||||
remaining: Infinity,
|
||||
};
|
||||
await this.client.api.gateway
|
||||
.get({ auth: false })
|
||||
.then(r => (gatewayURL = r.url))
|
||||
.catch(() => {});
|
||||
|
||||
const { total, remaining } = sessionStartLimit;
|
||||
const total = Infinity;
|
||||
const remaining = Infinity;
|
||||
const recommendedShards = 1;
|
||||
|
||||
this.debug(`Fetched Gateway Information
|
||||
URL: ${gatewayURL}
|
||||
@ -294,7 +273,7 @@ class WebSocketManager extends EventEmitter {
|
||||
} catch (error) {
|
||||
this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`);
|
||||
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);
|
||||
this.reconnecting = false;
|
||||
return this.reconnect();
|
||||
@ -368,11 +347,12 @@ class WebSocketManager extends EventEmitter {
|
||||
/**
|
||||
* Emitted whenever a packet isn't handled.
|
||||
* @event Client#unhandledPacket
|
||||
* @param {Object} packet The packet (t: Event name, d: Data)
|
||||
* @param {Number} shard The shard that received the packet (Auto = 0)
|
||||
* @param {Object} packet The packet (t: EVENT_NAME, d: any)
|
||||
* @param {Number} shard The shard that received the packet (Shard 0)
|
||||
*/
|
||||
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { isJSONEncodable } = require('@discordjs/builders');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
|
||||
const CachedManager = require('./CachedManager');
|
||||
@ -13,15 +14,14 @@ const Permissions = require('../util/Permissions');
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ApplicationCommandManager extends CachedManager {
|
||||
constructor(client, iterable, user) {
|
||||
constructor(client, iterable) {
|
||||
super(client, ApplicationCommand, iterable);
|
||||
|
||||
/**
|
||||
* The manager for permissions of arbitrary commands on arbitrary guilds
|
||||
* @type {ApplicationCommandPermissionsManager}
|
||||
*/
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this, user);
|
||||
this.user = user;
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +43,7 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* @private
|
||||
*/
|
||||
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);
|
||||
return id ? path.commands(id) : path.commands;
|
||||
}
|
||||
@ -58,7 +58,7 @@ class ApplicationCommandManager extends CachedManager {
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* Data that resolves to the data of an ApplicationCommand
|
||||
* @typedef {ApplicationCommandDataResolvable|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
||||
* @typedef {ApplicationCommandData|APIApplicationCommand|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
|
||||
*/
|
||||
/* eslint-enable max-len */
|
||||
|
||||
@ -94,7 +94,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
|
||||
// Change from user.createDM to opcode (risky action)
|
||||
if (typeof id === 'object') {
|
||||
({ guildId, cache = true, locale, withLocalizations } = id);
|
||||
} else if (id) {
|
||||
@ -102,11 +101,10 @@ class ApplicationCommandManager extends CachedManager {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing) return existing;
|
||||
}
|
||||
await this.user.createDM().catch(() => {});
|
||||
const command = await this.commandPath({ id, guildId }).get();
|
||||
return this._add(command, cache);
|
||||
}
|
||||
await this.user.createDM().catch(() => {});
|
||||
|
||||
const data = await this.commandPath({ guildId }).get({
|
||||
headers: {
|
||||
'X-Discord-Locale': locale,
|
||||
@ -132,7 +130,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async create(command, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const data = await this.commandPath({ guildId }).post({
|
||||
data: this.constructor.transformCommand(command),
|
||||
});
|
||||
@ -162,7 +159,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async set(commands, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const data = await this.commandPath({ guildId }).put({
|
||||
data: commands.map(c => this.constructor.transformCommand(c)),
|
||||
});
|
||||
@ -185,7 +181,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async edit(command, data, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
@ -208,7 +203,6 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(command, guildId) {
|
||||
if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
@ -226,6 +220,8 @@ class ApplicationCommandManager extends CachedManager {
|
||||
* @private
|
||||
*/
|
||||
static transformCommand(command) {
|
||||
if (isJSONEncodable(command)) return command.toJSON();
|
||||
|
||||
let default_member_permissions;
|
||||
|
||||
if ('default_member_permissions' in command) {
|
||||
@ -240,6 +236,7 @@ class ApplicationCommandManager extends CachedManager {
|
||||
? new Permissions(command.defaultMemberPermissions).bitfield.toString()
|
||||
: command.defaultMemberPermissions;
|
||||
}
|
||||
|
||||
return {
|
||||
name: command.name,
|
||||
name_localizations: command.nameLocalizations ?? command.name_localizations,
|
||||
|
@ -10,7 +10,7 @@ const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Consta
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
constructor(manager, user) {
|
||||
constructor(manager) {
|
||||
super(manager.client);
|
||||
|
||||
/**
|
||||
@ -37,8 +37,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.commandId = manager.id ?? null;
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,10 +47,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* @private
|
||||
*/
|
||||
permissionsPath(guildId, commandId) {
|
||||
return this.client.api
|
||||
.applications(typeof this.user === 'string' ? this.user : this.user.id)
|
||||
.guilds(guildId)
|
||||
.commands(commandId).permissions;
|
||||
return this.client.api.applications(this.client.application.id).guilds(guildId).commands(commandId).permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,7 +159,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
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);
|
||||
|
||||
if (commandId) {
|
||||
@ -226,7 +220,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async add({ guild, command, permissions }) {
|
||||
if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
if (!Array.isArray(permissions)) {
|
||||
@ -278,13 +271,12 @@ class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
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);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
|
||||
|
||||
const resolvedIds = [];
|
||||
let resolvedIds = [];
|
||||
if (Array.isArray(users)) {
|
||||
users.forEach(user => {
|
||||
const userId = this.client.users.resolveId(user);
|
||||
|
@ -3,15 +3,15 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { Error } = require('../errors');
|
||||
const { lazy } = require('../util/Util');
|
||||
const User = lazy(() => require('../structures/User'));
|
||||
const User = require('../structures/User');
|
||||
|
||||
/**
|
||||
* Manages API methods for users who reacted to a reaction and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ReactionUserManager extends CachedManager {
|
||||
constructor(reaction, iterable) {
|
||||
super(reaction.client, User(), iterable);
|
||||
super(reaction.client, User, iterable);
|
||||
|
||||
/**
|
||||
* The reaction that this manager belongs to
|
||||
@ -22,7 +22,7 @@ class ReactionUserManager extends CachedManager {
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, Discord.User>}
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @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.
|
||||
* @param {FetchReactionUsersOptions} [options] Options for fetching the users
|
||||
* @returns {Promise<Collection<Snowflake, Discord.User>>}
|
||||
* @returns {Promise<Collection<Snowflake, User>>}
|
||||
*/
|
||||
async fetch({ limit = 100, after } = {}) {
|
||||
const message = this.reaction.message;
|
||||
|
@ -1,28 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { findBestMatch } = require('string-similarity');
|
||||
const Base = require('./Base');
|
||||
const MessagePayload = require('./MessagePayload');
|
||||
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
|
||||
const {
|
||||
ApplicationCommandOptionTypes,
|
||||
ApplicationCommandTypes,
|
||||
ChannelTypes,
|
||||
Events,
|
||||
InteractionTypes,
|
||||
} = require('../util/Constants');
|
||||
const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const { lazy, getAttachments, uploadFile } = require('../util/Util');
|
||||
const Message = lazy(() => require('../structures/Message').Message);
|
||||
|
||||
/**
|
||||
* Represents an application command.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class ApplicationCommand extends Base {
|
||||
constructor(client, data) {
|
||||
constructor(client, data, guild, guildId) {
|
||||
super(client);
|
||||
|
||||
/**
|
||||
@ -37,11 +26,24 @@ class ApplicationCommand extends Base {
|
||||
*/
|
||||
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
|
||||
* @type {ApplicationCommandPermissionsManager}
|
||||
*/
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this, this.applicationId);
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this);
|
||||
|
||||
/**
|
||||
* The type of this application command
|
||||
@ -49,30 +51,10 @@ class ApplicationCommand extends Base {
|
||||
*/
|
||||
this.type = ApplicationCommandTypes[data.type];
|
||||
|
||||
this.user = client.users.cache.get(this.applicationId);
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild this command is part of
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.resolve(this.guildId);
|
||||
}
|
||||
|
||||
_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) {
|
||||
/**
|
||||
* The name of this command
|
||||
@ -148,7 +130,6 @@ class ApplicationCommand extends Base {
|
||||
*/
|
||||
this.defaultPermission = data.default_permission;
|
||||
}
|
||||
|
||||
/* eslint-disable max-len */
|
||||
|
||||
if ('default_member_permissions' in data) {
|
||||
@ -336,7 +317,6 @@ class ApplicationCommand extends Base {
|
||||
setDefaultPermission(defaultPermission = true) {
|
||||
return this.edit({ defaultPermission });
|
||||
}
|
||||
|
||||
/* eslint-enable max-len */
|
||||
|
||||
/**
|
||||
@ -391,6 +371,7 @@ class ApplicationCommand extends Base {
|
||||
equals(command, enforceOptionOrder = false) {
|
||||
// If given an id, check if the id matches
|
||||
if (command.id && this.id !== command.id) return false;
|
||||
|
||||
let defaultMemberPermissions = null;
|
||||
let dmPermission = command.dmPermission ?? command.dm_permission;
|
||||
|
||||
@ -404,6 +385,7 @@ class ApplicationCommand extends Base {
|
||||
defaultMemberPermissions =
|
||||
command.defaultMemberPermissions !== null ? new Permissions(command.defaultMemberPermissions).bitfield : null;
|
||||
}
|
||||
|
||||
// Check top level parameters
|
||||
const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type];
|
||||
if (
|
||||
@ -446,9 +428,7 @@ class ApplicationCommand extends Base {
|
||||
const newOptions = new Map(options.map(option => [option.name, option]));
|
||||
for (const option of existing) {
|
||||
const foundOption = newOptions.get(option.name);
|
||||
if (!foundOption || !this._optionEquals(option, foundOption)) {
|
||||
return false;
|
||||
}
|
||||
if (!foundOption || !this._optionEquals(option, foundOption)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -597,423 +577,6 @@ class ApplicationCommand extends Base {
|
||||
[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;
|
||||
|
@ -3,7 +3,6 @@
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
|
||||
/**
|
||||
@ -21,12 +20,6 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
*/
|
||||
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
|
||||
* @type {GuildTextThreadManager}
|
||||
@ -187,15 +180,10 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
bulkDelete() {}
|
||||
fetchWebhooks() {}
|
||||
createWebhook() {}
|
||||
setRateLimitPerUser() {}
|
||||
setNSFW() {}
|
||||
sendSlash() {}
|
||||
searchInteraction() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
|
||||
|
@ -3,7 +3,6 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const { VideoQualityModes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
@ -28,12 +27,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
*/
|
||||
this.nsfw = Boolean(data.nsfw);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
@ -95,7 +88,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
}
|
||||
|
||||
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.
|
||||
* @param {number} bitrate The new bitrate
|
||||
* @param {string} [reason] Reason for changing the channel's bitrate
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
* @example
|
||||
* // Set the bitrate of a voice channel
|
||||
* voiceChannel.setBitrate(48_000)
|
||||
* .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.name}`))
|
||||
* channel.setBitrate(48_000)
|
||||
* .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setBitrate(bitrate, reason) {
|
||||
@ -201,11 +194,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
* Sets the user limit of the channel.
|
||||
* @param {number} userLimit The new user limit
|
||||
* @param {string} [reason] Reason for changing the user limit
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
* @example
|
||||
* // Set the user limit of a voice channel
|
||||
* voiceChannel.setUserLimit(42)
|
||||
* .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
|
||||
* channel.setUserLimit(42)
|
||||
* .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUserLimit(userLimit, reason) {
|
||||
@ -216,7 +209,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
* Sets the camera video quality mode of the channel.
|
||||
* @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode.
|
||||
* @param {string} [reason] Reason for changing the camera video quality mode.
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @returns {Promise<BaseGuildVoiceChannel>}
|
||||
*/
|
||||
setVideoQualityMode(videoQualityMode, reason) {
|
||||
return this.edit({ videoQualityMode }, reason);
|
||||
@ -229,9 +222,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
bulkDelete() {}
|
||||
fetchWebhooks() {}
|
||||
createWebhook() {}
|
||||
setRateLimitPerUser() {}
|
||||
|
@ -7,7 +7,7 @@ const Base = require('./Base');
|
||||
* Represents a call
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Call extends Base {
|
||||
class CallState extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
/**
|
||||
@ -16,14 +16,11 @@ class Call extends Base {
|
||||
*/
|
||||
this.channelId = data.channel_id;
|
||||
|
||||
/**
|
||||
* The list of user ID who is ringing
|
||||
* @type {Collection<Snowflake, User>}
|
||||
*/
|
||||
this.ringing = new Collection();
|
||||
this._ringing = [];
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('region' in data) {
|
||||
/**
|
||||
@ -33,11 +30,10 @@ class Call extends Base {
|
||||
this.region = data.region;
|
||||
}
|
||||
if ('ringing' in data) {
|
||||
for (const userId of data.ringing) {
|
||||
this.ringing.set(userId, this.client.users.cache.get(userId));
|
||||
}
|
||||
this._ringing = data.ringing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel of the call
|
||||
* @type {?DMChannel|PartialGroupDMChannel}
|
||||
@ -45,14 +41,23 @@ class Call extends Base {
|
||||
get channel() {
|
||||
return this.client.channels.cache.get(this.channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the voice region of the call
|
||||
* @param {string} region Region of the call
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
setVoiceRegion(region) {
|
||||
setRTCRegion(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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached auto moderation rules.
|
||||
* @type {Collection<Snowflake, AutoModerationRule>}
|
||||
@ -487,7 +486,6 @@ class GuildAuditLogsEntry {
|
||||
count: Number(data.options.count),
|
||||
};
|
||||
break;
|
||||
|
||||
case Actions.MESSAGE_PIN:
|
||||
case Actions.MESSAGE_UNPIN:
|
||||
this.extra = {
|
||||
@ -533,13 +531,11 @@ class GuildAuditLogsEntry {
|
||||
channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
|
||||
};
|
||||
break;
|
||||
|
||||
case Actions.APPLICATION_COMMAND_PERMISSION_UPDATE:
|
||||
this.extra = {
|
||||
applicationId: data.options.application_id,
|
||||
};
|
||||
break;
|
||||
|
||||
case Actions.AUTO_MODERATION_BLOCK_MESSAGE:
|
||||
case Actions.AUTO_MODERATION_FLAG_TO_CHANNEL:
|
||||
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 },
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
const { Buffer } = require('node:buffer');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const MessageEmbed = require('./MessageEmbed');
|
||||
const WebEmbed = require('./WebEmbed');
|
||||
const { RangeError } = require('../errors');
|
||||
const ActivityFlags = require('../util/ActivityFlags');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
@ -42,7 +41,6 @@ class MessagePayload {
|
||||
* @property {Buffer|string|Stream} attachment The original attachment that generated this file
|
||||
* @property {string} name The name of this file
|
||||
* @property {Buffer|Stream} file The file to be sent to the API
|
||||
* @extends {APIAttachment}
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -52,15 +50,6 @@ class MessagePayload {
|
||||
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}
|
||||
* @type {boolean}
|
||||
@ -133,12 +122,12 @@ class MessagePayload {
|
||||
* Resolves data.
|
||||
* @returns {MessagePayload}
|
||||
*/
|
||||
async resolveData() {
|
||||
resolveData() {
|
||||
if (this.data) return this;
|
||||
const isInteraction = this.isInteraction;
|
||||
const isWebhook = this.isWebhook;
|
||||
|
||||
let content = this.makeContent();
|
||||
const content = this.makeContent();
|
||||
const tts = Boolean(this.options.tts);
|
||||
|
||||
let nonce;
|
||||
@ -208,37 +197,6 @@ class MessagePayload {
|
||||
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
|
||||
let activity;
|
||||
if (
|
||||
@ -247,7 +205,7 @@ class MessagePayload {
|
||||
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;
|
||||
activity = {
|
||||
type,
|
||||
@ -348,7 +306,7 @@ module.exports = MessagePayload;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
@ -3,8 +3,6 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const Base = require('./Base');
|
||||
const TeamMember = require('./TeamMember');
|
||||
const User = require('./User');
|
||||
const { Error } = require('../errors');
|
||||
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 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Team object.
|
||||
|
@ -1,10 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const { ApplicationFlags } = require('../../util/ApplicationFlags');
|
||||
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
||||
const Permissions = require('../../util/Permissions');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const { ApplicationRoleConnectionMetadata } = require('../ApplicationRoleConnectionMetadata');
|
||||
const Base = require('../Base');
|
||||
const Team = require('../Team');
|
||||
|
||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||
|
||||
@ -67,6 +70,132 @@ class Application extends Base {
|
||||
} else {
|
||||
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
|
||||
* @param {Snowflake} guild_id The id of the guild that you want to invite the bot to
|
||||
* @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 :)
|
||||
* Obtains this application from Discord.
|
||||
* @returns {Promise<Application>}
|
||||
*/
|
||||
async invite(guild_id, permissions, captcha = null) {
|
||||
permissions = Permissions.resolve(permissions || 0n);
|
||||
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({
|
||||
async fetch() {
|
||||
const app = await this.client.api.oauth2.authorize.get({
|
||||
query: {
|
||||
client_id: this.id,
|
||||
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';
|
||||
|
||||
/* eslint-disable import/order */
|
||||
const InteractionManager = require('../../managers/InteractionManager');
|
||||
const MessageCollector = require('../MessageCollector');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
const { InteractionTypes, ApplicationCommandOptionTypes, Events } = require('../../util/Constants');
|
||||
const { Error } = require('../../errors');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
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 { setTimeout } = require('node:timers');
|
||||
const { s } = require('@sapphire/shapeshift');
|
||||
const Util = require('../../util/Util');
|
||||
const validateName = stringName =>
|
||||
s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
@ -32,12 +29,6 @@ class TextBasedChannel {
|
||||
*/
|
||||
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
|
||||
* @type {?Snowflake}
|
||||
@ -76,7 +67,7 @@ class TextBasedChannel {
|
||||
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
||||
* @property {string} [nonce=''] The nonce 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)
|
||||
* @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)
|
||||
@ -84,7 +75,6 @@ class TextBasedChannel {
|
||||
* @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
|
||||
* Action rows containing interactive components for the message (buttons, select menus)
|
||||
* @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;
|
||||
|
||||
if (options instanceof MessagePayload) {
|
||||
messagePayload = await options.resolveData();
|
||||
messagePayload = options.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(this, options).resolveData();
|
||||
messagePayload = MessagePayload.create(this, options).resolveData();
|
||||
}
|
||||
|
||||
let { data, files } = await messagePayload.resolveFiles();
|
||||
|
||||
if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
|
||||
options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
|
||||
}
|
||||
|
||||
if (options?.usingNewAttachmentAPI === true) {
|
||||
const attachments = await getAttachments(this.client, this.id, ...files);
|
||||
const { data, files } = await messagePayload.resolveFiles();
|
||||
// New API
|
||||
const attachments = await Util.getUploadURL(this.client, this.id, files);
|
||||
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 {
|
||||
id: attachment.id,
|
||||
filename: files[attachment.id].name,
|
||||
@ -195,14 +180,175 @@ class TextBasedChannel {
|
||||
const attachmentsData = await Promise.all(requestPromises);
|
||||
attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
||||
data.attachments = attachmentsData;
|
||||
files = [];
|
||||
}
|
||||
|
||||
const d = await this.client.api.channels[this.id].messages.post({ data, files });
|
||||
// Empty Files
|
||||
const d = await this.client.api.channels[this.id].messages.post({ data });
|
||||
|
||||
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.
|
||||
* @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.
|
||||
* @returns {Promise<Collection<Snowflake, Webhook>>}
|
||||
@ -414,139 +465,21 @@ class TextBasedChannel {
|
||||
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 = []) {
|
||||
const props = ['send'];
|
||||
if (full) {
|
||||
props.push(
|
||||
'sendSlash',
|
||||
'searchInteraction',
|
||||
'lastMessage',
|
||||
'lastPinAt',
|
||||
'bulkDelete',
|
||||
'sendTyping',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentCollector',
|
||||
'awaitMessageComponent',
|
||||
'fetchWebhooks',
|
||||
'createWebhook',
|
||||
'setRateLimitPerUser',
|
||||
'setNSFW',
|
||||
'sendSlash',
|
||||
'searchInteraction',
|
||||
);
|
||||
}
|
||||
for (const prop of props) {
|
||||
@ -564,3 +497,225 @@ module.exports = TextBasedChannel;
|
||||
|
||||
// Fixes Circular
|
||||
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