創作內容

144 GP

[達人專欄] 製作哈哈姆特不 EY 的聊天機器人!架設原理概述

作者:解凍豬腳│2019-02-05 03:17:38│巴幣:434│人氣:6494

  幾天前,站方發布關於哈哈姆特機器人後台的公告,於是當天半夜我馬上埋在電腦前面研究了一番。

  從來沒有開發過聊天機器人的我,原本看到公告的時候連什麼是 Webhook 都不知道,直到現在終於把哈哈姆特接收訊息、傳送訊息、傳送貼圖、傳送圖片的 API 都摸透,甚至還能把機器人拿去往外串接 MySQL、Dialogflow、做網路爬蟲,可以算是有點心得了。

  有鑑於哈哈姆特不 EY 聊天機器人的說明文件實在太簡潔,某些細節即使找有經驗的工程師都不見得能完全看懂,所以在我確定可以寫出一個機器人以後,決定發這篇文章分享一下我的步驟。

  本篇教學適用於「已經會至少一種可以拿來做後端開發的程式語言(例如 JavaScript、Golang、PHP、Python 等),並曾有後端開發經驗,知道如何處理 GET、POST 方法的 HTTP 請求資料」者。

  首先談一下哈哈姆特不 EY 的架構。哈哈姆特不 EY 這次開放的機器人服務,並不是讓使用者可以直接在巴哈上面寫機器人,而是你需要另外找到一台屬於自己的主機,然後讓巴哈跟那台主機互相傳資料溝通,所以哈哈姆特不 EY 可以說只是一個媒介,協助讓機器人跟使用者互動。

  在這個架構裡面,巴哈姆特設有兩個 API:「傳送訊息 API」跟「傳送圖片 API」,前者負責幫機器人發送純文字訊息或哈哈姆特貼圖的資訊,後者則是負責幫機器人傳送圖片,大概長這樣:



  假設今天有個使用者 johnny860726 想跟哈哈姆特不 EY 的機器人 bot@68 聊天,這樣當使用者傳了一句 hello 的訊息過去的時候,哈哈姆特會把這則訊息處理成以下的 JSON 資料,結構如下:

