From b7311f090727cfc3307924bf6a0f6a0b64e3f1df Mon Sep 17 00:00:00 2001 From: yeongaori Date: Sat, 16 Aug 2025 02:43:23 +0900 Subject: [PATCH] commandhandler refactor --- index.js | 34 ++++- src/commands/military/getDischargeInfo.js | 59 +++++++-- src/commands/military/setEnlistmentDate.js | 64 ++++++---- src/events/interactionCreate.js | 8 +- src/handlers/commandHandler.js | 142 +++++++-------------- 5 files changed, 164 insertions(+), 143 deletions(-) diff --git a/index.js b/index.js index 624c285..cb21e3e 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ require('dotenv').config(); -const { Client, GatewayIntentBits, Partials } = require('discord.js'); +console.log('index.js is being executed'); +const { Client, GatewayIntentBits, Partials, Collection } = require('discord.js'); const fs = require('fs'); const path = require('path'); const logger = require('./modules/colorfulLogger'); @@ -20,6 +21,35 @@ const client = new Client({ loadData(); setupProcessHandlers(client); +// Load commands +client.commands = new Collection(); +client.legacyCommands = new Collection(); + +const commandFolders = fs.readdirSync(path.join(__dirname, 'src', 'commands')); + +for (const folder of commandFolders) { + const commandFiles = fs.readdirSync(path.join(__dirname, 'src', 'commands', folder)).filter(file => file.endsWith('.js')); + for (const file of commandFiles) { + const filePath = path.join(__dirname, 'src', 'commands', folder, file); + const command = require(filePath); + + // Load slash command part + if (command.data && command.execute) { + client.commands.set(command.data.name, command); + } + + // Load legacy command part + if (command.legacy) { + client.legacyCommands.set(command.legacy.name, command.legacy); + if (command.legacy.aliases) { + command.legacy.aliases.forEach(alias => { + client.legacyCommands.set(alias, command.legacy); + }); + } + } + } +} + // Load event handlers const eventsPath = path.join(__dirname, 'src', 'events'); const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); @@ -38,4 +68,4 @@ logger.info('Attempting to log in with token...'); client.login(process.env.DISCORD_TOKEN).catch(e => { logger.error(`Failed to login: ${e.message}`); process.exit(1); -}); \ No newline at end of file +}); diff --git a/src/commands/military/getDischargeInfo.js b/src/commands/military/getDischargeInfo.js index 3c4a71d..4036307 100644 --- a/src/commands/military/getDischargeInfo.js +++ b/src/commands/military/getDischargeInfo.js @@ -12,15 +12,7 @@ const { const { createProgressBar, getNameById } = require('../../utils/discordUtils'); const { binarySearch, addSpace } = require('../../utils/helpers'); -const commandData = new SlashCommandBuilder() - .setName('전역일') - .setDescription('전역일과 관련된 정보들을 확인합니다') - .setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel) - .setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall) - .addUserOption(option => option.setName('유저').setDescription('전역일을 확인할 유저').setRequired(false)) - .addIntegerOption(option => option.setName('소수점').setDescription('진행도의 소수점').setMinValue(0).setMaxValue(100).setRequired(false)) - .addBooleanOption(option => option.setName('상세내용').setDescription('상세 내용 표시 여부').setRequired(false)); - +// Core logic for getting discharge info function getDischargeInfo(client, targetUserId, targetUserName, decimal, usedFullInfo) { try { const ipdaeDatas = getIpdaeData(); @@ -29,7 +21,7 @@ function getDischargeInfo(client, targetUserId, targetUserName, decimal, usedFul if (index === -1) { return new EmbedBuilder() .setTitle('저장된 데이터가 없습니다') - .setDescription(`먼저 \`!입대일\` 또는 \`/입대일\` 명령어로 데이터를 저장해주세요.`) + .setDescription(`먼저 \`!입대일\` 또는 \`/입대일\` 명령어로 데이터를 저장해주세요.`) // Corrected backticks to escaped backticks for description string .addFields({ name: '대상자', value: `${userNameForEmbed} (${targetUserId})` }) .setColor('#F44336'); } @@ -76,7 +68,7 @@ function getDischargeInfo(client, targetUserId, targetUserName, decimal, usedFul return new EmbedBuilder() .setTitle(`${userNameForEmbed}의 ${endText}`) .addFields( - { name: '현재 진행도', value: `${progressBarText}\n${militaryRank}${addSpace(Math.max(0, 14 - militaryRank.length))}${dDayText}` }, + { name: '현재 진행도', value: `${progressBarText}\n${militaryRank}${addSpace(Math.max(0, 14 - militaryRank.length))}${dDayText}` }, // Corrected newline escape sequence { name: '전체 복무일', value: `${totalDays}일` }, { name: '현재 복무일', value: `${daysServed}일` } ) @@ -105,6 +97,16 @@ function getDischargeInfo(client, targetUserId, targetUserName, decimal, usedFul } } +// Slash Command +const commandData = new SlashCommandBuilder() + .setName('전역일') + .setDescription('전역일과 관련된 정보들을 확인합니다') + .setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel) + .setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall) + .addUserOption(option => option.setName('유저').setDescription('전역일을 확인할 유저').setRequired(false)) + .addIntegerOption(option => option.setName('소수점').setDescription('진행도의 소수점').setMinValue(0).setMaxValue(100).setRequired(false)) + .addBooleanOption(option => option.setName('상세내용').setDescription('상세 내용 표시 여부').setRequired(false)); + async function execute(interaction) { const decimalArg = interaction.options.getInteger('소수점') || 4; const targetUserOption = interaction.options.getUser('유저'); @@ -117,8 +119,39 @@ async function execute(interaction) { await interaction.editReply({ embeds: [resultEmbed] }); } +// Legacy Command +const legacy = { + name: '전역일', + aliases: ['ㅈㅇㅇ', '소집해제일', 'ㅅㅈㅎㅈㅇ', '소해일', 'ㅅㅎㅇ'], + async execute(message, args) { + const { author, guild, channel, client } = message; + let targetUserId = author.id; + let decimal = 4; + let potentialDecimalArgIndex = 0; + + if (args[0]) { + const mentionMatch = args[0].match(/^<@!?(\d+)>$/); + if (mentionMatch) { + targetUserId = mentionMatch[1]; + potentialDecimalArgIndex = 1; + } else if (/^\d{17,19}$/.test(args[0])) { + targetUserId = args[0]; + potentialDecimalArgIndex = 1; + } + } + + if (args[potentialDecimalArgIndex] && !isNaN(parseInt(args[potentialDecimalArgIndex]))) { + decimal = parseInt(args[potentialDecimalArgIndex]); + } + + const targetUserName = getNameById(client, targetUserId, guild); + const resultEmbed = getDischargeInfo(client, targetUserId, targetUserName, decimal, false); + await channel.send({ embeds: [resultEmbed] }); + } +}; + module.exports = { data: commandData, - getDischargeInfo, - execute + execute, + legacy }; \ No newline at end of file diff --git a/src/commands/military/setEnlistmentDate.js b/src/commands/military/setEnlistmentDate.js index 7ff1e35..b307892 100644 --- a/src/commands/military/setEnlistmentDate.js +++ b/src/commands/military/setEnlistmentDate.js @@ -8,21 +8,7 @@ const { dateFormatter } = require('../../utils/dateUtils'); const { getNameById } = require('../../utils/discordUtils'); const { handleCommandError } = require('../../../utils/errorHandler'); -const commandData = new SlashCommandBuilder() - .setName('입대일') - .setDescription('입대일을 설정합니다') - .setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel) - .setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall) - .addStringOption(option => option.setName('입대일').setDescription('형식: YYYY-MM-DD').setRequired(true)) - .addStringOption(option => option.setName('복무형태').setDescription('복무 형태').setRequired(true) - .addChoices( - { name: '육군', value: '육군' }, - { name: '해군', value: '해군' }, - { name: '공군', value: '공군' }, - { name: '해병대', value: '해병대' }, - { name: '사회복무요원', value: '사회복무요원' } - )); - +// Core logic for setting enlistment date async function setEnlistmentDate(client, callerUserId, dateArg, typeArg, commandString, customTargetUserId) { dateArg = String(dateArg || '').trim(); typeArg = String(typeArg || '').trim(); @@ -34,11 +20,7 @@ async function setEnlistmentDate(client, callerUserId, dateArg, typeArg, command if (!(/^\d{4}-\d{2}-\d{2}$/.test(dateArg) && serviceTypes.includes(typeArg))) { return new EmbedBuilder() .setTitle('명령어 형식 오류') - .setDescription(`입력된 날짜: -${dateArg} -, 복무형태: -${typeArg} -`) + .setDescription(`입력된 날짜: ${dateArg}, 복무형태: ${typeArg}`) .addFields({ name: '올바른 형식', value: `${commandString} YYYY-MM-DD ${serviceTypes.join('/')}` }) .setColor('#F44336'); } @@ -48,9 +30,7 @@ ${typeArg} if (isNaN(enlistDate.getTime()) || enlistDate.getFullYear() !== year || enlistDate.getMonth() + 1 !== month || enlistDate.getDate() !== day) { return new EmbedBuilder() .setTitle('유효하지 않은 날짜입니다.') - .setDescription(`입력한 날짜 -${dateArg} -를 확인해주세요.`) + .setDescription(`입력한 날짜 ${dateArg}를 확인해주세요.`) .setColor('#F44336'); } @@ -78,8 +58,24 @@ ${dateArg} } } +// Slash Command +const commandData = new SlashCommandBuilder() + .setName('입대일') + .setDescription('입대일을 설정합니다') + .setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel) + .setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall) + .addStringOption(option => option.setName('날짜').setDescription('형식: YYYY-MM-DD').setRequired(true)) + .addStringOption(option => option.setName('복무형태').setDescription('복무 형태').setRequired(true) + .addChoices( + { name: '육군', value: '육군' }, + { name: '해군', value: '해군' }, + { name: '공군', value: '공군' }, + { name: '해병대', value: '해병대' }, + { name: '사회복무요원', value: '사회복무요원' } + )); + async function execute(interaction) { - const slashStartDateArg = interaction.options.getString('입대일'); + const slashStartDateArg = interaction.options.getString('날짜'); const slashStartTypeArg = interaction.options.getString('복무형태'); logger.info(`Processing /입대일 for ${interaction.user.username} (${interaction.user.id}) with date: ${slashStartDateArg}, type: ${slashStartTypeArg}`); await interaction.deferReply(); @@ -87,8 +83,24 @@ async function execute(interaction) { await interaction.editReply({ embeds: [resultEmbed] }); } +// Legacy Command +const legacy = { + name: '입대일', + aliases: ['ㅇㄷㅇ', '소집일', 'ㅅㅈㅇ'], + async execute(message, args) { + const { channel, author, client, content } = message; + const commandName = content.split(/\s+/)[0].slice(1); + const argStartDate = args[0]; + const argStartType = args[1]; + const customUserIdForAdmin = args[2] && adminUserIds.includes(author.id) ? args[2] : undefined; + + const resultEmbed = await setEnlistmentDate(client, author.id, argStartDate, argStartType, `!${commandName}`, customUserIdForAdmin); + await channel.send({ embeds: [resultEmbed] }); + } +}; + module.exports = { data: commandData, - setEnlistmentDate, - execute + execute, + legacy, }; \ No newline at end of file diff --git a/src/events/interactionCreate.js b/src/events/interactionCreate.js index 5a2ee2c..70047e7 100644 --- a/src/events/interactionCreate.js +++ b/src/events/interactionCreate.js @@ -1,14 +1,10 @@ const { handleCommandError } = require('../../utils/errorHandler'); -const { onSlashCommand, loadCommands } = require('../handlers/slashCommandHandler'); +const { onSlashCommand } = require('../handlers/slashCommandHandler'); const { onButton } = require('../handlers/buttonHandler'); module.exports = { name: 'interactionCreate', async execute(interaction) { - if (!interaction.client.commands) { - interaction.client.commands = loadCommands(); - } - if (!interaction.isCommand() && !interaction.isButton()) return; try { @@ -21,4 +17,4 @@ module.exports = { await handleCommandError(e, interaction, interaction.client); } }, -}; \ No newline at end of file +}; diff --git a/src/handlers/commandHandler.js b/src/handlers/commandHandler.js index 089d8f1..eed31eb 100644 --- a/src/handlers/commandHandler.js +++ b/src/handlers/commandHandler.js @@ -1,105 +1,55 @@ -const { EmbedBuilder } = require('discord.js'); +const { Collection } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); const logger = require('../../modules/colorfulLogger'); -const { - startDateCommands, - endDateCommands, - randomCommands, - diceCommands, - deleteCommands, - adminUserIds, - commandPrefix -} = require('../../config/constants'); -const { setEnlistmentDate } = require('../commands/military/setEnlistmentDate'); -const { getDischargeInfo } = require('../commands/military/getDischargeInfo'); -const { handleGiftCode } = require('../commands/admin/giftCode'); -const { handleEval } = require('../commands/admin/eval'); -const { handleTest } = require('../commands/admin/test'); -const { handleDelete } = require('../commands/admin/delete'); -const { handleRandom } = require('../commands/general/random'); +const { commandPrefix } = require('../../config/constants'); const { getNameById } = require('../utils/discordUtils'); -async function handleCommand(message) { - const { channel, content, author, guild } = message; - const userId = author.id; - const userName = getNameById(message.client, userId, guild); +function loadLegacyCommands(client) { + client.legacyCommands = new Collection(); + const commandFolders = fs.readdirSync(path.join(__dirname, '..', 'commands')); - const contentWithoutPrefix = content.substring(commandPrefix.length); - const args = contentWithoutPrefix.trim().split(/\s+/); - const commandName = args[0].toLowerCase(); - - if (startDateCommands.includes(commandName)) { - logger.info(`Processing !${commandName} from ${userName} (${userId}) in ${guild ? `guild ${guild.name} (${guild.id})` : 'DM'}`); - const argStartDate = args[1]; - const argStartType = args[2]; - const customUserIdForAdmin = args[3] && adminUserIds.includes(userId) ? args[3] : undefined; - const resultEmbed = await setEnlistmentDate(message.client, userId, argStartDate, argStartType, `!${commandName}`, customUserIdForAdmin); - await channel.send({ embeds: [resultEmbed] }); - return; - } - - if (endDateCommands.includes(commandName)) { - logger.info(`Processing !${commandName} from ${userName} (${userId}) in ${guild ? `guild ${guild.name} (${guild.id})` : 'DM'}`); - let targetUserIdForCmd = userId; - let decimalArgForCmd = 4; - let decimalArgIndex = 1; - - if (args[1]) { - const mentionMatch = args[1].match(/^<@!?(\d+)>$/); - if (mentionMatch) { - targetUserIdForCmd = mentionMatch[1]; - decimalArgIndex = 2; - } else if (/^\d{17,19}$/.test(args[1])) { - targetUserIdForCmd = args[1]; - decimalArgIndex = 2; + for (const folder of commandFolders) { + const commandFiles = fs.readdirSync(path.join(__dirname, '..', 'commands', folder)).filter(file => file.endsWith('.js')); + for (const file of commandFiles) { + try { + const command = require(path.join(__dirname, '..', 'commands', folder, file)); + + if (command.legacy) { + const legacyCommand = command.legacy; + client.legacyCommands.set(legacyCommand.name, legacyCommand); + if (legacyCommand.aliases && legacyCommand.aliases.length > 0) { + legacyCommand.aliases.forEach(alias => client.legacyCommands.set(alias, legacyCommand)); + } + } + } catch (error) { + logger.error(`Error loading legacy command from ${file}:`, error); } } - - if (args[decimalArgIndex] && !isNaN(parseInt(args[decimalArgIndex]))) { - decimalArgForCmd = parseInt(args[decimalArgIndex]); - } else if (decimalArgIndex === 1 && args[1] && !isNaN(parseInt(args[1]))) { - decimalArgForCmd = parseInt(args[1]); - } - - const targetUserNameForCmd = getNameById(message.client, targetUserIdForCmd, guild); - const resultEmbed = getDischargeInfo(message.client, targetUserIdForCmd, targetUserNameForCmd, decimalArgForCmd, false); - await channel.send({ embeds: [resultEmbed] }); - return; - } - - if (randomCommands.includes(commandName)) { - await handleRandom(message, args.slice(1)); - return; - } - - if (diceCommands.includes(commandName)) { - await handleRandom(message, ['1', '6']); - return; - } - - if (commandName === 'gicode') { - await handleGiftCode(message, 'genshin'); - return; - } - - if (commandName === 'hsrcode') { - await handleGiftCode(message, 'hsr'); - return; - } - - if (commandName === 'eval') { - const evalCode = message.content.substring(commandPrefix.length + commandName.length).trim(); - await handleEval(message, evalCode); - return; - } - - if (commandName === 'test') { - await handleTest(message); - } - - if (deleteCommands.includes(commandName)) { - await handleDelete(message.client, message); - return; } } -module.exports = { handleCommand }; +async function handleCommand(message) { + if (!message.content.startsWith(commandPrefix) || message.author.bot) return; + + const args = message.content.slice(commandPrefix.length).trim().split(/\s+/); + const commandName = args.shift().toLowerCase(); + + const command = message.client.legacyCommands.get(commandName); + + if (!command) return; + + const { author, guild } = message; + const userName = getNameById(message.client, author.id, guild); + + logger.info(`Processing !${commandName} from ${userName} (${author.id}) in ${guild ? `guild ${guild.name} (${guild.id})` : 'DM'}`); + + try { + await command.execute(message, args); + } catch (error) { + logger.error(`Error executing legacy command ${commandName}:`, error); + await message.reply('명령어 실행 중 오류가 발생했습니다.'); + } +} + +module.exports = { loadLegacyCommands, handleCommand };