最近のこと2025年4月某日
最近のことなど。今回はTech系の話が多いです
X APIのOAuth2 Tokenの有効期限について改めて調べてみた話
まず最初に結論から言うが、調べたのだがよくわからなかった。事の発端は、ここの記事でも書いたが、X APIのドキュメントに以下のような記述が記載されていたことだった:
Access tokens are not explicitly expired. An access token will be invalidated if a user explicitly revokes an application in the their Twitter account settings, or if Twitter suspends an application. If an application is suspended, there will be a note in the Twitter app dashboard stating that it has been suspended
これだけ読むと「明示的に破棄しない限りアクセストークンは期限切れしないよ」って言ってるように読める。同じような記述は別のドキュメントにもあり、:
By default, the access token you create through the Authorization Code Flow with PKCE will only stay valid for two hours unless you’ve used the
offline.access
scope.
ここも「普通はアクセストークンは2時間で期限切れするけどoffline.access
付けてる場合はその限りではないよ」って言っている。上の2つの話を総合すると「offline.access
をスコープに付与してアクセストークンを発行すれば無期限トークンになる」というように読み取れる。少なくともそれを否定する材料に関してこれらのドキュメントから読み取れるものはない。現状は2時間で期限切れするので、それを防止するために10分間隔でリフレッシュトークン使って再発行かけるというなんとも健気なことをやってるのだが、もしかしてそれ必要なくなるのか??と、ちょっと希望をもってここを確認したいと思っていた。
ただ、実際やってみるとわかるけど、アクセストークン発行時にexpires_in:7200
っていうフィールドを持ったレスポンスが返ってくるし、実際アクセストークン発行後2時間以上経過してからその時もらったリフレッシュトークン使ってアクセストークンの再発行をrequestした場合は400エラーになったので(3/10にXがサイバー攻撃でダウンしたときに復旧までに2時間以上を要したため、意図せず検証できた)、上の話と矛盾している。少なくとも現状の俺の理解では 「X APIのOAuth2アクセストークンは2時間で切れる」 という通説は崩れておらず、「10分間隔でリフレッシュトークン使って再発行かける」というワークロードを廃止することはできないと結論づけた。
というわけで個人の視点では結論が出ているのだが、ドキュメントの記述と実態が一致しないのでどれが真実なのかわからない、という状況で冒頭の記述に戻る。本当に有効期限なしのトークンが発行できるなら是非それが欲しいものだが、、、
という話を一応Githubのリポジトリにまとめておいた。
X APIってFree Planでもreadできるのか?っていう話
これも結論からいうが、どうやら2024年11月ごろに仕様変更があったらしく、Free Planでもツイートやアカウント情報のread(参照)ができるようになっていた、らしい。「らしい」というのは公式ドキュメントからその変更履歴に関する通知や案内が見つけられてなくて、これがそもそもイケてない気がするんだが、ググって出てきたブログからの情報のためだ。(そんなんばっかだな)以下とか。
https://programming-zero.net/how-to-start-twitter-api-basic-and-free/
俺の認識だと、イーロン・マスク買収後の数度の改悪騒ぎの後、X APIのFree PlanはWrite(ツイートのPOST)とDelete(ツイートの削除)の権限のみが与えられており、Read(ツイートの参照)は剥奪されたと思っていた。上の記事によると、どうやらこの点が2024年11月の仕様変更で見直されたようで、Free PlanでもReadができるようになったらしい。逆にWrite/Deleteの上限が前より厳しくなったようだが(1日50件→17件)これは個人的には今の所気にするほどでもないが、しかしかなり世知辛い数値だなとは思う。
というか、そもそも「Write/Deleteしかできない(Readはできない)」と公式から明言されていた頃ですら、ツイートのlookup(単一ツイートの内容参照;/2/tweets/:id
)はAPI叩けば実行できるのは知ってたので、
なんか新しいFreeプランのTwitter APIでもTweetの取得(lookup)はできるっぽいんだけどこれそういうもんなのかな?readの権限はなくてwrite権限しか与えられてないんじゃなかったっけか。それともこの辺ソフトリミット(?)で知らずにfreeでlookup使ってるとまたBANされるとか?う~ん??
— RM (@ricemountainer) May 26, 2023
要するに最初から「できた(んだけど言ってないだけ)」のかもしれない。つまりハードリミットじゃなくてソフトリミットだったんじゃないか説を推している。あるいは「なんだ、『できない』とか言ってる割にAPI閉じてないじゃん、じゃどんどんlookupしよ♪」みたいな奴を見つけてBANするための罠だった説。両方を兼ねてるのかもしれないが。とにかく「実は昔からできたんじゃないの?」という話である。まあ、(どうやら)公式に「やっていいよ」と公開された今となってはどうでもいい話だが…
具体的に何ができるのか(できないのか)に関しては、Developers Portalのproductsページで確認はできるのだが、Public Access可能なサイトにはどこにもまとまってない。(これもイイケてないと思う、、、って口開けば文句ばかり出てくるが)ざっと調べた感じでは、参照系のAPI(GET
メソッドのENDPOINT)に関しては、「15分に1request」という制限下で概ね利用可能なようだ。実施可能とされているAPIでそれぞれどの程度の情報が参照できるのか?については調べていない。
今ふと考えているのは、ツイートの参照ができなくなったことでサイトの閉鎖を余儀なくされた「ひなっちのおはっちのサイト」の復活可能性だ。「APIによるツイートの参照」はサイトのデータを作るうえで必須だったので(逆に言えばツイートの参照ができるんだったら今でも継続していた)、実は復活できるんじゃね?説が再浮上している。ただ、1番~5番おはっちまでのリプを参照するのに合計5回API投げなければならないはずなので(この辺は詳しく追ってない)、「単一ツイートの参照が15分に1回」だとすると、「1番おはっちの処理後に(2番おはっちの処理前までの間に)意図的に15分待つ」とかいうなんとも切ない処理を組み入れないといけなくなりそうで、そんなことしてまで復活させたいとは思わないな、、、というのが今の感想。一度sunsetまで持って行って個人的には決着もついたし満足もしているので、積極的に検討したいものではなくなっている。
ま、そんなわけで「Readができる」というのが判明したとしても今の時点では別に新しいアイディアがあるわけでもないな、と思ったという話なのだった。今後何か面白いことが思いついたらやるかもしれない。
ThreadsのOAuth TokenのRevokeの仕方が分からない話
表題の通りなのだが、ThreadsのOAuth TokenのRevokeの仕方が分かっていない。ドキュメント探しても出てこないので、そもそも方法が提供されていない感じがする。つまり、一度Lve-Lived Token発行したら、その有効期限である60日経過するのを待つ以外で任意のタイミングで破棄することができない。のではないか?という予想。60日経つ前にリフレッシュすることはできるのだが、リフレッシュしても前のトークンは生きたままで、有効期限までは利用可能なので、これだと「リフレッシュするたび生きてるトークンがどんどん増産される」だけで、個人的な感覚だとこれはもはや「リフレッシュ」ではない。リフレッシュして新しいトークン取った後、そのトークン使ってAPIコールするのはいいとして、そうなると最早使う必要のない古いトークンは自動で破棄されて欲しい(少なくとも俺は管理したくない)のだが、生きたまま残るんだともはやセキュリティリスクにしかならないという感じで、なんとかして破棄したい(してほしい)とずっと思っていた。
後述するが、これを自動的なワークロードとして組み入れたい(実際、XのOAuth2 Tokenに関しては定間隔でリフレッシュして更新するワークロードを構築済なので、それに倣って同種のものを構築したい)のが発端なのだ。裏で「有効なトークン」が自動的に最新化されていれさえすれば、ThreadsへAPI経由で投稿するにあたって、そのことをいちいち気にする必要がなくなる。毎回OAuth Token発行するのも面倒だしさ。そのために色々探っていた流れでこの疑問に行きついたのだが、例えば仮に30日に1回リフレッシュするとしても、一時的に最大2つの「生きているトークン」が場に現れることになり、あまりやりたくない。それに、60日有効期限のトークンのリフレッシュタイミングが「30日に1回」では少し心もとない(30日に1回のリフレッシュがなんらかの理由でミスすると、次のリフレッシュは確実に失敗する。つまりリフレッシュできるタイミングが実質1回しかないので、1回ミスるとアウト、というのはチャンスが少なく危険、という印象)。できれば1週間に1回とか、もう少し多頻度にしたい。そうなるとその分「有効なトークン」が増えることになり、よりセキュリティリスクが増すのではないか、という懸念がある。リフレッシュのタイミングで古いトークンが死んでくれないのは百歩譲ってまぁいいとしても、だったら任意のタイミングで破棄できる手段が提供されていてほしい。(リフレッシュのタイミングで意図して破棄するから、という意図)
どうやってrevokeすんの?というのが気になり、ネット検索したりChatGPTとかに聴いても答えが得られなかったので、コミュニティで聞いてみた。ただ、Instagram Graph APIでもこのコミュニティ使ったことあるが、「知ってる人がお互い助け合うこと」を前提としたものなので、答えが得られる期待はだいぶ薄い(実際、期待していない)。これはMetaの技術者に直接アプローチできるものではないし、そもそもそういう窓口が用意されていない時点で、この辺りのサポートは実質機能していないという理解でいる。まあ無料でオモチャとして利用している時点であまりブーブー文句立てる筋合いもないっちゃないのだが、そういう意味でもMetaのAPI周りに関しては、Developerへの寄り添いが足りないという不満がある。そもそも俺みたいな個人利用者なんか想定していない可能性もあるが。。
Instagram Graph APIが使えるようになった話
2023年9月ごろに突然死して以降、Instagram Graph APIが使えない状態が続いていたが、ふと思い立ってMetaアプリから再度作り直したところ、割と簡単にInstagram Graph APIを使える状態まで復活させることができた。(アクセストークン発行まで行きついた。)その辺の検証結果等は以下Qiitaにまとめたので参考になればいいなと思います。
https://qiita.com/ricemountainer/items/4badc22c77af52c01e5c
2020年頃に使い始めて、その後2023年9月に死亡するまで、当時はその間Instagram Graph APIを呼び出せていたわけだが、そのとき使用していたアクセストークンを、どのようにして発行したんだか、正直にいうともはや覚えてない。ただ、今回やったことを振り返って見ると、今回と同じやり方でなかったのは確かだという確信がある(当時こんなことした記憶は絶対にない)。よって、明確な記録は見つけられていないが、多分2023年9月ごろにInstagram側で何か仕様変更でもあって、そのときその仕様変更前に発行されていた「旧アクセストークン」を、一度強制的にRevokeでもかけたんだろうと、勝手に予想している。動くようになった今となっては当時の死因なんて最早あまり興味もないしどうでもいいが、そのようにして納得させることにした。
2020年当時Instagram Graph APIを使いはじめた当初は、主に参照系のAPIのみが利用できて、更新系のAPIで可能だったのは「コメントする」とかその程度のものだった(そう記憶している)。そのため当時は、Instagramから参照系APIで投稿を抽出してきて、画像をDLのうえ、Twitterにそれを回すという、俗にいう(というか俺が個人的にそう呼んでいるだけなんだが)“I2T”(Instagram To Twitter)という機能を作って遊んでいた。2023年9月の突然死以降これが動作しなくなったが、Instagramさえ生き返れば再開できるのにな、と当時しばらくは悩んでいた時期があった。月日が経つとその辺の悩みも薄れ、復活の望みがなさそうだとわかり興味も感心も失っていったわけだが、今回復活できたので(できてしまったので)、また新しいオモチャが手に入ったとわかり俄然ワクワクし出してしまっている。
ただ、だからといってI2Tを復活させるつもりもない。そもそも、Twitter APIは現状v2.0系のみであり、v2.0では画像のアップロードはできなくなっているため、I2Tの当初の機能性は半分以上失われている。その改悪を受けて2023年6月ごろにInstagramの投稿へのpermlinkをTwitterに回すように変えており、その実装は今も残っているので使いまわそうと思えば使いまわせるが、わざわざ同じことをやり直そうと思わないし、正直に言ってその機能性には最早面白みというかやりがいを感じない。というか、I2Tは開発者としての興味関心だけを純粋に顕在化したようなツールであり、それ以外の発展要素が何もないのだ。要するに「作ってみたいから作った」であり、それ以外の、なんというか「明確に実現したいビジョン」みたいのがない(作れればそれでいいという感じだった)。手段と目的がごっちゃになってしまっているのだ。強いて言うなら「Instagramへの動線をTwitterにつくる」のは目的の一つだったが、前に書いた通り、SNSの使い方を明確に区別化したいと思っているので、InstagramとTwitterの動線確保はあまりやる気がない。ここに来て、自身のコンテンツをSNSを使っていかにうまく演出するかというのを、Instagram Graph APIの使い方を含めてちゃんと考えたいと思っているフェーズなのである。
では何をやるかって話なのだが、そんなこと言っておきながら現時点ではあまり明確に使い方を決めているわけではない。しかし、画像や動画の投稿ができることが判明しているため、これを使って何かできないかと画策中である。このため、例えば他のSNSへの宣伝投稿と同じく、Instagramにもそういう宣伝動画を定期的に(週一で)流すような処理を作っても面白いんじゃないか、と考えているところである。インスタは特にリール動画がフォロー外のユーザーに効果的だときいているので、リール動画つくって週一で流す(という機能を作って自動化する)、とかできないかなぁ、と考え中。他の宣伝投稿処理と同じく、Cloudflare Workersにそのワークロードを構築すれば、管理運用もやりやすくなる(この辺の話は後述する)。一方、Instagram Graph APIによる画像や動画の投稿はちょっとクセがあり、Cloudflare WorkersのようなOutbound requestや実行時間に制限のある環境では、あまり多発したくないという思いもある。ここは色々検証しながら実現性を探っていくつもりである。
Lambda+API GatewayのワークロードをCloudflare Workersに移行した話
各種SNSにテキスト投稿するためのAPIや、ブログの更新通知(をSNSやpingに投げる処理)、自身のマンガ等の宣伝(をSNSに投稿する処理)など、完全個人の趣味用途のワークロードを、主にLambda+API Gatewayで構築していたのだが、それをCloudflare Workersに移植した。Lambda+API Gatewayの構成は、開発しやすかった(慣れていた)というのもあってあまり深く考えずに採用していたが、この構成の場合、やりたいことが増えてくるたびLambda関数をその分作らなければならず、管理や運用の手間が増える。使うSNSややりたいことが増えてきたことでこの問題が如実に表面化し始め、少し前から不満を感じていた。その点、Cloudflare Workersだと、1つのWorkersアプリに全部の処理を乗せられるので管理が非常に楽。また、例えば「Twitterに投稿する」という要件があった場合、この構成だとそのためのLambda関数を用意したうえでAPI Gatewayを生やして、呼び出す側のLambda関数からそのAPI Gatewayに向けてfetch
するみたいな処理を組むことで共通化する必要があったが、Cloudflare Workersだと1つのリポジトリ内で完結するので、内部処理としてそれを実装しておいて、それを呼び出すだけでよく、そもそも外部に向けてAPIの口を用意しておく必要すらない。hono
を使うことでWeb標準に則って実装でき、Lambda特有の癖からも解放される(大した癖でもないけど)。しかもhono
は基本がTypescriptなので生Javascriptに比べて型を導入できるメリットもある。「絶対Cloudflare Workersにしたほうが幸せだな…」とはずっと思ってて、もともとひっそり計画していたのを、3月中旬~下旬くらいにかけて徐々に移行していった。主要な処理はLambdaで既に実装済のものがあるので、それを移植すればよく、移行も比較的容易だった。以下にいくつか振り返り事項を載せておく。
X(Twitter)やThreadsのOAuth Tokenの扱い
Xに投稿する処理はOAuth2 Tokenを使っているが、このトークンは(上でも書いたように)7200秒=2時間で期限切れするので、10分に1回リフレッシュして更新しておくという処理を組んでいた。つまり
- XにPOSTする処理
- XのOAuth2 Tokenをリフレッシュして更新する処理
の2種類の処理がある。2.で更新した(最新の)OAuth2 TokenはDynamoDBに保持していたわけだが、Cloudflare Workersへの移行にあたって、今回はそれをRDBのテーブルに変えた。(というわけでやはりPrismaがここにも登場する。)DBは相変わらずSupabase。ここに別スキーマを切った。で、2.の処理は、Cloudwatch EventのCronトリガーで起動していたが、これをCloudflare WorkersのCronトリガーに変えた。ちょっと実行タイミングも変えたが、まあ大した話ではない。また、1.の処理は、最新のOAuth2 Tokenの取得元をDynamoDBからRDBのテーブルに変えたくらいである。
Threadsに関しても同様なのだが、上記の懸念もあって、Threadsに関しては2.にあたる処理がもともと存在していない。ので、とりあえず1.だけ用意した。この関連でとりあえずトークン保持しておくテーブルだけは作ったが、自動更新する仕組み(2.)はないので、そのうち切れる。その前に手動でリフレッシュ+更新が必要である。面倒なので「コールしたらリフレッシュしてくれるエンドポイント」だけ生やしておいてそれ叩くようにしようかな、という気でいる。
Blueskyへの投稿処理に改修が必要になった
もともとBskyAgent
というライブラリを使って実装していたのだが、これは2025年3月現在deprecationなので、変更する必要があった。というのと、OGP画像の投稿が当時実装したやり方では想定通りに動作しなかった(画像投稿できなかった)ので、修正を要した。なのでこの辺はCloudflare Workers移行に際して諸々改修が必要になった。ただ別にCloudflare Workersが悪いとかいうのではなくて、基本的にはoutdatedな処理を直したというだけの話に過ぎない。詳しくはQiitaにまとめたのでそちらを参考にしていただきたい。
Cloudflare Workers特有(固有)の対処が必要になった
LambdaのときにBlueskyへの投稿に際して画像リサイズのために使っていたsharp
や、ブログの更新通知をSNSに投稿する際にHTMLの解析をするのに使っていたjsdom
が、wrangler
(というかCloudflare Workers)で使用できなくなっていたので、それらを使用しない形で改修が必要になった。それぞれの話はこのQiitaの記事にまとめた。jsdom
は正規表現使う形で割と簡単に作り変え可能だったが、sharp
はそうもいかなかった。探した限りでは「画像リサイズ」だとやはりsharp
が第一候補にあがり、それ以外のライブラリはあまり使用例もなくピンと来るものがなかった。というわけで「画像リサイズ」に関しては諦めざるを得ない=運用で画像サイズを調整するしかない(運用回避しかない)、と判断するに至った。調べてみるとBlueskyの画像サイズに関しては約1Mバイトまで可能とのことで、Blueskyに投稿する予定となる画像=今の所ユースケースとしてはOGP画像しかなく、これらは1M超えることはまずないので、まあ大丈夫だろうと判断した。縦横サイズの論理的なMAX値は確認できてないが、OGP画像の範疇(1200×630)が問題なく投稿できてるので、これが出来てれば今の所問題ないし、他はまぁ何か起きたらそのときまた考えればいいか、という感じでいる。
その他、Cronトリガーで少しハマったポイントがある。これに関してもQiitaの記事にまとめたのでまぁ興味あったら読んでください。
Cloudflare Workers上のワークロードとしての懸念
- Cloudflare WorkersのCronトリガーは、Freeプランだと5種類しか定義できない。現状5種類も必要としていない(3つしか使用していない)ので今の所困っていないが、AWSのCloudwatch Eventのほうはそんなキツイ制限はない(多分無制限に作れる、はず、いやなんか制限あるのかな?よく知らない)ので、明確に「5つまで」と制限されると少し焦る。本当に厳しくなったらCronトリガーは全部Cloudwatch Eventあたりに移行して、Cloudflare WorkersにはAPI生やしておいてそこ叩いてもらうって感じにすればいいのだが、トリガーの管理が別プラットフォームになってしまうのは出来れば避けたい。制限である5つをうまくやりくりするしかないかなあ、という感じである。
- そもそもAPIとして生やしておく必要あるのかという、懸念というか疑問。前のときは、あるLambda関数から別の(共通処理的な)Lambda関数を呼び出す必要があったので、API GatewayくっつけてAPIの口を露出させていたが、Cloudflare Workersだと1つのアプリに全部の処理を組み入れられるので、わざわざAPIとして露出させておいてそこコールする必要がない。そうなるとAPIとしてエンドポイントを露出させておく意味は?という気になっている。例えば「XにPOSTする」という処理を、APIとしてわざわざ外部に露出させておく意味をあまり感じない。「OAuthトークンとってrequestヘッダにセットしPOSTパラメータ取り出して
api.x.com
にmethod: "POST"
でfetch
する、というのをその度毎回curl
で組み立てるのが面倒くさいから、というのはまぁわからんでもないのだが、外部に晒すというのはセキュリティリスクを検討する必要があるので、、、というジレンマ。まあ本当に外部から叩いてもらわないと成り立たない処理に関しては露出せざるを得ないのだが、それ以外の場合は本当にAPIとして露出する必要性を考えないといけないな、と思った。そういう意味だと前から同じ懸念はあったはずで、単にAPIとして口を露出する必要性があったからあまり気にならなかっただけということなんだと思われ、Cloudflare Workersに移行したことで気づいたって感じであり、Cloudflare Workers云々ではなく一般的な懸念事項という感じだが。