Docker×Python×Oracle-CloudでLINE Botを作ってみた。 #1

## はじめに
Flask×PythonでBotのアルゴリズムを作り、dockerの乗せてリモートサーバ稼働させる流れをまとめたものです。
LINE Devloperの登録~アクセストークンの発行までは[こちら](https://qiita.com/nkjm/items/38808bbc97d6927837cd)を参考にしました。

第1回では、
アクセストークンの発行後から python3の記述までの内容を扱います。
Python3 on Flask でルールベースの返答できるようにし、テキスト、画像、クイックリプライを実装します。

第2回(次回)では、
Dockerfileの作成からリモートサーバでのデプロイまでの内容を扱います。
次回記事: [Docker×Python×Oracle-CloudでLINE Botを作ってみた。#2](https://qiita.com/kuroneko-hornet/private/3ea83d3349f0d759586a)

<a id="index"></a>  
## 目次
- [目次](#index)
- [実装環境と条件](#anchor1)
- [実装(概観)](#anchor2)
    - [環境変数](#anchor2-1)
    - [botの実装](#anchor2-2)
- [実装(詳細)](#anchor3)

<a id="anchor1"></a>
## 実装環境と条件
**環境**

- MacOS X 10.13.6 (local)
- Docker 18.09.2
- Python 3.6
- Flask 1.1.1 
- line-bot-sdk 1.12.1

**条件**

- 例外処理すらない最低限の実装
- シングルユーザのみを考慮(クラスを定義していない)
- リモートサーバ設計についての記述なし

<a id="anchor2"></a>
## 実装(概観)
ディレクトリ構成はこんな感じでいきます.

```
docker-bot
├ Dockerfile
└ src
  ├ bot.py
  └ .env
```

docker-botディレクトリで `docker run` することを想定します。bot.pyがbotの本体部分で、.envはアクセストークンやChanel Secretなどの環境変数を記述します。

これ以降 `${PWD} == docker-bot/` という前提で話を進めます。

<a id="anchor2-1"></a>
### 環境変数の設定
LINE Devloperの
「TOP > プロバイダーリスト > {プロバイダー名} > {botのアカウント名} > チャネル基本設定」で、
「Channel Secret」と「アクセストークン」を.envに記述します。

```sh:terminal
$ mkdir src     # srcディレクトリの作成
$ cd src        # srcディレクトリに移動
$ vi .env       # viで.envを作成&記述

LINE_CHANNEL_ACCESS_TOKEN={アクセストークン}
LINE_CHANNEL_SECRET={Channel Secret}

:wq             # 上書きして終了

$ cd ../        # docker-bot/ へ戻る
```

<a id="anchor2-2"></a>

### botの実装
それではbot.pyを書いていきます。
まずは必要なパッケージをimportします。  

```python3:import
# for flask
from flask import Flask, request, abort
from argparse import ArgumentParser

# line api
from linebot import (
    LineBotApi, WebhookParser
)
from linebot.models import (
    TextMessage, TextSendMessage, ImageSendMessage,
    QuickReply, QuickReplyButton, MessageAction
)

# for env
import os
from dotenv import load_dotenv
```

事前に作成していた環境変数を読み込みます。勿論、直に書いても動きますがお勧めしません。

```python3:環境変数の読み込み

load_dotenv('.env')
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)

line_bot_api = LineBotApi(channel_access_token)
parser = WebhookParser(channel_secret)
```

いわゆるbotの本体です。
第2回で指定する`{Webhook_URL}/callback`にPOSTリクエストが送られると、`callback()`を実行します。
`event[0]`を`reply()(後述)`に渡します。

```python3:callback()
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    events = parser.parse(body, signature)

    ####### ここで受け取ったメッセージに対して処理する ########
    reply(events[0])
    ####################################################
```

`bot.py`のmain部分です。ここでflaskの設定を記述します。
ポートは8000を指定します。

```python3:flask
if __name__ == "__main__":
    arg_parser = ArgumentParser(
        usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
    )
    # 8000portでflask待機
    arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
    arg_parser.add_argument('-d', '--debug', default=True, help='debug')
    options = arg_parser.parse_args()

    app.run(debug=options.debug, host='0.0.0.0', port=options.port)
```

<a id="anchor3"></a>

## 実装(詳細)
ここでは、botのブレーンとなる`reply()`の中身を書いていきます。

- `Hello, bot` : テキストメッセージ
- `img`と送信 : 画像メッセージ
- `quick reply` : テキストメッセージとクイックリプライ

というように返信するようにします。

```python3:reply
def reply(event):

    # text: 受け取ったテキスト
    # address_id: ユーザへメッセージを送信するためのid
    text = event.message.text
    address_id = event.source.user_id

    if text == 'Hello, bot':
        send_message(address_id, 'Hello!')
    elif text == 'img':
        send_image(address_id, url)
    elif text == 'quick reply':
        message = 'this is Quick Reply!'
        # 表示するボタンのリスト
        button_s = ['button1', 'button2', 'button3']
        send_quickreply(address_id, message, button_s)
```

それでは、各メッセージを送信する関数を書きます。

```python3:functions
def send_message(address, message):
    line_bot_api.push_message(address, TextSendMessage(text=message))

def send_image(address, url):
    message = ImageSendMessage(
        original_content_url=url, 
        preview_image_url=url
    )
    line_bot_api.push_message(address, message)

def send_quickreply(address, message, button_s):
    items =\
        [QuickReplyButton(action=MessageAction(
            label=f"{button}",
            text=f"{button}")) for button in button_s]
    message_info =\
        TextSendMessage(
            text=message,
            quick_reply=QuickReply(items=items)
        )
    line_bot_api.push_message(address, message_info)
```

[...to be continued](https://qiita.com/kuroneko-hornet/private/3ea83d3349f0d759586a)(第2回へ)

# 参考
- [LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)