424 lines
13 KiB
JavaScript
424 lines
13 KiB
JavaScript
|
|
// 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>} - 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>} - 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<Object>} - 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);
|
||
|
|
});
|