{
    "botid": "bot@68",
    "time": 1512353744843,
    "messaging": [
        {
            "sender_id": "johnny860726",
            "message": {
                "text": "Hello~"
            }
        }
    ]
}

  處理好以後,哈哈姆特的伺服器會把這筆資料用 HTTP 的 POST 方法傳送到機器人上面,傳送對象的地址就是後台需要設定的 Webhook 了,簡單來說 Webhook = 你的機器人接收哈哈姆特訊息的地方。

  請注意,這裡 Webhook 要求使用 HTTPS,所以你一定要有自己的網域,或者是把機器人放在網路上有提供 HTTPS 的雲端主機商。

  同時,你會注意到後台有兩個字串,一個叫做 Access Token、一個叫做 App Secret,請注意這兩筆資料都很重要,絕對不能夠外洩!如果讓別人知道了這筆 Access Token 的話,這樣別人就能夠用你機器人的身分傳訊息給任意巴友了;如果讓別人知道了 App Secret 的話,這樣別人就能夠偽裝成任意巴友,傳送假的訊息資料給你,讓你以為這筆資料真的是巴哈姆特傳來的。所以一旦這兩組都洩漏出去,你的機器人控制權就會落入別人手中。

  App Secret 如何發揮保護的作用呢?巴哈姆特會使用 HMAC-SHA1 算法,將訊息資料做一次加密雜湊演算,得到一個訊息摘要,並且把這個訊息摘要設為 Header 裡面 X-Baha-Data-Signature 的值,用以驗證來源。

  舉個例:假設今天 Sega 想要寄一封信給 Johnny,內容是 4876348763,而郵局的人跟 Johnny 事先約定好了「內容 × 687,再除以 1000,得到的餘數就是訊息校驗值」,這樣當 Sega 寄信給 Johnny 以前,Sega 會計算:

  4876348763 × 687 = 3350051600181
  3350051600181 mod 1000 = 181

  然後 Sega 把「4876348763、Sega、181」這些資訊一起寄給 Johnny,這樣 Johnny 只要再做一次一樣的計算步驟:

  4876348763 × 687 = 3350051600181
  3350051600181 mod 1000 = 181

  這樣 Johnny 就可以確認信箱裡的這個 Sega 是經過郵局驗證,而不是其他人了,因為其他人並不知道密碼是 687,這些外人如果用其他的數字去乘,就會得出不同的結果,就算瞎猜那三位數也只有千分之一的機率可以偽造成功,這就是驗證的基本原理。

  倘若 687 這個密碼落入第三者手中,這第三者做一樣的步驟(因為 HMAC-SHA1 的算法是公開的,大家都知道這個計算流程),然後把「8787878787、Sega、669」傳給 Johnny,就會害 Johnny 以為這封「8787878787」的信真的是 Sega 本人寄來的。

  (註:8787878787 × 687 mod 1000 = 669)

  所以 App Secret 就好像上例的 687 一樣,希望這樣說明有讓大家看了比較好理解。當然 HMAC-SHA1 實際的計算流程比這個例子還要複雜太多,若想單靠瞎猜而偽造成功的機率只有 1.46×10^48 分之一,至於如何產生校驗碼就不贅述了,有興趣的可以自己去研究一下。

  這裡就要注意,哈哈姆特傳來的 request 裡面,Content-Type 屬性是 application/json,這跟一般 form 表單的 key-value 格式不同,所以當你的機器人收到 request 的時候,是用 request.Body 來取得巴哈傳來的 JSON 原始資料(等等我會給範例)。

  既然理解了如何讓機器人收訊息,我們就先動手實作吧!這裡我使用的語言是 Go(我習慣稱 Golang)、主機是 Heroku、編輯器是 VS Code、套件管理器是 Govendor。

  Heroku 是個可以放伺服器的網站,人人都能申請一個免費的 Heroku 帳號。免費的用戶可以裝五個不同的 App,每個月可以有讓 App 運作 450 個小時的額度(兩個 App 同時運作的話,消耗速度就會變成兩倍),要注意的是如果連續 30 分鐘沒動靜的話,主機會暫時進入休眠狀態(但會暫停消耗額度),這時候若有新的 request 出現,就會需要花一點點時間啟動,所以如果遇到休眠狀態的主機,會有一段時間稍微有點慢,然後才恢復正常速度。

  使用 Heroku + Golang 的環境,你至少需要安裝以下幾樣東西:
  1. Git
  2. Heroku CLI
  3. Golang 執行環境

  以上三者安裝好以後,先到 %GOPATH% 的位置,像我位置就設在 C:UsersJohnnygo(Johnny 是我電腦使用者帳戶的名字)。在這裡新建資料夾,命名為你想要取的專案名字,接著進去新增一個檔案叫做 main.go。

  創好以後,可以按 [Windows] + [R] 快捷鍵呼叫執行窗來執行 cmd,或是如果你已經是習慣使用 VS Code 的開發者,也可以在 VS Code 上用 [Ctrl] + [~] 的快捷鍵來開啟 command 畫面。

  畫面跳出來後,執行指令:

go get -u -v github.com/kardianos/govendor

  這樣就安裝好 Govendor 套件管理器了。等到安裝好以後令 command 開啟你的專案資料夾,執行指令:

git init

  接著再執行指令:

govendor init

  這樣你的專案就可以初始化成 Heroku 可以懂的專案架構,之後機器人的 code 就都可以寫在 main.go 裡面了。

  畢竟本篇主要不是 Golang 教學,所以就不講 Golang 本身語法之類的基本內容了,只講一些必要的東西。

  距離上次用 Heroku 跟 Git 已經是 2017 年的事情,剛開始為了讓機器人可以在 Heroku 上面正常運作,我也頭痛很久,才知道如果要在 Heroku 上面跑 Golang 的程式,Govendor 是必要的東西。

  倘若你有 import 來自第三方的套件(例如 github.com/PuerkitoBio/goquery),記得在跑完 go get 的指令以後,用 govendor fetch github.com/PuerkitoBio/goquery 指令來把這個套件裝進 vendor/vendor.json,如此一來之後把程式 push 上去的時候,Heroku 那邊的環境才能知道你想要在程式裡面加入這個套件。

  這時候在 command 上面執行指令:

