お役立ち Twitter Bot を作りながら学ぶ AWS ドリル

~第 10 回 IaC 入門しながら作るリマインダー Bot (中編)

2022-10-03
How to be a Developer

Author : 金澤 圭

ソリューションアーキテクト(SA)の金澤 (@ketancho) です。10 月になりましたが皆さまいかがお過ごしでしょうか ? 先月号 で "スポーツが終盤戦に差し掛かるシーズン” と書きましたが、これからシーズンが始まるスポーツもたくさんありますよね ! 私は駅伝が大好きなので、毎年観るのを楽しみにしています。チームによる戦略の違い、番狂わせな快走、苦楽を共にしたメンバーが繋いできた襷の重みなどなど、見てる側もワクワクドキドキしてしまいます。このドリル連載も最終盤、この記事を含めてあと 2 回で終了です。ゴールまでしっかり襷を運んでいきたいと思っております。(私は記事の締切にドキドキしています。)

さて、前回の記事 では皆さまに Infrastructure as Code (IaC) に挑戦していただきながら、リマインダー Bot を作りました。具体的には、 AWS Serverless Application Model (AWS SAM) を使い、テンプレートから AWS Lambda 関数、Amazon EventBridge ルールを作成しました。これまで手で構築していたものが自動で構築されていく快感 ( ? ) を味わっていただけたのでは ? と思っています。

ただし、ひとつの記事では AWS SAM の触り程度しかできなかったのと、リマインダー Bot もまだ道半ばでしたので、この記事でも AWS SAM を使ったリマインダー Bot 開発の続きをやっていきます。

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 おはよう Bot 編
  • 第 2 回 昔書いた記事の宣伝 Bot 編
  • 第 3 回 リファクタリング & 曜日ごとのツイート 編
  • 第 4 回 新章突入 ! 気になるワード検索 & 通知 Bot 編
  • 第 5 回 皆さまの代わりに英語でツイートしておくよ Bot 編
  • 第 6 回 AWS Step Functions を使って Well-being Bot を作ろう ! (前編)
  • 第 7 回 AWS Step Functions を使って Well-being Bot を作ろう ! (後編)
  • 第 8 回 - 総集編 & 夏休みの自由研究のすすめ
  • 第 9 回 - IaC 入門しながら作るリマインダー Bot (前編)
  • 第 10 回 - IaC 入門しながら作るリマインダー Bot (中編)
  • 完結回 - IaC & CI/CD 入門しながら作るリマインダー Bot (後編)

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »

毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。 


1. この記事で作る Bot の機能と、とりあげる AWS サービス・機能

前回は、下記の要件でリマインダー Bot を作成しました。

  • 「あとで読む」を含む自分のツイートに対して、3 日後に「本当にあとで読みましたか ?」とメンションを送る (3 日後なので、検索がそのまま使えます)
  • メンションを 12 時に送る

そして、現状の構成図はこのようになっています。今回の記事では、ここまで構築を進めていただいている前提で開発を進めるので、もしまだ取り組まれていない方は、前回の記事 にまずはチャレンジしてみてください。

クリックすると拡大します

そして、今回の記事ではこのリマインダー Bot を下記の動きになるようにアップデートします。

  • 前日分の「あとで読む」を含む自分のツイート ID をデータベースに格納しておく (この処理は毎日 0 時過ぎに行う)
  • ツイートから 14 日後に そのツイートに対して「本当にあとで読みましたか ?」とメンションを送る (メンションを 12 時に送る)

先ほどまでは「3 日後に」でしたので Twitter の検索 API で情報を取得できたのですが、前回の記事でも触れた通り、この検索 API では 7 日以内のツイートしか取得できません。ですので、「14 日後に」リマインドするために、①「あとで読む」ツイートを検索し、データベースにその情報を格納しておく、②日次でデータベースを確認し、リマインドするタイミングになったツイートがあればリマインドする、という二段階の構成にする必要があるわけです。

そのためにこのような構成を目指します。前回までの構成に加え、Lambda 関数をひとつ追加しています。そして、リマインド対象となるツイート情報を格納するためのデータベースとして Amazon DynamoDB を追加しています。

この記事では、下記の 3 つのステップに分けて実装を進めていきます。

  1. リマインドする対象のツイート情報を保存する DynamoDB テーブルを作成する
  2. 前回作成した Lambda 関数 twitter-bot-send-reminder-function をアップデートし、DynamoDB テーブルからリマインド対象のツイート情報を取得し、リマインドツイートする形に変更する
  3. Lambda 関数 twitter-bot-find-tweets-to-remind-function を新規に作成し、「あとで読む」ツイートを探し、DynamoDB テーブルに格納するようにする

