一、今天的學習目標
將add的功能從index.js裡面拉出來,並創一個獨立的檔案放add.js處理 /add 指令,這種把不同功能拆分到不同檔案中管理的方式,通常稱為模組化。
原本的架構是這樣:
index.js
├── Bot 登入
├── interactionCreate
├── /ping
├── /add
├── 日期驗證
├── 時間驗證
└── 日期格式整理
變這樣:
index.js
├── Bot 登入
├── 接收 interaction
└── 決定要交給哪個 command
add.js
└── 專門處理 /add
我希望可以讓index.js主要負責接收Discord傳來的interaction,而 /add 實際要做的事情則交給 add.js 處理。
二、拆分 /add 後,interaction 要怎麼傳到 add.js
當使用者在 Discord 執行指令時,Discord 會觸發 interactionCreate 事件,並把這一次互動的資料傳進來。
這時候要創建一個新的add.js就要需要可以取用使用者在Discord執行指令時,Discord傳給index.js的資料。
這時候就需要因此在 add.js 中建立:
async function execute(interaction) {}
並在index.js裏面呼叫:
await addCommand.execute(interaction);
這時候的流程就會變成這樣:
Discord
↓
index.js 收到 interaction
↓
把 interaction 傳給 add.js
↓
add.js 處理 /add 指令
這樣 /add 的功能就可以獨立放在 add.js 裡,同時又能使用 Discord 傳進來的資料。
三、實際拆分 /add 指令
先在 src 裡建立commands資料夾,用來集中管理不同指令的功能,今天先新增 add.js。
add.js:
async function handleAddCommand(interaction) {
const title = interaction.options.getString('title');
const date = interaction.options.getString('date');
const time = interaction.options.getString('time');
const dateRegex = /^\d{8}$/;
const timeRegex = /^\d{4}$/;
const errors = [];
if (!dateRegex.test(date)) {
errors.push('日期格式錯誤,請輸入 8 位數日期,例如 20260903');
}
if (!timeRegex.test(time)) {
errors.push('時間格式錯誤,請輸入 4 位數時間,例如 1830');
}
if (errors.length > 0) {
await interaction.reply(errors.join('\n'));
return;
}
const year = date.slice(0, 4);
const month = date.slice(4, 6);
const day = date.slice(6, 8);
const hour = time.slice(0, 2);
const minute = time.slice(2, 4);
const formattedDate = `${year}-${month}-${day}`;
const formattedTime = `${hour}:${minute}`;
await interaction.reply(
`行程名稱:${title}\n日期:${formattedDate}\n時間:${formattedTime}`
);
}
module.exports = handleAddCommand;
接著在 index.js 載入 add.js:
const addCommand = require('./commands/add');
更改之前用來處理/add指令的部分,讓 index.js 在判斷收到 /add 後,將 interaction 傳給 add.js 執行。
if (interaction.commandName === 'add') {
await addCommand.execute(interaction);
}
四、實際測試結果
拆分後,我重新啟動 Bot,並在 Discord 輸入 /add 進行測試。
輸入正確的標題、日期與時間後,Bot 仍然可以正常回覆行程內容;輸入錯誤的日期或時間格式時,也能正常顯示之前設定的錯誤訊息。
/add 的程式碼已經從 index.js 移到 add.js,原本的功能依舊可以正常執行。
五、修改檔案的完整程式碼
index.js:
const handleAddCommand = require('./commands/add');
require("dotenv").config();
const { Client, Events, GatewayIntentBits } = require("discord.js");
const token = process.env.DISCORD_TOKEN;
if (!token) {
console.error("錯誤:.env 裡還沒有設定 DISCORD_TOKEN");
console.error("請先到 Discord Developer Portal 複製 Bot Token,然後在 .env 加上:");
console.error("DISCORD_TOKEN=你的BotToken");
process.exit(1);
}
const client = new Client({
intents: [
GatewayIntentBits.Guilds
]
});
client.once(Events.ClientReady, () => {
console.log(`Bot 已登入:${client.user.tag}`);
});
client.on(Events.InteractionCreate, async (interaction) => {
try {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "ping") {
await interaction.reply("Pong!");
return;
}
if (interaction.commandName === 'add') {
await handleAddCommand(interaction);
}
} catch (error) {
console.error("處理指令時發生錯誤:");
console.error(error);
const errorMessage = "程式發生錯誤,請看終端機錯誤訊息。";
if (interaction.replied || interaction.deferred) {
await interaction.followUp(errorMessage);
} else {
await interaction.reply(errorMessage);
}
}
});
client.login(token).catch((error) => {
console.error("Bot 登入失敗,請檢查 Token 或網路連線。");
console.error(error.message);
process.exit(1);
});
add.js:
async function handleAddCommand(interaction) {
const title = interaction.options.getString('title');
const date = interaction.options.getString('date');
const time = interaction.options.getString('time');
const dateRegex = /^\d{8}$/;
const timeRegex = /^\d{4}$/;
const errors = [];
if (!dateRegex.test(date)) {
errors.push('日期格式錯誤,請輸入 8 位數日期,例如 20260903');
}
if (!timeRegex.test(time)) {
errors.push('時間格式錯誤,請輸入 4 位數時間,例如 1830');
}
if (errors.length > 0) {
await interaction.reply(errors.join('\n'));
return;
}
const year = date.slice(0, 4);
const month = date.slice(4, 6);
const day = date.slice(6, 8);
const hour = time.slice(0, 2);
const minute = time.slice(2, 4);
const formattedDate = `${year}-${month}-${day}`;
const formattedTime = `${hour}:${minute}`;
await interaction.reply(
`行程名稱:${title}\n日期:${formattedDate}\n時間:${formattedTime}`
);
}
module.exports = handleAddCommand;