Changelog¶
所有版本变更记录。格式基于 Keep a Changelog。
[Unreleased] — 面向 v2.0.0¶
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 构建