2 Commits

Author SHA1 Message Date
5fda9afb83 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
2025-12-13 18:47:40 +01:00
CL TheDreWen
3d2a1125b3 Update Dockerfile 2025-11-28 14:13:42 +01:00
5 changed files with 90 additions and 48 deletions

View File

@@ -5,8 +5,8 @@ WORKDIR /app
COPY . . COPY . .
RUN apt-get update && apt-get install -y iputils-ping RUN apt-get update && apt-get install -y iputils-ping
RUN npm i RUN npm clean-install
# RUN npm run register RUN npm run register
CMD [ "npm", "run", "start" ] CMD [ "npm", "run", "start" ]

View File

@@ -1,50 +1,67 @@
// import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js";
// import { CommandDefinition } from "../../type"; import { CommandDefinition } from "../../type";
// import { AppDataSource } from "../../data-source"; import { AppDataSource } from "../../data-source";
// import { Follow } from "../../entity/follow.entity"; import { Follow } from "../../entity/follow.entity";
// import statusService from "../../services/status.service"; import statusService from "../../services/status.service";
// const cmd: CommandDefinition = { const cmd: CommandDefinition = {
// data: new SlashCommandBuilder() data: new SlashCommandBuilder()
// .setName('follow') .setName('follow')
// .setDescription('Enables/disables the receipt of service status notifications.') .setDescription('Enables/disables the receipt of service status notifications.')
// .setIntegrationTypes( .addStringOption((o) =>
// ApplicationIntegrationType.UserInstall o.setName('service')
// ) .setDescription('Select a service to follow')
// .setContexts( .setRequired(true)
// InteractionContextType.BotDM, .setAutocomplete(true)
// InteractionContextType.Guild, )
// InteractionContextType.PrivateChannel .setIntegrationTypes(
// ), ApplicationIntegrationType.UserInstall
// async execute(interaction: ChatInputCommandInteraction) { )
// const userRepo = AppDataSource.getRepository(Follow); .setContexts(
// const hostvalue = interaction.options.getString('host'); InteractionContextType.BotDM,
InteractionContextType.Guild,
InteractionContextType.PrivateChannel
),
async execute(interaction: ChatInputCommandInteraction) {
const userRepo = AppDataSource.getRepository(Follow);
const hostvalue = interaction.options.getString('service');
// const services = await statusService.serviceRepo.find(); const services = await statusService.serviceRepo.find();
// const realHost = services.find((v) => v.notify && v.host == hostvalue); const realHost = services.find((v) => v.notify && v.name == hostvalue);
// if (!hostvalue || !realHost) { if (!hostvalue || !realHost) {
// await interaction.reply({ content: '⚠️ Host not found !', flags: [MessageFlags.Ephemeral] }); await interaction.reply({ content: '⚠️ Host not found !', flags: [MessageFlags.Ephemeral] });
// } else { } else {
// let follow = await userRepo.findOne({ where: { user_discord: interaction.user.id, service: { id: realHost.id } } }); let follow = await userRepo.findOne({ where: { user_discord: interaction.user.id, service: { id: realHost.id } } });
// if (!follow) { if (!follow) {
// follow = new Follow(); follow = new Follow();
// follow.user_discord = interaction.user.id; follow.user_discord = interaction.user.id;
// follow.service = realHost; follow.service = realHost;
// await userRepo.save(follow); await userRepo.save(follow);
// } }
// follow.enable = !follow.enable; follow.enable = !follow.enable;
// await userRepo.save(follow); await userRepo.save(follow);
// await interaction.reply({ content: `✅ Notification successfully ${follow.enable ? 'enabled 🔔' : 'disabled 🔕'} for ${realHost.name}!`, flags: [MessageFlags.Ephemeral] }); await interaction.reply({ content: `✅ Notification successfully ${follow.enable ? 'enabled 🔔' : 'disabled 🔕'} for ${realHost.name}!`, flags: [MessageFlags.Ephemeral] });
// if (follow.enable) { if (follow.enable) {
// await interaction.user.send({ content: `🔔 Notifications have been successfully enabled for ${realHost.name} ! To disable: /follow host:${realHost.name}` }) 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) => {
// export default cmd; const services = await statusService.serviceRepo.find({where: {notify: true}});
interaction.respond(services.map((v) => ({name: v.name, value: v.name})));
}
}
]
}
export default cmd;

View File

@@ -2,6 +2,8 @@ import path from "path";
import fs from "fs"; import fs from "fs";
import { configDotenv } from "dotenv"; import { configDotenv } from "dotenv";
import { REST, Routes } from "discord.js"; import { REST, Routes } from "discord.js";
import "reflect-metadata";
import { AppDataSource } from "./data-source";
configDotenv(); configDotenv();
@@ -57,6 +59,10 @@ const rest = new REST().setToken(process.env.TOKEN);
(async () => { (async () => {
try { try {
// Initialize DataSource first
await AppDataSource.initialize();
console.log("Data Source initialized for command deployment!");
console.log(`Started refreshing ${commands.length} application (/) commands.`); console.log(`Started refreshing ${commands.length} application (/) commands.`);
const data = await rest.put( const data = await rest.put(
@@ -65,6 +71,10 @@ const rest = new REST().setToken(process.env.TOKEN);
) as any[]; ) as any[];
console.log(`Successfully reloaded ${data.length} application (/) commands.`); console.log(`Successfully reloaded ${data.length} application (/) commands.`);
// Close the connection
await AppDataSource.destroy();
process.exit(0);
} catch (error) { } catch (error) {
console.error('[ERROR] Failed to deploy commands:', error); console.error('[ERROR] Failed to deploy commands:', error);
process.exit(1); process.exit(1);

View File

@@ -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; return;
} }

4
src/type.d.ts vendored
View File

@@ -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 InfraType = 'website' | 'ryzen' | 'xeon' | 'games' | 'router';
export type CommandDefinition = { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[]}; export type CommandDefinition = { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[], autocompletes?: {name: string, execute: (interaction: AutocompleteInteraction) => void}[]};