完成版の Bot の動きを時系列に並べ替えると、3. の Lambda 関数が実行される → 1. の DynamoDB テーブルにデータが挿入される → 2. の Lambda 関数が実行されるとなり、この記事の作業順とは異なる点だけ最初にご認識いただければと思います。それでは少しずつやっていきましょう !


2. AWS Cloud9 環境を起動し直す

それでは作業を開始していきます。前回に引き続き、AWS Cloud9 を使っていきます。前回の記事で作成したものをそのまま使いたいので、今回は新しく環境を作るのではなく、環境を再開させていきます。

AWS Cloud9 のホーム画面 に移動すると、このような状態になっていると思います。

こちらの「Open IDE」ボタンをクリックしましょう。前回の作業から間が空いている場合、“EC2 instance is stopped. Attempting to start instance...” というメッセージが出て少しお待ちいただく形になります。

クリックすると拡大します

少し待つと AWS Cloud9 環境が再起動してくると思います。その際、ターミナルのカレントディレクトリが、~/environment に戻っていると思います。ビルドしたりデプロイしたりする作業は、前回作成した aws-drill-reminder-bot ディレクトリで行う必要がありますので、

$ cd ~/environment/aws-drill-reminder-bot/

としてディレクトリを移動しておきましょう。


3. AWS SAM を用いて DynamoDB テーブルを追加する

では DynamoDB テーブルを作成するように template.yaml を修正していきます。DynamoDB は AWS ドリル 第 2 回 にて使用した以来ですが、皆さま DynamoDB の特徴やテーブルの作り方は覚えておりますでしょうか ? もし、復習しておきたい方は こちらの記事 もあわせてご確認いただければと思います。

今回は、下記の設定で DynamoDB テーブルを構築していきます。

  • テーブル名 : TweetsToRemindTable
  • パーティションキー : RemindDate
  • ソートキー : TweetId

パーティションキーの RemindDate には、そのツイートをいつリマインドするかを YYYYMMDD 形式で保持する想定です。検索にヒットしたツイートが見つかったら、そのツイートから 14 日後の日付を入れるイメージになります。TweetId にはツイートごとにユニークに割り当てられる ID を格納します。具体的にはこちらのような項目が挿入される形になります。

3-1. template.yaml を修正する

では、前回作成した template.yaml に、DynamoDB テーブルリソースの記述を追加します。前回は、AWS リソースとして Lambda 関数 AWS::Serverless::Function を使用しました。DynamoDB テーブルも、AWS SAM の機能としては AWS::Serverless::SimpleTable というリソースが用意されており、こちらを用いることでシンプルな DynamoDB テーブルを作成できます。

しかし、今回の DynamoDB テーブルを作る際に細かい設定をしたいのですが、AWS::Serverless::SimpleTable では一部の定義ができません。そこで、AWS::DynamoDB::Table という AWS CloudFormation のリソースを使って定義します。前回の記事でもご紹介しましたが、AWS SAM は AWS CloudFormation の拡張で、AWS CloudFormation で用意されているすべてのリソースも使用できますので、今回はこれを使っていきます。

aws-drill-reminder-bot ディレクトリの直下にある template.yaml を下記のように修正します。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  aws-drill-reminder-bot

Globals:
  Function:
    Timeout: 3

Resources:
  SendReminderFunction:
    Type: AWS::Serverless::Function 
    Properties:
      FunctionName: twitter-bot-send-reminder-function
      CodeUri: twitter-bot-send-reminder-function/
      Handler: app.lambda_handler
      Runtime: python3.7
      Policies:
        - AmazonSSMReadOnlyAccess
      Events:
        SendReminderEvent:
          Type: Schedule
          Properties:
            Schedule: 'cron(0 3 * * ? *)'
            Name: SendReminderEvent
            Enabled: True
# DynamoDB に関する記述を追加(ここから)
  TweetsToRemindDynamoDbTbl:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: TweetsToRemindTable
      AttributeDefinitions:
        -
          AttributeName: "RemindDate"
          AttributeType: "S"
        -
          AttributeName: "TweetId"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: 'RemindDate'
          KeyType: 'HASH'
        - 
          AttributeName: 'TweetId'
          KeyType: 'RANGE'
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
# DynamoDB に関する記述を追加(ここまで)

各項目の細かい説明は AWS::DynamoDB::Table のドキュメント に譲りますが、今回テンプレートに追加した内容としては、

  • テーブルの名前
  • 属性 (Attribute) として何を用意するか
  • テーブルのキーはどの属性 (Attribute) にするか
  • テーブルの Read / Write Capacity Unit をどうするか

