TECH::CAMP Advent Calendar17日目はGo + LINE Messaging APIで作る東京メトロ運行情報botについて書いていきたいと思います。
Go, LINE Messaging APIに関してはすでに良質な記事がqiitaにあるので、今回はそれらを参考にして、自分なりに形になるものを作ってみました。
Goは今まで扱ったことがなかったので、インストールからしていきます。
Goのインストールからセットアップ
とりあえず、brewでgoをインストールします。
$ brew install go
.bash_profileに以下を追加し、source ~/.bash_profile
を実行します。
export GOROOT=/usr/local/opt/go/libexec
export GOPATH=$HOME
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
HelloWorld
おきまりのHelloWorldもしていきます。
package main
import "fmt"
func main(){
fmt.Println("HelloWorld")
}
$ go run hello.go
HelloWorld
LINEBotアカウントの登録
Botを作るための下準備ということで、こちらからアカウントを登録していきます。
「Messaging APIを始める」と「Developer Trialを始める」の2つがありますが、これはどちらでも構いません。違いに関しては下の方に表でまとまっております。
登録後は公式のリファレンスがあるのでそちらを参考にして設定していきます。動画付きでわかりやすくまとまっています。
東京メトロ開発者利用登録
今回は東京メトロが提供するapiを利用するため開発者サイトからユーザー登録をします。
ユーザー登録が完了するとアクセストークンを確認・発行できるようになるので、確認します。
のちのコードで出てきますが、発行したアクセストークンを使い、
にアクセスすると東京メトロ9路線の運行情報がjson形式で取得できます。
[
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE4","dc:date":"2016-12-02T22:10:03+09:00","dct:valid":"2016-12-02T22:15:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Ginza","odpt:timeOfOrigin":"2016-12-01T11:31:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE7","dc:date":"2016-12-02T22:05:03+09:00","dct:valid":"2016-12-02T22:10:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Marunouchi","odpt:timeOfOrigin":"2016-11-22T11:41:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE2","dc:date":"2016-12-02T22:10:03+09:00","dct:valid":"2016-12-02T22:15:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Chiyoda","odpt:timeOfOrigin":"2016-12-01T14:31:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE6","dc:date":"2016-12-02T22:10:03+09:00","dct:valid":"2016-12-02T22:15:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Hibiya","odpt:timeOfOrigin":"2016-12-01T11:38:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE8","dc:date":"2016-12-02T22:00:03+09:00","dct:valid":"2016-12-02T22:05:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Namboku","odpt:timeOfOrigin":"2016-12-01T10:33:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BEA","dc:date":"2016-12-02T22:00:03+09:00","dct:valid":"2016-12-02T22:05:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Yurakucho","odpt:timeOfOrigin":"2016-11-25T11:12:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE3","dc:date":"2016-12-02T22:10:03+09:00","dct:valid":"2016-12-02T22:15:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Fukutoshin","odpt:timeOfOrigin":"2016-12-01T12:42:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE5","dc:date":"2016-12-02T22:10:03+09:00","dct:valid":"2016-12-02T22:15:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Hanzomon","odpt:timeOfOrigin":"2016-12-02T10:34:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"},
{"@context":"http://vocab.tokyometroapp.jp/context_odpt_TrainInformation.json","@id":"urn:ucode:_00001C000000000000010000030C3BE9","dc:date":"2016-12-02T22:00:03+09:00","dct:valid":"2016-12-02T22:05:03+09:00","odpt:operator":"odpt.Operator:TokyoMetro","odpt:railway":"odpt.Railway:TokyoMetro.Tozai","odpt:timeOfOrigin":"2016-12-02T11:50:00+09:00","odpt:trainInformationText":"現在、平常どおり運転しています。","@type":"odpt:TrainInformation"}
]
サンプルコード
下準備が整ったので、実装に入ります。
足りないパッケージがある場合はインストールします。
$ go get github.com/gin-gonic/gin
$ go get github.com/line/line-bot-sdk-go/linebot
いよいよ、「運行情報」とユーザーが入力すると運行情報を返してくれるBotを作っていきます。
今回使用するサンプルコードは以下のようなコードになります。
※ goに触れたことはほとんどないので、より良い記述方法があるかもしれません。多めにみてください笑
package main
import (
"os"
"fmt"
"log"
"time"
"regexp"
"strings"
"net/http"
"io/ioutil"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/line/line-bot-sdk-go/linebot"
)
type TrainInfomation struct {
Context string `json:"@context"`
Id string `json:"@id"`
Type string `json:"@type"`
Date time.Time `json:"dc:date"`
Valid time.Time `json:"dct:valid"`
Operator string `json:"odpt:operator"`
TimeOfOrigin time.Time `json:"odpt:timeOfOrigin"`
Railway string `json:"odpt:railway"`
TrainInformationStatus string `json:"odpt:trainInformationStatus"`
TrainInformationText string `json:"odpt:trainInformationText"`
}
type TrainInformations []TrainInfomation
func fetchTrainName(railway string) string {
name := map[string]string{
"Ginza": "銀座線",
"Marunouchi": "丸の内線",
"Chiyoda": "千代田線",
"Hibiya": "日比谷線",
"Namboku": "南北線",
"Yurakucho": "有楽町線",
"Fukutoshin": "副都心線",
"Hanzomon": "半蔵門線",
"Tozai": "東西線",
}
return name[railway]
}
func fetchTrainInfo(message string) string{
info := "運行情報:\n"
if message == "運行情報" {
url := make([]byte, 0, 10)
url = append(url, "https://api.tokyometroapp.jp/api/v2/datapoints?rdf:type=odpt:TrainInformation&acl:consumerKey="...)
url = append(url, os.Getenv("CONSUMER_KEY")...)
res, err := http.Get(string(url))
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
var trains TrainInformations
err = json.Unmarshal(body, &trains)
if err != nil {
log.Fatal(err)
}
for _, train := range trains {
rep := regexp.MustCompile(`[A-Za-z]*odpt.Railway:TokyoMetro.`)
railway := rep.ReplaceAllString(train.Railway, "")
railway = fetchTrainName(railway)
text := train.TrainInformationText
if len(train.TrainInformationStatus) > 0 {
text = fmt.Sprintf("%s (%s)", train.TrainInformationStatus, train.TrainInformationText)
}
info += fmt.Sprintf("%s: %s\n", railway, text)
}
} else {
info = "「運行情報」と入力すると東京メトロの運行情報を表示します"
}
return info
}
func main() {
port := os.Getenv("PORT")
if port == "" {
log.Fatal("$PORT must be set")
}
bot, err := linebot.New(
os.Getenv("CHANNEL_SECRET"),
os.Getenv("CHANNEL_TOKEN"),
)
if err != nil {
log.Fatal(err)
}
r := gin.New()
r.Use(gin.Logger())
r.POST("/callback", func(c *gin.Context) {
events, err := bot.ParseRequest(c.Request)
if err != nil {
if err == linebot.ErrInvalidSignature {
log.Print(err)
}
return
}
for _, event := range events {
if event.Type == linebot.EventTypeMessage {
switch message := event.Message.(type) {
case *linebot.TextMessage:
text := fetchTrainInfo(message.Text)
if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(text)).Do(); err != nil {
log.Print(err)
}
}
}
}
})
r.Run(":" + port)
}
herokuへデプロイ
herokuでgoアプリを動かす方法などに関しては公式で説明がまとまっているので参考にしながら進めます。
まず、herokuアプリの起動時に参照されるProcfileを作成します。
$ echo "web: $(basename `pwd`)" > Procfile
次に、herokuでgoアプリを動かす際は、ビルドパックを使用しパッケージを管理しなければいけないので、今回はgovendorを用いて管理します。
$ go get -u github.com/kardianos/govendor
$ govendor init
$ govendor fetch +out
作成したアプリをgit配下におきます。
$ git init
$ git add .
$ git commit -m "first commit"
最後にherokuにデプロイしていきます。
herokuのアカウントがない場合は作成してください。
先ほど取得した、apiキーなども環境変数としてセットしておきます。
$ heroku login
$ heroku create
$ git push heroku master
$ heroku config:add CONSUMER_KEY="xxxxxxxxxx"
$ heroku config:add CHANNEL_SECRET="xxxxxxxxxx"
$ heroku config:add CHANNEL_TOKEN="xxxxxxxxxx"
あとは、作ったアプリのWebhook URL(https://〇〇〇〇.herokuapp.com/callback
)をlinebotの管理画面で登録すれば完成です。