【AWS Lambda × Twitter API】Twitter Botを作ってみよう!!

2022/06/01

はじめまして。ヨコイと申します。
今回はAWS LambdaとTwitter APIを使用して、簡単なbotを作成してみたいと思います。

AWS Lambdaとは?

そもそも AWS Lambda とはどういったサービスなのでしょうか?
公式の文章を引用してみます。

AWS Lambda は、サーバーレスでイベント駆動型のコンピューティングサービスであり、サーバーのプロビジョニングや管理をすることなく、事実上あらゆるタイプのアプリケーションやバックエンドサービスのコードを実行することができます。200 以上の AWS のサービスやサービス型ソフトウェア (SaaS) アプリケーションから Lambda をトリガーすることができ、使用した分だけお支払いいただきます。

少しこれではわかりづらいですね。
まずサーバーレスとは「サーバーがいらない」という意味になります。これはどういうことを表しているかと言いますと、普通どんなサービスであれ、そのシステムが稼働するためのサーバーが必要になってきます。しかし常にサーバーを動かし続けているとその分膨大なコストがかかってしまいます。
Lambdaでは常にサーバーを動かすのではなく、必要な時だけ実行したい処理を実行することになります。こうすることでコストを抑えた効率的なアプリケーションを開発することが可能になります。
またイベント駆動型とあるようにイベントからLambdaを実行するサービスと組み合わせることで、さらに柔軟性を持たせることが可能になっています。

Twitter APIとは?

もうひとつ重要になってくる要素が Twitter API です。
Twitterに関するあらゆる操作をAPI経由で可能にしてくれるもので、ほぼ全ての操作が可能になっております。
ただ、当然の事ながらいくつかの制限もあり、開発の際には注意が必要です。またいくつかのバージョンに分かれておりますのでそちらも注意が必要です。基本的にバージョン2を使用すれば問題はないかと思われますが、要件によっては対応していない可能性があります。

どんなbotを作るのか?

今回作成するbotは毎週行われるイベントに関する告知ツイートをイベントの開始10分前にRTするというものです。
実行するAPIは以下の3つになります。

  • ツイートを検索するAPI
  • RTするAPI
  • ツイートするAPI(検索した結果ツイートが見つからなかった場合は定型文をツイートする)

イベントは毎週決まった時間に行われるものとします。

構成について


まず実際に実行するスクリプトをLambdaに登録しておきます。
クレデンシャルはスクリプトにハードコードせずにParameterStoreに登録してそこから読み出すようにします。
EventBridgeのcron式で定期的にイベントを発行し、そのイベントをトリガーにスクリプトを実行します。

では実際にやっていきます

Twitter APIを使用する準備

まずTwitter APIを使用できるように準備を進めます。
bot用のTwitterアカウントを作成します。
TwitterのTOPページへアクセスするといくつかサインアップの方法が出てくるのでお好きな方法でアカウントを作成してください。
アカウントが作成できたのであれば、Developer Platformにアクセスし、サインアップします。
必要な情報を入力してサインアップを完了してください。
続いてDeveloper PortalのダッシュボードにアクセスしてProjectとAppを作成します。作成するとAPI KeyAPI Key SecretBearer Tokenが出てきますのでこちらを控えておきます。(こちらの情報はこの画面でしか確認できません。もし忘れてしまった場合は再生成する必要があります。)

さてダッシュボードに再度遷移するとApp名が表示されていると思います。
App名横の歯車アイコンをクリックして詳細設定画面へ遷移します。
この画面の下部にあるUser authentication settingsのsetupボタンを押下します。(画像では既に設定済みのためeditとなっています。)

User authentication settings 画面にて

  • OAuth 1.0a を ON に変更
  • App permissions は Read and write に変更


してください。続いて

  • Callback URI / Redirect URL
  • Website URL

を入力しますが、こちらどんな値でも構いません。適当な値を入力してください。
その後saveボタンを押下します。

次にAppの設定画面に戻るのでKeys and tokensをクリック。
Keys and tokens タブに遷移したら、Access Token and Secret の Generate をクリックします。
Access TokenAccess Token Secret が表示されます。こちらも同様に控えておいてください。