を定義しています。テンプレートの修正がおわりましたら、忘れずにファイルを保存してください。

3-2. ビルドとデプロイを行い、DynamoDB テーブルができていることを確認する

それでは、ビルドとデプロイを行います。

$ sam build
$ sam deploy

デプロイコマンドを実行し、少し待つと Change Set が表示されるはずです。

$ sam deploy


(中略)


CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                              LogicalResourceId                      ResourceType                           Replacement                          
---------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                  TweetsToRemindDynamoDbTbl              AWS::DynamoDB::Table                   N/A                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:981766141304:changeSet/samcli-deploy1662856992/46b5cce0-8682-4959-8389-8a4cbe7a2108

Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: 

DynamoDB テーブルが新たに追加されることを確認したのち、y と入力してください。少しお待ちいただくと、デプロイが完了したメッセージが表示されるはずです。

Deploy this changeset? [y/N]: y

2022-09-09 06:51:07 - Waiting for stack create/update to complete

CloudFormation events from changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                         ResourceType                           LogicalResourceId                      ResourceStatusReason                 
---------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                     AWS::DynamoDB::Table                   TweetsToRemindDynamoDbTbl              -                                    
CREATE_IN_PROGRESS                     AWS::DynamoDB::Table                   TweetsToRemindDynamoDbTbl              Resource creation Initiated          
CREATE_COMPLETE                        AWS::DynamoDB::Table                   TweetsToRemindDynamoDbTbl              -                                    
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS    AWS::CloudFormation::Stack             aws-drill-reminder-bot                 -                                    
UPDATE_COMPLETE                        AWS::CloudFormation::Stack             aws-drill-reminder-bot                 -                                    
---------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - aws-drill-reminder-bot in ap-northeast-1

DynamoDB テーブルが作られているかをマネージメントコンソールで確認しましょう。DynamoDB のテーブル一覧画面 に遷移します。このように TweetsToRemindTable ができていれば無事デプロイが成功しています🎉

クリックすると拡大します


4. 前回作成した AWS Lambda 関数 twitter-bot-send-reminder-function をアップデートする

DynamoDB テーブルの準備ができたので、続いてリマインドツイートをする Lambda 関数 twitter-bot-send-reminder-function をアップデートします。前回の記事の時点では、この関数は

  • 検索条件に合う「あとで読む」ツイートがあるか Twitter 検索する
  • 該当のツイートがあれば、それに対して「読んだ ?」とリプライを飛ばす

の 2 つの役割を持っていました。

しかし、この記事では、前者の役割を後ほど新規作成する Lambda 関数に譲る形とし、

  • その当日にリプライを飛ばすべき過去ツイートがあるか DynamoDB を検索する
  • 該当のツイートがあれば、それに対して「読んだ?」とリプライを飛ばす

という役割を担う形に修正します。後者は元々あった機能をそのまま使えるので、前者のリマインド対象の取得部分を修正していきましょう。

4-1. Lambda 関数 twitter-bot-send-reminder-function をアップデートする

それでは、Lambda 関数 twitter-bot-send-reminder-function を修正します。twitter-bot-send-reminder-function ディレクトリ配下の app.py をエディタで開きます。

前回の記事で、リマインド対象ツイートは Twitter API を叩く形で取得していました。該当部分を再掲します。

def lambda_handler(event, context): 
    init()
    
    # ★Twitter API を叩いて検索している部分(ここから)
    search_query = 'あとで読む from:ketancho -is:retweet'
    start_time = (datetime.now() + timedelta(days=-4)).strftime('%Y-%m-%dT15:00:00Z')
    end_time =  (datetime.now() + timedelta(days=-3)).strftime('%Y-%m-%dT15:00:00Z')
    
    json_response = get_matching_tweets(search_query, start_time, end_time)
    
    if('data' not in json_response):
        return
    # ★Twitter API を叩いて検索している部分(ここまで)
    
    matched_tweets = json_response['data']
    
    for matched_tweet in matched_tweets:
        reply("@ketancho あとで読みましたか?", matched_tweet['id'])

今回は、既に DynamoDB テーブルにリマインド対象のツイート情報が格納されていることを前提とし、テーブルからデータを取得する形に変更します。最終的なソースコードは後ほど紹介しますが、先にポイントだけ紹介します。

まず、DynamoDB にクエリを投げる下準備をします。こちらのページ が参考になるので、参照いただきたいのですが、lambda_handler メソッドの外側に下記のコードを追加します。

