AWSで自分用のLINE Botを作った①

# はじめに
傘を持たずに出掛けて、午後から雨が降ってしまって困ることありますよね。
天気予報を朝見れば良いのでしょうが、少し面倒です。

というわけで、AWSの無料枠を使って自分への通知用のLINE Botを作ろうと思いました。
朝、天気予報サイトのデータを自動でスクレイピングして、予報が雨なら「今日は傘が必要です」というようなメッセージが配信されるようなイメージです。他にも電車の遅延状況の通知もできたら便利そうです。（既にこのようなサービスはありそうですが。。）

## 今回の内容
AWSもLine APIもそこまで触ったことがないので、まずは簡単な部分から実装を目指します。
今回の記事では図のようなAWS環境を構築しました。
2つあるLambdaの機能はそれぞれ以下のとおりです。使用言語はPythonです。
- 返信用（reply_message関数）：単純なオウム返しの機能
- 配信用（push_message関数）：定型文の配信

![system_conf-aws.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2701379/e4a14abe-dce5-7e6c-2cb3-72e4f012746c.png)

# 実装編
LINE Botを作るには、まずLINE Developersコンソールにログインして、チャネルを作る必要があります。チャネルの作り方は以下の公式ドキュメントに書かれています。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

今回はMessaging APIを利用する方式にしています。そうするとWebhook URL（LINEプラットフォームからボットにイベントを送信する際の送信先URL）を設定できるようになります。
このWebhook URLに、reply_message関数呼び出し用のAPI GatewayのURLを指定することで、登録ユーザーのチャットに返信できるようになります。

## AWS Lambdaの実装
### Python SDK
LINE Botの作成用には、以下のようなMessaging APIのSDKがあります。

https://developers.line.biz/ja/docs/messaging-api/line-bot-sdk

今回はPythonを使用するため、こちらの公式Python SDKを利用します。

https://github.com/line/line-bot-sdk-python

Lambdaで外部パッケージを利用する方法はいくつかあるようですが、今回はLambdaレイヤーを利用する方法を採用しました。レイヤーにzip化したパッケージをアップロードすることで、複数のLambdaで共通してパッケージを利用できるようになります。
Lambdaはレイヤーのpythonディレクトリにあるパッケージを読み込みます。そのため、以下のようなコマンドでzipファイルを作成し、用意したレイヤーにアップロードします。

```sh
#pythonディレクトリにパッケージをインストール
$ pip install line-bot-sdk -t ./python

#pythonディレクトリごとzip化（ファイル名は何でも良い）
$ zip -r extra_package.zip ./python/
```


#### （補足）開発環境
- Ubuntu 20.04.4 LTS
- Python 3.10.4
- line-bot-sdkパッケージ 2.3.0
- Lambdaランタイム python3.9

### reply_message関数
まずは単純なオウム返しの機能を持つreply_message関数を実装します。
事前の準備として、上で用意したレイヤーを参照するようにしておきます。また、以下の環境変数を追加します。どちらもLINE Developersコンソールから値を取得できます。
- CHANNEL_ACCESS_TOKEN：チャネルのアクセストークン（今回はlong-lived版を使用）
- CHANNEL_SECRET：チャネルのシークレット


reply_message関数に登録したソースコードは以下のようになります。テキストメッセージを受信したら、同じテキストを返信する処理を実装しています。


```lambda_function.py
import os

from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

# LINE BotのAPI
line_bot_api = LineBotApi(os.environ['CHANNEL_ACCESS_TOKEN'])
# Webhook（メッセージ等のイベント）のハンドラー
handler = WebhookHandler(os.environ['CHANNEL_SECRET'])


def lambda_handler(event, context):
    # LINE Platformから送信されるリクエストの署名とボデイ
    signature = event['headers']['x-line-signature']
    body = event['body']

    # ハンドラーにより署名を検証してWebhookを処理する
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        print("Invalid signature. Please check your channel access token/channel secret.")
        return {
            'statusCode': 400
        }

    return {
        'statusCode': 200,
    }


# ハンドラーにテキスト型メッセージを受信した場合の処理を追加
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    # 受信したメッセージに対して同じテキストを返信
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))
```
#### （補足）Webhookについて
Webhookというのは、LINE Platformで発生したイベントを他システムに送信する仕組みのことです。今回は、Webhook URLとして指定したreply_message関数（API Gateway経由）にイベントが送信されます。イベントにはLINE Botのフォローや、メッセージ送信などがあります。使用したLINE Bot SDKでは、Webhookハンドラーに対して、デコレータを使ってWebhookイベントに対する処理を実装していきます。

### push_message関数
次に定型文を配信する機能を持つpush_message関数を実装します。
reply_message関数と同様に、事前の準備としてレイヤーの参照と、環境変数の登録を行います。

push_message関数に登録したソースコードは以下のようになります。このLambda関数が実行されると"Hello World"というメッセージが自分に対して送信されます。ちなみに、自分のLINEユーザIDはreply_message関数の実行ログなどから確認できます（print関数などでbodyを出力しておくとCloudWatchログから見えます）。

```lambda_function.py
import os

from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import LineBotApiError
from linebot.models import TextSendMessage

# LINE BotのAPI
line_bot_api = LineBotApi(os.environ['CHANNEL_ACCESS_TOKEN'])


def lambda_handler(event, context):
    # 送信先のLINEユーザーID
    user_id = {自分のLINEユーザID}
    try:
        # メッセージの送信
        line_bot_api.push_message(
            user_id, TextSendMessage(text='Hello World!'))
    except LineBotApiError as e:
        # error handle
        print("Failure push message.")
        return {
            'statusCode': 400
        }

    return {
        'statusCode': 200
    }
```

## 動作の確認
実際に作成したLINE Botを友達登録して動作確認してみました。
送信したテキストメッセージと同じメッセージが返信されています。また、push_message関数を起動すると定型メッセージが配信されています。正しく動いているようです（よかった！）。

<img width="400" alt="line bot動作確認" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2701379/3d9af3d6-3ab6-e534-999c-d44f52444e10.jpeg">

# おわりに
今回はAWS LambdaとPythonを使って簡単な機能のLINE Botを作ってみました。
今後はもともとの目的だった天気情報のスクレイピングや、DynamoDBを使ってユーザ情報の管理などを試してみたいです。

（続く）