24
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TECH::CAMPAdvent Calendar 2016

Day 17

Go + LINE Messaging APIで東京メトロ運行情報botを作る

Posted at

TECH::CAMP Advent Calendar17日目はGo + LINE Messaging APIで作る東京メトロ運行情報botについて書いていきたいと思います。

Go, LINE Messaging APIに関してはすでに良質な記事がqiitaにあるので、今回はそれらを参考にして、自分なりに形になるものを作ってみました。

Goは今まで扱ったことがなかったので、インストールからしていきます。

Goのインストールからセットアップ

とりあえず、brewでgoをインストールします。

terminal
$ brew install go

.bash_profileに以下を追加し、source ~/.bash_profileを実行します。

.bash_profile
export GOROOT=/usr/local/opt/go/libexec
export GOPATH=$HOME
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

HelloWorld

おきまりのHelloWorldもしていきます。

hello.go
package main
import "fmt"

func main(){
    fmt.Println("HelloWorld")
}
$ go run hello.go
HelloWorld

LINEBotアカウントの登録

Botを作るための下準備ということで、こちらからアカウントを登録していきます。

スクリーンショット 2016-12-02 21.38.33.png

「Messaging APIを始める」と「Developer Trialを始める」の2つがありますが、これはどちらでも構いません。違いに関しては下の方に表でまとまっております。

スクリーンショット 2016-12-02 21.43.33.png

登録後は公式のリファレンスがあるのでそちらを参考にして設定していきます。動画付きでわかりやすくまとまっています。

東京メトロ開発者利用登録

今回は東京メトロが提供する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に触れたことはほとんどないので、より良い記述方法があるかもしれません。多めにみてください笑

main.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の管理画面で登録すれば完成です。

スクリーンショット 2016-12-08 22.21.05.png

参考文献

東京メトロオープンデータ開発者サイト
Go言語で東京メトロAPIを叩く

24
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?