heroku login

  它會要求你隨意點選鍵盤的任意按鍵,按下去,然後等網頁跳出來以後再按登入就可以了。





  接下來開始寫 code 吧!一般我們在 Golang 上面架設 HTTP server 的時候,port 都可以自己設定,但若要在 Heroku 上面架 server 的話,就必須要獲得 PORT 環境變數的內容,這個內容屆時會由 Heroku 那邊決定。

  然後我們綁一個稱為 receiveMsg 的 function 上去(函式的名字該取什麼,你爽就好,左邊的 "/gamerBotReceiveMsg" 則決定了之後讓巴哈呼叫機器人用的網址):

http.HandleFunc("/gamerBotReceiveMsg", receiveMsg)

  接著來寫 receiveMsg 函式的內容吧:

sign := r.Header.Get("X-Baha-Data-Signature")
body, _ := ioutil.ReadAll(r.Body)
if sign == hmacSign(body, appSecret) {
    fmt.Println("Message received: ", string(body))
} else {
    fmt.Println("Fake request.")
}

  為了方便看 code,我把計算 HMAC-SHA1 的過程獨立寫成一個函式 hmacSign 了:

func hmacSign(str []byte, key string) string {
    h := sha1.New()
    io.WriteString(h, string(str))
    mac := hmac.New(sha1.New, []byte(key))
    mac.Write([]byte(str))
    return fmt.Sprintf("sha1=%x", mac.Sum(nil))
}

  因為巴哈傳來的 X-Baha-Data-Signature 開頭還會有一個「sha1=」,所以計算出校驗值以後要再補上。

  ListenAndServe 的步驟我就不贅述,做到這步的話 code 會長這樣:範例程式碼

  千萬別單純複製貼上就想拿去跑啊,範例程式碼裡面的 appSecret 是我隨便亂打的,這要改成你機器人的 App Secret 內容。

  儲存好 code 以後,再回剛剛的 command 畫面執行指令:

heroku create upktest --buildpack heroku/go

  upktest 這個位置填的是 App 名字,可以自己取,把它改成你想取的名字吧,這會影響到之後你 app 的網域名稱。

  像我執行完以後,我的 App 位置就會在 https://upktest.herokuapp.com/,你也可以直接開 Heroku 網站的控制台查看 App 的相關資訊,但如果你想要把 code 上傳到 Heroku 的主機,就一定要用 command 執行 git 來把程式碼推上去。

  如何把程式碼推上去呢?依序執行下列三條指令:

git add .
git commit -m "ver01"
git push heroku master

  ver01 這個地方可以隨便填。等它跑完,至少出現 https://upktest.herokuapp.com/ deployed to Heroku 之類的內容,你的程式就差不多生效了。這時候你可以另外開一個 command 視窗,一樣在專案資料夾裡執行指令:

heroku logs --tail

  就可以從遠端即時看到你 App 的 console 內容。

  請注意,之後如果你要修改程式的話,每次修改完、存檔以後都要再執行一次上面三條 git 的指令。

  回到哈哈姆特 BOT 的開發者後台。建立機器人並把 Webhook 欄位設為:

https://upktest.herokuapp.com/gamerBotReceiveMsg

  名字一樣只是舉例,請把 upktest 改為你的 App 名字。這裡的 gamerBotReceiveMsg 就是剛剛在 code 裡面談到可以自己任意取名的 route 位置了。

  接著開啟哈哈姆特,對你的機器人隨便傳個訊息:



  再來看看遠端的 console,成功了!不用幾行程式碼就可以接收到機器人的訊息,有沒有很簡單?你已經串接好一半了。再來想如何把 JSON 解碼、處理、判斷,做出你理想中的機器人,那就是你的事。

  接下來談談傳送訊息吧。回去看看我剛才貼的架構圖就會知道,如果要傳送訊息的話就要傳給哈哈姆特指定好的 API,這個 API 的位置是:

https://us-central1-hahamut-8888.cloudfunctions.net/messagePush?access_token=5f566f5a14a3de1b6608

  當然這裡的 access_token 要改成自己機器人的 Access Token,這樣哈哈姆特傳送訊息的 API 才能知道是你的機器人想傳訊息(而不是其他人的機器人)。

  由機器人傳出去的訊息也要用 JSON 的格式,如下:
{
    "recipient": {
        "id": "<sender_id>"
    },
    "message": {
        "type": "text",
        "text": "你好我是機器人"
    }
}

  傳送貼圖也是傳到這個 API,但格式是像這樣:
{
    "recipient": {
        "id": "<sender_id>"
    },
    "message": {
        "type": "sticker",
        "sticker_group": "13",
        "sticker_id": "06"
    }
}

  以上格式表示了「向 <sender_id> 這個巴哈帳號傳送第 13 組的第 6 張貼圖」,貼圖的編號資訊可以在哈哈姆特不 EY 的機器人開發者後台看到,不在列表上的貼圖就不能傳了。

  要解析從哈哈姆特傳來的 JSON 原始訊息會麻煩一些,因為以 Golang 來說,還需要另外寫 struct 定義出這個 JSON 的結構,然後再把整段原始訊息 unmarshal,所以佛心的豬腳就乾脆幫你寫完整個 struct,連同傳送訊息、傳送貼圖的函式也都寫好了:範例在這裡

  傳送訊息:sendMessage(0, "傳送對象", "訊息內容")
  傳送貼圖:sendMessage(1, "傳送對象", "貼圖組編號,貼圖編號")

  至少上面這個範例已經具有了「如果傳『你好』給機器人,機器人就會回應『你好我是機器人』;如果傳其他訊息給機器人,機器人就會回應問號貼圖」的功能。

  根於這樣的基礎,你就可以輕易地設定一些指令來讓使用者控制機器人了。

  想讓機器人傳送圖片就比較麻煩。你需要讓程式去開啟圖片檔案,然後再把圖片的二進位資料連同其他資訊,用 multipart 的方式傳到站方設的傳送圖片專用 API:

https://us-central1-hahamut-8888.cloudfunctions.net/ImgmessagePush?access_token=5f566f5a14a3de1b6608

  我也初步簡化了,只要 sendImg("傳送對象", "檔案路徑") 就可以傳圖片:



  寫好的範例程式碼:這裡

  其中讀取圖片檔案跟上傳的步驟,來源是在這裡

  若想進階一點的話,可以再把機器人往外嵌更多東西,例如我目前寫的機器人就往外連了一個 MySQL 伺服器來存資料,又嵌了 Dialogflow 用語意分析來處理使用者傳的文字意思,還做了個功能是可以提供使用者目前水桶狀態資訊的服務,架構就變成這樣:





  (註:本人實際並未受到水桶處分,僅為隨意挑選一巴友帳號作為查詢測試樣本)

  所以,只要你的想像力豐富的話,機器人不只可以依照你設定的規則來應答,還可以叫他幫你查股票,甚至幫你推薦股票呢。有了這種 Webhook 的架構,能做的事情就很多。

  以上就是豬腳最近寫哈哈姆特不 EY 機器人的心得分享,希望這篇文章可以幫想要寫機器人的開發者們減輕一些負擔,至少不用一直碰壁。

  感謝你的觀看,我是豬腳,我們下次見~

  本文縮圖來源:哈哈姆特不 EY 官方機器人桃子 BOT 大圖
  相關哈啦板:《哈哈姆特不 EY》Bot 設計交流
 
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4283806
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:巴哈|巴哈姆特|哈哈姆特|哈哈姆特不EY|聊天機器人|機器人|程式設計|Golang

留言共 34 篇留言

真實悖論
豬腳摸摸

02-05 03:22

路過的西瓜
大佬

02-05 03:23

黃米
讚喔

02-05 03:23

Kurt Lowe
JSON 癢,好吃 _(:3 」∠ ❀)_

02-05 03:25

積極努力認真向上
仰望大佬

