LINE Messaging API と Google App Engine でボットを作ってみた話

友人と定期的に飲み会を開催しているのだけど、次回の飲み会のリマインダーをLINEに投げてくれるボットがほしいなぁという話になったので、作ってみたという話。

LINEにはMessaging APIという機能があり、これを使うとユーザー個人やグループトーク宛にメッセージを送ることが出来る。

用意するものとしては、

- LINE developers側の設定
- メッセージを受け取ったり送ったりするアプリケーションの作成 (今回は Python)

と言った感じ。

## 基本構成とメッセージの流れ

基本的な構成はこんな感じ。

![LINE Messaging API 構成図.png](https://qiita-image-store.s3.amazonaws.com/0/106561/8086b310-6a7e-1c55-fc93-c74f5f2f2f62.png)

基本機能としてボットで出来ることは、

- メッセージが来たときに返事をする (REPLY_MESSAGE)
- ボットから特定のユーザやグループへメッセージを発信する (PUSH_MESSAGE)

REPLY_MESSAGE の大まかな流れは、

1. ユーザがチャネルに対して直接メッセージを送るか、チャネルを含むグループに対してメッセージを送る
2. チャネルはメッセージの内容を Webhook で登録した URL （Webアプリケーション) に送る　
3. Webアプリケーションではメッセージが本当に LINE Messaging API から来た正当なリクエストであるということの検証を行う
4. 問題なければメッセージの内容に合わせて適切な処理を行い、返事を https://api.line.me/v2/bot/message/reply に返す

一方 PUSH_MESSAGE の方はもっとシンプルで、

1. Webアプリケーションから https://api.line.me/v2/bot/message/push にメッセージを送る

これだけ。

## アプリケーションの作成

#### LINE developers の設定

LINEアカウントを持っていれば、https://developers.line.me/ja/ から設定することが出来る。ログインするときの認証にLINEのモバイルアプリに送られる２段階認証のコードを入力する必要があるので、端末を手元においておく。

設定自体はすごく簡単。

- まず新規プロバイダーを作成。これは企業とか団体とかの単位で後述するチャネルを管理する箱だと思えばいいだろう。
- 新規チャネルを作る。これが要はボットとしてつぶやくアカウントだと思えばいいと思う。プランを選ばなければならないが、お試しで使う分には「Developer Trial」がいい。追加できる友達の数や遅れるメッセージの量に制限があるが、基本的にすべての機能を使える。
- Webhookの登録。これはLINEからのメッセージを受け取る自分のアプリケーションのURLを指定する。

チャネルが作れると、基本的にはもうすぐに友達登録はできる状態になる。QRコードを使って自分の友だちに登録すると、チャネルに対してメッセージが送れるようになる。新規でチャネルを作ると、デフォルトでの返信メッセージが設定されている。チャネルに対して何かメッセージを書くと、そのデフォルトメッセージが返ってくる。初期状態としてはまあいいけど、そのままだとこのあとボットから返信させるようにしてもデフォルトメッセージが送られてしまうことになるので、タイミングを見てデフォルトのメッセージは削除しておいたほうがいい。

メッセージの削除は、

- LINE developers コンソールでチャネルを表示
- 「LINE@機能の利用」のセクションの下にある「自動応答メッセージ」を「利用しない」にするか、「設定はこちら」のリンクをたどって「LINE Manager」という別のツールにアクセスしてメッセージを編集

という感じでできる。

#### アプリケーション認証に必要な情報の取得

LINE Messaging API からWebアプリケーションへのデータの送信においては、残念ながらアクセス認証の方法は提供されていない。またLINE Messaging APIがどんなIPアドレスを使うかについても公開されていない。したがって、Webアプリケーション側ではWebhookの受付先は基本的にパブリックに公開して置かなければならない。

ただそうはいってもWebアプリケーションは、送られてきたメッセージが本当にLINE Messaging APIから来たリクエストであり、誰かがなりすまして送っているわけではないということを検証するひつようがあるだろう。その検証に使うのが「Channel Secret」。Channel Secret はLINE developersのコンソールから取得することが出来る。これは他の人に知られないように厳重に管理しておく必要がある。

逆にWebアプリケーションからLINE Messaging APIへのリクエストについてはOAuth2認証を行う。その際に「アクセストークン」が必要なので、これもとっておく。

#### Webアプリケーションのホスティング

今回は私の慣れている Google App Engine を使うことにした。Google App Engine はいわゆる PaaS (Platform as a Service) の走りで、自分の書いたコードをデプロイするだけで勝手にオートスケーリングなどもしてくれる便利なサービス。サーバやミドルウェアの管理は基本的に必要ない。

Google App Engine にアプリケーションをデプロイするには、

