Python + Flask + ngrok + LINE Messaging API で LINE-bot 作ってみた

# はじめに
タイトルにある通り Python + Flask + ngrok + LINE Messaging API で簡単なタイマー機能を持つ LINE-bot を作成してみました。
短時間で作成することができるので興味がある方は気軽にチャレンジしてみてください。

# 環境
* macOS Catalina 10.15.5
* Homebrew 2.4.9
* Python 3.8.3
* pip 20.2.1

# 手順

## ① LINE Developers - チャネルの作成
- [LINE Developers](https://developers.line.biz/ja/) にてお手持ちのLINEアカウント等でログイン
- プロバイダー作成を選択し任意の名称を入力
- チャネル設定にて Messaging API を選択
- 各項目を入力（以下、必須項目）
    - チャネルの種類 ： Messaging API
    - プロバイダー ： 先ほど入力したプロバイダー名
    - チャネル名 ： LINEアプリ内表示名
    - チャネル説明 ： 簡単な機能説明や作成者名等
    - 大業種 ： e.g. 個人
    - 小業種 ： e.g. 学生
- 利用規約に同意して作成
- チャネルの登録が完了したら下記の2点をメモしておきます。
    - Messaging API 設定 -> チャネルアクセストークン（空欄の場合は発行）
    - チャネル基本設定 -> チャネルシークレット

## ② 仮想環境の構築
今回は`venv`を用いて、`Workspace`というディレクトリに`lineVenv`という名前で仮想環境を構築していきます。

```
% mkdir Workspace
% cd Workspace
% python3 -m venv lineVenv
```

下記コマンドを実行後、ターミナルの行の先頭に`(lineVenv)`との表記があれば成功です。

```
% . lineVenv/bin/active
```

続いて`pip`を用いて、`Flask`と`LINE Messaging API SDK for Python`をインストールしていきます。
必要なパッケージは下記コマンドでインストールすることができます。

```
% pip3 install flask
% pip3 install line-bot-sdk
```

以上で仮想環境の構築は終了です。

## ③ bot プログラムの実装
さて、先ほどインストールした LINE Messaging API for SDK の [GitHub](https://github.com/line/line-bot-sdk-python) 内の`README.rst`を参考にしながらタイマー機能を持つ bot プログラムを実装していきます。
先ほどのディレクトリに`app.py`を作成し、以下のように記述します。

```python:app.py
from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

from time import time
from datetime import timedelta

app = Flask(__name__)

line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')  #チャネルアクセストークン
handler = WebhookHandler('YOUR_CHANNEL_SECRET')  #チャネルシークレット

@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        print("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)

    return 'OK'

user = {}
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    id = event.source.user_id   #LINEのユーザーIDの取得
    if event.message.text == "start":

        if not id in user:  #新規ユーザーの場合
            user[id] = {}
            user[id]["total"] = 0

        user[id]["start"] = time()  #start実行時の時間を取得

        totalTimeStr = timedelta(seconds = user[id]["total"])  #s -> h:m:s

        reply_message = f"Start Timer\n\nTotal: {totalTimeStr}"

    elif event.message.text == "stop":
        end = time()  #end実行時の時間を取得
        dif = int(end - user[id]["start"])  #経過時間を取得
        user[id]["total"] += dif  #総時間を更新

        timeStr = timedelta(seconds = dif)
        totalTimeStr = timedelta(seconds = user[id]["total"])

        reply_message = f"Stop Timer\n\nTime: {timeStr}s\nTotal: {totalTimeStr}"

    elif event.message.text == "reset":
        user[id]["total"] = 0 #総時間を0にリセット

        totalTimeStr = timedelta(seconds = user[id]["total"])

        reply_message = f"Reset Timer\n\nTotal: {totalTimeStr}"

    else:
        reply_message = "Please send \"start\" or \"stop\"or \"reset\""  #指定外の3語に対する応答

    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=reply_message))


if __name__ == "__main__":
    app.run()

```

これで完成のように見えますが、あと2点下記の箇所を変更を加える箇所があります。

```python
line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')  #チャネルアクセストークン
handler = WebhookHandler('YOUR_CHANNEL_SECRET')  #チャネルシークレット
```
ここで必要になるのが①でメモしたチャネルアクセストークンとチャネルシークレットです。
`YOUR_CHANNEL_ACCESS_TOKEN`と`YOUR_CHANNEL_SECRET`にそれぞれコピペしてください。

## ⑤ 実行
さて、いよいよ実行に移ります。
先ほどの仮想環境で`flask run`と実行すると以下のように表示されます。

```
% flask run
* Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```
`http://127.0.0.1:5000`というアドレスがありますが、localhostなのでLAN外からアクセスすることができません。
（つまりLINEのサーバーからアクセスすることができず、Webhookを利用できないという状態）
デプロイしてしまうという手もありますが、他の方法を探していたところ`ngrok`というツールが便利そうでした。
> ngrokの利用方法 https://qiita.com/hirokisoccer/items/7033c1bb9c85bf6789bd

ということで`flask run`を実行しているターミナルとは別のターミナルを開いて、以下のコマンドでインストールします。

```
% brew install ngrok
```

[ngrok公式サイト](https://ngrok.com)から実行ファイルをダウンロードすることもできます。

インストール完了後`ngrok http 5000`と実行すると以下のように表示されます。

```
%　ngrok http 5000

ngrok by @inconshreveable                                                           (Ctrl+C to quit)
                                                                                                    
Session Status                online                                                                
Session Expires               7 hours, 59 minutes                                                   
Version                       2.3.35                                                                
Region                        United States (us)                                                    
Web Interface                 http://127.0.0.1:4040                                                 
Forwarding                    http://xxxxxxxxxxxx.ngrok.io -> http://localhost:5000                 
Forwarding                    https://xxxxxxxxxxxx.ngrok.io -> http://localhost:5000                
                                                                                                    
Connections                   ttl     opn     rt1     rt5     p50     p90                           
                              0       0       0.00    0.00    0.00    0.00                          
```
これで`https://xxxxxxxxxxxx.ngrok.io`というパブリックなURLを介して`http://127.0.0.1:5000`にアクセスすることができるようになりました。

## ⑥ LINE Developers - Webhook 設定 ・ 応答設定
Webhook 用の URL を取得することができたので LINE Developers に戻ります。 
Messaging API 設定 -> Webhook 設定 の以下の2点を変更します。

- Webhook URL ： `https://xxxxxxxxxxxx.ngrok.io`
- Webhook の利用 ： 有効

LINE 公式アカウント機能の以下の2点を変更します。

- グループ・複数人チャットへの参加を許可する ： 無効
- 応答メッセージ ： 無効

　
以上ですべての手順が終了です。

# テスト
下記より LINE の友達追加を行い、プログラム内で指定したリクエストを送ってみます。
Messaging API 設定 -> QRコード / ボットのベーシックID


[こちら](https://line.me/R/ti/p/%40935kygdg) は本記事内容を再現した bot です。（デプロイ済み）
参考までにどうぞ。

# おまけ
今回作成した LINE-bot のデプロイに Heroku（無料版）は不向きでした。
Heroku では公開ポート番号が一定ではない為、リクエストが複数回でひとまとまりといった処理は安定しません。
無料で安定させるという条件では PythonAnywhere だと上手くいきました。

# 参考サイト
> [LINE Developers - Documents - Messaging API](https://developers.line.biz/en/docs/messaging-api/)
> [LINE Messaging API SDK for Python](https://github.com/line/line-bot-sdk-python)
> [ngrokの利用方法](https://qiita.com/hirokisoccer/items/7033c1bb9c85bf6789bd)