Compare commits

..

22 Commits

Author SHA1 Message Date
1336ee7d0d igore DMs and split content by words 2024-04-05 14:17:29 +02:00
e2ef65203e prepare messageCreate event 2024-04-05 13:51:58 +02:00
272525ad9c force lowercase response names for case insensitiviy 2024-04-05 13:51:14 +02:00
089ccade66 clean up: consistency 2024-04-05 11:56:24 +02:00
c666f5bf8d implement and categorized info methods and completion 2024-04-05 11:47:50 +02:00
904c33c2f8 bugfix: abort if not found 2024-04-05 11:47:37 +02:00
765e80071a use shared method for keyword autocomplete 2024-04-05 11:36:33 +02:00
1d5d22dd5f optional parameter to pass focused value 2024-04-05 11:33:13 +02:00
03fd826952 acknowledge with reply 2024-04-05 11:32:31 +02:00
54dc8bd245 implement removal methods 2024-04-05 11:19:12 +02:00
d10dd2da00 autocomplete remove categories 2024-04-05 11:12:43 +02:00
ab53486ab3 bugfix: find responses only by keyword 2024-04-05 11:12:16 +02:00
0688dad57b abort submit if response name is taken 2024-04-05 10:57:32 +02:00
b812d0e74d categorize remove operations 2024-04-05 10:54:49 +02:00
2586ffb155 respond to modalSubmit event 2024-04-05 10:52:53 +02:00
fb17c4d10b bugfix: adjusted parameters accordingly 2024-04-05 10:52:20 +02:00
601c9db54f bugfix: display name instead of value 2024-04-05 10:51:36 +02:00
370361f9d3 'none found' fallback response 2024-04-05 10:51:17 +02:00
7f39be34ad bugfix: find responses by keyword 2024-04-05 10:51:01 +02:00
9b4d5a1d02 bugfix: adjusted parameters accordingly 2024-04-05 10:31:06 +02:00
7672748ac8 bugfix: adjusted relative paths from major refactor 2024-04-05 10:30:41 +02:00
aed1d8472e properly extracting parameters 2024-04-05 10:16:59 +02:00
10 changed files with 275 additions and 91 deletions

View File

@ -4,7 +4,7 @@ import {
SlashCommandBuilder,
ChatInputCommandInteraction
} from 'discord.js';
import { Guilds, VoiceChannels } from '../../../database.js';
import { Guilds, VoiceChannels } from '../../database.js';
export const data = new SlashCommandBuilder()
.setName('custom_vc')

View File

