Pythonの次世代Webフレームワークとして期待されているResponderを利用してLINEbotを作成してみた。
若干躓くポイントがあり、日本語での情報が少なかったので記事にまとめてみた。
この記事の内容
- Responderを使ってユーザーが入力した文章をオウム返しするLINEbotの作成
そもそもResponderとは?
PythonのWebフレームワークとしてはDjangoやFlaskなどが有名だが、Responderはこのようなフレームワークの良い部分を取り入れた次世代Webフレームワーク。
私自身は今までFlaskを使ってきたが、Flaskでは非同期処理を実装するのが困難だった。
一方、Responderではasync/awaitを用いて簡単に非同期処理を実装できる。
今後使用する人が増えていくことが期待されている、非常にスマートなWebフレームワークである。
Responderの導入
ResponderはPyPIに登録されているので、pipで簡単にインストールできる。
Responderの詳細な使い方はここでは説明しない。
丁寧に説明しているページがあるので、そちらを参考にするのが良い。
また、実際に手を動かしながら覚える場合、この記事がおすすめ。
私もこれで勉強した。
オウム返しbotの作成
Flaskではサンプルとしてユーザーのメッセージをオウム返しするLINEbotのコードが公開されている。
このコードを改変することで様々なLINEbotを作成できる。
ちなみにLINE Messaging APIを利用する場合、サーバーがhttps接続でないと正常に動作しない。
自分で証明書を取得するのが面倒な場合はherokuなどを利用してサーバーを公開すると便利。
Responderを用いて先ほど述べたオウム返しbotと同様の挙動をするコードを以下に示す。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
import responder
import json
from linebot import ()
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage
)
api = responder.API()
# LINEの設定
line_bot_api = LineBotApi("LINE_ACCESS_TOKEN") # ここにACCESS TOKENを入力
handler = WebhookHandler("LINE_CHANNEL_SECRET") # ここにCHANNEL SECRETを入力
@api.route('/callback')
class LINEbot:
async def on_post(self, req, resp):
# get X-Line-Signature header value
signature = req.headers['X-Line-Signature']
# get request body as text
body = await req.media()
body = json.dumps(body, ensure_ascii=False).replace(' ', '')
try:
# handle webhook body
handler.handle(body, signature)
resp.status_code = 200
resp.text = 'OK'
except InvalidSignatureError as e:
print(e)
resp.status_code = 400
resp.text = e
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text)
)
if __name__ == '__main__':
api.run(address='0.0.0.0')
|
基本的にFlaskのものと大きく変わっている部分はない。
しかし、ハマるポイントが一点だけある。
それはsignatureを利用した認証の部分。
はじめ、何回やってもInvalidSignatureErrorが発生し、心が折れそうになった…
line-bot-sdkの認証部分のコードを探したところ、以下の処理を行っている模様。
(上記のコードのみで動作するため、ここから先は興味のある方のみ読めばOK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
if hasattr(hmac, "compare_digest"):
def compare_digest(val1, val2):
"""compare_digest function.
If hmac module has compare_digest function, use it.
Or not, use linebot.utils.safe_compare_digest.
:param val1: string or bytes for compare
:type val1: str | bytes
:param val2: string or bytes for compare
:type val2: str | bytes
:rtype: bool
:return: result
"""
return hmac.compare_digest(val1, val2)
else:
def compare_digest(val1, val2):
"""compare_digest function.
If hmac module has compare_digest function, use it.
Or not, use linebot.utils.safe_compare_digest.
:param val1: string or bytes for compare
:type val1: str | bytes
:param val2: string or bytes for compare
:type val2: str | bytes
:rtype: bool
:return: result
"""
return safe_compare_digest(val1, val2)
class SignatureValidator(object):
"""Signature validator.
https://developers.line.biz/en/reference/messaging-api/#signature-validation
"""
def __init__(self, channel_secret):
"""__init__ method.
:param str channel_secret: Channel secret (as text)
"""
self.channel_secret = channel_secret.encode('utf-8')
def validate(self, body, signature):
"""Check signature.
:param str body: Request body (as text)
:param str signature: X-Line-Signature value (as text)
:rtype: bool
"""
gen_signature = hmac.new(
self.channel_secret,
body.encode('utf-8'),
hashlib.sha256
).digest()
return compare_digest(
signature.encode('utf-8'), base64.b64encode(gen_signature)
)
|
簡単にいうとLINE Messaging APIでの認証は以下の流れで行っている。
- CHANNEL SECRET をkeyとしてbodyをSHA-256でハッシュ値に変換
- headerに含まれている signature と比較し、一致していない場合は InvalidSignatureError を例外としてraiseする
LINEのサーバー側でも同様の処理でハッシュ値に変換しているため、同じ CHANNEL SECRET を使用していない場合は認証エラーとなるということ。
実際にこの処理を行ってデバッグしながら調べたところ、 json.dumps() を利用してdictをstrに変換した場合、途中にスペースが入ってしまう。
どうやらLINEのサーバー側ではスペースが入っていない状態のbodyを使ってハッシュ値を計算しているようで、スペースを削除したらハッシュ値がheaderの signature と一致した。
これで無事オウム返しbotを動作させることができた。
まとめ
Responderを使ってオウム返しをする簡単なLINEbotを作成した。
非同期処理が簡単に実装できるのは魅力的なので、今後は積極的にResponderを使っていきたい。