from boto3.dynamodb.conditions import Key
dynamodb_tweets_to_remind_tbl = boto3.resource('dynamodb').Table('TweetsToRemindTable')

そして、元々 検索 API を呼んでいた部分を変更し、DynamoDB テーブルに対して Lambda 関数が実行された当日日付をキーにクエリするように修正します。

    today_yyyymmdd = datetime.now().strftime('%Y%m%d')
    
    json_response = dynamodb_tweets_to_remind_tbl.query(
        KeyConditionExpression=Key('RemindDate').eq(today_yyyymmdd)
    )

1点だけ注意点を共有します。当日日付を表す today_yyyymmdd ですが、こちらは UTC 時間での現在日付になります。今回、この Lambda 関数は日本時間で 12 時に実行する設定にするため、UTC でも 同日の 3 時になり、日付としては同じ値になります。もし、日本時間の 9 時より前にこのリマインド処理を実行したい場合は同じやり方だと 1 日ずれてしまうので、調整してください。

そして、データが取れたあとの処理ですが、前回と同様、該当するツイートがなければ終了、あればメンションするという形は変わりません。ただし、データの格納のされ方が少し違うので、この点だけ微修正します。

私は下記のように実装してみました。リプライを送る先は皆様の Twitter アカウントに変更する点だけお忘れなきようお願いいたします。

import json
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta

import boto3
ssm_client = boto3.client('ssm')

# 追加(ここから)
from boto3.dynamodb.conditions import Key
dynamodb_tweets_to_remind_tbl = boto3.resource('dynamodb').Table('TweetsToRemindTable')
# 追加(ここまで)

oauth = None
    
def lambda_handler(event, context):
    init()
    # 修正(ここから)
    today_yyyymmdd=datetime.now().strftime('%Y%m%d')
    
    json_response = dynamodb_tweets_to_remind_tbl.query(
        KeyConditionExpression=Key('RemindDate').eq(today_yyyymmdd)
    )

    if ('Items' not in json_response):
        return
    target_tweets = json_response['Items']

    for target_tweet in target_tweets:
        reply("@ketancho あとで読みましたか?", target_tweet['TweetId']) #FIXME: 皆さまの Twitter アカウントに変更
    # 修正(ここまで)

def init():
    response = ssm_client.get_parameter(
        Name='/credentials/twitter',
        WithDecryption=True
    )
    twitter_parameters = json.loads(response['Parameter']['Value'])

    consumer_key = twitter_parameters['consumer_key']
    client_secret = twitter_parameters['client_secret']
    access_token = twitter_parameters['access_token']
    access_token_secret = twitter_parameters['access_token_secret']
    
    global oauth
    oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)
  
# get_matching_tweets 関数は使用しなくなるので削除

def reply(text, target_tweet_id):
    payload = {
        'text': text,
        'quote_tweet_id': target_tweet_id,
        'reply': {
            'in_reply_to_tweet_id': target_tweet_id
        }
    }
    response = oauth.post(
        'https://api.twitter.com/2/tweets',
        json=payload,
    )
    if response.status_code != 201:
        raise Exception(
            '[Error] {} {}'.format(response.status_code, response.text)
        )

また、この実装の中で DynamoDB に対してクエリを投げるので、Lambda 関数 twitter-bot-send-reminder-function に紐づく IAM ロールに対して、DynamoDB テーブルにアクセスを許す IAM ポリシーを付与する必要があります。本来、最小権限になるように IAM ロールを定義すべきですが、今回は標準で用意された AmazonDynamoDBFullAccess ポリシーをアタッチするように、template.yaml を修正します。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  aws-drill-reminder-bot

Globals:
  Function:
    Timeout: 3

Resources:
  SendReminderFunction:
    Type: AWS::Serverless::Function 
    Properties:
      FunctionName: twitter-bot-send-reminder-function
      CodeUri: twitter-bot-send-reminder-function/
      Handler: app.lambda_handler
      Runtime: python3.7
      Policies:
        - AmazonSSMReadOnlyAccess
        - AmazonDynamoDBFullAccess # 1行追加
      Events:
        SendReminderEvent:
          Type: Schedule
          Properties:
            Schedule: 'cron(0 3 * * ? *)'
            Name: SendReminderEvent
            Enabled: True
  TweetsToRemindDynamoDbTbl:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: TweetsToRemindTable
      AttributeDefinitions:
        -
          AttributeName: "RemindDate"
          AttributeType: "S"
        -
          AttributeName: "TweetId"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: 'RemindDate'
          KeyType: 'HASH'
        - 
          AttributeName: 'TweetId'
          KeyType: 'RANGE'
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

