// registry.js import { REST } from '@discordjs/rest'; import { Routes } from 'discord-api-types/v10'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import config from './config.js'; // Get directory name in ES module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Command line argument parsing with required parameters const args = process.argv.slice(2); const actionArg = args.find(arg => arg.startsWith('--action='))?.split('=')[1]; const guildArg = args.find(arg => arg.startsWith('--guild='))?.split('=')[1]; const clientArg = args.find(arg => arg.startsWith('--client='))?.split('=')[1]; const dryRun = args.includes('--dryrun'); // Validate required parameters if (args.includes('--help') || args.includes('-h') || !actionArg || !guildArg || !clientArg) { console.log(` Discord Command Registry Tool Usage: node registry.js --action=ACTION --guild=GUILD_ID --client=CLIENT_ID [options] Required Parameters: --action=ACTION Action to perform: register, unregister, or list --guild=GUILD_ID Target guild ID or "all" for global commands --client=CLIENT_ID Target client ID or "all" for all clients Options: --dryrun Show what would happen without making actual changes --help, -h Show this help message Examples: node registry.js --action=list --guild=123456789012345678 --client=IO3 node registry.js --action=register --guild=all --client=ASOP node registry.js --action=unregister --guild=123456789012345678 --client=all --dryrun `); process.exit(1); } // Validate action parameter const validActions = ['register', 'unregister', 'list']; if (!validActions.includes(actionArg.toLowerCase())) { console.error(`Error: Invalid action "${actionArg}". Must be one of: ${validActions.join(', ')}`); process.exit(1); } const action = actionArg.toLowerCase(); // Validate guild parameter const isGuildAll = guildArg.toLowerCase() === 'all'; const targetGuildId = isGuildAll ? null : guildArg; // Validate client parameter - must be "all" or match a client in config const isClientAll = clientArg.toLowerCase() === 'all'; const targetClients = isClientAll ? config.clients.filter(client => client.enabled !== false) : config.clients.filter(client => client.id === clientArg && client.enabled !== false); if (targetClients.length === 0) { console.error(`Error: No matching clients found for "${clientArg}"`); console.log('Available clients:'); config.clients .filter(client => client.enabled !== false) .forEach(client => console.log(` - ${client.id}`)); process.exit(1); } /** * Load and extract commands from a module * @param {string} modulePath - Path to the module file * @returns {Promise} - Array of command data objects */ async function extractCommandsFromModule(modulePath) { try { // Import the module const moduleUrl = `file://${modulePath}`; const module = await import(moduleUrl); // Check for commands array if (Array.isArray(module.commands)) { // Extract command data const extractedCommands = module.commands.map(cmd => { if (cmd && cmd.data && typeof cmd.data.toJSON === 'function') { try { return cmd.data.toJSON(); } catch (error) { console.warn(`Error converting command to JSON in ${path.basename(modulePath)}: ${error.message}`); return null; } } return null; }).filter(Boolean); // Remove null entries console.log(` - Extracted ${extractedCommands.length} commands from ${path.basename(modulePath)}`); return extractedCommands; } else { console.log(` - No commands found in ${path.basename(modulePath)}`); return []; } } catch (error) { console.error(`Error loading module ${modulePath}: ${error.message}`); return []; } } /** * Process a client's modules and extract commands * @param {Object} clientConfig - Client configuration * @returns {Promise} - Array of command data objects */ async function processClientModules(clientConfig) { console.log(`\nExtracting commands from modules for client: ${clientConfig.id}`); const commands = []; const optDir = path.join(__dirname, '_opt'); // Process each module for (const moduleName of clientConfig.modules || []) { console.log(`Processing module: ${moduleName}`); const modulePath = path.join(optDir, `${moduleName}.js`); if (!fs.existsSync(modulePath)) { console.warn(` - Module not found: ${moduleName}`); continue; } const moduleCommands = await extractCommandsFromModule(modulePath); commands.push(...moduleCommands); } console.log(`Total commands extracted for ${clientConfig.id}: ${commands.length}`); return commands; } /** * Get guild information by ID * @param {REST} rest - Discord REST client * @param {string} guildId - Guild ID * @returns {Promise} - Guild information */ async function getGuildInfo(rest, guildId) { try { return await rest.get(Routes.guild(guildId)); } catch (error) { console.error(`Error fetching guild info: ${error.message}`); return { name: `Unknown Guild (${guildId})` }; } } /** * List registered commands for a client * @param {Object} clientConfig - Client configuration * @param {string|null} guildId - Guild ID or null for global */ async function listCommands(clientConfig, guildId) { const { id, discord } = clientConfig; if (!discord || !discord.token || !discord.appId) { console.error(`Invalid client configuration for ${id}`); return; } // Set up REST client const rest = new REST({ version: '10' }).setToken(discord.token); // Handle global or guild-specific commands if (guildId === null) { // Global commands await listGlobalCommands(clientConfig, rest); } else { // Guild-specific commands await listGuildCommands(clientConfig, rest, guildId); } } /** * List global commands for a client * @param {Object} clientConfig - Client configuration * @param {REST} rest - Discord REST client */ async function listGlobalCommands(clientConfig, rest) { console.log(`\nListing global commands for client: ${clientConfig.id}`); try { const route = Routes.applicationCommands(clientConfig.discord.appId); const commands = await rest.get(route); if (commands.length === 0) { console.log(`No global commands registered for client ${clientConfig.id}`); return; } console.log(`Found ${commands.length} global commands:`); // Display commands in a formatted table console.log(''); console.log('ID'.padEnd(20) + 'NAME'.padEnd(20) + 'DESCRIPTION'.padEnd(60)); for (const cmd of commands) { console.log( `${cmd.id.toString().padEnd(20)}${cmd.name.padEnd(20)}${(cmd.description || '')}` ); } } catch (error) { console.error(`Error listing global commands for client ${clientConfig.id}: ${error.message}`); } } /** * List guild-specific commands for a client * @param {Object} clientConfig - Client configuration * @param {REST} rest - Discord REST client * @param {string} guildId - Guild ID */ async function listGuildCommands(clientConfig, rest, guildId) { // Get guild info const guildInfo = await getGuildInfo(rest, guildId); const guildName = guildInfo.name || `Unknown Guild (${guildId})`; console.log(`\nListing commands for client: ${clientConfig.id} in guild: ${guildName} (${guildId})`); try { const route = Routes.applicationGuildCommands(clientConfig.discord.appId, guildId); const commands = await rest.get(route); if (commands.length === 0) { console.log(`No commands registered for client ${clientConfig.id} in guild ${guildName}`); return; } console.log(`Found ${commands.length} commands:`); // Display commands in a formatted table console.log(''); console.log('ID'.padEnd(20) + 'NAME'.padEnd(20) + 'DESCRIPTION'.padEnd(60)); for (const cmd of commands) { console.log( `${cmd.id.toString().padEnd(20)}${cmd.name.padEnd(20)}${(cmd.description || '')}` ); } console.log(''); } catch (error) { console.error(`Error listing commands for client ${clientConfig.id} in guild ${guildName}: ${error.message}`); } } /** * Register commands for a client * @param {Object} clientConfig - Client configuration * @param {string|null} guildId - Guild ID or null for global */ async function registerCommands(clientConfig, guildId) { const { id, discord } = clientConfig; if (!discord || !discord.token || !discord.appId) { console.error(`Invalid client configuration for ${id}`); return; } // Extract commands from modules const commands = await processClientModules(clientConfig); if (commands.length === 0) { console.log(`No commands found for client ${id}`); return; } // Set up REST client const rest = new REST({ version: '10' }).setToken(discord.token); // Determine route and scope description let route; let scopeDesc; if (guildId === null) { route = Routes.applicationCommands(discord.appId); scopeDesc = 'global'; } else { route = Routes.applicationGuildCommands(discord.appId, guildId); const guildInfo = await getGuildInfo(rest, guildId); const guildName = guildInfo.name || `Unknown Guild (${guildId})`; scopeDesc = `guild ${guildName} (${guildId})`; } // Register commands console.log(`\nRegistering ${commands.length} commands for client ${id} in ${scopeDesc}...`); // List commands being registered console.log('\nCommands to register:'); for (const cmd of commands) { console.log(` - ${cmd.name}: ${cmd.description}`); } if (dryRun) { console.log(`\n[DRY RUN] Would register ${commands.length} commands for client ${id} in ${scopeDesc}`); } else { try { await rest.put(route, { body: commands }); console.log(`\nSuccessfully registered ${commands.length} commands for client ${id} in ${scopeDesc}`); } catch (error) { console.error(`Error registering commands for client ${id} in ${scopeDesc}: ${error.message}`); } } } /** * Unregister commands for a client * @param {Object} clientConfig - Client configuration * @param {string|null} guildId - Guild ID or null for global */ async function unregisterCommands(clientConfig, guildId) { const { id, discord } = clientConfig; if (!discord || !discord.token || !discord.appId) { console.error(`Invalid client configuration for ${id}`); return; } // Set up REST client const rest = new REST({ version: '10' }).setToken(discord.token); // Determine route and scope description let route; let scopeDesc; if (guildId === null) { route = Routes.applicationCommands(discord.appId); scopeDesc = 'global'; } else { route = Routes.applicationGuildCommands(discord.appId, guildId); const guildInfo = await getGuildInfo(rest, guildId); const guildName = guildInfo.name || `Unknown Guild (${guildId})`; scopeDesc = `guild ${guildName} (${guildId})`; } // Get current commands to show what will be unregistered try { const currentCommands = await rest.get(route); console.log(`\nFound ${currentCommands.length} commands for client ${id} in ${scopeDesc}`); if (currentCommands.length > 0) { console.log('\nCommands to unregister:'); for (const cmd of currentCommands) { console.log(` - ${cmd.name}: ${cmd.description}`); } } else { console.log(`No commands to unregister for client ${id} in ${scopeDesc}`); return; } if (dryRun) { console.log(`\n[DRY RUN] Would unregister ${currentCommands.length} commands for client ${id} in ${scopeDesc}`); } else { await rest.put(route, { body: [] }); console.log(`\nSuccessfully unregistered all commands for client ${id} in ${scopeDesc}`); } } catch (error) { console.error(`Error unregistering commands for client ${id} in ${scopeDesc}: ${error.message}`); } } // Main execution async function main() { console.log(''); console.log('Discord Command Registry Tool'); console.log(`\nOperation: ${action.toUpperCase()}`); console.log(`Target Guild: ${isGuildAll ? 'ALL (Global)' : targetGuildId}`); console.log(`Target Client: ${isClientAll ? 'ALL' : targetClients[0].id}`); if (dryRun) { console.log('\n*** DRY RUN MODE - NO CHANGES WILL BE MADE ***'); } // Process each client for (const clientConfig of targetClients) { // Skip disabled clients if (clientConfig.enabled === false) { console.log(`\nSkipping disabled client: ${clientConfig.id}`); continue; } console.log(''); console.log(`Processing client: ${clientConfig.id}`); if (isGuildAll) { // Global operation if (action === 'list') { await listCommands(clientConfig, null); } else if (action === 'register') { await registerCommands(clientConfig, null); } else if (action === 'unregister') { await unregisterCommands(clientConfig, null); } } else { // Guild-specific operation if (action === 'list') { await listCommands(clientConfig, targetGuildId); } else if (action === 'register') { await registerCommands(clientConfig, targetGuildId); } else if (action === 'unregister') { await unregisterCommands(clientConfig, targetGuildId); } } } console.log(''); console.log('Command registry operation complete'); } main().catch(error => { console.error('Fatal error:', error); process.exit(1); });