使用Golang串接哈哈姆特

文章最下面有原碼 首先建立一個http server
        func main() {
                 //開啟伺服器
                 http.HandleFunc("/", handler)

                 port := os.Getenv("PORT")
                 if port == "" {
                          log.Fatal("$PORT must be set")
                 }

                 address := ":" + port
                 log.Println("Starting server on address", address)
                 err := http.ListenAndServe(address, nil)
                 if err != nil {
                    panic(err)
                 }
        }
根據Golang的HandleFunc的官方文件,建立一個handler
func handler(w http.ResponseWriter, r *http.Request) {

 // Do: 結束後執行 關閉httpRequest
 defer r.Body.Close()

 // Do: Read body
 b, err := ioutil.ReadAll(r.Body)
 if err != nil {
  http.Error(w, err.Error(), 500)
  return
 }

 // Do: 傳回200
 request200(w)

 // Do: 驗證簽章
 err = verifyWebhook(r, b)
 if err != nil {
  http.Error(w, err.Error(), 500)
  return
 }

 // Do: Decode body 的東西
 var msg Message
 err = json.Unmarshal(b, &msg)
 if err != nil {
  http.Error(w, err.Error(), 500)
  return
 }

 // Do: 剩下靠自己
 fmt.Println("bot_id=", msg.BotID)
 fmt.Println("time=", msg.Time)
 fmt.Println("senderid=", msg.Messaging[0].SenderID)
 fmt.Println("text=", msg.Messaging[0].Message.Text)

 handleMessage(w, msg.Messaging[0].Message.Text, msg.Messaging[0].SenderID)

}
哈哈姆特需要我們傳回一個status ok,傳回200,也可以用http內建的StatusOK的const
 func request200(w http.ResponseWriter) {
  w.WriteHeader(200)
 }
接下來需要驗證webhook,需要使用hmac-sha1,我們使用golang內建的crypto包裡的函式來雜湊伺服器端傳來的body和bot的APP_SECRET來雜湊比對和伺服器傳來的密鑰是否一樣 這裡我採用判斷後不符合傳回一個error來判斷
type VerifyError struct {
 When time.Time
 What string
}

func (e *VerifyError) Error() string {
 fmt.Println("Error:", e.What)
 return fmt.Sprintf("at %v, %s", e.When, e.What)
}

func verifyWebhook(r *http.Request, body []byte) error {

 //Note: 取得x-baha-data-signature開頭的Header
 headerget := r.Header.Get("x-baha-data-signature")
 key := []byte(headerget[5:])
 fmt.Println("key=", key)

 //Note: 使用HMAC-SHA1雜湊APP_SECRET和傳來的Body
 mac := hmac.New(sha1.New, []byte(APP_SECRET))
 mac.Write(body)
 fmt.Printf("%x\n", mac.Sum(nil))

 //轉為字串比較
 str1 := string(key[:])
 str2 := fmt.Sprintf("%x", mac.Sum(nil))

 fmt.Println("str1=", str1)
 fmt.Println("str2=", str2)

    if str1 != str2 {
        return &VerifyError{
               time.Now(),
               "Verify error",
                }
     } else {
      return nil
     }

}
接下來就能開心的接收和傳訊息惹!
先定義用來裝json東西的struct,用法跟gson差不多
先看看他們給的json長什麼樣子

拆解成這些struct bean
type Text struct {
 Text string `json:"text"`
}

type Messaging struct {
 SenderID string `json:"sender_id"`
 Message  Text   `json:"message"`
}

type Message struct {
 BotID     string      `json:"botid"`
 Time      int         `json:"time"`
 Messaging []Messaging `json:"messaging"`
}
把伺服器端傳來的Body的Json字串用Golang內建的包來解析放入struct
 
    var msg Message
    err = json.Unmarshal(b, &msg)
    if err != nil {
          http.Error(w, err.Error(), 500)
          return
     }
接下來就能大大方方的用啦!
 fmt.Println("bot_id=", msg.BotID)
 fmt.Println("time=", msg.Time)
 fmt.Println("senderid=", msg.Messaging[0].SenderID)
 fmt.Println("text=", msg.Messaging[0].Message.Text)
而傳訊息也沒有很難!先定義好使用者傳送什麼字觸發什麼功能
func handleMessage(w http.ResponseWriter, msg string, senderid string) {

     switch msg {

         case "打招呼":
              sendMsg(w, senderid, "嗨嗨OwO")

        case "發貼圖":
              sendSticker(w, senderid, "28", "09")

         case "發圖片":
              sendImg(w, senderid, "src/img/ralsei.png")

         default:
              sendMsg(w, senderid, "我聽不懂==")

     }

}
先看傳訊息的json包的內容

定義struct bean
type Recipient struct {
 ID string `json:"id"`
}

type MessageingSend struct {
 Type string `json:"type"`
 Text string `json:"text"`
}

type MessageSend struct {
 MyRecipient      Recipient      `json:"recipient"`
 MyMessageingSend MessageingSend `json:"message"`
}

接下來都有註解了 自己看吧= =
func sendMsg(w http.ResponseWriter, senderID string, msg string) {

 // 包成json
 r := Recipient{senderID}
 m := MessageingSend{"text", msg}
 ms := MessageSend{r, m}
 json, err := json.Marshal(ms)
 if err != nil {
  http.Error(w, err.Error(), 500)
  return
 }

 // 請求Post
 req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", POST_URL, ACCESS_TOKEN), bytes.NewBuffer(json))
 if err != nil {
  http.Error(w, err.Error(), 500)
  return
 }

 // 設置header
 req.Header.Set("X-Custom-Header", "badfox-sender")
 req.Header.Set("Content-Type", "application/json")

 // For control over HTTP client headers
 client := &http.Client{}
 resp, err := client.Do(req)
 if err != nil {
  panic(err)
 }

 //取得哈哈的伺服器取得狀態
 body, _ := ioutil.ReadAll(resp.Body)
 fmt.Println("Response Status:", resp.Status)
 fmt.Println("Response Headers:", resp.Header)
 fmt.Println("Response Body:", string(body))

}
傳貼圖也是相同的道理,就不再貼了

而傳圖片稍微有點複雜,可以看看這篇
豬腳的文章也說得很清楚

接下來去用Dep來控制專案,要使用到cmd指令,用Vs code內建的就好了
下載dep
$go get -u github.com/golang/dep/cmd/dep
初始化
$dep init
資料夾會出現 Gopkg.toml 和 Gopkg.lock

接著佈署到平台上面,範例是heroku
下載 Heroku command line
登入
$heroku login
clone 專案
$heroku git:clone -a Easy-hahamut-bot
移到clone的專案資料夾
$cd Easy-hahamut-bot
git 到緩存區
$git add .
提交 git
$git commit -am "my first hahamut bot"
佈署到heroku
$git push heroku master
Github源碼連結

留言