跳转至

Changelog

所有版本变更记录。格式基于 Keep a Changelog


[Unreleased] — 面向 v2.0.0

Fixed — XChat v3 Phase 3.1 P0 wire 矫正(基于 2026-05-12 真实抓包)

  • 🔴 AES-GCM IV 长度修正:12B → 16Bsrc/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 但当前不接受加密 DM
    • keys 非空 + can_dm_on_xchat=true → 加密路径
  • 接口契约更新XChatService trait、XChatSendResult struct、PyXChatSendResult pyclass、Python send_message method 四处 docstring 列出新值

  • encoded_message_create_event outer 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、AddEncryptedConversationKeysMutation variables 等)保持现状

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.f100 inner_payload + looks_encrypted 启发式
  • thrift::decode_plaintext_message_text(inner_payload) -> String —— 明文路径文本提取(用 973_GetInitialXChatPage.network-response fixture 字节级验证:"Are you a victim of scam")
  • api::get_conversation_page —— GraphQL IVlXls9JTnbgQ1gxsGAfJA/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,cli361 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) -> XChatSendResult
  • send_batch(user_ids, text) -> XChatBatchResult
  • send_batch_with_custom_texts(user_ids, texts) -> XChatBatchResult
  • 全部异步(future_into_py);参数预检:text.trim().is_empty()ValueError,避免与 RuntimeError 语义混淆
  • 新增 PyXChatSendResult / PyXChatBatchResult pyclass
  • CLI (twitter-cli xchat):
  • send --user-id <ID> --text <TEXT> --keystore <PATH>:单条 Envelope
  • send-batch --user-ids u1,u2,u3 --text <TEXT> --keystore <PATH>:统一文案,三段式 BatchLine
  • send-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_establishAddEncryptedConversationKeysMutation → 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 KEK
  • wrap_conversation_key / unwrap_conversation_key —— AES-GCM-256 包/解 conv key
  • encode_encrypted_payload / decode_encrypted_payload —— 消息载荷 AES-GCM 加解密(AAD=conversation_id_bytes
  • api::add_encrypted_conversation_keys —— AddEncryptedConversationKeysMutation 封装(queryId 4V1KC8ue2tHHvRuIzeczdg
  • 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_bytesZeroizing<[u8;32]> 包裹
  • H-2 DecryptionFailed::reason 不再出现在 Display(避免日志/异常泄漏)
  • M-1 key_versionu64 parse 排序而非字典序
  • 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:基础设施

  • 🟣 新增 xchat feature(默认启用)— 引入 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.rsbuild_signature_payload(CSV 域分隔,含逗号字段拒签)
  • keystore.rsXChatKeyMaterial + XChatKeyStore(JSON 文件 + 0600 权限 + 原子写入 + Zeroize
  • error.rsXChatError 错误枚举(thiserror,14 个变体),透过 TwitterError::XChat 透传
  • src/common/text_validation.rs — 抽取 dm 模块的文本/批量验证逻辑供 xchat 复用,dm v1/v2 行为零变化
  • 新增依赖xchat feature 下):p256 aes-gcm hmac hkdf spki pkcs8 zeroize rand_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 服务
  • XChatService trait(async):
  • enroll(pin) — 完整 enrollment 流程(5 步原子,含失败回滚)
  • reset_pin(old_pin, new_pin) — 更换 PIN
  • delete_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_sdk v0.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)

  • Pythonsrc/python/xchat/PyXChatClientPyEnrollResultPyXChatStatus
  • 方法:enroll / reset_pin / delete_account / is_enrolled / status
  • 静态构造:XChatClient.create(cookies, keystore_path, ...)
  • Twitter 新增 xchat(keystore_path) 方法(方法非属性,需传文件路径)
  • CLItwitter-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 模块对齐);XChatError 16 变体穷尽映射到 cli ErrorCode
  • 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 反馈

  • 🔴 CRITICALXChatError::ApiErrorDisplay 不再输出 body 字段, 避免响应体内容(可能含 GraphQL variables / user_id)经 Python 异常消息泄露
  • 🟡 PyXChatClient.delete_account 增加 PIN 非空校验(与 enroll / reset_pin 对齐)
  • 🟡 XCHAT_PIN / XCHAT_CONFIRM_PIN 分离:防止 enroll 环境变量被 delete-account 静默复用
  • 🟡 ErrorCode 映射语义校准:ConversationKeyMissing / KeyVersionMismatchServer(不是参数问题),Io / KeyFileNotFoundUnknown(不是配置解析问题),EnrollRollbackFailedUnknown(防 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 全套原语组装请求
  • 明文路径:与 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/SendMessageMutation
  • XChatSendResult 类型 —— success / message_id / event_id / sequence_id / timestamp_ms / warning / conversation_key_version
  • extract_inner_payload helper —— 从 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 在限流时返回 H2 RST_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 重试警告
  • DMService trait 顶部说明非幂等性 + 配置要求
  • 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 — 被转推的原推 ID
  • in_reply_to_status_id_str — 被回复的推文 ID
  • in_reply_to_user_id_str — 被回复用户 ID
  • in_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>
  • 新增类型:BlockResultUnblockResult
  • 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
  • 新增类型:RemoveProfileImageResultRemoveProfileBannerResult
  • CLI:twitter-cli user remove-profile-image / remove-profile-banner
  • User 模块:get_user_lists(count) — 获取已加入/订阅的 Lists
  • GraphQL: GET /i/api/graphql/MZPMTf6uAC8bCYZmoiqVVg/ListsManagementPageTimeline
  • 新增类型:GetUserListsResultListInfo(含 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_listsclear_bookmarksunsubscribe_listblock(含 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.mdtwitter-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 indicessrc/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.rsvalidate_message / validate_message_with_media / validate_batch_messages 3 处 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 bugsrc/python/inbox/types.rs:177,546src/python/dm/types.rs:213src/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:240ct0 日志截断虽实际安全(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 / 混合文本 / 真实中文长推回归 / emoji
  • src/x/dm/client.rs 新增 2 个 CJK 回归测试:9999 中文字符应通过、 10001 中文字符应拒绝
  • 测试总数 177 → 193,全部通过

Changed

  • Docker 构建从 ~80min 降至 ~15mincli-docker.yml 重写为 matrix 并行 双 runner 原生编译(amd64 on ubuntu-latest + arm64 on ubuntu-24.04-arm GitHub 免费 ARM64 runner)+ push-by-digest + manifest 合并模式。砍掉 QEMU 用户态模拟(单架构慢 3-5 倍)+ 双架构串行叠加的双重损失。参考 Docker 官方 multi-platform 最佳实践

CI / Infrastructure

  • 修复 PyPI 发版门禁 403 bugbuild-and-release.ymlwait-for-cli-pipelines job 调 gh api 查询 workflow 状态时, GITHUB_TOKEN 缺少 actions: read 权限导致 403,门禁 job 自己 crash 并误触发"失败阻止 PyPI"假阳性(1.0.11 CHANGELOG 声称 "已验证工作"实际是假阳性)。已补齐权限
  • github-release job 新增 wait-for-cli-pipelines 依赖: 与 PyPI 发布约束对齐,防止 GitHub Release 创建了含 wheel 的 artifact 包但 CLI binary / Docker 实际失败的半成品状态

Removed

  • 停止支持 Intel macOS 预编译产物(源码安装仍可用)
  • cli-release.yml:删除 x86_64-apple-darwin / macos-13 matrix 条目
  • build-and-release.yml:macOS PyPI wheel 从 universal2 (x86_64+arm64 合体)降级为 aarch64-apple-darwin(纯 Apple Silicon), MACOSX_DEPLOYMENT_TARGET 10.13 → 11.0
  • 原因:GitHub 免费 Intel macOS runner 池从 2025 年起严重短缺, 1.0.12 首次发版时 macos-13 runner 排队 10h+ 未分配,永久卡住 整条 CLI release 流水线。Apple 已于 2023 年停产 Intel Mac
  • 影响:Intel Mac 用户需通过 cargo install x-api-rs --features clipip 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-pipelines job,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-sys2ensure_patches_applied()git init 初始化 BoringSSL 源码目录并 apply patch;rust:1-slim-bookworm 默认不含 git
  • Dockerfile.cli:补齐 cmake / nasm / ninja-build / build-essential 依赖,移除不稳定的 stub 预热层
  • cli-release.yml:macOS sha256sum 命令不存在,改为 shasum -a 256 fallback(macOS + Linux 通用)
  • cli-release.yml:macOS 加 brew install cmake nasm ninja、 Linux 加 apt-get install nasm ninja-build、Windows 加 choco install nasm
  • cli-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-build
  • cargo fmt --all --check:24 个历史文件批量 rustfmt 统一(主要是 src/x/search、src/x/settings、src/cli 等模块的格式漂移),纯风格调整 无功能变化
  • cargo clippy --fix:自动修复 5 处 lint(io::Error::other 简化、 redundant_closureneedless_borrow 等)

Changed

  • .github/workflows/cli-release.yml:暂时移除 aarch64-unknown-linux-gnu cross 编译 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 剩余 actiontwitter-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_ms
  • twitter-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.rs 10 个黑盒集成测试(assert_cmd + predicates
  • tests/cli/integration_cli_snapshot.rs 5 个 insta 快照测试(锁定 Envelope / BatchLine / ErrorCode 输出格式)
  • Phase F 分发流水线
  • .github/workflows/cli-release.yml 5 平台预编译 matrix(macOS x86_64/arm64、Linux x86_64/aarch64 via cross、Windows x86_64)+ 自动 GitHub Release + SHA256 校验和
  • .github/workflows/cli-docker.yml distroless 镜像,多平台 linux/amd64 + linux/arm64 推送到 ghcr.io/open-luban/x-api-rs/twitter-cli
  • .github/workflows/cli-check.yml fmt + clippy -D warnings + 全测试集 + cargo publish --dry-run
  • Dockerfile.cli 多阶段构建(rust:1-slim-bookwormdistroless/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:新增 cli feature,级联全部 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_FILE env > ~/.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.md CLI 索引
  • getting-started.md 安装与认证入门(4 种认证方式)
  • output-schema.md Envelope / BatchLine / ErrorCode / 退出码完整规范
  • 6 份模块命令文档(dm / posts / user / search / upload / settings)
  • llms.txt / llms-full.txt AI 可读参考
  • 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 个字段:langpossibly_sensitiveviews_countconversation_id_stris_quote_statusquoted_status_id_strauthor_display_nameauthor_profile_image_urlauthor_is_blue_verifiedauthor_followers_count
  • TweetResult 新增 tweet_info: Option<TweetInfo> 字段,create_tweet 成功时自动填充完整推文信息
  • 通用响应 dump 工具(DUMP_RESPONSES 环境变量开关,调试用)

Fixed

  • 修复 SearchTimeline GraphQL query ID 过期问题(404)
  • 修复 Media 搜索解析:适配 grid 布局(search-grid entry + items 数组)
  • 修复 Lists 搜索解析:适配 module 布局(list-search entry + items 数组)

1.0.6 - 2026-03-29

Added

  • Settings 模块:新增账号隐私设置功能
  • 账号敏感内容设置(nsfw_userdisplay_sensitive_media
  • 搜索安全设置(optInFilteringoptInBlocking
  • 查询当前设置状态
  • 完整的 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_v2send_batch_v2send_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() 方法
  • CommunityCommunitySearchResultJoinCommunityResult 强类型
  • 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 构建