DiscordJS-Example/commands/response/slash.js

402 lines
9.6 KiB
JavaScript

import {
ModalBuilder,
TextInputBuilder,
ActionRowBuilder,
SlashCommandBuilder,
PermissionFlagsBits,
ModalSubmitInteraction,
AutocompleteInteraction,
ChatInputCommandInteraction,
TextInputStyle
} from 'discord.js';
import { Guilds, Keywords, Responses } from '../../database.js';
/** @param {ChatInputCommandInteraction} interaction */
async function createResponse(interaction) {
const { options } = interaction;
// Get command options
const keyword = options.getString('keyword');
// Abort if keyword already exists or is empty
/** @type {import('../../models/keywords.js').Keyword|null} */
const found = await Keywords.findOne({
where: {
guild: interaction.guildId,
name: keyword
}
});
// Reply with error
if (found !== null || !keyword) {
await interaction.reply({
content: 'Invalid parameters or keyword already exists!',
ephemeral: true
});
return;
}
// Create guild if not exists
const guildData = { id: interaction.guildId };
await Guilds.findOrCreate({
where: guildData,
defaults: guildData
});
// Create new keyword
await Keywords.create({
guild: interaction.guildId,
name: keyword
});
// Reply successfully to acknowledge command
await interaction.reply({
content: `Keyword for '${keyword}' successfully created!`,
ephemeral: true
});
console.info(`[INFO] Keyword for '${keyword}' successfully created.`);
}
/** @param {ChatInputCommandInteraction} interaction */
async function addResponse(interaction) {
const { options } = interaction;
// Get command options
const current = options.getString('keyword');
// Abort if keyword doesn't exist
/** @type {import('../../models/keywords.js').Keyword|null} */
const found = await Keywords.findOne({
where: {
guild: interaction.guildId,
name: current
}
});
// Reply with error
if (found === null) {
await interaction.reply({
content: 'Keyword has not been registered yet!',
ephemeral: true
});
return;
}
const modal = new ModalBuilder().setCustomId('response-pair').setTitle('Response Content');
const keyword = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setLabel('The keyword this command is run for.')
.setStyle(TextInputStyle.Short)
.setCustomId('keyword')
.setRequired(true)
.setValue(current)
);
const name = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setLabel('The name of the response.')
.setStyle(TextInputStyle.Short)
.setCustomId('name')
.setRequired(true)
);
const response = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setLabel('The data to respond with.')
.setStyle(TextInputStyle.Paragraph)
.setCustomId('response')
.setRequired(true)
);
modal.addComponents(keyword, name, response);
await interaction.showModal(modal);
}
/** @param {ChatInputCommandInteraction} interaction */
async function removeResponse(interaction) {
const { options } = interaction;
// Get command options
/** @type {'keyword'|'response'} */
const type = options.getString('type');
const name = options.getString('name');
switch (type) {
case 'keyword':
// Try removing keyword
await Keywords.destroy({
where: {
guild: interaction.guildId,
name
}
});
break;
case 'response':
// Try removing response
await Responses.destroy({
where: {
guild: interaction.guildId,
name
}
});
}
}
/** @param {ChatInputCommandInteraction} interaction */
async function listResponse(interaction) {
// Get list of keywords from database
/** @type {import('../../models/keywords.js').Keyword[]} */
const keywords = await Keywords.findAll({
where: {
guild: interaction.guildId
}
});
// Abort if no keywords registered
if (keywords.length === 0) {
await interaction.reply({
content: 'No keywords have been registered yet!',
ephemeral: true
});
return;
}
// Join list of keyword names
const joined = keywords.map((keyword) => keyword.name).join('\n- ');
// Reply with list of keywords
await interaction.reply({
content: `List of known keywords:\n- ${joined}`,
ephemeral: true
});
}
/** @param {ChatInputCommandInteraction} interaction */
async function infoResponse(interaction) {
const { options } = interaction;
// Get command options
const keyword = options.getString('keyword');
// Find keyword in database
/** @type {import('../../models/keywords.js').Keyword|null} */
const found = await Keywords.findOne({
where: {
guild: interaction.guildId,
name: keyword
}
});
// Abort if keyword not found
if (found === null) {
await interaction.reply({
content: 'Unknown keyword was specified!',
ephemeral: true
});
return;
}
// Get list of responses from database
/** @type {import('../../models/responses.js').Response[]} */
const responses = await Responses.findAll({
where: {
guild: interaction.guildId,
name: found.id
}
});
// Join list of responses
const joined = responses.map((response) => response.response).join('\n- ');
// Reply with list of responses
await interaction.reply({
content: `List of responses for ${keyword}:\n- ${joined}`,
ephemeral: true
});
}
/**
* @param {string} guildId
* @param {string} focused
*/
async function keywordAutocomplete(guildId, focused) {
// Get list of keywords from database
/** @type {import('../../models/keywords.js').Keyword[]} */
const keywords = await Keywords.findAll({
where: {
guild: guildId
}
});
// Filter total list of keywords
const filtered = keywords.filter((choice) => choice.name.startsWith(focused));
// Respond with possible suggestions
await interaction.respond(filtered.map((choice) => ({ name: choice.name, value: choice.name })));
}
/**
* @param {string} guildId
* @param {string} focused
* @param {string} keyword
*/
async function responseAutocomplete(guildId, focused, keyword) {
// Get keyword
/** @type {import('../../models/keywords.js').Keyword} */
const found = await Keywords.findOne({
where: {
guild: guildId,
name: keyword
}
});
// Get list of responses from database
/** @type {import('../../models/responses.js').Response[]} */
const responses =
found === null
? []
: await Responses.findAll({
where: {
guild: guildId,
keyword: found.id
}
});
// Filter total list of responses
const filtered = responses.filter((choice) => choice.name.startsWith(focused));
// Respond with possible suggestions
await interaction.respond(
filtered.map((choice) => ({ name: choice.name, value: choice.response }))
);
}
/** @param {AutocompleteInteraction} interaction */
async function removeAutocomplete(interaction) {
const { options } = interaction;
const type = options.getString('type');
switch (type) {
case 'keyword':
await keywordAutocomplete(interaction.guildId, options.getFocused());
break;
case 'response':
await responseAutocomplete(
interaction.guildId,
options.getFocused(),
options.getString('keyword')
);
break;
}
}
export const data = new SlashCommandBuilder()
.setName('response')
.setDMPermission(false)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.setDescription('Event based responses to specific messages with keywords.')
.addSubcommand((subcommand) =>
subcommand
.setName('create')
.setDescription('Creates a new event based response.')
.addStringOption((option) =>
option
.setName('keyword')
.setDescription('The keyword to trigger the response.')
.setRequired(true)
)
)
.addSubcommand((subcommand) =>
subcommand
.setName('add')
.setDescription('Registers a response to a keyword.')
.addStringOption((option) =>
option
.setName('keyword')
.setDescription('The keyword to trigger the response.')
.setAutocomplete(true)
.setRequired(true)
)
)
.addSubcommand((subcommand) =>
subcommand
.setName('remove')
.setDescription('Unregisters a response to a keyword.')
.addStringOption((option) =>
option
.setName('type')
.setDescription('The type of data to be removed.')
.setRequired(true)
.addChoices(
{ name: 'Keyword', value: 'keyword' },
{ name: 'Response', value: 'response' }
)
)
.addStringOption((option) =>
option
.setName('name')
.setDescription('The name of the data to be removed.')
.setAutocomplete(true)
.setRequired(true)
)
)
.addSubcommand((subcommand) =>
subcommand.setName('list').setDescription('Lists all registered keywords.')
)
.addSubcommand((subcommand) =>
subcommand
.setName('info')
.setDescription('Shows responses of a registered keyword.')
.addStringOption((option) =>
option
.setName('keyword')
.setDescription('The keyword to show the details of.')
.setAutocomplete(true)
.setRequired(true)
)
);
/** @param {ModalSubmitInteraction} interaction */
export async function modalSubmit(interaction) {
// Only executable in 'add' subcommand
}
/** @param {AutocompleteInteraction} interaction */
export async function autocomplete(interaction) {
const { options } = interaction;
switch (options.getSubcommand()) {
case 'remove':
removeAutocomplete(interaction);
break;
case 'add':
case 'info':
keywordAutocomplete(interaction.guildId, interaction.options.getFocused());
break;
}
}
/** @param {ChatInputCommandInteraction} interaction */
export async function execute(interaction) {
const { options } = interaction;
switch (options.getSubcommand()) {
case 'create':
createResponse(interaction);
break;
case 'add':
addResponse(interaction);
break;
case 'remove':
removeResponse(interaction);
break;
case 'list':
listResponse(interaction);
break;
case 'info':
infoResponse(interaction);
break;
}
}