ここまでできたら、再度ビルド & デプロイします。

$ sam build
$ sam deploy

表示される変更点を確認し、y と入力してください。少しお待ちいただくと、デプロイが完了したメッセージが表示されるはずです。

$ sam deploy

(中略)

CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                              LogicalResourceId                      ResourceType                           Replacement                          
---------------------------------------------------------------------------------------------------------------------------------------------------------
* Modify                               SendReminderFunctionRole               AWS::IAM::Role                         False                                
* Modify                               SendReminderFunctionSendReminderEven   AWS::Lambda::Permission                Conditional                          
                                       tPermission                                                                                                        
* Modify                               SendReminderFunctionSendReminderEven   AWS::Events::Rule                      False                                
                                       t                                                                                                                  
* Modify                               SendReminderFunction                   AWS::Lambda::Function                  False                                
---------------------------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:675174999332:changeSet/samcli-deploy1662711586/bf11e62e-b349-4d81-9f62-33762df4118c


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

2022-09-09 08:21:57 - Waiting for stack create/update to complete

CloudFormation events from changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                         ResourceType                           LogicalResourceId                      ResourceStatusReason                 
---------------------------------------------------------------------------------------------------------------------------------------------------------
UPDATE_IN_PROGRESS                     AWS::IAM::Role                         SendReminderFunctionRole               -                                    
UPDATE_COMPLETE                        AWS::IAM::Role                         SendReminderFunctionRole               -                                    
UPDATE_IN_PROGRESS                     AWS::Lambda::Function                  SendReminderFunction                   -                                    
UPDATE_COMPLETE                        AWS::Lambda::Function                  SendReminderFunction                   -                                    
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS    AWS::CloudFormation::Stack             aws-drill-reminder-bot                 -                                    
UPDATE_COMPLETE                        AWS::CloudFormation::Stack             aws-drill-reminder-bot                 -                                    
---------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - aws-drill-reminder-bot in ap-northeast-1

以上で、Lambda 関数 twitter-bot-send-reminder-function の修正は完了です。

4-2. Lambda 関数 twitter-bot-send-reminder-function が正しく動作するかを確認する

デプロイが上手くいきましたら、期待する動きになっているか確認していきましょう。この Lambda 関数の動きを確認するには DynamoDB テーブルに項目を追加する必要がありますが、今の時点でテーブルは空だと思います。そこで、テスト用の項目を追加しましょう。

皆さまの普段づかいの Twitter アカウントから、お試しでリプライを送るツイートを選んでください。そのツイートの詳細ページに飛ぶと URL の末尾に 20 桁前後の数字が含まれているはずで、こちらがツイート ID になります。例えばこの例だと 1568446822561247240 です。

クリックすると拡大します

このツイート ID を DynamoDB に格納します。DynamoDB のサービス画面を開き、「項目を探索」、「TweetsToRemindTable」、「項目を作成」の順にクリックします。

クリックすると拡大します

RemindDate には YYYYMMDD 形式でこの作業を行っている当日日付 (ただし、UTC での日付になるので、日本時間の 0 時から 9 時までの間に実装している場合は前日の日付)、TweetId には先ほど調べたテスト対象のツイート ID を入力し、「項目を作成」をクリックしてください。

クリックすると拡大します

DynamoDB に項目を追加したら、Lambda 関数 twitter-bot-send-reminder-function を実行してみましょう。先ほど対象としたツイートに対して、このようにリマインドされれば成功です🎉

クリックすると拡大します


5. Lambda 関数 twitter-bot-*find-tweets-to-remind*-function を新規に作成する

DynamoDB にリマインド対象のツイート情報が入れられればリマインドできるところまで来ました。ここからは今回のドリルの仕上げとして、Twitter 検索し、ヒットした「あとで読む」ツイートの情報を DynamoDB テーブルに格納する Lambda 関数 twitter-bot-find-tweets-to-remind-function を作成します。検索する部分は前回のドリルの実装をそのまま利用することができます。

まず、AWS SAM で Lambda 関数を新規作成するための、ディレクトリとファイル群を作成します。これは前回のドリルでも行った作業ですね。このように Lambda 関数ごとにディレクトリや Python ファイルを用意します。

$ cd ~/environment/aws-drill-reminder-bot/
$ mkdir twitter-bot-find-tweets-to-remind-function
$ touch twitter-bot-find-tweets-to-remind-function/app.py
$ touch twitter-bot-find-tweets-to-remind-function/requirements.txt

