※当ブログではアフィリエイト広告を利用しています
Line BotをPython+responderで実装し、Dockerコンテナで動作させる方法を解説しています。herokuへのDeployもできるようにしてあるので、これからLine BotをPythonで実装しようと思っている人は参考にしてください。
目次
はじめに
Line BotをPythonで作る場合はflaskを使った解説がほとんどだと思います。公式もflask使っていますし、私も最近まで使っていました。
https://github.com/line/line-bot-sdk-python
開発している中で、「返信を5分後に返したい」という要件が出てきました。
reply tokenを使うと30秒ぐらいで返さないとトークンの有効期限が切れてしまうため、このような場合はPush通知で対応するのが一般的です。
しかし、herokuだとフロントエンドのタイムアウトが1分程度のようで、同期処理で書くと普通にタイムアウトとなってしまいます。
そのため、応答としてはすぐに200を返し、バックグラウンドで指定した時間までsleepした後でPush通知することが必要でした。
Pythonの非同期処理のモジュールを使うとflaskでも実現できそうでしたが、responderを使うと非常に簡易に実現できることが分かり、この機会に全部responderに書き換えることにしました。
また、heroku上でDockerコンテナとして動作させることができるよう、responder入りのイメージをDockerHubに用意しました。これはこれで長くなるので別記事にすると思います。
responderとは?
「a familiar HTTP Service Framework for Python」ということで、非常に簡単にAPIやWebサーバーを作ることができるようになっています。
以前はFlaskを使っていましたが、難なく乗り換えることができました。
GitHubリポジトリはこちら。
https://github.com/taoufik07/responder
バックグラウンド処理
responderの特徴的なところは、バックグラウンド処理が簡単に書けることだと思います。
以下はドキュメントの内容そのままですが、これをそのまま応用してBotに数分後に返信させることができました。
@api.route("/")
def hello(req, resp):
@api.background.task
def sleep(s=10):
time.sleep(s)
print("slept!")
sleep()
resp.content = "processing"
コード
公式のsdkのサンプルにある、単純に受け取った内容をそのまま返信するBotをresponderで書き直したコードです。
from __future__ import unicode_literals
import os
import sys
import responder
from argparse import ArgumentParser
from linebot import LineBotApi, WebhookParser
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage
api = responder.API(debug=True)
# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
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)
parser = WebhookParser(channel_secret)
@api.route("/")
def nopage(req, resp):
resp.text = "This is not a webpage you are looking for."
resp.status_code = api.status_codes.HTTP_404
@api.route("/healthz")
def healthcheck(req, resp):
resp.text = "OK"
@api.route("/callback")
async def callback(req, resp):
if req.method == "post":
signature = req.headers['X-Line-Signature']
# get request body as text
body = await req.text
print("Request body: " + body)
sys.stdout.flush()
# parse webhook body
try:
events = parser.parse(body, signature)
except InvalidSignatureError:
resp.status_code = api.status_codes.HTTP_503
return
# if event is MessageEvent and message is TextMessage, then echo text
for event in events:
if not isinstance(event, MessageEvent):
continue
if not isinstance(event.message, TextMessage):
continue
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text)
)
resp.text = "OK"
else:
resp.text = "404: This is not a webpage you are looking for."
resp.status_code = api.status_codes.HTTP_404
if __name__ == "__main__":
arg_parser = ArgumentParser(
usage='Usage: python ' + __file__ + ' [--port ] [--help]'
)
options = arg_parser.parse_args()
port = int(os.environ.get('PORT', 5000))
api.run(address='0.0.0.0', port=port, debug=True)
Flaskと比べて多少の違いはありますが、違和感なく移植することができました。
コードはこちらに置いてあります。
https://github.com/k8sinfo/linebot-responder
実際にはリポジトリにあるDockerfileとheroku.ymlを使ってherokuへデプロイして使っているのですが、別記事で解説しようと思います。
まとめ
flask使うのはその日にやめました。
皆さんもresponder使っていきましょう。
responder入りのdockerイメージも作ったのでよければ使ってください。