02-05 04:25

風に薫る夏の記憶
打那麼多他x.... 沒 我是說 大佬太神啦

02-05 04:42

ひなのの無夜Δ.
明明是資管人 卻看不懂你說了甚麼Orz

02-05 04:54

非您莫屬✌
請收下我的一拜

02-05 05:17

人…人家才不大聲呢
窩根本看不懂 那我可以看你跟大濕ii嗎?

02-05 05:58

解凍豬腳
你可以跟我ii02-06 09:23
秘教徒
不... 不明覺厲

02-05 08:52

正能量
太棒了

02-05 09:57

緋渚綾瀨
有神快拜(完全看不懂XD

02-05 10:10

玥晴 Luna (#ΦωΦ#)
大師可以收下我的膝蓋嗎?

02-05 10:44

皮皮桑
嗯嗯我完全懂了(點頭

02-05 11:01

LOVe高橋李依
這是python還是C啊?

02-05 12:18

解凍豬腳
都不是,這是一個叫做 Golang 的程式語言,由 Google 開發02-06 09:22
雪之王女‧F‧巧可奈
完全外行只能純推><

02-05 12:18

シッコク
你寫Golang大概多久? 都拿來開發什麼R
我最近在想要不要跳過去學一下
前陣子去聽雷亞的徵才說明會有提到他們內部會用Golang開發

02-05 14:26

解凍豬腳
第一次寫 Golang 是去年 11 月的事情,用了 Golang 以後我從此棄用 Python,真他媽讚,記得安裝 VS code 上面的 Go 擴充功能

以前沒寫過 Golang 但有程式設計底子的話,邊寫邊查大約兩到三天就能學會了

除了上次發的 YouTube-OBS 外掛之外,也有用 Golang 寫自己要用的巴哈姆特通知小助手,直接連動 Growl 推播通知給我,讓我能一邊打 LOL 一邊看巴哈通知

之後想把以前做的小屋創作備份工具拿到 Golang 上面重寫一次02-06 09:41
芊芊∣ㄑㄑ
資訊白痴完全看不懂
不過專業給推

02-05 18:49

葵花熊-噁熊模式
有夠猛

02-05 20:41

夜言葉
請收下我的膝蓋

02-07 13:02

衝動的笨蛋

02-07 20:23

櫻咲 萌
有可以擋站方機器人的指令嗎 聊天室被洗 有點煩

02-09 12:36

保安妹妹❤躺平無限好
可以寫個豬腳機器人每天給我通知嗎

02-15 17:22

八龍易主(´・ω・`)つ
大概知道在講什麼
還沒學GOLANG
目前應該不打算學
只想至少先把Javascript先弄專精
下次我過來留言 應該就是看懂你這篇文章
要來發問的時候了

03-27 09:46

媽媽樂博士
怎麼感覺golang比拍森還難r 目前正在用拍森寫

04-06 04:54

解凍豬腳
Golang 真的需要一些底子,有些地方特別嚴謹、有限制,而 Python 的特性本來就是因為很簡單好上手才會受歡迎

當初我用 Python 做網路爬蟲的時候覺得很方便,後來碰了 Golang 以後發現 Golang 在很多方面的表現更棒,所以就選擇 Golang 了,畢竟自己都已經寫過十幾年的 code,程式語言上手的難度對我來說就不怎麼重要了04-06 05:02
媽媽樂博士
想請問一下
為什麼
func fib2(n int) int {
if n <= 2 {
return n
}
return fib2(n-1) + fib2(n-2)
}
這一段費氏數列的遞迴
為什麼n<=2數值才正確
像是其他語言python,c,java
都是用<2或 n==0 or 1

04-06 20:46

解凍豬腳
費氏數列是
F(0) = 0
F(1) = 1
F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8

是這段 code 有問題吧,只用這個 function 的話就會變成 0, 1, 2, 3, 5, 8 了,不然的話我想你給的這段應該只是被挪用的一部分而已(畢竟刻意取名叫做 fib2)04-06 20:53

卡等ASP.Net配Visual Basic、JavaScript、PHP和python版的教學

06-06 21:45

ゞ雷雷✿°
電腦白痴路過,不明覺厲 >///////< 第1次知道這種東西就是了,我家豬腳果然厲害

06-09 19:05

牙控灰機
趕快留言 免得人家以為我看不懂

06-09 19:08

Amenice
大腦在星爆

06-09 19:16

FBI:麥當勞歡樂送
這個有機器學習嗎

06-10 09:06

解凍豬腳
Dialogflow 那邊本來就有內建機器學習,但跟巴哈姆特這邊本身無關06-15 01:20
每個男生都該有姊姊
你寫的好容易懂 笑死官方文件寫那什麼鬼 改天NODEJS玩玩看

01-03 15:00

YUKI.N
想請教豬腳大,官方Api有提供遍歷私人聊天室的功能嗎?例如想找一張圖片、或某個人的對話紀錄等等

11-10 22:06

解凍豬腳
就我對巴哈的瞭解是沒有這種專門給一般開發者用的專用 API,你最多只能模仿瀏覽器的行為,重新模擬一次取得聊天室內容的過程來得到對話紀錄

這個做起來極其複雜,你得先帶著已登入巴哈姆特的 session cookie,送一個 GET request 到某個叫做 login_token.php 的地方,得到一個 custom token

接著你要把這個 custom token 送到 Google 專門處理這個東西的 API,得到一個 credential

再來開 web socket 把這個 credential 包成 JSON 格式送到巴哈聊天室的 API 上面,從聊天室的 API 得到 JSON 格式的資料內容

得到資料內容以後你還要看懂他們的格式,他們的參數超級醜,資料欄位幾乎都是 t、d、r、a、b、p 這種只有一個字的名稱

這樣的東西你絕對不會想做的,我唯一能夠建議你的優先方案是放棄11-10 22:29
YUKI.N
看起來真的很麻煩.. 只好先放棄了,感謝豬腳解惑

11-10 22:36

我要留言提醒:您尚未登入,請先登入再留言

144喜歡★johnny860726 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:0203 近況... 後一篇:什麼叫做卯起來押...

追蹤私訊切換新版閱覽

作品資料夾

------------------ (0)

豬腳生活 (1)
日常雜談、巴哈大小事 (193)
煞氣a國中生 (7)
高中生活日誌 (55)
大學生活日誌 (34)
冬令營回憶錄 (19)
也許藏有一些小祕密吧? (3)
各式各樣的開箱文 (11)
貓科動物時間 (15)

------------------ (0)

繪圖創作 (1)
電繪插圖、草稿 (199)
短篇漫畫、單幅標語 (61)
上課太無聊的手繪塗鴉 (8)
不知道該怎麼分類的綜合作品 (18)

文字創作 (1)
草莓兵的國軍紀實 (14)
我與らい的點點滴滴 (12)
那些榮耀的時刻與心跳加速的瞬間 (60)
有感而發的隨筆之作、無法分類的短文 (17)

------------------ (0)

語言學習 (1)
日語:天気がいいから (5)
粵語:唔好再淨係識講粗口喇 (6)
英語:Hey, you! (1)

程式設計及電腦網路 (1)
系列文:跟著豬腳 C 起來 (10)
系列文:論壇網站運作原理 (3)
Go(Golang) (11)
Ruby / RGSS (7)
Visual Basic (13)
JavaScript (1)
各種原理 (17)

思想:多思考一下,世界會更不一樣 (1)
網路經驗、社會觀察 (23)
檸檬庫 (21)

數學:我來拯救你的期中考了 (1)
各類基礎觀念 (5)
國中生也能懂的微積分 (9)
微分方程 (0)

小說:用筆鋒劃出新世界的入口 (1)

繪圖:我也想畫出私巴拉西的美圖 (10)

------------------ (0)

施工中 (22)

不堪回首的痕跡、雜物堆放 (31)

------------------ (0)

未分類 (1)

colanncolann
【繪圖創作】【科嵐工作室】11週年! 2024/4/1 https://home.gamer.com.tw/creationDetail.php?sn=5909405看更多我要大聲說9小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】