ClientX/registry.js

424 lines
13 KiB
JavaScript
Raw Permalink Normal View History

2025-04-25 21:27:00 -04:00
// 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);
});