diff --git a/services/arbiter-3.0/src/discord/delserver.js b/services/arbiter-3.0/src/discord/delserver.js new file mode 100644 index 0000000..e22207a --- /dev/null +++ b/services/arbiter-3.0/src/discord/delserver.js @@ -0,0 +1,190 @@ +/** + * /delserver Command + * Deletes a complete server setup: + * - All channels in the category + * - The category itself + * - The server role + * + * Requires confirm:True to execute. + * Without confirm, shows preview of what would be deleted. + * + * Created: April 8, 2026 + * Chronicler: #71 + * Task: #98 Discord Channel Automation + */ + +const { SlashCommandBuilder, ChannelType } = require('discord.js'); + +// Staff role names that can use this command +const STAFF_ROLES = ['Staff', '🛡️ Moderator', '👑 The Wizard', '💎 The Emissary', '✨ The Catalyst']; + +// Build the slash command +const delServerCommand = new SlashCommandBuilder() + .setName('delserver') + .setDescription('Delete a server setup completely (Staff only)') + .addStringOption(option => + option.setName('name') + .setDescription('Server name (e.g., "Beyond Depth")') + .setRequired(true) + .setMaxLength(50) + ) + .addBooleanOption(option => + option.setName('confirm') + .setDescription('Set to True to confirm deletion') + .setRequired(false) + ); + +/** + * Check if user has staff role + */ +function isStaff(member) { + return member.roles.cache.some(role => STAFF_ROLES.includes(role.name)); +} + +/** + * Handle /delserver command + */ +async function handleDelServerCommand(interaction) { + // Check permissions + if (!isStaff(interaction.member)) { + return interaction.reply({ + content: '❌ This command is restricted to Staff members.', + ephemeral: true + }); + } + + const serverName = interaction.options.getString('name'); + const confirmed = interaction.options.getBoolean('confirm') || false; + const guild = interaction.guild; + + await interaction.deferReply({ ephemeral: false }); + + try { + await guild.channels.fetch(); + await guild.roles.fetch(); + + // Find the category (with or without emoji) + const category = guild.channels.cache.find( + ch => ch.type === ChannelType.GuildCategory && + (ch.name === serverName || ch.name === `🎮 ${serverName}`) + ); + + // Find the role + const serverRole = guild.roles.cache.find( + r => r.name.toLowerCase() === serverName.toLowerCase() + ); + + // Build preview + const channelsToDelete = category + ? guild.channels.cache.filter(ch => ch.parentId === category.id) + : new Map(); + + const preview = []; + + if (category) { + preview.push(`**Category:** ${category.name}`); + if (channelsToDelete.size > 0) { + preview.push(`**Channels (${channelsToDelete.size}):**`); + channelsToDelete.forEach(ch => { + const typeLabel = ch.type === ChannelType.GuildVoice ? '🔊' : + ch.type === ChannelType.GuildForum ? '💬' : '#'; + preview.push(`• ${typeLabel} ${ch.name}`); + }); + } + } else { + preview.push(`**Category:** ⚠️ Not found`); + } + + if (serverRole) { + preview.push(`**Role:** @${serverRole.name} (${serverRole.members.size} members)`); + } else { + preview.push(`**Role:** ⚠️ Not found`); + } + + // If nothing found + if (!category && !serverRole) { + return interaction.editReply(`❌ Server **${serverName}** not found.\n\nNo category or role matches that name.`); + } + + // If not confirmed, show preview + if (!confirmed) { + const previewMessage = `⚠️ **Delete Server: ${serverName}** + +This will permanently delete: + +${preview.join('\n')} + +--- + +**To confirm, run:** +\`\`\` +/delserver name:${serverName} confirm:True +\`\`\``; + + return interaction.editReply(previewMessage); + } + + // CONFIRMED - Execute deletion + await interaction.editReply(`🗑️ Deleting **${serverName}**...`); + + const deleted = { + channels: 0, + category: false, + role: false + }; + + // Delete channels first + if (category) { + for (const [id, channel] of channelsToDelete) { + try { + await channel.delete(`/delserver by ${interaction.user.tag}`); + deleted.channels++; + } catch (err) { + console.error(`Failed to delete channel ${channel.name}:`, err.message); + } + } + + // Delete category + try { + await category.delete(`/delserver by ${interaction.user.tag}`); + deleted.category = true; + } catch (err) { + console.error(`Failed to delete category:`, err.message); + } + } + + // Delete role + if (serverRole) { + try { + await serverRole.delete(`/delserver by ${interaction.user.tag}`); + deleted.role = true; + } catch (err) { + console.error(`Failed to delete role:`, err.message); + } + } + + // Success message + const successMessage = `🗑️ **${serverName}** deleted! + +**Removed:** +• ${deleted.channels} channels +• ${deleted.category ? '1 category' : '0 categories'} +• ${deleted.role ? '1 role' : '0 roles'} + +--- + +**Don't forget to:** +1. Remove the reaction emoji from <#1403980899464384572> +2. Remove the role mapping from Carl-bot`; + + await interaction.editReply(successMessage); + + console.log(`🗑️ /delserver: ${serverName} deleted by ${interaction.user.tag}`); + + } catch (error) { + console.error('/delserver error:', error); + await interaction.editReply(`❌ Error deleting server: ${error.message}`); + } +} + +module.exports = { delServerCommand, handleDelServerCommand }; diff --git a/services/arbiter-3.0/src/discord/events.js b/services/arbiter-3.0/src/discord/events.js index 33311fb..169b15e 100644 --- a/services/arbiter-3.0/src/discord/events.js +++ b/services/arbiter-3.0/src/discord/events.js @@ -1,5 +1,6 @@ const { handleLinkCommand } = require('./commands'); const { handleCreateServerCommand } = require('./createserver'); +const { handleDelServerCommand } = require('./delserver'); const discordRoleSync = require('../services/discordRoleSync'); function registerEvents(client) { @@ -11,6 +12,9 @@ function registerEvents(client) { if (interaction.commandName === 'createserver') { await handleCreateServerCommand(interaction); } + if (interaction.commandName === 'delserver') { + await handleDelServerCommand(interaction); + } }); client.on('ready', () => { diff --git a/services/arbiter-3.0/src/index.js b/services/arbiter-3.0/src/index.js index 35ef301..9c88865 100644 --- a/services/arbiter-3.0/src/index.js +++ b/services/arbiter-3.0/src/index.js @@ -17,6 +17,7 @@ const stripeRoutes = require('./routes/stripe'); const { registerEvents } = require('./discord/events'); const { linkCommand } = require('./discord/commands'); const { createServerCommand } = require('./discord/createserver'); +const { delServerCommand } = require('./discord/delserver'); const { initCron } = require('./sync/cron'); const discordRoleSync = require('./services/discordRoleSync'); @@ -129,7 +130,7 @@ const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN) console.log('Refreshing application (/) commands.'); await rest.put( Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.GUILD_ID), - { body: [linkCommand.toJSON(), createServerCommand.toJSON()] }, + { body: [linkCommand.toJSON(), createServerCommand.toJSON(), delServerCommand.toJSON()] }, ); console.log('✅ Successfully reloaded application (/) commands.'); } catch (error) {