Nodejs+LINE BOTでレストラン検索


##はじめに
2016年4月7日にLINEより「BOT API Trial Account」の無償提供（先着1万名限定）が始まりました。
既に様々な記事が出ていますがExpressを使ったものが見つからなかったので、
試しに[サービス開始時のNEWS](http://linecorp.com/ja/pr/news/ja/2016/1320)にあった、レストランBOTを作ってみました。

※これを書いている時に酷似した記事を見つけましたが全然気にしてません。まったく。


今回はHerokuにデプロイしてますが、ググれば先人たちの記録がたくさん見つかるので、その辺りの話はしません。
参考：[Node.js+Express+EJS+HerokuでWebアプリケーション開発 その1](http://kokushin.hatenablog.com/entry/2016/04/04/Node.js%E3%81%A8Heroku%E3%81%A7Web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA%E3%83%BB%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E7%B7%A8)


##環境
- OS X El Capitan 10.11.3
- node　v5.4.1
- npm　3.5.3

##準備
- Herokuを使えるようにしておく
- [BOT API Trial Account](https://business.line.me/services/products/4/introduction)に登録して次の３つを取得
	- Channel ID
	- Channel Secret
	- MID
- Herokuのアドオン（Fixie）用にクレジットカード　※無料
- [ぐるなび Webサービス](http://api.gnavi.co.jp/api/)からアクセスキーを取得


##さっそくですが
とりあえず動いたものを。


```javascript:index.js
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var request = require('request');
var async = require('async');

app.set('port', (process.env.PORT || 5000));
app.use(bodyParser.urlencoded({extended: true}));  // JSONの送信を許可
app.use(bodyParser.json());                        // JSONのパースを楽に（受信時）
// app.get('/', function(request, response) {
//     response.send('Hello World!');
// });

app.post('/callback', function(req, res){

    async.waterfall([
        // ぐるなびAPI
        function(callback) {

            var json = req.body;

            // 受信テキスト
            var search_place = json['result'][0]['content']['text'];
            var search_place_array = search_place.split("\n");

            //検索キーワード
            var gnavi_keyword = "";
            if(search_place_array.length == 2){
                var keyword_array = search_place_array[1].split("、");
                gnavi_keyword = keyword_array.join();
            }

            // ぐるなびAPI レストラン検索API
            var gnavi_url = 'http://api.gnavi.co.jp/RestSearchAPI/20150630/';
            // ぐるなび リクエストパラメータの設定
            var gnavi_query = {
                "keyid":"<ぐるなびのアクセスキー>",
                "format": "json",
                "address": search_place_array[0],
                "hit_per_page": 1,
                "freeword": gnavi_keyword,
                "freeword_condition": 2
            };
            var gnavi_options = {
                url: gnavi_url,
                headers : {'Content-Type' : 'application/json; charset=UTF-8'},
                qs: gnavi_query,
                json: true
            };

            // 検索結果をオブジェクト化
            var search_result = {};

            request.get(gnavi_options, function (error, response, body) {
                if (!error && response.statusCode == 200) {
                    if('error' in body){
                        console.log("検索エラー" + JSON.stringify(body));
                        return;
                    }

                    // 店名
                    if('name' in body.rest){
                        search_result['name'] = body.rest.name;
                    }
                    // 画像
                    if('image_url' in body.rest){
                        search_result['shop_image1'] = body.rest.image_url.shop_image1;
                    }
                    // 住所
                    if('address' in body.rest){
                        search_result['address'] = body.rest.address;
                    }
                    // 緯度
                    if('latitude' in body.rest){
                        search_result['latitude'] = body.rest.latitude;
                    }
                    // 経度
                    if('longitude' in body.rest){
                        search_result['longitude'] = body.rest.longitude;
                    }
                    // 営業時間
                    if('opentime' in body.rest){
                        search_result['opentime'] = body.rest.opentime;
                    }

                    callback(null, json, search_result);

                } else {
                    console.log('error: '+ response.statusCode);
                }
            });

        },
    ],

    // LINE BOT
    function(err, json, search_result) {
        if(err){
            return;
        }

        //ヘッダーを定義
        var headers = {
            'Content-Type' : 'application/json; charset=UTF-8',
            'X-Line-ChannelID' : '<Your Channel ID>',
            'X-Line-ChannelSecret' : '<Your Channel Secret>',
            'X-Line-Trusted-User-With-ACL' : '<Your MID>'
        };

        // 送信相手の設定（配列）
        var to_array = [];
        to_array.push(json['result'][0]['content']['from']);


        // 送信データ作成
        var data = {
            'to': to_array,
            'toChannel': 1383378250, //固定
            'eventType':'140177271400161403', //固定
            "content": {
                "messageNotified": 0,
                "messages": [
                    // テキスト
                    {
                        "contentType": 1,
                        "text": 'こちらはいかがですか？\n【お店】' + search_result['name'] + '\n【営業時間】' + search_result['opentime'],
                    },
                    // 画像
                    {
                        "contentType": 2,
                        "originalContentUrl": search_result['shop_image1'],
                        "previewImageUrl": search_result['shop_image1']
                    },
                    // 位置情報
                    {
                        "contentType":7,
                        "text": search_result['name'],
                        "location":{
                            "title": search_result['address'],
                            "latitude": Number(search_result['latitude']),
                            "longitude": Number(search_result['longitude'])
                        }
                    }
                ]
            }
        };

        //オプションを定義
        var options = {
            url: 'https://trialbot-api.line.me/v1/events',
            proxy : process.env.FIXIE_URL,
            headers: headers,
            json: true,
            body: data
        };

        request.post(options, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                console.log(body);
            } else {
                console.log('error: '+ JSON.stringify(response));
            }
        });

    });

});

app.listen(app.get('port'), function() {
    console.log('Node app is running');
});

```


####なにをやっているか
`/callback`にリクエスト（ユーザが送信するメッセージ）が来たら、

- 一行目：住所 例）岩手県盛岡市
- 二行目：検索キーワード　例）焼き肉、冷麺…

を取り出して、ぐるなびのAPIに投げる。
検索結果から、

- お店の名前
- 画像
- 住所
- 緯度・経度
- 営業時間

を取り出し、ここからようやくLINE BOTから送信したい情報をセットしてPOSTする。
送信したいものが１つであれば [Sending messages](https://developers.line.me/bot-api/api-reference#sending_message)、複数なら[Sending multiple messages](https://developers.line.me/bot-api/api-reference#sending_multiple_messages)を使う。

####つまづいたところ
[LINE BOT をとりあえずタダで Heroku で動かす](http://qiita.com/yuya_takeyama/items/0660a59d13e2cd0b2516#comment-e6737f3166be32597681)を見ると、
>LINE BOT API の呼び出しには Server IP Whitelist に接続元 IP の指定が必要とのことで、Heroku だと難しいかなー、と思いましたがアドオンで解決できました。
>
>[Fixie](https://elements.heroku.com/addons/fixie)

なんと！！！ありがたい。
Fixieを使うときにfreeでもクレジットカードの登録が求められるので、渋々必要情報を入力します。
そして、`次の作業を忘れていたため、かなり時間を取られました。`

Herokuの管理画面から、Resources > Fixie > account　と進み、
`Proxy URL`を、LINE BOTにPOSTする際に必要になります。

すると、ようやくそれっぽいものができました。

![IMG_9307.PNG](https://qiita-image-store.s3.amazonaws.com/0/92359/38f48e24-bb06-9d47-0669-775b6f27b2e9.png "IMG_9307.PNG")



##まとめ
ツッコミあればよろしくお願いします！
（BOT API Trial Account、まだ10,000名達してないのかな。。。）