以上でTwitter APIを利用する準備は整いました。
Twitter API v1.1以下であればelevated 申請をしないとまともに使えないようなのですがTwitter API v2であればelevated accessが許可されていなくとも十分 Twitter API を利用できます。
以下4つの情報を使用していきます。

  • API Key
  • API Key Secret
  • Access Token
  • Access Token Secret


AWS Lambdaのセットアップ

まずAWSのアカウントが既にある方はbot作成のためのIAMユーザーを作成することをおすすめします。(AWSのアカウント登録及びIAMユーザー作成については割愛させていただきます。詳しくはアカウント作成ハンズオンを参照してみてください。)
画面上部の検索窓からLambdaと入力して、Lambdaのサービス画面に遷移します。
「関数の作成」をクリックして関数を作成します。
関数の「基本的な情報欄」に下記のように入力して関数を作成してください。

  • 関数名 : event-remind-bot
  • ランタイム : Python 3.9
  • アーキテクチャ : x86_64

続いて今回使用するモジュールrequests_oauthlibをインストールします。
適当なディレクトリを作成し、必要なファイルを作成していきます。
必要なファイルが作成できたらzip化してLambda関数にアップロードします。

mkdir event-remind-bot
cd event-remind-bot
pip3 install -U pip
pip install requests_oauthlib -t ./
touch lambda_function.py
zip -r twitter-bot-hello.zip ./

ここまでできればLambdaのセットアップはとりあえず完了です。
おそらくこの段階で関数を実行するとエラーになるはずです。

関数を作り込んでいく

それでは関数の中身を作り込んでいきたいと思います。
関数の流れとしては

  • API KeyAccess Tokenを使用してOAuthセッションを作成
  • イベントに関する告知ツイートの検索APIの実行(GET)
  • RTのために必要な情報の取得(GET)←RT APIの実行時にはRTするアカウントのidが必要
  • 「告知ツイート」が存在する場合そのツイートをRT(POST)
  • 「告知ツイート」が存在しなかった場合定型文をツイートする(POST)

それではまず手順1からやっていきます。
API KeyAccess Tokenはコード内にハードコードするべきではございません。
そのためParameter Storeを使用してクレデンシャルをそこから読むようにします。

import boto3
ssm_client = boto3.client('ssm')
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)

Parameter Storeの画面に遷移して、「パラメーターの作成」をクリック。
パラメータの名前を /credentials/twitter とします。また、SecureString として格納したいので、タイプを 安全な文字列 としてください。
Parameter Storeに入れるクレデンシャルはjson形式で登録します。
入力できたら「パラメータを作成」をクリック

{
  "consumer_key": "XXXXXXXXX",
  "client_secret": "XXXXXXXXX",
  "access_token": "XXXXXXXXX",
  "access_token_secret": "XXXXXXXXX"
}

Parameter Storeの登録が完了したのであれば、Lambda関数に適切な権限を紐づける必要があります。
Lambda 関数 twitter-bot-hello の上部までスクロールし、タブを「設定」に切り替え、左側に表示されるメニューから「アクセス権限」を選択してください。すると、実行ロールが表示されるので、ロール名のリンクをクリックしてください。
アクセス許可を追加」から「ポリシーをアタッチ」を選択してください。
上部の検索窓に「SSMRead」と入力し、エンターを押してください。表示される「AmazonSSMReadOnlyAccess」の左側にチェックを入れ、「ポリシーをアタッチ」してください。

次に検索APIの実行を行います。
search APIでは検索するためのクエリーと様々なオプションを設定することができます。
今回はfrom:で誰のツイートかという条件とツイート内に「イベント名」が含まれることを条件に加えています。
それからオプションにstart_timeを指定して当日中にツイートされたものに絞り込んでいます。
これでツイートを検索することが可能になります。

def search(start_time):
    query = 'from:sample_event イベント名'
    params = {
        'query': query,
        'start_time': start_time,
    }
    response = oauth.get(
        "https://api.twitter.com/2/tweets/search/recent",
        params=params
    )
    return response.json()
    if response.status_code != 200:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )

