connector-x について
connector-x は、X API 仕様から生成された Motoko バインディングを提供し、X API v2 への投稿方法を説明します。ユーザーに代わって投稿する canister の開発者向けで、OAuth 2.0 PKCE を利用します。
主な機能
- 01X API v2 の Motoko バインディング
- 02TweetsApi.createPosts による投稿
- 03OAuth 2.0 PKCE のベアラートークン設定
- 04既定で非レプリケートの HTTP リクエスト
- 05JSON から null の任意フィールドを除外
- 06トークン更新とレート制限の説明
活用シーン
- 01Motoko canister から投稿
- 02OAuth 2.0 PKCE でユーザーを認証
- 03送信前に投稿フィールドを設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ caffeinelabs/ skills --skill connector-xインストール後、Claude Code で/connector-xを入力して読み込まれたか確認します。
詳細
x-clientでXに投稿する
XのOpenAPI仕様から生成された、X API v2向けのMotokoバインディングです。書き込みには TweetsApi.createPosts
(POST /2/tweets)を使用します。リクエストモデルは TweetCreateRequest です。
バックエンド
OAuth 2.0のベアラートークンを持つユーザーに代わってツイートを投稿する、最小限の canister です(トークンの取得と更新は canister 側で行います。詳細は後述)。デフォルトでは非レプリケートなので、トークンを渡すだけで使えます。すべてのオプションフィールドを指定する必要があり、nullは「指定なし」を意味します。
import { createPosts } "mo:x-client/Apis/TweetsApi";
import { type TweetCreateRequest } "mo:x-client/Models/TweetCreateRequest";
import { defaultConfig } "mo:x-client/Config";
actor {
// Post a tweet on behalf of a user holding an OAuth 2.0 bearer token.
public func postTweet(accessToken : Text, body : Text) : async () {
let cfg = { defaultConfig with auth = ?#bearer accessToken };
let req : TweetCreateRequest = {
text_ = ?body;
for_super_followers_only = null;
poll = null;
reply = null;
reply_settings = null;
media = null;
geo = null;
quote_tweet_id = null;
nullcast = null;
direct_message_deep_link = null;
community_id = null;
card_uri = null;
edit_options = null;
made_with_ai = null;
paid_partnership = null;
share_with_followers = null;
};
ignore await* createPosts(cfg, req);
};
}
テキストフィールドはtext_ : ?Textです(末尾のアンダースコアはMotokoのキーワードとの衝突を避けるためのもので、JSONキー"text"にシリアライズされます)。
OAuth 2.0の設定 — PKCE、クライアントシークレット不要
すべての書き込みエンドポイント(特に/2/tweets)には、ユーザーごとのOAuth 2.0ベアラートークンが必要です。x-clientはPKCEフロー向けに構築されているため、クライアントシークレットは不要で、公開情報であるClient IDだけを使用します。
-
X Developer Portalにアクセスし、プロジェクト(無料プランでは月1,500件の投稿)とアプリを作成します。
-
アプリ → 設定 → ユーザー認証設定 → 編集を開き、OAuth 2.0をオンにします。アプリの種類は
Web App, Automated App or Bot(PKCE)を選択します。Native Appや「Confidential Client」は選択しないでください。これらは、このクライアントが出力しないクライアントシークレットフローを強制します。 -
コールバックURI:
?code=…を受け取る canister のHTTPSエンドポイントを指定します。完全一致が必要です(例:https://<canister-id>.ic0.app/oauth/x/callback)。 -
認証時にリクエストするスコープ:
スコープ 理由 tweet.writecreatePosts/投稿に必須tweet.readUIに「@…として接続済み」と表示 users.read認証済みユーザーを特定 offline.accessリフレッシュトークンを発行(アクセストークンの有効期間は約2時間) -
保存し、OAuth 2.0 Client ID(約30文字の公開文字列)をコピーします。これはシークレットではないため、コミット、ログ出力、ハードコードをしても安全です。
デプロイモデル — どちらか一方を選ぶか、両方に対応します。管理者が一度設定する canister 全体共通のClient ID(デフォルト)、またはレート制限のクォータを共有すべきでないマルチテナントアプリ向けのユーザーごとのClient IDです。
スコープは認証時にリクエストしますが、チェックを外していると発行されたトークンに含まれません。createPostsで「Insufficient OAuth scope」が表示される場合、ほとんどはtweet.writeが不足しています。
呼び出しはデフォルトで非レプリケート
x-clientの呼び出しはすべて、IC上のhttp_requestです。このパッケージではdefaultConfigにis_replicated = ?falseを設定しています。Xは副作用があり(投稿によって状態が変更され)、レート制限ヘッダーや応答のタイムスタンプもリクエストごとに異なります。そのため、レプリケートされたアウトコールでは、すべてのサブネットノードがリクエストを発行し、ICが完全に同一の応答を要求するうえ、サイクルも約13倍必要になるため、投稿が重複し、コンセンサスにも失敗します。自分で設定する必要はありません。デフォルトのままで問題ありません。コンセンサスが特に必要な場合に限り、is_replicated = ?trueに上書きしてください。
オプションフィールドはnullのままにする
x-clientはserde-coreのskip_null_fieldsオプションを使い、値がnullのオプションフィールドを送信するJSONから除外します。そのため、/2/tweetsには設定したフィールドだけが渡されます。text_ = ?"…"を設定し、ほかのすべてのフィールドをnullにしたTweetCreateRequestを構築すれば(上記のスニペットのように)、リクエストボディは検証に通ります。Motokoでは値を指定する際にレコードの全フィールドが必要です。nullは「指定なし」を表します。
null以外のオプションフィールドのサブオブジェクト規則
poll、reply、geo、media、edit_optionsのいずれかを?Someに設定すると、Xはそのサブオブジェクトに必要なフィールドを検証します。空のオブジェクトは送信できないため、フィールドをnullのままにするか、必要な値をすべて設定してください。
poll—options(2つ以上)とduration_minutes。reply—in_reply_to_tweet_id。media—media_ids(事前にアップロードしておく必要があります)。geo—place_id。
トークンの更新
アクセストークンは期限切れになります(約2時間)。各呼び出しの前に、expires_atまでの安全バッファ内であれば、保存済みのrefresh_tokenとClient IDを使用して
https://api.x.com/2/oauth2/tokenにgrant_type=refresh_tokenを指定してPOSTし、canisterでトークンを更新してください。
Xは更新のたびにリフレッシュトークンをローテーションします。新しいaccess_token
とrefresh_tokenの両方を保存してください。古いリフレッシュトークンを再利用すると400が返され、再認証が必要になります。x-clientは更新を認識しません。更新処理はcanister側で行います。標準的なコード形式については、posting-to-x拡張機能を参照してください。
レート制限
無料プランでは、アプリあたり月間1500件の投稿、月間500件の読み取りが可能です。本番環境ではHTTP 429を受け取ったらバックオフしてください。投稿を黙って再試行してはいけません(再試行するとツイートが重複する可能性があります)。Xのレート制限ヘッダーはレスポンス本文に含まれますが、このパッケージでは解釈されません。