LINE BOT APIで簡単BOT作成

こんにちは、広告システム開発部 山浦です。

2回目の投稿となります今回は、LINE BOT APIでBOTを作成してみたいと思います。

LINE BOT APIとは

サービスとLINEユーザとのコミュニケーションを可能にするAPI開発が行えるものです。 まだトライアルの状態であるためデフォルトでは友だち上限数が50人までに制限されていたり、使用可能な機能がメッセージの送受信程度に限られています。

2016年に突如ChatBotが盛り上がったのは記憶に新しいところですが、 このLINE BOT APIの発表もその要因の一つだと言われています。

何を作るか

多くの人は毎日なにげなく天気をチェックしていると思います。
いつもの場所ならアプリにお気入り登録しているかもしれませんが、お出かけ先など知らない地名から天気を検索するのはなかなか面倒だったりします。
そんなちょっとしたストレスを解消するために、LINE API BOTと位置情報を使って簡単な天気検索を作ってみることにしました。

作成の前に

LINE BOT APIを利用するためには、Developerアカウントが必要なので取得します。

https://business.line.me/ja/products/4/introduction

登録しただけでは利用できず、若干高いハードルを超える必要があります。

  • Callback URLにはHTTPSを必ず指定すること
  • APIサーバーのOutbound IPアドレスが固定されていること

これらをクリアするためにHTTPS通信が行えるHerokuと、Outbound IPを固定できるアドオンFixieを使用しますので、 Herokuにアプリを作成しFixieを適用しておきます。

作成

それでは早速作成していきます。

開発環境

  • Go 1.6
  • Heroku + Fixieアドオン
  • OpenWeatherMap API

LINE BOT APIにホワイトリストIPアドレスを登録

FixieのAccount Detailsに表示されているIPを、LINE BOT API側に登録します。

LINE BOT APIはこのIPアドレスからのみメッセージを受信しますので、忘れずに設定します。

LINE BOT APIにアクセスする

当記事のメインパートです。 LINEがGo言語用のSDKを提供しているので、そちらを利用するのがよいと思います。

https://github.com/line/line-bot-sdk-go

また天気の取得にはOpenWeatherMap APIを利用します。 OpenWeatherMapとは、様々な気象データを無料APIとして提供している大変便利なサービスです。

http://openweathermap.org/

早速ですが、作成したサンプルコードと作成にあたっての要点をまとめました。

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "os"
    "strconv"

    "github.com/line/line-bot-sdk-go/linebot"
)

// Wdata represents Json core fields.
type Wdata struct {
    Weather []Weather `json:"weather"`
    Info    Info      `json:"main"`
}

// Weather represents weather item.
type Weather struct {
    Main string `json:"main"`
    Icon string `json:"icon"`
}

// Info represents main item.
type Info struct {
    Temp     float32 `json:"temp"`     // 気温(ケルビン)
    Humidity float32 `json:"humidity"` // 湿度
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    // Proxyの設定
    proxyURL, _ := url.Parse("")
    client := &http.Client{
        Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
    }

    bot, err := linebot.NewClient(Channel ID, "Channel Secret", "MID" linebot.WithHTTPClient(client))
    if err != nil {
        fmt.Println(err)
        return
    }

    //コールバック
    http.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) {

        // メッセージ受信
        received, err := bot.ParseRequest(req)
        if err != nil {
            fmt.Println("ParseRequest error:", err)
            return
        }

        for _, result := range received.Results {
            content := result.Content()

            if content != nil && content.IsMessage && content.ContentType == 7 {

                // 緯度経度から天気問い合わせのURLを作成
                location, _ := content.LocationContent()
                lat := strconv.FormatFloat(location.Latitude, 'f', 6, 64)
                lon := strconv.FormatFloat(location.Longitude, 'f', 6, 64)
                u := "http://api.openweathermap.org/data/2.5/weather?lat=" + lat + "&lon=" + lon + "&APPID=APP ID"

                // 天気情報を取得
                resp, _ := http.Get(u)
                defer resp.Body.Close()
                byteArray, _ := ioutil.ReadAll(resp.Body)
                jsonBytes := ([]byte)(string(byteArray[:]))

                wdata := new(Wdata)
                if err := json.Unmarshal(jsonBytes, wdata); err != nil {
                    fmt.Println("JSON Unmarshal error:", err)
                    return
                }

                //メッセージ送信
                _, senderr := bot.NewMultipleMessage().
                    AddText("現在の天気をお知らせします。").
                    AddText("天気 : "+wdata.Weather[0].Main).
                    AddImage("http://openweathermap.org/img/w/"+wdata.Weather[0].Icon+".png", "http://openweathermap.org/img/w/"+wdata.Weather[0].Icon+".png").
                    AddText("気温 : " + fmt.Sprintf("%.2f", (wdata.Info.Temp-273.15))).
                    AddText("湿度 : " + fmt.Sprintf("%.2f", wdata.Info.Humidity)).
                    Send([]string{content.From})

                if senderr != nil {
                    fmt.Println("message error:", senderr)
                }
            } else {
                bot.SendText([]string{content.From}, "位置情報を送信してください。")
            }
        }
    })
    if err := http.ListenAndServe(":"+port, nil); err != nil {
        fmt.Println("port error:", senderr)
    }

}

要点をまとめていきます。

  • プロキシの設定

