Add /delserver slash command
Deletes complete server setup: - All channels in category - The category itself - The server role Requires confirm:True to execute. Without confirm, shows preview of what would be deleted. Reminds to clean up Carl-bot reaction roles. Staff only. Chronicler: #71
This commit is contained in:
190
services/arbiter-3.0/src/discord/delserver.js
Normal file
190
services/arbiter-3.0/src/discord/delserver.js
Normal file
@@ -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 };
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user