[無料][2024年版] LINE Messaging API v3 + Python(Flask) でボットを作る [その0 - v3 での変更点]

## はじめに

つい最近、LINE の Messenger API の v3 なるものを試す機会があり([この記事](https://qiita.com/nozomiyamada/items/4ac398159cdcf2f92056))、しばらくいじっているうちにやっと使い方がわかってきたのでここにメモしておこうと思います。

わざわざこうやって記事にまとめようと思った理由は、もちろん自分用の備忘録が一番ですが、他にも

- Line は日本、タイ、台湾などでしか使われていないガラパゴスツールなので、Stack Overflow に情報がない
- Qiita の情報も、v3 以前のものが多い
- そもそもPython で開発している人も多くない
- **本家 `line-bot-sdk` の [GitHub](https://github.com/line/line-bot-sdk-python) の説明が少なすぎる**

などの理由から、せっかくなので共有しておこうと思ったからです。あくまで個人開発のレベルで、専門的なことはパッパラパーなのですが、こんな素人の私の記事でも多少は役に立つ内容にしようと思います。

ちなみにどうしても Python + Flask を使いたい方向けの解説です。最近はGAS(Google Apps Script)を使って開発する方も多く、そちらの方がGCPでデプロイもできるので楽です。私が Python を使う理由は機械学習や自然言語処理系のライブラリを使っているからであり、そういったものを必要としないならばGASの方が簡単にできると思います、記事もたくさん見つかります。

これから何回かに分けて記事を書いていく予定ですが、今回はまず v3 という新しいバージョンがどういうものか、実際に使ってみた内容をまとめてみます。既に Flask + Messeging API を使って開発したことがある方向けの説明なので、「これから Python を使ってボット開発を始めたい！」という方は読み飛ばして、次回以降の記事から読んでいただいても構いません。

## v3 について

2023年6月頃に導入された OpenAPI 仕様に沿った新バージョンのSDK(ソフトウェア開発キット)の仕様になります。ただし、APIのエンドポイントは https://api.line.me/v2/bot/ のままで、リクエストの方法も同じです。

https://github.com/line/line-bot-sdk-python/tree/master?tab=readme-ov-file#version-3x

こちらにも書かれているように将来的には v3 に統一化するようで、現状でも deprecated warning が出てくるので精神衛生上よろしくありません。なのでこれから開発をする方は、v3 で始めた方が良いかと思います。

## v3 での変更点

使っていて気づいた変更点をまとめると、

1. 認証用のクラスをインスタンス化
2. クラスの再編成
3. 返信時は、メッセージタイプに関わらず `ReplyMessageRequest` オブジェクトとして渡す
4. キーワードのキャメルケース対応化
5. Flex Message 用のクラスが導入

というものです。

### 1. 認証用のクラスをインスタンス化

`line-bot-sdk` でメッセージを送信する際は、今まで

~~~python
app = Flask(__name__)

## APIインスタンス化
line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(CHANNEL_SECRET)

## callback認証
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'

## テキストメッセージ返信
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    
    ### ここでメッセージ処理する

    ## 返信
    line_bot_api.reply_message(event.reply_token, message)
~~~

のような形式だったかと思います。これが v3 においては、大幅に変更されています（[こちら](https://github.com/line/line-bot-sdk-python?tab=readme-ov-file#synopsis)を参照）。以下は公式のオウム返しサンプルコードです。

~~~python
app = Flask(__name__)

configuration = Configuration(access_token='YOUR_CHANNEL_ACCESS_TOKEN')
handler = WebhookHandler('YOUR_CHANNEL_SECRET')


@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        app.logger.info("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    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)]
            )
        )
~~~

発行した `CHANNEL_ACCESS_TOKEN` を渡すのはAPIオブジェクトではなく、`Configurtion` という認証用のオブジェクトに代わっています。`line_bot_api` のインスタンス化は関数内部（イベントハンドラー内）で行われるようになり、名称も `LineBotApi` から `MessagingApi` に変更されています。

ちなみに、このAPIのインスタンス化は関数内部ではなく最初にやってしまっても動くことは動きます。つまり、

~~~python
app = Flask(__name__)

configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(CHANNEL_SECRET)

## APIインスタンス化
with ApiClient(configuration) as api_client:
	line_bot_api = MessagingApi(api_client)

@app.route("/callback", methods=['POST'])
def callback():
    ...

@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    
    ## メッセージ処理諸々
    
    line_bot_api.reply_message(
		ReplyMessageRequest(
			replyToken=event.reply_token,
			messages=message
		)
	)
~~~

のようにするということです。ただし、認証に関するタイムアウト的な問題なのか、時間が空くとプロトコルエラーが起きたり起きなかったりします（ネットワーク関連の知識が全然なくて申し訳ありません）。なので、素直に関数内でその都度インスタンス化することをオススメします。

また、返信用のメソッドも v3 以前では `line_bot_api.reply_message()` だったのが、v3 では `line_bot_api.reply_message_with_http_info()` という長ったらしいものに変わっています。

ですが、実は v3 にも `.reply_message()` が残されており、ソースコードを見る限りこの内部で `.reply_message_with_http_info()` を呼び出している（違いはあるようですが私にはわかりません）ので、私は短い方を使っています。上記のコードもそう修正しています。

### 2. クラスの再編成

v3 以前で `linebot.models` にあった様々なクラスは、送信処理関連の `linebot.v3.messaging` と webhook 受信処理の `linebot.v3.webhooks` に分離しています。具体的には

:::note
#### `linebot.v3.messaging`

必ず使うもの

- `Configuration` : 認証用クラス
- `MessagingApi`, `ApiClient` : APIのインスタンス化

メッセージ送信方法別 ([APIドキュメント](https://developers.line.biz/ja/docs/messaging-api/sending-messages/#methods-of-sending-message))

- `ReplyMessageRequest` : webhook に応答して返信する時
- `PushMessageRequest` : 任意のタイミングでプッシュメッセージを送信する時

メッセージタイプ別 ([APIドキュメント](https://developers.line.biz/ja/docs/messaging-api/message-types/))

- `TextMessage`, `AudioMessage`, `TemplateMessage`, `FlexMessage` など

アクションタイプ別 ([APIドキュメント](https://developers.line.biz/ja/docs/messaging-api/actions/))

- `PostbackAction`, `MessageAction`, `URIAction` など

リッチメニュー関連

- `RichMenuArea`, `RichMenuSwitchAction` など ([APIドキュメント](https://developers.line.biz/ja/reference/messaging-api/#rich-menu))
:::

:::note
#### `linebot.v3.webhooks`

イベントタイプ別 ([APIドキュメント](https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects))

- `FollowEvent`, `MessageEvent`, `PostbackEvent` など

コンテンツ別 ([APIドキュメント](https://developers.line.biz/ja/reference/messaging-api/#getting-content))

- `TextMessageContent`, `PostbackContent`, `AudioMessageContent` など
:::

のようになっています。従来 webhook のコンテンツ用だった `TextMessage` は、v3 では `TextMessageContent` となり、逆に `TextMessage` はテキストをメッセージオブジェクト化するためのクラスとなっています。

### 3. 返信時は、メッセージタイプに関わらず `ReplyMessageRequest` オブジェクトとして渡す

上述の v3 のサンプルコードの返信部分を確認してみましょう。

~~~python
@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    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_api.reply_message_with_http_info()` の中身が、`ReplyMessageRequest` というクラスになっており、`messages` というキーワードで各種のタイプのメッセージオブジェクトのリストを受け取るようになっています。

以前は `.replay_message()` メソッドに直接 `TextSendMessage` や `AudioSendMessage` などのクラスを渡していました。v3 ではそれらを一旦 Request オブジェクトにしてから、`.replay_message()` に渡すという形式になっています。`reply_token` も、この `ReplyMessageRequest` に渡します。

### 4. キーワードのキャメルケース対応化

サンプルコードでは全て以前同様にスネークケースで書かれているのですが、エディタ内ではキャメルケースで表示されたので気づきました。これはエイリアスされていて、どちらで書いても動きます。例えば上述の `ReplyMessageRequest` なら、

~~~python
ReplyMessageRequest(
    reply_token=event.reply_token,
    messages=[TextMessage(text=event.message.text)]
)

ReplyMessageRequest(
    replyToken=event.reply_token,
    messages=[TextMessage(text=event.message.text)]
)
~~~

の両方が可能です。旧コードをコピーして v3 用に修正する時にはスネークケースのままで問題ありません。

### 5. Flex Message 用のクラスが導入

これは[前回の記事](https://qiita.com/nozomiyamada/items/4ac398159cdcf2f92056)で紹介したんですが、FLex Message を使うためのクラスが充実しています。JSONや辞書を一切書くことなく、オブジェクトの組み合わせで全てが完了します。

もちろん [Flex Message Simulator](https://developers.line.biz/flex-simulator/) を使ってのプレビュー確認は欠かせませんのでJSON書き出しは使います。また、コーディング量自体にそこまで大差はないですが、各クラスをインスタンス化した際に適切なパラメータを与えないと `ValidationError` が出て怒られるので、非常にデバッグがしやすくなりました。

---

これ以外にも細かな変更点はたくさんあると思いますが、使いながらまた追記していきます。

ということで、次回の記事からタイトルの **[無料][2024年版]** のボット開発の内容に入っていきます。

## 目次 : [無料][2024年版] LINE Messaging API v3 + Python(Flask) でボットを作る

[GitHub レポジトリ](https://github.com/nozomiyamada/linebot/tree/main)
`older_version` 内に、各回時点での `app.py` と `requirements.txt` があります。

- その0 - v3 での変更点
- [その1 - 準備編](https://qiita.com/nozomiyamada/items/f6c6816e02359e08a74e)
- [その2 - テストアプリ編](https://qiita.com/nozomiyamada/items/87389aacb151b8418501)
- [その3 - 情報検索理論(前編)](https://qiita.com/nozomiyamada/items/d1bcedcc885c43794ff7)
- [その4 - 情報検索理論(後編)](https://qiita.com/nozomiyamada/items/6b0b30ebc36eeddc73d2)
- [その5 - tips 編](https://qiita.com/nozomiyamada/items/58918cd4024987ad1979)
- [その6 - モジュール化編](https://qiita.com/nozomiyamada/items/b80eaf5aef0c294c4acf)
- [その7 - クラス化編](https://qiita.com/nozomiyamada/items/c8315c8a0f0f7d19129b)
- その8 - Flex Message 編