forked from syntaxbullet/AuroraBot-discord
feat: split reload command into refresh for command reloading and update for git-based bot restarts with update checking and confirmation.
This commit is contained in:
33
src/commands/admin/refresh.ts
Normal file
33
src/commands/admin/refresh.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { createCommand } from "@lib/utils";
|
||||||
|
import { KyokoClient } from "@/lib/BotClient";
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||||
|
import { createErrorEmbed, createSuccessEmbed, createWarningEmbed } from "@lib/embeds";
|
||||||
|
|
||||||
|
export const refresh = createCommand({
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("refresh")
|
||||||
|
.setDescription("Reloads all commands and config without restarting")
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||||
|
execute: async (interaction) => {
|
||||||
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const start = Date.now();
|
||||||
|
await KyokoClient.loadCommands(true);
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
|
||||||
|
// Deploy commands
|
||||||
|
await KyokoClient.deployCommands();
|
||||||
|
|
||||||
|
const embed = createSuccessEmbed(
|
||||||
|
`Successfully reloaded ${KyokoClient.commands.size} commands in ${duration}ms.`,
|
||||||
|
"System Refreshed"
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.editReply({ embeds: [embed] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
await interaction.editReply({ embeds: [createErrorEmbed("An error occurred while refreshing commands. Check console for details.", "Refresh Failed")] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -30,14 +30,16 @@ export const reload = createCommand({
|
|||||||
embeds: [createWarningEmbed("Pulling latest changes and restarting...", "Redeploy Initiated")]
|
embeds: [createWarningEmbed("Pulling latest changes and restarting...", "Redeploy Initiated")]
|
||||||
});
|
});
|
||||||
|
|
||||||
const { stdout, stderr } = await execAsync("git pull");
|
// Get current branch
|
||||||
|
const { stdout: branchName } = await execAsync("git rev-parse --abbrev-ref HEAD");
|
||||||
|
const branch = branchName.trim();
|
||||||
|
|
||||||
if (stderr && !stdout) {
|
// Fetch and reset
|
||||||
throw new Error(stderr);
|
await execAsync("git fetch --all");
|
||||||
}
|
const { stdout } = await execAsync(`git reset --hard origin/${branch}`);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [createSuccessEmbed(`Git Pull Output:\n\`\`\`\n${stdout}\n\`\`\`\nRestarting process...`, "Update Successful")]
|
embeds: [createSuccessEmbed(`Git Reset Output:\n\`\`\`\n${stdout}\n\`\`\`\nRestarting process...`, "Update Successful")]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write context for post-restart notification
|
// Write context for post-restart notification
|
||||||
|
|||||||
116
src/commands/admin/update.ts
Normal file
116
src/commands/admin/update.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { createCommand } from "@lib/utils";
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags, ButtonBuilder, ButtonStyle, ActionRowBuilder, ComponentType } from "discord.js";
|
||||||
|
import { createErrorEmbed, createSuccessEmbed, createWarningEmbed, createInfoEmbed } from "@lib/embeds";
|
||||||
|
|
||||||
|
export const update = createCommand({
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName("update")
|
||||||
|
.setDescription("Check for updates and restart the bot")
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||||
|
execute: async (interaction) => {
|
||||||
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||||
|
|
||||||
|
const { exec } = await import("child_process");
|
||||||
|
const { promisify } = await import("util");
|
||||||
|
const { writeFile, appendFile } = await import("fs/promises");
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get current branch
|
||||||
|
const { stdout: branchName } = await execAsync("git rev-parse --abbrev-ref HEAD");
|
||||||
|
const branch = branchName.trim();
|
||||||
|
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [createInfoEmbed("Fetching latest changes...", "Checking for Updates")]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch remote
|
||||||
|
await execAsync("git fetch --all");
|
||||||
|
|
||||||
|
// Check for potential changes
|
||||||
|
const { stdout: logOutput } = await execAsync(`git log HEAD..origin/${branch} --oneline`);
|
||||||
|
|
||||||
|
if (!logOutput.trim()) {
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [createSuccessEmbed("The bot is already up to date.", "No Updates Found")]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare confirmation UI
|
||||||
|
const confirmButton = new ButtonBuilder()
|
||||||
|
.setCustomId("confirm_update")
|
||||||
|
.setLabel("Update & Restart")
|
||||||
|
.setStyle(ButtonStyle.Success);
|
||||||
|
|
||||||
|
const cancelButton = new ButtonBuilder()
|
||||||
|
.setCustomId("cancel_update")
|
||||||
|
.setLabel("Cancel")
|
||||||
|
.setStyle(ButtonStyle.Secondary);
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(confirmButton, cancelButton);
|
||||||
|
|
||||||
|
const updateEmbed = createInfoEmbed(
|
||||||
|
`The following changes are available:\n\`\`\`\n${logOutput.substring(0, 1000)}${logOutput.length > 1000 ? "\n...and more" : ""}\n\`\`\`\n**Do you want to update and restart?**`,
|
||||||
|
"Updates Available"
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await interaction.editReply({
|
||||||
|
embeds: [updateEmbed],
|
||||||
|
components: [row]
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const confirmation = await response.awaitMessageComponent({
|
||||||
|
filter: (i) => i.user.id === interaction.user.id,
|
||||||
|
componentType: ComponentType.Button,
|
||||||
|
time: 30000 // 30 seconds timeout
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmation.customId === "confirm_update") {
|
||||||
|
await confirmation.update({
|
||||||
|
embeds: [createWarningEmbed("Applying updates and restarting...", "Update In Progress")],
|
||||||
|
components: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(`git reset --hard origin/${branch}`);
|
||||||
|
|
||||||
|
await interaction.followUp({
|
||||||
|
flags: MessageFlags.Ephemeral,
|
||||||
|
embeds: [createSuccessEmbed(`Git Reset Output:\n\`\`\`\n${stdout}\n\`\`\`\nRestarting process...`, "Update Successful")]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write context for post-restart notification
|
||||||
|
await writeFile(".restart_context.json", JSON.stringify({
|
||||||
|
channelId: interaction.channelId,
|
||||||
|
userId: interaction.user.id,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Trigger restart
|
||||||
|
await appendFile("src/index.ts", " ");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
await confirmation.update({
|
||||||
|
embeds: [createInfoEmbed("Update cancelled.", "Cancelled")],
|
||||||
|
components: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
// Timeout
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [createWarningEmbed("Update confirmation timed out.", "Timed Out")],
|
||||||
|
components: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [createErrorEmbed(`Failed to check for updates:\n\`\`\`\n${error instanceof Error ? error.message : String(error)}\n\`\`\``, "Update Check Failed")]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user