DEV Community

D
D

Posted on

1

Golang (heroku) で LINE Bot 作ってみる

追記 (2016/04/12)
Botの機能追加+ソースコードリファクタリングしてるうちにライブラリ化しました
https://github.com/dongri/line-bot-sdk-go
今のところ、text, sticker, image, video簡単に送れます。プロフィールも簡単に取得可能です。


まずコードから。メッセージを送るとそのメッセージをそのまま返すサンプル。下にちゃんとしたやつもあります。


package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "os"
    "time"
)

// ReceivedMessage ...
type ReceivedMessage struct {
    Result []Result `json:"result"`
}

// Result ...
type Result struct {
    ID          string   `json:"id"`
    From        string   `json:"from"`
    FromChannel int      `json:"fromChannel"`
    To          []string `json:"to"`
    ToChannel   int      `json:"toChannel"`
    EventType   string   `json:"eventType"`
    Content     Content  `json:"content"`
}

// SendMessage ..
type SendMessage struct {
    To        []string `json:"to"`
    ToChannel int      `json:"toChannel"`
    EventType string   `json:"eventType"`
    Content   Content  `json:"content"`
}

// Content ...
type Content struct {
    ID          string   `json:"id"`
    ContentType int      `json:"contentType"`
    From        string   `json:"from"`
    CreatedTime int      `json:"createdTime"`
    To          []string `json:"to"`
    ToType      int      `json:"toType"`
    Text        string   `json:"text"`
}

// const ...
const (
    EndPoint  = "https://trialbot-api.line.me"
    ToChannel = 1383378250
    EventType = "138311608800106203"
)

func main() {
    http.HandleFunc("/", helloHandler)
    http.HandleFunc("/callback", callbackHandler)
    port := os.Getenv("PORT")
    addr := fmt.Sprintf(":%s", port)
    http.ListenAndServe(addr, nil)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, LINE Bot")
}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)
    var m ReceivedMessage
    err := decoder.Decode(&m)
    if err != nil {
        log.Println(err)
    }
    apiURI := EndPoint + "/v1/events"
    for _, result := range m.Result {
        from := result.Content.From
        text := result.Content.Text
        content := new(Content)
        content.ContentType = result.Content.ContentType
        content.ToType = result.Content.ToType
        content.Text = text
        request(apiURI, "POST", []string{from}, *content)
    }
}

func request(endpointURL string, method string, to []string, content Content) {
    m := &SendMessage{}
    m.To = to
    m.ToChannel = ToChannel
    m.EventType = EventType
    m.Content = content
    b, err := json.Marshal(m)
    if err != nil {
        log.Print(err)
    }
    req, err := http.NewRequest(method, endpointURL, bytes.NewBuffer(b))
    if err != nil {
        log.Print(err)
    }
    req = setHeader(req)
    client := &http.Client{
        Transport: &http.Transport{Proxy: http.ProxyURL(getProxyURL())},
        Timeout:   time.Duration(30 * time.Second),
    }
    res, err := client.Do(req)
    if err != nil {
        log.Print(err)
    }
    defer res.Body.Close()

    var result map[string]interface{}
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Print(err)
    }
    if err := json.Unmarshal(body, &result); err != nil {
        log.Print(err)
    }
    log.Print(result)
}

func setHeader(req *http.Request) *http.Request {
    req.Header.Add("Content-Type", "application/json; charset=UTF-8")
    req.Header.Add("X-Line-ChannelID", os.Getenv("ChannelID"))
    req.Header.Add("X-Line-ChannelSecret", os.Getenv("ChannelSecret"))
    req.Header.Add("X-Line-Trusted-User-With-ACL", os.Getenv("MID"))
    return req
}

func getProxyURL() *url.URL {
    proxyURL, err := url.Parse(os.Getenv("ProxyURL"))
    if err != nil {
        log.Print(err)
    }
    return proxyURL
}
Enter fullscreen mode Exit fullscreen mode

やってるうちに詰まったところをまとめ

  1. callback URLはhttpsのみ
    今まで簡単なサンプルは自前のサーバーでやってて、当然https対応してない。しかしLINE Botのcallbackはhttpsにportを443に指定しないといけない。let's encryptでhttps化。しかしcallbackにリクエストが来ない。herokuでなんとかすると決める。

  2. 許可したipしかsendmessageできない
    herokuにデプロイしてログを見てるとipで弾かれてる。指定したipでしかpostできないみたい。bot管理ページにwhitelistがあってそこに自分のサーバーip追加しないといけないらしい。しかし、herokuはデプロイする度にipが変わってて使いものにならない。こちらの記事( http://qiita.com/yuya_takeyama/items/0660a59d13e2cd0b2516 )にもかいてあるようにproxyを使ってproxyのipを指定すれば解決できる。

  3. contentTypeで弾かれてる
    headerのcontent-typeかと思ったがメッセージcontentのcontentTypeをちゃんと指定しないといけない

  4. 先着1万と書いてあるのが怪しい
    1万枠外でもBotの登録できるんじゃないかと疑ってる。自分のアカウントは昨日早めに登録したので、使えるが、1アカウント1Botの制限のため今日嫁のアカウントで登録したけど、callbackが来ない。

とりあえず電車でも haskell

Screen Shot 2016-04-08 at 11.21.08 PM.png

Screenshot_20160408-233023.png

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay