おうむ返しをしてくれるLINE BotをAWS Lambdaを使用して作成する

## 概要
この記事では送信したメッセージをオウム返しするだけの単純なLINEbotの製作手順を解説する。

## 製作環境
・MacOS
・python3.10
・pip 23.3.2

## LINE Message APIを使うための環境を整える
まずは、LINE Developersに登録する。
その後、[公式ドキュメント](https://developers.line.biz/ja/docs/messaging-api/getting-started/)の記載に従って、チャネルを作成する。今回は、メッセージのやり取りをしたいので、Messaging APIチャネルを選択して、作成する。
作成が完了したら終了。ただ、後のことを考えて、"Messaging API設定"の一番下、"チャンネルアクセストークン"を押下し、メモ帳などにその値を控えておく。

## AWS の環境を整える+LINEと連携する
### Lambda関数の設定
AWSコンソールにログインして、Lambdaの画面を開き、関数->関数作成を押下する。
以下のような画面が出るので、関数名は適当に記入、ランタイムは自分のパソコンにあったバージョンのpythonを選択する。（python --version　とターミナルに打ち込めばバージョンがわかります。）
![スクリーンショット 2024-02-20 10.52.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/eb80b23d-56fe-8fb3-7238-7bfc495980f3.png)
### タイムアウト設定
デフォルトは5秒に設定されているが、API等 別のシステムとやり取りをする場合は、タイムアウトが起こり得るため、30秒程度まで伸ばした設定に変更する。
![スクリーンショット 2024-02-20 10.55.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/d992c3be-0af9-bc27-73e5-1cad223122a3.png)

### AWS Lambdaレイヤー作成
今回は、LINE BOT開発用のline-bot-sdkをインストールして、レイヤーとして設定することで、モジュールを使用できる。

まず、pipコマンドを使って必要モジュールファイルをダウンロードする。

以下コードでは、"python”フォルダを作成して、そのフォルダに必要モジュールファイルを格納、zip化する。ターミナルを開いて以下のコマンドで作成する。**この時zip化する前のフォルダの名前はpythonとする必要がある。他の名前ではエラーが出てしまう。**
```
mkdir python
$ cd /Users/miwatakuma/python
$ pip install line-bot-sdk -t ./
$ cd ../
$ zip -r python.zip python
```
zipファイルの構成は以下の通り。ここでディレクトリ構成を間違えるとうまくいかない可能性がある。
```
python.zip
　　-python
　　　　|-line-bot-sdk
```

その後は作成したzipファイルをレイヤに設定する。メニューバーから「レイヤー」をクリックし、オレンジ色の「レイヤーを作成」ボタンをクリックする。以下の写真のように先ほど作成したzipファイルをアップロードする。ランタイムは先ほどと同じpythonバージョンを選択する。
![スクリーンショット 2024-02-20 11.50.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/96f28cdb-aa73-e2c7-669a-a33b81c0dd47.png)
そして関数の画面に戻り、一番下までスクロールする。すると右端に「レイヤーの追加」という項目があるので、そこから先ほど作成したレイヤーを追加する。以下の写真のように設定。
![スクリーンショット 2024-02-20 11.53.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/b1d66a49-3e5c-0c8e-5cb6-373228db2da2.png)

### 環境変数の登録
環境変数に特定パラメータ(今回はAPIキー)を設定してプログラムで利用する。

キーは任意に入力し、値に先ほどメモしておいたAPIキーを入力する。
LINE Message APIのチャンネルアクセストークンはキー: LINE_CHANNEL_ACCESS_TOKENの値に入力して、今回は設定した。
![スクリーンショット 2024-02-20 11.56.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/0b3a94c0-edde-d0f9-3b40-ebd0a066abfd.png)

### API Gatewayの作成/設定
Lambdaの関数画面で、以下画像では左下に"API Gateway"という文字とアイコンが存在するが、初期は存在しない。
"+トリガーを追加"個所をクリックして、ウィンドウを開く。
![スクリーンショット 2024-02-20 11.58.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/d1783615-67cc-3599-0b80-df98faebc246.png)

ウィンドウが開いたのち、"API Gateway"を選択すると以下画像のような設定画面が現れる。
![スクリーンショット 2024-02-18 11.22.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/7808fd22-f508-2072-a99b-79b837bf054c.png)

Rest APIを選択し、他は写真のように設定する。
上記の通り作成すると、先程の関数ウィンドウのトリガー欄に、API Gatewayが作成される。

以下の写真の下部にあるline-bot-APIと書かれたリンクをクリックする。するとAPI Gatewayの設定画面に移る。

![スクリーンショット 2024-02-18 11.17.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/3df218f7-da6f-c8b2-8be6-3b5dc58cd8c9.png)

以下画像では、"Resources"欄に"/POST"という1つしか存在しないが、当初は"ANY"が存在している。

"ANY"メソッドと関数名がはいったリソースは削除して"POST"メソッドを作成する。そうすると写真のようにPOSTメソッドだけが残る状態になる。POSTメソッドは、HTTP POSTリクエストをサポートしており、HTTP POSTとは、クライアントからの入力内容をWebサーバに送信するために使用するメソッドのこと。
![スクリーンショット 2024-02-18 11.56.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/61765fe7-97f7-573b-f499-47fc6495e0f9.png)

POSTメソッド作成時の設定は以下の通り、"Lambda Function"の入力を求められるが、そこにはLambda 関数名を入力する。そして重要なのが**Lambdaプロキシ統合**をオンにすることである。これはAPI GatewayとLambdaを接続する際の決まりごとのようなものなので、これをしないとエラーが発生する。

![スクリーンショット 2024-02-18 11.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/f1fa2543-4864-79aa-b22b-e447c7de58be.png)


### リクエストヘッダの設定
作ろうとしているシステムでは、AWS API GatewayへのHTTP POSTリクエストはLINEから来ることを想定しているが、LINEから来たかどうかの検証をする方法として、署名の検証方法が推奨されているようなので搭載してみる。

やり方はPOSTメソッドのメソッドリクエストのタグを選択、編集をクリックして、
HTTPリクエストヘッダの欄にx-line-signatureと入力し、他は以下の写真のようにしておく。
![スクリーンショット 2024-02-18 11.27.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/de629bdc-0c44-34bd-f2ed-d1d6e8d0fe05.png)

### APIのデプロイ
オレンジ色のボタンである「APIをデプロイ」をクリックする。以下の写真のように設定し、「デプロイ」ボタンをクリックする。（ステージ名は任意）
![スクリーンショット 2024-02-20 12.05.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/336135e2-329e-79a1-b94b-1fd2cf4b715a.png)


### webhookの設定
「ステージの詳細」にある「URLを呼び出す」という欄を探す。そのURLを写真のようにLINE DevelopersのWebhook settingに貼り付け、verify(検証)を押す。successと表示されば成功している。
![スクリーンショット 2024-02-18 11.40.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/8c7c9791-e866-c059-e32b-6a91ee29024a.png)

今回では、LINE上でメッセージを受けた際にそのメッセージと通知の連絡をAWS API Gatewayにリクエストする際に使用するためにこれを設定した。

## AWS Lambdaでコード実行(動作確認)

### AWS Lambdaとは
AWS Lambdaとはサーバーを意識することなく、コードを実行できるAWSのサーバーレスコンピューティングサービスのこと。そしてLambda関数とはAWS Lambdaによって管理、実行されるアプリケーションのコードとその設定のことである。

まず関数の画面にもどり、以下の写真の左側にあるlambda_function.pyをクリックする。そしてそこに以下のコードを貼り付け、**Deploy**ボタンを押す。問題なく実行できているか確認するため、testボタンを押し、適当な名前を入れて保存をおし、再度testボタンを押してエラーが出ないか確認する。（testボタンを押すといろいろな設定をできるようになっているが、ここでは名前を適当につけて保存するだけでいい）
![スクリーンショット 2024-02-20 12.09.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/56724e27-753c-740f-1d0b-eeab435fcfee.png)
貼り付けるコード
```
import json
import os
from linebot import LineBotApi
from linebot.models import TextSendMessage
from datetime import datetime

# 環境変数からLINE Botのチャネルアクセストークンを取得
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']
# チャネルアクセストークンを使用して、LINE BotのAPIインスタンスを作成
LINE_BOT_API = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

# ログ出力関数
def logging(errorLv, lambdaName, errorMsg):
    loggingDateStr = (datetime.now()).strftime('%Y/%m/%d %H:%M:%S')
    print(loggingDateStr + " " + lambdaName + " [" + errorLv + "] " + errorMsg)
    return

def lambda_handler(event, context):
    logging("info", context.function_name, "実行開始")

    try:
        body = json.loads(event.get("body", "{}"))  # event["body"]が空の場合、空のdictを返す
        events = body.get("events", [])  # eventsキーがない場合は空のリストを返す

        if events:  # eventsリストが空でないことを確認
            first_event = events[0]
            if first_event.get("type") == "message":
                message_type = first_event["message"].get("type")
                if message_type == "text":
                    replyToken = first_event['replyToken']  # リプライ用トークン
                    messageText = first_event["message"]["text"]  # 受信メッセージ
                    LINE_BOT_API.reply_message(replyToken, TextSendMessage(text=messageText))  # メッセージを返信
    except json.JSONDecodeError as e:
        logging("error", context.function_name, f"JSON decoding error: {str(e)}")
        return {'statusCode': 400, 'body': json.dumps('Invalid JSON format.')}
    except Exception as e:
        logging("error", context.function_name, f"Unexpected error: {str(e)}")
        return {'statusCode': 500, 'body': json.dumps('Internal server error.')}

    return {'statusCode': 200, 'body': json.dumps('Reply ended normally.')}

```
そして以下の写真のような内容がexecution resultに出力されていれば問題なく実装できている。
![スクリーンショット 2024-02-24 18.29.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/3b40e9da-4de4-4f53-277c-9d48adf03643.png)

あとはline developerのコンソールに戻り、自分のスマホのlineアプリの友達追加からQRコードの読み取り画面を開き、messaging API設定にあるQRコードを読み取ればいい。そして適当に文章を打ってみて以下の写真のように返信がくれば実装できている。
![スクリーンショット 2024-02-24 18.32.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/f5661ca2-1b79-f554-191d-f249a5f63051.png)

### 応答メッセージを無効にする
メッセージを送信するとおうむ返しの文章と共に定型文も送信されてしまう。これを削除したい場合、以下の写真の応答メッセージの「編集」ボタンから「無効」を選択すればいい。
![スクリーンショット 2024-02-25 10.55.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3685116/032d8801-5506-78f7-7747-af49c7bf42a1.png)

## 終わりに
今回はエラーの解決にかなりの時間を取られてしまった。具体的にはzipファイルがうまく作成できていなかったり、API Gatewayのプロキシ結合をオンにしていなかったことがエラーの原因だった。原因を発見するにはやみくもに操作をするのではなく、落ち着いて考えられるエラーの原因を書き出し、問題を分離していくことが大事だということを学んだ。
私のようなプログラミング初心者はエラーが出続けて嫌になることもあると思いますが、落ち着いて取り組むことを常に念頭において勉強していきましょう！