LINE のトーク上に送信した画像を S3 にアップロードする

記事タイトルとURLをコピーする

こんにちは。アプリケーションサービス部 河野です。

またまた LINE 関連のテーマで恐縮ですが、今回は、LINEのトークで送信した画像を S3 にアップロードする機能を実装してみます。

今回作成するもの

LINEから画像を送信すると、S3フォルダに画像をアップロードします。

f:id:swx-go-kawano:20210522152424p:plain

f:id:swx-go-kawano:20210522152644p:plain

準備

チャンネル作成

実行環境

LINE Bot を実装するためには、Webhook の送信先に設定するHTTPSエンドポイントを用意する必要があります。
今回は、API Gateway <-> Lambda <-> SQS <-> Lambda の構成で、Serverless Framework を使用してデプロイします。
上記構成の詳細については、【入門編①】Serverless Framework で 「おうむ返し」LINE Bot を作る - サーバーワークスエンジニアブログ をご参照ください。

テンプレートは以下のリポジトリに公開していますので、Clone してご使用ください。

github.com

実装

先ほど Clone した リポジトリの bot_execute.py に以下内容を追記します。

import boto3

@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    logger.info('image event')

    image = line_bot_api.get_message_content(event.message.id)

    with tempfile.TemporaryFile() as tmp:
        for chunk in image.iter_content():
            tmp.write(chunk)
        tmp.seek(0)

        s3 = boto3.resource('s3')
        bucket = s3.Bucket('line-s3-upload-sample')
        try:
            bucket.put_object(
                Body=tmp,
                Key=f'{event.message.id}.png'
            )
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text='アップロードに成功しました')
                )
        except Exception as e:
            logger.error(e)
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text='アップロードに失敗しました')
            )

追記する内容としては以上なのですが、各処理について説明していきます。

画像送信のイベントを取得

@handler.add(MessageEvent, message=ImageMessage)
    def handle_image(event):

上記のデコレーターを実装することで、ユーザーが画像を送信した際に処理を実行する関数を定義することができます。
デコレータの引数をそれぞれ、MessageEvent(ユーザーがLINEにメッセージを送信した際の発生するイベント)、ImageMessage(ユーザー送信したメッセージが画像の場合) に設定しています。

このように、line-bot-sdk-python を使用すると、ユーザーが行ったアクションに対して処理を実行する関数を簡単に定義できます。
画像送信イベント以外にも、友達登録時、スタンプ送信時、動画、音声、位置情報の送信など様々なイベントが用意されています。

画像を取得

image = line_bot_api.get_message_content(event.message.id)

画像取得は、メッセージIDを指定して、get_message_contentを実行するだけです。簡単ですね。
実際には、Content クラスを取得するので、クラスに実装されている content() を使用すれば、バイナリデータを取得することができます。
画像サイズが大きい場合は、以下のようにiter_content()を使用して、バイナリデータを小分けにして書き込むことができます。 今回は、S3に画像ファイルとしてしてアップロードするため、テンプファイルに書き込みます。

with tempfile.TemporaryFile() as tmp:
    for chunk in image.iter_content():
        tmp.write(chunk)
    tmp.seek(0)

s3 にアップロード

s3 = boto3.resource('s3')
        bucket = s3.Bucket('line-s3-upload-sample')
        try:
            bucket.put_object(
                Body=tmp,
                Key=f'{event.message.id}.png'
            )

先ほど書き込んだ画像ファイルを S3 にアップロードします。 今回は、事前に用意したline-s3-upload-sample バケットにアップロードします。 ファイル名は、一意になるようにメッセージIDをつけています。

結果メッセージを送信

line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text='アップロードに成功しました')
        )
except Exception as e:
    logger.error(e)
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text='アップロードに失敗しました')
    )

アップロードに成功した場合と、失敗した場合でそれぞれメッセージを送信しています。 メッセージの送信は、reply_message()に、リプライトークンと送信したいメッセージ内容を指定するだけです。

serverless framework 修正

Clone したままのテンプレートでは、Lambda に s3 の操作権限を付与していないため、serverless.ymlexecutePolicyに以下内容を追記します。

- Effect: Allow
    Action:
    - s3:PutObject
    Resource:
    - "*"

AWS環境にデプロイ

serverless.yml の環境変数にチャンネル作成時に取得した値をそれぞれ設定します

LINE_CHANNEL_SECRET: XXXXXXXXXX
LINE_CHANNEL_ACCESS_TOKEN: XXXXXXXXXX

以下コマンドを実行します。

npm install 
npx serverless deploy

デプロイ後に、HTTPSのエンドポイントが表示されるので、作成したチャンネルの Webhook に設定すれば、 s3 にアップロードする bot の完成です。

さいごに

line-bot-sdk-python のおかげで、想像以上に簡単に実装することができました。 LIFF を使って画像を送信することもできますが、フロントエンドで実装すると複雑になりがちなので、 画像の扱いはバックエンドに寄せて、トーク上で実現するのも選択肢の一つとして持ってると良さそうです。
また s3event を使用すれば、画像のアップロードをトリガーに Lambda を実行することもできるので、さらに連携の幅が広がりそうです。

今後もいろいろ試して行きたいと思います。

swx-go-kawano (執筆記事の一覧)