オウム返しするLineBotを作ってみた
ネットサーフィンをした中で最も新しそうだった以下の記事を参考にオウム返ししてみます
502 BadGatewayを吐いて、WebhookのVerify(検証)に失敗したので、
CloudWatchでLambdaのログを見てみた
エラー内容を見たところ、どうやらLambdaをアップロードしたzipファイルのディレクトリ構成がよくない気がしたので、.zip直下にlambda_function.pyが来るように見直した
.zip直下にlambda_function.pyが来るようにすること
Lambdaを再アップロードしたら、解決!
Lambdaレイヤーを使ってみた
このあたりの記事を参考にさせていただいた
https://qiita.com/DASHi/items/b03d3efd401260d8add4
環境
Windows11
手順
以下、手順(備忘録)
ライブラリをインストールしてZIP化
レイヤーにしたいライブラリ(今回だとline-bot-sdk)を再度インストールしてZIP化
ライブラリは /python フォルダ内に配置する!
(少しハマッた)
mkdir python
cd python
pip install line-bot-sdk -t .
cd ../
Compress-Archive -Path python -DestinationPath .\line-bot-sdk.zip
レイヤーをアップロード
作ったZipをレイヤーとしてアップロードする
レイヤーをLambda関数に追加
LineBotApiのpush_message()
push_message()の使用例
from linebot import (LineBotApi)
from linebot.models import (TextSendMessage, ImageSendMessage)
line_bot_api = LineBotApi(channel_access_token)
// テキストの送信
line_bot_api.push_message(USER_ID, TextSendMessage(text='hoge'))
line_bot_api.push_message(USER_ID, ImageSendMessage(
original_content_url=presigned_url, preview_image_url=presigned_url))
USER_IDは、チャネルに割り当てられているユーザーIDをコピーして格納すること。
APIについて
APIの詳細は、下記のline-bot-sdk-python
MessagingAPIの説明のページを参照
S3署名付きURLをコマンドラインで発行してLineで送信
S3署名付きURLを発行
Pythonでこう書くみたいだからLambdaに書いてみた
# S3署名付きURLの発行
presigned_url = s3.generate_presigned_url(
ClientMethod = 'get_object',
Params = {'Bucket' : BUCKET_NAME, 'Key' : object_name},
ExpiresIn = 60,
HttpMethod = 'GET'
)
以下の記事を参考にしました。
エラー
Lambdaで実行して、署名付きURLを発行してもなんか画像見れない・・・
S3オブジェクトの読み取り権限のあるIAMロールを作成、Lambdaに付与
調べてると、上のエラーの原因は、LambdaにS3の読み取り権限がないことらしい。
以下のようなS3の読み取り権限を付けるIAMポリシーを作成。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::parroting/*"
]
}
]
}
CloudWatchに対する権限を付与するIAMポリシーも作成しておく。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:ap-northeast-1:081507884114:*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:ap-northeast-1:081507884114:log-group:/aws/lambda/parroting-line-bot-function:*"
]
}
]
}
作成した2つのIAMポリシーを割り当てたIAMロールを作成。
LambdaにIAMロールを付与。
以下の記事を参考にしました
SSMからLineのパラメータを取得してLambdaのPythonで使用
以下の記事を基本的に参考にした
SSMでパラメータの作成
記事で紹介されているのと異なる点:
SecureString型で暗号化したかったので、そのように設定
作成時にはデフォルトのKMSキーを指定した。
以下のように作成完了した
IAMポリシーの作成
LambdaからSSMのパラメータを取得するためのポリシーを作成。
作成後Lambdaの関数ロールにアタッチ
記事と異なる点:
"kms:Decrypt"を追加
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ssm:GetParameters",
"kms:Decrypt"
],
"Resource": "*"
}
]
}
完成!
こんな感じで返ってくる!
構成図
LambdaのPythonコード
仕事じゃないので汚いコードでも動けばよいのである。
import os
import sys
import logging
import boto3
from botocore.client import Config
from linebot import (LineBotApi, WebhookHandler)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage, ImageSendMessage)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# SSMから複数のパラメータを取得し辞書形式で返す
def get_ssm_params(*keys, region='ap-northeast-1'):
result = {}
ssm = boto3.client('ssm', region)
response = ssm.get_parameters(
Names=keys,
WithDecryption=True,
)
for p in response['Parameters']:
result[p['Name']] = p['Value']
return result
# 複数のパラメータをSSM取得
ssm_params = get_ssm_params('LINE_CHANNEL_SECRET', 'LINE_CHANNEL_ACCESS_TOKEN', 'LINE_BOT_USER_ID')
#LINEBOTと接続するための記述
#LINEBotのチャンネルアクセストークンとシークレットを読み込む
channel_secret = ssm_params['LINE_CHANNEL_SECRET']
channel_access_token = ssm_params['LINE_CHANNEL_ACCESS_TOKEN']
#LineBotのUser_idを読み込む
line_bot_user_id = ssm_params['LINE_BOT_USER_ID']
#無いならエラー
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)
#オウムの画像を格納しているバケット名
BUCKET_NAME = 'parroting'
#S3クライアントの生成
s3 = boto3.client('s3', region_name='ap-northeast-1', config=Config(signature_version='s3v4'))
#Lambdaのメインの動作
def lambda_handler(event, context):
print("Event:", event)
# 認証用のx-line-signatureヘッダー
if "headers" in event and "x-line-signature" in event["headers"]:
signature = event["headers"]["x-line-signature"]
else:
# エラーレスポンスを返す
return {
"isBase64Encoded": False,
"statusCode": 400,
"headers": {},
"body": "Missing x-line-signature header"
}
body = event["body"]
# リターン値の設定
ok_json = {
"isBase64Encoded": False,
"statusCode": 200,
"headers": {},
"body": ""
}
error_json = {
"isBase64Encoded": False,
"statusCode": 500,
"headers": {},
"body": "Error"
}
#メッセージを受け取る・受け取ったら受け取ったテキストを返信する
@handler.add(MessageEvent, message=TextMessage)
def message(line_event):
print("Line_Event:",line_event)
# タイムスタンプ
timestamp: int = line_event.timestamp
# メッセージID
id = line_event.message.id
# タイムスタンプとメッセージIDを見て送信する写真と送信メッセージを決定
rem19_time = timestamp % 19
# 受信したメッセージテキストを送信メッセージにそのまま採用(オウム返し)
reply_text = line_event.message.text
# 送信する画像の名前を決定
object_name = str(rem19_time) + ".jpg"
preview_object_name = str(rem19_time) + "_preview.jpg"
# メッセージのリプライを実行
line_bot_api.reply_message(line_event.reply_token, TextSendMessage(text=reply_text))
# S3署名付きURLの発行
presigned_url = s3.generate_presigned_url(
ClientMethod = 'get_object',
Params = {'Bucket' : BUCKET_NAME, 'Key' : object_name},
ExpiresIn = 60,
HttpMethod = 'GET'
)
# S3署名付きURLの発行(プレビュー用)
preview_presigned_url = s3.generate_presigned_url(
ClientMethod = 'get_object',
Params = {'Bucket' : BUCKET_NAME, 'Key' : preview_object_name},
ExpiresIn = 60,
HttpMethod = 'GET'
)
# S3署名付きURLで写真のリンクを送る
line_bot_api.push_message(line_bot_user_id, ImageSendMessage(
original_content_url=presigned_url,
preview_image_url=preview_presigned_url))
#例外処理としての動作
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