AWS Lambdaを利用したLINEbotハンズオン〜S3に写真保存〜

# はじめに
今回はLINE　APIを利用した「S3に写真保存」botの作成をしていきます。

動きとしてはLINEのMessaging APIを利用して写真を送信します、写真をきっかけとしてAWS　API GatewayがLambdaを起動させて、Lambdaに記述されているコードの動作（S3に写真を保存して保存された場合返信する）を行います

## 制作意図
9月に結婚式を挙げた友人用に考えた構築のハンズオンです（一部異なる点ありますが、ほぼほぼ）。
結婚式当日に参加した人がQRコードからLINEのお友達登録をしてもらうことで、当日撮影した写真を枚数・保存期間を気にせずS3に投げ込み、別途構築した2人のオリジナルHPから投げ込んだ写真を全員が閲覧できることを考えました。ちなみに後日でも、LINEから写真を投げるだけでOK。
実際の構築では動画・メッセージも送れるようにしていましたが、今回は写真だけに絞ってハンズオンしていきます。

# 構成図
![スクリーンショット 2021-11-19 17.24.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/ba96b9bf-49ce-ad15-b578-e586385e3c05.png)

# 実装
## 1.[LINE Developers](https://developers.line.biz/ja/)でプロバイダーの登録(省略)

### 1.1 前回記事『[AWS Lambdaを利用したLINEbotハンズオン〜鸚鵡返し〜](https://qiita.com/i3no29/items/6329f0e114dad1b6a525#1line-developers%E3%81%A7%E3%83%97%E3%83%AD%E3%83%90%E3%82%A4%E3%83%80%E3%83%BC%E3%81%AE%E7%99%BB%E9%8C%B2)』における『1.[LINE Developers](https://developers.line.biz/ja/)でプロバイダーの登録』部分を参照ください