作成した twitter-bot-find-tweets-to-remind-function ディレクトリの下の app.py に、Lambda 関数のコードを実装していきます。DynamoDB テーブルへの挿入はこのドリルシリーズでは初めてになりますが、こちらの put_item 関数 を使います。

また、要件として 14 日後にリマインドする必要があるので、本来 remindDate には 14 日後の日付を YYYYMMDD 形式で入れるべきなのですが、これだとテスト実行で 14 日待つ必要があります。まずは稼働確認をしたいので、1 日後の日付を DynamoDB テーブルに挿入してみましょう。

この実装部分は腕試しとしてチャレンジしてみていただきたいので、時間を取って取り組んでみてください。

 

 

 


いかがでしょうか ? 私は下記のように実装してみました。途中の search_query の部分は、皆様の Twitter アカウントに置き換える必要があることにご注意ください。

import json
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta

import boto3
ssm_client = boto3.client('ssm')
dynamodb_tweets_to_remind_tbl = boto3.resource('dynamodb').Table('TweetsToRemindTable')

oauth = None

def lambda_handler(event, context): 
    init()
    
    search_query = 'あとで読む from:ketancho -is:retweet' #FIXME: 皆さまの Twitter アカウントに変更
    start_time = (datetime.now() + timedelta(days=-1)).strftime('%Y-%m-%dT15:00:00Z')
    end_time =  datetime.now().strftime('%Y-%m-%dT15:00:00Z')
    
    json_response = get_matching_tweets(search_query, start_time, end_time)
    
    if('data' not in json_response):
        return
    matched_tweets = json_response['data']
    
    for matched_tweet in matched_tweets:
        tweet_text_id = matched_tweet['id']
        dynamodb_tweets_to_remind_tbl.put_item(
          Item = {
            "RemindDate": (datetime.now() + timedelta(days=+1)).strftime('%Y%m%d'), # テストのために1日後の日付にする
            "TweetId": tweet_text_id
          }
        )
        
    return json_response

def init():
    response = ssm_client.get_parameter(
        Name='/credentials/twitter',
        WithDecryption=True
    )
    twitter_parameters = json.loads(response['Parameter']['Value'])

    consumer_key = twitter_parameters['consumer_key']
    client_secret = twitter_parameters['client_secret']
    access_token = twitter_parameters['access_token']
    access_token_secret = twitter_parameters['access_token_secret']
    
    global oauth
    oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)
    
def get_matching_tweets(search_query, start_time, end_time):
    url = 'https://api.twitter.com/2/tweets/search/recent'
    params = {
        'query': search_query,
        'max_results': 20,
        'start_time': start_time,
        'end_time': end_time
    }
    response = oauth.get(
       url, params=params
    )
    
    if response.status_code != 200:
        raise Exception(
            '[Error] {} {}'.format(response.status_code, response.text)
        )

    return response.json()

また、twitter-bot-find-tweets-to-remind-function ディレクトリの下の requirements.txt には、

requests_oauthlib

と記入していただき、保存してください。

最後に、template.yaml の修正です。Lambda 関数ですので AWS::Serverless::Function タイプのリソースを追加します。この Lambda 関数も、Parameter Store と DynamoDB へのアクセスが必要なので適切な権限を付与します。また、毎日 0 時 5 分に起動させる設定も必要です。template.yaml の修正もぜひ挑戦してみてください。

 



 

いかがでしょうか ? 私は下記のようにアップデートしました。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  aws-drill-reminder-bot

Globals:
  Function:
    Timeout: 3

Resources:
  # 追加(ここから)
  FindTweetsToRemindFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: twitter-bot-find-tweets-to-remind-function
      CodeUri: twitter-bot-find-tweets-to-remind-function/
      Handler: app.lambda_handler
      Runtime: python3.7
      Policies:
        - AmazonSSMReadOnlyAccess
        - AmazonDynamoDBFullAccess
      Events:
        FindTweetsToRemindEvent:
          Type: Schedule
          Properties:
            Schedule: 'cron(5 15 * * ? *)'
            Name: FindTweetsToRemindEvent
            Enabled: True
  # 追加(ここまで)
  SendReminderFunction:
    Type: AWS::Serverless::Function 
    Properties:
      FunctionName: twitter-bot-send-reminder-function
      CodeUri: twitter-bot-send-reminder-function/
      Handler: app.lambda_handler
      Runtime: python3.7
      Policies:
        - AmazonSSMReadOnlyAccess
        - AmazonDynamoDBFullAccess
      Events:
        SendReminderEvent:
          Type: Schedule
          Properties:
            Schedule: 'cron(0 3 * * ? *)'
            Name: SendReminderEvent
            Enabled: True
  TweetsToRemindDynamoDbTbl:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: TweetsToRemindTable
      AttributeDefinitions:
        -
          AttributeName: "RemindDate"
          AttributeType: "S"
        -
          AttributeName: "TweetId"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: 'RemindDate'
          KeyType: 'HASH'
        - 
          AttributeName: 'TweetId'
          KeyType: 'RANGE'
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

