Xの通知をWebhookに流すAngelic Angelの仕組みと危うさ

コードを読まないAIエンジニア
サムネイル

X APIの従量課金を前に、特定アカウントのツイートをリアルタイムで取得する安い方法、つまりX APIの代替を探していませんか? Angelic Angelは、ブラウザ向けに届くX(旧Twitter)の通知を受け取り、指定したWebhookへ流します。Rust製のCLIで、3月にGitHubへ置かれ、10月4日から5日にかけて急にフォークが増えました。

試したのは、CPU1コアの使い捨てコンテナでビルドし、ダミーのCookieで register を実行したところまでです。自分のXアカウントは使っておらず、通知の受信、Webhookに届くJSON、遅延は確かめていません。それでも、Xに401で弾かれた時点でMozilla側には購読ができていたこと、設定ファイルの権限、READMEに書かれていない動きは、使う前に知っておきたい内容でした。

Angelic AngelでXの通知がWebhookに届くまで

XからMozilla AutoPushを経由してAngelic Angelが受け取り、Webhookへ送るまでの通知の経路図

XはWeb Pushで、Mozillaのサーバーに通知を投げる

ブラウザでXの通知を許可すると、Xはブラウザごとの中継サーバーへ通知を送ります。この仕組みがWeb Pushで、標準仕様です。中継役はブラウザで違い、ChromeはFCM、SafariはAPNs、FirefoxはMozilla AutoPushです。FirefoxはAutoPushとWebSocketでつながり続けるので、ブラウザが起動していればタブを閉じていても通知が出ます。

Angelic Angelは、このFirefoxの役を演じます。AutoPushに購読を作り、受け取った購読用URLをXに「ここへ通知を送って」と登録して、あとは待ち続ける。Xから見れば、通知の受け手はMac版Firefox1台です。

READMEによれば、届くのはフォローしていて、なおかつ投稿通知をオンにしたアカウントのツイートです。いいねやDMなど他の種類の通知が届くかは確かめていません。キーワードで探す使い方には向きません。

ECEで復号して、WEBHOOK_ENDPOINTにPOSTする

通知はWeb Pushの暗号化方式ECE(Encrypted Content-Encoding)で暗号化されていて、購読時に作った秘密鍵がないと読めません。ソースでは ece クレートで aes128gcm と旧方式の aesgcm の両方を復号しています。

復号した中身は、環境変数 WEBHOOK_ENDPOINT のURLへHTTP POSTで送られます。Xの通知の中身をそのまま渡すので、どんな項目が入るかはソースからは分かりませんでした。

再接続は、接続に失敗したときが5秒から倍々(上限5分)、つながったあとに切れたときが1秒で、close code 4774なら30分待ちます。復旧できないエラーでは listen が終了します。

ダミーのauth_tokenでregisterすると、どこで止まるのか?

cargo install --path . のビルドは6分42秒で、1回で通りました。angelic-angel init にダミーの --auth-token と --ct0 を渡すと、angelic-angel.toml ができます。

[twitter]
auth_token = "DUMMY..."
ct0 = "DUMMY..."

Cookieの値が平文で並び、ファイル権限は -rw-r--r--(644)でした。共有のマシンなら、Cookieを他のユーザーに見せているのと同じです。なお既定のファイル名はリポジトリの .gitignore に入っていますが、-c で別名にすると対象外です。

続けて angelic-angel -v register を実行すると、前半のAutoPushへの登録はXの認証情報なしで通り、https://updates.push.services.mozilla.com/wpush/v2/... という購読用URLが返ってきました。ダミーのCookieでも、ここまでは進めてしまいます。止まったのは、そのURLをXの login.json へ送る後半です。

error: Twitter API error: push subscription registration failed (401 Unauthorized): {"errors":[{"code":32,"message":"Could not authenticate you."}]}

ここまで約1.3秒。デバッグログでは、送信ボディの os_version と udid がどちらも "Mac/Firefox" で、ダミーのCookie値とBearerトークンの文字列は1件も出ませんでした。失敗後も status は「not registered」のままです。ソースを読むと、AutoPush側の購読だけが残り、設定ファイルに保存されないので unregister でも消せません。

Angelic AngelのREADMEに書かれていない、3つの動き

READMEの説明とソースの実際の動きを並べた比較図。listen中の再登録、Webhook失敗時の扱い、unregister後に残るX側の登録

listen中もX APIを呼ぶことがある

READMEには、X APIを呼ぶのは register のときだけで、待ち受け中は呼ばないとあります。ソースでは、listen の最中にAutoPush側の購読ID(UAID)が無効になると自動で登録し直し、そのときXの登録用エンドポイントをもう一度呼んでいます。

