MMMブログ

自動返信LINE BotをAWS SAM+DynamoDBで作ってみた

このエントリーをはてなブックマークに追加

こんにちは、関口です。
最近はUber EatsとAmazonフレッシュを多用して以前にもまして引きこもり気味です。

今回は現状所属しているプロジェクトのキャッチアップ的意味合いを込めて、
ポジティブなワードを返してくれる自動LineBotを
AWS SAMとDynamoDBで試してみました。

雛形作成

まずは雛形のプロジェクトを作成します。

1
2
3
4
5
6
$ sam init --runtime go1.x --name positive-line-bot

$ cd positive-line-bot

//依存モジュール管理
$ go mod init line-positive-bot

DynamoDB

Lineと連携する前に、
DynamoDBから自動返信されるワードを取得する部分を実装します。

一覧をDynamoDBのテーブルから取得してきて、
そのうち1つをランダムに抽出しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
type Positive struct {
ID int `json:"ID"`
Name string `json:"Name"`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

endpoint := os.Getenv("DYNAMODB_ENDPOINT")
tableName := os.Getenv("DYNAMODB_TABLE_NAME")

sess := session.Must(session.NewSession())
config := aws.NewConfig().WithRegion("ap-northeast-1")
if len(endpoint) > 0 {
config = config.WithEndpoint(endpoint)
}

db := dynamodb.New(sess, config)

result, err := db.Scan(&dynamodb.ScanInput{
TableName: aws.String(tableName),
ConsistentRead: aws.Bool(true),
ReturnConsumedCapacity: aws.String("NONE"),
})
if err != nil {
return events.APIGatewayProxyResponse{}, err
}

var positives []Positive

err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &positives)
if err != nil {
fmt.Println(err)
return events.APIGatewayProxyResponse{}, err
}

var words []string
for _, positive := range positives {
words = append(words, positive.Name)
}

rand.Seed(time.Now().UnixNano())
i := rand.Intn(len(words))
word := words[i]

return events.APIGatewayProxyResponse{
Body: word,
StatusCode: 200,
}, nil
}

func main() {
lambda.Start(handler)
}

template.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Resources:
PositiveLineBotFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: positive-line-bot/
Handler: positive-line-bot
Runtime: go1.x
Tracing: Active
Policies:
- arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
Events:
CatchAll:
Type: Api
Properties:
Path: /positive
Method: GET
Environment:
Variables:
DYNAMODB_ENDPOINT: ''
DYNAMODB_TABLE_NAME: 'PositiveLineBotTable'

PositiveLineBotTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: 'PositiveLineBotTable'
AttributeDefinitions:
- AttributeName: 'ID'
AttributeType: 'N'
KeySchema:
- AttributeName: 'ID'
KeyType: 'HASH'
ProvisionedThroughput:
ReadCapacityUnits: '2'
WriteCapacityUnits: '2'

DynamoDB Local

うまくできているかどうかローカルで確認します。

docker-compose.yml

1
2
3
4
5
6
7
8
version: '3'

services:
dynamodb:
image: amazon/dynamodb-local
container_name: dynamodb
ports:
- 8000:8000

ローカルテストで用いるテーブル定義および、テストデータを作成します。
test/positive-line-bot_table.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"AttributeDefinitions": [
{
"AttributeName": "Id",
"AttributeType": "N"
}
],
"TableName": "PositiveLineBotTable",
"KeySchema": [
{
"AttributeName": "Id",
"KeyType": "HASH"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 2,
"WriteCapacityUnits": 2
}
}

test/positive-line-bot_table_data.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"PositiveLineBotTable": [
{
"PutRequest": {
"Item": {
"ID": {"N": "1"},
"Name": {"S": "test"}
}
}
},
{
"PutRequest": {
"Item": {
"ID": {"N": "2"},
"Name": {"S": "testtest"}
}
}
}
]
}

ローカルテスト用の環境変数を設定します

1
2
3
4
5
6
{
"PositiveLineBotFunction": {
"DYNAMODB_ENDPOINT": "http://{ポート番号}:8000",
"DYNAMODB_TABLE_NAME": "PositiveLineBotTable"
}
}

AWS CLI のコマンドを実行してデータを反映した上で
実行すると、テストデータの値が1つランダムで返されます

1
2
3
4
5
6
7
8
9
10
11
12
$ docker-compose up -d

$ aws dynamodb create-table --cli-input-json file://test/positive-line-bot_table.json --endpoint-url http://127.0.0.1:8000

$ aws dynamodb batch-write-item --request-items file://test/positive-line-bot_table_data.json --endpoint-url http://127.0.0.1:8000

$ aws dynamodb scan --table-name PositiveLineBotTable --endpoint-url http://127.0.0.1:8000

$ sam local start-api --env-vars test/env.json --profile dummy

$ curl http://localhost:3000/positive
{"id":1,"name":"testtest"}

Lineと連携する

Messaging APIを利用するにはなどを参考にLine側の設定行います。

設定時、ChannelSecretとアクセストークン(ロングターム)をメモします。

Lineに入力された値が渡ってくるように、コードとtemplate.ymlを修正します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
func UnmarshalLineRequest(data []byte) (LineRequest, error) {
var r LineRequest
err := json.Unmarshal(data, &r)
return r, err
}

type LineRequest struct {
Events []Event `json:"events"`
Destination string `json:"destination"`
}

type Event struct {
Type string `json:"type"`
ReplyToken string `json:"replyToken"`
Source Source `json:"source"`
Timestamp int64 `json:"timestamp"`
Message Message `json:"message"`
}

type Message struct {
Type string `json:"type"`
ID string `json:"id"`
Text string `json:"text"`
}

type Source struct {
UserID string `json:"userId"`
Type string `json:"type"`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

myLineRequest, err := UnmarshalLineRequest([]byte(request.Body))
if err != nil {
log.Fatal(err)
}

bot, err := linebot.New(
"ChannelSecret",
"アクセストークン(ロングターム)")
if err != nil {
log.Fatal(err)
}

// 中略

var tmpReplyMessage string
tmpReplyMessage = word
if _, err = bot.ReplyMessage(myLineRequest.Events[0].ReplyToken, linebot.NewTextMessage(tmpReplyMessage)).Do(); err != nil {
log.Fatal(err)
}

return events.APIGatewayProxyResponse{
Body: word,
StatusCode: 200,
}, nil

}

template.yml

1
Method: POST

デプロイ

最後にデプロイをして実際に確認してみます。

1
2
3
4
5
$ sam validate --profile my_profile --debug

$ sam build --template template.yaml --profile my_profile

$ sam deploy --guided --profile my_profile

作成されたdynamoDBテーブルにデータを入れた状態で
試しに自身の携帯から確認したところ無事自動返答がされました。

参考サイト

このエントリーをはてなブックマークに追加

お問い合わせ

見積もり依頼や詳しいご相談など、クラウド・AWSに関する困りごとをお気軽にご相談ください。
以下のお問い合わせ先から受け付けています。

お問合わせはこちら

※通常1営業日内にご回答いたします。