Update Slash

- Send Slash with sub command
This commit is contained in:
March 7th 2022-03-30 23:12:56 +07:00
parent fe1b3fc880
commit 3eb27834af
3 changed files with 167 additions and 68 deletions

View File

@ -270,6 +270,12 @@ await command.sendSlashCommand(Message, ['option1', 'option2']);
// value: ['123456789', '987654321'] // value: ['123456789', '987654321']
// v2 // v2
await Channel.sendSlash(botID, commandName, ['option1', 'option2']); await Channel.sendSlash(botID, commandName, ['option1', 'option2']);
// Eg /addrole roleID: 12345678987654321 userID: 98765432123456789
// => await Channel.sendSlash(botID, 'addrole', ['12345678987654321', '98765432123456789']);
// Command group
await Channel.sendSlash(botID, commandName, ['sub command', 'option1', 'option2']);
// Eg: /role add roleID: 12345678987654321 userID: 98765432123456789
// => await Channel.sendSlash(botID, 'role', ['add', '12345678987654321', '98765432123456789']);
``` ```
</details> </details>
<details> <details>

View File

@ -1,6 +1,6 @@
{ {
"name": "discord.js-selfbot-v13", "name": "discord.js-selfbot-v13",
"version": "1.2.0", "version": "1.2.1",
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]", "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
"main": "./src/index.js", "main": "./src/index.js",
"types": "./typings/index.d.ts", "types": "./typings/index.d.ts",

View File

@ -2,7 +2,11 @@
const Base = require('./Base'); const Base = require('./Base');
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager'); const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes } = require('../util/Constants'); const {
ApplicationCommandOptionTypes,
ApplicationCommandTypes,
ChannelTypes,
} = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil'); const SnowflakeUtil = require('../util/SnowflakeUtil');
const { Message } = require('discord.js'); const { Message } = require('discord.js');
@ -43,7 +47,10 @@ class ApplicationCommand extends Base {
* The manager for permissions of this command on its guild or arbitrary guilds when the command is global * The manager for permissions of this command on its guild or arbitrary guilds when the command is global
* @type {ApplicationCommandPermissionsManager} * @type {ApplicationCommandPermissionsManager}
*/ */
this.permissions = new ApplicationCommandPermissionsManager(this, this.applicationId); this.permissions = new ApplicationCommandPermissionsManager(
this,
this.applicationId,
);
/** /**
* The type of this application command * The type of this application command
@ -78,7 +85,9 @@ class ApplicationCommand extends Base {
* The options of this command * The options of this command
* @type {ApplicationCommandOption[]} * @type {ApplicationCommandOption[]}
*/ */
this.options = data.options.map(o => this.constructor.transformOption(o, true)); this.options = data.options.map((o) =>
this.constructor.transformOption(o, true),
);
} else { } else {
this.options ??= []; this.options ??= [];
} }
@ -236,23 +245,32 @@ class ApplicationCommand extends Base {
if (command.id && this.id !== command.id) return false; if (command.id && this.id !== command.id) return false;
// Check top level parameters // Check top level parameters
const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type]; const commandType =
typeof command.type === 'string'
? command.type
: ApplicationCommandTypes[command.type];
if ( if (
command.name !== this.name || command.name !== this.name ||
('description' in command && command.description !== this.description) || ('description' in command && command.description !== this.description) ||
('version' in command && command.version !== this.version) || ('version' in command && command.version !== this.version) ||
('autocomplete' in command && command.autocomplete !== this.autocomplete) || ('autocomplete' in command &&
command.autocomplete !== this.autocomplete) ||
(commandType && commandType !== this.type) || (commandType && commandType !== this.type) ||
// Future proof for options being nullable // Future proof for options being nullable
// TODO: remove ?? 0 on each when nullable // TODO: remove ?? 0 on each when nullable
(command.options?.length ?? 0) !== (this.options?.length ?? 0) || (command.options?.length ?? 0) !== (this.options?.length ?? 0) ||
(command.defaultPermission ?? command.default_permission ?? true) !== this.defaultPermission (command.defaultPermission ?? command.default_permission ?? true) !==
this.defaultPermission
) { ) {
return false; return false;
} }
if (command.options) { if (command.options) {
return this.constructor.optionsEqual(this.options, command.options, enforceOptionOrder); return this.constructor.optionsEqual(
this.options,
command.options,
enforceOptionOrder,
);
} }
return true; return true;
} }
@ -270,12 +288,15 @@ class ApplicationCommand extends Base {
static optionsEqual(existing, options, enforceOptionOrder = false) { static optionsEqual(existing, options, enforceOptionOrder = false) {
if (existing.length !== options.length) return false; if (existing.length !== options.length) return false;
if (enforceOptionOrder) { if (enforceOptionOrder) {
return existing.every((option, index) => this._optionEquals(option, options[index], enforceOptionOrder)); return existing.every((option, index) =>
this._optionEquals(option, options[index], enforceOptionOrder),
);
} }
const newOptions = new Map(options.map(option => [option.name, option])); const newOptions = new Map(options.map((option) => [option.name, option]));
for (const option of existing) { for (const option of existing) {
const foundOption = newOptions.get(option.name); const foundOption = newOptions.get(option.name);
if (!foundOption || !this._optionEquals(option, foundOption)) return false; if (!foundOption || !this._optionEquals(option, foundOption))
return false;
} }
return true; return true;
} }
@ -292,17 +313,23 @@ class ApplicationCommand extends Base {
* @private * @private
*/ */
static _optionEquals(existing, option, enforceOptionOrder = false) { static _optionEquals(existing, option, enforceOptionOrder = false) {
const optionType = typeof option.type === 'string' ? option.type : ApplicationCommandOptionTypes[option.type]; const optionType =
typeof option.type === 'string'
? option.type
: ApplicationCommandOptionTypes[option.type];
if ( if (
option.name !== existing.name || option.name !== existing.name ||
optionType !== existing.type || optionType !== existing.type ||
option.description !== existing.description || option.description !== existing.description ||
option.autocomplete !== existing.autocomplete || option.autocomplete !== existing.autocomplete ||
(option.required ?? (['SUB_COMMAND', 'SUB_COMMAND_GROUP'].includes(optionType) ? undefined : false)) !== (option.required ??
existing.required || (['SUB_COMMAND', 'SUB_COMMAND_GROUP'].includes(optionType)
? undefined
: false)) !== existing.required ||
option.choices?.length !== existing.choices?.length || option.choices?.length !== existing.choices?.length ||
option.options?.length !== existing.options?.length || option.options?.length !== existing.options?.length ||
(option.channelTypes ?? option.channel_types)?.length !== existing.channelTypes?.length || (option.channelTypes ?? option.channel_types)?.length !==
existing.channelTypes?.length ||
(option.minValue ?? option.min_value) !== existing.minValue || (option.minValue ?? option.min_value) !== existing.minValue ||
(option.maxValue ?? option.max_value) !== existing.maxValue (option.maxValue ?? option.max_value) !== existing.maxValue
) { ) {
@ -313,13 +340,17 @@ class ApplicationCommand extends Base {
if ( if (
enforceOptionOrder && enforceOptionOrder &&
!existing.choices.every( !existing.choices.every(
(choice, index) => choice.name === option.choices[index].name && choice.value === option.choices[index].value, (choice, index) =>
choice.name === option.choices[index].name &&
choice.value === option.choices[index].value,
) )
) { ) {
return false; return false;
} }
if (!enforceOptionOrder) { if (!enforceOptionOrder) {
const newChoices = new Map(option.choices.map(choice => [choice.name, choice])); const newChoices = new Map(
option.choices.map((choice) => [choice.name, choice]),
);
for (const choice of existing.choices) { for (const choice of existing.choices) {
const foundChoice = newChoices.get(choice.name); const foundChoice = newChoices.get(choice.name);
if (!foundChoice || foundChoice.value !== choice.value) return false; if (!foundChoice || foundChoice.value !== choice.value) return false;
@ -328,8 +359,8 @@ class ApplicationCommand extends Base {
} }
if (existing.channelTypes) { if (existing.channelTypes) {
const newTypes = (option.channelTypes ?? option.channel_types).map(type => const newTypes = (option.channelTypes ?? option.channel_types).map(
typeof type === 'number' ? ChannelTypes[type] : type, (type) => (typeof type === 'number' ? ChannelTypes[type] : type),
); );
for (const type of existing.channelTypes) { for (const type of existing.channelTypes) {
if (!newTypes.includes(type)) return false; if (!newTypes.includes(type)) return false;
@ -337,7 +368,11 @@ class ApplicationCommand extends Base {
} }
if (existing.options) { if (existing.options) {
return this.optionsEqual(existing.options, option.options, enforceOptionOrder); return this.optionsEqual(
existing.options,
option.options,
enforceOptionOrder,
);
} }
return true; return true;
} }
@ -373,22 +408,33 @@ class ApplicationCommand extends Base {
* @private * @private
*/ */
static transformOption(option, received) { static transformOption(option, received) {
const stringType = typeof option.type === 'string' ? option.type : ApplicationCommandOptionTypes[option.type]; const stringType =
typeof option.type === 'string'
? option.type
: ApplicationCommandOptionTypes[option.type];
const channelTypesKey = received ? 'channelTypes' : 'channel_types'; const channelTypesKey = received ? 'channelTypes' : 'channel_types';
const minValueKey = received ? 'minValue' : 'min_value'; const minValueKey = received ? 'minValue' : 'min_value';
const maxValueKey = received ? 'maxValue' : 'max_value'; const maxValueKey = received ? 'maxValue' : 'max_value';
return { return {
type: typeof option.type === 'number' && !received ? option.type : ApplicationCommandOptionTypes[option.type], type:
typeof option.type === 'number' && !received
? option.type
: ApplicationCommandOptionTypes[option.type],
name: option.name, name: option.name,
description: option.description, description: option.description,
required: required:
option.required ?? (stringType === 'SUB_COMMAND' || stringType === 'SUB_COMMAND_GROUP' ? undefined : false), option.required ??
(stringType === 'SUB_COMMAND' || stringType === 'SUB_COMMAND_GROUP'
? undefined
: false),
autocomplete: option.autocomplete, autocomplete: option.autocomplete,
choices: option.choices, choices: option.choices,
options: option.options?.map(o => this.transformOption(o, received)), options: option.options?.map((o) => this.transformOption(o, received)),
[channelTypesKey]: received [channelTypesKey]: received
? option.channel_types?.map(type => ChannelTypes[type]) ? option.channel_types?.map((type) => ChannelTypes[type])
: option.channelTypes?.map(type => (typeof type === 'string' ? ChannelTypes[type] : type)) ?? : option.channelTypes?.map((type) =>
typeof type === 'string' ? ChannelTypes[type] : type,
) ??
// When transforming to API data, accept API data // When transforming to API data, accept API data
option.channel_types, option.channel_types,
[minValueKey]: option.minValue ?? option.min_value, [minValueKey]: option.minValue ?? option.min_value,
@ -409,26 +455,66 @@ class ApplicationCommand extends Base {
*/ */
async sendSlashCommand(message, options = []) { async sendSlashCommand(message, options = []) {
// Check Options // Check Options
if (!message instanceof Message) throw new TypeError('The message must be a Discord.Message'); if (!message instanceof Message)
if (!Array.isArray(options)) throw new TypeError('The options must be an array of strings'); 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') return false; if (this.type !== 'CHAT_INPUT') return false;
const optionFormat = []; const optionFormat = [];
let option_ = [];
let i = 0; let i = 0;
for (i; i < options.length ; i++) { // 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]);
option_[0] = {
type: ApplicationCommandOptionTypes[subCommand.type],
name: subCommand.name,
options: optionFormat,
};
} else {
option_ = optionFormat;
}
for (i; i < options.length; i++) {
const value = options[i]; const value = options[i];
if (typeof value !== 'string') { if (typeof value !== 'string') {
throw new TypeError(`Expected option to be a String, got ${typeof value}`); throw new TypeError(
`Expected option to be a String, got ${typeof value}`,
);
} }
if (!this.options[i]) continue; if (!subCommandCheck && !this.options[i]) continue;
if (subCommandCheck && !subCommand.options[i]) continue;
if (!subCommandCheck) {
const data = { const data = {
type: ApplicationCommandOptionTypes[this.options[i].type], type: ApplicationCommandOptionTypes[this.options[i].type],
name: this.options[i].name, name: this.options[i].name,
value: value, value: this.options[i].type == 'INTEGER' ? Number(value) : (this.options[i].type == 'BOOLEAN' ? Boolean(value) : value),
} };
optionFormat.push(data);
} else {
if (!options[i + 1]) continue;
const data = {
type: ApplicationCommandOptionTypes[subCommand.options[i].type],
name: subCommand.options[i].name,
value:
subCommand.options[i].type == 'INTEGER'
? Number(options[i + 1])
: subCommand.options[i].type == 'BOOLEAN'
? Boolean(options[i + 1])
: options[i + 1],
};
optionFormat.push(data); optionFormat.push(data);
} }
if (this.options[i]?.required) throw new Error('Value required missing'); }
await this.client.api.interactions.post({ body: { if (!subCommandCheck && this.options[i]?.required)
throw new Error('Value required missing');
if (subCommandCheck && subCommand.options[i-1]?.required)
throw new Error('Value required missing');
await this.client.api.interactions.post({
body: {
type: 2, // ??? type: 2, // ???
application_id: this.applicationId, application_id: this.applicationId,
guild_id: message.guildId, guild_id: message.guildId,
@ -440,9 +526,10 @@ class ApplicationCommand extends Base {
id: this.id, id: this.id,
name: this.name, name: this.name,
type: ApplicationCommandTypes[this.type], type: ApplicationCommandTypes[this.type],
options: optionFormat, options: option_,
}, },
}}) },
});
return true; return true;
} }
/** /**
@ -457,9 +544,11 @@ class ApplicationCommand extends Base {
* await command.sendContextMenu(messsage); * await command.sendContextMenu(messsage);
*/ */
async sendContextMenu(message, sendFromMessage = false) { async sendContextMenu(message, sendFromMessage = false) {
if (!message instanceof Message && !sendFromMessage) throw new TypeError('The message must be a Discord.Message'); if (!message instanceof Message && !sendFromMessage)
throw new TypeError('The message must be a Discord.Message');
if (this.type == 'CHAT_INPUT') return false; if (this.type == 'CHAT_INPUT') return false;
await this.client.api.interactions.post({ body: { await this.client.api.interactions.post({
body: {
type: 2, // ??? type: 2, // ???
application_id: this.applicationId, application_id: this.applicationId,
guild_id: message.guildId, guild_id: message.guildId,
@ -471,9 +560,13 @@ class ApplicationCommand extends Base {
id: this.id, id: this.id,
name: this.name, name: this.name,
type: ApplicationCommandTypes[this.type], type: ApplicationCommandTypes[this.type],
target_id: ApplicationCommandTypes[this.type] == 1 ? message.author.id : message.id, target_id:
ApplicationCommandTypes[this.type] == 1
? message.author.id
: message.id,
}, },
}}) },
});
return true; return true;
} }
} }