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 { handleLinkCommand } = require('./commands');
|
||||||
const { handleCreateServerCommand } = require('./createserver');
|
const { handleCreateServerCommand } = require('./createserver');
|
||||||
|
const { handleDelServerCommand } = require('./delserver');
|
||||||
const discordRoleSync = require('../services/discordRoleSync');
|
const discordRoleSync = require('../services/discordRoleSync');
|
||||||
|
|
||||||
function registerEvents(client) {
|
function registerEvents(client) {
|
||||||
@@ -11,6 +12,9 @@ function registerEvents(client) {
|
|||||||
if (interaction.commandName === 'createserver') {
|
if (interaction.commandName === 'createserver') {
|
||||||
await handleCreateServerCommand(interaction);
|
await handleCreateServerCommand(interaction);
|
||||||
}
|
}
|
||||||
|
if (interaction.commandName === 'delserver') {
|
||||||
|
await handleDelServerCommand(interaction);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('ready', () => {
|
client.on('ready', () => {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const stripeRoutes = require('./routes/stripe');
|
|||||||
const { registerEvents } = require('./discord/events');
|
const { registerEvents } = require('./discord/events');
|
||||||
const { linkCommand } = require('./discord/commands');
|
const { linkCommand } = require('./discord/commands');
|
||||||
const { createServerCommand } = require('./discord/createserver');
|
const { createServerCommand } = require('./discord/createserver');
|
||||||
|
const { delServerCommand } = require('./discord/delserver');
|
||||||
const { initCron } = require('./sync/cron');
|
const { initCron } = require('./sync/cron');
|
||||||
const discordRoleSync = require('./services/discordRoleSync');
|
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.');
|
console.log('Refreshing application (/) commands.');
|
||||||
await rest.put(
|
await rest.put(
|
||||||
Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.GUILD_ID),
|
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.');
|
console.log('✅ Successfully reloaded application (/) commands.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user