Pythonだとasync/awaitな非同期処理よりもマルチスレッドで対応してしまいがちな雰囲気ありますが、サーバレスだとマルチスレッドを許してもらえなかったりするのでLINE Messaging APIのライブラリlinebot.LineBotApi
を非同期対応してみました。
ちなみにこの手のサードパーティライブラリは公式SDKの更新についていけず陳腐化するのでは・・・という恐れもあるかと思いますし、以前作ったバージョンはまさにその通りだったのですが、今回ちょっとしたハックにより陳腐化しないようにしてみました。知りたい方は文末のおまけを参照ください。
インストール方法
$ pip install aiolinebot
依存パッケージ
- aiohttp
- line-bot-sdk
使い方
linebot.LineBotApi
と同じ手順でインスタンス化します。AioLineBotApi
はlinebot.LineBotApi
を継承しており、すべてのメソッドが公式そのままのものを利用できるのに加え、メソッド名+_async
の命名規則で非同期メソッドが生えています。
# APIインターフェイスのインスタンス化
api = AioLineBotApi(channel_access_token="<YOUR CHANNEL ACCESS TOKEN>")
# 返信
await api.reply_message_async(reply_token, messages)
Azure Functionsでのおうむ返しBOTの実装例は以下の通り。
import logging
import azure.functions as func
from linebot import WebhookParser
from linebot.models import TextMessage
from aiolinebot import AioLineBotApi
async def main(req: func.HttpRequest) -> func.HttpResponse:
# create api client
line_api = AioLineBotApi(channel_access_token="<YOUR CHANNEL ACCESS TOKEN>")
# get events from request
parser = WebhookParser(channel_secret="<YOUR CHANNEL SECRET>")
events = parser.parse(req.get_body().decode("utf-8"), req.headers.get("X-Line-Signature", ""))
for ev in events:
# reply echo
await line_api.reply_message(ev.reply_token, TextMessage(text=f"You said: {ev.message.text}"))
# 200 response
return func.HttpResponse("ok")
バイナリコンテンツをストリーミングによりダウンロードするときは、事後に必ずresponse
を閉じるようにしてください。line-bot-sdk==1.16.0時点ではget_message_content_async
のみが対象のようです。
content = await line_api.get_message_content_async("<MESSAGE ID>")
async for b in content.iter_content(1024):
do_something(b)
await content.response.close()
おまけ:どうして陳腐化しないの?
A. なぜならインストール済みの公式SDKから非同期クライアントを動的に作るからです。すべてのパブリックメソッドの非同期版を自動で作ります。自動で作ったクラスはファイルとして保存されるので次回以降はそれを利用します。
また、どのバージョンのSDKを元にライブラリ生成したかという情報を内部に持たせるようにしているので、インストールされているSDKが更新された場合、次に利用(import)したタイミングで最新版を元に再度自動生成するような作りになっています。
この手のハックは作っていて楽しいですね!