ファイルの保存を忘れずに行い、再度ビルド & デプロイします。

$ sam build
$ sam deploy

表示される変更点を確認し、y と入力してください。少しお待ちいただくと、デプロイが完了したメッセージが表示されるはずです。

$ sam deploy
Uploading to aws-drill-reminder-bot/f1995df3e16464bfa1b3c157e0a0dd8a  641024 / 641024  (100.00%)
File with same data already exists at aws-drill-reminder-bot/f0bbe53823be2f63da5d9fa62169a404, skipping upload

        Deploying with following values
        ===============================
        Stack name                   : aws-drill-reminder-bot
        Region                       : ap-northeast-1
        Confirm changeset            : True
        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-kp9g03ds7rpt
        Capabilities                 : ["CAPABILITY_IAM"]
        Parameter overrides          : {}
        Signing Profiles             : {}

Initiating deployment
=====================
Uploading to aws-drill-reminder-bot/63b7cbe646a578fd84c792e2850308e8.template  1858 / 1858  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                              LogicalResourceId                      ResourceType                           Replacement                          
---------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                  FindTweetsToRemindFunctionFindTweets   AWS::Lambda::Permission                N/A                                  
                                       ToRemindEventPermission                                                                                            
+ Add                                  FindTweetsToRemindFunctionFindTweets   AWS::Events::Rule                      N/A                                  
                                       ToRemindEvent                                                                                                      
+ Add                                  FindTweetsToRemindFunctionRole         AWS::IAM::Role                         N/A                                  
+ Add                                  FindTweetsToRemindFunction             AWS::Lambda::Function                  N/A                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:981766141304:changeSet/samcli-deploy1662871651/a7b5fd2e-eb3f-43d8-9dad-16ef9fb274b1


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

2022-09-11 04:47:46 - Waiting for stack create/update to complete

CloudFormation events from changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                         ResourceType                           LogicalResourceId                      ResourceStatusReason                 
---------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                     AWS::IAM::Role                         FindTweetsToRemindFunctionRole         -                                    
CREATE_IN_PROGRESS                     AWS::IAM::Role                         FindTweetsToRemindFunctionRole         Resource creation Initiated          
CREATE_COMPLETE                        AWS::IAM::Role                         FindTweetsToRemindFunctionRole         -                                    
CREATE_IN_PROGRESS                     AWS::Lambda::Function                  FindTweetsToRemindFunction             -                                    
CREATE_IN_PROGRESS                     AWS::Lambda::Function                  FindTweetsToRemindFunction             Resource creation Initiated          
CREATE_COMPLETE                        AWS::Lambda::Function                  FindTweetsToRemindFunction             -                                    
CREATE_IN_PROGRESS                     AWS::Events::Rule                      FindTweetsToRemindFunctionFindTweets   -                                    
                                                                              ToRemindEvent                                                               
CREATE_IN_PROGRESS                     AWS::Events::Rule                      FindTweetsToRemindFunctionFindTweets   Resource creation Initiated          
                                                                              ToRemindEvent                                                               
CREATE_COMPLETE                        AWS::Events::Rule                      FindTweetsToRemindFunctionFindTweets   -                                    
                                                                              ToRemindEvent                                                               
CREATE_IN_PROGRESS                     AWS::Lambda::Permission                FindTweetsToRemindFunctionFindTweets   -                                    
                                                                              ToRemindEventPermission                                                     
CREATE_IN_PROGRESS                     AWS::Lambda::Permission                FindTweetsToRemindFunctionFindTweets   Resource creation Initiated          
                                                                              ToRemindEventPermission                                                     
CREATE_COMPLETE                        AWS::Lambda::Permission                FindTweetsToRemindFunctionFindTweets   -                                    
                                                                              ToRemindEventPermission                                                     
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS    AWS::CloudFormation::Stack             aws-drill-reminder-bot                 -                                    
UPDATE_COMPLETE                        AWS::CloudFormation::Stack             aws-drill-reminder-bot                 -                                    
---------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - aws-drill-reminder-bot in ap-northeast-1

