FlaskでLINE botを実装し，herokuにdeployするまで

# はじめに
**注：2017/9/21にLINE Business centerが廃止され，Messaging API関連の設定コンソールがLINE Developersに一元化され簡単に行えるようになりました**

PythonのWebアプリケーションフレームワークであるFlaskを使ってLINE botを構築します．
ここでは一通りの流れを記述するため，構築するLINE botはユーザから送信されたメッセージをオウム返しするだけとします．
ローカルで構築・テストしたbotはherokuへdeployします．
参考にしたサイト等は末尾に列挙しています．

# 開発環境
- Mac OS Sierra
- Homebrew 環境構築済み
- Python 環境構築済み
- LINEの個人アカウントを持っている

# LINE Developersでの設定

## チャネルの作成

公式のチュートリアルが詳しいので，これに従って設定を行ってください．
[Messaging APIを利用するには - LINE Developers](https://developers.line.me/ja/docs/messaging-api/getting-started/)

1. LINE DevelopersでBOT用チャネルを作成
2. プランはDeveloper Trialとフリーの2種類ありますが，Push APIの有無と友達登録数50人の上限を天秤にかけて選んでください．

![plan.png](https://qiita-image-store.s3.amazonaws.com/0/128298/b318e03a-bc66-6d8b-8670-d659e12dfd76.png)
プランの詳細は以下から．
[プラン・料金 - LINE@でファン獲得！無料アプリで簡単に始めるビジネスLINE](http://at.line.me/jp/plan)

## コンソールの設定
こちらも公式のチュートリアルが充実してます．
[ボットを作成する - LINE Developers](https://developers.line.me/ja/docs/messaging-api/building-bot/)
以前はMessaging APIを設定する場所が複数箇所に分かれていたのですが，
現在ではLINE Developersのコンソールから簡単に設定できます．
今後もこのコンソールから各種設定をします．

1. 作成したチャネルの基本設定から，メッセージ送受信設定 -> Webhook送信を利用するにチェック
2. 同じく基本設定から，メッセージ送受信設定 -> アクセストークンを発行
3. 基本設定にあるChannel Secretと発行したChannel Access Tokenを控えておく

# Flaskを使ってLINE botを作成
まずはローカル環境で一通り開発します．
Flaskのdevelopment serverで実行したあと，一時的にngrokで外部に公開しテストします．
LINEのプラットフォームにngrokのURLを登録し，メッセージをローカルのFlaskで処理するためです．

## 環境構築
Flask・SDK of the LINE Messaging API for Python・ngrokをインストールします．
Python環境をanacondaで構築している人は元からFlaskが入っていると思います．

```
$ pip install flask 
$ pip install line-bot-sdk
$ brew install ngrok
$ pip install gunicorn % optional
```

## サンプルプログラムの作成
line-bot-sdk-pythonのサンプルプログラムを実行してみましょう．
https://github.com/line/line-bot-sdk-python
上記URLのUsageにあるサンプルプログラムをapp.pyとして保存します．
下記のapp.pyではデバッグのためにhello_worldを追加しています。
LINE Developersの設定で控えたChannel SecretとChannel Access TokenをYOUR_CHANNEL_SECRETとYOUR_CHANNEL_ACCESS_TOKENに書きます．
（本来なら環境変数に持たせるなどするべきですが，一旦ここではこのまま実行します．）

```py3: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,
)

app = Flask(__name__)

line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
handler = WebhookHandler('YOUR_CHANNEL_SECRET')

@app.route("/")
def hello_world():
    return "hello world!"

@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:
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))


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

## Flaskの実行とngrokによる公開
### Flaskのdevelopment severの実行
app.pyのあるディレクトリに移動して，以下のコマンドを実行

```
$ export FLASK_APP=app.py
$ export FLASK_DEBUG=1
$ flask run --host=0.0.0.0
```
ブラウザで http://localhost:5000 にアクセスしhello world!と出ればOK．

### ngrokによる外部への公開
Flaskでプログラムが問題なく起動したら，ngrokを実行し外部へ一時的に公開しましょう．
以下のコマンドを実行してテストします．

```
$ ngrok http 5000
```
Forwarding https://[乱数].ngrok.com -> 0.0.0.0:5000 
と表示されているURLをメモっておきます．

### webhook URLの登録
LINE developersのコンソールにアクセスし，チャネルの基本設定ページに行きます．
そこでWebhook URLに先程ngrokで取得したhttpsのURLを登録します．
ただし https://[乱数].ngrok.com/callback としてください．
サンプルプログラムでは/callbackにきたpostリクエストを処理するようになっているので．

### オウム返しの確認
作成したbotと友達になり，適当にメッセージを送ってみましょう．
以下のようにオウム返ししてくれば一旦は完成です．
<img src="https://qiita-image-store.s3.amazonaws.com/0/128298/1a47fb46-6815-abce-c283-420ad64f0e30.png" width=30%>

# LINE botをherokuにdeploy
ローカルで作ったbotをherokuにdeployします．
herokuでの最適な開発フローは模索中ですが自分用メモとして公開します．

## herokuに登録
まだherokuに登録してない人は以下から登録します．
[Cloud Application Platform | Heroku](https://www.heroku.com/)

登録後，ターミナルからherokuを操作するためheroku-cliをインストールし，ログインしておきます．

```
$ brew install heroku/brew/heroku
$ heroku login
```

## heroku用ファイル準備
以下のファイルをapp.pyと同じディレクトリ内に作成します．
app.pyも環境変数からchannel secret等を読み込む用に書き換えます．
Pythonのversionやrequirements.txtの内容などは適宜読み替えてください．
（本来ならvirtualenvしてpip freezeすべきですが・・・）

- app.py
    - flaskの実行ファイルです．
- .gitignore
    - gitignore
- .env
    - heroku localを実行したときに読み込む環境変数ファイル
    - [Heroku Local | Heroku Dev Center](https://devcenter.heroku.com/articles/heroku-local)
- Procfile
    - heroku上で実行するコマンドを書く．
- requirements.txt
    - pipでインストールしたライブラリの管理．
    - ここに書かれたライブラリ（version）がheroku上でもインストールされる．
- runtime.txt
    - pythonのversionを書く．

```py3:app.py
import os
import sys
from flask import Flask, request, abort

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

app = Flask(__name__)

# 環境変数からchannel_secret・channel_access_tokenを取得
channel_secret = os.environ['LINE_CHANNEL_SECRET']
channel_access_token = os.environ['LINE_CHANNEL_ACCESS_TOKEN']

if channel_secret is None:
    print('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

@app.route("/")
def hello_world():
    return "hello world!"

@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:
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))


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

```text:.gitignore
.env
__pycache__
```

```text:.env
LINE_CHANNEL_SECRET=hogehoge
LINE_CHANNEL_ACCESS_TOKEN=fugafuga
```

```text:Procfile
web: gunicorn app:app --log-file -
```

```text:requirements.txt
Flask==0.12.2
gunicorn==19.6.0
line-bot-sdk==1.3.0
```

```text:runtime.txt
python-3.5.2
```

## herokuにdeploy
gitを使ってherokuにdeployします．

```
$ cd path/to/your/linebot/directory
$ git init
$ git add . 
$ git commit -m "first commit Yeaahhhh"
$ heroku create [Your bot name] % http://[your bot name].herokuapp.com/ になる．指定しないとランダム文字列．
$ git push heroku master
```

dynoの数を1にし，
LINE messaging APIのChannel secret等はherokuの環境変数に持たせます．

```
$ heroku ps:scale web=1
$ heroku config:set LINE_CHANNEL_SECRET=hogehoge
$ heroku config:set LINE_CHANNEL_ACCESS_TOKEN=fugafuga
```

heroku openでhello world!を確認できればOKです．

```
$ heroku open
```

最後に先程と同様にLINE developersのコンソールにアクセスし，
Webhook URLにherokuのアドレスを登録すればbotの完成です．
（登録するのは，https://[your bot name].herokuapp.com/callback ）

## herokuでよく使うコマンド

```
$ heroku logs % logの確認
$ heroku open % ブラウザでherokuのappのURLを開く
$ heroku run ls
$ heroku run python
```

# 参考
- LINE
    - [Messaging API - LINE Developers](https://developers.line.me/ja/docs/messaging-api/overview/)
    - [LINE Messaging API Reference](https://developers.line.me/ja/docs/messaging-api/reference/)
    - [line/line-bot-sdk-python: SDK of the LINE Messaging API for Python.](https://github.com/line/line-bot-sdk-python)
- Qiita
    - [ngrokを使用してローカル環境を外部に公開する - Qiita](http://qiita.com/kitaro729/items/44214f9f81d3ebda58bd)
- Flask
    - [Welcome to Flask — Flask Documentation (0.12)](http://flask.pocoo.org/docs/0.12/)
- heroku
    - [Heroku CLI | Heroku Dev Center](https://devcenter.heroku.com/articles/heroku-cli)
    - [Getting Started on Heroku with Python | Heroku Dev Center](https://devcenter.heroku.com/articles/getting-started-with-python#introduction)
    - [Configuration and Config Vars | Heroku Dev Center](https://devcenter.heroku.com/articles/config-vars)
    - [Heroku Local | Heroku Dev Center](https://devcenter.heroku.com/articles/heroku-local)
    - [Deploying Python Applications with Gunicorn | Heroku Dev Center](https://devcenter.heroku.com/articles/python-gunicorn)