[AWS Lambdaを利用したLINEbotハンズオン〜鸚鵡返し〜](https://qiita.com/i3no29/items/6329f0e114dad1b6a525#1line-developers%E3%81%A7%E3%83%97%E3%83%AD%E3%83%90%E3%82%A4%E3%83%80%E3%83%BC%E3%81%AE%E7%99%BB%E9%8C%B2)

### 1.2 作成完了画面
![スクリーンショット 2021-11-17 7.59.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/857d8775-f54e-54b4-dfe1-d5f0d6811b52.png)


## 2.作業ディレクトリ作成（ローカル環境）
[LINE公式ドキュメント](https://developers.line.biz/ja/docs/downloads/)より、LINE APIのSDK（ソフトウェア開発キット）のPythonをダウンロードする
※注意書きにもあるようにダウンロードは必須ではありませんが、構築が容易になるので、このQiita記事ではダウンロードしています

![スクリーンショット 2021-11-06 8.45.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/c9166620-e47c-ac80-5d25-f6717fcf0ebf.png)

### 2.1 作業ディレクトリ作成とSDKインストール

```
tetutetu214@mbp projects % mkdir 20211117_S3_SavePhotos
tetutetu214@mbp projects % cd 20211117_S3_SavePhotos 
tetutetu214@mbp 20211117_S3_SavePhotos % python -m pip install line-bot-sdk -t .

```
### 2.2 [boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)をインストール
AWS S3のファイルを操作するために『boto3』をインストールをしていきます

```
tetutetu214@mbp 20211117_S3_SavePhotos % pip install boto3
```

### 2.3 「lambda_function.py」ファイルを作成する
フォルダのなかに下記ファイル「lambda_function.py」を作成する
※このファイル名以外だと読み込まれないので注意する

![スクリーンショット 2021-11-18 7.54.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/c5f20c26-4df6-4dda-60c0-bede65074bc9.png)

```lambda_function.py
import os
import sys
import logging

#boto3を利用できるようにimport
import boto3

from linebot import (LineBotApi, WebhookHandler)
from linebot.models import (MessageEvent, ImageMessage,TextSendMessage)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)

logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#1.LINEBOTと繋げるための記述
# 環境変数からline botのチャンネルアクセストークンとシークレットを読み込む
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)

#無いならエラー
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

# apiとhandlerの生成（チャンネルアクセストークンとシークレットを渡す）
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)


######################################################
# boto3を利用してS3連携
# バケット名は各自命名したものを利用する
s3 = boto3.client("s3")
bucket = "XXXXXXXXXXXX"
######################################################


#2.イベントからLINEBOT署名とボディ内容を受け取る
# Lambdaのメインファンクション
def lambda_handler(event, context):

    # 検証用のx-line-signatureヘッダー
    signature = event["headers"]["x-line-signature"]
    body = event["body"]

    # リターン値の設定
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}

    error_json = {"isBase64Encoded": False,
                  "statusCode": 403,
                  "headers": {},
                  "body": "Error"}

# 画像保存(messageがImageMessageの場合の挙動)
    @handler.add(MessageEvent, message=ImageMessage)
    def message(line_event):

        # メッセージIDを抽出
        message_id = line_event.message.id

        # 画像ファイルを抽出(画像が大きい場合分割する)
        message_content = line_bot_api.get_message_content(message_id)
        content = bytes()
        for chunk in message_content.iter_content():
            content += chunk

        # 保存する画像ファイル名
        key = "2021/" + message_id + '.jpg'
        new_key = message_id[-3:]
        s3.put_object(Bucket=bucket, Key=key, Body=content)

        # 画像保存するとメッセージの返信
        line_bot_api.reply_message(line_event.reply_token,TextSendMessage(text='写真の保存に成功!'))

#例外処理としての動作
    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json
```
### 2.4 ファイルをzip化する
上述したファイルも含めて圧縮していく
![スクリーンショット 2021-11-18 8.07.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/e8a3b928-cc32-3e2f-d3f9-3a09dbc4ab20.png)

zipの名前は自分で管理しやすいようにネーミングする

## 3.S3の設定（AWSコンソール）
### 3.1 S3バケットの作成
1　S3コンソール画面よりバケットの作成を押下する
![スクリーンショット 2021-11-19 8.13.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/d0344660-2f6b-66fc-2682-bd74169eeffe.png)
2 設定をしていく（今回はバケット名のみ記述してデフォルト）
![スクリーンショット 2021-11-19 8.14.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/879fc6f2-a541-6304-25a9-a72bf81dca50.png)
3 バケットが作成されたのを確認する
![スクリーンショット 2021-11-19 8.14.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/148d2e74-ab30-866b-3035-985517786e6a.png)

※前後していますが『2.3 「lambda_function.py」ファイルを作成する』ファイル内のバケット名は、こちらの手順で作成したバケットのことです。

## 4.Lambdaの設定（AWSコンソール）
1.前回記事『[AWS Lambdaを利用したLINEbotハンズオン〜鸚鵡返し〜](https://qiita.com/i3no29/items/6329f0e114dad1b6a525#3lambda%E3%81%AE%E8%A8%AD%E5%AE%9Aaws%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB)における『3.Lambdaの設定（AWSコンソール）の登録』』
部分を参照ください

※LINEとの環境変数まで設定を終えている状態

![スクリーンショット 2021-11-19 8.42.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/6f8ec71d-26e7-c5f0-e45b-3533bd5130eb.png)

##　5.AWS API Gatewayを設定する
LINEからの[Webhook](https://qiita.com/soarflat/items/ed970f6dc59b2ab76169)（イベント発生時、指定したURLにPOSTリクエストで写真データを送信する仕組み）を受け取るためにAPI Gatewayを設定する

### 5.1 トリガーを作成する
1.Lambda画面から__トリガーを追加__を押下する
![スクリーンショット 2021-11-19 8.42.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/38cda222-34c7-3b87-af53-f747c325c917.png)

2.__トリガーの設定__で__「API Gateway」__を選択する
APIタイプは__REST API__で、セキュリティは__オープン__を選択する

![スクリーンショット 2021-11-19 9.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/112b3089-5c1d-9dd3-7ba3-8d56ee152e61.png)

※__「HTTP API」__の方で構築したかったのですが、設定の方法が分からなかったため__「REST API」__を選択しています

### 5.2 構築が完了したことを確認する
![スクリーンショット 2021-11-19 9.28.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/4350500d-4483-1052-cece-69d9aec98886.png)

### 5.3 API Gatewayの設定
1.API　Gatewayの名前を押下して画面を遷移する
![スクリーンショット 2021-11-19 9.28.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/179bdfce-6d37-3a88-d240-3098d8859f62.png)

2.『アクション』を押下して『メソッドの作成』を選択する
![スクリーンショット 2021-11-19 9.51.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/c5fd2d23-ba29-5b9f-7828-73046e851c81.png)

3.『アクション』から『POST』を選択する
![スクリーンショット 2021-11-19 10.00.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/cda73c11-993e-f8ba-ec0f-e36f1109b7fb.png)

4.セットアップ
4-1.__Lambdaプロキシにチェック__(API Gateway側でパラメータをよしなにしてくれる程度の知識)
4-2.__Lambda関数__ですが遷移直後は、同リージョンにLambda関数がありませんとコメントが出たので、関数のARNから値をいれたら関数を選ぶことができるようになりました

![スクリーンショット 2021-11-19 10.16.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/a15e995c-4813-89a0-8f43-af0ca546c3c5.png)

5.既存で作成されていた『ANY』を削除
![スクリーンショット 2021-11-19 10.26.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/0296962c-c5ab-2415-6fde-af7b5660d6f7.png)

### 5.4 API Gateway　メソッドリクエストの設定
1.『メソッドリクエスト』を押下する
![スクリーンショット 2021-11-19 10.47.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/ce39b03e-146c-6ce6-f54e-56bb615f1863.png)

2.『メソッドリクエスト』の設定をしていく
2-1.[Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/)に従って、リクエストの検証をプルダウンから選択して保存する
2-2.『HTTPリクエストヘッダー』に署名の文言を入力

![スクリーンショット 2021-11-19 11.19.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/398de10c-962d-c90d-ac17-4c8333989cc2.png)

※保存等のボタンはなく左上『メソッドの実行』から戻ります

3.『メソッドリクエスト』の設定が完了したことを確認する
![スクリーンショット 2021-11-19 11.28.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/0296755c-d1de-3034-c42a-dfba82809900.png)

### 5.5 API　のデプロイをする
1.『アクション』から『APIのデプロイ』を選択する
![スクリーンショット 2021-11-19 11.38.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/d829b39f-0999-a410-47a0-e70a599d0e9a.png)

2.デプロイの『ステージ』を選択して『デプロイ』を押下する
![スクリーンショット 2021-11-19 11.39.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/590570d4-2fd2-45ca-348b-0eb5e765c3e1.png)

3.自動的に『ステージ』へ遷移して、デプロイしていることが確認できる
![スクリーンショット 2021-11-19 11.42.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/52884fc6-629a-a4a3-bb73-1656bc53b86b.png)

## 6.Lambdaの詳細設定（AWSコンソール）
### 6.1 API Gateway作成中に作られたAnyのAPI Gateway削除

![スクリーンショット 2021-11-19 11.51.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/5d615232-52ce-37af-656a-d28fd2988556.png)

※きっと良い方法があるはずですが解決しなかったので、作ってから削除しました

### 6.2 APIからリソースを操作するためのロール作成
1.『設定』の『アクセス制限』から『ロール名』を押下してIAMへ遷移
![スクリーンショット 2021-11-19 15.01.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/a6b56e78-f8af-f8f8-b5b7-0ea99d7b100d.png)

2.画面中央辺りの『ポリシーをアタッチします』を押下
![スクリーンショット 2021-11-19 15.01.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/385a3474-2187-6960-10a7-37d3b3214019.png)

3.API　GatewayからAPI操作を呼び出すための制御に関しての権限をLambdaに付与する
![スクリーンショット 2021-11-19 15.26.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/60ab2e7c-4709-be68-d92a-644acf443ccd.png)

4.LambdaからS3にオブジェクトを保存するための権限をLambdaに付与する
![スクリーンショット 2021-11-19 16.18.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/48be241b-7c57-090d-a184-960d42ec3cf7.png)


5.ロールにポリシーがアタッチされていることを確認する
![スクリーンショット 2021-11-19 16.21.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/6519c193-999d-e8b8-f423-4fd72bee8a66.png)


6.Lambdaの画面でもロールの確認をすることができる
![スクリーンショット 2021-11-19 16.22.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/fbfc642e-5b9f-c7f2-4cec-5363490fe5ae.png)


## 7.LINE DevelopersにAWSの設定を登録する
### 7.1 作成されたAPIエンドポイントへのURLを[LINE Developers](https://developers.line.biz/ja/)に登録をする

![スクリーンショット 2021-11-19 15.38.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/89150038-7324-3fd5-88bf-ea73a831ccd1.png)


### 7.2　WebhookにURLをCOPYする
LINE Developersの「Messaging API設定」の「Webhook設定」にAPI Gatewayの「APIエンドポイント」のURLをCOPYする
COPYして__更新__後に表示される、__Webhookの利用__をON（緑色）にする
![スクリーンショット 2021-11-06 16.02.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/63086923-c4c1-5231-89e6-90c0665acb71.png)


### 7.3「あいさつメッセージ」の「編集」を押下して、詳細設定の__
__「応答メッセージ」__を__「オフ」__にして、__「Webhook」__を__「オン」__にする

![スクリーンショット 2021-11-06 16.02.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/395e83f9-95a6-baa6-6d94-d6c36612bea5.png)


## 8 挙動の確認
### 8.1　QRコードから友達登録の遷移
QRコードを読み取ると、お友達登録の画面へ遷移して登録することができる
![スクリーンショット 2021-11-19 16.32.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/38be5ee9-06ab-eea3-2649-339248ea1d61.png)

### 8.2　LINE画面挙動の確認
LINE画面でメッセージを送信して、写真の保存が成功すると『写真の保存に成功！』というメッセージが返される
![スクリーンショット 2021-11-19 16.41.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/dd98bb72-a4c6-c68d-f244-ffe053b936c1.png)

### 8.3　S3に画面が保管されているかの確認
作成しているバケットに写真が保存されていることがタイムスタンプからも確認できます
![スクリーンショット 2021-11-19 16.44.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/1cbb9d58-0999-de70-cb8b-db89d2a5db20.png)

### 8.4　CloudWatchロググループでも確認する
1.IAM権限の際に触れませんでしたが、LambdaにCloudWatchの権限も付与されているので、CloudWatchでログの確認をすることができます
![スクリーンショット 2021-11-19 16.48.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/5d7c8c82-acf9-3800-7370-5d6d91e9fe28.png)

2.ログイベントで2度LINE APIを利用して送付して保存されていることが確認できます
![スクリーンショット 2021-11-19 16.53.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/ee2c73b2-cb14-a1cd-f31f-707a4004d7d5.png)

## 9.CloudWatchロググループでエラーの挙動を確認する
1.こちらは作成中に表示されたエラーです、『PutObject』などから権限がないのかなと考えIAM周辺を確認するきっかけとなりました

![スクリーンショット 2021-11-19 16.56.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/694365/299da2e6-45ad-31ac-11ff-1d5f3848507c.png)


## 最後に
長丁場になりましたが、なんとかLINEから写真を送信してS3へ画像を保存することは出来ました。
『まだまだ分からない、なんとなくやってしまっている』部分も多々ありますが、作っていかなければ理解も出来ないかと思って構築しています。「ベストプラクティは「こうじゃ！」」「ちょっ、セキュアじゃねぇよ！」などありお時間があれば、なんとも厚顔無恥なお願いですがご指摘お願いします。

## 参照
Qiita記事
[Boto3 で S3 のオブジェクトを操作する（高レベルAPIと低レベルAPI）](https://qiita.com/sokutou-metsu/items/5ba7531117224ee5e8af)
[API Gateway + Lambda プロキシ結合の使用有無による違い](https://qiita.com/yuuwatanabe/items/a3bd65e709f20574b6db)
[白いマスクから怪人マスクへ、AWSでサーバレスLINE写真処理アプリの開発記](https://qiita.com/y2-peng/items/0eee87423176137ce466)

ドキュメント
[Boto3 ドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)