Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8825

息子の可愛さを普及するために、AWS + LINEでBotを作った話

$
0
0

2020年7月、我が家に長男が誕生。
もう天使。かわいい。CMのオファー来るんじゃないのか?(親バカ)

親族・友人に息子を会わせて、可愛さを自慢やりたかったが、このご時世それも叶わず。。。
我が息子の可愛さを普及したい。どうしよう。

そうだ。我が息子の可愛さを普及するLINE Botを作ろう。

1. デモ

最初にご紹介。(かわいい)
demo.gif

2. 全体構成

Design.png

2-1. LINEBotからWebHookでAPIGateway→Lambda実行
2-2. メッセージを解析して、返信用のメッセージと画像を取得
2-3. LINEに返信

とシンプルなもの。
GASとかで構築した方が安い。というのは秘密。

3. LINE①

まずはLINEBot MessagingAPIの設定。
下記サイトを参考に設定。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

Webhook URLはAPI構築後に設定。

4. AWS

Lambda + DynamoDBはCloudFormationで、APIGateway + S3は手動で構築。

4-1. CloudFormation

template.yml
AWSTemplateFormatVersion:2010-09-09Resources:HaruBotFunction:Type:AWS::Lambda::FunctionProperties:Code:./release/app.zipFunctionName:HaruBotFunctionHandler:index.handlerRuntime:nodejs12.xRole:Fn::GetAtt:- "HaruBotRole"- "Arn"MemorySize:128Timeout:30Environment:Variables:TZ:Asia/TokyoACCESSTOKEN:<LINE ACCESSTOKEN>CHANNELSECRET:<LINE CHANNELSECRET>ReplyMapping:Type:AWS::DynamoDB::TableProperties:TableName:ReplyMappingAttributeDefinitions:-AttributeName:typeAttributeType:SKeySchema:-AttributeName:typeKeyType:HASHProvisionedThroughput:ReadCapacityUnits:3WriteCapacityUnits:3HaruBotRole:Type:"AWS::IAM::Role"Properties:AssumeRolePolicyDocument:Version:"2012-10-17"Statement:-Effect:AllowPrincipal:Service:-"edgelambda.amazonaws.com"-"lambda.amazonaws.com"-"dynamodb.amazonaws.com"-"cloudwatch.amazonaws.com"Action:-sts:AssumeRole
  • ACCESSTOKEN, CHANNELSECRETはLINE Bot設定時に発行した値を設定。
  • ReadCapacityUnits, WriteCapacityUnitsは適当に。一応複数人に公開することを考慮して3。
  • Roleは最低限の権限のみ付与。

4-2. DynamoDB

4-2-1. テーブルデザイン

Key説明
typeStringPK
imgList画像URLのリスト、今回はS3上に静的WebホスティングしたURLを設定
msgList返信メッセージのリスト

4-2-2. サンプル

{"img":["https://hogehoge.com/oyasumi01.jpewg","https://hogehoge.com/oyasumi02.jpewg"],"msg":["おやすみなちゃい","Zzzz..."],"type":"おやすみ"}

4-3. Lambda

4-3-1. ソース全文

index.js
'use strict'constline=require('@line/bot-sdk')constclient=newline.Client({channelAccessToken:process.env.ACCESSTOKEN})constcrypto=require('crypto')constAWS=require('aws-sdk')constdynamodb=newAWS.DynamoDB.DocumentClient({region:'ap-northeast-1'})exports.handler=async(event)=>{returnnewPromise(async(resolve,reject)=>{constsignature=crypto.createHmac('sha256',process.env.CHANNELSECRET).update(event.body).digest('base64')constcheckHeader=(event.headers||{})['X-Line-Signature']if(signature!==checkHeader){reject('Authentication error')}constbody=JSON.parse(event.body)constevents=body.events[0]constmessage=events.message.textconstparam={TableName:'ReplyMapping',Key:{type:message}}constresult=awaitdynamodb.get(param).promise()letmsg,imgconsole.log(result.Item)if(result.Item){constgetRandomList=(list)=>list[Math.floor(Math.random()*list.length)]msg=getRandomList(result.Item.msg)img=getRandomList(result.Item.img)}else{msg=`${message}ってなんでちゅか?`img=''}constreplyText={type:'text',text:msg}constreplyImage={type:'image',originalContentUrl:img,previewImageUrl:img}client.replyMessage(events.replyToken,replyText).then((r)=>{returnclient.pushMessage(events.source.userId,replyImage)}).then((r)=>{resolve({statusCode:200,headers:{'X-Line-Status':'OK'},body:'{"result":"completed"}'})}).catch(reject)})}

