#概要
4回目の投稿です。
以前、@yagi_engさんがスピーカーを務めたLINEBOTの勉強会に参加してきたので、復習がてら作成記事を書きます。
今回はGoで下記のように自分の位置情報を送信すると、周辺のホテル情報を返すLINEBOTを作成しました。
機能としては大きく2つです。
- 位置情報以外の情報(画像やテキスト)を送信すると、「位置情報を送信してください。」と定型文を返す。
- 位置情報を送信すると、周辺のホテル情報を最大10件返す。
#各種ツール
- go 1.14
- VS Code 1.51.1
- Docker 19.03.13
- ngrok 2.3.35
- github.com/line/line-bot-sdk-go v7.6.0+incompatible
- 楽天トラベル施設検索API (version:2017-04-26)
構成
ローカルで立ちあげたサーバをngrokを使って外部公開し、LINF Platformに登録する形をとっています
周辺のホテル情報の検索には楽天トラベル施設検索APIを利用しています。
#ツールの準備をする
##LINE Developersに登録する
実装に入る前にまずは、LINEが提供するMassaging APIを利用するために下記の三つを行い、APIを利用するためのChannel secret
及びChannel access token
を取得する必要があります。取得にはLINEのアカウントが必要になります。
##楽天APIに登録する
楽天APIの利用にはアプリID
が必要になります。
Rakuten Developersより、アプリIDの発行を行うことができます。
こちらも利用には楽天会員のアカウントが必要になります。
##line-bot-sdkを取得する
実装にはline-bod-sdk-go
を利用するため、go get します。
go get github.com/line/line-bot-sdk-go/linebot
#環境構築
DockerでGoの開発環境を構築する
こちらの記事を参考にDockerでコンテナを作り、8080ポートを割り当ててます。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e56630e11723 linebot_app "bash" 2 days ago Up 2 seconds 0.0.0.0:8080->8080/tcp linebot_app_1
定型文の応答
##ローカルホストの立ち上げ
まずはmain関数でローカルホストを立ち上げます。
func main() {
http.HandleFunc("/callback", callback)
log.Fatal(http.ListenAndServe(":8080", nil))
}
これでlocalhost:8080/callback
にアクセスすると、callback関数が実行されるようになります。
次にcallback関数の実装です。
https://github.com/line/line-bot-sdk-goの手順に沿って実装していきます。
実装は下記のようになってます。
(hotelInfoBack()
は記事後半で説明します。)
func callback(w http.ResponseWriter, r *http.Request) {
//チャネル作成時に取得したChannel secret及びChannel access tokenを引数に渡す
bot, err := linebot.New(config.SECRET, config.TOKEN)
if err != nil {
log.Fatal(err)
}
//http.Requestを*linebot.Eventにパースする。
events, err := bot.ParseRequest(r)
if err != nil {
if err == linebot.ErrInvalidSignature {
w.WriteHeader(400) //Bad Request
} else {
w.WriteHeader(500) //Internal Server Error
}
return
}
for _, event := range events {
//リクエストのイベントがメッセージの受信かどうか
if event.Type == linebot.EventTypeMessage {
//受信したメッセージの種類による分岐
switch event.Message.(type) {
case *linebot.LocationMessage: //位置情報を受信した場合
hotelInfoBack(bot, event)
default: //位置情報以外を受信した場合
_, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(backMsg)).Do()
//bakcMsg = "位置情報を送信してください。"
if err != nil {
log.Print(err)
}
}
}
}
}
WebhookURLの登録
定型文返答の実装まで終わったら、作成したチャネルのMessageing APIタブからWebhookURLの登録を行います。
ngrokを使って、外部からローカルのサーバにアクセスするためのURLを発行します。(ngrokの使い方はこちら)
発行したhttps://......ngrok.io/callback
入力し、updateをクリックすれば、登録は完了です。
起動
作成したチャネルのMessageing APIタブのQRコードを読み取り、友達追加します。
go run
でローカルのサーバを立ち上げてから、メッセ―ジを送信すると定型文が返って来るようになりました....が余計なメッセージも返って来ています。
##自動応答機能をオフにする
上記の問題は、MassaginAPIタブのAuto-reply messages
の設定を変更することで解決できます。
画面右側のedit
をクリックして、応答メッセージをオフにすれば、余計なメッセージが返ってくることはなくなります。
#ホテル情報の応答
##応答のための処理
hotelInfoBack()
を実装していきます。
func hotelInfoBack(bot *linebot.Client, e *linebot.Event) {
msg := e.Message.(*linebot.LocationMessage)
//受信した位置情報から、緯度経度を取得する
lat := strconv.FormatFloat(msg.Latitude, 'f', 2, 64)
lng := strconv.FormatFloat(msg.Longitude, 'f', 2, 64)
//ホテル情報を取得する
replyMsg, couldGetInfo := getHotelInfo(lat, lng)
//取得に失敗した場合は、定型文(エラーが発生しました。)を返す。
if !couldGetInfo {
_, err := bot.ReplyMessage(e.ReplyToken, linebot.NewTextMessage(errorMsg)).Do()
if err != nil {
log.Print(err)
}
}
//応答するカルーセルテンプレートを作成する
res := linebot.NewTemplateMessage(
"ホテル一覧",
linebot.NewCarouselTemplate(replyMsg...).WithImageOptions("rectangle", "cover"),
)
//応答を返す
_, err := bot.ReplyMessage(e.ReplyToken, res).Do()
if err != nil {
log.Print(err)
}
}
受信した位置情報に含まれる緯度経度を引数にgetHotelInfo()
を実行し、周辺のホテル情報を取得しています。
応答にはMessaging APIに用意されているカルーセルテンプレートを使います。(詳しくはこちら)
ホテル情報を取得する
楽天APIの利用
ホテル情報の検索には楽天トラベル施設検索APIを利用します。
今回は下記のパラメータをリクエストURLにセットして検索を行います。
https://app.rakuten.co.jp/services/api/Travel/SimpleHotelSearch/20170426?[parameter]=[value]…
入力パラメータ名 | |
---|---|
format(レスポンス形式) | json |
latitude(緯度) | 送信された位置情報の緯度 |
longitude(経度) | 送信された位置情報の経度 |
serchRadius(検索範囲 : km max = 3) | 1 |
datumType(緯度経度の表示形式) | 1 : 世界測地系 |
applicationID | 発行したアプリID |
尚、今回利用する出力パラメータは以下のようになります。
出力パラメータ名 |
---|
HotelSpecial(施設特色) |
HotelName (施設名称) |
HotelThumbnailURL (施設画像サムネイルURL) |
HotelInformationURL(施設情報ページURL) |
###パースする構造体の作成
今回は取得したjsonを構造体にparseしています。
APIテストフォームより、適当なjsonを取得し、json整形ツールに入れてから、JSON-to-Goを使うことでパースする構造体のコードを取得することができます。
利用する出力パラメータのみを残した下記の構造体を使います。
type response struct {
Hotels []struct {
Hotel []struct {
HotelBasicInfo struct {
HotelName string `json:"hotelName"`
HotelInformationURL string `json:"hotelInformationUrl"`
HotelSpecial string `json:"hotelSpecial"`
HotelThumbnailURL string `json:"hotelThumbnailUrl"`
} `json:"hotelBasicInfo,omitempty"`
} `json:"hotel"`
} `json:"hotels"`
}
###ホテル情報取得の実装
getHotelInfo()
の実装です。
func getHotelInfo(lat, lng string) ([]*linebot.CarouselColumn, bool) {
url := fmt.Sprintf(apiURL, lat, lng, config.API_ID)
r, err := http.Get(url)
if err != nil {
return nil, false
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, false
}
var res response
//構造体にパースする
if err = json.Unmarshal(body, &res); err != nil {
return nil, false
}
var ccs []*linebot.CarouselColumn
//カルーセルカラムの作成
for index, hotel := range res.Hotels {
if index == 10 {
break
}
cc := linebot.NewCarouselColumn(
hotel.Hotel[0].HotelBasicInfo.HotelThumbnailURL,
cutOutCharacters(hotel.Hotel[0].HotelBasicInfo.HotelName, 40),
cutOutCharacters(hotel.Hotel[0].HotelBasicInfo.HotelSpecial, 60),
linebot.NewURIAction("楽天トラベルで開く", hotel.Hotel[0].HotelBasicInfo.HotelInformationURL),
).WithImageOptions("#FFFFFF")
ccs = append(ccs, cc)
}
return ccs, true
}
引数で受け取った緯度経度を使い、APIをたたいて取得したjsonを構造体にパースしています。
構造体スライスを取得したら、それをもとにfor文を回して、カルーセルカラムを作っていきます。
応答で返すカルーセルテンプレートはカルーセルカラムの配列をフィールドとして持っています。(詳しくはこちら)
今回はカルーセルカラムにAPIから取得した、ホテル名、ホテルサムネイル画像、ホテル特色をセットしています。
また、linebot.NewURIAction()
で楽天トラベルのホテルページへアクセスするactionを作成し、カルーセルカラムにセットしています。
尚、応答で返せるカルーセルカラムは最大10件となっています。
また、カルーセルカラムに設定する一部フィールドには文字数の制限があるため、文字を切り出す処理を書いています。
//cutOutCharacters 先頭から指定文字数だけを切りだす("abcde",3) → "abc"
func cutOutCharacters(s string, count int) string {
if utf8.RuneCountInString(s) > count {
return string([]rune(s)[:count])
}
return s
}
#参考