はじめに
AccountActivityApiがなんぞやという方はぐぐってね。
AccountActivityApiが生まれた経緯には諸説ありますが、まあそんなことは置いておいて、ちょっと使ってみた話です。
さて本来ならwebhookなのでそれを受け取るためのサーバが必要となります。ですがちょっと試すだけにサーバ立てたりするのも面倒だなーAWSの諸々使えばサーバレスにできるよね? ということでやってみました記事です。
やりたいこととしては、サーバレスで受け取ってKinesis FirehoseからS3に流す、という感じです。
手順
- twitterのデベロッパアカウントに登録するよ
- twitterアプリの設定やAccountActivityApiのセットアップするよ
- twitterにwebhookのサーバを登録するよ(CRCを通すよ)
- webhookで見るアカウントを登録するよ
twitterのデベロッパアカウントを登録する
https://developer.twitter.com/en/apply から登録!
twitterアプリの設定やAccountActivityApiのセットアップ
まずtwitterのアプリを作ります。これに関しては特別なことはありませんので適当に入力してサクッと作っちゃいましょう。
作成後、アプリケーションの詳細画面に行き、トークンの発行とpermissionを設定する必要があります。
permissionsの設定
Permissionsの項目から、AccessのうちRead,Write and Access direct messagesを選択しupdate settingsを行ってください。この設定をおろそかにすると、いざ自分を対象に下記のアクセストークンを使ってsubscribeしようとすると、エラーが返ってきます。subscribeにはtwitterの通知イベントの全てが含まれるので、direct messageへのアクセス権限も必要です。
もし先にaccess tokenの発行を行ってしまった場合は、一度自分のtwitterアカウントの連携アプリケション認証から該当アプリを削除し、Access Tokenを作り直す必要があります。
トークンの発行
発行が必要なトークンは2種類4つです。Keys and Access Tokensへ行きGenerate Consumer Key and Secretというボタンを押してください。これによりConsumer KeyとConsumer Secretが発行されます。
次にその下にあるAccessTokenを発行してください。同じようなボタンがあるのでポチッと押すとAccess TokenとAccess Token Secretが発行されます。この四つを、この後のAPIでの認証に使います。参考にしようとした記事では、ConsumerKeyとConsumerSecretからBearerTokenと呼ばれるものを作れば認証できるとあったのですが、正式版になってから仕様が変わったのか、BearerTokenでの認証ができなくないものがあるようです(公式リファレンスを参照すると、これらのトークンを全て使い認証する必要があるとありました)。
AccountActivityApiのセットアップ
twitterのdeveloperサイトに行くと、以下のような画面が出てきます。
ここの項目のうち、AccountActivityApiは一番下の項目ですね。環境名は適当で構わないので、好きなものを入力してしまいましょう(ここの環境名を控えておく必要があります。apiのURLに含まれるようになります)。なお無料枠では環境は一つしか作れないようです。
Webhookのサーバを登録する
さて、いよいよ本題です。webhookのサーバを登録するには、twitterのapiから登録する必要があります。また、webhookのサーバを登録すると、CRC(Challenge-Response Checks)が走ります。twitterからあるリクエストが届き、それに対し正しいレスポンスを返せばokです。twitterからのサーバが正しいものかの認証だと考えれば良いと思います。ちなみにこれからのAPIを叩く作業はローカルマシンで構いません。
さきに登録するwebhookのエンドポイントを作っておきましょう。APIGatewayに適当にAPIを作っておきましょう。エンドポイントは一つで大丈夫です。
まずはwebhookを登録する手順です。以下に対しpostリクエストを送ります。その際にOAuthの認証パラメータをheaderに含める必要があります。ここで、発行したトークンの出番です。
method: POST
API: https://api.twitter.com/1.1/account_activity/all/[env_name]/webhooks.json?url=[webhookのURL]
header:
Authorization: OAuth [OAuth parameters]
OAuthには以下の項目が必要になります。
oauth_token: 取得したアクセストークン
oauth_consumer_key: 取得したコンシューマキー
oauth_signature_method: HMAC-SHA1(固定)
oauth_timestamp: unix時間が入ればいいらしい。phpならtime()でok
oauth_nonce: ランダムな文字列。毎回変更されれば良い
oauth_version: 1.0(固定)
oauth_signature: コンシューマシークレットとアクセスシークレットトークンを使用したシグネチャ
このうちoauth_signatureが非常に面倒でした。こちらを参考にするとよいです(https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html )。Collecting parametersを見ると、oauth_signatureの作り方が順を追って書いてあります。以下に抜粋します。
- Convert the HTTP Method to uppercase and set the output string equal to this value.
- Append the ‘&’ character to the output string.
- Percent encode the URL and append it to the output string.
- Append the ‘&’ character to the output string.
- Percent encode the parameter string and append it to the output string.
ガバガバ意訳は以下のような感じかな?(以下におけるパーセントエンコードはこちら https://developer.twitter.com/en/docs/basics/authentication/guides/percent-encoding-parameters.html )
- HTTPメソッドを大文字にしたものをまず配置します
- &をつけます
- パーセントエンコードしたurlをくっつけます
- &つけます
- パーセントエンコードしたパラメータをくっつけます
つまり順にパラメータをつないでってねってことだと思われます。5のパラメータに関しては同じページのCollecting parametersで解説しています。同じように訳そうかと思ったのですが、面倒なので一口に言ってしまうと、パーセントエンコードしたパラメータをkey名でアルファベット順にソートして、key1=value1&key2=value2&...のようにつないだものを用意してください、って感じです。またこのパラメータには、getパラメータとかpostパラメータも含まれるっぽい(There are two such locations for these additional parameters - the URL (as part of the querystring) and the request body. )。
POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Faccount_activity%2Fall%2F[env_name]%2Fsubscriptions.json&oauth_consumer_key%3D[encoded_consumer_key]%26oauth_nonce%3D0.00797400%25201528773325%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1528773325%26oauth_token%3D[encoded_access_token]%26oauth_version%3D1.0%26url%3D[encoded webhook url]
で、これをsigning keyを使ってHMAC-SHA1でhash化する。signing keyはお互いパーセントエンコードしたものを&でつないだものを使う。で、最終的に[OAuth parameters]は以下のような感じになる。
oauth_consumer_key=[consumer_key],oauth_nonce=0.70191400+1528271379,oauth_signature_method=HMAC-SHA1,oauth_timestamp=1528271379,oauth_token=[aouth_token],oauth_version=1.0,oauth_signature=[上記で作ったシグネチャデータ]%3D
上記で作ったものを送信すると、認証がokなら指定したwebhook urlにCRCが始まる。
(ここまでやって、これphpのaouth認証ライブラリでも使えばよかったんじゃないかなあと思い当たる。みんなは楽しようね!)
CRC(Challenge-Response Checks)
これまた面倒臭いことをやらねばなりません。要件としては以下です。
- twitterから指定したurlに対しGETメソッドでリクエストが送信される。
- crc_tokenというパラメータが含まれる。
- consumer secretを使ってcrc_tokenをHMAC SHA-256でハッシュ化しろ
- レスポンスは以下のjsonを返せ
- 200のHTTPステータスコードで3秒以内ね
{
'response_token': 'sha256=' + base64.b64encode(sha256_hash_digest)
}
さてこっからはAPIGatewayです。とはいえAPIGatewayを知っている人にとっては難しくないかも。APIGatewayからLambdaに流し作ってもらったら上記のレスポンスをそのまま流すだけです。手順は以下です(なお細かな手順の説明はしません)。
- APIにGETメソッドをつくるよ
- Lambdaに来たパラメータをそのままパスするよ
- Lambdaで上記のレスポンスを作ってAPIGatewayに返すよ
- そのままレスポンスにするよ
Lambdaのコードは以下。
'use strict';
exports.handler = (event, context, callback) => {
// パラメータに含まれるcrc_token取得
const crc_token = event.params.querystring.crc_token;
// ハッシュ化するよ
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'consumer key').update(crc_token).digest('base64');
const calculatedSignature = 'sha256=' + hmac;
const response_token = calculatedSignature;
callback(null, {response_token: response_token});
};
これでwebhookのエンドポイントを登録した際にエラーが返ってこなければokです。
webhookで見るアカウントを登録する
最後にwebhookで通知を受け取りたいアカウントを登録します。これには対象ユーザのアクセストークンが必要です。自分のアカウントでよければ、今まで使ったものと同じものが使えます。ほぼwebhookを登録した際のものを、urlを変更してpostメソッドで送れば良いでしょう。
method: POST
API: https://api.twitter.com/1.1/account_activity/all/[env_name]/subscriptions.json
header:
Authorization: OAuth [OAuth parameters]
[OAuth parameters]はwebhook登録の際と同じものです(正確には少し違います。oauth_tokenが対象ユーザのものになるので、自分以外のユーザを見たい場合は、webhook登録の際の時のoauth_tokenを全て置き換えて考えてください。ちなみにまだ試してないです)。
さて、これで登録は完了しました。
エンドポイントにPOSTメソッドを用意する
さて、これでtwitterからのイベントがjsonで送信されてくるようになりましたので、そのままKinesis Firehoseを経由してS3に流すようにしましょう。APIGatewayからそれらに流す方法はいくらでもテキストが溢れているので割愛。
しかしこれをathenaで見ようとしたら改行されておらず、閲覧できないデータが発生したので改行して保存してもらうようにします。
統合リクエストの本文マッピングテンプレートをちょっといじります。無理矢理改行していますが、なんかいい感じの改行方法があったら教えて欲しいです。。。
#set($tmp = $input.json('$')+'
')
#set($data = $util.base64Encode($tmp))
{
"DeliveryStreamName": "aptwitter-log",
"Record": {
"Data": "$data"
}
}
これで改行もされathenaで見れるようになり万事めでたしです。