iT邦幫忙

2022 iThome 鐵人賽

DAY 6
2

Day 6 模組 Pipeline

今天來說說我們的 Bot,也就是框架的部分是如何來處理模組的。

pure-cat

使用模組 .use

我們從最簡單的骨架開始,Bot 中會擁有一個 discord.js 的 client,以及多個模組 modules

如果 .use 被呼叫,我們就把新的模組放到 modules 陣列裡。當 .start 被呼叫時,我們就用先前加入的模組聲明的 intents 來初始化 client 並登入。

export class Bot {
    client: Client = new Client({ intents: [] });
    modules: Module[] = [];

    use(...modules: Module[]): this {
        this.modules.push(...modules);
        return this;
    }

    async start(): Promise<void> {
        this.client.options.intents = [
            ...new Set(this.modules.flatMap((module) => module.intents ?? [])),
        ];

        await this.client.login(process.env.BOT_TOKEN);
    }
}

模組 Module

目前,我們的模組只處理文字的輸入,所以我只先定義了 messagedm 兩種事件方法。

因為底層是 discord.js,所以之後應該會對於所有的事件都有對應的方法(當然是可選的)。

export interface Module {
    name: string;
    intents?: GatewayIntentBits[];

    init?(bot: Bot): Promise<void>;
    message?(message: Message<true>, next: CallNextModule): Promise<void>;
    dm?(message: Message<false>, next: CallNextModule): Promise<void>;
}

intents 用來聲明模組需要的意圖。

init 會在 Bot 登入後被呼叫,我們可以在這裡做一些初始化的工作,例如更新資料庫、下載檔案等。

messagedm 方法會在 Bot 收到訊息時被呼叫,我們可以在這裡做一些處理,例如回應訊息、處理指令等。

建構模組 pipeline

在這裡我參考了一下 Koa Compose 聰明的實作方式。

首先,我們在所有模組初始化完之後,會用建立一個 .shortcut.<event> 來儲存有監聽該事件的模組。

async start(): Promise<void> {
    // ...

    this.shortcut.message = this.modules.filter(
        (module) => module["message"],
    ) as Required<Module>[];

    // ...
}

然後,我們把所有 shortcut 內的對應事件方法,包裝成一個 pipeline

這裡有簡單做了一些錯誤處理,例如多次呼叫 downstream 拋出錯誤,以及最內層的 next 會直接回傳。

async start(): Promise<void> {
    // ...

    this.pipeline.message = (message: Message<true>) => {
        let idx = -1;

        const execute = async (i: number): Promise<void> => {
            if (i <= idx) {
                throw new Error("handler already called.");
            }
            if (i >= this.shortcut.message.length) {
                return;
            }
            idx = i;

            return await this.shortcut.message[i].message(
                message,
                execute.bind(this, i + 1),
            );
        };

        return execute(0);
    };
}

收到訊息時

Bot 收到訊息時,呼叫相應 pipeline 進行處理,這樣各模組就可以在內部呼叫它的 downstream 模組。

this.client.on("messageCreate", async (message) => {
    if (message.author.id === this.client.user?.id) {
        return;
    }

    if (message.guild) {
        await this.pipeline.message(message);
    } else {
        await this.pipeline.dm(message);
    }
});

完成到這裡,昨天的兩個模組就可以在洋蔥模型下正常運作了。

接下來,應該來談談如何處理 slash command 了。我個人蠻喜歡 commander 的,所以想看看能不能用類似的方式來處理 slash command。

今天拿 Ping 改一改寫了 Echo 模組,可以用 pnpm i pure-cat-module-echo 然後 .use(new Echo()) 試試喔。


每日鐵人賽熱門 Top 10 (2022-09-21)

以 2022/09/20 20:00 ~ 2022/09/21 20:00 文章觀看數增加值排名

誤差: 1 小時

  1. +630 「全端挑戰」了解Scss與React Component與首頁概念圖與UI實作
    • 作者: SamKo
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  2. +620 「全端挑戰」Scss與React Component的動態實作Navbar與Header
    • 作者: SamKo
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  3. +598 「全端挑戰」製作動態網站第一步從了解useState與它的用法開始
    • 作者: SamKo
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  4. +575 「全端挑戰」學習Mern全端開發概念與動態網站開發流程懶人包
    • 作者: SamKo
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  5. +412 學徒、忍者、大師? 忍蛋三人組的火影之路 (Day 0)
    • 作者: J W Hu
    • 系列:JS 忍者訓練計畫
  6. +382 D01 - 沒時間解釋了,快上車!
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  7. +306 Introduction of Vue, Django, MongoDB, Nginx
    • 作者: 哲嘉 (Zhe-Jia)
    • 系列:Vue+Django+MongoDB+Nginx 全端開發
  8. +249 D06 - 打造遊戲選單按鈕
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  9. +234 D04 - 門面怎麼可以沒有背景
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  10. +204 [DAY-01] - Julia 介紹與引言大綱
    • 作者: William
    • 系列:從 0 開始學習Julia

SamKo 的 MERN 系列依舊佔滿榜上前幾名的位置,我覺得文中的圖做的很好,值得一看。


上一篇
Day 5 實作 Ping 和 Timing 模組
下一篇
Day 7 Slash Command
系列文
Discord Bot with TypeScript: Framework, Database, and Modules30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言