iT邦幫忙

0

Day 6|將 /add 指令模組化,拆分 index.js

  • 分享至 

  • xImage
  •  

一、今天的學習目標
將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 進行測試。
https://ithelp.ithome.com.tw/upload/images/20260929/20184393vZFmPXluV8.png
輸入正確的標題、日期與時間後,Bot 仍然可以正常回覆行程內容;輸入錯誤的日期或時間格式時,也能正常顯示之前設定的錯誤訊息。
https://ithelp.ithome.com.tw/upload/images/20260929/20184393dMhSs5E92s.png
/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;

*提醒邦友,使用第三方服務/API 時,請務必評估資安風險與隱私保護
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言