import { SlashCommandBuilder, EmbedBuilder, ChatInputCommandInteraction, } from 'discord.js'; import { listGameNames, getGame, setGame, deleteGame, deleteField, } from './utils/db/game'; // Initialise the command data. export const data = new SlashCommandBuilder() .setName('game') .setDescription('Perform ButlerBot game database operations.') .addSubcommand((subcommand) => subcommand.setName('list').setDescription('List all games in the database.') ) .addSubcommand((subcommand) => subcommand .setName('get') .setDescription('Get a game from the database.') .addStringOption((option) => option .setName('game') .setDescription('The name of the game to get.') .setRequired(true) ) ) .addSubcommand((subcommand) => subcommand .setName('set') .setDescription('Set a key value pair on a game in the database.') .addStringOption((option) => option .setName('game') .setDescription('The name of the game to set the field in.') .setRequired(true) ) .addStringOption((option) => option .setName('key') .setDescription('The key or label to set.') .setRequired(true) ) .addStringOption((option) => option .setName('value') .setDescription('The value of the field to set.') .setRequired(true) .setMinLength(1) .setMaxLength(1024) ) ) .addSubcommand((subcommand) => subcommand .setName('delete') .setDescription('Delete a game, or a field from a game.') .addStringOption((option) => option .setName('game') .setDescription( 'The name of the game to remove, or remove a field from.' ) .setRequired(true) ) .addStringOption((option) => option .setName('key') .setDescription('The key or label of the field to remove.') .setRequired(false) ) ); console.log(`Loaded ${data.name} command.`); /** * Handle the interaction for the game command. * @param interaction The interaction that triggered the command. * @returns A promise that resolves when the command is finished executing. */ export async function execute( interaction: ChatInputCommandInteraction ): Promise { await interaction.deferReply(); const subcommand = interaction.options.getSubcommand(); switch (subcommand) { case 'list': { const gameNames = await listGameNames(); if (gameNames.length === 0) { await interaction.editReply('No games found in the database.'); return; } const embed = new EmbedBuilder() .setTitle('Game Database') .setColor(0xff0000) .setDescription('List of games in the database.'); embed.addFields({ name: 'Games', value: gameNames.map((game) => game.game).join('\n'), }); await interaction.editReply({ embeds: [embed] }); break; } case 'get': { const gameName = interaction.options.getString('game', true); const game = await getGame(gameName); if (!game) { await interaction.editReply( `Game ${gameName} not found in the database.` ); return; } try { const embed = createEmbedFromGame(game); await interaction.editReply({ embeds: [embed] }); } catch (error) { await interaction.editReply( 'Embed too large to send. Game has not been retrieved.' ); } break; } case 'set': { const gameName = interaction.options.getString('game', true); const key = interaction.options.getString('key', true); const value = interaction.options.getString('value', true); const oldGame = await getGame(gameName); await setGame(gameName, key, value); const updatedGame = await getGame(gameName); try { const embed = createEmbedFromGame(updatedGame); await interaction.editReply({ embeds: [embed] }); } catch (error) { // Revert game back to original state if the embed is too large. if (oldGame) { // If key already existed, revert to old value, else delete the key. if (oldGame[key]) { await setGame(gameName, key, oldGame[key]); } else { await deleteField(gameName, key); } } await interaction.editReply( 'Embed too large to send. Game has not been updated.' ); } break; } case 'delete': { const gameName = interaction.options.getString('game', true); const key = interaction.options.getString('key'); if (key) { await deleteField(gameName, key); const updatedGame = await getGame(gameName); if (!updatedGame) { await interaction.editReply( `Game ${gameName} deleted from the database.` ); return; } try { const embed = createEmbedFromGame(updatedGame); await interaction.editReply({ embeds: [embed] }); } catch (error) { await interaction.editReply( 'Embed too large to send. Field deleted from game but game has not been retrieved.' ); } } else { await deleteGame(gameName); await interaction.editReply( `Game ${gameName} deleted from the database.` ); } break; } default: await interaction.editReply('Unknown subcommand.'); break; } } /** * Create an embed from a game object. * @param game The game object containing key-value pairs. * @returns The embed object to be sent in the interaction. */ function createEmbedFromGame(game: any): EmbedBuilder { const embed = new EmbedBuilder().setTitle(game.game).setColor(0xff0000); // Add fields for each key value pair - skip name, _id and guild. embed.addFields( Object.entries(game) .filter(([key]) => !['_id', 'game', 'guild'].includes(key)) .map(([key, value]) => ({ name: key, value: String(value) })) ); if (embed.length > 6000) { throw new Error('Embed size exceeds maximum.'); } return embed; }