FIXIEのProxy URLを設定することでOutbound IPが有効になりますので忘れずに設定します。

proxyURL, _ := url.Parse("")
client := &http.Client{
    Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
}
  • 位置情報の取得

どの地域の天気情報が欲しいかをBOTに伝えるため、位置情報(緯度経度)を取得します。

BOTが受信するメッセージはcontentに格納されており、下記のようなパラメータを持っています。

プロパティ名詳細
idメッセージID
contentTypeメッセージの種類
from送信者のMID
createdTimeメッセージ送信時間
toメッセージの受信者
toTypeメッセージを受信するユーザのタイプ
contentMetadataメッセージのメタデータ
text送信されたメッセージ
location位置情報

位置情報メッセージ以外を受信した場合は何も処理したくないので、【位置情報を送信してください。】というメッセージを送信したいと思いました。
この時注目するのが【contentType】で、これにはどのような種類のメッセージをユーザが送ったかが格納されます。

contentTypeの値詳細
1テキストメッセージ
2画像メッセージ
3動画メッセージ
4音声メッセージ
7位置情報メッセージ
8スタンプメッセージ
10ユーザ情報メッセージ

処理したいのは contentType : 7の位置情報メッセージのため、それ以外は処理しないように分岐を設ける場合は

content.ContentType == 7

とすればよいことが分かります。

位置情報メッセージが送信された場合、locationプロパティに情報が格納されます。(それ以外はnil)

プロパティ名詳細
title位置情報名、通常は住所
latitude緯度
longitude経度

ここでようやく 位置情報(緯度・経度) の取得ができました。

  • 天気情報の取得

天気情報の取得にはOpenWeatherMap APIを利用します。 本題とはズレますが、OpenWeatherMap APIの利用方法を簡単に紹介します。

天気を検索する時のキーには、地域名・地域コード・緯度経度のいずれかが利用可能ですが、緯度経度を取得できているので悩むことなくそれを利用します。

http://api.openweathermap.org/data/2.5/weather?lat=<"latitude">&lon=<"longitude">&APPID=<"App ID">

latには緯度、lonには経度を指定し、APIアクセスを行うと天気情報JSONが返ってきます。 APPIDはOpenWeatherMapのwebページから取得可能なので、別途取得しておきます。もちろん無料です。

  • メッセージの送信

いよいよメッセージ送信部分です。
ひとえにメッセージ送信といっても色々ありますが、今回は2つの送信方法を実装しています。
どちらも送信先を指定するためにはMIDというユーザ特定IDが必要なのですが、ユーザからのメッセージ受信時に一緒に受け取っている(上記表のfromに該当)ので、それをそのまま使うのが一番簡単です。

  • 位置情報メッセージ以外の場合、Sending messages API

    bot.SendText([]string{content.From}, "位置情報を送信してください。")
    
  • 天気情報を送信する場合、Sending multiple messages API

    bot.NewMultipleMessage().
        AddText("現在の天気をお知らせします。").
        AddText("天気 : "+wdata.Weather[0].Main).
        AddImage("http://openweathermap.org/img/w/"+wdata.Weather[0].Icon+".png", "http://openweathermap.org/img/w/"+wdata.Weather[0].Icon+".png").
        AddText("気温 : " + fmt.Sprintf("%.2f", (wdata.Info.Temp-273.15))).
        AddText("湿度 : " + fmt.Sprintf("%.2f", wdata.Info.Humidity)).
        Send([]string{content.From})
    

Sending messages APIでは1送信1メッセージですが、Sending multiple messages APIでは1送信nメッセージとなっているのが特徴です。
この他にもインタラクティブなメッセージが送れる Sending rich messages API というものもあり、商用利用する際はメッセージの作りこみが重要になってくるように思います。

Herokuへデプロイ

最後にHerokuへデプロイします。

Herokuの動作に必要なProcfileを作成します。

$ cd $GOPATH/src/line-bot-api
$ echo 'web: line-bot-api' > Procfile 

続いてライブラリの依存関係をgodepを利用して保存します。

$ go get -u github.com/tools/godep
$ cd $GOPATH/src/line-bot-api
$ godep save ./...
$ godep go install ./...

最後にHerokuへソースをPUSHします。

$ cd $GOPATH/src/line-bot-api
$ git init
$ heroku git:remote -a 
$ git add .
$ git commit -m "hoge"
$ git push heroku master

動作確認

ちゃんとBOTは天気を教えてくれるのでしょうか。渋谷区の天気を聞いてみます。

ちゃんと教えてくれました!地図から場所さえ伝えてあげれば自動的に天気を返してくるBOTができたので、日々のストレスから少し開放されそうです。 (OpenWeatherMapが用意している天気アイコンがボヤケてしまっているのがやや残念です。)

位置情報を送ってから結果が返ってくるまで、おおよそ1~2秒程度なのでストレスを感じることもありませんでした。

まとめ

LINE BOT APIはSDK利用することで簡単に利用できることが分かりました。 特に位置情報の利用が非常に簡単なのは驚きでした。

Callback URLはHTTPSしか利用できないことや、固定IPを設定しなければならないなどの若干のハードルはあるものの、アイディア次第で有効活用できるのではないでしょうか。
正式リリースでさらに機能が拡充されるようなので、他にもBOTを作っていければと思います。