refactor(SendSlash): Perfect 🐛
This commit is contained in:
parent
89349b9eac
commit
347caf9dce
@ -2,6 +2,10 @@
|
||||
- Support Autocomplete feature (half)
|
||||
- Unused `guild.searchInteraction()` (Use only if not working properly)
|
||||
|
||||
# <strong>BREAKING CHANGE: Using Slash Command (Sub Command / Sub Group Command) will not accept subCommand argument in args. That means Command Name needs to be changed same as Discord Client</strong>
|
||||
|
||||
# All image demo : v2.3
|
||||
|
||||
# Slash Command (no options)
|
||||
|
||||
### Demo
|
||||
@ -14,7 +18,6 @@
|
||||
|
||||
```js
|
||||
await message.channel.sendSlash('botid', 'aiko')
|
||||
// Return nonce (view document)
|
||||
```
|
||||
|
||||
### Result
|
||||
@ -23,15 +26,17 @@ await message.channel.sendSlash('botid', 'aiko')
|
||||
|
||||
# Slash Command + Sub option (group)
|
||||
|
||||
### Demo
|
||||
### Demo (v2.5)
|
||||
|
||||
![image](https://user-images.githubusercontent.com/71698422/173346438-678009a1-870c-49a2-97fe-8ceed4f1ab64.png)
|
||||
|
||||
### Code test
|
||||
|
||||
```js
|
||||
await message.channel.sendSlash('450323683840491530', 'animal', 'chat', 'bye')
|
||||
// Return nonce (view document)
|
||||
```diff
|
||||
v2.5
|
||||
- await message.channel.sendSlash('450323683840491530', 'animal', 'chat', 'bye')
|
||||
v2.6+
|
||||
+ await message.channel.sendSlash('450323683840491530', 'animal chat', 'bye')
|
||||
```
|
||||
|
||||
### Result
|
||||
@ -51,7 +56,6 @@ const { MessageAttachment } = require('discord.js-selfbot-v13')
|
||||
const fs = require('fs')
|
||||
const a = new MessageAttachment(fs.readFileSync('./wallpaper.jpg') , 'test.jpg')
|
||||
await message.channel.sendSlash('718642000898818048', 'sauce', a)
|
||||
// Return nonce (view document)
|
||||
```
|
||||
|
||||
### Result
|
||||
|
@ -50,6 +50,7 @@ const Messages = {
|
||||
/* Add */
|
||||
MISSING_PERMISSIONS: (...permission) => `You can't do this action [Missing Permission(s): ${permission.join(', ')}]`,
|
||||
EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.',
|
||||
INVALID_COMMAND_NAME: allCMD => `Could not parse subGroupCommand and subCommand due to too long: ${allCMD.join(' ')}`,
|
||||
|
||||
BUTTON_LABEL: 'MessageButton label must be a string',
|
||||
BUTTON_URL: 'MessageButton URL must be a string',
|
||||
|
@ -10,6 +10,7 @@ const Permissions = require('../util/Permissions');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const { lazy } = require('../util/Util');
|
||||
const Message = lazy(() => require('../structures/Message').Message);
|
||||
|
||||
/**
|
||||
* Represents an application command.
|
||||
* @extends {Base}
|
||||
@ -588,11 +589,15 @@ class ApplicationCommand extends Base {
|
||||
/**
|
||||
* Send Slash command to channel
|
||||
* @param {Message} message Discord Message
|
||||
* @param {Array<string>} subCommandArray SubCommand Array
|
||||
* @param {Array<string>} options The options to Slash Command
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
*/
|
||||
async sendSlashCommand(message, options = []) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
async sendSlashCommand(message, subCommandArray = [], options = []) {
|
||||
// Todo: Refactor
|
||||
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');
|
||||
@ -604,6 +609,167 @@ class ApplicationCommand extends Base {
|
||||
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);
|
||||
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 = this.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) => {
|
||||
const subCommand = this.options.find(o => o.name == subCommandName && o.type == 'SUB_COMMAND');
|
||||
if (!subCommand) {
|
||||
throw buildError(
|
||||
'SubCommand',
|
||||
subCommandName,
|
||||
this.options.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;
|
||||
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.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);
|
||||
return {
|
||||
type: ApplicationCommandOptionTypes[subGroup.type],
|
||||
name: subGroup.name,
|
||||
options: [data],
|
||||
};
|
||||
};
|
||||
async function addDataFromAttachment(data) {
|
||||
if (!(data instanceof MessageAttachment)) {
|
||||
throw new TypeError('The attachment data must be a Discord.MessageAttachment');
|
||||
@ -617,49 +783,54 @@ class ApplicationCommand extends Base {
|
||||
attachmentsBuffer.push({ attachment: data.attachment, name: data.name, file: resource });
|
||||
return id;
|
||||
}
|
||||
let option_ = [];
|
||||
let i = 0;
|
||||
// Check Command type is Sub group ?
|
||||
const subCommandCheck = this.options.some(option => ['SUB_COMMAND', 'SUB_COMMAND_GROUP'].includes(option.type));
|
||||
let subCommand;
|
||||
if (subCommandCheck) {
|
||||
subCommand = this.options.find(option => option.name == options[0]);
|
||||
options.shift();
|
||||
option_[0] = {
|
||||
type: ApplicationCommandOptionTypes[subCommand.type],
|
||||
name: subCommand.name,
|
||||
options: optionFormat,
|
||||
};
|
||||
} else {
|
||||
option_ = optionFormat;
|
||||
}
|
||||
// Autoresponse
|
||||
const getAutoResponse = async (options_, type, name, value) => {
|
||||
const op = Array.from(options_);
|
||||
op.push({
|
||||
type,
|
||||
name,
|
||||
value,
|
||||
focused: true,
|
||||
});
|
||||
const getDataPost = (guildAdd = false, dataAdd = [], nonce, autocomplete = false) => {
|
||||
if (!Array.isArray(dataAdd) && typeof dataAdd == 'object') {
|
||||
dataAdd = [dataAdd];
|
||||
}
|
||||
if (guildAdd) {
|
||||
return {
|
||||
type: autocomplete ? 4 : 2, // Slash command, context menu
|
||||
// Type: 4: Auto-complete
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.session_id,
|
||||
data: {
|
||||
// ApplicationCommandData
|
||||
version: this.version,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: ApplicationCommandTypes[this.type],
|
||||
options: dataAdd,
|
||||
attachments: attachments,
|
||||
guild_id: message.guildId,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: autocomplete ? 4 : 2, // Slash command, context menu
|
||||
// Type: 4: Auto-complete
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.session_id,
|
||||
data: {
|
||||
// ApplicationCommandData
|
||||
version: this.version,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: ApplicationCommandTypes[this.type],
|
||||
options: dataAdd,
|
||||
attachments: attachments,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
}
|
||||
};
|
||||
const getAutoResponse = async (sendData, value) => {
|
||||
let nonce = SnowflakeUtil.generate();
|
||||
const data = {
|
||||
type: 4, // Auto-complete
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.session_id,
|
||||
data: {
|
||||
// ApplicationCommandData
|
||||
version: this.version,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: ApplicationCommandTypes[this.type],
|
||||
options: op,
|
||||
attachments: attachments,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
const data = getDataPost(false, sendData, nonce, true);
|
||||
await this.client.api.interactions
|
||||
.post({
|
||||
data,
|
||||
@ -667,10 +838,9 @@ class ApplicationCommand extends Base {
|
||||
})
|
||||
.catch(async () => {
|
||||
nonce = SnowflakeUtil.generate();
|
||||
data.data.guild_id = message.guildId;
|
||||
data.nonce = nonce;
|
||||
const data_ = getDataPost(true, sendData, nonce);
|
||||
await this.client.api.interactions.post({
|
||||
data,
|
||||
body: data_,
|
||||
files: attachmentsBuffer,
|
||||
});
|
||||
});
|
||||
@ -693,193 +863,68 @@ class ApplicationCommand extends Base {
|
||||
this.client.on(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
||||
});
|
||||
};
|
||||
for (i; i < options.length; i++) {
|
||||
const value = options[i];
|
||||
if (!subCommandCheck && !this?.options[i]) continue;
|
||||
if (subCommandCheck && subCommand?.options && !subCommand?.options[i]) continue;
|
||||
if (!subCommandCheck) {
|
||||
// Check value is invalid
|
||||
let choice;
|
||||
if (this.options[i].choices && this.options[i].choices.length > 0) {
|
||||
choice =
|
||||
this.options[i].choices.find(c => c.name == value) || this.options[i].choices.find(c => c.value == value);
|
||||
if (!choice && value !== undefined) {
|
||||
throw new Error(
|
||||
`Invalid option: ${value} is not a valid choice for this option\nList of choices:\n${this.options[
|
||||
i
|
||||
].choices
|
||||
.map((c, i) => ` #${i + 1} Name: ${c.name} Value: ${c.value}`)
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
type: ApplicationCommandOptionTypes[this.options[i].type],
|
||||
name: this.options[i].name,
|
||||
value:
|
||||
choice?.value || this.options[i].type == 'ATTACHMENT'
|
||||
? await addDataFromAttachment(value)
|
||||
: this.options[i].type == 'INTEGER'
|
||||
? Number(value)
|
||||
: this.options[i].type == 'BOOLEAN'
|
||||
? Boolean(value)
|
||||
: this.options[i].autocomplete
|
||||
? await getAutoResponse(
|
||||
optionFormat,
|
||||
ApplicationCommandOptionTypes[this.options[i].type],
|
||||
this.options[i].name,
|
||||
value,
|
||||
)
|
||||
: value,
|
||||
};
|
||||
if (value !== undefined) optionFormat.push(data);
|
||||
} else {
|
||||
// First element is sub command and removed
|
||||
if (!value) continue;
|
||||
// Check value is invalid
|
||||
let choice;
|
||||
if (subCommand?.options && subCommand.options[i].choices && subCommand.options[i].choices.length > 0) {
|
||||
choice =
|
||||
subCommand.options[i].choices.find(c => c.name == value) ||
|
||||
subCommand.options[i].choices.find(c => c.value == value);
|
||||
if (!choice && value !== undefined) {
|
||||
throw new Error(
|
||||
`Invalid option: ${value} is not a valid choice for this option\nList of choices: \n${subCommand.options[
|
||||
i
|
||||
].choices
|
||||
.map((c, i) => `#${i + 1} Name: ${c.name} Value: ${c.value}\n`)
|
||||
.join('')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
type: ApplicationCommandOptionTypes[subCommand.options[i].type],
|
||||
name: subCommand.options[i].name,
|
||||
value:
|
||||
choice?.value || subCommand.options[i].type == 'ATTACHMENT'
|
||||
? await addDataFromAttachment(value)
|
||||
: subCommand.options[i].type == 'INTEGER'
|
||||
? Number(value)
|
||||
: subCommand.options[i].type == 'BOOLEAN'
|
||||
? Boolean(value)
|
||||
: this.options[i].autocomplete
|
||||
? await getAutoResponse(
|
||||
optionFormat,
|
||||
ApplicationCommandOptionTypes[subCommand.options[i].type],
|
||||
subCommand.options[i].name,
|
||||
value,
|
||||
)
|
||||
: value,
|
||||
};
|
||||
if (value !== undefined) optionFormat.push(data);
|
||||
}
|
||||
}
|
||||
if (!subCommandCheck && this.options[i]?.required) {
|
||||
throw new Error('Value required missing');
|
||||
}
|
||||
if (subCommandCheck && subCommand?.options && subCommand?.options[i]?.required) {
|
||||
throw new Error('Value required missing');
|
||||
}
|
||||
let nonce = SnowflakeUtil.generate();
|
||||
const data = {
|
||||
type: 2, // Slash command, context menu
|
||||
// Type: 4: Auto-complete
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.session_id,
|
||||
data: {
|
||||
// ApplicationCommandData
|
||||
version: this.version,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: ApplicationCommandTypes[this.type],
|
||||
options: option_,
|
||||
attachments: attachments,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
await this.client.api.interactions
|
||||
.post({
|
||||
body: data,
|
||||
files: attachmentsBuffer,
|
||||
})
|
||||
.catch(async () => {
|
||||
nonce = SnowflakeUtil.generate();
|
||||
data.data.guild_id = message.guildId;
|
||||
data.nonce = nonce;
|
||||
await this.client.api.interactions.post({
|
||||
const sendData = async (optionsData = []) => {
|
||||
let nonce = SnowflakeUtil.generate();
|
||||
const data = getDataPost(false, optionsData, nonce);
|
||||
await this.client.api.interactions
|
||||
.post({
|
||||
body: data,
|
||||
files: attachmentsBuffer,
|
||||
})
|
||||
.catch(async () => {
|
||||
nonce = SnowflakeUtil.generate();
|
||||
const data_ = getDataPost(true, optionsData, nonce);
|
||||
await this.client.api.interactions.post({
|
||||
body: data_,
|
||||
files: attachmentsBuffer,
|
||||
});
|
||||
});
|
||||
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(data.metadata);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('INTERACTION_TIMEOUT'));
|
||||
}, 15_000).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on('interactionResponse', handler);
|
||||
});
|
||||
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(data.metadata);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('INTERACTION_TIMEOUT'));
|
||||
}, 15_000).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on('interactionResponse', handler);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Message Context Menu
|
||||
* @param {Message} message Discord Message
|
||||
* @param {boolean} sendFromMessage nothing .-. not used
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
*/
|
||||
async sendContextMenu(message, sendFromMessage = false) {
|
||||
if (!sendFromMessage && !(message instanceof Message())) {
|
||||
throw new TypeError('The message must be a Discord.Message');
|
||||
};
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (this.type == 'CHAT_INPUT') return false;
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
await this.client.api.interactions.post({
|
||||
body: {
|
||||
type: 2, // Slash command, context menu
|
||||
application_id: this.applicationId,
|
||||
guild_id: message.guildId,
|
||||
channel_id: message.channelId,
|
||||
session_id: this.client.session_id,
|
||||
data: {
|
||||
// ApplicationCommandData
|
||||
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,
|
||||
},
|
||||
});
|
||||
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(data.metadata);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener('interactionResponse', handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('INTERACTION_TIMEOUT'));
|
||||
}, 15_000).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on('interactionResponse', handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,14 @@ const { TypeError, Error } = require('../../errors');
|
||||
const InteractionCollector = require('../InteractionCollector');
|
||||
const { lazy } = require('../../util/Util');
|
||||
const Message = lazy(() => require('../Message').Message);
|
||||
const { s } = require('@sapphire/shapeshift');
|
||||
const validateName = stringName =>
|
||||
s.string
|
||||
.lengthGreaterThanOrEqual(1)
|
||||
.lengthLessThanOrEqual(32)
|
||||
.regex(/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u)
|
||||
.setValidationEnabled(true)
|
||||
.parse(stringName);
|
||||
|
||||
/**
|
||||
* Interface for classes that have text-channel-like features.
|
||||
@ -395,7 +403,7 @@ class TextBasedChannel {
|
||||
/**
|
||||
* Send Slash to this channel
|
||||
* @param {Snowflake} botId Bot Id (Supports application ID - not bot)
|
||||
* @param {string} commandName Command name
|
||||
* @param {string} commandString Command name (and sub / group formats)
|
||||
* @param {...?string|string[]} args Command arguments
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @example
|
||||
@ -407,9 +415,21 @@ class TextBasedChannel {
|
||||
* channel.sendSlash('123456789012345678', 'embed', 'title', 'description', 'author', '#00ff00')
|
||||
* // Send embed with Title and Color:
|
||||
* channel.sendSlash('123456789012345678', 'embed', 'title', undefined, undefined, '#00ff00')
|
||||
* // CommandName is Group Command / Sub Command
|
||||
* channel.sendSlash('123456789012345678', 'embed title', 'description', 'author', '#00ff00')
|
||||
*/
|
||||
async sendSlash(botId, commandName, ...args) {
|
||||
async sendSlash(botId, commandString, ...args) {
|
||||
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 (!botId) throw new Error('Bot ID is required');
|
||||
// ? maybe ...
|
||||
const user = await this.client.users.fetch(botId).catch(() => {});
|
||||
@ -463,6 +483,7 @@ class TextBasedChannel {
|
||||
content: '',
|
||||
id: this.client.user.id,
|
||||
}),
|
||||
sub && sub.length > 0 ? sub : [],
|
||||
args && args.length ? args : [],
|
||||
);
|
||||
}
|
||||
|
2
typings/index.d.ts
vendored
2
typings/index.d.ts
vendored
@ -474,7 +474,7 @@ export class ApplicationCommand<PermissionsFetchType = {}> extends Base {
|
||||
private static transformCommand(command: ApplicationCommandData): RESTPostAPIApplicationCommandsJSONBody;
|
||||
private static isAPICommandData(command: object): command is RESTPostAPIApplicationCommandsJSONBody;
|
||||
// Add
|
||||
public static sendSlashCommand(message: Message, options?: string[]): Promise<InteractionResponseBody>;
|
||||
public static sendSlashCommand(message: Message, subCommandArray?: string[], options?: string[]): Promise<InteractionResponseBody>;
|
||||
public static sendContextMenu(message: Message): Promise<InteractionResponseBody>;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user