LINE Messaging APIで飲み会提案botを作った

*（2018/10/23追記）LINE developers communityのBot一覧に掲載されました https://www.line-community.me/bots*

# 作ったもの
グループチャットに追加された状態で、「(地区名)へ飲みに行くぞ！」と誰かが発言すると、その地区に存在する居酒屋のうち１軒を、ぐるなびの紹介ページのURLとともに提案してくれるbotをハッカソンで作りました。
![Screenshot_20180930-122815_LINE.jpg](https://qiita-image-store.s3.amazonaws.com/0/275804/b59a5b46-4d5d-0371-f262-ce95e994987a.jpeg)
以下のQRコードから登録できます。
<img width="178" alt="スクリーンショット 2018-09-29 17.58.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/275804/42417602-6497-26db-5ee5-9de800c2cae9.png">
~~ただし2018/9/29現在で、対応している地区名は"四条"、"河原町"、"京都駅"のみとなっています。~~
**（2018/10/5追記）コードを見直し、日本全国の小地区名に対応しました！**

# 実装
## 環境 

- macOS High Sierra 10.13.6
- Python 3.6.5 (built in Anaconda 3-5.2.0)
- Heroku 7.16.0

## 使ったもの一覧

- LINE Messaging API
    - botへの指示
- line-bot-sdk-python
    - サンプルコードを利用
- Python
- Flask
    - サーバーサイドで動くプログラムの記述
- Heroku
    - サーバーサイドのプラットフォーム
- ぐるなびAPI
    - 飲み屋情報の取得

## チャネルを作成
[Messaging APIのページ](https://developers.line.me/ja/services/messaging-api/)でbotのアカウント（「チャネル」という名前で、見た目はLINE@）を作ったり、基本設定ができたりします。

## サーバーサイドでbotを動かす
とりあえずオウム返しbotを実装してサーバーサイドで動かすには、[こちらの記事](https://qiita.com/akabei/items/38f974716f194afea4a5)が参考になります。
ポイントは次の点です。

- `main.py`の`if __name__ == "__main__":`部分を書き換える（Herokuで動かすための措置）
- Herokuの環境変数設定にChannel Secretとともに必要なアクセストークンはチャネル基本設定上で発行
- Webhook送信のURLは`https://（アプリ名）.herokuapp.com/callback`として登録
これをデプロイするととりあえずbotとしては動作するようになりますが、グループチャットで適切に応答してもらうために、
- 「Botのグループトーク参加」を「利用する」にする
- 「自動応答メッセージ」を「利用しない」にする

などの変更もチャネル基本設定上でしておきます。

## メッセージ送信部分の書き換え
元のコードを眺めていると、次の箇所がbotにメッセージを送信させている記述だとわかる。

```python:main.py
@handler.add(MessageEvent, message=TextMessage)
def message_text(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text)
    )
```

グループチャット内における誰かの発言は`event`という変数に格納され、そこから`event.message.text`で発言内容を得ることができる。
つまり、`event.message.text`に`"へ飲みに行くぞ！"`という文字が含まれる場合のみ、その直前にあるはずの地区名を取り出してAPIに投げ、得られた情報を`TextSendMessage(text=`の先に指定すればよい。
そのために、

- if条件文で`event.message.text`に`"へ飲みに行くぞ"`が含まれていなければ`return None`（早期return）
- `event.message.text.split("へ")`でメッセージを分割、`text_split`に格納
- その前側を`text_split[0]`で得、`place`に格納
という処理を行うよう、次のように書き換えました。

```python:main.py
def message_text(event):
    if not 'へ飲みに行くぞ' in event.message.text:
        return None
    text_split = event.message.text.split("へ")
    place = text_split[0]
    line_get_data(place)
    nomiyas = line_answer()
    nomiya = random.choice(nomiyas)
    TEXT="{}へ飲みに行くぞ！ {}".format(nomiya[0], nomiya[1])
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=TEXT) #event.message.text がメッセージの本文
    )
```

## APIによる飲み屋情報の取得
ここからは僕ではなくハッカソンでの共同開発者が書いているのですが、APIを叩いて居酒屋のデータをcsv形式にダンプする処理を`line_get_data(place)`で、そのcsv形式のリストからデータを読み込む処理を`line_answer`で実現し、import行の下にそれらの関数を定義しているようです。
APIにはぐるなびの[レストラン検索API](https://api.gnavi.co.jp/api/manual/restsearch/)を用いており、無料でアカウントを発行して利用することができます。で、共同開発者が「地区はエリアコードでないと指定できない」と言っていて、そのため取り急ぎ3地区のみ一致条件で拾うという形になってしまったのですが、[エリアSマスタ取得API](https://api.gnavi.co.jp/api/manual/areasmaster/)を併用することでこれは解決できそうです。ただし、ぐるなびの地区分類は最小単位のエリアSでも結構広域なので、実際に一致させるにはもう少し工夫が必要かもしれません。

```python:main.py
def line_get_data(place):
    data={'京都市':3404,'四条':3414,'河原町':3402}
    apikey='（APIキー）'
    place=data[place]
    url='http://api.gnavi.co.jp/RestSearchAPI/20150630/?keyid={key}&format=json&category_s=RSFST09004&areacode_s=AREAS{place}'.format(key=API_KEY,place=place)

    html=requests.get(url)

    data=json.loads(html.text)

    with open('a.csv','w',newline='',encoding='utf-8') as w:
        writer=csv.writer(w)
        for k in data['rest']:
            writer.writerow([k['name'],k['url']])

def line_answer():
    data=[]
    csvfile = 'a.csv'
    f = open(csvfile, "r",encoding="utf-8")
    reader = csv.reader(f)
    for x in reader:
        data.append(x)
    f.close()
    return data
```

こうして`line_answer`の返り値として得られた飲み屋のリストを`nomiyas`に格納し、そこから1件のデータの店名とURLを得て送信する、というわけです。

# 課題
当座しのぎで実装した、地区名に一致するエリアSのコードをAPIに投げるという処理を、上記のように別のAPIを挟む形で解決できたら、それなりに使い物になるbotになると思います。
その際の地区名との一致について、今回はClovaを使った実装にはしなかったのですが、こちらであればコンソール上で言葉の定義の幅を指定することができ、より柔軟に実際の用例に対応できると思います。

# ソースコード
GitHubに公開してあります。
https://github.com/silloi/nomikai-suggest-bot