【AWS入門】電車遅延をSMS通知してくれるアプリを作成した(TwitterAPI利用)

2022/6/17

(最終更新: 2022/6/17

【AWS入門】電車遅延をSMS通知してくれるアプリを作成した【TwitterAPI】

はじめに

AWS初心者のはち子です。普段の業務では、AWSシステムの要件定義/企画/開発をしています。

プログラミングの勉強は自分で好きなアプリを作るのが一番ということで・・・

普段の生活の不満を解消するアプリをAWS Lambda / Node.jsを使って作りました

所要時間は、休日を利用し丸4日ほどです。

この記事では、私と同じようにAWS勉強中の方が、Lambdaでアプリを作ってみようかなと思った時の参考になるよう、実施内容を共有します。

自分で考えたものが動くのはとても楽しいので、ぜひ本記事を応用して、何か作ってみてください!

どんなアプリケーション?

Twitterを監視し、特定のキーワードが急増したらスマホに通知してくれるアプリケーションです。

自分の通勤路線は人身事故が多く、朝、駅に行ってみると運転を見合わせていた・・・ということがしばしばあります。家を出る前に、この状況に気づければ、会社に早々に連絡して、テレワーク宣言をして、ゆっくり朝ごはんを食べることができます。

そこで、Twitterで最寄り路線に関するツイートが急増した場合に通知をしてくれるアプリを作成しました。

こんな感じで、15分おきにジャカジャカ通知してくれます。 SMS通知の様子

利用する技術

AWSのマネージドサービスを使います。

  • AWS Lambda ー サーバレスでアプリを実行するために利用します。言語はNode.jsを使います。
  • Amazon SNS - SMSを飛ばすために使います。
  • Amazon EventBridge ー Lambda関数を定期的に自動実行するために使います。

AWS以外では以下の技術を使います。

  • Node.js ー バックエンドをJavaScriptで書けるようにした言語。Lambdaでは、一般的にソースコードはPythonかNode.jsで書きます。
  • Twitter API ー Twitter社がREST-APIを提供しており、好きなクエリを作ってツイート情報を取得することができます。

システム仕様

システム構成図

システム構成図

システム仕様

  • 「山手線」という言葉が含まれているツイートを検索します。
    • 現在の時刻~30分前のツイートを取得します。(例:2022/6/19 19:00~19:30)
    • 昨日の、今の時刻~30分前のツイートを取得します。(例:2022/6/18 19:00~19:30)
  • 今日のツイート数が昨日の同じ時間のツイート数の5倍以上の場合、SMS通知を行ないます。
  • この処理を15分おきに行ないます。

残念ながら山手線沿線に住んでいるわけではないです

TwitterAPIでツイート数を取得する

ここから実際の開発に入ります。

TwitterAPIのDeveloper申請

TwitterのAPIを利用するには、Developer申請して、アクセスキーを手に入れる必要があります。

方法は以下記事にまとめています。

TwitterAPIを使うための開発者アカウント審査に合格した

TwitterAPIを使うための開発者アカウント審査に合格した

TwitterAPIを利用するためには、開発者アカウントとして申請をして、Twitter側に審査してもらう必要があった。手順をメモ。

https://bunsugi.com/twitter-developer-examination

TwitterAPIの叩き方

このアプリでは、Tweet counts APIという、期間を指定して特定のツイート数を確認できるAPIを利用します。

参考資料

https://developer.twitter.com/en/docs/twitter-api/tweets/counts/api-reference/get-tweets-counts-recent

Twitter APIの叩き方は以下の記事にまとめています。

試しにcurlコマンドで叩いてみましょう。 GETメソッドを利用します。 ※curlコマンドだと日本語のコマンドが簡単に扱えないので、ここではquery=JRとします。

$ curl --request GET 'https://api.twitter.com/2/tweets/counts/recent?start_time=2022-06-17T10:40:00.000Z&end_time=2022-06-17T10:45:00.000Z&granularity=day&query=JR' --header 'Authorization: Bearer {Bearer Tokenの値}'

Bearer Tockenの値の取得方法はAPIアクセスキーをメモする。を参照してください。

応答が返却されます。

{
    "data": [
        {
            "end": "2022-06-17T10:45:00.000Z",
            "start": "2022-06-17T10:40:00.000Z",
            "tweet_count": 478
        }
    ],
    "meta": { "total_tweet_count": 478 }
}

tweet_countでツイート数を取得できます。ツイート数が増えたら通知する仕組みを作ります。

Node.jsでアプリを作成する

開発環境

  • VurtualBox 6.0
  • オペレーティングシステム Ubuntu(64-bit)

初期設定

ローカルでNode.jsアプリを作成します。

$ npm init -y
$ touch index.js
$ npm install

GitHubとも連携しておきます。

$ git init
$ git remote add origin git@github.com:<ユーザ名>/<リポジトリ名>.git
$ git branch -m main
$ git add .
$ git commit -m "initial commit"
$ git push origin main

モジュールインストール

3つのモジュールが必要です。

  • axios: REST-API(Twitter API)を叩くために必要です。
  • dotenv: 環境変数を利用するために必要です。Twitter APIのアクセスキーは環境変数として管理します。
  • @aws-sdk/client-sns:SNSを関数から操作するために必要です。
$ npm install axios dotenv @aws-sdk/client-sns

TwitterのAPIから情報を取得する関数

utils.jsのファイルを作成して、getTweetCount関数を作成します。

開始時刻と、終了時刻と、検索文字列を与える関数にします。

utils/utils.js
const axios = require("axios");
require("dotenv").config();

module.exports.getTweetCount = async (startTime, endTime, query) => {
    // startTimeの書式:"2022-05-12T22:00:00.000Z"
    // endTimeの書式:"2022-05-13T00:00:00.000Z"
    // queryの書式:"池袋線"

    const url = "https://api.twitter.com/2/tweets/counts/recent";

    const granularity = "day";
    const token = process.env.TOKEN;

    console.log(`startTime:${startTime}`)
    console.log(`endTime:${endTime}`)
    
    const req = encodeURI(
        `${url}?query=${query}&start_time=${startTime}&end_time=${endTime}&granularity=${granularity}`
    );

    try {
        const res = await axios.get(req, { headers: { Authorization: `Bearer ${token}` } });
        return res.data;
    } catch (error) {
        console.log(error);
    }
};

トークンの取得

Twitter APIにはAPIアクセスキーが必要ですが、秘密情報はGitHub等にpushしてはいけないので、環境変数化します。

.env
TOKEN=Twitterから払いだされたAPIキー

.gitignore.envが含まれることを忘れずに確認する。

dotenvモジュールを使って環境変数を利用できます。

axiosを利用したAPI要求

axiosモジュールを利用します。

axios.get(<APIURL>, <ヘッダーなどのconfig>)

という構文になっています。

JavaScriptの非同期関数

axiosを利用する場合は、関数全体をasyncで囲みます。 get文を投げる処理にはawaitを付けます。

こうすることで、APIから情報を取得し終わる前にreturnしてしまうことを防げます。

TwitterAPIにリクエストする情報の準備

開始時刻と終了時刻を取得して、getTweetCountに渡します。 今日のツイート数と昨日のツイート数を取得するので、getTweetCountは2回実行します。

※きれいなコードではないので参考程度に。

index.js
const { setDateTime, getTweetCount } = require("./utils/utils");

const index = () => {
    const now = new Date();
    const now1mAgo = setDateTime(now, 0, 1);
    const yesterday = setDateTime(now, 1, 1);
    const now30mAgo = setDateTime(now, 0, 31);
    const yesterday30mAgo = setDateTime(now, 1, 31);

    const todayData = await getTweetCount(
        now30mAgo.toISOString(),
        now1mAgo.toISOString(),
        "山手線"
    );
    const yesterdayData = await getTweetCount(
        yesterday30mAgo.toISOString(),
        yesterday.toISOString(),
        "山手線"
    );

    const todayCount = todayData.data[0].tweet_count;
    const yesterdayCount = yesterdayData.data[0].tweet_count;

};

Twitter APIに入れる時刻パラメータはISO 8601形式にする必要があるため、toISOString()を利用して変換しています。

utils.jsの中にsetDateTime関数を追加します。 現在時刻から、dayの日付分、miniteの分数分ずらした時刻オブジェクトを取得する関数です。

utils/utils.js
module.exports.setDateTime = (datetime, day = 0, minute = 0) => {
    return new Date(
        datetime.getFullYear(),
        datetime.getMonth(),
        datetime.getDate() - day,
        datetime.getHours(),
        datetime.getMinutes() - minute,
        datetime.getSeconds(),
        datetime.getMilliseconds()
    );
};

今日のツイート数は、「30分前~現在時刻」にしたかったのですが、現在時刻にすると

"Invalid 'end_time':'2022-06-19T10:45Z'. 'end_time' must be a minimum of 10 seconds prior to the request time."

というエラーが発生します。

ent_timeは現在から10秒以上前にする必要があります。 そこで今回は1分前~31分前のツイートを取得する方法をとります。

カウント数は、オブジェクトから以下のように取り出すことができます。

const todayCount = todayData.data[0].tweet_count;
const yesterdayCount = yesterdayData.data[0].tweet_count;

条件式に従ってSMS通知(Amazon SNS)

sns関数を作成し、aws-sdkの記法に従ってメッセージの送信命令を出せるようにします。

TwitterからSMSを送る方法は以下の記事にまとめています。

utils/sns.js
const REGION = "ap-northeast-1" //e.g. "us-east-1"
const { SNSClient, PublishCommand  } = require("@aws-sdk/client-sns") // npm install @aws-sdk/client-sns が必要

module.exports.sns = async (todayCount, yesterdayCount) => {
  
  const client = new SNSClient({ region: REGION })

  const params = {
    Message: `ツイート数が増えました。今日のカウント数:${todayCount}。昨日のカウント数:${yesterdayCount}` ,
    TopicArn: "arn:aws:sns:ap-northeast-1:<AWSアカウント>:ay-s-topic-test",
  }
  const command = new PublishCommand(params)

  try {
    const data = await client.send(command)
    console.log(data)
  } catch (error) {
    console.log(`error: ${error}`)
  } finally {
  }
}

sns関数をindex.jsから呼び出します。

index.js
const { sns } = require("./utils/sns"); // ロール設定忘れずに。const { setDateTime, getTweetCount } = require("./utils/utils");

const index = async () => {
    const now = new Date();
    const now1mAgo = setDateTime(now, 0, 1);
    const yesterday = setDateTime(now, 1, 1);
    const now30mAgo = setDateTime(now, 0, 31);
    const yesterday30mAgo = setDateTime(now, 1, 31);

    const todayData = await getTweetCount(
        now30mAgo.toISOString(),
        now1mAgo.toISOString(),
        "山手線"
    );
    const yesterdayData = await getTweetCount(
        yesterday30mAgo.toISOString(),
        yesterday.toISOString(),
        "山手線"
    );

    const todayCount = todayData.data[0].tweet_count;
    const yesterdayCount = yesterdayData.data[0].tweet_count;
    
    console.log(`今日のカウント:${todayCount}`);
    console.log(`昨日のカウント:${yesterdayCount}`);

    if (yesterdayCount !== 0) {
        const growthRate = todayCount / yesterdayCount;
        if (growthRate > 5) {
            await sns(todayCount, yesterdayCount);        }
        return growthRate;
    } else {
        return 0;
    }
};

module.exports.handler = index;

このとき、

module.exports.handler = index;

はLambdaでindex関数を実行するための記述です。

非同期処理

client.send(command)

の処理にはawaitを付けます。そのため関数全体もasyncで囲む必要があります。

メッセージの内容

メッセージに、今日のツイート数と昨日のツイート数を出力したかったため、todayCountyesterdayCountを引数で受け取っています。

LambdaでNode.jsアプリを実行する

Lambda関数を作成し、作成したソースコードをアップロードします。

具体的なやりかたは以下の記事を参照。

Lambda関数にIAMロールを設定するのも忘れないようにします。

EventBridgeを使ってLambdaを定期実行する

AWSのマネジメントコンソールより、EventBridgeのルール作成します。

EventBridge設定

Lambda関数にトリガーとして設定します。

Lambda関数にトリガーとして設定します

具体的なやり方は以下の記事にまとめています。

実行結果の確認

最寄りの路線(ここでは山手線)関連のツイートが増えると、スマホに通知が来るようになります。

人身事故があったときは、通知が15分置きに来ます。

SMS通知の様子

2日連続で人身事故があったら通知されないじゃないか、とか、課題はいろいろありますがひとまず完成です。

つまづいたところ

簡単なアプリですが、、全ての工程でつまづきました。

  • TwitterAPIを使う準備
    • 開発者申請が必要
    • ISO 8601形式の時刻指定を間違えた(協定世界時(UTC)と日本標準時(JST))
    • 環境変数の勉強が必要だった
  • Lambda関数を実行するところ
    • 外部モジュールの入れ方がわからない
    • イベントハンドラ(module.exports.handler)が無い
    • 環境変数を間違えた
  • SNSでショートメールを送るところ
    • IAMロールが設定できていない問題
    • Lambdaがタイムアウトする問題
    • ES6記法が使えない問題
    • SMSの使用上限がデフォルトで月1ドルだった問題

など。

まとめ

本記事では、Twitterのツイート数から電車遅延を検知し、通知するアプリをAWS Lambda / Node.jsを使って作りました。

AWSどころか、個人開発自体も初心者なので、まずは簡単なところから実施してみました。 勉強中の方は一緒に頑張りましょう!

間違いなどあればご連絡いただければと思います。



個別連絡はこちらへ→Twitterお問い合わせ

プロフィール

プロフィールイメージ

はち子

事業会社のシステム部門で働きはじめて5年目の会社員。システム企画/要件定義/システムアーキテクチャ等。

Twitter→@bun_sugi

過去の記事について

はてなブログに掲載の記事(主にプログラミングメモ)についてはこちらに掲載しております。(本ブログに移行中)

タグ一覧

関連記事

Copyright© 2023, エンジニアを目指す日常ブログ

お問い合わせ|プライバシーポリシー