以 JavaScript 撰寫簡單的卡米狗

C.T. Lin
YOCTOL.AI
Published in
8 min readJan 25, 2018

注意!這篇文章是舊版的,新版文章請參考「以 Bottender v1 撰寫簡單的卡米狗」。

在台灣只要講到 LINE Chatbot,就不能不提到最有代表性的卡米狗

剛好在前一陣子卡米狗的開發者寫了一個很完整的系列 — 「只要有心,人人都可以做卡米狗」,可以讓我們稍微了解卡米狗背後運作的模式。

Ruby 是很優雅的語言,Rails 也是功能完善且強大的框架,但在這篇文章中,我們會介紹另一種實作卡米狗功能的方式:使用我們公司所開發的 Chatbot 專用框架 — Bottender,來作為給 JavaScript 開發者的參考。

前置準備

準備開發帳號、開啟 Messaging API、加入群組功能等等細節就不在這細述,有需要請參閱卡米狗以及 Bottender 的 LINE 教學。

開啟一個 Bottender 的專案

如果你的電腦上的 npm 版本 >=5.2.0,可以直接使用內建的 npx 來進行初始化專案:

npx bottender init

或是安裝到全域再來執行也可以:

npm install -g bottender
bottender init

需要注意的點:

  1. 在互動式的準備過程中,要選擇 line 作為 platform。
  2. 必須把之前準備好的 accessToken 以及 channelSecret 填入 bottender.config.js

建立 Database

為了不讓資料庫的概念跟種類模糊了這篇文章的焦點,我們直接使用 In-Memory 的方式來處理,宣告一個常數來儲存:

const db = {
map: {},
};

備註:因為沒有持久化,伺服器重啟之後資料被重置純屬正常現象。

實作功能

卡米狗學說話

首先我們要實做的就是第一重要的功能,教卡米狗說話,指令如下:

卡米狗學說話;關鍵字;要回的話

例如:

卡米狗學說話;紅茶拿鐵;拿的動嗎?

依照這個語法,我們準備了還算堪用的正規表達式 /^卡米狗學說話;([^;]+);([^;]+)$/ 來判斷這個語句,並可以把擷取的鍵值存進 Database:

const { text } = context.event;if (/^卡米狗學說話;([^;]+);([^;]+)$/.test(text)) {
// 斷開後第一個部分是「卡米狗學說話」,可以直接忽略它
const [, key, val] = text.split(';');

// 如果沒有教過就初始化
if (!db.map[key]) db.map[key] = [];

// 記錄到列表中
db.map[key].push({
sessionId: context.session.id,
keyword: key,
message: val,
});

// 這整段程式碼會放到 async function 中,所以可以等待這個 promise
await context.replyText('好哦~好哦~*1');
}

備註:為了讓後面的功能好實作,這邊用物件的方式來存,並保留 sessionId,用 session.id 不使用 session.room.idsession.group.id 可方便同時支援 Room 跟 Group,又順便支援跟單一 User 對話。

卡米狗見人說人話,見鬼說鬼話

前面已經實作完教狗說話的學習部分了,接著就是要讓牠能真的把句子說出來。跟牠說任何一句非指令的話時都必須去檢查是否有教過,有教過就必須照著回應。另一個特色是,如果這個關鍵字沒在當下的 Room 或 Group 內教過,則會採其他地方所教過的回答:

const { partition, last } = require('lodash');const { text } = context.event;const mappings = db.map[text];// 如果曾經有任何關於這個關鍵字的紀錄
if (mappings && mappings.length > 0) {
// 以 sessionId 匹配與否切分成兩個陣列
const [localMappings, globalMappings] = partition(mappings, {
sessionId: context.session.id
});

// 先取 local 裡設定的最後一個,取不到才用 global 的
const answer = last(
localMappings.length > 0 ? localMappings: globalMappings
).message;

await context.replyText(answer);
}

最後的 context.replyText 會把我們藉由前面程式所找出的字串給送出去,這樣就完成了說話的部分。

卡米狗忘記

忘記的語法如下,下了這個指令後,前面教的對應回覆會被刪除:

卡米狗忘記;關鍵字

例如:

卡米狗忘記;紅茶拿鐵

接下來我們一樣用特製的正規表達式來判斷這個忘記的語法,再把對應的記錄過濾掉:

const { text } = context.event;if (/^卡米狗忘記;([^;]+)$/.test(text)) {
// 斷開後第一個部分是「卡米狗忘記」,可以直接忽略它
const [, key] = text.split(';');

// 只過濾掉這個 session 所定義的
db.map[key] = db.map[key].filter(
mapping => mapping.sessionId !== context.session.id
);

await context.replyText('好哦~好哦~*1');
}

使用 Handler 類別

Bottender 有內建可以用來組裝規則的 Handler 類別,蠻適合用在卡米狗這樣的對話情境。例如,我們可以用 LineHandler 進行這樣的改寫:

const { LineHandler } = require('bottender');const builder = new LineHandler()
.onText(/^卡米狗學說話;([^;]+);([^;]+)$/, async context => {
// 學說話指令的程式碼
})
.onText(/^卡米狗忘記;([^;]+)$/, async context => {
// 忘記指令的程式碼
})
.onText(async context => {
// 回話的程式碼
});

bot.onEvent(builder);

這樣就讓整個程式好管理一些。

有很多方式可以整理程式碼,Handler 類別使用的 Builder 設計模式是其中很簡單明瞭的一種。

這個範例的完整程式碼都可以在 bottender-kamigo-example 找到。

跟 Bottender 或是 Chatbot 開發相關的討論除了上 GitHub 開 Issue 以外,也可以到我們的 Discord Server 跟大家一起討論。

優拓資訊 (Yoctol Info Inc.)

At YOCTOL, We AI Your Business by Bot.

優拓為新銳 AI 團隊,利用自行研發的機器人框架、自然語意理解、網路爬蟲、推薦引擎,為企業提供全方位的商務機器人解決方案,不僅可以即時回應顧客的客服需求,也能主動推播個人化商品推薦,提昇企業經營效能。

若您有相關業務需求或是任何建議、疑問,都歡迎寄信至 service@yoctol.com,我們將盡速與您聯繫,期待您的來信!

--

--

C.T. Lin
YOCTOL.AI

Architect @ Dcard. Author of Electron React Boilerplate and Bottender. JavaScript Developer.