关于 connector-x
connector-x 提供根据 X API 规范生成的 Motoko 绑定,并说明如何通过 X API v2 发帖,面向构建代表用户发布内容的 canister 的开发者,采用 OAuth 2.0 PKCE。
主要功能
- 01X API v2 的 Motoko 绑定
- 02使用 TweetsApi.createPosts 发帖
- 03OAuth 2.0 PKCE Bearer Token 配置
- 04默认发送非复制 HTTP 请求
- 05从 JSON 中省略空值可选字段
- 06令牌刷新与速率限制说明
适用场景
- 01通过 Motoko canister 发布帖子
- 02使用 OAuth 2.0 PKCE 授权用户
- 03发送前配置帖子字段
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ caffeinelabs/ skills --skill connector-x安装后在 Claude Code 中输入/connector-x确认已加载。
详细介绍
使用 x-client 发布到 X
针对 X API v2 的 Motoko 绑定,
根据 X 的 OpenAPI 规范生成。写入接口是 TweetsApi.createPosts
(POST /2/tweets);请求模型是 TweetCreateRequest。
后端
一个精简的 canister,代表持有 OAuth 2.0 bearer token 的用户发布推文(token 的获取和刷新在 canister 端完成,详见下文)。默认不进行复制,因此只需提供 token;所有可选字段都必须存在,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 bearer token。x-client 专为 PKCE 流程构建,因此无需客户端密钥,只需要公开的 Client ID。
-
访问 X Developer Portal, 创建一个 Project(免费套餐 = 每月 1500 条帖子)和一个 App。
-
App → Settings → User authentication settings → Edit,开启 OAuth 2.0。 Type of App:
Web App, Automated App or Bot(PKCE)。不要选择Native App或 "Confidential Client"——这些选项会强制使用本客户端不支持的客户端密钥流程。 -
Callback URI:接收
?code=…的 canister HTTPS 端点,必须完全匹配 (例如https://<canister-id>.ic0.app/oauth/x/callback)。 -
授权时要请求的作用域:
Scope 用途 tweet.writecreatePosts/ 发布所必需tweet.read在 UI 中显示“已连接为 @…” users.read解析已通过身份验证的用户 offline.access签发刷新令牌(访问令牌有效期约为 2 小时) -
保存;复制 OAuth 2.0 Client ID(约 30 个字符的公开字符串)。它不是 密钥——可以安全地提交、记录日志或硬编码。
部署模式——选择一种或同时支持两种:由管理员一次性设置的单个canister 级 Client ID(默认),或适用于不应共享速率限制配额的多租户应用的每用户 Client ID。
授权时会请求作用域,但如果未勾选,相应作用域会在签发的 token 中悄然缺失——调用 createPosts 时出现“Insufficient OAuth scope”,几乎总是因为缺少 tweet.write。
调用默认不进行复制
每次 x-client 调用都会在 IC 上发起一次 http_request。该软件包在
defaultConfig 中提供 is_replicated = ?false:X 会产生副作用(发布会更改
状态),其速率限制标头和响应时间戳也会因请求而异,因此,进行复制
调用——每个子网节点都发出请求,IC 要求响应完全一致,消耗约 13 倍 cycles——会导致重复发布并无法通过共识。无需手动设置;默认值就是正确的。只有在确实需要共识时,才将其覆盖为 is_replicated = ?true。
可选字段:保持为 null
x-client 会从出站 JSON 中移除值为 null 的可选字段(通过
serde-core 的 skip_null_fields 选项),因此 /2/tweets 只会收到你设置的字段。构造
TweetCreateRequest 时,设置 text_ = ?"…",其他所有字段均设为
null(如上方代码片段所示),请求正文即可通过验证。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 已进入安全缓冲期,canister 应通过 POST 向
https://api.x.com/2/oauth2/token 发送 grant_type=refresh_token,并提供存储的 refresh_token 和 Client ID。
X 每次刷新都会轮换 refresh token——请存储新的 access_token
和 refresh_token;重用旧的 refresh token 会返回 400,并需要重新授权。x-client 不处理刷新——这由 canister 负责;
标准代码示例请参见 posting-to-x 扩展。
速率限制
免费套餐:每个应用每月可发布 1500 条帖子、读取 500 次。在生产环境中遇到 HTTP 429 时应退避;切勿静默重试帖子(重试可能会导致推文重复)。X 的速率限制标头会随响应正文返回,但该软件包不会对其进行解析。