はじめに
TIL(Today I Learned): 今日学んだこと
をtwitterのつぶやきで行おうと思いました
-
#til
というハッシュタグで学びをつぶやく - 1日1回自分の投稿の
#til
ハッシュタグを拾いに行く - GitHubにコミット
というのがいけないかな〜と思ったのがきっかけです
AWSで CloudWatch Events
+ Lambda Functions
あたりでできそうな気がしたのでやってみます
参考
準備
- AWSアカウント
- Twitter開発者アカウント
- TILをコミットしていくGitHubリポジトリ
Twitter APIを利用するためには開発者アカウントの登録が必要です
※ 利用目的とか審査とかあってやや面倒です…
手順
- Amazon CloudWatch Eventsで定期的にAWS Lambdaを起動
- LambdaでTwitter APIを叩いて
#til
ツイートを取得 -
#til
ツイートがあればGitHub APIを叩いてコミットする
アプリケーションの動きとしては上記の流れを想定します
なのでやらないといけないことは、
- Lambda Functionsの作成
- Twitter APIを叩いて
#til
ツイートを取得するjsを作る - GitHub APIを叩いてコミットするjsを作る
- Lambdaに登録する
- Twitter APIを叩いて
- 定期的にLambda Functionsを実行するCloudWatch Eventsの作成
という感じになります
1. Lambda Functionsの作成
最終的にはこんなjsができあがりました
色々詰め込み過ぎだし改善の余地は大いにありそうですが、中身を見ていきます
1-1. Twitter APIを叩いて #til
ツイートを取得する
Twitter開発者アカウントを登録してアプリ作成を行うと、以下の値が得られます
- Consumer API key
- Consumer API secret key
- Access token
- Access token secret
これらを環境変数から設定し、twitterモジュールを利用します
const Twitter = require('twitter');
const twitterClient = new Twitter({
consumer_key: process.env.API_KEY,
consumer_secret: process.env.API_SECRET,
access_token_key: process.env.ACCESS_TOKEN,
access_token_secret: process.env.ACCESS_TOKEN_SECRET,
});
あとはTwitter APIのドキュメントを見ながら、特定のツイートを取得するように実装します
const nowString = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate() - 1}`;
const params = {
q: `(from:${process.env.ACCOUNT_NAME}) since:${nowString} ${TARGET_HASHTAG}`,
count: process.env.MAX_COUNT || 5,
};
const tweets = await twitterClient.get('search/tweets', params).catch(() => []);
ACCOUNT_NAME
は自分のTwitterアカウントを環境変数で設定します
最終的には毎日日付が変わったタイミングぐらいで実行させるので、前日以降のツイートに絞っています
※ jsの日付処理貧弱すぎない?みんなmoment.jsとか使うの?
実際はこのあとに各ツイートのハッシュタグを厳密にチェックしてますが、だいたい↑ぐらいでお目当てのツイートは取得できるはずです
1-2. GitHub APIを叩いてPRを作成する
これが地味に大変だった…
CLIなら git add
して git commit
して git push
するだけのかんたんなお仕事ですが、APIでやろうとするとある程度踏み入った理解が必要になります
流れとしては blob
を作って tree
を作って commit
を作って HEADのSHAを書き換える
ということになります
const prevRefsHead = await getRefsHead();
const commitSha = prevRefsHead.object.sha;
const prevCommit = await getCommit(commitSha);
const blob = await postBlob(data[i]);
const tree = await postTree(prevCommit.tree.sha, blob.sha, i + 1);
const commit = await postCommit(commitSha, tree.sha, i + 1);
await patchRefsHead(commit.sha);
await sleep(1);
中身はそれぞれ対応したAPIを実行しているだけですが、こちらもドキュメントとにらめっこしながらパラメータと流れを調節しました
※ 連続して実行したときにコミットがうまくいかないことがあったので、1件ずつsleepするようにしてます
1-3. Lambdaに登録する
jsは何度もお試し実行すると思うので、ローカルでDockerとかで書くのがよいです
jsができあがったらLambdaに登録していきます
注意点として、Lambda上で外部モジュールは基本的にそのまま require
することはできません
今回でいうと
const Twitter = require('twitter');
ですね
これは事前に Lambda Layers
として作成しておくことで解決できます
zipファイルを直接アップロードするか、 S3
に上げておいてそれを利用することができます
zipファイルの中身は注意が必要で、例えば modules.zip
を解凍したときに以下の構成になっている必要があります
$ ls -1 modules/
node_modules/
node_modules
の中に利用したいモジュールが入っているイメージです
この構成になっていないと、Lambda Functionsの方で利用するのにうまくいきません
先にLambda Layersを作ったら、Lambda Functionsを作成していきます
ランタイムはLambda Layersと同じになるようにします
Lambda FunctionsもzipファイルをアップロードしたりS3のファイルを利用することができますが、今回はそのまま作成したコードを貼り付けます
環境変数をたくさん使うので、ぽちぽち登録します
タイムアウト設定をデフォルトの3秒 -> 30秒に変更しておきます
最後にLambda FunctionsにLambda Layersを追加すればOKです
右上から適当なテストイベントを作ってテストしてみて、無事に動けばLambdaとしては完成です
2. 定期的にLambda Functionsを実行するCloudWatch Eventsの作成
以上
簡単に設定できました
Cron式については、日付が変わったころに前日分のツイートを取得するような感じで実行されるようにします
CloudWatch EventsのCron式で実行されるイベントは、UTCで誤差1分以内だそうです
前日に #til
ツイートをしていれば、日本時間でだいたい翌日の朝9時頃にコミットができあがる想定ですね
実行結果
コミットされました
これで無事にツイートするだけで草が生える環境ができあがりました
学び
- node_modulesを
Lambda Layers
に追加しておくことで、Lambda Functionsで使えるようになって便利 - Gitのコミットができるまでの流れ 10.2 Git Internals - Git Objects
-
CloudWatch Events
のスケジュールでCron式を使う場合、日と曜日のどちらかは?
にする必要がある
Todo
- Lambda FunctionsとLambda Layersへのデプロイを
GitHub Actions
で自動化したい人生だった… - GCPで
Cloud Functions
とCloud Scheduler
でも似たようなことができそうなのでやってみたい
まとめ
GitHubの草が生えているからといって活発に開発をしているとは限らないぞ