4-3-2. 認証処理

constsignature=crypto.createHmac('sha256',process.env.CHANNELSECRET).update(event.body).digest('base64')constcheckHeader=(event.headers||{})['X-Line-Signature']if(signature!==checkHeader){reject('Authentication error')}

LINEからハッシュ化されている値と突合して、正しいリクエストかどうかハンドリング。

4-3-3. 返信用のメッセージ、画像を取得

constbody=JSON.parse(event.body)constevents=body.events[0]constmessage=events.message.textconstparam={TableName:'ReplyMapping',Key:{type:message}}constresult=awaitdynamodb.get(param).promise()letmsg,imgif(result.Item){constgetRandomList=(list)=>list[Math.floor(Math.random()*list.length)]msg=getRandomList(result.Item.msg)img=getRandomList(result.Item.img)}else{msg=`${message}ってなんでちゅか?`img=''}

送信されたメッセージを元に、DynamoDBからレコード取得。
※ ユーザーからの送信テキストををKeyに完全一致でレコードを取得しているため、良さげなワードから良さげに引っ張ってくることはできない。
例えば、「かわいい」のみレコードを登録していた場合、「かわいいね」とメッセージが送られてきても、良さげな返信はされない。
形態素解析して、良さげな言葉を引っ張ってきたかったが、時間が足りず。。(お宮参りに間に合わせたかったんだもん。)

レコードが存在する場合は、レコード内のimg, msgからランダムに値を取得。
存在しない場合はデフォルトを返却。

4-3-4. 返信

constreplyText={type:'text',text:msg}constreplyImage={type:'image',originalContentUrl:img,previewImageUrl:img}client.replyMessage(events.replyToken,replyText).then((r)=>{returnclient.pushMessage(events.source.userId,replyImage)}).then((r)=>{resolve({statusCode:200,headers:{'X-Line-Status':'OK'},body:'{"result":"completed"}'})}).catch(reject)

詳しいことはドキュメントを参考に。

4-4. APIGateway

RestAPIで作成。
公開するAPIではないので、特にカスタムドメイン名の設定はしない。

4-4-1. APIGateway > API作成押下

01.png

4-4-2. REST API > 構築

02.png

4-4-3. API名入力 > APIの作成

任意の文字列を入力する。
03.png

4-4-4. メソッドの作成 > post選択

04.png
05.png

4-4-5. Lambda関数選択 > 保存

Lambda関数はCloudFormationmで構築したLambda関数を定義。
今回は HaruBotFunction
06.png

権限付与ダイアログは脳死でOK
07.png

4-4-6. メソッドリクエスト > HTTPリクエストヘッダー追加

08.png

設定する値。

名前必須キャッシュ
X-Line-SignatureTF

09.png

4-4-7. APIのデプロイ > URLコピー

10.png
ステージ名は任意の文字列を入力。
11.png
枠内のURLを控える。
12.png

5. LINE②

4-4-7. でコピーしたURLを、LINE Developers > Messaging API設定 > Webhook設定 のWebhook URLに登録。
スクリーンショット 2020-08-12 22.02.54.png

6. 完成

これで完成。
DynamoDBに会話用のレコードを登録し、CDNに画像をアップロードすればデモのように動かせることができる。

7. まとめ

7-1. 残念なロジック

今回は、息子に直接会えない親族や、友人に少しでも息子に触れ合ってもらえるようにBotを構築。
お宮参り後に公開したくてちょっと無理やりな構成になってしまったのは残念。
特に、形態素解析して、ワードをピックアップするロジックを組みたかったけど、うまくいかなかったため、残念なロジックに・・・
ここは改善したい。

ゆくゆくは、メンテナンス不要で、いい感じに更新されていくような構築ができたら完璧。

7-2. CDNをS3にする必要は..

家族アルバムアプリのみてねっていい感じのAPIないかなぁ?
日時指定すると、いい感じの画像を返却してくれるAPIが欲しい。笑
もしあれば課金しますわ。

というか、Instagramでもいいかなぁ。(怒られる恐れあり?)


Viewing all articles
Browse latest Browse all 8825

Trending Articles