UAIDが変わるかはAutoPush側の都合です。通知を待っているだけのはずの常駐プロセスが、いつの間にかCookieを付けてXへ接続している可能性があります。この食い違いはIssue #3でも10月5日に指摘されています。常駐させるなら、動かす場所とログを見る頻度を先に決めておくのが前提です。

Webhookが2xx以外を返しても、AutoPushには受け取ったと返す

コードを読むと、Webhookが2xx以外を返したときは警告ログを出すだけで、AutoPushには「受け取った」と返しています。READMEはこの扱いに触れていません。

not_delivered を返すのは、Webhookへ接続できない場合や WEBHOOK_ENDPOINT が未設定の場合など、復号以外の処理が失敗したときです。Webhookが500を返した通知に気づける手がかりは、警告ログだけになります。

unregisterでは、X側の登録が残る

unregister が解除するのはAutoPush側の購読だけで、Xに登録した「このFirefoxへ通知を送る」設定を消す処理はソースにありません。READMEには購読の解除とだけあります。X側に何が残るかまでは確かめていませんが、ツールにはX側を消す手段がありません。やめたつもりでも、登録は残っているかもしれません。

auth_tokenとct0は、アカウントの合鍵

auth_tokenとct0の2つのCookieが、通知だけでなくアカウント全体を開ける合鍵になることを示すイラスト

auth_token はXにログインしたときに発行されるCookieで、ct0 はCSRF対策のトークンです。2つがそろうと、ログイン済みのブラウザと同じようにWeb版Xへリクエストを送れます。

ソースでは、Web版Xが使う公開のBearerトークンを固定値で埋め込み、2つのCookieと x-csrf-token を添えて送っています。開発者向けのAPIキーは使いません。欲しいのは通知だけなのに、渡しているのはアカウント全体の鍵です。

その鍵を、通知のためだけに常駐プロセスへ預けてよいでしょうか? 漏れれば、セッションが切れるまで、他人があなたのログイン状態でWeb版Xを操作する恐れがあります。

X利用規約は、このアクセスをどう書いているのか?

X利用規約の「Misuse of the Services」の項 (iii) は、Xが用意した手段以外でのアクセスを禁じています。要の部分は "other than through our currently available, published interfaces"(現在提供している公開済みのインターフェース以外の手段で)です。

これは2026年10月6日時点の現行版の条文で、10月9日に施行される次版にも同じ文があります。どちらの要約部分にも、Xが課す技術的な制限を回避しようとしてはならない、という趣旨の一文があります。

一方、Angelic Angelが叩く login.json は、開発者向けドキュメントに載っていないWeb版の内部エンドポイントです。これが違反にあたるかを、私は断定しません。Issue #3も違反とは言わず、条文を知っておくべきだという立場です。

同じくWeb Pushを使うtweet-boxは、「アカウントの BAN や IP BAN のリスクはありません」と説明しています。ただ、目立ちにくいことと規約上問題がないことは別で、凍結を決めるのはXです。

X APIの従量課金と並べたときの違い

項目
X APIで投稿を取得
Angelic Angel
対象
指定したアカウントやキーワード
ルール最大1,000本に合う投稿(同時接続は1本まで)
フォロー中かつ通知オンのアカウント
料金
投稿の読み取り1件0.005ドル(約0.75円)
従量課金で利用可(単価は料金ページに記載なし)
無料
遅延
取りに行く間隔しだい
P99で約4〜5秒
未計測
規約上の位置
公式の提供手段
公式の提供手段
非公開の内部エンドポイント
壊れたとき
ドキュメントがある
ドキュメントがある
Xの仕様変更で止まりうる

数字は2026年10月6日時点のX APIの料金ページによります。X APIは2026年に従量課金だけの体系へ移り、同じ投稿を24時間(UTC)以内に何度読んでも1回分の課金です。

20アカウントが1日10投稿ずつなら、月6,000投稿で30ドル(約4,500円)です。あなたの用途は、この金額に収まりますか? 収まるなら、Cookieを預けるより公式の手段で取るほうが失うものは少ない、と私は考えます。

Angelic Angelを使うなら、サブアカウントと止め方を先に決める

それでも試すなら、壊れても困らない形にします。

  • 対象をフォローして通知をオンにしただけのサブアカウントのCookieを使う
  • init の前に umask 077 をかけ、angelic-angel.toml を最初から自分だけが読める権限で作る
  • Webhook側は受け取った内容をまず保存し、重い処理はあとに回す
  • やめるときは unregister で終わらせず、Xのセッション一覧から該当するログインも切る

サブアカウントにしても、規約上の扱いは変わりません。変わるのは、凍結や漏えいが起きたときに失うものの大きさです。

様子見も筋の通った選択です。v0.1.0でコミットは3月5日の3本、READMEとの食い違いを指摘したIssueもまだ開いたままです。その扱いを見てから決めても遅くありません。