AWS Lambda の画面にて、Lambda 関数 twitter-bot-find-tweets-to-remind-function が作成されていることをご確認ください。その後、皆さまの普段遣いの twitter アカウントで「あとで読む」ツイートを行い、その翌日の 0 時 5 分にこの Lambda 関数が動き、DynamoDB テーブルに項目が追加されることを確認してみてください。

なお、手動で Lambda 関数を動かすことでこの確認はできるのですが、Twitter の検索 API に渡すパラメータ end_time の「API リクエスト時間より 10 秒以上前であること」という制約 (詳細は、AWS ドリル 第 5 回 の 7. 落ち穂拾い & 参考情報 を参照してください) にぶつかる可能性があります。この場合は、「'end_time' must be a minimum of 10 seconds prior to the request time.」というエラーが出るので、もしこのエラーに遭遇した場合は、検索条件のパラメーターを調整するなどの対応が必要になります。

そして、DynamoDB テーブルに項目が追加されると、その日のお昼にリマインドリプライが来るはずです。

リマインド Bot がうまく動くすることが確認できましたら、Lambda 関数 twitter-bot-find-tweets-to-remind-function を再度修正し、14 日後にリマインドされるようにしてみてください。この作業は次回のドリルでも扱うので、しばらくはこのまま運用していただいても構わないです😉

クリックすると拡大します

以上で、今回の AWS ドリルの実装は完了になります ! お疲れさまでした🎉


6. まとめ

「お役立ち Twitter Bot を作りながら学ぶ AWS ドリル」連載の第 10 弾として、前回の記事から作成を開始したリマインダー Bot を完成させました。このおせっかいな Bot によって、皆さまの学習ライフがよりよいものになることを願っています😉

「リマインダー Bot を完成」と書きましたが、タイトルは「中編」となっているのに気付かれた方もいらっしゃるかもしれません。このリマインダー Bot の機能としては完成したのですが、今後運用するにあたって最後に継続的インテグレーション (Continuous Integration; CI)、継続的デリバリー (Continuous Delivery; CD) な仕組みを追加しておこうと思います。この CI/CD パイプラインの構築をご体験いただき、この AWS ドリル完走 ! としたいと思いますので、来月の最終回も楽しみにしていただければ嬉しいです。

また、次回で最終回ということで、来年以降の記事についてもボチボチ考えていきたいと思っています。皆さまのご感想・ご要望に加え、来年以降こんな連載があれば読んでみたいというご希望がございましたら、#AWSウェブマガジン タグをつけてツイートしていただきたいです🙏 もし、パブリックに書くのが嫌だな.. という方がいらっしゃいましたら、DM 開放しておりますので @ketancho まで直接投げつけていただいても構いませんー ! ぜひお待ちしております。

それではまた来月お会いしましょう ! 🙌

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 おはよう Bot 編
  • 第 2 回 昔書いた記事の宣伝 Bot 編
  • 第 3 回 リファクタリング & 曜日ごとのツイート 編
  • 第 4 回 新章突入 ! 気になるワード検索 & 通知 Bot 編
  • 第 5 回 皆さまの代わりに英語でツイートしておくよ Bot 編
  • 第 6 回 AWS Step Functions を使って Well-being Bot を作ろう ! (前編)
  • 第 7 回 AWS Step Functions を使って Well-being Bot を作ろう ! (後編)
  • 第 8 回 - 総集編 & 夏休みの自由研究のすすめ
  • 第 9 回 - IaC 入門しながら作るリマインダー Bot (前編)
  • 第 10 回 - IaC 入門しながら作るリマインダー Bot (中編)
  • 完結回 - IaC & CI/CD 入門しながら作るリマインダー Bot (後編)

builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

金澤 圭 @ketancho
アマゾン ウェブ サービス ジャパン合同会社
技術統括本部 ソリューションアーキテクト

サーバーレスが好きなソリューションアーキテクト。業種業界問わず、お客様のプロダクト開発をサポートさせていただいています。オンデマンドで視聴できるハンズオン「AWS Hands-on for Beginners」、実際にものづくりをしながら AWS を学ぶ「AWS ドリルシリーズ」を企画・推進しており、楽しく学べるコンテンツを日々考えています。好きなサービスは AWS Lambda、AWS Step Functions、Amazon Personalize で、好きな休日の過ごし方は娘ふたりと川の字になって昼寝👧👧と赤ん坊を抱っこしながらの散歩です👶

さらに最新記事・デベロッパー向けイベントを検索

下記の項目で絞り込む
1

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する