From 5fda9afb83e7122496519d7a0afe276d8810500f Mon Sep 17 00:00:00 2001 From: thedrewen Date: Sat, 13 Dec 2025 18:47:40 +0100 Subject: [PATCH] feat(commands): implement follow command with service selection and autocomplete fix(deploy): initialize and destroy DataSource for command deployment fix(index): handle autocomplete interactions in command execution refactor(types): add autocomplete support to CommandDefinition type fix(Dockerfile): ensure npm run register is executed during build --- Dockerfile | 2 +- src/commands/utility/follow.command.ts | 103 ++++++++++++++----------- src/deploy-commands.ts | 10 +++ src/index.ts | 15 ++++ src/type.d.ts | 4 +- 5 files changed, 88 insertions(+), 46 deletions(-) diff --git a/Dockerfile b/Dockerfile index aa81a90..420ae70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,6 @@ COPY . . RUN apt-get update && apt-get install -y iputils-ping RUN npm clean-install -# RUN npm run register +RUN npm run register CMD [ "npm", "run", "start" ] diff --git a/src/commands/utility/follow.command.ts b/src/commands/utility/follow.command.ts index d7114e8..6805ad1 100644 --- a/src/commands/utility/follow.command.ts +++ b/src/commands/utility/follow.command.ts @@ -1,50 +1,67 @@ -// import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; -// import { CommandDefinition } from "../../type"; -// import { AppDataSource } from "../../data-source"; -// import { Follow } from "../../entity/follow.entity"; -// import statusService from "../../services/status.service"; +import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; +import { CommandDefinition } from "../../type"; +import { AppDataSource } from "../../data-source"; +import { Follow } from "../../entity/follow.entity"; +import statusService from "../../services/status.service"; -// const cmd: CommandDefinition = { -// data: new SlashCommandBuilder() -// .setName('follow') -// .setDescription('Enables/disables the receipt of service status notifications.') -// .setIntegrationTypes( -// ApplicationIntegrationType.UserInstall -// ) -// .setContexts( -// InteractionContextType.BotDM, -// InteractionContextType.Guild, -// InteractionContextType.PrivateChannel -// ), -// async execute(interaction: ChatInputCommandInteraction) { -// const userRepo = AppDataSource.getRepository(Follow); -// const hostvalue = interaction.options.getString('host'); - -// const services = await statusService.serviceRepo.find(); -// const realHost = services.find((v) => v.notify && v.host == hostvalue); +const cmd: CommandDefinition = { + data: new SlashCommandBuilder() + .setName('follow') + .setDescription('Enables/disables the receipt of service status notifications.') + .addStringOption((o) => + o.setName('service') + .setDescription('Select a service to follow') + .setRequired(true) + .setAutocomplete(true) + ) + .setIntegrationTypes( + ApplicationIntegrationType.UserInstall + ) + .setContexts( + InteractionContextType.BotDM, + InteractionContextType.Guild, + InteractionContextType.PrivateChannel + ), + async execute(interaction: ChatInputCommandInteraction) { + const userRepo = AppDataSource.getRepository(Follow); + const hostvalue = interaction.options.getString('service'); -// if (!hostvalue || !realHost) { -// await interaction.reply({ content: '⚠️ Host not found !', flags: [MessageFlags.Ephemeral] }); -// } else { -// let follow = await userRepo.findOne({ where: { user_discord: interaction.user.id, service: { id: realHost.id } } }); -// if (!follow) { -// follow = new Follow(); -// follow.user_discord = interaction.user.id; -// follow.service = realHost; -// await userRepo.save(follow); -// } + const services = await statusService.serviceRepo.find(); + const realHost = services.find((v) => v.notify && v.name == hostvalue); -// follow.enable = !follow.enable; + if (!hostvalue || !realHost) { + await interaction.reply({ content: '⚠️ Host not found !', flags: [MessageFlags.Ephemeral] }); + } else { + let follow = await userRepo.findOne({ where: { user_discord: interaction.user.id, service: { id: realHost.id } } }); + if (!follow) { + follow = new Follow(); + follow.user_discord = interaction.user.id; + follow.service = realHost; + await userRepo.save(follow); + } -// await userRepo.save(follow); + follow.enable = !follow.enable; -// await interaction.reply({ content: `✅ Notification successfully ${follow.enable ? 'enabled 🔔' : 'disabled 🔕'} for ${realHost.name}!`, flags: [MessageFlags.Ephemeral] }); + await userRepo.save(follow); -// if (follow.enable) { -// await interaction.user.send({ content: `🔔 Notifications have been successfully enabled for ${realHost.name} ! To disable: /follow host:${realHost.name}` }) -// } -// } -// } -// } + await interaction.reply({ content: `✅ Notification successfully ${follow.enable ? 'enabled 🔔' : 'disabled 🔕'} for ${realHost.name}!`, flags: [MessageFlags.Ephemeral] }); -// export default cmd; \ No newline at end of file + if (follow.enable) { + await interaction.user.send({ content: `🔔 Notifications have been successfully enabled for ${realHost.name} ! To disable: /follow host:${realHost.name}` }) + } + } + }, + autocompletes: [ + { + name: 'service', + execute: async (interaction) => { + + const services = await statusService.serviceRepo.find({where: {notify: true}}); + + interaction.respond(services.map((v) => ({name: v.name, value: v.name}))); + } + } + ] +} + +export default cmd; \ No newline at end of file diff --git a/src/deploy-commands.ts b/src/deploy-commands.ts index ced451e..1d88eb8 100644 --- a/src/deploy-commands.ts +++ b/src/deploy-commands.ts @@ -2,6 +2,8 @@ import path from "path"; import fs from "fs"; import { configDotenv } from "dotenv"; import { REST, Routes } from "discord.js"; +import "reflect-metadata"; +import { AppDataSource } from "./data-source"; configDotenv(); @@ -57,6 +59,10 @@ const rest = new REST().setToken(process.env.TOKEN); (async () => { try { + // Initialize DataSource first + await AppDataSource.initialize(); + console.log("Data Source initialized for command deployment!"); + console.log(`Started refreshing ${commands.length} application (/) commands.`); const data = await rest.put( @@ -65,6 +71,10 @@ const rest = new REST().setToken(process.env.TOKEN); ) as any[]; console.log(`Successfully reloaded ${data.length} application (/) commands.`); + + // Close the connection + await AppDataSource.destroy(); + process.exit(0); } catch (error) { console.error('[ERROR] Failed to deploy commands:', error); process.exit(1); diff --git a/src/index.ts b/src/index.ts index a368845..0016362 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,6 +60,21 @@ client.on(Events.InteractionCreate, async interaction => { } }); + return; + }else if(interaction.isAutocomplete()){ + const option = interaction.options.getFocused(true); + + commands.forEach((value) => { + if(value.autocompletes) { + const auto = value.autocompletes.filter((a) => a.name == option.name); + if(auto.length >= 1){ + auto.forEach((a) => { + a.execute(interaction) + }) + } + } + }); + return; } diff --git a/src/type.d.ts b/src/type.d.ts index 5b13484..498c123 100644 --- a/src/type.d.ts +++ b/src/type.d.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; +import { AutocompleteInteraction, ButtonInteraction, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; export type InfraType = 'website' | 'ryzen' | 'xeon' | 'games' | 'router'; -export type CommandDefinition = { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[]}; \ No newline at end of file +export type CommandDefinition = { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[], autocompletes?: {name: string, execute: (interaction: AutocompleteInteraction) => void}[]}; \ No newline at end of file