Changelog¶
所有版本变更记录。格式基于 Keep a Changelog。
[3.0.1] — 2026-06-23¶
Fixed¶
- 🔴
Twitter::create()初始化失败(Python / Rust):ClientTransaction::create此前传None(无 cookies)获取 x.com 首页,X 前端更新后未登录页已切换为 Vite 格式(不含ondemand.s),导致所有通过Twitter.create()初始化的客户端均失败。现在将 cookies 解析后传入,以登录态获取主页。(src/lib.rs) - Python transaction 测试同类问题:
ClientTransaction.create()的三处 Python 测试 fixture 未传 cookies,同步修复。(python_tests/conftest.py、python_tests/test_transaction.py) - cookies 失效错误信息不明确:原报错"无法提取 ondemand chunk id"难以定位根因;现在检测页面特征,cookies 失效时直接提示"cookies 已失效或过期,请重新登录并更新 cookies"。(
src/common/transaction/client.rs)
Changed¶
- CI:升级 GitHub Actions 至 Node 24 运行时(
bb85cd1)
[3.0.0] — 2026-06-08¶
Added¶
- 统一指纹模型
ClientProfile:把 TLS 指纹(JA3/JA4 + HTTP/2 SETTINGS)与 HTTP 头部(User-Agent / Sec-Ch-Ua)绑成一个自洽身份,支持 Rust / Python / CLI 三端每账号独立配置 - 支持浏览器家族:Chrome 136、Safari(macOS / iOS / iPad)、Firefox、Edge、OkHttp(Android app),以及
None(禁用 TLS 模拟,调试用) - 不传 profile 时默认 Chrome 136 Windows x64(混进最大真实人群,确定性可复现)
- Python 工厂方法:
ClientProfile.chrome(os, arch)/safari_mac(v)/safari_ios(v)/safari_ipad()/firefox(v)/edge(v)/okhttp(v)/none() - CLI 新增全局 flag
--fingerprint <SPEC>(如chrome:windows:x64/safari-ios:18.1.1/okhttp:5/none) Twitter.fingerprint_info()返回ProfileInfo(browser/version/platform/user_agent);doctor命令响应新增profile段-
设计依据:真实浏览器的 TLS 与 HTTP 头是同一身份,拆成两个开关会产生 UA/JA3 错配(反爬识别特征)。Chrome 锚定 136(rquest-util TLS 上限,唯一可验证自洽的版本);跨账号真实 JA3 差异需跨浏览器家族。HTTP/2 始终开启,h2 SETTINGS 指纹随 profile 模拟。
-
Raw HTTP 调试接口:新增
Twitter.raw.raw_get(url, params)/Twitter.raw.raw_post(url, params, json_str, form)+ CLItwitter-cli raw get/post,复用 JA3/csrf/transaction 认证,方便调试任意 X API 端点
Breaking Changes¶
Twitter::create()签名变更(Rust + Python):旧(cookies, proxy_url, enable_ja3, fingerprint)合并为(cookies, proxy_url, profile)- Rust:
Twitter::create(cookies, None, true, None)→Twitter::create(cookies, None, None);enable_ja3=false→Some(ClientProfile::None) - Python:
Twitter.create(cookies, enable_ja3=False)→Twitter.create(cookies, profile=ClientProfile.none());所有子客户端XxxClient.create()同步 - 删除
enable_ja3参数:禁用 TLS 模拟改用ClientProfile::None/ClientProfile.none() - 删除
BrowserFingerprint公开指纹 API 与随机指纹池:移除BrowserFingerprint.random/chrome/from_filter、fingerprint.configure/enable_randomization/disable_randomization/is_randomization_enabled/get_random_fingerprint/get_pool_size及FingerprintConfig。改用ClientProfile - 删除
RquestClientBuilder::enable_ja3()/fingerprint():改为.profile(ClientProfile) - 删除
HttpClienttraitpost()的 per-requestfingerprint参数:客户端指纹现在固定,不再支持逐请求覆盖 - CLI 删除
--ja3/--fp-os/--fp-arch/--fp-version:合并为--fingerprint <SPEC> doctor响应字段fingerprint重命名为profile(字段:browser/version/platform/user_agent,去掉 arch/major_version)
Fixed¶
- 🔴 批量 DM 指纹错配:旧实现批量发送时每条私信重新随机一个 HTTP 指纹,而 TLS 层恒为 Chrome136 —— 同一批私信每条 UA 都不同、却共享同一 TLS 握手,是明显的机器人特征。现在同一客户端的所有请求使用同一个自洽 profile。受影响:
dm.send_batch/send_batch_with_custom_texts(v1 与 v2)。
[2.0.0] — 2026-06-04¶
Changed¶
- CI 发版流水线:Linux wheel 构建迁出 self-hosted Mac mini(
.github/workflows/build-and-release.yml) - 动机:x86_64 wheel 此前在 Apple Silicon Mac mini 上用 QEMU/Rosetta 模拟 amd64 构建,导致 release-v1.0.18/1.0.19 出现 2h+ 卡死与
libwriteable-*.rlib: failed to open object file对象文件竞态失败 build-linux-wheels(x86_64):runs-on从[self-hosted, macOS, ARM64]改为ubuntu-latest(托管原生 amd64,消除 QEMU)build-linux-arm-wheels(aarch64):改为ubuntu-24.04-arm(GitHub 托管原生 ARM64,私有仓 2026-01 起可用)- 构建命令、
--manylinux 2_28标签、复用的manylinux-rust-builder镜像、job 名与 artifact 名均保持不变 → wheel 文件名(cp38-abi3-manylinux_2_28_{arch})与 Release 下载链接不受影响 - macOS wheel 仍保留在 self-hosted Mac mini(Apple 硬件刚需 + 托管 macOS 10× 计费)
-
仅删除 self-hosted 专属的"清理残留产物"步骤与
clean: true(托管 runner 每次全新) -
CI 文档部署迁出 self-hosted Mac mini(
.github/workflows/docs.yml) - 动机:
mike推送 gh-pages 在 Mac mini 网络下反复失败(RPC failed; curl 16 HTTP2 framing layer/remote end hung up,连续两次),与 wheel 构建是同一台机器的网络问题;MkDocs 构建本身每次都成功 runs-on从[self-hosted, macOS, ARM64]改为ubuntu-latest- Python 环境从
uv(托管 runner 未预装)改为actions/setup-python@v5+pip install -r requirements-docs.txt mike deploy --push(gh-pages)与 Cloudflare Pages 部署逻辑不变;托管 runner 直连 GitHub,推送稳定
Added¶
- Settings:新增搜索安全 blocking setter + Explore personalized_trends setter(
src/x/settings/,2026-06-04,关闭 Issue #7) set_search_filter_blocked_muted(enabled)— 控制"Remove blocked and muted accounts from search results"(searchSafetyoptInBlocking)set_personalized_trends(enabled)— 控制"Explore → Trends for you"(i/api/2/guide/set_explore_settings.json)get_search_safety()— 读取opt_in_filtering(Hide sensitive content)+opt_in_blocking(Remove blocked/muted)两个字段,返回SearchSafetyResponseget_explore_settings()— 读取use_personalized_trends、use_current_location,返回ExploreSettings- Python 绑定:
PySettingsClient同步新增set_search_filter_blocked_muted、set_personalized_trends、get_search_safety、get_explore_settings;新增类型PyExploreSettings、PySearchSafety - CLI:新增
settings set-search-filter-blocked-muted、settings set-personalized-trends、settings get-search-safety、settings get-explore-settings四个子命令
Breaking Changes(面向 v2.0.0)¶
- Settings 模块端点级重构:删除 20 个字段级 setter 和跨端点聚合方法,改为 8 个端点级方法(2026-06-04)
- 删除的方法:
set_nsfw_user、set_display_sensitive、set_protected、set_discoverable_by_email、set_discoverable_by_mobile、set_allow_media_tagging、set_allow_dms_from、set_allow_dm_groups_from、set_dm_receipt、set_dm_quality_filter、set_mention_filter、set_allow_ads_personalization、set_allow_third_party_personalization、set_geo_enabled、set_search_hide_sensitive、set_search_filter_blocked_muted、set_personalized_trends、set_sensitive_account、get_sensitive_settings、get_privacy_settings - 新增方法:
update_account_settings(AccountSettingsUpdate)、set_search_safety(filtering, blocking)、set_explore_settings(ExploreSettingsUpdate) - 迁移:
set_nsfw_user(true)→update_account_settings(AccountSettingsUpdate { nsfw_user: Some(true), ..Default::default() });多个字段可合并一次调用,减少 HTTP 请求数 - 删除的类型:
SensitiveSettings、SetSensitiveAccountResult、PrivacySettings、MediaTagging、DmFrom、MentionFilter - Settings POST 参数修复:历史版本(1.0.x)中所有
account/settings.jsonPOST 方法的参数被追加到 URL query string 而非请求体,导致 Twitter 返回 404 code 34(实际未生效)。v2.0.0 修复,所有写操作现在正常工作。
Fixed¶
-
Settings POST 参数从 URL query string 移入 request body:
RequestBody::Form使用.query()导致所有account/settings.jsonPOST(set_display_sensitive、set_nsfw_user、set_protected、set_allow_media_tagging、set_allow_dms_from、set_allow_dm_groups_from、set_dm_receipt、set_dm_quality_filter、set_discoverable_by_email、set_discoverable_by_mobile、set_geo_enabled、set_mention_filter、set_allow_ads_personalization、set_allow_third_party_personalization共约 14 个方法)参数附加到 URL 而非请求体,Twitter 返回 404 code 34。已改为RequestBody::FormBody(.form()正确设置application/x-www-form-urlencodedbody)。历史版本(1.0.19 及以前)通过 CLI/Python 调用上述 POST 方法均受此 bug 影响。 -
Release notes 下载链接文件名错误(
.github/workflows/build-and-release.yml):GitHub Release 正文"从 GitHub Release 安装"示例链接此前硬编码为x_api-<ver>-...,与实际产物名x_api_rs-<ver>-...不符(commite8c4cc4重构包名时遗漏),导致示例 pip 链接 404。已修正 3 个 wheel + 1 个 sdist 链接为x_api_rs-(wheel 文件本身一直正确挂载,仅正文示例命令名错误)
[1.0.19] — 2026-06-02¶
Added¶
- Settings 模块全面扩展:补全 Privacy & Safety 页面所有隐私设置项(Rust SDK + Python 绑定 + CLI,2026-06-02 chrome-devtools 抓包确认)
- Rust API(
SettingsServicetrait):新增 15 个方法- 帖子/媒体:
set_protected、set_protect_videos(GraphQLAudienceAndTaggingAllowVideoDownloadsMutation)、set_allow_media_tagging - DM:
set_allow_dms_from、set_allow_dm_groups_from、set_dm_receipt、set_dm_quality_filter - 隐私/可发现性:
set_discoverable_by_email、set_discoverable_by_mobile、set_geo_enabled、set_mention_filter、set_allow_ads_personalization、set_allow_third_party_personalization - 综合查询:
get_audience_settings(GraphQLAudienceAndTaggingQuery)、get_privacy_settings(3 接口聚合,16 字段)
- 帖子/媒体:
- Python 绑定:同步暴露全部方法;新增
AudienceSettings、PrivacySettings类型;AccountSettings补充 5 个新字段(dm_receipt_setting、dm_quality_filter、mention_filter、allow_ads_personalization、allow_sharing_data_for_third_party_personalization) - CLI:新增 16 个子命令(
settings get-privacy、settings set-protected、settings set-protect-videos、settings set-allow-media-tagging、settings set-allow-dms-from、settings set-allow-dm-groups-from、settings set-dm-receipt、settings set-dm-quality-filter、settings set-discoverable-email、settings set-discoverable-mobile、settings set-geo-enabled、settings set-mention-filter、settings set-ads-personalization、settings set-third-party-personalization、settings set-nsfw-user、settings set-display-sensitive) - 集成测试:Rust 集成测试 37 个(+31),Python 测试 +35 个
[1.0.18] — 2026-06-01¶
Added — 发帖支持 Content Warning + AI Disclosure(4 项)¶
- 🟣 新增
MediaMetadata+SensitiveCategory+UploadClient::set_media_metadata+CreateTweetParams.{media_warnings, ai_generated_disclosure}(src/x/upload/types.rs,src/x/upload/client.rs,src/x/posts/types.rs,src/x/posts/client.rs,src/python/upload/,src/python/posts/,src/cli/,tests/x/upload/integration_set_metadata.rs) - 动机:X Web 在 2026 年间陆续把"敏感内容标注"和"AI 生成声明"拆成 4 个独立 UI 控件,对应 SDK 此前 0% 支持。2026-06-01 通过 chrome-devtools 抓包确认完整协议(详见
.claude/captures/) - 覆盖能力:
- 图片级
Nudity/Violence/Sensitive—MediaMetadata.sensitive_warnings,序列化为["adult_content","graphic_violence","other"] - 图片级
Generated with AI—MediaMetadata.self_reported_ai_generated(X Web: Edit media → Content warning 标签页) - 图片级
Block modifications by Grok—MediaMetadata.block_grok_edit - 帖子级
Made with AI—CreateTweetParams.ai_generated_disclosure(X Web 顶层 Content disclosure toggle,与图片级 AI 字段独立)
- 图片级
- 协议层:新端点
POST https://x.com/i/api/1.1/media/metadata/create.json,每个 media_id 一次,在 upload FINALIZE 之后、CreateTweet 之前调用。CreateTweet variables 按需注入content_disclosure.ai_generated_disclosure - Rust API:
UploadClient::set_media_metadata(media_id: &str, metadata: &MediaMetadata) -> Result<()>CreateTweetParams::with_media_warning(media_id, MediaMetadata)/.with_made_with_ai()PostsClient::create_tweet内部自动编排(迭代media_warnings调set_media_metadata,再发 CreateTweet,fail-fast)MediaMetadata::is_empty()短路:全默认值跳过 HTTP 请求
- Python API:
PyMediaMetadata/PySensitiveCategory(含.NUDITY/.VIOLENCE/.SENSITIVE常量);CreateTweetParams新增 keyword 参数media_warnings: dict[str, MediaMetadata] | None和ai_generated_disclosure: bool = False - CLI:
twitter-cli posts create --made-with-aitwitter-cli posts create --warning-for <MEDIA_ID>=<flags>(flags:nudity,violence,sensitive,ai,block-grok,no-download)twitter-cli upload set-metadata --media-id <ID> [--warning ...] [--ai-generated] [--block-grok-edit] [--disallow-download]
- 抓包样本:
.claude/captures/media_metadata_create.network-request、.claude/captures/media_metadata_violence_sensitive.network-request、.claude/captures/create_tweet_with_warning.network-request - 向后兼容:纯增量改动。
CreateTweetParams::default()仍可用(media_warnings默认空 HashMap,ai_generated_disclosure默认 false)。CreateTweetParams移除了Serialize/Deserializederive(MediaMetadata无 serde 实现;该结构仅在内部使用,不在 HTTP 层序列化)
[1.0.17] — 2026-05-27¶
Added — 媒体上传单次代理覆盖(feature request)¶
- 🟣 新增
ProxyOverride+UploadOptions.proxy_override:单次 upload 调用可绕开 client 配置的代理(src/x/upload/types.rs,src/x/upload/client.rs,src/common/client.rs,src/python/upload/) - 动机:媒体密集型发帖场景下,
upload.x.com对 IP 远不如graphql.x.com敏感。下游希望「图片/视频走直连或便宜的数据中心代理,发推继续走住宅代理」以节省代理出口流量。之前Twitter.create(proxy_url=...)把代理绑死给整个 client,没有 per-call 覆盖手段,只能 fork。 - Rust API:
UploadOptions::proxy_override字段,取值ProxyOverride::Inherit(默认)/Disable/Custom(url)UploadService::upload_multiple_times新增options: Option<UploadOptions>参数- Facade 新增
Twitter::upload_image_with_options和Twitter::upload_image_multiple_times_with_options RquestClient::clone_with_proxy()共享 auth/transaction/fingerprint,仅替换底层 rquest Client 的代理
- Python API:
upload.image()/upload.video()/upload.upload()/upload.image_multiple_times()以及UploadOptions新增 keyword-only 参数:proxy_override: Optional[str]— 指定本次上传使用的代理 URLuse_proxy: bool = True—False表示本次直连- 不传 → 继承 client 代理(行为不变)
- 实现:upload 流程的 INIT/APPEND/FINALIZE/STATUS 全部走临时 client,不影响同会话 graphql 发推 / DM 等其他请求
-
向后兼容:非破坏性变更。原有
UploadOptions字面量需要补proxy_override: Default::default()(或改用..Default::default()) -
🔴 修复
Twitter::create在代理 DNS / 网络错误时误导性提示「请检查 cookies 格式」(src/python/errors.rs新增 + 10 处 client 替换) - 现象:代理域名 NXDOMAIN、连接被拒、TLS 失败等纯网络问题,统一被包装为
RuntimeError: Twitter 客户端初始化失败: <err>。请检查 cookies 格式是否正确。,把排错方向引向 cookies,运营侧出现误删/重导 cookies 的操作 - 根因:所有 10 个 client(
Twitter,DMClient,PostsClient,UserClient,UploadClient,InboxClient,CommunitiesClient,SettingsClient,SearchClient,XChatClient)的create()失败路径不区分错误来源,硬编码追加 cookies 文案 - 修复:新增
src/python/errors.rs::map_init_error(),按TwitterError变体 +rquest::Error语义 (is_timeout/is_connect/is_request) + 错误链关键字(dns/proxy/socks/tls/certificate 等)分类,映射到不同 Python 异常:ValueError← Cookies 格式 / 参数 / JSON 解析ConnectionError← 代理 / DNS / TCP / TLSTimeoutError← 请求超时RuntimeError← 其他运行时
- 效果:
- 影响:Python 用户可通过
except ConnectionError/except TimeoutError/except ValueError精准分流,无需正则匹配错误文案
Fixed — XChat v3 Phase 3.1 P0 wire 矫正(基于 2026-05-12 真实抓包)¶
- 🔴 AES-GCM IV 长度修正:12B → 16B(
src/x/xchat/crypto.rs) - 现象:之前实现按 NIST SP800-38D 推荐 12B(也是 RustCrypto
Aes256Gcm默认),加密 payload 与 Twitter 实际 wire 长度不匹配 - 根因:2026-05-12 浏览器
crypto.subtle.encrypt探针抓包:Twitter XChat 客户端用 16B IV - 修复:切换
AesGcm<Aes256, U16, U16>类型;wrap_conversation_key输出 60B → 64B(16B IV + 32B ct + 16B tag);encode_encrypted_payload同步 -
影响范围:尚未发布的 Phase 3.1 加密路径;现有 v1.x 用户无影响(v1.x 不含 XChat 加密)
-
🔴
can_dm_on_xchat双边语义拆分 + 新 warning"recipient_disabled_xchat"(src/x/xchat/client.rs+ 4 处 docstring) - 抓包发现:即便双方都 enrolled,sender 视角
can_dm_on_xchat仍可能false(社交图/隐私 gate) - 修复:
send_message现在拆为has_keys+can_send_encrypted两个独立判定keys 空→ warning"recipient_not_enrolled"(语义不变)keys 非空 + can_dm_on_xchat=false→ 新 warning"recipient_disabled_xchat"—— 对方已 enrolled 但当前不接受加密 DMkeys 非空 + can_dm_on_xchat=true→ 加密路径
-
接口契约更新:
XChatServicetrait、XChatSendResultstruct、PyXChatSendResultpyclass、Pythonsend_messagemethod 四处 docstring 列出新值 -
encoded_message_create_eventouter Thrift trailer 字段(f102/f105/f106)已在 Phase 3.1 主体提交(9b94e37)实现;本次抓包字节级验证一致,无需进一步修改 -
code-reviewer 反馈处理:
- HIGH:
send_message公共入口重构为薄壳,委托send_message_with_material,CLI/Python 统一走单一路由 - LOW:
unwrap_conversation_key长度校验从< 32改为!= 64精确门槛 + negative test
Known limitation — XChat 加密 wire 整体仍待 iOS/Android 抓包矫正¶
- Twitter web 端不暴露启动加密 chat 入口(composer textbox 始终标 "Unencrypted message",无 lock toggle)
- 2026-05-12 浏览器抓包确认:双方都 enrolled 时,客户端仍只发送明文 SendMessage;只有 ECDH/AES-GCM 准备步骤在前台进行(推断为推测性预热),wrap 输出从未上链
- 其余 ~12 处
TODO(phase3.x-verify)(HKDF salt/info、wrap AAD、AddEncryptedConversationKeysMutationvariables 等)保持现状
Added — XChat v3 Phase 3.2:receive_messages(接收 + 解密 + 验签)¶
- 🟣
XChatService::receive_messages(conv_id, limit) -> ReceiveMessagesResult - 流程:
GetConversationPage拉取 → 一次批量GetPublicKeys拉发送方公钥 → 逐条解码/解密/验签 - 失败一律转 warning + 字段 None,不抛 Err——上层按消息维度处理
DecryptedMessage类型 ——event_id / message_id / sender_id / conversation_id / timestamp_ms / sequence_id / text: Option<String> / encrypted: bool / signature_valid: Option<bool> / warning: Option<String>thrift::decode_encoded_message_event(bytes) -> DecodedMessageEnvelope—— 完整解码顶层字段 + 提取f7.f1.f100inner_payload +looks_encrypted启发式thrift::decode_plaintext_message_text(inner_payload) -> String—— 明文路径文本提取(用973_GetInitialXChatPage.network-responsefixture 字节级验证:"Are you a victim of scam")api::get_conversation_page—— GraphQLIVlXls9JTnbgQ1gxsGAfJA/GetConversationPageQuery封装- Python:
await xchat.receive_messages(conv_id, limit=20) -> ReceiveMessagesResult,新增PyDecryptedMessage/PyReceiveMessagesResult(__repr__对 text 仅展示字符数,不泄漏内容) - CLI:
twitter-cli xchat receive --conv-id <id> --limit 20 --keystore <path>走三段式 BatchLine,Summary 按warning.is_none()真实累加 success/fail
warning 字段语义(接收侧)¶
| warning | 含义 |
|---|---|
"conversation_key_unknown" |
无对话密钥(先发一条触发协商) |
"decrypt_failed" |
AES-GCM 解密失败 |
"signature_input_unknown" |
inner_payload 字节形态不明,跳过验签(signature_valid=None) |
"sender_not_enrolled" |
拉不到发送方公钥,无法验签 |
已知限制(同 Phase 3.1,待真实双端 enrolled 抓包矫正)¶
- 加密 wire 格式(IV/AAD/Thrift 包装)按 xchat-kmp 反混淆推测,~5 处
TODO(phase3.2-verify) - 接收侧 CSV 验签字节选取(40B vs 63B)—— 当前仅在长度等于 63B 时尝试验签,其他形态返回
signature_valid=None
测试¶
- 全库
cargo test --lib --features xchat,cli:361 passed(基线 354 → +7) - fixture 驱动:
decode_inbox_event_fixture_plaintext/decode_plaintext_text_from_fixture_inner - 安全断言:
receive_verify_skips_when_inner_payload_len_is_not_63
Added — XChat v3 Phase 3:Python 绑定 + CLI 子命令(send/send-batch/send-batch-custom)¶
- Python (
x_api_rs.xchat.XChatClient): send_message(user_id, text) -> XChatSendResultsend_batch(user_ids, text) -> XChatBatchResultsend_batch_with_custom_texts(user_ids, texts) -> XChatBatchResult- 全部异步(
future_into_py);参数预检:text.trim().is_empty()抛ValueError,避免与RuntimeError语义混淆 - 新增
PyXChatSendResult/PyXChatBatchResultpyclass - CLI (
twitter-cli xchat): send --user-id <ID> --text <TEXT> --keystore <PATH>:单条 Envelopesend-batch --user-ids u1,u2,u3 --text <TEXT> --keystore <PATH>:统一文案,三段式 BatchLinesend-batch-custom --user-ids u1,u2 --texts-file <PATH> --keystore <PATH>:每行严格对应一个 user_id,不静默过滤空行(避免索引错位,由业务层validate_batch_message_input校验)XCHAT_KEYSTORE环境变量统一注入 keystore 路径
Added — XChat v3 Phase 3.1:AES-GCM 加密路径 + 批量发送¶
- 🟣
XChatService::send_batch/send_batch_with_custom_texts tokio::task::JoinSet无限制并发(与 dm v2 一致;遵循 CLAUDE.md "不引入 Semaphore" 约束)XChatBatchResult { results, total, success, failure },单条失败转warning = "send_failed: <reason>",整批不 fail- keystore 入口预读一次,N 个并发子任务共享
Arc<XChatKeyMaterial>(避免 N 次磁盘 IO) - 🟣
XChatService::send_message加密分支 —— 双方都 enrolled 时切换加密路径 - 流程:
ConversationKeyManager.get_or_establish→AddEncryptedConversationKeysMutation→ AES-GCM-256 加密 inner Thrift → 签名 → 发送 - 返回
XChatSendResult { warning: Some("encryption_implemented_unverified"), conversation_key_version: Some(...) } - 否则回退 Phase 3.0 明文+签名路径(
warning = "recipient_not_enrolled") conversation::ConversationKeyManager——Arc<RwLock<HashMap<conv_id, Arc<ConversationKeyState>>>>内存缓存crypto新增:hkdf_sha256(ikm, salt, info, len)—— RFC 5869 派生generate_conversation_key()—— 32 字节 CSPRNG(栈上Zeroizing<[u8;32]>,离开作用域归零)derive_kek(my_priv, peer_pub)—— ECDH P-256 → HKDF-SHA256 salt=b"", info=b"xchat-conversation-key-wrap"→ 32B KEKwrap_conversation_key/unwrap_conversation_key—— AES-GCM-256 包/解 conv keyencode_encrypted_payload/decode_encrypted_payload—— 消息载荷 AES-GCM 加解密(AAD=conversation_id_bytes)api::add_encrypted_conversation_keys——AddEncryptedConversationKeysMutation封装(queryId4V1KC8ue2tHHvRuIzeczdg)thrift::wrap_encrypted_payload_thrift—— field 100 包装加密 payload- 错误模型:新增
ConversationKeyEstablishmentFailed/PeerNotEnrolled/KekDerivationFailed,全部穷尽映射到ErrorCode
code-reviewer 已修复(Phase 3.1)¶
- C-1 CLI feature
xchat,cli编译失败(穷尽匹配补 3 个新变体) - H-1 栈上
conv_key_bytes用Zeroizing<[u8;32]>包裹 - H-2
DecryptionFailed::reason不再出现在 Display(避免日志/异常泄漏) - M-1
key_version按u64parse 排序而非字典序 - M-2
ConversationKeyEstablishmentFailed失败路径调conversation_keys.invalidate - M-3
send_batch_with_custom_texts预读 keystore,N 次磁盘 IO 降为 1 次
已知限制(待真实双端抓包矫正)¶
- 加密 wire 格式按 xchat-kmp 反混淆推测,~12 处
TODO(phase3.1-verify)(HKDF salt/info、wrap AAD、AddEncryptedConversationKeys variables shape、ttl_msec 等)
Added — XChat v3(端到端加密私信)— Phase 1:基础设施¶
- 🟣 新增
xchatfeature(默认启用)— 引入 XChat v3 模块的密码学底座 src/x/xchat/全新模块:crypto.rs— ECDSA / ECDH P-256 + AES-GCM-256 + SPKI/PKCS#8 编解码thrift.rs— Thrift Binary Protocol writer/reader +encode_text_message_event+encode_signature_envelope(字节级与官方 web 抓包一致)payload.rs—build_signature_payload(CSV 域分隔,含逗号字段拒签)keystore.rs—XChatKeyMaterial+XChatKeyStore(JSON 文件 + 0600 权限 + 原子写入 +Zeroize)error.rs—XChatError错误枚举(thiserror,14 个变体),透过TwitterError::XChat透传src/common/text_validation.rs— 抽取 dm 模块的文本/批量验证逻辑供 xchat 复用,dm v1/v2 行为零变化- 新增依赖(
xchatfeature 下):p256aes-gcmhmachkdfspkipkcs8zeroizerand_core - 新增 49 个单元测试(含 4 个 fixture 字节级验证测试),全库测试数 193 → 242
- 新增设计文档:
docs/architecture/xchat-implementation.md
Internal¶
- 不涉及任何 Twitter HTTP 调用(Phase 1 范围内)
- 不引入破坏性 API 变更:v1/v2 DM 模块完全保持原行为
- Phase 2-4 计划见
.claude/plans/20260511-160002~160004-XChat-v3-Phase*.md
Added — XChat v3 Phase 2:Enrollment(Rust API)¶
- 🟣
Twitter::with_xchat_keystore(path)—— 启用 XChat 客户端的入口 - 🟣
Twitter::xchat() -> Option<&XChatClient>—— 访问 XChat 服务 XChatServicetrait(async):enroll(pin)— 完整 enrollment 流程(5 步原子,含失败回滚)reset_pin(old_pin, new_pin)— 更换 PINdelete_account(pin)— 注销(清服务端公钥 + Juicebox 份额 + 本地文件)is_enrolled()/status()— 查询状态- GraphQL endpoint 封装(
src/x/xchat/api.rs): add_xchat_public_key/get_public_keys/delete_xchat_public_key/generate_xchat_token- 严格匹配官方 wire envelope
{"variables": "<json-string>"}(不带 operationName/extensions) - Juicebox SDK 集成(
src/x/xchat/juicebox.rs): - 通过
juicebox_sdkv0.3.4(git tag,X 服务端同版本) XChatJuicebox::from_x_server_payload(config_json, tokens, user_id, num_guesses)工厂- register / recover / delete 三套封装
RecoverError穷尽 match →XChatError::{PinIncorrect, NotEnrolled, JuiceboxError}- 错误模型扩展:
EnrollRollbackFailed(复合错误)、ApiError { op, status, body }
Added — XChat v3 Phase 2:Python 绑定 + CLI(step 4)¶
- Python:
src/python/xchat/—PyXChatClient、PyEnrollResult、PyXChatStatus - 方法:
enroll/reset_pin/delete_account/is_enrolled/status - 静态构造:
XChatClient.create(cookies, keystore_path, ...) Twitter新增xchat(keystore_path)方法(方法非属性,需传文件路径)- CLI:
twitter-cli xchat <action>—enroll/status/reset-pin/delete-account - PIN 通过
--pin或环境变量传入;hide_env_values=true防--help泄露 delete-account用独立的XCHAT_CONFIRM_PIN环境变量(不复用XCHAT_PIN)+ 强制--yes- 输出 Envelope JSON(与 settings 模块对齐);
XChatError16 变体穷尽映射到 cliErrorCode - Twitter facade:新增
build_xchat_client(&self, path)非消耗式构造(供 Python/CLI 共用同一 HTTP 客户端) - 文档:
docs/api/xchat-enrollment.md用户级注册指南;mkdocs nav 加入 - 示例:
examples/python/xchat/{enroll,status}.py
Fixed — Phase 2 code-reviewer 反馈¶
- 🔴 CRITICAL:
XChatError::ApiError的Display不再输出body字段, 避免响应体内容(可能含 GraphQL variables / user_id)经 Python 异常消息泄露 - 🟡
PyXChatClient.delete_account增加 PIN 非空校验(与enroll/reset_pin对齐) - 🟡
XCHAT_PIN/XCHAT_CONFIRM_PIN分离:防止enroll环境变量被delete-account静默复用 - 🟡 ErrorCode 映射语义校准:
ConversationKeyMissing/KeyVersionMismatch改Server(不是参数问题),Io/KeyFileNotFound改Unknown(不是配置解析问题),EnrollRollbackFailed改Unknown(防 AI agent 把可重试 Server 当循环重试入口) - 🟢
PyTwitter.__repr__区分 xchat 是方法而非属性(避免client.xchat误访问)
Internal — Phase 2¶
- 测试增量:258 → 260(+10 自 Phase 1 末尾)
- 不涉及任何破坏性 API 变更
- Python 绑定 / CLI 子命令 / 真账号集成测试留待下一轮
- 详见
.claude/plans/pending-test/20260511-160002-XChat-v3-Phase2-Enrollment.md
Added — XChat v3 Phase 3.0:发送(明文 + 签名最小路径)¶
XChatService::send_message(recipient_user_id, text)—— 1v1 私信发送最小路径- 用 Phase 1 已字节级验证的
encode_text_message_event+build_signature_payload- ECDSA P-256 sign +
encode_signature_envelope全套原语组装请求
- ECDSA P-256 sign +
- 明文路径:与 Phase 1 抓包一致;收件人未 enrolled 时返回
XChatSendResult { warning: Some("recipient_not_enrolled"), ... }, 消息以明文 Thrift + ECDSA 签名发送(官方 web 在该场景的等价行为) - 收件人已 enrolled 时返回
warning: Some("encryption_not_implemented"), SDK 仍然发送(服务端会返回 event_id),但消息在收件人客户端会被静默丢弃 —— 完整加密留 Phase 3.1 api::send_message_mutation—— SendMessageMutation GraphQL 调用- 服务端
encoded_message_event响应解码(event_id / sender_id / conversation_id / timestamp) SEND_MESSAGE_URL端点常量 ——LkAIEchf8AGj-WgeLoTVcw/SendMessageMutationXChatSendResult类型 ——success / message_id / event_id / sequence_id / timestamp_ms / warning / conversation_key_versionextract_inner_payloadhelper —— 从encoded_message_create_event外层 86 字节 中剥离 field 100 包装的 63 字节内层子结构作为签名 payload 输入(实测 X 服务端 签名输入用的是内层而非外层)- 测试增量:+3 新单元测试(
extract_inner_payload_matches_phase1_csv_fixture/extract_inner_payload_rejects_malformed/send_message_signing_pipeline_round_trips) - 全库
cargo test --lib --features xchat:263 / 263 通过
Phase 3.0 不在范围内(留下一轮)¶
- ❌ Phase 3.1:AES-GCM 加密(双方均 enrolled 时)+
AddEncryptedConversationKeysMutation - ❌
send_batch/send_batch_with_custom_texts - ❌
receive_messages(GetConversationPage / GetMessageEventsBySequenceId 解密+验签) - ❌ Python / CLI 绑定本 phase 范围内(待 Phase 3.1 完整 API 稳定后一并接入)
- ❌ 真账号集成测试(待 Phase 3.1 加密路径就绪)
[1.0.15] - 2026-05-09¶
Fixed¶
- 🔴 修复高并发 DM 消息重复发送 bug(数据正确性,影响所有写操作)
- 现象:
send_batch/send_batch_v2在 200 并发场景下,每个收件人会收到 2 条相同私信(实测出现 11-19 条重复) - 根因:rquest 5.x
ClientBuilder默认http2_max_retry_count = 2。Twitter 在限流时返回 H2RST_STREAM(REFUSED_STREAM),rquest 误认为请求未发出而自动重发;但 Twitter 实际已处理首次请求,导致同一条 DM 被发送 2 次 - 影响范围:所有走
RquestClient的写操作(DM v1/v2、发帖、点赞、转发、关注、上传),高并发场景下都可能出现重复 - 修复:
RquestClientBuilder默认http2_max_retry_count = 0(禁用 H2 自动重试) - 升级建议:从 v1.0.14 及更早版本升级时,调用 DM 批量接口的代码无需改动,行为自动变为安全默认
Added¶
RquestClientBuilder::http2_max_retry_count(usize)— 暴露 HTTP/2 自动重试配置- 默认值
0(禁用)—— 对非幂等写操作(DM/Posts/Like)安全 - 高级用户(仅纯读场景)可设为
1-2提升可用性 - DM/Posts 等写操作场景必须保持 0,否则会触发上述重复发送 bug
- 详见
docs/api/configuration.md docs/api/configuration.md— 新增 SDK 配置项参考文档(默认值/风险/何时改)
Changed¶
- DM trait 和 Python binding docstring 添加 H2 重试警告
DMServicetrait 顶部说明非幂等性 + 配置要求PyDMClient类、send_batch*系列方法添加高并发提示- 🔴 DM 发送成功判定收紧(破坏性语义变更)
- 旧逻辑:HTTP 200 + 无 errors 数组 → 判定 success(即使
event_id缺失) - 新逻辑:必须返回有效
event_id才判定 success - 影响:原来被误判为成功但实际未送达的请求(对方隐私设置/拉黑/账号失效/反垃圾过滤静默拦截)现在会标记为 failure,
error_msg = "Twitter 未返回 event_id..." - 受影响方法:
send_message/send_message_v2/ 所有send_batch*系列 - 升级建议:调用方原本依赖
result.success的统计会变得更真实,可能从 99% 降到实际投递率(如 14/150)。这是修复,不是回归 - 背景:此前
send_batch_v2报告 149/150 成功,但 inbox 只新增 14 个对话——135 个虚假成功掩盖了真实投递率
[1.0.14] - 2026-05-09¶
Added¶
- Posts 模块:
clear_bookmarks()— 一次性清空当前账号所有书签 - GraphQL:
POST /i/api/graphql/skiACZKC1GDYli-M8RzEPQ/BookmarksAllDelete - 新增类型:
ClearBookmarksResult(成功时message="Done") - Python 绑定:
await client.posts.clear_bookmarks() - CLI:
twitter-cli posts clear-bookmarks - Posts 模块:
TweetInfo扩 4 字段用于识别转推/评论类推文 retweeted_status_id_str— 被转推的原推 IDin_reply_to_status_id_str— 被回复的推文 IDin_reply_to_user_id_str— 被回复用户 IDin_reply_to_screen_name— 被回复用户的 screen_name- 用法:「删除转推」调用
delete_retweet(retweeted_status_id_str); 「删除评论」用in_reply_to_status_id_str.is_some()筛选后调用delete_tweet(tweet_id) - 同步更新:
src/x/search/client.rs两处TweetInfo构造、PyTweetInfo - User 模块:
block(user_id)/unblock(user_id)— 拉黑/解除拉黑 - REST:
POST api.x.com/1.1/blocks/{create,destroy}.json,form:user_id=<id> - 新增类型:
BlockResult、UnblockResult - Python:
await client.user.block(uid)/unblock(uid) - CLI:
twitter-cli user block --user-id <ID>/unblock --user-id <ID> - 「强制清除粉丝」可通过 block→unblock 组合实现(拉黑会强制对方取消关注)
- User 模块:
remove_profile_image()/remove_profile_banner()— 还原默认头像/Banner - REST:
POST api.x.com/1.1/account/remove_profile_{image,banner}.json - 新增类型:
RemoveProfileImageResult、RemoveProfileBannerResult - CLI:
twitter-cli user remove-profile-image/remove-profile-banner - User 模块:
get_user_lists(count)— 获取已加入/订阅的 Lists - GraphQL:
GET /i/api/graphql/MZPMTf6uAC8bCYZmoiqVVg/ListsManagementPageTimeline - 新增类型:
GetUserListsResult、ListInfo(含 id_str/name/mode/owner/following 等字段) - CLI:
twitter-cli user get-lists [--count 100] - User 模块:
unsubscribe_list(list_id)— 退订指定 List - GraphQL:
POST /i/api/graphql/-diULb6PX5grQ_MvItGiJQ/ListUnsubscribe - 新增类型:
UnsubscribeListResult - CLI:
twitter-cli user unsubscribe-list --list-id <ID>
Notes¶
- 上述补全了原始功能清单中缺失的 6 个新方法 + 1 个类型扩展
- 所有 GraphQL queryId 均经过 chrome-devtools 实地抓包验证(2026-05-08)
- 新增集成测试:
integration_clear_bookmarks/integration_block/integration_remove_profile/integration_lists - 已实测通过:
get_user_lists、clear_bookmarks、unsubscribe_list、block(含 404 容错)
Planned¶
- CLI 剩余业务 action:user 补齐(profile / followers / following / follow / unfollow / edit / set-avatar / set-banner)、search 补齐(people / media / lists)、settings set 系列、posts reply、upload video
- F3 maturin pip bundle 方案(需要研究
bundle_bin或 post-install hook,A0 POC 已证明--features cli,python不能共存) - D4
docs/cli/reference.md从twitter-cli output-schema自动生成 - aarch64-unknown-linux-gnu 预编译(需配置 Cross.toml pre-build 钩子安装 cmake/nasm)
- Node 20 → Node 24 action 升级(2026-06-02 GitHub 强制切换前完成)
[1.0.13] - 2026-04-28¶
Fixed¶
- ClientTransaction 初始化失败:
无法提取 KEY_BYTE indices(src/common/transaction/client.rs:60)—— X.com 重新混淆ondemand.s后,承载 key byte 索引的临时变量名从n[..]变为t[..],导致旧正则n\[(\d{1,2})\],16在新 JS 中无任何匹配,所有走 GraphQL 的接口 (get_about_account/get_profile/create_tweet/search等) 在创建Twitter客户端阶段直接 panic。改用\(\w\[(\d{1,2})\],\s*16\)通配单字母变量名并锚定到完整的(X[N],16)形态以避免误匹配, 与上游 iSarabjitDhiman/XClientTransaction 最新策略保持一致。新版 JS 抽出 indices[1, 24, 28, 42](row_index=1,key_bytes_indices=[24, 28, 42])
Tests¶
cargo test --lib test_extract_key_byte_indices_from_js单元测试 fixture 为带括号格式(n[7],16),新正则向后兼容cargo test --test integration_user test_get_about_account -- --ignored实证通过(@elonmusk → rest_id=44196397)
[1.0.12] - 2026-04-21¶
Fixed¶
- Posts 发帖长度校验误判 (
src/x/posts/client.rs:468):params.text.len()返回 UTF-8 字节数而非字符数,导致含中文/emoji 的推文被误判超过 280 限制 (单个中文占 3 字节 → 93 个中文就会触发假超限)。改为按 Twitter 官方 twitter-text 库的加权规则计数:URL 固定 23、CJK/emoji/全角标点每字符 2、 Basic Latin 每字符 1。新增twitter_weighted_length()函数 +TWITTER_URL_LENGTH=23常量并从src/x/posts/mod.rs导出 - DM 私信长度校验同类 bug:
- Rust 业务层
src/x/dm/client.rs的validate_message/validate_message_with_media/validate_batch_messages3 处text.len()改为text.chars().count(),匹配 Twitter DM 的 10000 字符(非字节)限制 - Python 绑定层
src/python/dm/client.rs共 6 个发送方法 (send_message/send_message_v2/send_batch/send_batch_v2/send_batch_with_custom_texts/send_batch_with_custom_texts_v2) 全部同步修复 - 修复前中文 DM 用户实际可发送字符数被压缩至 ~3333(10000 字节 / 3 字节每字符),修复后恢复到 10000 中文字符
- 4 处
__repr__UTF-8 字节索引 panic bug:src/python/inbox/types.rs:177,546、src/python/dm/types.rs:213、src/python/posts/types.rs:64使用&self.text[..30]按字节切片,遇中文/emoji 落在多字节 UTF-8 字符中间时 Rust panic。Python 侧调repr()或打印对象含 11 个以上中文即稳定触发 (posts 阈值为 7 个中文)。改用新增common::text::truncate_preview()按 Unicode codepoint 安全截断 src/common/auth.rs:240的ct0日志截断虽实际安全(CSRF token 是 ASCII hex) 但为避免未来误用,顺手改用同一辅助函数
Added¶
src/common/text.rs新增truncate_preview(s, max_chars)辅助函数——按 Unicode codepoint 数安全截断字符串,附 7 个单元测试(ASCII / CJK / emoji / 混合 / 零长度 / 字节边界回归)
Tests¶
src/x/posts/types.rs新增 7 个length_tests单元测试:ASCII / CJK / 全角标点 / URL 固定 23 / 混合文本 / 真实中文长推回归 / emojisrc/x/dm/client.rs新增 2 个 CJK 回归测试:9999 中文字符应通过、 10001 中文字符应拒绝- 测试总数 177 → 193,全部通过
Changed¶
- Docker 构建从 ~80min 降至 ~15min:
cli-docker.yml重写为 matrix 并行 双 runner 原生编译(amd64 onubuntu-latest+ arm64 onubuntu-24.04-armGitHub 免费 ARM64 runner)+ push-by-digest + manifest 合并模式。砍掉 QEMU 用户态模拟(单架构慢 3-5 倍)+ 双架构串行叠加的双重损失。参考 Docker 官方 multi-platform 最佳实践
CI / Infrastructure¶
- 修复 PyPI 发版门禁 403 bug:
build-and-release.yml的wait-for-cli-pipelinesjob 调gh api查询 workflow 状态时,GITHUB_TOKEN缺少actions: read权限导致 403,门禁 job 自己 crash 并误触发"失败阻止 PyPI"假阳性(1.0.11 CHANGELOG 声称 "已验证工作"实际是假阳性)。已补齐权限 github-releasejob 新增wait-for-cli-pipelines依赖: 与 PyPI 发布约束对齐,防止 GitHub Release 创建了含 wheel 的 artifact 包但 CLI binary / Docker 实际失败的半成品状态
Removed¶
- 停止支持 Intel macOS 预编译产物(源码安装仍可用)
cli-release.yml:删除x86_64-apple-darwin/macos-13matrix 条目build-and-release.yml:macOS PyPI wheel 从universal2(x86_64+arm64 合体)降级为aarch64-apple-darwin(纯 Apple Silicon),MACOSX_DEPLOYMENT_TARGET10.13 → 11.0- 原因:GitHub 免费 Intel macOS runner 池从 2025 年起严重短缺,
1.0.12 首次发版时
macos-13runner 排队 10h+ 未分配,永久卡住 整条 CLI release 流水线。Apple 已于 2023 年停产 Intel Mac - 影响:Intel Mac 用户需通过
cargo install x-api-rs --features cli或pip install x-api-rs(sdist 源码构建)使用本项目
[1.0.11] - 2026-04-20¶
1.0.11 是 1.0.9 / 1.0.10 的补丁重发——前两版 PyPI wheel 已发布成功但
Release twitter-cli(binary 预编译)和 Build & Push twitter-cli Docker
两个 workflow 都失败,导致 CLI 组件缺失。由于 PyPI 同版本号不可重传,
本版累积前两版的全部功能 + 所有 CI 修复。
Added¶
- twitter-cli 21 个 action 全部可用(1.0.9 功能:dm / posts / user / search / upload / settings / 诊断命令 + shell 补全;见下方 1.0.9 [yanked] 条目)
- PyPI 发版门禁:
build-and-release.yml新增wait-for-cli-pipelinesjob,PyPI 上传前强制等待cli-release.yml+cli-docker.yml全部 success。任一 CLI 流水线失败则阻止 PyPI 发布(防止 1.0.9/1.0.10 类型的 "版本号存在但 CLI 组件缺失"破损状态) - CLAUDE.md 新增"🚨 发版失败必须删除重试"小节,文档化 PyPI 占位规则与
gh release delete --cleanup-tag标准恢复流程
Fixed¶
Dockerfile.cli:新增git依赖——boring-sys2的ensure_patches_applied()调git init初始化 BoringSSL 源码目录并 apply patch;rust:1-slim-bookworm默认不含 gitDockerfile.cli:补齐cmake/nasm/ninja-build/build-essential依赖,移除不稳定的 stub 预热层cli-release.yml:macOSsha256sum命令不存在,改为shasum -a 256fallback(macOS + Linux 通用)cli-release.yml:macOS 加brew install cmake nasm ninja、 Linux 加apt-get install nasm ninja-build、Windows 加choco install nasmcli-check.yml:clippy 从-D warnings放宽到-D clippy::correctness,避免历史代码的 style/pedantic 风格 lint 污染 CI。CLI 核心 API 设计 lint(clippy::result_large_err)通过src/cli/mod.rs顶部#![allow]抑制cli-check.yml:新增 Install build deps step 补充 nasm/ninja-buildcargo fmt --all --check:24 个历史文件批量 rustfmt 统一(主要是 src/x/search、src/x/settings、src/cli 等模块的格式漂移),纯风格调整 无功能变化cargo clippy --fix:自动修复 5 处 lint(io::Error::other简化、redundant_closure、needless_borrow等)
Changed¶
.github/workflows/cli-release.yml:暂时移除aarch64-unknown-linux-gnucross 编译 target——cross默认镜像缺 cmake/nasm/git 构建依赖,需 维护Cross.toml的 pre-build 钩子。作为已知 TODO 保留在 [Unreleased]
[1.0.10] - 2026-04-20 [YANKED]¶
⚠️ 此版本已作废:CLI binary 预编译 + Docker 镜像构建失败,GitHub Release 和 GHCR 均无有效 artifact,仅 PyPI wheel 成功上传。请使用 1.0.11。
PyPI 因同版本号占位规则无法删除,此版号永久保留。
[1.0.9] - 2026-04-18 [YANKED]¶
⚠️ 此版本已作废:CLI binary 预编译 + Docker 镜像构建失败,CLI 组件缺失。请使用 1.0.11。
PyPI 因同版本号占位规则无法删除,此版号永久保留。
Added¶
- Phase B1 剩余 action:
twitter-cli dm send-batch三段式 BatchLine 输出(header + items + summary)+twitter-cli dm inbox --max N - Phase B 只读批次:
twitter-cli user get --screen-name <NAME>/user get-by-id --rest-id <ID>twitter-cli search top --query <Q>/search latest --query <Q>(均支持--cursor)twitter-cli settings get(合并 sensitive + account 两组设置)- Phase B 写操作批次:
twitter-cli posts create --text <S> [--media-ids <IDS>] [--reply-to <ID>]twitter-cli posts {delete,like,unlike,retweet,unretweet} --id <ID>twitter-cli upload image --file <PATH> [--category tweet-image|dm-image|banner-image]- Phase B9 诊断命令(5 个,全部无需认证或仅需 cookies):
twitter-cli doctor综合诊断(cookies + API 连通性 + 版本),返回api_latency_mstwitter-cli auth-check仅校验 cookies 解析twitter-cli version-full详细版本(cli / schema / lib / git_sha / target)twitter-cli output-schema完整 JSON Schema(12 ErrorCode + 13 退出码 + 递归 clap Command 树)twitter-cli example <module> <action>打印硬编码 JSONL 示例- Phase G5 shell 补全:
twitter-cli completion bash|zsh|fish|powershell|elvish,通过clap_complete::generate自动派生 - CLI 完整文档体系(
docs/cli/,11 文件 3018 行):README / getting-started / output-schema / 6 份模块文档 / llms.txt / llms-full.txt - Phase E 测试基础设施:
tests/cli/integration_cli.rs10 个黑盒集成测试(assert_cmd+predicates)tests/cli/integration_cli_snapshot.rs5 个insta快照测试(锁定 Envelope / BatchLine / ErrorCode 输出格式)- Phase F 分发流水线:
.github/workflows/cli-release.yml5 平台预编译 matrix(macOS x86_64/arm64、Linux x86_64/aarch64 via cross、Windows x86_64)+ 自动 GitHub Release + SHA256 校验和.github/workflows/cli-docker.ymldistroless 镜像,多平台linux/amd64 + linux/arm64推送到ghcr.io/open-luban/x-api-rs/twitter-cli.github/workflows/cli-check.ymlfmt + clippy-D warnings+ 全测试集 +cargo publish --dry-runDockerfile.cli多阶段构建(rust:1-slim-bookworm→distroless/cc-debian12)- README 新增"twitter-cli — 面向 AI agent 的命令行"独立段落,补全 search / settings 到功能模块表
Changed¶
- 单元测试总数 233 → 243(含诊断命令 clap 解析和内部测试)
- CLI 现支持 21 个 action:15 业务 + 6 诊断/自省
Fixed¶
- (无 bug 修复,纯功能迭代)
[1.0.8] - 2026-04-17¶
Added¶
- twitter-cli 命令行二进制:面向 AI agent 的 Twitter API 工具,Phase A 基础设施 + Phase B 高频 action 实装
- Phase A0:
cdylib+[[bin]]链接兼容性 POC 通过(macOS arm64),cli单独构建路径完全可用 - Phase A1:新增
clifeature,级联全部 8 业务模块 +clap/clap_complete/dirs/toml/libc可选依赖 - Phase A3:输出包络
Envelope<T>(带meta.schema_version)、BatchLine<T>三段式(header / item / summary)、12 变体ErrorCode(穷尽匹配TwitterError,禁止_ => Unknown兜底)、ErrorInfo扩展字段(docs_url/recovery_actions/retry_after_secs/issue_url)、JsonlWriter线程安全写入器 - Phase A4:四源认证链(
--cookies-file> stdin[5s 超时] >X_API_COOKIES_FILEenv >~/.x-api/config.toml[version=1 校验])+ cookies 文件权限检查(>0o644 拒绝、>0o600 warn)+ proxy URL sanitize 防凭据泄漏 - Phase A5:stderr tracing 初始化(
--log-level+--log-format json|compact)、SIGPIPE 恢复默认处理避免panic=abort产生 core dump - dm 模块:
send/send-batch(三段式 BatchLine)/inbox - posts 模块:
create/delete/like/unlike/retweet/unretweet - user 模块:
get --screen-name/get-by-id --rest-id - search 模块:
top/latest(含--cursor分页) - upload 模块:
image(含tweet-image/dm-image/banner-image三种 category) - settings 模块:
get(合并 sensitive + account 两组设置) - CLI 文档体系(
docs/cli/): README.mdCLI 索引getting-started.md安装与认证入门(4 种认证方式)output-schema.mdEnvelope / BatchLine / ErrorCode / 退出码完整规范- 6 份模块命令文档(dm / posts / user / search / upload / settings)
llms.txt/llms-full.txtAI 可读参考- CLI 单元测试 56 条(参数解析 / Envelope 序列化 / ErrorCode 映射 / 认证链 / 权限检查)
- CLAUDE.md 新模块接入 SOP 新增 CLI 绑定步骤(第 4.5 步)和 Phase 2.6 检查项
Changed¶
- 单元测试总数 177 → 233(CLI feature 启用时)
Cargo.toml引入[[bin]] twitter-cli声明,required-features = ["cli"]隔离
[1.0.7] - 2026-04-08¶
Added¶
- 新增 Search(搜索采集)模块,支持热门、最新、用户、媒体、Lists 五种搜索类型
- 补全 TweetInfo 的 media_urls 字段解析(图片/视频/GIF)
- 搜索结果支持 cursor 分页
- Search 模块 Python 绑定(
client.search.search_top()等) - Search 模块 API 文档和示例文档
- TweetInfo 新增 10 个字段:
lang、possibly_sensitive、views_count、conversation_id_str、is_quote_status、quoted_status_id_str、author_display_name、author_profile_image_url、author_is_blue_verified、author_followers_count - TweetResult 新增
tweet_info: Option<TweetInfo>字段,create_tweet 成功时自动填充完整推文信息 - 通用响应 dump 工具(
DUMP_RESPONSES环境变量开关,调试用)
Fixed¶
- 修复 SearchTimeline GraphQL query ID 过期问题(404)
- 修复 Media 搜索解析:适配 grid 布局(
search-gridentry + items 数组) - 修复 Lists 搜索解析:适配 module 布局(
list-searchentry + items 数组)
1.0.6 - 2026-03-29¶
Added¶
- Settings 模块:新增账号隐私设置功能
- 账号敏感内容设置(
nsfw_user、display_sensitive_media) - 搜索安全设置(
optInFiltering、optInBlocking) - 查询当前设置状态
- 完整的 Python 绑定和强类型响应
- code-docs-guardian 文档守护 skill(被动监控 + 主动审查两种模式)
- Settings 模块完整文档和示例
Fixed¶
- mkdocs.yml 导航菜单补全 Settings 模块
1.0.5 - 2026-03-24¶
Fixed¶
- 更新 Followers/Following GraphQL query ID(适配 Twitter API 变更)
- 新增批量采集 followers/following 测试
Added¶
- llms.txt 和 llms-full.txt(AI 可读的 API 文档)
- Cloudflare Pages 配置
/llms.txt根路径重定向
Changed¶
- 补充文档网站维护规范和 DM v2 注意事项
- 修复文档与实际 API 不一致的问题
1.0.4 - 2026-03-18¶
Changed¶
- Transaction 模块适配 webpack manifest 新格式,拆分提取逻辑
1.0.3 - 2026-03-13¶
Added¶
- DM v2 接口:实现私信 v2 发送(
send_message_v2、send_batch_v2、send_batch_with_custom_texts_v2) - DM v2 支持纯图片发送(text 为空 + media_id)
- DM v2 GraphQL API 支持媒体附件发送
- XChat 对话列表接口(
get_conversations) - 多账号批量发送 + 收件箱验证测试
Changed¶
- 统一异步并发模式,修复多项 async 问题
- 代码质量优化两轮(clippy 修复、DRY、性能改进),达到 A- 标准
- DM 客户端按 Rust 最佳实践重构
- 标记
get_user_updates为弃用,改用get_conversations
Fixed¶
- GraphQL DM 响应解析,正确提取 event_id
- GraphQL API 域名改为 api.x.com
- 补全 inbox 新类型的 Python 导出和类型存根
- 关注用户功能实现
1.0.2 - 2026-02-14¶
Fixed¶
- CreateTweet 226 反爬检测及多图上传问题
- rquest 5.x multipart 请求的 Content-Type 问题
- Emulation default_headers 合并陷阱(清空 default_headers 解决)
Changed¶
- Python 导出方式重构以符合社区惯例
- CI/CD 流水线迁移到组织 self-hosted runner
- PyPI 发布迁移到 ubuntu-latest
- macOS 构建按需安装 cmake
1.0.1 - 2026-01-23¶
Added¶
- Communities 模块:社区搜索和加入功能
search_communities()方法,支持分页join_community()方法Community、CommunitySearchResult、JoinCommunityResult强类型- User 模块:新增用户资料获取、编辑、头像/Banner 更换
- Transaction 模块:动态生成 X-Client-Transaction-Id
- Twitter 类构造函数改为异步
create()方法 - 跨平台编译支持(Linux Docker 构建)
Changed¶
- UserClient 构造函数改为异步创建方法
- HTTP 客户端和请求头生成逻辑重构
- 移除批量发送私信时的 client_transaction_ids 参数
1.0.0 - 2026-01-15¶
Added¶
- DM 模块:Twitter 私信发送核心功能
- 单条私信发送
- 批量并发发送
- 批量自定义文案发送(
send_batch_with_custom_texts) - Upload 模块:三阶段图片上传(INIT -> APPEND -> FINALIZE)
- 批量上传(MD5 扰动获取多个独立 media_id)
- 支持带图片的私信
- Posts 模块:发帖、删帖、点赞功能
- Inbox 模块:收件箱功能
- User 模块:用户资料获取
- Fingerprint 模块:浏览器指纹随机化
- 基于 Cookies 的认证机制
- rquest HTTP 客户端(JA3/JA4 指纹模拟)
- 完全异步的 Python 绑定(PyO3)
- 模块化 Python API(
client.dm.send_message()风格) - GitHub Actions 构建与发布工作流
- macOS universal2 + Linux aarch64 wheels 构建