@ -1,5 +1,5 @@
import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
import { Roles, Guilds } from '../../../database.js';
import { Roles, Guilds } from '../../database.js';
/**
* @param {Guilds} guild

View File

@ -115,34 +115,67 @@ async function addResponse(interaction) {
await interaction.showModal(modal);
}
/** @param {ChatInputCommandInteraction} interaction */
async function removeKeyword(interaction) {
const { options } = interaction;
// Get command options
const keyword = options.getString('name');
// Try deleting keyword from database
await Keywords.destroy({
where: {
guild: interaction.guildId,
name: keyword
}
});
// Reply with success
await interaction.reply({
content: `Keyword '${keyword}' was successfully deleted!`,
ephemeral: true
});
}
/** @param {ChatInputCommandInteraction} interaction */
async function removeResponse(interaction) {
const { options } = interaction;
// Get command options
/** @type {'keyword'|'response'} */
const type = options.getString('type');
const keyword = options.getString('keyword');
const name = options.getString('name');
switch (type) {
case 'keyword':
// Try removing keyword
await Keywords.destroy({
// Find keyword in database
/** @type {import('../../models/keywords.js').Keyword|null} */
const found = await Keywords.findOne({
where: {
guild: interaction.guildId,
name
name: keyword
}
});
break;
case 'response':
// Try removing response
// Abort if keyword not found
if (found === null) {
await interaction.reply({
content: 'Unknown keyword was specified!',
ephemeral: true
});
return;
}
// Try deleting response from database
await Responses.destroy({
where: {
guild: interaction.guildId,
keyword: found.id,
name
}
});
}
// Reply with success
await interaction.reply({
content: `Response with name '${name}' was successfully deleted!`,
ephemeral: true
});
}
/** @param {ChatInputCommandInteraction} interaction */
@ -175,11 +208,62 @@ async function listResponse(interaction) {
}
/** @param {ChatInputCommandInteraction} interaction */
async function infoResponse(interaction) {
async function responseInfos(interaction) {
const { options } = interaction;
// Get command options
const keyword = options.getString('keyword');
const name = options.getString('name');
// 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;
}
// Find response in database
/** @type {import('../../models/responses.js').Response|null} */
const response = await Responses.findOne({
where: {
keyword: found.id,
name
}
});
// Abort if response not found
if (response === null) {
await interaction.reply({
content: 'Unknown response was specified!',
ephemeral: true
});
return;
}
// Reply with success
await interaction.reply({
content: `Response with name '${name}' has data of \`${response.response}\`!`,
ephemeral: true
});
}
/** @param {ChatInputCommandInteraction} interaction */
async function keywordInfos(interaction) {
const { options } = interaction;
// Get command options
const keyword = options.getString('name');
// Find keyword in database
/** @type {import('../../models/keywords.js').Keyword|null} */
@ -203,13 +287,21 @@ async function infoResponse(interaction) {
/** @type {import('../../models/responses.js').Response[]} */
const responses = await Responses.findAll({
where: {
guild: interaction.guildId,
name: found.id
keyword: found.id
}
});
// Abort if no responses registered
if (responses.length === 0) {
await interaction.reply({
content: 'No responses have been registered yet!',
ephemeral: true
});
return;
}
// Join list of responses
const joined = responses.map((response) => response.response).join('\n- ');
const joined = responses.map((response) => response.name).join('\n- ');
// Reply with list of responses
await interaction.reply({
@ -218,11 +310,13 @@ async function infoResponse(interaction) {
});
}
/**
* @param {string} guildId
* @param {string} focused
*/
async function keywordAutocomplete(guildId, focused) {
/** @param {AutocompleteInteraction} interaction */
async function keywordAutocomplete(interaction, focused) {
const { options, guildId } = interaction;
// Get command options
if (!focused) focused = options.getFocused();
// Get list of keywords from database
/** @type {import('../../models/keywords.js').Keyword[]} */
const keywords = await Keywords.findAll({
@ -239,11 +333,15 @@ async function keywordAutocomplete(guildId, focused) {
}
/**
* @param {string} guildId
* @param {AutocompleteInteraction} interaction
* @param {string} focused
* @param {string} keyword
*/
async function responseAutocomplete(guildId, focused, keyword) {
async function completeResponses(interaction, focused) {
const { options, guildId } = interaction;
// Get command options
const keyword = options.getString('keyword');
// Get keyword
/** @type {import('../../models/keywords.js').Keyword} */
const found = await Keywords.findOne({
@ -260,7 +358,6 @@ async function responseAutocomplete(guildId, focused, keyword) {
? []
: await Responses.findAll({
where: {
guild: guildId,
keyword: found.id
}
});
@ -269,27 +366,22 @@ async function responseAutocomplete(guildId, focused, keyword) {
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 }))
);
await interaction.respond(filtered.map((choice) => ({ name: choice.name, value: choice.name })));
}
/** @param {AutocompleteInteraction} interaction */
async function removeAutocomplete(interaction) {
async function responseAutocomplete(interaction) {
const { options } = interaction;
const type = options.getString('type');
switch (type) {
// Get command options
const focused = options.getFocused(true);
const { name, value } = focused;
switch (name) {
case 'keyword':
await keywordAutocomplete(interaction.guildId, options.getFocused());
keywordAutocomplete(interaction, value);
break;
case 'response':
await responseAutocomplete(
interaction.guildId,
options.getFocused(),
options.getString('keyword')
);
case 'name':
completeResponses(interaction, value);
break;
}
}
@ -322,20 +414,33 @@ export const data = new SlashCommandBuilder()
.setRequired(true)
)
)
.addSubcommandGroup((group) =>
group
.setName('remove')
.setDescription('Unregisters a response or a keyword.')
.addSubcommand((subcommand) =>
subcommand
.setName('remove')
.setDescription('Unregisters a response to a keyword.')
.setName('keyword')
.setDescription('Deletes a keyword completely.')
.addStringOption((option) =>
option
.setName('type')
.setDescription('The type of data to be removed.')
.setName('name')
.setDescription('The keyword to be deleted.')
.setAutocomplete(true)
.setRequired(true)
.addChoices(
{ name: 'Keyword', value: 'keyword' },
{ name: 'Response', value: 'response' }
)
)
.addSubcommand((subcommand) =>
subcommand
.setName('response')
.setDescription('Unregisters a response of a keyword.')
.addStringOption((option) =>
option
.setName('keyword')
.setDescription('The keyword that would trigger the response.')
.setAutocomplete(true)
.setRequired(true)
)
.addStringOption((option) =>
option
.setName('name')
@ -344,29 +449,54 @@ export const data = new SlashCommandBuilder()
.setRequired(true)
)
)
)
.addSubcommand((subcommand) =>
subcommand.setName('list').setDescription('Lists all registered keywords.')
)
.addSubcommandGroup((group) =>
group
.setName('info')
.setDescription('Lists information about a response or a keyword.')
.addSubcommand((subcommand) =>
subcommand
.setName('info')
.setDescription('Shows responses of a registered keyword.')
.setName('keyword')
.setDescription('Lists registered responses of a keyword.')
.addStringOption((option) =>
option
.setName('name')
.setDescription('The keyword to be shown the details of.')
.setAutocomplete(true)
.setRequired(true)
)
)
.addSubcommand((subcommand) =>
subcommand
.setName('response')
.setDescription('Lists the data being sent by a response.')
.addStringOption((option) =>
option
.setName('keyword')
.setDescription('The keyword to show the details of.')
.setDescription('The keyword that would trigger the response.')
.setAutocomplete(true)
.setRequired(true)
)
.addStringOption((option) =>
option
.setName('name')
.setDescription('The name of the data to be listed.')
.setAutocomplete(true)
.setRequired(true)
)
)
);
/** @param {ModalSubmitInteraction} interaction */
export async function modalSubmit(interaction) {
const { fields } = interaction;
// Get text inputs from modal
const name = fields.getTextInputValue('name');
const keyword = fields.getTextInputValue('keyword');
const response = fields.getTextInputValue('response');
const name = fields.getTextInputValue('name').toLowerCase();
// Get id of keyword
/** @type {import('../../models/keywords.js').Keyword} */
@ -376,20 +506,50 @@ export async function modalSubmit(interaction) {
}
});
// Abort if response exists
if (
(await Responses.findOne({
where: {
keyword: found.id,
name
}
})) !== null
) {
await interaction.reply({
content: `Response with name '${name}' already exists!`,
ephemeral: true
});
return;
}
// Create new response data with keyword attached
await Responses.create({ keyword: found.id, name, response });
// Reply with success
await interaction.reply({
content: `Successfully registered '${name}' as response to '${keyword}'!`,
ephemeral: true
});
}
/** @param {AutocompleteInteraction} interaction */
export async function autocomplete(interaction) {
const { options } = interaction;
switch (options.getSubcommand()) {
case 'remove':
removeAutocomplete(interaction);
const command = options.getSubcommand();
const group = options.getSubcommandGroup();
const joined = group === null ? command : `${group} ${command}`;
switch (joined) {
case 'info keyword':
case 'remove keyword':
await keywordAutocomplete(interaction);
break;
case 'info response':
case 'remove response':
await responseAutocomplete(interaction);
break;
case 'add':
case 'info':
keywordAutocomplete(interaction.guildId, interaction.options.getFocused());
keywordAutocomplete(interaction);
break;
}
}
@ -397,21 +557,31 @@ export async function autocomplete(interaction) {
export async function execute(interaction) {
const { options } = interaction;
switch (options.getSubcommand()) {
const command = options.getSubcommand();
const group = options.getSubcommandGroup();
const joined = group === null ? command : `${group} ${command}`;
switch (joined) {
case 'create':
createResponse(interaction);
break;
case 'add':
addResponse(interaction);
break;
case 'remove':
case 'remove keyword':
removeKeyword(interaction);
break;
case 'remove response':
removeResponse(interaction);
break;
case 'list':
listResponse(interaction);
break;
case 'info':
infoResponse(interaction);
case 'info keyword':
keywordInfos(interaction);
break;
case 'info response':
responseInfos(interaction);
break;
}
}

View File

@ -9,7 +9,7 @@ import {
ContextMenuCommandBuilder,
ContextMenuCommandInteraction
} from 'discord.js';
import { addSelfRoles } from '../../../../shared.js';
import { addSelfRoles } from '../../../shared.js';
export const data = new ContextMenuCommandBuilder()
.setDMPermission(false)

View File

@ -4,7 +4,7 @@ import {
PermissionFlagsBits,
ContextMenuCommandInteraction
} from 'discord.js';
import { Messages } from '../../../../database.js';
import { Messages } from '../../../database.js';
export const data = new ContextMenuCommandBuilder()
.setDMPermission(false)

View File

@ -4,7 +4,7 @@ import {
PermissionFlagsBits,
ContextMenuCommandInteraction
} from 'discord.js';
import { removeSelfRoles } from '../../../../shared.js';
import { removeSelfRoles } from '../../../shared.js';
export const data = new ContextMenuCommandBuilder()
.setDMPermission(false)

View File

@ -1,6 +1,6 @@
import { PermissionFlagsBits, SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import { addSelfRoles, removeSelfRoles } from '../../../shared.js';
import { Guilds, Messages } from '../../../database.js';
import { addSelfRoles, removeSelfRoles } from '../../shared.js';
import { Guilds, Messages } from '../../database.js';
/**
* Sends a `Message` in the current channel and registers for self roles.

View File

@ -9,7 +9,7 @@ import Module from 'module';
const executeCommand = async (interaction, command) => {
// Try executing command
try {
console.info(`[INFO] Command ${interaction.commandName} was executed.`);
console.info(`[INFO] Command '${interaction.commandName}' was executed.`);
await command.execute(interaction);
} catch (error) {
console.error(error);
@ -38,7 +38,7 @@ const executeCommand = async (interaction, command) => {
const genericExecute = async (interaction, command, name, description, cmdName) => {
try {
console.info(
`[INFO] Command ${cmdName ?? interaction.commandName ?? 'anonymous'} ${
`[INFO] Command '${cmdName ?? interaction.commandName ?? 'anonymous'}' ${
description ?? `used "${name}"`
}.`
);

View File

@ -0,0 +1,13 @@
import { Keywords, Responses, sequelize } from '../../database.js';
import { Events, Message } from 'discord.js';
import { Op } from 'sequelize';
export const name = Events.MessageCreate;
/** @param {Message} message */
export async function execute(message) {
// Ignore direct messages
if (!message.inGuild()) return;
// Split message content into words
const words = message.content.split(/\s+/);
}

View File

@ -19,6 +19,7 @@ const runClient = (commands, events) => {
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessageReactions
],