[python] 簡単な応答ができるLineBotを作ってみた（Flask＋ngrok）

# はじめに
Line Messaging API, line-bot-sdkを使ってPythonで簡単なLineBotを実装した際の備忘録。
今回はユーザーのチャットへの自動返答、Flaskとngrokを用いたサーバーの実装と公開までをやります。
以前は[Heroku](https://jp.heroku.com/home)を使って実装していましたが、[無料プランの終了](https://blog.heroku.com/next-chapter)に伴い、Flask + ngrokで実装しました。

# 開発環境
- Ubuntu 20.04
- python 3.9.13
- Flask 1.1.2
- line-bot-sdk 3.5.0
- ngrok-v3-stable-linux-amd64

# 大まかな流れ
FlaskとLINEBotSDKでPythonプログラム（main.py）を実装
→ ngrokでローカルサーバーを外部公開 
→ Webhookを利用してLINE Messaging APIを介してやり取り

![スクリーンショット 2023-11-05 015737.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/a7a5da38-b7a8-851b-aca7-9d30e839c12c.png)

# LINE Developersの登録とBotの設定
まずはBotを利用するために開発者コンソールへのログインと、Botの作成・設定をしていきます。

## 1. コンソールへログイン & Bot作成
[LINE Developers](https://developers.line.biz/ja/?status=success) へアクセスし、コンソールにログインをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/dd2040d1-7160-0a54-a395-4d54a95465c1.png)

ビジネスアカウントでも普通のアカウントどちらでも良いのでお持ちのアカウントでログイン（万が一ない場合はアカウントを作成）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/79b7f060-46f5-4c41-1b27-5e69026249ee.png)

右画面下部、プロバイダー欄の作成ボタンを押してプロバイダー名を入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/8d8e775a-04fe-a618-4d47-0dba73cb7819.png)

チャネル設定でMessagingAPIを選択し、各種項目を入力して作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/991ae259-5f55-2966-b1bc-d382316662aa.png)

これでBotが作成できました。

## 2. Botの各種設定
ではBotの設定をしていきます。

まず、画面下部にチャンネルシークレットという項目があるので、そこに記載されている文字列を確認します。後で必要になりますので確認方法を覚えておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/d9b520c6-4ba1-f77b-f3ea-18cbbb826cba.png)


画面上部に行き、Messaging API設定タブをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/5b41f66d-118d-bbbb-4989-ba6c862af459.png)

最下部にチャンネルアクセストークンの項目があるので、発行を押してトークンを発行します。
APIのアクセストークンは外部に知られると悪用される恐れがあるので慎重に扱います。
こちらも後で必要になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/fa9319de-f08b-cf0a-c84a-00c62637be03.png)

チャネル基本設定タブに移動
基本情報の下にLINE Official Account Managerへのリンクがあるのでクリックして移動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/9380f6c6-4417-b479-9c12-ae343a225e60.png)

アカウント設定の画面が表示されるかと思います。
画面左側の応答設定をクリックして応答設定の画面を開き、画像内の上から三番目の項目『Webhook』をオンにします。
その他3つの項目は今回は必要ないのでオフにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/5174492b-67ae-0ed7-21fc-719d53208298.png)

ここで一旦Botの設定は終了です。

# Botの実装
Pythonのインストール、必要であれば仮想環境の準備等は終わっているものとします。

