LINEメッセージをAWS Lambdaで送受信してみたお話

## はじめに
業務にてLINE MessagingAPIを利用したLINEでのコミュニケーションツールを作成したので、技術共有と自分の備忘録もかねて書いていきます。
流れとしては、[公式LINEアカウントへのログイン処理をAWS Lambda経由で実装してみた](https://qiita.com/inacyc_k/items/8337489e3e148db1f9e5)の続きの実装になります。あわせてぜひ読んでいただければと思います。


## LINE MessagingAPI ってなに
みなさん、LINEの公式アカウント利用していますか？（私はよく佐川急便とかUNIQLOの公式アカウントをよく利用しています）LINEの公式アカウントをユーザーに友だち登録いただくことでメッセージが届いたり、チャット上で会話形式の質問に回答したりとWebサイトやメルマガよりも気軽に情報発信することができます。

このようにMessagingAPIは、
・さまざまなメッセージをユーザーへ送信
・ユーザーが送ったメッセージを受信
そして
・ユーザープロフィールの取得（アイコン画像やユーザー名）
・アカウント連携（LINEアカウントと自社サービスの連携）
などの様々な機能を実現できます。

料金体系は3つあり、無料からでも始めることができます。
|| コミュニケーションプラン | ライトプラン | スタンダードプラン |
|:---:|:------------:|:------------:|:------------:|
| 月額固定費（税別） | 0円 | 5,000円 | 15,000円　|
| 無料メッセージ通数（月） | 200通 | 5,000通 | 30,000通 |
| 追加メッセージ料金（税別） | 不可 | 不可 | ~3円/通 |

メッセージ数は送付人数×メッセージ数となります。（登録している50人の友だち全員へ一斉に送信した場合、50人×4メッセージ＝200通となります）
30,000通以上の場合、1通あたり最大3円、メッセージ総数が増えれば増えるほど1通あたりのお値段が安くなります。

参考：[LINE公式ドキュメント-Messaging APIの概要-](https://developers.line.biz/ja/docs/messaging-api/overview/)

## LINE構成
今回作成する構成は以下のようになります。
<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/1f69a188-f7aa-8622-4541-5adccfc0cecd.png">

LINE公式アカウントにユーザーが友達登録を行うと、メッセージを受信できる。そしてユーザーからのメッセージを受信できる。という処理になります。
実際の業務では別アプリケーションからメッセージのLINEへ転送する、LINEでも同様にメッセージのやり取りができる、というものを目的としています。

## 事前準備
必要なものは
・LINEDevelopersアカウント（LINEBussiness ID）
・AWSアカウント
となります。

:::note info
LINE公式アカウントへの設定画面は”LINE Official Account Manager”と”LINE Developersコンソール”の２種類存在します。
LINE Official Account Managerでは公式LINEアカウントの運用状況の確認、分析や標準設定を管理し、
LINE DevelopersコンソールではLINEのAPIの設定を管理します。
:::

LINE DevelopersではMessaging APIチャネルを作成します。
新規にMessaging APIを作成すると、同名のLINE公式アカウントが自動で作成されます。
この公式アカウントからメッセージのやり取りを行うことができる、ということになります。

<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/e7ef6440-7bd1-dea1-4644-cee8ac8918ec.png">

すでに作成されたLINE公式アカウントに対しMessaging APIを付与することも可能です。
LINE Official Account Managerより該当のLINE公式アカウントの設定画面よりMessaging APIを有効に変更できます。
<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/fdaf2a33-6b80-1495-8e5d-f5895e8ffeea.png">

次に、作成したLINE公式アカウントの設定変更を行います。
LINE Official Account Managerにて、設定を開き、WebhookをON、応答メッセージをOFFにします。

<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/05b3d92b-cda2-56a4-6732-e2ab22244ccc.png">

またWebhook設定配下のMessaging APIの設定画面を開くを押下し、Webhookの設定を行います。
Webhookとはユーザーが公式LINEへ向けてメッセージを送ったり、友達追加または解除を行なったりなどの操作が行われた場合、Webhookイベントという、LINEがイベント情報をPOSTリクエストとして送信します。このWebhookの中身にはLINEユーザーIDや操作した内容、送信したメッセージ内容などが含まれます。
ここではそのWebhookをどこへ送信するか、WebhookURLを設定します。
このURLは以下作業で作成するAPI Gatewayのエンドポイントを指定してください。

参照：[Webhookについて](https://developers.line.biz/ja/docs/messaging-api/receiving-messages/)

---
AWSではAPI GatewayとLamdbaを作成します。
こちらの作成方法は[以前の記事](https://qiita.com/inacyc_k/items/8337489e3e148db1f9e5)と同様に作成します。
API GatewayおよびLamdba関数はそれぞれ2つずつ作成し、プロキシ統合します。今回はどちらもPOSTメソッドで実装します。


## LINE公式アカウントへメッセージ送信

早速、友だち登録したのでメッセージを送ってみましょう。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/fbcedffa-58d6-dc03-aa63-670c70992b65.png">

...もちろんですがなにも設定していないので、既読無視されてしまいました。

今回はこのメッセージ内容へ返信できるようにしてみましょう。
テキストメッセージが送信されたら、そのメッセージをそのままおうむ返しします。
画像、LINEスタンプの場合は固定の文言を返します。
メッセージ送信以外の処理としては、メッセージ送信取消を行った場合それを検知した旨をメッセージとして返します。

```python:Receive.py

import json
from linebot.models import TextSendMessage
from linebot import (
    LineBotApi,
)

def lambda_handler(event, context):
    
    # paramater取得
    body = json.loads(event["body"])
    event_type = body["events"][0]["type"]
    user_id = body['events'][0]['source']['userId']
    
    # 環境値（ご自身の環境値に置き換えてください。）
    line_channel_access_token = 'LINE_CHANNEL_ACCESS_TOKEN'
    
    # メッセージ送信BOT作成
    line_bot_api = LineBotApi(line_channel_access_token)

    # Type分岐
    # メッセージ送信の場合の処理
    if event_type == 'message':
        message_type = body['events'][0]['message']['type']
        reply_token = body['events'][0]['replyToken']

        # 送信されたメッセージがtext（テキスト）の場合の処理
        if message_type == 'text':
            message = body['events'][0]['message']['text']
            line_bot_api.reply_message(reply_token, TextSendMessage(message))
            
        # 送信されたメッセージがimage（画像）の場合の処理
        elif message_type =='image':
            line_bot_api.reply_message(reply_token, TextSendMessage("綺麗な画像ですね"))
        
        # 送信されたメッセージがsticker（LINEスタンプ）の場合の処理
        elif message_type =='sticker':
            line_bot_api.reply_message(reply_token, TextSendMessage("かわいいスタンプですね"))
        
        # 上記以外のmessage_typeの場合の処理（今回の対象外）
        else:
            pass
               
    # メッセージ送信取消の場合の処理
    elif event_type == 'unsend':
        line_bot_api.push_message(user_id, TextSendMessage("送信取消されました。"))
    
    # 上記以外のevent_typeの場合の処理（今回の対象外）
    else:
        pass
    
    return {
        'statusCode': 200
    }


```
実際の動きはこのような感じです。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/34e99756-d8fe-d9aa-180b-96c8ecfef798.png">

既読無視されていたのが、返信してくれるようになりました..!
他にも色々送信してみましょう。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/6e9f60f0-8e7d-26cf-aaad-6d40ea4ec141.png">

送信した内容に応じ、応答メッセージが変化しています。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/b8fb29f6-56db-b7bc-16a5-3df0a8eed2a5.png">

こんにちは！と送信したメッセージに対し、送信取消を行いました。こちらの操作に対してもメッセージが返答されているのがわかりますね。

---

具体的なコードの説明を行なっていきます。コードの以下の部分で返信をしています。

```
①line_bot_api.reply_message(reply_token, TextSendMessage("綺麗な画像ですね"))
```
```
②line_bot_api.push_message(user_id, TextSendMessage("送信取消されました。"))
```

この2つ、どちらも固定メッセージを返しているだけではありますが、処理方法が異なります。
①は、イベント受信したメッセージに対しリプライメッセージを送信しており、課金メッセージ数に加算されません。
②は、イベント受信したユーザーに対しプッシュメッセージを送信しており、課金メッセージ数に加算されます。

すべてリプライメッセージにすれば良いのでは？となりますが、メッセージイベント以外のイベント（ここでは送信取消イベント）ではリプライメッセージへ対応がない、つまり`reply_token`パラメーターが存在しないため、プッシュメッセージとして送るしかないのです。

---

トークでのWebhookイベントは以下のように挙げられます。（一部抜粋）
コードでいう`event_type`での分岐箇所になります。
| イベント | 概要 | event_type |
|-----------|------------|------------|
| メッセージイベント | ユーザーがメッセージを送信した際 | message |
| 送信取消イベント | ユーザーがメッセージの送信を取り消した際（送信から24時間以内） | unsend |
| 友だち登録イベント | ユーザーが該当LINE公式アカウントを友だち追加した際 | follow |
| 友だちブロックイベント | ユーザーが該当LINE公式アカウントをブロックした際 | unfollow |
| ポストバックイベント | ユーザーが関連付けられたアクションを実行した際（ボタン選択アクションなど） | postback |

参照：[Webhookイベントのタイプ](https://developers.line.biz/ja/docs/messaging-api/receiving-messages/#webhook-event-types)

またメッセージイベント内、送信されたメッセージごとに以下のように挙げられます。（一部抜粋）
コードでいう`message_type`での分岐箇所になります。
| メッセージ | 例 | message_type |
|-----------|------------|------------|
| テキストメッセージ | <img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/1c8dc676-2ab0-e27c-5485-fb14727c599c.png">| text |
| 画像メッセージ | <img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/95f908b7-206f-4699-a888-d4b9d8374239.png">| image |
| 位置情報メッセージ | <img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/aafe3591-f3e4-ede9-70c0-4befba160971.png"> | location |
| LINEスタンプメッセージ | <img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/00a045af-0f03-8eb1-da62-a480f1bdaffc.png">| sticker |


参照：[メッセージオブジェクト](https://developers.line.biz/ja/reference/messaging-api/#message-objects)

## LINE公式アカウントからメッセージ受信

ユーザーがLINE公式アカウントを通じてメッセージを送信することができました。
次にAPIを通じ、LINE公式アカウントからメッセージを受信してみましょう。

と、いっても使う技術はすでに記述済みなので組み合わせるだけなのです。
API呼び出し時に送信したいLINEユーザーIDとテキストメッセージをパラメーターとして付与し、プッシュメッセージとして送信します。

```python:Send.py

import json
from linebot.models import TextSendMessage
from linebot import (
    LineBotApi,
)

def lambda_handler(event, context):

    # paramater取得
    body = json.loads(event["body"])
    user_id = body['userId']
    text_message = body['message']

    # 環境値（ご自身の環境値に置き換えてください。）
    line_channel_access_token = 'LINE_CHANNEL_ACCESS_TOKEN'
    
    # メッセージ送信BOT作成
    line_bot_api = LineBotApi(line_channel_access_token)

    line_bot_api.push_message(user_id,  TextSendMessage(text_message))

    return {
        'statusCode': 200
    }   

```
実際の動きはこのような感じです。
APIを叩くと...？
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/96fa94d0-8aad-298a-2747-cdf53a3333bf.png">

ユーザーからのメッセージの返信ではなく、LINE公式アカウントからメッセージを受信できました！

## Flex Message

LINE公式アカウントを通じてメッセージの送受信を行うことができました。
しかし、ただのテキストメッセージというのは味気ないですよね..

そこで通常のメッセージに比べ、より自由なレイアウトのメッセージを作成し、送信することが可能です。それが[Flex Message](https://developers.line.biz/ja/docs/messaging-api/using-flex-messages/)です。

一例として日常生活の中で利用している公式LINEアカウントでのFlex Messageを利用しているトーク画面です。

例：佐川急便のLINE公式アカウント
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/c65b133f-c587-d5ee-204d-52af5bd5a58c.png">

→ヘッダー部分（〜のお知らせ）の背景色および文字色の変化
→重要部分を太字、下部のフォントサイズを小さく変更
→中央「配達日時を変更する」ボタン押下で紐づいたURLリンクへ遷移

例：新型コロナウイルス療養サポートのLINE公式アカウント
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1827273/e5ec39ae-da15-aeb5-5cd0-aa5caaa1ef87.png">

→毎朝メッセージを受信
→下部「回答を開始」ボタン押下でポストバック処理。「回答を開始」メッセージの自動送信
→先ほどの「配達日時を変更する」ボタンとは色、サイズが異なるボタン
※”このまま少々お待ちください”は通常テキストメッセージ

通常テキストメッセージより、文字の大きさや色を変化させることで目立たせたり、ボタンアクションを追加して次の操作をしやすくしたりなどより良いUIのメッセージを送信することができます。
他にも自由なメッセージの形を作成できます。実際どんなメッセージが送信できるか、[Flex Message Simulator](https://developers.line.biz/flex-simulator/)（LINEDevelopersアカウントが必要）確認ください。

## まとめ
今回はLINE公式アカウントを通じ、イベントを受信し内容に応じてメッセージ返答したり、APIを通じてLINE公式アカウントからメッセージを配信したりなどを行いました。特にFlex Messageを利用することで、よりわかりやすいメッセージをLINEを通じてユーザーへ届けることができると思います。
メッセージの送受信だけなら簡単にできるので、200通まで無料ですのでいろいろ試してみるのは楽しいかもしれませんね。
引き続き、AWSおよびLINEについての技術吸収、発信ができればと思います。よろしくお願いします〜！