今回はPython(FastAPI)とLINE messaging APIを用いてオウム返しをするLINE BOTを開発します。
FastAPI は pythonのAPI用フレームワークです。
私はこれを使ったことなかったですが、ドキュメントを見た感じとっかかりやすそうだったのと、高速ということでレス時間がクオリティをかなり左右するボット開発においては有用かなと思い使ってみました。
なるべくLINE BOT SDKのメソッドの働き等についても踏み込んで説明します。
python-sdkに関してはドキュメントが存在しないので、参考になれば良いなと思います。
入門者の方はこちらを先に目をとしていただければFastAPIに関しての知識は十分得れるかと思います。
また、事前準備のセクションで記載していますが、今回はこの記事の環境構築部分を用いているのでついでにdocker環境を構築すると良いかなと思います。
FastAPI入門
FastAPI日本語ドキュメント
FastAPI
事前準備
オウム返しをするLINE BOTをFastAPI (Python)を用いて作るにあたって主に以下のような下準備が必要になります。
今回はdockerで開発しますが、コードは環境に問わず参考になるかなと思います。
環境も同じにしたい場合は以下の4つのサイトをもとに構築してください。
ngrokインストール
外部にlocalhostを公開するために使います。LINE botの動作確認に使用します。
ngrokとは?インストール〜公開までの手順まとめ | Miyachi Labo
FastAPI(python) + Docker環境の構築
以下のサイトを参考に、FastAPIインストールの章まで実行します。
FastAPI入門
LINE BOTの作成
以下サイトの1番のセクションを実施して、アカウントの作成までを行う。
【python】LINE botの作り方(Messaging API) | プログラミングLab
Docker環境内でのパス設定
初期状態ではPythonの参照元がdockerコンテナ内のものではない可能性が高いです。そのため、ファイル内でライブラリをインポートしてもvs codeのエラーが出てしまう場合があります。
Visual Studio Code のDev Containersという拡張機能で解消できるので試してみてください。(import時のウジウジ下線が気にならない場合は特にしなくても大丈夫です。僕は気になります。)
Visual Studio Code を使用して Docker コンテナーを開発環境として使用する - Training
実装
初期設定
.envファイルを作り環境変数を設定
.envファイルをapiフォルダ内に作成します。
LINE Developersからチャネルシークレットとチャネルアクセストークンを確認し、環境変数として設定します。
CHANNEL_SECRET=~~~~~~~~~~~~~~~~~~~
CHANNEL_ACCESS_TOKEN=~~~~~~~~~~~~~~~~~~~
.envファイルをプロジェクト内で読み込めるようにするために以下のコマンドを実行し、dotenvライブラリをインストールします。
poetry add python-dotenv
main.pyを編集しテスト用エンドポイントを作成
以下のコードをapiは以下のmain.pyに記述します。
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
async def hello():
return {"message": "hello world!"}
/docsをブラウザで開き、helloエンドポイントを実行してhello worldが帰ってきていたら成功です。Fast APIではswagger UIが自動生成され、/docsにアクセスするとそれを確認することができます。
line-bot-sdkをインストール
Line messaging APIを用いた開発を行うにはあらかじめ用意されているLINE-BOT-SDKを使用します。
そこでこちらも同様にインストールしておきます。
poetry add line-bot-sdk
以下がインストールしたsdkのgithubリンクになります。
詳細な仕様等についてreadmeに書いてありますので、今後使い方も説明しますが詳しく使い方が知りたい人は目を通してみてください。
Webhook用のエンドポイント定義
LINEのチャットボットをpython等の自作バックエンドで実行させるには、webhook用のエンドポイントの設定が必要です。
役割の説明を要約すると、ここではLINEプラットフォームからのリクエストであるということを確かめる動作をするということです。
その処理をこのセクションでは実装していきます。
LINE用のファイルを用意
これからスケールすることを考えてファイルを分割していこうと思います。
apiフォルダの配下にroutersフォルダを作成します。
また、作成したroutersフォルダ内に__init__.pyとline.pyファイルを追加します。
Webhook用のエンドポイント作成
line.pyを編集してWebhook用のエンドポイントを作成していきます。
from fastapi import (
APIRouter,
Header,
Request
)
import os
from dotenv import load_dotenv
from linebot.v3 import WebhookHandler
from linebot.v3.exceptions import InvalidSignatureError
from starlette.exceptions import HTTPException
load_dotenv()
router = APIRouter()
handler = WebhookHandler(os.environ.get('CHANNEL_SECRET'))
@router.post(
'/api/callback',
summary='LINE Message APIからのコールバック',
description='ユーザーからメッセージを受信した際、LINE Message APIからこちらにリクエストが送られます。',
)
async def callback(request: Request, x_line_signature=Header(None)):
body = await request.body()
try:
# リクエストがLINEプラットフォームからのものかを検証する
handler.handle(body.decode("utf-8"), x_line_signature)
except InvalidSignatureError:
raise HTTPException(status_code=400, detail="InvalidSignatureError")
return "OK"
また、これらのルートファイルをapi/main.pyに認識させるために、main.pyに以下を追記します。
from fastapi import FastAPI
# 追加
# lineファイルをインポート
from api.routers import line
app = FastAPI()
# 追加
# lineファイルで定義したルートをアプリケーションに適用
app.include_router(line.router)
@app.get("/hello")
async def hello():
return {"message": "hello world!"}
(使用したLINE-BOT-SDK部分の解説)
- WebhookHandler
後ほど他のメソッド等も出てきますが、こちらで出てきたコードについて説明します。
このオブジェクトはこのコードで使用したように、lineプラットフォームから何らかのリクエストがあった時のハンドリングを行うためのものです。
インスタンス化
handler = linebot.WebhookHandler('YOUR_CHANNEL_SECRET')
handleメソッド
以下のようにhandlerオブジェクトのhandleメソッドを使用し、リクエストボディとリクエストヘッダーに含まれるsignatureを検証してLINEプラットフォームからのアクセスであるかどうかを検証しています。
厳密に言えばもう少し複雑なことが行われていますが、イメージとしてこんな感じで理解できていれば十分かと思います。
handler.handle(body, signature)
検証
ngrokで起動しているlocalhostを外部公開し、Line messaging APIのwebhook用URLに設定していきます。
ngrok http 8000
以下のような記載が出てくるのでForwardingにあるURLをコピーして、webhook用のURLとして使います。
ngrok (Ctrl+C to quit)
Introducing Always-On Global Server Load Balancer: https://ngrok.com/r/gslb
Session Status online
Account 〇〇 (Plan: Free)
Update update available (version 3.3.5, Ctrl-U to update)
Version 3.0.7
Region Asia Pacific (ap)
Latency 37ms
Web Interface http://127.0.0.1:4040
Forwarding https://c52e-203-205-52-152.ngrok-free.app -> http://localhost:8000
Connections ttl opn rt1 rt5 p50 p90
3 0 0.00 0.00 1.97 4.51
HTTP Requests
-------------
オウム返し
前のセクションでWebhookでの疎通に関する部分が終わりました。
これから実際の処理(オウム返し)を実装していきます。
設定
アカウントの設定から以下のように挨拶メッセージとwebhookのみにしましょう。
応答メッセージ等がオンになっていると、自動的に余計な返信が行われてしまいますのでこのようにオフにしておきましょう。
オウム返し実装
line.pyの記述を以下のように変更します。
from fastapi import (
APIRouter,
Header,
Request
)
import os
from dotenv import load_dotenv
from linebot.v3 import WebhookHandler
# 以下のimportを追加
from linebot.v3.exceptions import InvalidSignatureError
from linebot.v3.messaging import (
Configuration,
ApiClient,
MessagingApi,
ReplyMessageRequest,
TextMessage
)
from linebot.v3.webhooks import (
MessageEvent,
TextMessageContent
)
# ここまで
from starlette.exceptions import HTTPException
load_dotenv()
router = APIRouter()
handler = WebhookHandler(os.environ.get('CHANNEL_SECRET'))
# 以下の記述を追加
configuration = Configuration(access_token=os.environ.get('CHANNEL_ACCESS_TOKEN'))
@router.post(
'/api/callback',
summary='LINE Message APIからのコールバック',
description='ユーザーからメッセージを受信した際、LINE Message APIからこちらにリクエストが送られます。',
)
async def callback(request: Request, background_tasks: BackgroundTasks, x_line_signature=Header(None)):
body = await request.body()
try:
background_tasks.add_task(
handler.handle(body.decode("utf-8"), x_line_signature)
)
except InvalidSignatureError:
raise HTTPException(status_code=400, detail="InvalidSignatureError")
return "OK"
# 以下を追加
@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event: MessageEvent):
with ApiClient(configuration) as api_client:
line_bot_api = MessagingApi(api_client)
line_bot_api.reply_message_with_http_info(
ReplyMessageRequest(
reply_token=event.reply_token,
messages=[TextMessage(text=event.message.text)]
)
)
(使用したLINE-BOT-SDK部分の解説)
- MessagingApi, ApiClient, Configuration
以下の流れでbotのインスタンスを作成し、MessagingApiインスタンスの持つメソッドによってユーザーへのメッセージ送信などを行うことができます。
この手順は深い理解というより、テンプレのように思っていれば大丈夫だと個人的には思います。
configuration = Configuration(access_token=os.environ.get('CHANNEL_ACCESS_TOKEN'))
api_client = ApiClient(configuration)
line_bot_api = MessagingApi(api_client)
MessagingApiオブジェクトのメソッドは以下から詳しく見ることができます。
公式ドキュメント
- WebhookHandler
addメソッド
addメソッドでは特定のイベントがあった場合の処理内容を定義することができるメソッドです。
具体的には以下のように使うことができます。
@handler.add(MessageEvent, message=TextMessage)
# メッセージイベントかつテキストメッセージを受け取った際の処理を記述
@handler.add(MessageEvent)
# メッセージイベントがあった際の処理を記述
@handler.add(FollowEvent)
# フォローイベントがあった際の処理を記述
まとめ
Fast APIとLine messaging APIを用いてオウム返しするbotを開発してみました。
Fast APIはほぼ初めて使いますが、かなり簡単で使い勝手が良いなと思いました。(もしこうした方が良い等ありましたらご指摘ください)
今回はただのオウム返しでしたが、LINE messaging APIではおしゃれなUIを作れたりするのでそういったことにも挑戦してみようかなと思います。
あと折角なのでこれからこのコードを拡張してより応用的なアプリケーションを次回以降作っていこうと思います!