いろいろなサービスを使ってLINEbotを作ろう【ngrok編】

# 概要

LINEbotのバックエンドをさまざまなサービスを利用して作ってみようというシリーズです。
第1弾はngrokを利用します。
また今回の記事に限り、導入編としてLINE Developersへの登録などの手順も記載に含めます。

そのほかのサービスを利用した構築については、以下を参照ください。

- 第１弾：ngrok  ※本記事
- 第２弾：[Heroku](https://qiita.com/mintak21/items/3c9adcabc270d4888c93)
- 第３弾：Google CloudFunctions ※近日更新
- 第４弾：Google Cloud Run
- 第５弾：Google Kubernetes Engine
- 第６弾：AWS Lambda (予定)

# 前提

- LINEのアカウントを持っている
- ローカルでPythonが実行可能な状態である

# 全体像

![linebot_1-webhook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/13ab399f-3ece-7963-d614-b13deb4bb7c7.png)


## LINE Developers準備

2020/10時点でのキャプチャ・手順です。今後、変更が出る可能性がある点はご留意ください。

### ログイン

Messaging APIを利用するため、まずはLINE Developersへアカウント登録をしましょう。
まず、[こちら](https://developers.line.biz/ja/)へアクセスし、右上のログインボタンを押下します。
次の画面では、「LINEアカウントでログイン」を押下します。
すると以下のような画面が表示されるので、LINEに登録しているメールアドレス・パスワードを入力するか、QRコードをスキャンするかのいずれかの方法でログインをします。成功すれば、コンソールのホーム画面へ遷移します。
![ログイン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/493be33c-2991-0418-ba80-dd1ec79e82ca.png)

### Channel作成

ホーム画面に`Providers`というセクションがあるので、横の「Create」ボタンを押下し、`Provider name`欄に任意の名称を入力します。
※作成に失敗するケースは、名称に問題があるので、適宜変更をしてみてください。
![Provider](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/85c640ee-8551-0a14-b64c-3ed14872aec1.png)

Providerのホーム画面へ遷移するので、「Create a Messaging API Channel」ブロックを押下します。

![Provider](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/7403aea3-3d68-63d3-f249-385e8b51b808.png)

情報の入力画面に遷移するので、以下を入力します（必要最低限の項目のみ記載）。

- Channel icon：アイコン、必須ではないけれど白アイコンは寂しいので、なんでもよいから設定しておきたい
- Channel name：チャンネルの名前、任意の名称でOK
- Channel description：チャンネルの説明、「LINE Bot練習用」などでOK
- Category：チャンネルのカテゴリ、なんでもよいが困ったら「個人」
- Subcategory：チャンネルのサブカテゴリ、なんでもよいが困ったら「個人（その他）」
- 最下部の規約リンクを確認、チェックボックスをONにする（２箇所）

### Channel SecretとChannel Access Tokenの発行

バックエンドで利用するので、この段階で発行しておきます。
なお、この２つの値は他人に知られないように慎重に保管するようにしてください。

- Channel Secret

`Basic settings`タブの`Channel secret`部分の文字列です。こちらはチャンネルを作成した段階で発行されています。

![Secret](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/1747df2f-a752-7d08-7a89-7fc95f10e671.png)


- Channel Access Token

`Messaging API`タブ最下部の`Channel access token`部分の文字列です。こちらはデフォルトでは発行されていないので、「Issue」ボタンを押下して生成しましょう。

![Token](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/3f8c76ed-b915-32b8-56b9-97f5bdfa453f.png)

### 友だち登録

`Messaging API`タブにQRコードがあるので、LINEアプリでスキャンして友だち登録しておきましょう。


## バックエンドサーバの構築

### ソースコード

[line-bot-sdk-python](https://github.com/line/line-bot-sdk-python#synopsis)のコードをコピーして、ローカルに`app.py`という名前で保存します。
そして、以下のようにシークレット情報を設定します。

- `YOUR_CHANNEL_SECRET`と書かれている部分（L16）を`Channel secret`で取得した文字列に置き換える
- `YOUR_CHANNEL_ACCESS_TOKEN`と書かれている部分（L15）を`Channel access token`で取得した文字列に置き換える

```python:app.py
# 例
line_bot_api = LineBotApi('YXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZGG==') # こっちが長い方の文字列
handler = WebhookHandler('fxxxxxxxxxxxxxxxxxxxxx')
```

※本編ではローカルでのみ起動することを想定しているため、ソースコードにベタ書きしますが、アンチパターンである点ご注意ください。

### ライブラリインストール

ソースコードはライブラリを利用しているので、ライブラリのインストールが必要です。ターミナルで次のコマンドを実行します。

```bash:Terminal
pip install Flask line-bot-sdk
```

## ngrokのインストール

[ngrok](https://ngrok.com/)とは一言でいうと、ローカルで起動しているプロセスへインターネットを介して外部からアクセスできるようにサービスです。なにより、`https`での公開エンドポイントが用意される点が非常に大きいです。（WebhookURLは`https`である必要がある）

MacOSの場合は１つめの`Homebrew`でインストールすることをおすすめします。そのほかは２つめのコマンドでインストールします。

### MacOSの場合

```bash:Terminal
brew cask install ngrok
```

### その他の場合

```bash:Terminal
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip
mv ngrok /usr/local/bin/ngrok
```

### インストール確認

正しくインストールされているか確認しておきましょう。`ngrok`コマンドを実行し、下のような表示が出ていれば問題ありません。
停止する際は`Ctrl + C`を入力します。

```bash:Terminal
ngrok http 80
```

実行結果

```bash:Terminal
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://a9e65703bdfa.ngrok.io -> http://localhost:80                                                                                                                                  
Forwarding                    https://a9e65703bdfa.ngrok.io -> http://localhost:80                                                                                                                                 
                                                                                                                                                                                                                   
Connections                   ttl     opn     rt1     rt5     p50     p90                                                                                                                                          
                              0       0       0.00    0.00    0.00    0.00 
```

## オウム返しさせる

では、いよいよサーバを立ち上げて、メッセージがオウム返しされるところを確認しましょう。

### バックエンドサーバの起動

ターミナルで以下のコマンドを実行します。
なお、Pythonのファイル名が`app.py`ではない場合は、環境変数`FLASK_APP`にそのファイル名を設定する必要があります。

```bash:Terminal
flask run --reload --port 8080

# 以下のように表示されていれば、起動状態
* 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:8080/ (Press CTRL+C to quit)
```

現在の状態を図にすると以下となります。

![linebot_1-local.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/4ac5e926-0dd1-a65d-ba5c-4060155b308d.png)


### サーバの外部公開

別のターミナルを起動して、以下のコマンドを実行します。
そして、`Forwarding`と書かれている行の`https`で始まる側のURLをコピーします。

```bash:Terminal
ngrok http 8080

Forwarding    http://e5e58099a87e.ngrok.io -> http://localhost:8080                     
Forwarding    https://e5e58099a87e.ngrok.io -> http://localhost:8080 # こっち
```

現在の状態を図にすると以下となります。

![linebot_1-ngrok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/7f57a2fe-99f2-f33f-5266-1910b60ce5a2.png)


### WebhookURLの設定

ここで一度LINE Developersの画面に戻ります。
`Messaging API`タブの`Webhook URL`の部分に「**先程コピーしたURL** + `/callback`」を入力します。

![Webhook](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/2ec6c0a0-21a7-9965-2975-f32ecdf99a25.png)

また、上記画像のEditボタンから応答設定画面へ遷移し、次のように設定してください。

- 応答メッセージ：OFF
- Webhook：ON


これでLINEへメッセージを送るとngrokエンドポイントを経由し、ローカルで起動しているサーバへリクエストが飛ぶようになりました。

![linebot_1-webhook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/13ab399f-3ece-7963-d614-b13deb4bb7c7.png)


### メッセージを送る

友だち登録しておいたBotで実際にメッセージを送ってみましょう。同じメッセージが返ってくれば成功です！！


## 注意点

`ngrok`は起動するたびに異なるエンドポイントを発行するため、一度終了したのちに再度起動した場合はLINE DevelopersのWebhookURLもあわせて修正する必要があります。


## ステップアップ

ソースコードについての解説は省きましたが、sdkのソースコードではテキストメッセージのオウム返しをするのみでした。
ステップアップとして挙動を変更する以下の２例を課題的に記載しておきますので、よろしければチャレンジしてみてください。

### 1. 常に「メッセージありがとう！」と返事をするようにする

<details><summary>こたえ</summary><div>

`handle_message`関数の`text`で指定している値が返事の内容となります。
もともとのソースでは`event.message.text`が設定されており、この`event.message.text`というものはこちらが送信したメッセージの内容なのでオウム返しとなっていたわけですね。
つまりは`text`に「メッセージありがとう！」と設定すればよいです。

```diff:app.py
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
-        TextSendMessage(text=event.message.text))
+        TextSendMessage(text='メッセージありがとう！'))
```

</div></details>


### 2. スタンプを送ったら、「かわいいスタンプだね」と返事するようにする

<details><summary>こたえ</summary><div>

なぜテキストメッセージだけがハンドリングされているかというと、`@handler.add(MessageEvent, message=TextMessage)`のデコレータで、WebhookHandlerに「TextMessage種別のメッセージイベントが来た場合は、`handle_message`関数の処理を行う」というようにしているためです。
これと同じ要領でスタンプ種別（StickerMessage）のイベントを処理する関数を追加すればよいです。

```diff:app.py
+ @handler.add(MessageEvent, message=StickerMessage)
+ def handle_sticker_message(event):
+     line_bot_api.reply_message(
+         event.reply_token,
+         TextSendMessage(text='かわいいスタンプだね'))
```


ほかにどのようなイベント種別・メッセージ種別があるのかは[API仕様](https://developers.line.biz/ja/reference/messaging-api/)を見ると良いでしょう。

</div></details>

# おわりに

ローカルで手軽に動作確認ができる点は非常によいと思います。
しかしながら、ずっとローカルで起動している必要があったり、起動するたびにWebhookURLを設定し直すのはメンドウですよね。
クラウドサービスを用いてサーバ起動することでこの手のメンドウは解決することが可能ですので、次回以降触れていきたいと思います。