Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

目的

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

前提

参考

流れ

  • LINE Developersに登録して、トークンなどを取得
  • トークン、LINE SDKを用いて応答する仕組みを作成
  • 管理はGitHubで、サーバはHeroku
  • デプロイ後、動作確認
  • 最終的なコードはこちら

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

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

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

main.py
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))

account_response.py
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

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

GitHubからHerokuへデプロイする

Herokuでアプリ作成後、DeployタブからGitHubを接続させる。
他の記事だとほとんどHeroku Git(Heroku CLI)を使っているが、今回はGitHubを使う。
heroku_github.JPG
自動デプロイを有効にしておく。
github_auto_deploy.JPG

接続は無事に出来たので、実際にGitHubにアップロードしてデプロイしてみよう。
適当なファイルをアップロードしてコミットする。
image.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
どの言語をデプロイするか指定しないといけなかったのか。
では改めてアップロードする。

-----> 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サポートに書いてありました。

(和訳後引用)
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設定をするため以下ファイルも必要です。

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プロキシ設定・・・プロキシ使っている人は設定忘れずに。

エラー
 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を実行した。

エラーログ
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個抜けてる、、、→修正しました。
よし。今度こそ!

エラーログ
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()の引数が足りないらしい。
これインスタンス生成されてないと出ますよねー→インスタンス生成されてませんでした!!

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

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

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)))--> ) 足した

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でユーザーに通知できるものを作ろう!

connectcrew
最高の「技術」「実行力」「明確な考え方」を持ったエンジニア集団です。
https://www.connectcrew.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした