Python, LINE APIを使ってbotを作成する

# 目的
Pythonを用いて、LINE botを作成する
イメージは、「～～公式LINEアカウント」みたいな感じ

# 前提
- Python環境をインストール済み(今回はv3.6.5)
- [LINE Developers](https://developers.line.biz/ja/)に登録済み
- [Heroku](https://jp.heroku.com/)に登録済み
- [GitHub](https://github.com/)に登録済み

# 参考
- [LINE API](https://developers.line.biz/ja/docs/messaging-api/)
- [LINE SDK(Python)](https://github.com/line/line-bot-sdk-python)
- [PythonとLINE APIとHerokuで自動返信BOTを作る【Python編】](https://qiita.com/bambi_engineer/items/0e8593d3a3047ece3f77)
- [LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)
- [LINE Messaging API を使ってLINEにメッセージ送信／メッセージ返信する](https://qiita.com/fkooo/items/d07a7b717e120e661093)
- [HerokuとGitHubの連携](https://j-hack.gitbooks.io/deploy-meteor-app-to-heroku/content/step4.html)
- [【LINE Notify】 PythonでLINEの メッセージと画像を送信する方法](http://kuroyagikun.com/python-line-message-picture-send/)
- [【初心者向け】Gitってなに？①まず流れを理解する（コードなし）](https://qiita.com/nutsinshell/items/96cb83aecf9d09a7a8bc)
- [【Python初心者! -LINE Botでオウム返し編-】](https://www.casleyconsulting.co.jp/blog/engineer/3028/)

# 流れ

- LINE Developersに登録して、トークンなどを取得
- トークン、LINE SDKを用いて応答する仕組みを作成
- 管理はGitHubで、サーバはHeroku
- デプロイ後、動作確認
- 最終的なコードは[**こちら**](https://github.com/connectcrew-ishii/integrate_line/tree/master/line_bot)

# コード書いてみた(修正前)

今回使用するコードです。クリックで開きます。
(ページ後半で修正済みコードもあります。)

`main.py`...メッセージが入力されたら返信する。
`account_response.py`...入力された内容によって、返信メッセージを選択する。

<details><summary>**main.py**</summary><div>

```python:main.py
# -*- coding: utf-8 -*-
import os
import sys
from account_response import Response
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__)

# Herokuの変数からトークンなどを取得
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)

# LINEからのWebhook
@app.route("/callback", methods = ['POST'])
def callback():
    # リクエストヘッダーから署名検証のための値を取得
    signature = request.headers['X-Line-Signature']
    
    # リクエストボディを取得
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # 署名を検証し、問題なければhandleに定義されている関数を呼び出す。
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'

 #LINEでMessageEvent（普通のメッセージを送信された場合）が起こった場合
# reply_messageの第一引数のevent.reply_tokenは、イベントの応答に用いるトークンです。 
# 第二引数には、linebot.modelsに定義されている返信用のTextSendMessageオブジェクトを渡しています。
@handler.add(MessageEvent, message = TextMessage)
def handle_message(event):
    
    #入力された内容(event.message.text)に応じて返信する
    line_bot_api.reply_message(
    event.reply_token,
    TextSendMessage(text = os.environ[Response.getResponse(event.message.text)])
    )

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.getenv("PORT", 5000))
```
</div></details>

<details><summary>**account_response.py**</summary><div>

```python:account_reponse.py
# -*- coding: utf-8 -*-

class Response:
    """
    dic: Herokuに登録してある変数
    key以外の入力があったら、count+=1する
　　(Heroku上で1~4までのkeyに対応する変数を登録済み)
    """
    dic={"会社概要":"COMPANY",
         "事業内容":"SERVICE",
         "採用情報":"CAREER",
         "コマンド":"COMMAND",
         "おはよう":"5",
         "こんにちは":"5",
         "こんばんは":"5",
         "こんにちわ":"6",
         "こんばんわ":"6"
         }
    count = 0
    
    def getResponse(self,text):
        if self.count >= 4:
            self.count = 0
        self.count += 1
        for _dic in self.dic:
            if _dic == text:
                return self.dic[text]
        return self.count
```
</div></details>

とりあえずコードはかけたので、デプロイしてみよう。

# GitHubからHerokuへデプロイする
Herokuでアプリ作成後、DeployタブからGitHubを接続させる。
他の記事だとほとんどHeroku Git(Heroku CLI)を使っているが、今回はGitHubを使う。
![heroku_github.JPG](https://qiita-image-store.s3.amazonaws.com/0/285564/4c771f57-d9e0-36f6-4d43-9dda159c7dbb.jpeg)
自動デプロイを有効にしておく。
![github_auto_deploy.JPG](https://qiita-image-store.s3.amazonaws.com/0/285564/f1fe17e0-9dc2-6ecf-6e08-d5636983d9b0.jpeg)

接続は無事に出来たので、実際にGitHubにアップロードしてデプロイしてみよう。
適当なファイルをアップロードしてコミットする。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/285564/a1e8a45e-def7-16f9-7d15-5fa9e56089e0.png)

Herokuでちゃんとデプロイできてるかな。
>  !     No default language could be detected for this app.
			HINT: This occurs when Heroku cannot detect the buildpack to use for this application automatically.
			See https://devcenter.heroku.com/articles/buildpacks
 !     Push failed

ばっちりエラー出てますね、、、調べると以下が原因だった。

| 原因 |
|:-:|
|Buildpackの未設定|

| 対策 |
|:-:|
|Herokuコンソール>Settings>Buildpacks 　　「Add Buildpack」で「Python」を選択、追加した|
![buildpack.JPG](https://qiita-image-store.s3.amazonaws.com/0/285564/39cc1f75-fa50-bcb0-0571-158dbe809783.jpeg)
どの言語をデプロイするか指定しないといけなかったのか。
では改めてアップロードする。

> -----> App not compatible with buildpack: https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/python.tgz
       More info: https://devcenter.heroku.com/articles/buildpacks#detection-failure
 !     Push failed

また安定のエラーがでましたね。
いろいろ調べると、[**Herokuサポート**](https://devcenter.heroku.com/articles/python-support)に書いてありました。

> (和訳後引用)
Heroku は、ルートディレクトリにrequirements.txtまたはsetup.pyファイルが含まれている場合、そのアプリケーションを自動的にPythonアプリケーションとして認識します。

ということで原因が分かりました。

| 原因| 対策 |
|:-:|:-:|
|requirements.txtの欠如|requirements.txtをルートに追加|

ちなみに内容はこんな感じです。
適宜増やしたし減らしたりしてください。
(バージョンは自分の実行環境に合わせてください。)

```requirements.txt
Flask==1.0.2
line-bot-sdk==1.8.0
urllib3==1.23
```

Pythonのバージョンは指定なしだと`python-3.6.8`らしい。
今回は`python-3.6.5`なので、次のファイルも一緒にアップロードしておきます。

```runtime.txt
puthon-3.6.5
```

また、HerokuのDyno設定をするため以下ファイルも必要です。

```puython:Procfile
web: python main.py
```

### 実際にアプリを実行してみる

「Settings」タブの"Domains and certificates" 欄からURLをクリックすると

> ###Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

なぜだか分からず、、、Heroku CLIからログをみてみよう。
[Herokuプロキシ設定](https://teratail.com/questions/125070)・・・プロキシ使っている人は設定忘れずに。

```python:エラー
 at=error code=H10 desc="App crashed" method=POST path="/callback" host={Myapp}.herokuapp.com request_id=***** fwd="203.104.146.155" dyno= connect= service= status=503 bytes= protocol=https
```

詳細をみるため`heroku logs --tail`を実行した。

```python:エラーログ
2019-03-02T01:14:36.783940+00:00 app[web.1]: File "main.py", line 64
2019-03-02T01:14:36.783966+00:00 app[web.1]:
2019-03-02T01:14:36.783967+00:00 app[web.1]: ^
2019-03-02T01:14:36.783968+00:00 app[web.1]: SyntaxError: unexpected EOF while parsing
```

あ、`main.py`の最後の部分で`)`が1個抜けてる、、、→修正しました。
よし。今度こそ！

```python:エラーログ
2019-03-02T01:25:48.353864+00:00 app[web.1]: TextSendMessage(text=os.environ[res.getResponse(event.message.text)])
2019-03-02T01:25:48.353872+00:00 app[web.1]: TypeError: getResponse() missing 1 required positional argument: 'text'
```

またエラーですが、`getResponse()`の引数が足りないらしい。
これインスタンス生成されてないと出ますよねー→インスタンス生成されてませんでした！！

# コード書いてみた(修正後)

というわけでコード修正しました。
これでエラーなく動くようになりました。

```python:main.py
# -*- coding: utf-8 -*-
import os
import sys
from account_response import Response
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__)
-----追加-----
# インスタンス生成
res = Response()
---ここまで---

# Herokuの変数からトークンなどを取得
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)

# LINEからのWebhook
@app.route("/callback", methods = ['POST'])
def callback():
    # リクエストヘッダーから署名検証のための値を取得
    signature = request.headers['X-Line-Signature']
    
    # リクエストボディを取得
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # 署名を検証し、問題なければhandleに定義されている関数を呼び出す。
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'

# LINEでMessageEvent（普通のメッセージを送信された場合）が起こった場合
# reply_messageの第一引数のevent.reply_tokenは、イベントの応答に用いるトークンです。 
# 第二引数には、linebot.modelsに定義されている返信用のTextSendMessageオブジェクトを渡しています。
@handler.add(MessageEvent, message = TextMessage)
def handle_message(event):
    #入力された内容(event.message.text)に応じて返信する
    line_bot_api.reply_message(
    event.reply_token,
    TextSendMessage(text = os.environ[res.getResponse(event.message.text)])
    )

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.getenv("PORT", 5000)))--> ) 足した
    
```

```python:account_response
# -*- coding: utf-8 -*-

class Response:
    """
    dic: Herokuに登録してある変数
    key以外の入力があったら、count+=1する
　　(Heroku上で1~4までのkeyに対応する変数を登録済み)
    """
    dic={"会社概要":"COMPANY",
         "事業内容":"SERVICE",
         "採用情報":"CAREER",
         "コマンド":"COMMAND",
         "おはよう":"5",
         "こんにちは":"5",
         "こんばんは":"5",
         "こんにちわ":"6",
         "こんばんわ":"6"
         }
    count = 0
    
    def getResponse(self,text):
        if self.count >= 4:
            self.count = 0
        self.count += 1
        for _dic in self.dic:
            if _dic == text:
                return self.dic[text]
        return str(self.count) -->intからstrへ
        記事には書いてないが、intで返すなと怒られました、、

```

# まとめ
はじめて触って動かしてみると楽しかった。
簡単な動作だけですが、実際に動かすとなると相応時間と苦労がありました。

次はYoutubeで動画がアップロードされたら、LINEでユーザーに通知できるものを作ろう！