這是「給不會寫程式的人的惠惠機器人開發攻略」的第四篇。
這一篇教學是要製作聊天機器人最關鍵的一篇了。會介紹 JSON、傳送訊息的原理、如何處理接收到的訊息、還有一堆雜七雜八的功能。
JSON 是什麼
JSON 是一種輕量級的資料交換格式。這是維基百科上面的範例。
{
"firstName": "John",
"lastName": "Smith",
"sex": "male",
"age": 25,
"address":
{
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
},
"phoneNumber":
[
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}
你會發現 JSON 長得跟 Javascript Object 超級像的,其實 JSON 的全名就是 JavaScript Object Notation。GAS 傳訊息到哈哈姆特、以及從哈哈姆特接收訊息,都會使用到 JSON 的格式。
像這個是哈哈姆特會傳送的 JSON 格式。
{
"botid":BOT_ID,
"time":1512353744843,
"messaging":[
{
"sender_id":SENDER_ID,
"message":{
"text":"Hello~"
}
}
]
}
然後這個是我們要傳送訊息到哈哈姆特的 JSON 格式。
{
"recipient":{
"id":"<SENDER_ID>"
},
"message":{
"type":"text",
"text":"你都知道了,柚子要失業了,嗚"
}
}
傳送和接收訊息
doPost(e)
和 botReply()
是聊天機器人運作兩個最關鍵的函式。把這些全都丟到 GAS 裡面吧。
function doPost(e) {
var postContents = JSON.parse(e.postData.contents); // "e.postData.contents" 這裡使用了 Dot Notation
var msg = postContents.messaging[0].message.text; // msg 是使用者輸入的訊息
senderId = postContents.messaging[0].sender_id; // senderId 是使用者的巴哈ID
if (msg == "你好") {
botReply("吾名為惠惠。紅魔族第一魔法師,操縱最強爆裂魔法之人。");
} else if (msg == "再見") {
botReply("再見");
} else if (msg == "惠惠") {
botReply("幹嘛");
} else {
botReply("Explosion!");
}
}
function botReply(replyMsg){
// 這是一個 Object
var sendObj = {
"recipient":{
"id":senderId // 還記得我們把 senderId 宣告成全域變數嗎?
},
"message":{
"type":"text",
"text":replyMsg
}
};
// 這是一個函式
UrlFetchApp.fetch(URL, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8'
},
'method': 'post',
'payload': JSON.stringify(sendObj) // 讓 sendObj 變字串(stringify)
});
}
doPost(e)
是當 GAS 接收到 POST 請求之後會執行的函式。
botReply()
則是傳送訊息的函式。
UrlFetchApp.fetch()
可以傳送 GET 或 POST 請求。它會回傳一個名為HTTPResponse
的 Class。
Google Apps Script 官方文件有更詳細的說明。
- doPost: https://developers.google.com/apps-script/guides/web
- UrlFetchApp: https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app
訊息傳送的原理
使用者在傳送訊息之後,哈哈姆特API 會透過 Webhook 傳送 POST 請求給 GAS,在這個 POST 請求裡面包含了使用者的巴哈ID以及使用者輸入的訊息。
在 GAS 接收到 POST 請求之後,就會執行 doPost(e)
裡面的程式碼。所以你可以在裡面設計當機器人接收到什麼訊息時要回覆什麼。最後使用 botReply()
這個函式來傳送訊息給哈哈姆特API,使用者就能看到設計好的回覆內容了。
這個是大概的流程圖。
設計回覆的訊息
接下來就開始可以設計惠惠機器人要回覆的訊息了,這個是剛剛的程式碼。
if (msg == "你好") {
botReply("吾名為惠惠。紅魔族第一魔法師,操縱最強爆裂魔法之人。");
} else if (msg == "再見") {
botReply("再見");
} else if (msg == "惠惠") {
botReply("幹嘛");
} else {
botReply("Explosion!");
}
如果使用者輸入了像是"你好阿"之類的訊息,那用 == 就讀不到了。可以用indexOf()
來檢查使用者傳送的訊息有沒有包含"你好"。
if (msg.indexOf('你好') > -1 || msg.indexOf('哈囉') > -1) {
botReply("吾名為惠惠。紅魔族第一魔法師,操縱最強爆裂魔法之人。");
}
可以設計一個/help
的指令,讓使用者查詢聊天機器人有什麼功能。類似這樣:
// ES5
else if (msg == '/help'){
botReply("<-- 主要功能 -->/music 推薦你一首歌\n/game 推薦你一款遊戲\n/anime 推薦你一部動漫");
}
使用者可能會輸入錯誤的指令,指令通常會以'/'開頭。如果沒有下面這一段,就會執行 else 區塊的程式碼了。
else if (msg[0] == '/'){ // msg[0] 是 msg 的第0個字
botReply("找不到這個指令");
}
我們可以改良一下 else 的部份,寫一個隨機回覆的函式吧。
else{
randomReply();
}
//得到隨機數字
function getRandom(min, max){
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 隨機回覆
function randomReply(){
var randomReplyList = [
"天地之火咆哮之時,吾乃萬象相應之理,化作崩壞與破壞之別名,業火的鐵錘降臨吾掌!\nExplosion!",
"比黑色更黑,比黑暗更深的漆黑,再次寄託吾真紅的金光吧,覺醒之時的到來,荒繆教會的墮落章理,化作無形的扭曲,顯現吧!\nExplosion!",
"被光明籠罩的漆黑啊,身披夜衣的爆炎啊,於終焉王國之地,引渡力量根源之物啊,在吾面前展現吧!\nExplosion!"
];
botReply(randomReplyList[getRandom(0,randomReplyList.length-1)]);
}
這樣就能一直加上隨機的回覆了。
讓惠惠傳送圖片
其實超簡單的,直接傳圖片網址就好,gif 也可以傳。
botReply("https://i.imgur.com/XmBRqEg.gif");
比較難的是"把上傳圖片的過程自動化",會在後面的篇幅介紹。
讓惠惠傳送貼圖
在這裡可以看到所有可以傳送的貼圖。
https://haha.gamer.com.tw/bot_sticker_list.php
傳送貼圖可以用這個函式:
function botSticker(stickerGroup,stickerId){
var sendObj = {
"recipient":{
"id":senderId
},
"message":{
"type":"sticker",
"sticker_group":stickerGroup,
"sticker_id":stickerId
}
};
UrlFetchApp.fetch(URL, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8'
},
'method': 'post',
'payload': JSON.stringify(sendObj)
});
}
這樣就能傳送貼圖了。
其他功能
這些是比較進階的功能。
特殊介面
可以參考這些文章。作者:Hayate
[教學]做一個不用打指令的哈哈姆特機器人(使用特殊介面)
[教學]做一個簡單的養成機器人(使用特殊介面)
Hayate 的小屋還有其他更厲害的教學!
檢查有沒有重覆的字。
// https://stackoverflow.com/questions/33656708/check-for-repeated-characters-in-a-string-javascript
function checkNotRepeat(str){
for (var i=0; i<str.length; i++) {
if ( str.indexOf(str[i]) !== str.lastIndexOf(str[i]) ) {
return false; // has repeats
}
}
return true;
}
把上傳圖片的過程自動化
這個是把圖片從 Google 雲端硬碟上傳到哈哈姆特伺服器的函式。
function imageUpload(imageName){
var files = DriveApp.getFilesByName('imageName');
var fileBlob = files.next().getBlob();
var formData = {
"filedata": fileBlob
};
var settings = {
"method": "POST",
'payload' : formData
};
var uploadURL = "https://us-central1-hahamut-8888.cloudfunctions.net/imagePush?bot_id=<你的bot_id>&access_token=<你的access_token>"
var response = UrlFetchApp.fetch(uploadURL, settings).getContentText();
var imageParams = JSON.parse(response);
Logger.log(imageParams); // imageParams就是圖片的參數了
return imageParams;
}
因為上傳圖片後回傳的是圖片ID,不是一個URL,就要用另外的函式才能傳送圖片。
這個是上傳圖片後回傳的圖片參數。
{"id":"a32e08c82b51ba4a687ef9d83f121c15","ext":"GIF","width":400,"height":225};
這個是傳送圖片的函式。
function sendImage(imageParams){
var sendObj = {
"recipient":{
"id":senderId
},
"message":{
"type":"img",
"id": imageParams['id'],
"ext": imageParams['ext'],
"width": imageParams['width'],
"height": imageParams['height']
}
};
UrlFetchApp.fetch(URL, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8'
},
'method': 'post',
'payload': JSON.stringify(sendObj)
});
}
Formatting String
//https://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format/4673436#4673436
if (!String.prototype.format) {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined'
? args[number]
: match
;
});
};
}
結語
這是第四篇的「給不會寫程式的人的惠惠機器人開發攻略」。
下一篇就能教惠惠說話了。