import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js"; import { UpdateService } from "@shared/modules/admin/update.service"; import { getCheckingEmbed, getNoUpdatesEmbed, getUpdatesAvailableMessage, getPreparingEmbed, getUpdatingEmbed, getCancelledEmbed, getTimeoutEmbed, getErrorEmbed, getRollbackSuccessEmbed, getRollbackFailedEmbed } from "@/modules/admin/update.view"; export const update = createCommand({ data: new SlashCommandBuilder() .setName("update") .setDescription("Check for updates and restart the bot") .addSubcommand(sub => sub.setName("check") .setDescription("Check for and apply available updates") .addBooleanOption(option => option.setName("force") .setDescription("Force update even if no changes detected") .setRequired(false) ) ) .addSubcommand(sub => sub.setName("rollback") .setDescription("Rollback to the previous version") ) .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), execute: async (interaction) => { const subcommand = interaction.options.getSubcommand(); if (subcommand === "rollback") { await handleRollback(interaction); } else { await handleUpdate(interaction); } } }); async function handleUpdate(interaction: any) { await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const force = interaction.options.getBoolean("force") || false; try { // 1. Check for updates await interaction.editReply({ embeds: [getCheckingEmbed()] }); const updateInfo = await UpdateService.checkForUpdates(); if (!updateInfo.hasUpdates && !force) { await interaction.editReply({ embeds: [getNoUpdatesEmbed(updateInfo.currentCommit)] }); return; } // 2. Analyze requirements const requirements = await UpdateService.checkUpdateRequirements(updateInfo.branch); const categories = UpdateService.categorizeChanges(requirements.changedFiles); // 3. Show confirmation with details const { embeds, components } = getUpdatesAvailableMessage( updateInfo, requirements, categories, force ); const response = await interaction.editReply({ embeds, components }); // 4. Wait for confirmation try { const confirmation = await response.awaitMessageComponent({ filter: (i: any) => i.user.id === interaction.user.id, componentType: ComponentType.Button, time: 30000 }); if (confirmation.customId === "confirm_update") { await confirmation.update({ embeds: [getPreparingEmbed()], components: [] }); // 5. Save rollback point const previousCommit = await UpdateService.saveRollbackPoint(); // 6. Prepare restart context await UpdateService.prepareRestartContext({ channelId: interaction.channelId, userId: interaction.user.id, timestamp: Date.now(), runMigrations: requirements.needsMigrations, installDependencies: requirements.needsRootInstall || requirements.needsWebInstall, previousCommit: previousCommit.substring(0, 7), newCommit: updateInfo.latestCommit }); // 7. Show updating status await interaction.editReply({ embeds: [getUpdatingEmbed(requirements)] }); // 8. Perform update await UpdateService.performUpdate(updateInfo.branch); // 9. Trigger restart await UpdateService.triggerRestart(); } else { await confirmation.update({ embeds: [getCancelledEmbed()], components: [] }); } } catch (e) { if (e instanceof Error && e.message.includes("time")) { await interaction.editReply({ embeds: [getTimeoutEmbed()], components: [] }); } else { throw e; } } } catch (error) { console.error("Update failed:", error); await interaction.editReply({ embeds: [getErrorEmbed(error)], components: [] }); } } async function handleRollback(interaction: any) { await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { const hasRollback = await UpdateService.hasRollbackPoint(); if (!hasRollback) { await interaction.editReply({ embeds: [getRollbackFailedEmbed("No rollback point available. Rollback is only possible after a recent update.")] }); return; } const result = await UpdateService.rollback(); if (result.success) { await interaction.editReply({ embeds: [getRollbackSuccessEmbed(result.message.split(" ").pop() || "unknown")] }); // Restart after rollback setTimeout(() => UpdateService.triggerRestart(), 1000); } else { await interaction.editReply({ embeds: [getRollbackFailedEmbed(result.message)] }); } } catch (error) { console.error("Rollback failed:", error); await interaction.editReply({ embeds: [getErrorEmbed(error)] }); } }