- [Google Cloud Console](https://console.cloud.google.com/) でプロジェクトを作成する
- [Google Cloud SDK](https://cloud.google.com/appengine/docs/standard/python/download ) をインストールする

といった事前準備が必要。

Google App Engine でコードをデプロイすると、アプリケーションは https://[プロジェクトID].appspot.com というホスト名でアクセスできるようになる。詳細については [この辺](https://cloud.google.com/appengine/docs/standard/python/) に詳細が乗っているので、参考にするといいと思う。

#### REPLY_MESSAGE の実装

##### メッセージの検証

Google App Engine の Python 実行環境では、webapp2 というライブラリを使って HTTP リクエストの処理をするのが一般的である。ユーザーからボットに送られてくるメッセージは HTTP POST を使って JSON 形式で送られてくる。まず最初にやらなければならないのはメッセージの検証である。これをしないと、最悪Webアプリケーション側で不正なメッセージを受け取って処理してしまうことになりかねない。

メッセージの検証は、だいたいこんな感じで行う。単純に言うと、HTTPリクエストのボディと先程取得した Channel Secret を用いて SHA-256 ハッシュを計算し、それを「X-Line-Signature」ヘッダーの内容を比較するというもの。

```python:linebot.py
import webapp2
import base64
import hashlib
import hmac

class MyWebhook(webapp2.RequestHandler):

  def post(self):

    ## Calculate message signature
    channel_secret = 'your_channel_secret'
    hash = hmac.new(channel_secret, self.request.body,
                    hashlib.sha256).digest()
    calc_signature = base64.b64encode(hash)

    ## Compare with header
    header_signature = ''
    try:
      header_signature = self.request.headers['X-Line-Signature']
    except KeyError:
      raise Exception('X-Line-Signature is not set')

    if header_signature != calc_signature:
      raise Exception('Signatures do not match')
```

##### メッセージを解析する

メッセージの検証が終わったらメッセージの解析。メッセージは JSON 形式で送られてくるが、だいたいこんな形をしている。

```json
{"events":[{"type":"message","replyToken": "hex_random_reply_token","source":{"userId":"hex_user_id_of_sender","type":"user"},"timestamp":1507468512561,"message":{"type":"text","id":"decimal_text_id","text":"メッセージ本文"}}]} 

```

Webhookは実はメッセージの送信だけでなく、ユーザを招待したとかブロックしたとかいう情報も送られてきていて、JSON の中身はリクエストの種類によって違う。リクエストがメッセージなのかどうかを判断するには、

```python:linebot.py
import json

......

  msg = json.loads(self.request.body)
  request_type = msg['events'][0]['type']

  if request_type == 'message':
    ......
```

という感じにすればいい。

ところで、LINE Messaging APIに対して返信を返すには、Reply Token を取得する必要がある。これもJSONに入っているので、以下のようにしてとっておく。

```python:linebot.py
  reply_token = msg['events'][0]['replyToken']
```

##### 返信を送る

返信を送るには https://api.line.me/v2/bot/message/reply に対して JSON を送れば良い。まずはJSONを作る。

```python:linebot.py
  json_payload = u'{"replyToken": "' + reply_token + u'", "messages": [{"type": "text", "text": "返信メッセージ"}]}'
```

これを App Engine の URLFetch API を使って送信する。ここで必要になってくるのがさっき取得したアクセストークン。HTTPリクエストの Authorization ヘッダにつけてあげなくてはならない。

```python:linebot.py
import logging
from google.appengine.api import urlfetch

......

  url = 'https://api.line.me/v2/bot/message/reply'
  access_token = 'your_access_token'
  headers = {'Content-Type': 'application/json',
             'Authorization': 'Bearer ' + access_token}
  try:
    result = urlfetch.fetch(
               url = url,
               payload = json_payload.encode('utf-8'),
               method = urlfetch.POST,
               headers = headers)
  except urlfetch.Error:
    logging.warning('Caught exception fetching url')
```

MESSAGE_REPLYのいいところは、Reply Token を指定してあげれば API が勝手にどのユーザーもしくはグループ向けに返信をすればいいか判断してくれるところ。送り先のユーザーやグループをこちらから指定してあげる必要はない。

##### Webアプリケーションとしての完成

基本的なコードはこれで終わりで、あとは webapp2 の作法に従って Webアプリケーションの登録をすると、Google App Engine 上で動くようになる。

```python:linebot.py
app = webapp2.WSGIApplication(
        [('/webhook', MyWebhook)]
      )
```

これに以下のような Google App Engine の定義ファイルを作ってあげ、

```yaml:app.yaml
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /webhook
  script: linebot.app
```

gcloudコマンドでデプロイすれば完成。

```shell-session
$ gcloud app deploy app.yaml
```

最後に Line developers コンソールでチャネルの設定を表示し、Webhook に https://project_id.appspot.com/webhook を登録してあげれば完了。ボットにメッセージを送ると、"返信メッセージ" という内容のメッセージが返ってくるはず。

##### 注意点

上記の流れからもわかるように、基本的に REPLY_MESSAGE の流れは非同期。

#### PUSH_MESSAGE の実装

PUSH_MESSAGE は REPLY_MESSAGE と違って一方向のコミュニケーションなので、REPLY_MESSAGE よりももっと単純。ただ、REPLY_MESSAGE と違うのは、どのユーザーあるいはどのグループに対してメッセージを送るのか、明示的に指定してあげる必要がある。

ユーザーID、グループIDの取得方法については、あまり他にいい方法を思いつなかったのだが、一旦ユーザーからチャネルに対して何らかのメッセージを送らせ、上述の REPLY_MESSAGE の処理の中で

```python
  user_id = msg['events'][0]['source']['userId']
  group_id = msg['events'][0]['source']['groupId']
```
みたいな感じで事前にとっておくことにした。

で、メッセージを送るときに

```python
  json_payload = u'{"to": "' + user_id + u'", "messages": [{"type": "text", "text": "これはプッシュメッセージ" }]}'
```

のような感じで指定してあげれば良い。あとは REPLY_MESSAGE と同じようにリクエストを投げれあげればよく、URL として https://api.line.me/v2/bot/message/reply を代わりに指定する。

## まとめ

非常にざっくりではあるが、LINE Messaging API と Google App Engine の組み合わせの例について説明した。LINE Messaging API はすごく簡単だし、Google App Engine のようなWebアプリケーションのプラットフォームに慣れている人であれば1時間もあればここまではたどりつけるだろう。実は私の作ったアプリケーションの中では、このあとさらに Google カレンダーや Google スプレッドシートの内容を拾ってきてつぶやくところまで作ってあるのだけど、長くなるのでまた次回。

#### Disclaimer

この記事は作者自身の個人の経験に基づいて書いているものであり、所属する団体や組織を代表するものではありません。