Cloud Function で、Retweetした人全員(無料プランなので1週間前まで)を取得してみた
概要
とあるツイートをリツイートした人を取得したかった。
今回は、全部Firebaseで行こう!ってなってたので、Cloud Functions でやってみました。
参考
Firebase Cloud Functions で Twitter API と連携してみる - Qiita
Firebaseを駆使してTwitterをbot化してみた - Qiita
[Twitter API] リツイートを100件より多く取得する方法 – プログラミング生放送
やること
twitterAPIの準備
Twitterのアクセスは、twitを利用しました。
GitHub - ttezel/twit: Twitter API Client for node (REST & Streaming API)
twitterの情報は、上の記事にあるように、configに入れて使ってます。
TweetIdからテキストを取得
こちらのAPIを利用すれば、TweetIdから取得できるのですが、最新100件までしか取得できません。
GET statuses/retweeters/ids — Twitter Developers
そのため、今回は、下記を使ってます。
Standard search API — Twitter Developers
テキストを用いて、Retweetしたユーザーを取得
だいたい先頭100文字が一致してれば、対象のTweetをRetweetしたTweetだと判断して、先頭100文字で検索しています。
上の記事では、filter:retweets 条件を追加していましたが、下記の理由で外しました。
search API は、100件取得できなくても、それより古いRetweetがある可能性があります。
そのため、100件以下しかRetweetが取得できなくても、もう一回叩いて、古いRetweetがないかを確認しないといけません。
そうすると、2倍呼び出すことになり、APIの制限にかかる可能性が上がります。
一方、filter:retweets を外すと、対象のTweetが取得できることになります。
対象のTweetが取得できたということは、それより古いRetweetが存在しないということなので、APIの呼び出しが1度で済みます。
そのため、対象のTweetを取得するために、filterを外しました。
検索条件には、since_idとmax_idを入れていますが、これは上の記事に説明があります。
また、計算時、BigIntに型変換しているのは、twitterIdは下記記事にあるように、通常のInt型ではおさまらないためです。 TwitterIDが33bitになったため、値をINT型に入れているとエラーに。
こんな感じで、Retweetした全てのユーザーを取得できると思います!
下に実際のプログラムを記載してますが、記事に載せるように変更した部分あるので、うまく動かなかったらすいません...
プログラム
import * as functions from 'firebase-functions'; import * as twit from 'twit'; const twitter = new twit( { consumer_key: functions.config().credential.twitter.consumer_key, consumer_secret: functions.config().credential.twitter.consumer_secret, access_token: functions.config().credential.twitter.access_token, access_token_secret: functions.config().credential.twitter.access_token_secret, } ); // TweetIdから、Retweetされた文字を取得する。 const fetchRetweetList = async (tweetId: String, latestTweetId: String): Promise<any[]> => { // TweetIdから、TweetのTextを取得する。 const text = await twitter.get('statuses/show/:id', { id: tweetId, include_entities: false }).catch(err => { console.log('caught error', err.stack) }) .then((result: any) => { if (result === undefined) return ''; return result.data.text; }); if (text === '') return []; // since_id に前回最新だったTweetIdを取得し、1引くことで、前回最新だったTweetを含む、新しいTweetを取得するようにする。 // TweetIdの桁数が大きいので、BigInt型を利用する。 const params = { q: `${text.slice(0, 100)}`, count: 100, since_id: String(BigInt(latestTweetId) - BigInt(1)) }; let isSearchContinue = true; let maxId = ''; let results: any[] = []; // 取得したことがない全てのReTweetを取得する。 // 一度に100件までしか取得できないので、max_idを更新しながら、whileを続けていく。 while (isSearchContinue) { // max_id にIDを指定すると、それより古いTweetを取得できる。(指定したIDを含むっぽい) const queryParams = maxId ? Object.assign(params, { max_id: maxId }) : params; // 100件取得できない場合が多い...鍵付きユーザーのためか? const tmp = await twitter.get('search/tweets', queryParams) .catch(err => { console.log('caught error', err.stack) }) .then((result: any) => { const searchList = result.data.statuses; // 50件以下の検索結果の場合は、流石にこれ以上古い結果はないだろうということで、次の検索を行わない。 // もっといい方法あるかも。もしかしたら、無限ループ陥る可能性もあるかもしれないので、もしコピーするときは注意してください。 if (searchList.length < 50) { isSearchContinue = false; } // 次の検索の時に、基準とするIDは、一番古いTweetIDより、1古いIDとする。 maxId = searchList[searchList.length - 1].id_str maxId = String(BigInt(maxId) - BigInt(1)); return searchList.map((tweet: any) => { const tweetId = tweet.id_str; // もし前回既に取得したものがあったら、次の検索を行わない。 if (tweetId == latestTweetId) { isSearchContinue = false; } const user = tweet.user; const retweet = tweet.retweeted_status; return { tweetId: tweetId, userId: user.id_str, retweetId: retweet ? retweet.id_str : '' } }); }); results = results.concat(tmp); } // retweetIdが、指定したTweetIdと異なる可能性があるので、除外する。 return results.filter((result: any) => result.retweetId == tweetId); } exports.retweeter = functions.https.onRequest(async (request, response) => { const result = await fetchRetweetList(1, 3); response.send({ data: result }); });
終わり
初めてcloud functionsとか、firestoreとか触りましたが、すごく便利でした!