近くの神社を教えてくれる LINE Bot を作ってみました。
鷹楠です。前回、前々回と、LINE Bot を作ってきましたが、今回もまた新しい LINE Bot を作ってみました。
機能はシンプル。近くの神社を教えてくれて、参拝済みの記録をつけられる LINE Bot です。
今回は、LINE API の カルーセル型のテンプレートメッセージを使ってみました。それから、Google Apps Script で無料で簡単に使えるデータベース Google Fusion Tables も使ってみました。
きっかけ&こんなものが作りたい
- 神社が好きで、有名どころはけっこうまわってる。
- とはいえ、まだまだ知らない神社はいっぱい。
- 近所や、ちょっと出かけた先で、まだ行ったことがない神社をみつけたい。暇つぶしや、ウォーキングがてら。
- 行ったことがある神社は、何かしらの形でわかるようにしたい。
- 社格なんかをすぐ知りたい。
- 御朱印を管理したい。
…とまあ、常日頃からこういう想いがあって、とりあえず作れるものを作ってみました。ちなみに、最後の 2 つの機能(社格なんかの表示、御朱印の管理)は、残念ながらこの記事を書いている時点では実現できていません。今後の課題です。
こんなものができた
- 位置情報を送信すると、そこから半径 3 km 以内の神社の情報を、最大 5 件教えてくれる。
- 神社の情報には、「名前」「そこまでの直線距離」「住所」が含まれる。
- 「その神社を Google 検索するためのボタン」「その神社までのルート(by Google マップ)を検索するためのボタン」もいっしょに表示される。詳細が知りたかったら、ここから検索できる。神社名だけでなく住所もいっしょに検索ワードになってるので、一意に神社を特定して検索できる。
- 「行ったことがある」ボタンもいっしょに表示される。これを押すと、次回からこの神社の情報には「参拝済み」マークがつく。
外観を紹介します。
Bot の名前は「近くの神社」。
友だち追加時のメッセージ:
位置情報を送信すると、
そこから半径 3 km 以内の神社の情報を、最大 5 件教えてくれます。
今回は、カルーセル型のテンプレートメッセージでメッセージを送ってます。横にスライドするやつです。最大 5 件というのは、LINE API の制限だったり…。
タップできるボタンが 3 つありまして、
「この神社を検索」ボタンをタップすると、「神社の名前」+「住所」で Google 検索します。(LINE 内のブラウザで開かないで欲しいんですけどね…)
「ここからのルート」ボタンをタップすると、Google マップのルート検索結果を表示します。徒歩の場合のルートにしてます。
最後に、「行ったことがある」ボタンついて。タップすると、内部で持ってるデータベースにユーザーと神社の ID が保存されて、次に神社が表示されたときには、「参拝済み」という文字がいっしょに表示されます。
大まかな作り方
- 前回と同じく、LINE Bot アカウントを作り、Google Apps Script で LINE Bot 用の web アプリケーションを作成した。
- 位置情報(緯度・経度)の近くにある神社を探すために、Yahoo!ローカルサーチAPI を利用した。この API の優れているところは、「コンビニ」「カフェ」「動物園」「歯科」などかなり具体的な施設のカテゴリ(業種コード)で検索できるところ。嬉しいことに、このカテゴリの中に「神社」があった。業種コードの一覧はここを参照(一覧の csv をダウンロードできる)。なお、利用する際はクレジット表示が必要。
- 神社の情報を伝えるために LINE API の カルーセル型のテンプレートメッセージ を利用した。タップしたときに URL リンクを開けるように uri アクションを使い、神社に行ったことがあるという情報を受け取るために postback アクションを使った。
- ユーザーごとの参拝済み情報(神社に行ったことがあるかどうか)は、Google Fusion Tables というデータベースで管理。Google Drive に Fusion Tables(試験運用) のアプリを追加して、Google API Console で有効化すると、Google Apps Script のコードから呼び出せるようになる。Google Fusion Tables は試験運用中らしく、今後どうなるかちょっと不安なところはあるけど、とりあえず気にしない。
ソースコードの紹介
※ GitHub に格納しています。
Google Apps Script(.gs ファイル)で実装してます。Google Apps Script を使った LINE Bot 用の基本的なコードについては、前回の記事も参照。
位置情報から、近くの神社を検索する
今回の LINE Bot で最も重要な部分です。送信された位置情報から、近くにある神社の情報を取得するための、getNearShrines() という関数を作りました。このコードでは、Yahoo!ローカルサーチAPI の仕様に従って、指定した 緯度 (latitude) と経度 (longitude) から、半径 3 km 以内にある「神社」(業種コード: 0423002)を最大 5 件、近い順に検索するための HTTP リクエストを投げています。結果は JSON 形式でもらうように指定してます。なお、事前に Yahoo! の アプリ ID を取得しておく必要があります。この Web API のレスポンスとして、神社の名前・住所・緯度・経度などを取得できます。
var YAHOO_APP_ID = 'XXXXX' // Yahoo! のアプリ ID var YAHOO_SEARCH_URL = 'https://map.yahooapis.jp/search/local/V1/localSearch'; function getNearShrines(latitude, longitude) { var url = YAHOO_SEARCH_URL + '?appid=' + YAHOO_APP_ID + '&dist=3' // 3 km 以内 + '&gc=0424002' // 業種コード: 神社 + '&results=5' // 最大 5 件 + '&lat=' + latitude + '&lon=' + longitude + '&output=json&sort=dist'; var response = UrlFetchApp.fetch(url); var shrines = []; var features = JSON.parse(response.getContentText('UTF-8'))['Feature']; for (i = 0; i < features.length; i++) { var uid = features[i]['Property'].Uid; // 場所の ID var name = features[i].Name; // 場所の名前 var address = features[i]['Property'].Address; // 場所の住所 var coords = features[i]['Geometry'].Coordinates.split(','); var shrine_longitude = coords[0]; // 経度 var shrine_latitude = coords[1]; // 緯度 // 神社の情報を配列に入れる処理 } return shrines; }
2 点の場所の距離を求める
Yahoo! の API を使うことによって、近くの神社の緯度・経度などの情報が取れました。ほんとはこの中に、検索した場所からの距離が含まれていてほしかったのですが、どうもこの情報は含まれてない。そこで、検索した場所から、取得した神社の場所までの直線距離を、2点間距離API(これも Yahoo! 提供)を使って求めました。…まあ、ほんとうは道なり距離が知りたかったんですけどね。ぱっと探した感じだと、無料でたくさんリクエストできる API が見つかりませんでした。直線距離でも、ないよりはマシな情報かな、と。
コードは下記のような感じ。2 つの 緯度・経度 から、直線距離(地球の楕円体に合わせた距離)を求めます。パラメータ coordinates に、「coordinates=139.73091159286,35.665662327613 135.49513388889,34.701974166667」のように、2 つの位置情報をスペースで繋げて渡すみたいなんですが、当然といえば当然ながら、パーセントエンコードしないとうまく動きません(ハマりました)。Google Apps Script 標準の関数を使って、encodeURIComponent(' ') とやってます(「%20」と直に書いてももちろん OK)。最後の Math.round(distance * 10) / 10 は、小数点以下 1 桁の距離 (km) にするためです。
function getDistanceInKilloMeters(latitude1, longitude1, latitude2, longitude2) { var url = YAHOO_DIST_URL + '?appid=' + YAHOO_APP_ID + '&coordinates=' + longitude1 + ',' + latitude1 + encodeURIComponent(' ') + longitude2 + ',' + latitude2 + '&output=json'; var response = UrlFetchApp.fetch(url); var distance = JSON.parse(response.getContentText('UTF-8'))['Feature'][0]['Geometry'].Distance; return Math.round(distance * 10) / 10; }
Google 検索の URL と Google マップのルート検索の URL
Google 検索の URL は、次のコードで生成しています。https://www.google.co.jp/search という URL に、パラメータ q を渡します。検索する文字列がマルチバイト文字の場合は、「入力文字のエンコードは UTF-8 ですよ」と指定する ie=UTF-8 もつけます(説明として正確ではないかも)。
function getGoogleSearchUrl(query) { return 'https://www.google.co.jp/search?q=' + encodeURIComponent(query) + '&ie=UTF-8'; }
Google マップのルート検索の URL は、次のコードで生成しています。http://maps.google.com/maps という URL に、2 つの緯度経度を saddr と daddr で渡します。dirflg=w をつけると、徒歩でのルート検索になります。ただ、このへんの URL 生成は、やってみたらできた、って感じなので、そのうち動かなくなるかもしれません。…いやー、しっかし、このような URL にアクセスするだけで一瞬でルートを示してくれるって、かなり凄いことだと思います。
function getGoogleMapRouteUrl(srcLatitude, srcLongitude, destLatitude, destLongitude) { return 'http://maps.google.com/maps' + '?saddr=' + srcLatitude + ',' + srcLongitude + '&daddr=' + destLatitude + ',' + destLongitude + '&dirflg=w'; }
カルーセル型のテンプレートメッセージと、ポストバック機能
横にスライドするやつです(下記画像のようなかんじ)。LINE API のリファレンスはこちら。
リファレンスに書いてあるとおりに送れば実現できるのですが、リファレンスは、なんだかリンク飛ばされまくって、どこに何をいれたらいいのかわかりづらいと感じます。結果的に、下記のような JSON を送ればカルーセル型のメッセージになりました。
// ユーザーがタップするボタンになるもの. 最大 3 個まで作成可能. var actions = [ { 'type': 'postback', // ポストバックの場合は postback 'label': '行ったことがある', 'data': 'action=visited&uid=<場所のID>' // ポストバックする任意の文字列 }, { 'type': 'uri', // リンクの場合は uri 'label': 'この神社を検索', 'uri': googleSearchUrl // 神社の 検索 URL }, { 'type': 'uri', // リンクの場合は uri 'label': 'ここからのルート', 'uri': googleMapRouteUrl // 神社の ルート検索 URL } ]; // 横にスライドされる 1 単位. 最大 5 個まで作成可能. var columns = [ { 'title': title, 'text': 'ここから ' + distance + 'km ― ' + address, 'actions': actions }, ... ] // ユーザーに返信するメッセージ. 最大 5 個まで作成可能. var messages = [ { 'type': 'template', 'altText': '代替のテキストメッセージ', // 通知窓なんかにもこれが表示される 'template': { 'type': 'carousel', // カルーセル型のテンプレートメッセージの指定 'columns': columns } } ];
アクションの type に postback を指定した場合、ユーザーがそれをタップすると、サーバーにポストバックが送信されます。今回、この機能を使って、ユーザーが行ったことがある神社を記録する処理に繋げてます。
送信されてきたデータを JSON に変換して(変数名 json)、json.events[0].postback.data でポストバックデータを取得できます。データの設計・使い方は自由です。この LINE Bot では、「action=visited&uid=<場所のID>」という文字列を渡して、これを受け取ってデータベースを操作するようにしています。
var LINE_BOT_CHANNEL_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('LINE_BOT_CHANNEL_ACCESS_TOKEN'); // Google Apps Script の [プロジェクトのプロパティ] > [スクリプトのプロパティ] で値を設定 var LINE_REPLY_URL = 'https://api.line.me/v2/bot/message/reply'; function doPost(e) { var json = JSON.parse(e.postData.contents); // 中略 if ('postback' == json.events[0].type) { var data = json.events[0].postback.data; // 送信されてきたデータを使った処理 // 今回の場合、「visited」or「unvisited」の文字列と、神社の場所 ID が送信される。 messages = [{'type': 'text', 'text': '行ったことがある神社を更新しました'}]; } UrlFetchApp.fetch(LINE_REPLY_URL, { 'headers': { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + LINE_BOT_CHANNEL_ACCESS_TOKEN, }, 'method': 'post', 'payload': JSON.stringify({ 'replyToken': replyToken, 'messages': messages, }), }); return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON); }
Google Apps Script でデータベースを利用する
Google Apps Script で無料で簡単に使えるデータベース Google Fusion Tables を使って、ユーザーごとの神社参拝記録を行うための、ごく簡単なデータベースを作りました。
上の方でも少し書きましたが、Google Drive に Fusion Tables(試験運用) のアプリを追加して、Google API Console で有効化すると、Google Apps Script のコードから呼び出せるようになります。
まず、Google Drive で任意の場所に Fusion Tables のファイルを作成します。そしてこれを開いて、あらかじめ、カラムを定義しておきます。下記のような感じです(※これはデータがいくつか入ってるとき)。Text 型とか Date 型とか使えます。ちなみに、Location 型という特殊な型もあって、これを使うと地図上に簡単にマッピングできるらしいです(今回使ってませんが)。
さて、こうして作ったデータベースに Google Apps Script からアクセスするには、下記のようにします。あんまり綺麗じゃないですが(undefined で判断してるとことか特に…)、「場所の ID」(uid) と「ユーザー ID」(userId) と「参拝の有無」(visited, 1 or 0) が引数で、指定した docId の Fusion Tables データベースに対して、select や insert、update の操作を行っています。既に「場所の ID」と「ユーザー ID」の組み合わせがある場合には、その rowId を取得して、これを使って update を実行します(rowId を指定しないと update ができないという、少し特殊なつくりになってます)。「場所の ID」と「ユーザー ID」の組み合わせがない場合には、新規に行を insert します。
// Google Apps Script の [プロジェクトのプロパティ] > [スクリプトのプロパティ] で値を設定 var GOOGLE_FUSION_TABLES_DOC_ID = PropertiesService.getScriptProperties().getProperty('GOOGLE_FUSION_TABLES_DOC_ID'); function changeVisited(uid, userId, visited) { var sql = "SELECT ROWID FROM " + GOOGLE_FUSION_TABLES_DOC_ID + " WHERE uid = '" + uid + "' and userId = '" + userId + "'"; var result = FusionTables.Query.sqlGet(sql); if (typeof result.rows === 'undefined') { sql = "INSERT INTO " + GOOGLE_FUSION_TABLES_DOC_ID + " (uid, userId, visited)" + " VALUES ('" + uid + "', '" + userId + "', " + visited + ")"; FusionTables.Query.sql(sql); } else { var rowid = result.rows[0]; sql = "UPDATE " + GOOGLE_FUSION_TABLES_DOC_ID + " SET visited = " + visited + " WHERE ROWID = '" + rowid + "'"; FusionTables.Query.sql(sql); } }
簡単なデータベースであれば、この Fusion Tables で十分ですね。
長くなってしまいましたが、とりあえずソースコードの紹介はこんな感じで。
最終的な .gs ファイルは、GitHub に格納しているので、よければ参考にしてください(あんまりいいコードじゃないですが)。
では。