## 1. 各種ライブラリのインストール
Flask, LINEBotSDKをインストール
```
pip install flask
pip install line-bot-sdk
```
## 2. Pythonプログラム
こちらが今回使用したプログラムになります。
じゃんけんの技を送ると後出しで必ず勝ってくるだけの簡単なBotです。
プログラムは[SDKのGitHub](https://github.com/line/line-bot-sdk-python/blob/master/README.rst)に記載されていたサンプルコードを少しだけ改変したものになります。
```python:main.py
import os
from flask import Flask, abort, request
from linebot.v3.webhook import (
    WebhookHandler
)
from linebot.v3.exceptions import (
    InvalidSignatureError
)
from linebot.v3.messaging import (
    Configuration,
    ApiClient,
    MessagingApi,
    ReplyMessageRequest,
    TextMessage
)
from linebot.v3.webhooks import (
    MessageEvent,
    TextMessageContent
)


app = Flask(__name__)

handler = WebhookHandler('YOUR_CHANNEL_SECRET')
configuration = Configuration(access_token='YOUR_CHANNEL_ACCESS_TOKEN')


@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:
        app.logger.info("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    with ApiClient(configuration) as api_client:
        #相手の送信した内容で条件分岐して回答を変数に代入
        if event.message.text == 'グー':
            msg = 'パー'
        elif event.message.text == 'チョキ':
            msg = 'グー'
        elif event.message.text == 'パー':
            msg = 'チョキ'
        else:
            msg = 'ごめんね。\nまだ他のメッセージには対応してないよ'

        line_bot_api = MessagingApi(api_client)
        line_bot_api.reply_message_with_http_info(
            ReplyMessageRequest(
                reply_token=event.reply_token,
                messages=[TextMessage(text=msg)]
            )
        )


if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port, debug=False)
```
'YOUR_CHANNEL_SECRET'の部分は先程確認したチャンネルシークレット、
'YOUR_CHANNEL_ACCESS_TOKEN'はチャンネルアクセストークンに置き換えます。

:::note warn
注意
※ソースコードにこれらの情報を記載するのはセキュリティ上あまりよろしくない為、心配な方は環境変数を用いるなど適宜対応して下さい。
参考 → [【Python】トークンなどの認証情報は環境変数で管理しよう](https://tadaken3.hatenablog.jp/entry/os-get-environ)
:::

# ngrokのインストールと設定
## 1. インストール
※Ubuntu環境でのインストール方法になります。Windows等でのインストール方法は下記の記事を参考に進めて下さい。

https://www.geeklibrary.jp/counter-attack/ngrok/

<br>

ngrokをダウンロードし、解凍した中身を/usr/binに配置します。
そのままでは実行時に権限のエラーが出たので権限も変更します。
下記のコマンドを実行することで行なえます。

```sh
wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz #ダウンロード
tar -zxvf ngrok-v3-stable-linux-amd64.tgz #解凍
sudo mv ngrok /usr/bin #/usr/binへ移動
sudo chmod +x /usr/bin/ngrok #権限の変更
```

下記コマンドで起動確認します。
下記の様にバージョンが表示されればインストールは完了です。
```sh
ngrok version
> ngrok version 3.4.0
```

# 2. アカウント認証
[ngrok](https://dashboard.ngrok.com/get-started/setup)に移動します。
ログインしていない場合はログインします。アカウントがなければ作成してログインします。
画面下部の『Connect your account』という欄に記載されているコマンドを実行して認証を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/d036c9b9-a9e5-9de5-2b0c-ba82a2557222.png)



# 3. 固定ドメインの設定
このままでも利用はできますが、ngrokを起動するたびにWebhook用のURLが変わってしまいます。
後々設定しますが、Webhookを利用するために先程の開発者コンソール画面でWebhook用のURLを入力しなければいけません。
今のままではngrokを再起動する度にそのURLを変更しに行かなければならず、とても面倒です。
そこで、ngrokのドメインを固定します。これで毎回URLを更新しに行く必要がなくなります。

[Domains-ngrok](https://dashboard.ngrok.com/cloud-edge/domains)へ移動し、画面右の『New Domain』をクリックして、出てきたポップアップの『Create Domain』をクリックして作成します。
その後出てくる画面に下記の様なコマンドが記載されているかと思います。こちらは生成したドメインに指定して実行する際のコマンドです。
```sh
ngrok http --domain=＜生成された固有文字列＞.ngrok-free.app 80
```
上記の＜生成された固有文字列＞がドメインの固定部分になります。
上記コマンドではポートの指定が80になっていますが、今回はポートを5000にしてプログラムを実装しているので、今回使用する起動コマンドは以下になります。
```sh
ngrok http --domain=＜生成された固有文字列＞.ngrok-free.app 5000
```
＜生成された固有文字列＞の部分はご自身のものに置き換えて実行して下さい。

# 実際に動かしてみる
では実際にプログラムの起動とサーバーの公開をしていきます。

### ngrokの起動
まずはngrokを起動します。
```sh
ngrok http --domain=＜生成された固有文字列＞.ngrok-free.app 5000
```

すると下記のような画面が出るかと思いますので、『Forwarding』の `https://＜生成された固有文字列＞.ngrok-free.app` の部分をコピーします。

```sh
ngrok                                                                                               (Ctrl+C to quit)
                                                                                                                    
Introducing Always-On Global Server Load Balancer: https://ngrok.com/r/gslb                                         
                                                                                                                    
Session Status                online                                                                                
Account                       Naru (Plan: Free)                                                                     
Version                       3.4.0                                                                                 
Region                        Japan (jp)                                                                            
Latency                       -                                                                                     
Web Interface                 http://127.0.0.1:4040                                                                 
Forwarding                    https://＜生成された固有文字列＞.ngrok-free.app -> http://localhost:5000               
                                                                                                                    
Connections                   ttl     opn     rt1     rt5     p50     p90                                           
                              0       0       0.00    0.00    0.00    0.00
```

### BotにWebhookURLを設定
[LINE Developers コンソール](https://developers.line.biz/console/) へアクセスし、作成したBotをクリックして先程の設定画面まで行きます。
MessagingAPI設定タブへ移動し、Webhook URLの『編集』をクリック。
先程コピーしたURLを貼り付け、末尾に『/callback』を付け加えたら、『更新』をクリックします。
その後、Webhookの利用をオンにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/ceda82ff-031c-81a2-e6da-99710e645762.png)

### main.pyの起動
最後にmain.pyを起動します。
```sh
python main.py
```
すると下記のような出力が得られると思います。
```sh
 * Serving Flask app "main" (lazy loading)
 * 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 all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://xxx.xxx.xxx.xxx:5000/ (Press CTRL+C to quit)
```

### LINEで送ってみよう
全て準備が整ったので実際にLINEでチャットを送信してみます。
まず、先程の[LINE Developers コンソール](https://developers.line.biz/console/) のMessagingAPI設定タブにQRコードがあるので、それを読み取ってBotを友達に追加します。
追加できたらトーク画面で実際にチャットを送信してみましょう。
何も問題が無ければ下記画像のように後出しで勝ち続けてきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842664/4d368554-e421-23ea-48b0-68aeecffe7bf.png" width="300">

# 最後に
今回はPythonでLineBotを実装してみました。
結構簡単に実装できますね。
気が向いたらChatGPTと組み合わせてChatBotでも作ってみたいです。

# 参考
- [LINE Messaging API SDK for Python - Github](https://github.com/line/line-bot-sdk-python/blob/master/README.rst)