そして次はRTに必要な情報を取得します。
RT API RTするアカウントのidを含める必要があるため自身の情報を事前に取得しなければなりません。

def getMe():
    response = oauth.get(
        "https://api.twitter.com/2/users/me"
    )
    
    return response.json()


最後に取得した情報を使用して RT or Tweet を実行します。
Tweet APIを実行する際の注意点として同じ内容のツイートをすることはできません。
なので今日の日付を取得してツイート文に含めています。
自身のidはリクエスト内に含める必要があります。

def retweetOrTweet(result, meInfo):
    if ('data' not in result):
        now = datetime.now(JST).strftime("%Y年%-m月%-d日")
        text = '本日('+now+')のイベントはお休みです'
        
        payload = {'text': text}
        response = oauth.post(
            "https://api.twitter.com/2/tweets",
            json=payload,
        )
    else:
        match_tweet = result['data'][0]
        payload = {'tweet_id': match_tweet['id']}
        oauth.post(
            "https://api.twitter.com/2/users/"+meInfo['data']['id']+"/retweets",
            json=payload,
        )


ここまでのコードをまとめるとlambda_function.pyは下記のようになるかと思います。

import json
import os
import boto3
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta, timezone

JST = timezone(timedelta(hours=+9), 'JST')
ssm_client = boto3.client('ssm')

oauth = None

def handler(event, context):
    init()
    
    now_utc = datetime.now()
    yesterday = now_utc - timedelta(1)
    yesterday_str = yesterday.strftime('%Y-%-m-%-d')
    start_time = yesterday_str + 'T15:00:00Z'
    result_json = search(start_time)
    meInfo_json = getMe()

    retweetOrTweet(result_json, meInfo_json)

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 search(start_time):
    query = 'from:sample_event イベント名'
    params = {
        'query': query,
        'start_time': start_time,
    }
    response = oauth.get(
        "https://api.twitter.com/2/tweets/search/recent",
        params=params
    )
    return response.json()
    if response.status_code != 200:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )
        
def getMe():
    response = oauth.get(
        "https://api.twitter.com/2/users/me"
    )
    
    return response.json()
    
def retweetOrTweet(result, meInfo):
    if ('data' not in result):
        now = datetime.now(JST).strftime("%Y年%-m月%-d日")
        text = '本日('+now+')のイベントはお休みです'
        
        payload = {'text': text}
        response = oauth.post(
            "https://api.twitter.com/2/tweets",
            json=payload,
        )
    else:
        match_tweet = result['data'][0]
        payload = {'tweet_id': match_tweet['id']}
        oauth.post(
            "https://api.twitter.com/2/users/"+meInfo['data']['id']+"/retweets",
            json=payload,
        )


関数が完成したのであればzip化してLambda関数にセットします。

スケジュールを設定する

関数が完成したのでこの関数を定期的に実行するために Event Bridgeを使用してイベントを作成します。
Lambda関数画面の「トリガーを追加」ボタンを押下します。

プルダウンから EventBridge (CloudWatch Events) を選択し、

  • 新規ルールの作成
  • ルール名 remind-tweet
  • ルールタイプ スケジュール式
  • スケジュール式 cron(50 9 ? * SUN *)

と追加してください。
cron 式は cron(分 時間 日 月 曜日 年) として表されます。上記のcron式は毎週日曜日9:50(UTC)に実行されることを意味しています。ここで注意していただきたいのが、UTCで実行されるという部分です。UTCとは協定世界時になります。つまり日本標準時(JST)との時差を考慮しなければならないことになります。イベントは19時から行われます。その10分前に通知したい場合にはそこから9時間遡って9:50で指定すればよいというわけです。

さて、ここまで無事に進めることができれば、問題なくbotが動作すると思います。
お疲れ様でした。

参考文献

お役立ち Twitter Bot を作りながら学ぶ AWS ドリル ~第 1 回 おはよう Bot 編
Twitter API公式doc

まとめ

今回はTwitter API とAWS Lambda を使用してイベント通知botを作成してみました。
今回作成したbotはかなりシンプルなものですが、
Twitter APIもLambdaもとても奥が深いものだと思いますので、
みなさんもぜひ研究してみてはいかがでしょうか?