LoginSignup
3
3

【Python】オウム返しするLINE BOT作成

Last updated at Posted at 2023-10-14

今回は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にアクセスするとそれを確認することができます。
スクリーンショット 2023-10-14 15.11.30.png

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
-------------

以下のように設定して検証を押します。
スクリーンショット 2023-10-14 15.29.10.png

以下のように表示されたら疎通成功です。
スクリーンショット 2023-10-14 15.30.31.png

オウム返し

前のセクションでWebhookでの疎通に関する部分が終わりました。
これから実際の処理(オウム返し)を実装していきます。

設定

アカウントの設定から以下のように挨拶メッセージとwebhookのみにしましょう。
応答メッセージ等がオンになっていると、自動的に余計な返信が行われてしまいますのでこのようにオフにしておきましょう。
スクリーンショット 2023-10-14 16.18.12.png

オウム返し実装

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を作れたりするのでそういったことにも挑戦してみようかなと思います。
あと折角なのでこれからこのコードを拡張してより応用的なアプリケーションを次回以降作っていこうと思います!

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3