Day 4 - 讓惠惠傳送文字、圖片和貼圖


這是「給不會寫程式的人的惠惠機器人開發攻略」的第四篇。

這一篇教學是要製作聊天機器人最關鍵的一篇了。會介紹 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 請求。它會回傳一個名為HTTPResponseClass

Google Apps Script 官方文件有更詳細的說明。

訊息傳送的原理

使用者在傳送訊息之後,哈哈姆特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
      ;
    });
  };
}

結語

這是第四篇的「給不會寫程式的人的惠惠機器人開發攻略」。
下一篇就能教惠惠說話了。

上一篇 | 回到系列文 | 下一篇

#Chatbot #Google Apps Script







你可能感興趣的文章

Android Keyboard 顯示管理

Android Keyboard 顯示管理

用React框架重構PHP網頁心得

用React框架重構PHP網頁心得

謂詞邏輯(Predicate Logic)

謂詞邏輯(Predicate Logic)






留言討論