跳转至

生产环境版本更新

当前浏览的是 生产环境 文档,本页只列出生产版本的更新记录。

环境说明

用页面顶部的版本选择器切换到 测试通道后,这里会显示测试版本的更新记录。

当前版本

v0.4.1 · 2026-09-17

  • 提交:v0.4.0 之后的 1 个功能提交——b64fd32 WebUsersApi 新增 Followers / Following / UserByRestID,外加本 CHANGELOG
  • 变更:① WebUsersApi 新增 3 个只读 RPC:UserByRestID(按数字 id 查用户,GraphQL UserByRestId,走 api.x.com/graphql 且只发 variables)、Followers / Following(拉一页粉丝 / 关注列表,不自动翻页,调用方用 next_cursor 继续)。新增 message WebUserByRestIDRequest、WebUserListRequest(user_id / count / cursor)、WebUserListResponse(users / next_cursor / raw_json / rate_limit)。Web 平台 RPC 由 16 个增至 19 个。② 版本类:WebOperation 新增 APIHost,按 operation 区分发往 x.com 还是 api.x.com;两个列表 operation 复用时间线的 38 项 features(逐键逐值比对一致)。③ 用户资料解析统一:UserByScreenName、UserByRestID 与列表条目共用同一解析器,新扁平结构(core / avatar / relationship_counts 等)优先、legacy 回退;列表按 user- / cursor-bottom- 条目解析,0| 开头的游标视为到底,有用户条目却全部解不出时报 Undecodable 而不是静默返回空列表。④ 真机修正:UserByRestId 响应是 data.user 直接为用户对象(无 result、无 __typename);不存在的 id 回 200 且无 data,映射为 unavailable=true
  • 兼容性:无破坏性变更。proto 为纯加法(新增 3 个 RPC、3 个 message),既有 RPC 与字段未删改;WebUserResponse.unavailable 的注释扩展到覆盖 UserByRestID 的判定方式,语义不变。WebAccountState 与 AndroidAccountState 均未改动,存量 state 直接沿用。Python 门面只新增方法
  • 平台:web
  • 验证:由 scripts/release_tag.py create 本地离线门禁执行(gofmt / go mod verify / go vet / go test -race ./... / proto 生成物重跑零差异 / uv 锁定同步 / Python 离线测试 / 发布工具测试 / 公开文档校验 / SDK 三件产物干净环境安装 / 生产镜像构建 / 镜像种子装载冒烟)。llms_docs.py check 实测 396 RPC / 207 message / 5 workflow / 20 公开文档。新增离线用例覆盖资料解析的新旧两种形态、UserByRestId 的缺 data / 缺 rest_id 分支、列表条目与到底游标解析、operation 按 host 选路径
  • E2E:passed——2026-09-16 用自有测试账号 cookie 走真实链路、直连出口,3 个新 RPC 均真机验收通过,全部只读、无副作用:UserByRestID 查自身,返回 id 与 cookie 账号一致;Followers / Following 对公开账号各拉两页,cursor 翻页生效。实测:列表每页返回 50 条(请求 count=20 时上游仍回 50);配额(每账号 / 15 分钟)Followers 50、Following 500、UserByRestId 500。证据留本地 E2E 日志,未外发账号标识
  • 部署:GHCR 0.4.1 与 latest;文档站根目录与 /pro/0.4.1/。镜像相对 0.4.0 只增加 Web 用户查询 handler,种子池与 keybox 交付方式未变
  • Python SDK:0.4.1,wheel、sdist、可复制包三件产物。twitter_sdk.x.web 的 users 分组新增 by_rest_id()、followers()、following(),均为 async def;无删改
  • 已知限制:UserByScreenName 已改用共用解析器,本版只有离线双形态测试覆盖,未单独真机复测(列表条目走同一解析器并已真机通过,但 UserByScreenName RPC 本身未重跑)。协议来源为外部参考实现,无原始抓包;UserByScreenName 的请求参数与该参考实现存在分歧,已记录,暂不改。UserByRestID 是轻量查询,只回 id / 名字 / handle / 头像,计数类字段为 0,要完整资料仍需 UserByScreenName。列表 count 参数上游不遵守(实测固定 50 条),调用方不要依赖它控制页大小。Followers 配额仅 50 次 / 15 分钟,批量拉粉丝需按此排期。iOS 平台仍未实现;Web 仍为 cookie-in,不含登录
  • 升级与回滚:直接升级,无状态迁移,server 与 Python SDK 同版本替换即可。回滚到 0.4.0 会失去 3 个新 RPC(调用方收到 Unimplemented),并恢复旧的 UserByScreenName 解析;AccountState 双向兼容,回滚不需要重新获取 cookie 或重新登录

历史生产版本

v0.4.0 · 2026-09-16
  • 提交:v0.3.7 之后的 11 个提交——Web 平台垂直切片(e58a024 接入规则、a73e6c1 proto 契约、a151bfd 实现、47f9dfd 接口登记、501411c 下游配合清单、c529bb0 E2E 环境模板、16f6910 transaction-id 预热下沉),以及四轮真机验收(6d56165 关注链、af5a1b0 五条行为链与配额实测、dfc7de3 裸 RPC 与 cursor 翻页、f829bb5 7 个裸单端点收口)
  • 变更:① 新增 Web 平台(第二个落地平台,cookie-in)。包 twitter.web.v1 → gen/go/webpb,共 16 个 RPC:11 个单端点(WebUsersApi/UserByScreenName、WebTimelineApi/UserTweetsAndReplies、WebSocialApi/Follow、WebPostsApi/Like、WebBookmarkApi/Add、WebMediaApi/UploadMedia、WebAccountApi 的 AccountSettings / UpdateAccountSettings / UpdateProfile / UpdateProfileImage / UpdateProfileBanner)+ WebChainApi 的 5 条行为链(关注、改资料、换头像、换横幅、改设置)。WebAccountState 承载 cookie、transaction-id 材料、代理与版本档,遵守完整状态往返,Go 侧不留跨 RPC 状态。service 名统一带 Web 前缀,避免与 Android 按短名编 key 的文档生成器 / metadata / 离线测试互相覆盖。② 行为链步骤契约进 common:新增 ChainStepStatus(含 SKIPPED / UNKNOWN / NOT_RUN)、RateLimit、ChainStep,失败复用 FailureDetail;行为链在此是真实客户端交互顺序的还原(先落地目标页拿到上下文再写),不是 SDK 侧的跨端点聚合。③ ProxyConfig / ProxyCredentials / ProxyProtocol 自 android.proto 迁入 common.proto,字段号不变,供两个平台共用。④ x-client-transaction-id 预热下沉到 transport.do():X Web 每个请求都必须携带该头,缺失时上游返回 404(不是 403),此前只有行为链入口做了预热,裸单端点 RPC 走不通;改为在传输层统一兜底。⑤ error.go 抽出 failureDetailFor / statusWithDetails 公共核心供 Web 与行为链复用;third_party/xnet fork 补入 proxy 子包
  • 兼容性:wire 层无破坏性变更,编译期有一处破坏性变更。★ProxyConfig 迁移后 Go androidpb.ProxyConfig → commonpb.ProxyConfig、Python android_pb2.ProxyConfig → common_pb2.ProxyConfig,直接引用这两个类型的下游必须改 import 才能编译★;序列化字节与字段号未变,已持久化的 AndroidAccountState 照常解析,不需要重新登录。Android 的 RPC 集合、状态三件套与失败契约均未改动。Web 为全新命名空间,对既有调用方是纯加法
  • 平台:android, web
  • 验证:由 scripts/release_tag.py create 本地离线门禁执行(gofmt / go mod verify / go vet / go test -race ./... / proto 生成物重跑零差异 / uv 锁定同步 / Python 离线测试 / 发布工具测试 / 公开文档校验 / SDK 三件产物干净环境安装 / 生产镜像构建 / 镜像种子装载冒烟)。llms_docs.py check 实测 393 RPC / 204 message / 5 workflow / 20 公开文档,元数据与 proto 严格一一对应
  • E2E:passed——2026-09-16 用自有测试账号的 cookie 走真实 x.com 链路、直连出口,Web 16 个 RPC 全部真机验收通过。① 5 条行为链全通:关注链(10 个目标 + force 补验完整 5 步)、改资料链(回显 user_id 与 cookie 账号一致)、换头像链(三段式上传后头像实际更换,jpeg 路径)、换横幅链(media_category=banner_image,png 路径,横幅实际更换)、改设置链(翻转 display_sensitive_media → 上游回显确认写入生效 → 用裸 RPC 复原,账号设置最终不变)。② 7 个裸单端点 RPC 各自单独走通 gRPC 映射的取参与响应装配(此前只有上游协议由行为链间接确认)。③ cursor 翻页真机通过。实测各端点配额(每账号 / 15 分钟):followUserMutation 50、update_profile 15、update_profile_banner 30、UserByScreenName 150、settings.json 180、UserTweetsAndReplies 与 FavoriteTweet 各 500、媒体上传 615;其中 update_profile 的 15 与抓包记录精确吻合,★一条链会同时消耗多个配额★,改资料类以 15 为瓶颈。Web 走 cookie-in,不消耗 Android 冷启动登录配额,L-003 频控纪律不适用于本轮。写操作验收采用幂等重放压零副作用。证据见本地 E2E 日志,未外发账号标识
  • 部署:GHCR 0.4.0 与 latest;文档站根目录与 /pro/0.4.0/。镜像内容相对 0.3.7 增加 Web 平台 handler,种子池与 keybox 交付方式未变
  • Python SDK:0.4.0,wheel、sdist、可复制包三件产物。新增 twitter_sdk.x.web 门面(WebClient / WebAccountClient + users / timeline / social / posts / bookmark / media / account / chain 分组,全部 async def)。平台在导入命名空间层固定,创建对象不传 platform 参数。下游需同步的改动:引用 android_pb2.ProxyConfig 的代码改为 common_pb2.ProxyConfig
  • 已知限制:iOS 平台仍未实现。Web 为 cookie-in——本版不含 Web 登录链路,cookie 与 transaction-id 材料须由调用方提供,过期后需调用方自行重新获取。Web 的 16 个 RPC 是首批切片,远未覆盖客户端全集(Android 侧同期为 377 个),其余 Web 端点排期中。行为链的步骤语义依赖上游页面结构,上游改版可能使某步降级为 SKIPPED / UNKNOWN,调用方必须按 ChainStep.status 判定而非只看整体成功。配额数值为单账号单出口实测值,不保证跨账号、跨出口一致。third_party/xnet 是 x/net 指纹补丁 fork,升级需重打补丁(勿 go get -u golang.org/x/net)
  • 升级与回滚:升级需一次动作——server 与 Python SDK 同版本整套替换,并把下游对 androidpb.ProxyConfig / android_pb2.ProxyConfig 的引用改到 commonpb / common_pb2;无状态迁移,AndroidAccountState 原样沿用,不需要重新登录。回滚到 0.3.7 会失去整个 Web 平台,且 ProxyConfig 的 import 路径需改回 androidpb / android_pb2;AccountState 双向兼容,回滚不需要重新登录,Web 调用方在回滚后会收到 Unimplemented
v0.3.7 · 2026-09-11
  • 提交:v0.3.6 之后的 4 个传输层提交——44d7ff3 移除服务端内部登录重试、c4f1df3 NOT_SENT 非确定性失败允许有界重连、a3ed89f 内置默认重试改为 2 次、255ed90 TLS 层 OTHER 拆细为 TLS_ALERT / TLS_RECORD_INVALID
  • 变更:① 不再把「明确一个字节都没发出」的失败判成 DO_NOT_RETRY。新增 neterr.IsDeterministicCause(CERTIFICATE_ERROR / AUTHENTICATION_REJECTED / PROTOCOL_ERROR / ALPN_MISMATCH / UNSPECIFIED);传输失败若 delivery=NOT_SENT 且 cause 非确定性,则允许有界重连并回 BACKOFF_RETRY。此前经第三方代理的 TLS 握手抖动会落到 TLS_HANDSHAKE + OTHER,因 IsTransient(OTHER)=false 被直接判 DO_NOT_RETRY,下游即使明知请求没发出去也不敢重试,只能把账号交人工(下游一次 Android 视频发帖任务的媒体上传失败即卡在这里)。② 内置默认重试 1 次 → 2 次(共 3 次尝试),仅对重放安全的失败生效(NOT_SENT,或幂等请求);非幂等且可能已送达的请求仍只尝试一次。③ TransportCause 加法扩展 13 / 14:TLS_ALERT(对端在握手期发来致命 alert)与 TLS_RECORD_INVALID(对端在 TLS 端口回非 TLS 字节,典型是代理错误页 / 明文服务),把此前一律压成 OTHER(「无法归因」)的 TLS 层失败拆成可操作值;分类同时认 crypto/tls 与 uTLS 两套类型(uTLS 是 crypto/tls 的 fork,同名类型互不相通)。④ 移除服务端内部登录重试:登录重试交回调用方按 retry_advice 决定
  • 兼容性:无破坏性变更。proto 为向后兼容的枚举扩展(新增取值 13/14);旧调用方读到未知枚举值时应保留原始数字,不得当成 UNSPECIFIED,也不得当成任何账号处置或重试依据。AndroidAccountState 未改动,存量 state 可直接沿用、不需要重新登录。行为变更需注意:① 无 deadline 的调用方最坏耗时随尝试次数放大(内部最多 3 次尝试;声明了 RPC deadline 的调用方不受影响,仍只有一份预算);② 部分此前 DO_NOT_RETRY 的 NOT_SENT 传输失败现在回 BACKOFF_RETRY 且服务端会先重试,写操作的上游请求次数可能增加,但都发生在「一个字节都没发出」的判定下,不会产生重复副作用;③ 按 cause 做白名单的调用方若不把两个新值补进放行集合,行为与从前一致(仍在 cause 上放弃),不会变得更糟
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;proto 生成物一致性(重跑 gen-proto.sh 零差异);llms_docs.py check 通过(377 RPC / 173 message / 5 workflow / 20 公开文档);Python 离线测试与发布工具测试通过。新增离线用例用真实 uTLS 握手打本地桩复现「对端 fatal alert(40)」与「TLS 端口回明文 HTTP/1.1 502」,断言分类结果与承载形态(net.OpError{Op:"remote error"} / RecordHeaderError),而不是构造错误字符串;TestTLSCausesKeepRetrySemantics 钉住两个新 cause 与 OTHER 在瞬时性 / 确定性上完全同格
  • E2E:passed——不碰上游、不消耗登录配额的真实 gRPC 链路验收(本地 Go server + 本地假代理),4/4 通过:① 新增用例把「假 SOCKS5 代理隧道建立后回 fatal alert(handshake_failure)」走到调用方,断言 phase=TLS_HANDSHAKE + cause=TLS_ALERT + delivery=NOT_SENT + advice=BACKOFF_RETRY + attempts=3(证明新枚举真的到达调用方,不只是 Go 侧单测自洽);② 连接被拒 → CONNECTION_REFUSED + BACKOFF_RETRY;③ 哑代理拨号超时 → TIMEOUT(不是 DEADLINE_EXCEEDED);④ 调用方 deadline 到期 → DEADLINE_EXCEEDED。真实账号登录 / 发帖 E2E 未执行:本版不涉及登录与业务语义,且测试账号长期受上游频控约束(L-003),单轮冷启动登录纪律下不做无新增可验语义的消耗。证据见本地 e2e-server.log
  • 部署:GHCR 0.3.7 与 latest;文档站根目录与 /pro/0.3.7/。镜像内容与 0.3.6 相比仅传输层分类与重试逻辑变化,种子池与 keybox 交付方式未变
  • Python SDK:0.3.7,wheel、sdist、可复制包三件产物。无 Python API 变更——twitter_sdk.failure 已能解码 transport.cause,两个新枚举值随 common_pb2 stub 一并更新;调用方无需改代码即可读到新值
  • 已知限制:TLS_ALERT 只表达「对端在握手期拒绝」,无法细分 alert 的具体 description(crypto/tls 与 uTLS 都把它包在各自未导出的类型里,只能靠 net.OpError.Op 识别);TLS_RECORD_INVALID 同样不含对端返回的字节内容。两者都不携带对端原文(脱敏红线)。触发层仍是第三方代理线路到 X 媒体接口的 TLS 抖动,线路治理 / 换线不在本仓库范围内,本版只保证这类失败被正确标注且可安全重试。CauseOther 仍是真正的兜底值
  • 升级与回滚:直接升级,无状态迁移,AccountState 双向兼容。回滚到 0.3.6 会失去 NOT_SENT 有界重连、3 次尝试与两个新 cause(TLS_ALERT / TLS_RECORD_INVALID 会重新并回 OTHER);若调用方已按新 cause 做白名单,回滚时需把这两个值并入放行集合,否则会退回「整体放弃」
v0.3.6 · 2026-09-10
  • 提交:v0.3.5 之后的 2 个提交——公开文档边界修复,以及本次 18 台金标种子池、同账号换机登录、Python SDK 契约、测试与文档同步
  • 变更:① castle 真机种子池从 9 台扩至 18 台,逐份同步 profile、传感器边车和同源 phone 参数;14 台 weight=1 进入随机池,Pixel 4、SM-A155M、SM-A165M、SM-A225F 四台 weight=0 仅供显式验证。② Login 同账号有限换机:默认最多 3 次,只在 username/C1 的 RATE_LIMITED / RISK_BLOCKED 后按 weight 无放回换一台金标,重铸完整设备身份并从 oauth2/token 重新冷启动;密码、2FA、封禁、锁定、网络、代理、GeoIP 与本地配置错误不换机。③ Proto LoginRequest 新增 optional max_attempts(字段号 8,取值 1..3)并同步 Go/Python stub、Python login(max_attempts=...) 与公开文档。④ 登录成功门禁补强为 user_id 非空且 screen_name 与请求账号一致;真实 E2E 用例改传 totp_seed,避免长登录链使预先计算的当前码过期
  • 兼容性:Proto 为加法扩展,旧客户端可继续调用,AndroidAccountState 未变且存量 state 可直接沿用。行为变化:调用方不传 max_attempts 时,单个 Login RPC 遇 username/C1 软拒最多尝试 3 台设备;要保留旧的一次尝试行为需显式传 max_attempts=1。固定机型环境覆盖仍只尝试该机型。换设备不改代理 host、port、credentials 或代理用户名里的 session 标识
  • 平台:android
  • 验证:go build ./...、go vet ./...、gofmt、go test -race ./... 全绿;Python 离线测试 416 项通过、发布工具测试 90 项通过;proto 生成物与 AI 对接文档一致性检查通过;18 台 profile/传感器与参考数据逐文件 JSON 语义一致,phone/build 机型 18/18 同源,随机池 14 台与参考 weight 逐项一致;internal/client/castle 覆盖率 82.3%,RandomSeedModels 100%
  • E2E:skipped——已按公开 Python SDK 门面执行真实登录验收,但未取得完整 SSO/whoami:首轮用例在调用前生成 TOTP,完整链路耗时 83 秒后停在 2FA,已修正为传 totp_seed;修正后第二轮遇代理 TLS 握手失败(UPSTREAM_UNAVAILABLE、NOT_SENT),最后一次在发出 X 登录请求前被代理 GeoIP 探针失败拦截,独立国家探针也因代理 TLS 失败不可用。遵守单轮最多 2–3 次冷启动纪律,未继续打账号;用户明确授权以此原因跳过。新增换机分支仍为 implemented、未真机验收
  • 部署:GHCR 0.3.6 与 latest;文档站根目录与 /pro/0.3.6/。镜像内置 18 台同源种子副本,外部种子目录仍可整体替换
  • Python SDK:0.3.6,wheel、sdist、可复制包三件产物;AndroidClient.login 新增可选 max_attempts,Python 本地与 Go server 双层限制 1..3
  • 已知限制:同账号换机只更换设备身份,不更换代理 credentials 或代理 session 标识;username/C1 同一句软拒可能同时代表设备身体拒绝与账号/IP 频控,最多 3 次仍失败后不得立即再次调用 Login。ASUS I01WD、Pixel 3、SM-A037F 仅有未收口 raw dump,缺完整 profile、同源 phone 或 seed weight,未纳入 18 台池。iOS / Web 仍未实现;本版真实 E2E 因代理 TLS/GeoIP 不可用跳过
  • 升级与回滚:直接升级,无状态迁移;需要旧单次登录节奏的调用方先传 max_attempts=1。回滚到 0.3.5 会失去新增 9 台种子、无放回换机重试与 max_attempts 字段;旧客户端会忽略新字段,AndroidAccountState 双向兼容,回滚不需要重新登录
v0.3.5 · 2026-09-07
  • 提交:v0.3.4 之后的 9 个提交——金标种子池扩至 9 台并支持外部目录/镜像交付、FailureDetail 诊断字段、批量冷启 txt 入口、CI 文档补发修复,外加本 CHANGELOG
  • 变更:① castle 真机种子池扩至 9 台并按 weight 进随机轮换——内置 Highwind dump 与 phone 机型模板同源;weight>0 进 RandomSeedModel,weight=0 只允许显式指定做验证。② 换种子免发版:XLOGIN_SEED_DIR 整体替换内置池(不合并、配错硬失败);生产镜像 COPY 同源种子到 /usr/share/twitter-seeds,compose 可挂外部目录。③ FailureDetail 诊断字段——proto 新增 FailureReason 固定词表与 reason / upstream_error_codes / upstream_error_message(解析后的展示字段并截断)/ next_component / jf_component / failure_id;kind/advice 语义不变,status message 仍为常量摘要,原文只进服务端日志。④ 批量冷启登录单一入口 scripts/login_batch.py,账号从 txt 读取(钉死机型 / 直连对照),禁止一次性 runner。⑤ CI:文档补发 job 补 !cancelled();公开文档白名单放行自有 XLOGIN_SEED_DIR / XLOGIN_MODEL / XLOGIN_COUNTRY 环境变量名
  • 兼容性:无破坏性变更,存量 AccountState 可直接沿用、不需要重新登录。FailureDetail 为 proto3 加法扩展,旧客户端忽略新字段即可;kind / gRPC 状态码取值集合未扩大。Python FailureInfo 新增只读属性并导出 FailureReason。登录默认仍按 weight 随机抽机型;XLOGIN_MODEL / -model 可覆盖。keybox 覆盖变量名与代码一致为 TWITTER_KEYBOX_FILE(仅覆盖,不是唯一来源)
  • 平台:android
  • 验证:由 scripts/release_tag.py create 本地离线门禁执行(gofmt / go mod verify / go vet / go test -race / proto 生成物零差异 / Python 离线测试 / 发布工具测试 / 文档校验 / SDK 三件产物 / 生产镜像构建与种子装载冒烟)
  • E2E:passed——金标种子池冷启动真机覆盖(2026-09-07):随机轮换池 8 台中 7 台完整 SSO(oauth2/token → guest → onboarding → attest → C1 → password → 2fa/TOTP → SSO → whoami,screen_name / user_id 正面确认目标账号),含新加坡代理出口与直连日本出口对照。weight=0 的一台与随机池中一台仍停在 username 步 RATE_LIMITED(JF X-Rate-Limit-Remaining=4999/5000,非接口配额耗尽)。证据见本地 out/login-c1-* 日志。未跑:业务 RPC 本轮未攒批复测,沿用存量 state 路径未改
  • 部署:GHCR 0.3.5 与 latest;文档站根目录与 /pro/0.3.5/。换种子可挂 docker-compose-pro-seeds.yml + TWITTER_SEED_DIR,不必为加机型再发版
  • Python SDK:0.3.5,三件产物。FailureInfo 增加诊断属性与 FailureReason 导出;其余门面无 API 变更。批量登录脚本不进 SDK 包
  • 已知限制:iOS / Web 平台未实现(本版仅 android)。随机池中一台与 weight=0 的一台冷启动仍可能在 username 步被软拒,原因未定为账号/出口/身体,接口配额未耗尽。参考的国家探针机型池(20+ 无种子机型)未移植。历史限制继续有效:登录遥测 stage 名形状匹配放行、previous_tweet_id 原因未定、定时发帖与长文 features 缺失、TEE oracle 对照工具未移植。third_party/xnet 是 x/net 指纹补丁 fork,升级需重打 3 处补丁(勿 go get -u golang.org/x/net)
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。回滚到 0.3.4 会失去 9 台种子池 / 外部种子目录 / FailureDetail 诊断字段 / 批量登录脚本;AccountState 双向兼容,回滚不需要重新登录。读了新诊断字段的调用方需能容忍字段缺席
v0.3.4 · 2026-09-02
  • 提交:v0.3.3 之后的 2 个提交——冷启动登录链路对齐参考默认装配(C1 全拒根因修复)+ 本 CHANGELOG
  • 变更:冷启动登录 C1 全拒根因修复(对齐参考仓库默认装配)。五路 wire 审计实证:castle 编码层与参考逐字节一致,差异全在生产装配层默认路径——(a) castle 装配改为参考默认:ApplyCoherentOverlay(build/区域随设备声明,冻结时 pin dump device_id)+ 无条件 EnableVary/RebuildEnvU(随机重铸 device_id + 172/主字段窄带相干缩放 + env_u 瞬态重掷 + 干净位),删除「冻结身体 + 外来持久 device_id」反模式(参考 P0-4 明令禁止);(b) 恢复被删行为:field8TZRaw/dstSavingsMinutes(field 8 按当前时区现算,金标 Asia/Shanghai→e000)、ApplyCoherentOverlay/DumpCastleDeviceID/FrozenInstallPinned + 金标测试;(c) 区域跟随出口 IP:新增 internal/geoip(ipinfo 经代理探测出口国家)+ internal/identity/region.go(43 国区域表 + ApplyRegion),HTTP 头(语言/时区/国家)与 castle overlay 同源——此前恒 zh-CN/Asia/Shanghai,出口在 JP/TW/US 时设备与网络自相矛盾;(d) 机型池轮换:internal/identity/phones.go 两台真机种子机型模板(Pixel 5 / M2007J22C)+ ComposeFreshDevice,server / cmd/manual/login 默认 RandomSeedModel() 随机轮换(XLOGIN_MODEL / -model 覆盖),attest 改 NewIdentityWithDevice 按机型/OS 参数化(Pixel 5=Android 14 vs 红米=12);(e) 移除 AndroidOptions.CastleVary / -castle-vary(默认恒 vary,对齐参考)
  • 兼容性:无破坏性变更。proto 无改动;AndroidDevice JSON 新增字段(locale/language_tags/network_country/device_name/mem_gb/cpu_cores/density/ui_mode/width_px/height_px/attest_os_version/attest_os_patch_level)——Go JSON 向后兼容(旧 state 缺字段用默认值),proto 字节无变化。API 变更:移除 AndroidOptions.CastleVary 与 cmd/manual/login 的 -castle-vary(默认恒 vary);新增 -country / -model flag 与 XLOGIN_COUNTRY / XLOGIN_MODEL 环境覆盖。行为变更:① 登录默认每登录一台新设备(随机 device_id + vary),不再跨登录复用 castle device_id;② server 登录前经代理探测出口国家(ipinfo),探测失败拒绝登录(不发出区域不自洽的设备);③ 未知机型硬失败(不再静默回退红米身体)
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线测试通过;proto 生成物零差异。新增回归:castle field8 金标(Asia/Shanghai→e000、DST 时区)、region 表 / ApplyRegion、geoip stub、ComposeFreshDevice 机型装配(Pixel 5 / 红米 / 未知机型)、castle 装配新语义(每登录新设备 / 未知机型硬失败)
  • E2E:passed——冷启动登录真机验收通过(2026-09-02,修复后链路):13 账号并发冷启动(JP/TW 出口,经公开 Python 门面 twitter_sdk.x.android):11 个完整 SSO 登录(oauth2/token → guest → onboarding → attest → C1 → password → 2fa/TOTP → SSO token → whoami,screen_name / user_id 正面确认目标账号),2 个失败为账号/IP 级 C1 软拒(fast-fail,同批另 4 个同区域账号成功,非链路问题)。并发隔离断言全过:每账号拿回自己的 screen_name(不串号)、client_uuid / castle_device_id / oauth_token 两两不同、代理按账号原样往返。修复前同配置 0/21 全拒,修复后 11/13——登录链路 wire 对齐参考默认装配生效。证据详见本地 e2e-server.log 与 docs/daily/2026-09-02.md
  • 部署:GHCR 0.3.4 与 latest;文档站根目录与 /pro/0.3.4/
  • Python SDK:0.3.4,三件产物。无 API 变更(proto 无改动);服务端行为变化(登录默认 vary 新设备、区域跟随出口、机型轮换、未知机型硬失败)对 Python 门面透明,并发登录用例 test_e2e_android_concurrent_login.py 已入库(live,默认跳过)
  • 已知限制:iOS / Web 平台未实现(本版仅 android)。参考的国家探针机型池(20+ 无种子机型)未移植——本仓库无 -castle-profile 模式,非种子机型无法过 C1(newCastleProvider 硬失败),需要时随该模式一并移植。transport 边缘行为未对齐参考(3xx 重定向跟随、cookie jar 语义、gzip 解码层、网络故障重试一次——后者为 Issue #8 超时契约,有 E2E 契约测试钉住)。历史限制继续有效:登录遥测 stage 名形状匹配放行、previous_tweet_id 原因未定、定时发帖与长文 features 缺失、TEE oracle 对照工具未移植。third_party/xnet 是 x/net 指纹补丁 fork,升级需重打 3 处补丁(勿 go get -u golang.org/x/net)
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。升级后注意:登录链路已改为参考默认(vary 恒开 + 区域跟随出口 + 机型轮换)——新登录的 castle device_id 每登录换新,若下游依赖旧「冻结重放」行为需评估。回滚到 0.3.3 会让冷启动登录回到 C1 全拒状态(vary 关闭 + 恒 CN 区域 + 固定红米身体);AccountState 双向兼容,回滚不需要重新登录
v0.3.3 · 2026-09-01
  • 提交:v0.3.2 之后的 3 个提交——castle field 31 时钟语义修正、oauth_nonce 对齐真机十进制格式、本 CHANGELOG
  • 变更:① castle field 31 改为 boot 时间语义——jadx Highwind.java:777 证实 field 31 = ((currentTimeMillis − elapsedRealtime)/1000) − 1535000000,是开机时刻而非 now。v0.3.2 第一轮移植误用外壳 epoch,token 声称「开机时间 = 当前时间」,与 env_u id 0 的开机分钟矛盾(长 uptime 设备差值达整个开机时长)。改为 Profile.UptimeMin 与 env_u id 0 同源,bootEpoch = Epoch − uptimeSec,仅 field 31 用 bootEpoch,外壳 epoch 保持 now;vary 路径由 RebuildEnvU 同步 UptimeMin。金标 dump 三值对拍:红米 uptime ≈30079s / Pixel 5 ≈3727s,与 env_u id 0 分钟截断自洽(boottime_test.go,±59s 窗口)。② oauth_nonce 对齐真机十进制格式——jadx com/x/signing/h.java:100 证实真机 nonce = String.valueOf(System.nanoTime()) + Math.abs(SecureRandom.nextLong())(金标 96 个全为 33–34 位十进制,前段自开机纳秒单调递增)。此前用 UUID hex(32 位十六进制),格式不符且无时间成分。oauth.NewNonce() 用进程启动 + 随机 6–72h 开机偏移模拟 nanoTime,后段 crypto/rand 取 [0, 2^63);登录链与业务客户端(含 jot)OAuth 头全部改走 NewNonce。attest exchange nonce 属 server 挑战语义,未改
  • 兼容性:无破坏性变更,存量 AccountState 可直接沿用、不需要重新登录。proto 无改动;本轮全是 Go 内部指纹对齐。AndroidDevice JSON 无新字段。调用方无 API 变更。行为变更仅限发出的 wire:新登录产生的 castle field 31 与既有会话的 OAuth oauth_nonce 形态变化,上游接受(见 E2E);调用方看不到这两个字段
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 414 项通过;proto 生成物零差异。新增回归:TestField31BootEpoch_Gold / TestGenerateField31BootEpoch / TestEnvUUptimeMin / TestSeedProfileUptime(金标 dump 对拍);TestNewNonce(十进制、长度带 28–38、唯一性)
  • E2E:passed——复用已持久化的 AccountState、全程未发起登录(当日另一次冷启动在 username 步 RATE_LIMITED,attest / official-app 已通过,按频控纪律未重试)。经公开 Python 门面 twitter_sdk.x.android 加载存量 state → Me 探活(screen_name / user_id 与会话凭据一致,未封禁)→ HomeTimeline 非空。本版 oauth_nonce 十进制格式被真实上游接受(OAuth 1.0a 签名路径)。未跑:castle field 31 只在冷启动生成 castle 时生效,本轮未覆盖冷启动登录
  • 部署:GHCR 0.3.3 与 latest;文档站根目录与 /pro/0.3.3/
  • Python SDK:0.3.3,三件产物。无 API 变更(proto 无改动);服务端发出的 OAuth nonce / castle field 31 形态变化对 Python 门面透明
  • 已知限制:iOS / Web 平台未实现(本版仅 android)。castle field 31 的冷启动登录本轮未真机覆盖——金标 dump 对拍与单元测试已锁语义,服务器接受待下一次有登录窗口时补验。v0.3.2 的限制继续有效:登录遥测 stage 名是形状匹配放行、previous_tweet_id 原因未定、定时发帖与长文 features 缺失、TEE oracle 对照工具未移植。third_party/xnet 仍是 x/net 指纹补丁 fork,升级需重打 3 处补丁(勿 go get -u golang.org/x/net)
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。回滚到 0.3.2 会让 field 31 重新误用外壳 epoch、oauth_nonce 回到 UUID hex;AccountState 双向兼容,回滚不需要重新登录
v0.3.2 · 2026-09-01
  • 提交:v0.3.1 之后的 8 个提交——登录链路对参考仓库(go/x)的四轮对齐审查与修复(C1 指纹、城堡种子池/vary、遥测、错误分类),外加复用 AccountState 的发帖验收工具
  • 变更:① 登录链路对齐参考仓库真机 wire(四轮审查共修复 13+1 处)——(a) 登录顺序对齐 Pixel 5 冷启动:oauth2/token → guest/activate → onboarding(Active-User=no) → attest(第二次 nonce)→ 拟人等待 → one_tap → username,attest 从 2-legged OAuth 改为 Bearer 并补 X-Guest-Token/apollo persistedQuery body 序;(b) h2 指纹 fork x/net(third_party/xnet,3 处补丁:SETTINGS 4:16777216|16711681|0、伪头序 m,p,a,s、请求头顺序哨兵(三套头序常量;具体头名见内部文档)),jf/api.x/api.tw 三套头序常量;(c) 请求指纹补齐:X-B3-Traceid(随机 16-hex)、空值头必发(X-Twitter-Client-Flavor/X-Jf-Client-Theme)、jf/attest body 保序(orderedBody)、fetchAppBearer 走 REST 预鉴权头;(d) castle 种子池:内置 Pixel 5 + 红米两台真机身体,按设备模型选种子(未知机型回退红米);vary 完整移植:RebuildEnvU(env_u 瞬态重掷 + 干净位 + DeviceSecure/PowerSave 随机化)+ 窄带相干缩放 ×U(0.99,1.01)(保物理锚),AndroidOptions.CastleVary 开关;(e) 登录遥测:AndroidLogin.Tel + emitTel 在 9 个 stage 点发 jot client_event(app_cold_start → login_success/fail),SkipTelemetry 默认开;(f) 错误分类补齐:username/password 步骤 HasBusinessError 分支(业务错误页不再落 UNSPECIFIED,保守归 RISK_BLOCKED)、2fa GET/POST 改尽力而为(失败不阻断)、probeAccount 用 SettingsLangQuery(locale/lang 按设备地区);(g) attest 对齐:BuildExchangeNonce(exchange 的 client_data.nonce 用第二次 nonce,rebind 用 nonce1)、model wire 形态(空格→+,UA 与 attest 同源);(h) WriteUserInstalledApps 冷启动前置(429 不挡)、Apps 默认值从设备取。② 新增 cmd/manual/post——复用登录导出的 AccountState JSON 发帖的验收工具(不重新登录、复用存量 AttestToken 不重铸、输出帖子地址)
  • 兼容性:无破坏性变更,存量 AccountState 可直接沿用、不需要重新登录。proto 无改动(本轮全是 Go/Python 内部与工具变更);AndroidDevice JSON 新增字段(apps/settings_country/egl/batt_technology/sensor_count/storage_total_mb/sdk_int/installed_app_slugs)——Go JSON 通道向后兼容(旧 state 缺字段用默认值),proto 字节无变化。行为变更需注意:① 登录默认发遥测(SkipTelemetry=false),jot 是 best-effort 不影响登录;② 登录链路多了拟人等待(秒级随机,SkipHumanPace 可关)与 WriteUserInstalledApps(429 不挡);③ 2fa 预热失败不再阻断登录(最终按「无 SSO」分类);④ go.mod 新增 replace golang.org/x/net => ./third_party/xnet——升级 x/net 必须按 third_party/README.md 重打补丁,勿 go get -u golang.org/x/net
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿(沙箱下 GOCACHE/GOMODCACHE/GOPATH 指可写目录);Python 离线测试通过;proto 生成物零差异。新增回归:castle 种子池双机型 token、envu 金标 env_u.a 字节 MATCH(红米/Pixel 5)、vary(RebuildEnvU 瞬态/静态/b 计数、EnableVary 缩放带)、jf 业务错误页(HasBusinessError/valueAfterKey/繁日限流)、orderedBody 保序、空值头必发、B3 16-hex、SettingsLangQuery、2fa 容忍、遥测 emitter 初始化、attest WirePlus/exchange nonce
  • E2E:passed——冷启动登录真机验收通过(2026-09-01,HK 出口测试账号):完整链路 oauth2/token → guest → onboarding → attest(guest nonce2 → verify → exchange)→ C1(session_token ok, next=login_enter_password)→ password → 2fa/TOTP → SSO token → attest rebind(user) → whoamilogged in as @测试账号(200,screen_name 正面确认)。真实 keybox 被接受(verify_android_key_attestation=true);登录遥测 jot 全部 200(含打开 APK 的android:app::::launch/become_active/app_open_warm+ attestation 4 步,payload 解压确凿验证);头序/body 序/B3/真机 castle 种子全部生效。**复用 state 发帖验收通过**:cmd/manual/post加载登录 state(不重新登录、复用 attest_token)→CreatePost` 成功,帖子已发布(地址含测试账号句柄,证据见本地脱敏日志)
  • 部署:GHCR 0.3.2 与 latest;文档站根目录与 /pro/0.3.2/
  • Python SDK:0.3.2,三件产物。无 API 变更(proto 无改动);服务端行为变化(登录遥测默认开、拟人等待、2fa 容忍、业务错误分类)对 Python 门面透明
  • 已知限制:iOS / Web 平台未实现(本版仅 android)。登录遥测 stage 名是形状匹配放行(launch/become_active 精确命中真机语料;app_open_warm/attestation 4 步为同形状命中,component 段通配)——逐字对齐需真机抓包再对,见 sync-audit 遗留。v0.3.0/v0.3.1 的限制继续有效:previous_tweet_id(编辑已发布推文)原因未定、定时发帖与长文 features 缺失、TEE oracle 对照工具未移植。third_party/xnet 是 x/net 指纹补丁 fork,升级需重打 3 处补丁(勿 go get -u golang.org/x/net)
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。升级后注意:登录现在默认发遥测/带拟人等待(SkipTelemetry/SkipHumanPace 可关);若依赖「2fa 预热失败即报错」的调用方,语义已改为容忍(按无 SSO 分类)。回滚到 0.3.1 会丢失登录链路全部对齐修复(C1 指纹/种子池/遥测)与发帖工具;AccountState 双向兼容,回滚不需要重新登录
v0.3.1 · 2026-08-28
  • 提交:v0.3.0 之后的 Issue #8 修复——上游数字错误码分类收口、超时预算归一、retry_advice 与 gRPC code 判据改按外层 context
  • 变更:① 上游数字错误码表收口成唯一一份——X 的 REST /1.1、媒体上传端点与 GraphQL errors[] 用的是同一套数字错误码,但这张表原先只写在 xclient.gqlErrorKind 里、且只在 GraphQL 200 + errors[] 一条路径被消费。其余出口各自 fmt.Errorf 或只看 HTTP 状态码,于是媒体上传的 403 + code 64(本账号被封禁)一路降级成 UNSPECIFIED,下游无法停掉该账号的后续任务(Issue #8 主症状)。码表下沉到 internal/failure(KindForAPICode / FromAPIBody,一个码都没新增),业务侧新增唯一转换入口 xclient.apiError:先查码表,命中才给结论;未知码、空 body、损坏正文一律退回既有保守分类(403 → SESSION_EXPIRED),绝不猜封禁/锁定。改用该入口的出口:媒体 INIT/APPEND/FINALIZE/STATUS、gqlData 的非 200 分支、mutate 非 200、cards/create;CreatePost 改为复用 gqlData 收口——它此前非 200 与 errors[] 两个分支都是裸 fmt.Errorf,发帖主路径同样在降级。attest 的 4 个端点(每个业务 RPC 的 BootstrapAttest 都过)用加法式 classifyAPIStatus:码表命中才分类,否则保持原样。★code 63 永远不进码表★——63 是「目标用户被封」(读一眼别人的封号资料页就会拿到),64 才是「本账号被封」,混为一谈会让下游把自己的健康账号永久剔除,已由 TestCode63IsNotBanned 钉死。fetchAppBearer(app 级 client_credentials)刻意不查账号级码表并留注释挡住「顺手补上」。② 超时预算归一——一次调用此前有两份互不知情的预算:transport.Do 在循环外建的 WithTimeout(ctx, c.timeout)(所有 attempt 共享),加上 tlsprof.NewHTTPClientFP 设的 http.Client.Timeout(context 放宽不了它)。于是调用方给视频上传 300 秒、服务端 60 秒照样掐断;而且第一次内部超时后第二次 attempt 拿到的 context 已经过期,「超时」这类失败永远得不到重试。改为:每个 attempt 独立 child context(cancel 在响应体读完之后才调用),预算取自调用方——调用方声明了 deadline 就直接沿用,没声明时 c.timeout 才作为兜底;http.Client.Timeout 移除,NewHTTPClientFP 不再接收 timeout 参数。③ 「谁到期了」改看外层 context——内部 attempt 超时与调用方 deadline 到期在错误链上长得一模一样,此前按 errors.Is(err, context.DeadlineExceeded) 判定,于是一次内部超时就把 RPC 打成 DEADLINE_EXCEEDED + DO_NOT_RETRY,下游连「明确一个字节都没发出去」的 TLS/连接失败都不敢重试。现在 transport.wrapFailure 与 server.codeForErr 一律以外层 context 的状态为判据:只有调用方 deadline 真到期/取消才回 DeadlineExceeded/Canceled + DO_NOT_RETRY;内部超时回 Unavailable + UPSTREAM_UNAVAILABLE(cause=TIMEOUT),advice 按投递状态给出——NOT_SENT 或幂等请求 BACKOFF_RETRY,非幂等且 POSSIBLY_SENT 保持 VERIFY_EFFECT_THEN_RETRY。transport 内部仍然不会自动重放非幂等且可能已送达的请求,这条没变。④ 连带修复 SOCKS5 握手超时被调用方预算放大——去掉内部固定超时之后暴露出 socks5.go 一个被掩盖的缺陷:握手 deadline 原先优先取 ctx 的 deadline,20 秒常量只在无 deadline 时兜底。此前 ctx 是内部 60 秒、握手最多挂 60 秒;改为服从调用方 deadline 后,一个只 accept 不应答的坏代理能把整个 RPC 预算吃满——真机 E2E 实测 200 秒的 RPC 挂满 200 秒才被客户端 deadline 打断,服务端全程既没失败也没重试。握手是协议层的固定小步骤(两次一来一回),健康代理毫秒级完成,上界应是常数,不该随整体预算放大;改为取「ctx deadline 与 now+20s 的较早者」,并抽成纯函数以便零耗时回归
  • 兼容性:无破坏性变更,存量 AccountState 可直接沿用、不需要重新登录。proto 变更均为向后兼容新增:AndroidDevice 新增 castle_device_id(field 15,8aa5da7,旧 state 缺此字段时登录自动补生成、随返回态回传持久化);新增 UsersApi/WriteStatus RPC(7715d41)。旧调用方忽略新字段/新 RPC 即可,AndroidAccountState 现有字段号无变动。行为变更需注意:① 登录 castle 从算法合成切回真机 dump——若你已经在跑 8aa5da7(未发布的合成 castle),本版把它改回真机 dump 是登录从不可用到可用的修复;直接从 v0.3.0(冻结 dump)升级则是「冻结共用 dump → 每设备独立 device_id」的改进,多账号不再共用同一 Highwind 身份。② BANNED / LOCKED_NEEDS_VERIFICATION 的影响面从媒体扩到全部 GraphQL 读写(gqlData 非 200 现在也查码表)+ whoami 步的 403 code 326/64(此前误报 SESSION_EXPIRED);下游对这两个 Kind 的处置是账号级且不可逆,需确认处置逻辑就绪。③ 服务端内部超时不再表现为 gRPC DEADLINE_EXCEEDED,改为 Unavailable——按 code 分支的调用方需调整,按 FailureDetail.retry_advice 分支的不受影响。④ 调用方声明的 RPC deadline 现在真正生效到底,服务端不再在 60 秒截断;不设 deadline 的调用方最坏耗时从「共享 60s」变为「每 attempt 各 60s,最多 2 次」。Go SDK 的 tlsprof.NewHTTPClientFP 签名变更(internal 包,无外部使用者)
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 414 项通过;proto 生成物与文档站产物重跑零差异。新增回归 4 组:internal/failure/apicode_test.go(码表 64/326/88/32/89、未知码、无 errors、空 body、非法 JSON、HTML 风控页、类型不符,以及 63 不得判 BANNED);internal/client/xclient/apierror_test.go(INIT/APPEND/FINALIZE/STATUS/CreatePost/cards-create 六个出口 × 四种 403 响应体,断言 Kind / HTTPStatus / Advice,并断言对外摘要不含上游正文);internal/transport/timeout_test.go(每 attempt 拿到新 context、调用方 deadline 不被内部 timeout 截断、无 deadline 时兜底仍生效、内部超时的 advice 按投递状态给出、调用方 deadline 到期即停止重试);internal/server/android_test.go 扩充(内部超时 → Unavailable+cause=TIMEOUT、调用方 deadline 到期 → DeadlineExceeded、媒体 403+64 → PermissionDenied+BANNED+HUMAN_INTERVENTION+upstream_http_status=403 且 status message 不含正文、403+326 → FailedPrecondition+LOCKED_NEEDS_VERIFICATION)。分类分支的用例 mock 的是响应体形状(Issue #8 下游实测的 403+code 64/326),被测对象是本地分类逻辑而非上游行为
  • E2E:passed——冷启动登录真机验收通过(2026-08-28,本版核心):一批干净账号用修复后 castle(真机 dump 种子)跑冷启动登录,3 个账号完整 SSO 登录(stephanie67sd5 / courtney76gt7 / dana99yb5:C1 session_token ok, next=login_enter_password → password → 2fa → SSO token → logged in @<账号>,oauth=true);第 4 个 karen68vc1 castle 过 C1、走完 SSO,仅在 whoami 确认时回 403 code 326(账号本身被 X 锁定,非 castle,且已正确分类为 LOCKED_NEEDS_VERIFICATION)。决定性 A/B(同账号只换 castle 实现):stephanie67sd5 在合成 castle 下 2026-08-27 撞「临时限制」失败 4-5 次,换真机 dump 后同账号直接完整登录——castle 门槛被破实锤。登录经 cmd/manual/login(Go SDK 层)真机验证;Issue #8 传输失败契约另经真实 gRPC 链路验收(test_e2e_android_timeout_contract.py,不碰上游、不消耗配额,3 条全过):连接被拒 → Unavailable+BACKOFF_RETRY+NOT_SENT;哑代理超时 → Unavailable+TIMEOUT,不是 DEADLINE_EXCEEDED;调用方 deadline 到期 → DEADLINE_EXCEEDED;attempts 2/2/1 证明每 attempt 拿到新 context。Python SDK gRPC 端到端:以登录成功的 AccountState(out/*.json)经 twitter_sdk.x.android 门面加载 → Me 探活确认同账号(复用 state 不消耗登录配额)。未跑:带视频发帖的正常路径回归用例已就位(test_upload_video_with_caller_deadline_and_post),本轮未在 Python live 下执行。403 + code 64 / 326 无法自有复现——需要一个被封/被锁账号,本地无此素材,见「已知限制」
  • 部署:GHCR 0.3.1 与 latest;文档站根目录与 /pro/0.3.1/
  • Python SDK:0.3.1,三件产物。无 API 变更——twitter_sdk.failure 已能解码 kind / upstream_http_status / transport.{phase,cause,delivery_state} / retry_advice,本版只是让服务端在更多路径上填对这些字段
  • 已知限制:403 + code 64 / 326 的分类无本仓库自有真机样本——置信度=高概率;来源=X 平台公开错误码表 + Issue #8 下游实测。去掉 http.Client.Timeout 后,卡死的连接会占满调用方声明的全部预算(此前 60 秒强断):若下游给所有 RPC 统一设几百秒 deadline 且代理黑洞化,服务端连接/goroutine 会堆积,建议按 RPC 类型分别设 deadline(读类几十秒、媒体类几百秒);如需硬上限需后续加 env 兜底,本版刻意未加。android_attest.go 里判 integrity 软失败的 "code":214 仍是子串匹配——它不在账号处置码表内,且现有宽松匹配是因为不确定 214 在 errors[] 顶层还是 extensions 里,改窄反而可能漏判,需一次真机样本才能收紧。登录链路的完整失败分类仍未做(密码错误落 UNSPECIFIED),归 Issue #5。v0.3.0 的其余限制(previous_tweet_id 原因未定、定时发帖与长文 features 缺失、iOS / Web 未实现)继续有效
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。升级后请复核两处调用方逻辑:对 BANNED / LOCKED_NEEDS_VERIFICATION 的账号级处置(影响面已扩到全部 GraphQL 读写),以及是否有按 gRPC DEADLINE_EXCEEDED 分支的重试逻辑(内部超时现在回 Unavailable)。回滚到 0.3.0 会让媒体与发帖路径的 403 重新降级为 UNSPECIFIED、内部超时重新伪装成 DEADLINE_EXCEEDED,并恢复 60 秒内部截断;AccountState 双向兼容,回滚不需要重新登录
v0.3.0 · 2026-08-19
  • 提交:v0.2.9 之后的 6 个提交——代理类型化与失败契约整批、发帖作曲字段类型化、投票能力、两处可观测性修复
  • 变更:① 新增投票(CreatePollCard)——投票不走 GraphQL:POST caps.twitter.com/v2/cards/create.json(form-urlencoded 单键 card_data=JSON)拿 card_uri,再填进 CreatePost.card_uri 发帖。host 是 caps.twitter.com(不是 caps.x.com/cards.x.com)。card_data 逐字段对齐 jadx PollCardDataJsonAdapter.toJson;★null 字段省略而非显式写出★(Moshi 默认 serializeNulls=false),真机 200 确认。入参本地校验:选项 2–4 个、时长 [5,10080](0=默认 1440)。card_uri 的鸡蛋问题就此破除——此前验它需要一张真实 card,而产 card 的写路径不存在。② 发帖作曲字段类型化:geo({place_id?, coordinates?, geo_search_request_id?},坐标三字段恒输出)、semantic_core_ids([{domain_id, entity_id, group_id}],★三个均为字符串★)、conversation_control({mode, allowed_country_codes?},mode 为 PascalCase 枚举名,导出 7 个常量),CreatePost / Reply / Quote 三入口一致(server 侧共用 composeOpts)。社区帖随之可用:真机确认它不是独立 mutation,就是 CreatePost 把社区放进 semantic_core_ids(domain_id="31"/group_id="8"/entity_id=<社区 id>),SDK 提供 CommunityAnnotation() / community_annotation()。③ 代理类型化 + 失败契约(cebe33a):新增 internal/proxy 与 internal/neterr,AccountState.proxy 由裸字符串改为结构化 ProxyConfig(protocol/host/port/credentials),transport 补重试与非幂等标注;服务端统一 androidFailureContext,错误契约与 Python twitter_sdk.failure 对齐,失败时通过标准 status details 回传 state,调用方不再需要解析错误文本。④ 修复两处可观测性缺陷:logUpstreamFailure 此前只记 code/kind/operation 不记 err,attachment_url parameter is invalid. (44) 这类带明确原因的上游报错在日志里只剩 code=Unavailable kind=UNSPECIFIED(违反宪章 §5「原文写服务端日志」,已补回);GraphQL 错误摘要改为 message 优先(新增 gqlErrSummary),此前 snippet 从头截 220 字符而 errors 数组开头全是 extensions 里重复的 code/kind/name,真正说明原因的 message 正好被挤出窗口
  • 兼容性:★★state-breaking:proxy 非空的存量 AccountState 无法加载★★。AndroidAccountState.proxy(字段号 5)由 optional string(socks5h://user:pass@host:port)改为 ProxyConfig 消息,字段号未变但类型变了——实测旧字节在本版直接 DecodeError: Error parsing message,不是静默降级。影响边界已实测确认:proxy 为空的存量 state 仍可正常加载并跑完整业务链路(本版 E2E 用的就是一份 8-14 存档的 0.2.6 state);proxy 非空的必须重新登录。使用方的 state 兼容版本白名单不得把 0.3.0 加进去,除非能确认自己所有存量 state 的 proxy 均为空。AndroidDevice / AndroidSession 字段号无变动。另:FailureDetail / AndroidFailureContext 为新增 status details,旧调用方忽略即可
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 413 项通过;proto 生成物与文档站产物重跑零差异。新增契约测试 12 项:投票 card_data 形状与边界(含两/三/四选项与时长越界)、geo 三种组合、semantic_core_ids 字符串类型、conversation_control PascalCase 与国家码、三入口字段一致性、未传时保持 wire null、gqlErrSummary message 优先。state 兼容性边界经实测验证(proxy 空/非空两组对照)
  • E2E:passed——复用已持久化的 AccountState、全程未发起登录(账号仍在频控冷却期,该路径不消耗登录配额),发出的推文全部即时删除。本版通过项:CreatePollCard→card://…、CreatePost+card_uri(card_uri 首次验收)、CreatePost+conversation_control(Verified)、CreatePost+geo(place_id)、geo(仅 coordinates)、geo(place_id+coordinates)。连同 v0.2.8/v0.2.9 已验收项,发帖+媒体 63 个 RPC 中已真机验收 9 个(新增 CreatePollCard)
  • 部署:GHCR 0.3.0 与 latest;文档站根目录与 /pro/0.3.0/
  • Python SDK:0.3.0,三件产物。新增 posts.create_poll_card(choices, duration_minutes=0);create/reply/quote 三者新增 geo / semantic_core_ids / conversation_control 三个 keyword-only 参数;新增模块级 community_annotation() 与 7 个 CONVERSATION_* 常量;新增 twitter_sdk.proxy 与重写的 twitter_sdk.failure(typed 错误,不再解析错误文本)
  • 已知限制:previous_tweet_id(编辑已发布推文)真机不可用且原因未定——上游一律回 code=190 AuthorizationError: Status creation failed。已逐一排除:字段名与形状(legacy JsonEditOptionsInput 与 Apollo adapter a2 双向核实均为 {previous_tweet_id: String})、编辑资格(实测 is_edit_eligible=true、edits_remaining=5、窗口未过)、时序(0/20/60s 均被拒)、features(带与不带相同)。最可能的剩余解释是 12.13 真机编辑并不走 Apollo CreatePost,需真机抓一次编辑请求才能定论;该字段不得作为可用能力使用。定时发帖与长文 features 仍缺,两者都需要能看到对应入口的 Premium 账号。semantic_core_ids(含社区帖)代码就绪但未真机验收(需先加入社区)。图上 @ 人(tagged_users)形状已知但未接 proto(需把 repeated string media_ids 扩成 media 实体)。另记录一处与真机的画像差异:真机 CreatePost body 无 features 键,本仓库配了 featCreatePost,两者普通发帖均成功,是否对齐需单独评估
  • 升级与回滚:升级前必须确认存量 state 的 proxy 是否为空——非空的一律重新登录,否则该账号所有调用会在加载 state 时直接失败。代理配置由字符串改为结构化 ProxyConfig,调用方需相应改造。回滚到 0.2.9 会丢失投票、三个作曲字段与两处可观测性修复;本版写入的新格式 state 在旧版同样无法加载(proxy 字段类型不兼容是双向的)
v0.2.9 · 2026-08-19
  • 提交:v0.2.8 之后的发帖三入口字段对齐、Quote 缺陷修复,以及一轮覆盖 7 项的真机验收
  • 变更:① ★破坏性★ Quote 新增必填的 quoted_screen_name,修复引用发帖一直不可用——原实现把 attachment_url 拼成 https://twitter.com/i/web/status/{id},上游一律拒为 BadRequest: attachment_url parameter is invalid. (44)。真机实测四种形状后确认:上游只接受带作者名的永久链接 https://{twitter.com|x.com}/{screen_name}/status/{id},匿名的 /i/web/status/ 两个域名都不行。这意味着光有 tweet_id 拼不出合法链接,作者名必须由调用方给出,因此 QuoteRequest 增加必填字段而不是保留一个必然失败的旧签名。缺该字段时在 handler 入口即 InvalidArgument 拒绝,不再发出去吃语义不明的 400。守护断言 TestQuoteAttachmentURLShape 同时断言「带作者名」与「不含 /i/web/status/」。② 三个发帖入口字段对齐:card_uri / broadcast_to_followers 此前只有 CreatePost 能传,Reply / Quote 没有——三者收口在同一个 submitPost、发的是同一个 CreatePost operation,字段层面没有任何理由不一致。改为共享 PostOptions(新增 apply 收口,消除三处重复分支),proto 侧 ReplyRequest / QuoteRequest 同步补齐。③ Reply 开放 exclude_reply_user_ids:此前硬编码为空数组,「回复时不提及某些人」这个客户端能力被吃掉了;现可由调用方传入,不传时仍序列化为 [](★不能是 null★,nil 切片会改变 wire 形状,TestReplyExcludeUserIDs 兜住)。④ 关闭两处协议核对 TODO:UploadVideo 的 processing_info 形状与 SetMediaMetadata 的 alt_text.text 嵌套形状均经真机确认成立
  • 兼容性:AccountState 不受影响,存量状态继续直接沿用、不需要重新登录。破坏性变更仅一处:QuoteRequest 新增必填 quoted_screen_name=7,Python posts.quote() 的第二个位置参数随之变为 quoted_screen_name——但 Quote 在本版之前 100% 失败(attachment_url 无效),不存在能正常工作的调用方会被破坏,实际影响是「从不可用变为可用」。ReplyRequest 新增 card_uri=5 / broadcast_to_followers=6 / exclude_reply_user_ids=7,QuoteRequest 新增 card_uri=5 / broadcast_to_followers=6,均为新增字段号,不传即保持原 wire 字节。Go SDK 的 Client.Quote / Client.Reply 签名变更(源码不兼容,gRPC 契约除上述必填字段外不受影响)
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 386 项通过;proto 生成物与文档站产物重跑零差异。新增 5 项发帖 variables 契约单测:三入口字段一致性、Reply/Quote 未传时保持 null、exclude_reply_user_ids 空数组语义、attachment_url 形状守护
  • E2E:passed——一轮 5 条推文覆盖 7 项,全部通过后即时删除:CreatePost 纯文字(回归)、UploadMedia+带图发帖(回归)、SetMediaMetadata(alt)、Reply 带图+exclude_reply_user_ids、Quote+broadcast_to_followers、CreatePost 带图+broadcast_to_followers、UploadVideo(640x360 H.264/AAC 3 秒 mp4,含转码 STATUS 轮询)+带视频发帖。验收复用已持久化的 AccountState,全程未发起登录——测试账号仍处于频控冷却期,这条路不消耗登录配额;该做法已写入 CLAUDE.md 测试章节作为默认路径。Quote 的 attachment_url 形状是在验收中断后用四组候选逐一探测定位的(失败不产生推文,可安全试错)
  • 部署:GHCR 0.2.9 与 latest;文档站根目录与 /pro/0.2.9/
  • Python SDK:0.2.9,三件产物。posts.quote() 新增必填位置参数 quoted_screen_name;posts.reply() 新增 card_uri / broadcast_to_followers / exclude_reply_user_ids 三个 keyword-only 参数;posts.quote() 新增 card_uri / broadcast_to_followers
  • 已知限制:card_uri 仍未真机验收——验它需要一个真实存在的 card,而产出 card 的写 RPC 本仓库还没有(投票卡即属此列),是先有鸡后有蛋的关系。发帖时的投票、定时、地点、谁可以回复、社区帖、订阅者专属、图上 @ 人等作曲能力仍未实现:字段在 createPostVars 里齐全,但抓包 item 304 是纯文字帖,只证明了「未填时为 null」,嵌套形状盲填会 400,逐字段抓包清单见 docs/development/createpost-field-capture-checklist.md。编辑已发布的推文仍无写路径(edit_options 硬编码为空对象,只有 EditHistory/EditablePosts 两个读 RPC)。长文 CreateNotePost 与草稿系列仍为 variables_json 透传、未类型化亦未验收。v0.2.7/v0.2.8 的其余限制继续有效
  • 升级与回滚:升级需同步修改 Quote 调用点补上 quoted_screen_name,其余调用方无改动。回滚到 0.2.8 会让 Quote 重新变为必然失败,并丢失 Reply/Quote 的可选字段与 exclude_reply_user_ids
v0.2.8 · 2026-08-19
  • 提交:v0.2.7 之后的 B 类行为遥测补齐,加上本次的 CreatePost 可选作曲字段与带图发帖首次真机验收
  • 变更:① 带图发帖全链路真机验收通过(本版核心)——UploadMedia(图片 INIT→APPEND→FINALIZE)→ media_id → CreatePost(media_ids=[...]) → DeletePost 在真实 api.x.com 上跑通,这条链路自实现以来一直是「代码完整、未经验证」状态,本版起为已验收。验收顺带定位到一个此前不可能通过的 E2E 缺陷:用例用的 1×1 透明 PNG 会被上游在 FINALIZE 阶段拒绝,返回 {"request":"/1.1/media/upload2.json","error":"media type unrecognized."}——INIT 与 APPEND 均正常通过、错误只在最后一步暴露,且与 media_category 是否传值无关;X 要的是能真正解码出像素的图片,退化尺寸不算。用例改为内存生成 256×256 真实 RGB 渐变 PNG(不依赖外部文件),并在函数 docstring 里写死「不要换回 1×1」的原因。② CreatePost 新增两个可选作曲字段:card_uri(挂已有卡片)与 broadcast_to_followers(广播给粉丝),proto3 optional,不传时 wire 保持显式 null、与已验收纯文字路径字节完全一致(TestCreatePostVars_OptionalsDefaultNull 守护)。只暴露这两个基元标量是刻意保守:抓包 item 304 是空 media 的纯文字帖,只证明了「字段名 + 未填时为 null」,没证明任何嵌套字段填值后的形状,盲填会 400。其余作曲字段(conversation_control / geo / exclusive_tweet_control_options / 媒体 tagged_users 等)的抓包清单与落地顺序见 docs/development/createpost-field-capture-checklist.md。③ B 类行为遥测补齐:unlike / retweet / unretweet / bookmark-remove / unfollow / block / unblock / mute / unmute 九个动作接上 jot 发送,真机语料 440 → 559 条。④ 接入文档补出口代理章节:说明 0.2.6 及更早版本 AccountState.proxy 静默失效的表现与排查方向(「一批绑不同线路的账号同时全量 RATE_LIMITED」,排查方向是版本不是供应商),并把登录频控冷却时长从「分钟级」更正为实测 ≥ 4.5 小时
  • 兼容性:无破坏性变更,存量 AccountState 可直接沿用、不需要重新登录。CreatePostRequest 新增 card_uri=4 / broadcast_to_followers=5 两个 proto3 optional 字段——新增字段号纯向后兼容,旧调用方不传即保持原有 wire 字节;AndroidAccountState 及其子消息字段号无任何变动。Go SDK 的 Client.CreatePost 签名新增 opts PostOptions 参数(Go 层面的源码不兼容,gRPC / Python 契约不受影响)
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 386 项通过。新增 2 项发帖 variables 单测(两字段传值后的 JSON 类型正确;未传时仍为显式 null)。E2E 用例修复后的 _png() 与真机验收实际上传的那张图片字节完全一致(已断言比对)
  • E2E:passed——带图发帖全链路在真实 api.x.com 上通过:Me 探活 → UploadMedia(256×256 PNG,media_category=tweet_image)→ CreatePost(media_ids=[...]) → DeletePost,发出的推文已即时删除。验收方式是复用已持久化的 AndroidAccountState,全程未发起登录——测试账号仍处于平台频控冷却期(本轮尝试冷启动登录 1 次,RATE_LIMITED at username step,服务端日志确认 attest / official-app 均已通过、属账号级频控;按 L-003 未做任何重试)。这也顺带实证了 v0.2.7「存量 state 可直接沿用」的兼容性承诺:8-14 存档的 0.2.6 格式 state 被本版 SDK 直接加载并完成了完整业务链路
  • 部署:GHCR 0.2.8 与 latest;文档站根目录与 /pro/0.2.8/
  • Python SDK:0.2.8,三件产物。posts.create() 新增 card_uri / broadcast_to_followers 两个 keyword-only 参数,均默认 None(不下发)
  • 已知限制:本版新增的 card_uri 与 broadcast_to_followers 两个字段未经真机验收——本轮 E2E 跑的是两者均不传的默认路径(wire 与已验收路径一致),字段填值后上游是否接受尚未验证。UploadVideo(含转码 STATUS 轮询与 processing_info 形状)与 SetMediaMetadata(alt text)仍为 🔷 已实现·未验收;带图回复与带图引用同样未单独验收。发帖时的投票、定时、地点、谁可以回复、社区、订阅者专属、媒体 @ 人等作曲能力仍未实现,需先抓包锁定嵌套形状。v0.2.7 遗留的限制(大多数新增 RPC 未真机验收、代理仅支持 socks5h、iOS / Web 未实现)继续有效
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。回滚到 0.2.7 会丢失两个可选发帖字段与 B 类遥测;不传这两个字段的调用方无任何行为差异
v0.2.7 · 2026-08-18
  • 提交:v0.2.6 之后的 42 个提交——代理链路事故修复(issue #4)、jot client_event 遥测体系、以及按接口全集补齐的 359 个 RPC
  • 变更:① 修复 AccountState.proxy 全程失效(issue #4,事故级)——NewHTTPClientFP 给两个 transport 都装了自定义 DialTLSContext,而 http.Transport.Proxy 在这种情况下不生效、http2.Transport 结构体根本没有 Proxy 字段;ALPN 默认协商到 h2 且 X 全站支持 h2,于是所有账号流量都从部署机公网 IP 直出,配置的代理形同虚设且无任何报错。下游 40 个账号绑 4 条区域住宅线路全部 RATE_LIMITED,根因即此。改为在 dialTCP 里自建 SOCKS5h 隧道、再在隧道上做 uTLS 握手(隧道对 TLS 层透明,ClientHello 指纹与 SNI 均不受影响);同时删除 h1 上那个有害的 Proxy 字段——它会让 Go 把 uTLS 握手打到代理地址、SNI 变成代理域名。只接受 socks5h://:socks5(无 h)在服务端本地解析目标域名(DNS 泄漏,且实测同一住宅节点同端口 socks5:// 失败、socks5h:// 成功),http/https CONNECT 把目标 host 明文写进代理的 HTTP 层;其它 scheme 一律拒绝,不静默降级成直连。代理只从每账号显式参数读取,刻意不读 HTTP(S)_PROXY 环境变量——进程级变量会让同机所有账号共用出口,等于没做隔离。② 新增 jot client_event 遥测:写操作后按 Android 12.13 协议 best-effort 发 /1.1/jot/client_event(无 jot 的裸客户端画像异常);新增 oauth.Cred.HeaderForm 支持 form-body 签名;会话状态(session_id + 递增 seq)随 AccountState 往返——真机 seq 跨事件递增,每次从 1 开始是可检测破绽。事件名一律经真机语料护栏 FilterReal 过滤,从不发送未在真机抓到过形状的名字。③ 遥测动态 client_event_info:从 GraphQL 响应抽取每条 item 的 component 按 tweetID 入 LRU(256) 缓存,点赞/收藏/删除的 item 级事件名用服务端当前 component 拼装,跟随平台 drift;未命中缓存或语料未收录时整批回退固定 Set。④ RPC 覆盖 16 → 375(按 391 个接口全集补齐 ≈96%):新增 DM/XChat 48、x-money 78、列表 23、社区 13、语音房 10、搜索 9、审核 7、设置 7、书签 10、关系 15、时间线与帖子长尾等
  • 兼容性:无破坏性变更,存量 AccountState 可直接沿用、不需要重新登录。AndroidAccountState 的 account=1 / device=2 / app_version=3 / proxy=4 / session=5 字段号全部不变,仅新增 telemetry=6;AndroidDevice(1–14)与 AndroidSession(1–5)字段号无变动。proto3 新增字段号是纯向后兼容,旧持久化状态反序列化后 telemetry 缺席为 nil,服务端自动补空。行为变更需注意:proxy 字段此前是无效字段,本版起真正生效——若此前填的是 http:///socks5:// 形式,本版会显式报错拒绝而不是继续直连,需改为 socks5h://;此前依赖「填了代理但实际直连」这一失效行为的部署,出口 IP 会真正改变
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 386 项通过;proto 生成物与文档站产物重跑零差异。代理修复含 4 项单测(scheme 白名单拒绝、缺省端口、完整 SOCKS5h 握手端到端含用户名密码认证与域名透传、代理拒绝时错误带可读 REP 原因)。代理链路已在真实网络上验证:新增 opt-in 的 TestLiveProxyEgress(未设 TWITTER_TEST_PROXY 时跳过),实测经本包构建的客户端出口 IP 与直连出口不同、落在配置的日本东京节点上——这正是 v0.2.6 静默失效的那一环
  • E2E:skipped —— 测试账号处于平台频控冷却期。本轮消耗 2 次冷启动登录(session fixture 与独立登录用例各 1 次),均在 username step 返回 RATE_LIMITED,服务端日志确认为「账号/IP 被临时限制登录(attest / official-app 已通过,属频控而非凭据问题)」,即设备证明与风控闸门均通过、请求正常到达上游,纯属账号级频控;按 L-003 纪律未做任何重试(重试本身又是一次失败登录,只会延长 ≥4.5 小时冷却)。代理修复这一项本身已有真实网络证据(见「验证」),未覆盖的是「经代理完成完整登录与业务链路」这一段;本版新增的 359 个 RPC 与遥测发送路径同样未经真机验收
  • 部署:GHCR 0.2.7 与 latest;文档站根目录与 /pro/0.2.7/
  • Python SDK:0.2.7,三件产物。login() 的 proxy 参数语义随之生效(格式 socks5h://user:pass@host:port,留空即直连);E2E 夹具新增可选 TWITTER_TEST_PROXY,未设时该字段保持 absent 而非空串
  • 已知限制:本版绝大多数新增 RPC 未经真机验收(16 → 375,账号处于频控冷却期无法验证),调用方应按「协议形状已对齐接口全集、真实响应未验证」对待,遇到字段偏差请反馈。遥测语料只覆盖已抓到的真机事件名,未收录模块的 jot 调用会被 FilterReal 静默丢弃(宁可少发不可编造);回复发帖的 event_name 尚未抓到故一律不发;controller_data 解析入库但不回抛。代理仅支持 socks5h,需要 HTTP CONNECT 或 socks5 的部署本版不可用。CREDENTIAL_INVALID 仍无发出点;X 的「临时限制登录」尚未从 RATE_LIMITED 里进一步细分出独立分类。iOS / Web 平台仍未实现
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。升级前必须把 proxy 字段改成 socks5h:// 形式,否则该账号的调用会直接报错(这是有意的——静默直连正是本版要消灭的失效)。回滚到 0.2.6 会重新出现代理完全失效(流量回到部署机 IP 直出、多账号共用出口被平台关联),同时丢失遥测与 359 个 RPC;telemetry 字段被旧版忽略,状态本身仍可用
v0.2.6 · 2026-08-13
  • 提交:v0.2.5 之后的 proto 命名空间修复(issue #1)与账号巡检字段补齐
  • 变更:① 修复与 threads-sdk 无法同进程共存(issue #1)——protobuf 的 descriptor pool 是进程级单例、按 proto 文件名去重,而 package 声明不参与去重;两个 SDK 都用裸文件名,common.proto 与 posts.proto 直接撞名,导致 import threads_sdk; import twitter_sdk 双向都抛 duplicate file name。proto 移到 proto/twitter/ 并按 twitter/xxx.proto 编译,descriptor 名因此全局唯一。② User 补 6 个账号巡检字段:location / created_at(unix 秒)/ protected / profile_image_url / suspended / needs_phone_verification,字段来源是真机采集到的上游 legacy 块实际内容。③ 放宽 protobuf 约束 <7 → <8:原约束会把 protobuf 降到 6.33.6,而 threads-sdk 的 gencode 要求 runtime ≥ 7.35.0,两者装在一起直接 VersionError。④ 快速开始的 docker 示例补 TWITTER_GRPC_ALLOW_INSECURE=1:容器内必须监听 0.0.0.0 才能做端口映射,而服务端默认拒绝非 loopback 的明文监听,照抄原示例会立即退出;同时补充了跨主机访问必须改配 TLS 的边界说明与健康检查用法
  • 兼容性:无破坏性变更。proto 改名只影响 FileDescriptorProto.name 这一个字符串——package(twitter.android.v1)不变、Python 导入路径(from twitter_sdk import x)不变、gRPC 方法全名(/twitter.android.v1.PostsApi/CreatePost)不变、已有 AccountState 序列化数据不受影响。User 的 6 个字段为新增,旧调用方忽略即可
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 27 项、发布工具 71 项通过;llms_docs.py check 通过。issue #1 的复现脚本在装有 protobuf 7.35.1 + threads-sdk + twitter-sdk 的干净 venv 里两个方向均通过,同期确认 descriptor 名唯一、AccountState 往返无损、threads-sdk 侧不受影响。新增守护测试断言每个生成模块的 descriptor 名带 twitter/ 前缀——该失败在本仓库自己的测试里永远看不到(只装一个 SDK 时不冲突)
  • E2E:passed。Me 真机回归通过;新增的 6 个字段在真实响应上确认填充(created_at 正确解析为 unix 秒、profile_image_url 非空、三个状态位正常返回),零上游错误
  • 部署:GHCR 0.2.6 与 latest;文档站根目录与 /pro/0.2.6/
  • Python SDK:0.2.6,三件产物。可与 threads-sdk 装在同一环境并同进程导入
  • 已知限制:账号所在地(account_based_in)确认不可得——真机核对上游 legacy 全部键均无该字段,location 是用户自填地区不能近似替代,需要该字段只能走 Web 通道。CREDENTIAL_INVALID 仍无发出点(密码错误表现为 UNSPECIFIED);BANNED / LOCKED_NEEDS_VERIFICATION 仍依赖未验证的 GraphQL 错误码 64/326,做不可逆处置前建议用 Me 的 suspended 二次确认。DeletePost 受协议限制无法区分删除结果。attest token 过期的重铸重试兜底未实现。iOS / Web 平台仍未实现
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。回滚到 0.2.6 之前会重新出现与 threads-sdk 的同进程冲突,且 User 的 6 个新字段消失(需回退到自行解析 raw_json)
v0.2.5 · 2026-08-13
  • 提交:v0.2.4 之后的失败分类、登录诊断、attest 生命周期修复与首轮真机验收(对应测试通道 test-v0.4.4 ~ test-v0.4.7)
  • 变更:本版集中解决集成方反馈的一期阻塞项。① 失败原因结构化分类:新增 internal/failure,按「调用方该怎么处置」分类(限流 / 风控 / 会话失效 / 凭据无效 / 2FA 缺码 / 设备证明缺失 / 锁定 / 封禁 / 上游不可用 / 未确定),经 trailing metadata x-failure-kind、x-failure-stage 下发。同时修复一处会误杀账号的分类错误:原先按错误文案子串匹配,而登录限流提示里含 official-app 已通过,命中风控分支返回 PermissionDenied,调用方据此把被临时限流的健康账号当作封禁剔除;密码步之后的限流提示不含该词又落到 Unavailable——同一个频控产出两个都错的码。② 登录逐步诊断 steps[]:在登录状态机的 9 个流程边界记录 name / ok / failure_kind / failure_summary / http_status,经 x-login-steps-bin 下发,把「登录失败了」定位到具体步骤。③ 修复 attest 生命周期:每个业务 RPC 此前都重跑完整 attest bootstrap,一次消耗两次 GenerateAttestationNonce,而该端点有短窗口配额,超出返回 404;改为复用状态里已往返的会话级 token。④ LoginRequest 支持 totp_seed:服务端在执行 2FA 步骤时才算码,消除调用方本地算码在途过期的竞态。⑤ 新增公开文档 integration-notes.md:失败分类与处置、AccountState 在 0.2.x 内的 wire 兼容承诺、容器健康检查两种方式、账号使用节奏建议值、写操作超时查证配方、删帖结果的协议限制。⑥ AccountState 契约守护:新增快照测试锁定状态三件套的字段编号,把兼容承诺变成可执行门禁
  • 兼容性:无破坏性变更。totp_seed、steps[]、DeletePostResponse.raw_json 均为新增,旧调用方忽略即可;gRPC 状态码取值集合未扩大。AndroidAccountState 三个 message 未改动,旧状态可原样沿用、无需重新登录,并已由契约快照测试锁定。Python 门面 posts.delete() 返回值由 None 改为 str(原始 data 节点),忽略返回值的调用方不受影响
  • 平台:android
  • 验证:gofmt / go build / go vet / go test -race ./... 全绿;Python 离线 26 项、发布工具 71 项通过;llms_docs.py check 通过;生产镜像构建与 wheel/sdist 干净环境安装通过。关键回归:限流不得被判为 PermissionDenied(用含 official-app 已通过 的原文案锁死)、未分类失败必须停在可重试码、steps[] 摘要不得携带上游正文、TOTP 按 RFC 6238 官方向量校验、契约快照实测有效
  • E2E:passed。UsersApi/Me 真机验收通过(返回账号与登录账号一致,同轮 HomeTimeline / UserTweets 非空);attest 修复前后同批用例对照:3 个业务 RPC 的 nonce 消耗 6 次→0 次、上游错误若干→0、耗时 17.66s→11.64s;UserTweets 超时查证配方实测成立(发帖后退避 3 秒命中);DeleteTweet 真删与重复删响应逐字节相同
  • 部署:GHCR 0.2.5 与 latest;文档站根目录与 /pro/0.2.5/
  • Python SDK:0.2.5,三件产物。新增 twitter_sdk.failure(failure_kind / failure_stage / login_steps 与 RETRYABLE / NEEDS_HUMAN 分组);login() 新增 totp_seed
  • 已知限制:CREDENTIAL_INVALID 目前无发出点,密码错误表现为 UNSPECIFIED(调用方应按可重试处理,不得据此改账号状态);BANNED / LOCKED_NEEDS_VERIFICATION 仅依赖 GraphQL 错误码 64/326,该码在 Android 协议上的真实样本尚未采集,收到 BANNED 建议二次确认再做不可逆剔号。DeletePost 受上游协议限制无法区分「真删」与「帖子不存在」。User 消息尚未补 location / created_at / protected / profile_image_url / suspended / needs_verification(数据已确认存在于 raw_json 的 legacy 块,下一版落地)。attest token 过期时的「拒绝即重铸并重试」兜底未实现。iOS / Web 平台仍未实现
  • 升级与回滚:直接升级,无迁移动作,AccountState 原样沿用。回滚到 0.2.4 后 x-failure-kind / x-login-steps-bin 消失(Python 侧安全降级为 UNSPECIFIED 与空列表)、totp_seed 被忽略(2FA 账号需回退为传当前码 totp)、attest 恢复为每次重铸(连续业务调用会重新撞 404)、限流误判为 PermissionDenied 的问题重现
v0.2.4 · 2026-08-12
  • 提交:v0.2.3 之后的 SDK 下载页文件名修复
  • 变更:修复文档站下载的 wheel 无法安装的缺陷。此前下载页把 wheel 重命名为不带版本号的「稳定别名」twitter_sdk-py3-none-any.whl,而 wheel 文件名是 PEP 427 规定的元数据,缺版本段会让 pip 与 uv 直接拒装并报 Must have a platform tag。改为保留版本化文件名 twitter_sdk-X.Y.Z-py3-none-any.whl;同步修正 workflow 中硬编码旧别名的校验步骤,并新增 sha256sum -c 核对下载页公布的校验和;新增 test_stage_sdk_docs.py 断言产物名符合 PEP 427 且组装过程不重命名
  • 兼容性:下载链接变更——由 downloads/twitter_sdk-py3-none-any.whl 变为 downloads/twitter_sdk-X.Y.Z-py3-none-any.whl,旧别名路径不再存在。GitHub Release 附件名一直是版本化的,不受影响;sdist 与复制包此前虽也被重命名但仍可安装
  • 平台:android
  • 验证:本地 71 项脚本测试通过;test-v0.4.5 已在测试通道完成端到端实测——从文档站下载 wheel、sha256sum -c 通过、干净环境安装成功、导入并完成状态往返
  • E2E:skipped —— 本版仅文档站产物命名与校验修复,业务代码与 gRPC 契约自 v0.1.0 起未改动;测试账号仍处于平台频控冷却期
  • 部署:GHCR 0.2.4 与 latest;文档站 /pro/0.2.4/、/pro/latest/ 与站点根
  • Python SDK:0.2.4,三件产物;这是第一个可从文档站直接下载安装 wheel 的版本
  • 已知限制:v0.2.3 及更早版本的文档站下载页提供的 wheel 无法安装(需手动重命名为带版本号的形式,或改用 GitHub Release 附件);iOS / Web 未实现;13 个 RPC 未真机验收
  • 升级与回滚:无迁移动作。回滚将 IMAGE_TAG 指回 0.2.3
v0.2.3 · 2026-08-12
  • 提交:v0.2.2 之后的发布记录更正
  • 变更:更正 v0.2.0 与 v0.2.1 条目中关于文档站可用性的不准确表述。原文称「v0.2.0 文档站未发布、/pro/0.2.0/ 不存在」,实际情况是该版本的 mike deploy 已成功写入版本树,仅最后的 Cloudflare 上传步骤失败,v0.2.1 发布时随全量聚合补齐——/pro/0.2.0/ 内容完整且版本戳正确。错误表述会误导回滚决策(让人以为该版本文档不可用),故更正。同时在发布规范中记录这一自愈特性
  • 兼容性:无破坏性变更;仅文档表述更正
  • 平台:android
  • 验证:已逐条核对 /pro/0.1.0/、/pro/0.2.0/、/pro/0.2.1/、/pro/0.2.2/ 均可访问且版本戳与各自版本一致;核对 v0.2.0 那次流水线中 mike deploy 为 success、仅 Wrangler 上传为 failure
  • E2E:skipped —— 仅文档表述更正,无代码变更;测试账号仍处于平台频控冷却期
  • 部署:GHCR 0.2.3 与 latest;文档站 /pro/0.2.3/、/pro/latest/ 与站点根
  • Python SDK:0.2.3,三件产物;代码与前序版本功能等价
  • 已知限制:iOS / Web 未实现;13 个 RPC 未真机验收;生产 Environment 无审批规则
  • 升级与回滚:无迁移动作
v0.2.2 · 2026-08-12
  • 提交:v0.2.1 之后的站点根复制清单修复
  • 变更:docs_site.py 把「复制到站点根」由手工白名单改为全量复制 + 排除版本树目录。此前每加一个文档页都需同步该清单,漏掉即在站点根静默 404——v0.2.1 发布后 /grpc/llms-full.txt、/python/llms-full.txt、/releases/ 三者在站点根缺失(版本目录 /pro/0.2.1/ 下均正常)
  • 兼容性:无破坏性变更;站点根内容仍完全来自 pro/latest,仅补齐此前遗漏的页面
  • 平台:android
  • 验证:本地 63 项脚本测试通过(新增「后加页面无需登记即到根」与「版本树不套娃」双向断言);test-v0.4.2 已在测试通道验证
  • E2E:skipped —— 本版仅文档站聚合逻辑修复,业务代码与 gRPC 契约自 v0.1.0 起未改动;测试账号仍处于平台频控冷却期
  • 部署:GHCR 0.2.2 与 latest;文档站 /pro/0.2.2/、/pro/latest/ 与站点根
  • Python SDK:0.2.2,三件产物;代码与前序版本功能等价
  • 已知限制:iOS / Web 未实现;13 个 RPC 未真机验收;生产 Environment 无审批规则
  • 升级与回滚:无迁移动作。回滚将 IMAGE_TAG 指回 0.2.1;文档站可用 /pro/0.2.1/ 固定版本地址
v0.2.1 · 2026-08-12
  • 提交:v0.2.0 之后的 wrangler 版本钉定修复
  • 变更:给 cloudflare/wrangler-action 钉死 wranglerVersion: 4.59.2。此前未指定版本,每次安装 wrangler@4 最新版;v0.2.0 发布时 wrangler@4.122.0 依赖的 miniflare@5.20260811.0-alpha 在 npm 上不存在,导致文档发布作业失败
  • 兼容性:无破坏性变更;仅 CI 依赖版本钉定。文档内容与 v0.2.0 一致
  • 平台:android
  • 验证:本地门禁全绿;test-v0.4.1 已在测试通道验证 wrangler 安装与文档发布恢复正常
  • E2E:skipped —— 本版仅 CI 依赖钉定,业务代码与 gRPC 契约自 v0.1.0 起未改动;测试账号仍处于平台频控冷却期
  • 部署:GHCR 0.2.1 与 latest;文档站 /pro/0.2.1/、/pro/latest/ 与站点根
  • Python SDK:0.2.1,三件产物;代码与 0.2.0、0.1.0 功能等价
  • 已知限制:v0.2.0 的文档在其发布当次未能上传到 Cloudflare(站点根在本版发布前停留在 v0.1.0 内容),但其版本树已由 mike 写入,本版全量聚合时已补齐,/pro/0.2.0/ 现可访问。其余限制同 v0.2.0
  • 升级与回滚:无迁移动作。回滚将 IMAGE_TAG 指回 0.2.0 或 0.1.0;文档站可用 /pro/0.2.0/ 或 /pro/0.1.0/ 固定版本地址
v0.2.0 · 2026-08-12
  • 提交:v0.1.0 之后至 test-v0.4.0 对应提交,文档站能力补齐
  • 变更:(1)新增文档站版本切换 UI,可在 Test / Pro 两个通道与各自历史版本之间切换,切换时保留当前页面路径;(2)AI 文档补足字段级契约——逐 RPC 输出请求/响应字段的类型、约束与语义,并附全部 27 个 message 定义,llms-full.txt 由 246 行增至 829 行;(3)新增 grpc/ 专项入口(含全部 proto 原文,供其它语言自行生成 stub)与 python/ 专项入口(门面方法签名、状态序列化、错误处理);(4)接口参考与模块指引改为由 proto descriptor 与元数据自动生成,消除与契约的漂移;(5)新增 5 个任务编排工作流,写明步骤顺序与步骤间参数传递;(6)新增按通道隔离的版本更新页;(7)状态页新增任务可用性汇总
  • 兼容性:无破坏性变更;gRPC 契约、Python 门面 API 与 SDK 用法均未改动。仅文档与文档生成器变更
  • 平台:android
  • 验证:本地门禁全绿(63 项脚本测试、20 项 Python 离线契约测试、llms 文档校验、mkdocs 严格构建、proto 生成物一致性、SDK 三件产物干净环境安装、生产镜像构建);CI validate 复核同一套离线门禁;test-v0.4.0 已在测试通道完整验证(版本切换数据源、新增入口可达、字段契约上线)
  • E2E:skipped —— 本版仅文档与生成器变更,业务代码与 gRPC 契约自 v0.1.0 起未改动;登录与发帖/删帖的真机验收结论沿用 2026-08-11 记录,其余 RPC 仍标记 implemented 待频控冷却期后验收
  • 部署:GHCR ghcr.io/robin528919/go-twitter-api:0.2.0 与 :latest 已发布。文档发布作业当次失败:cloudflare/wrangler-action 未钉版本,安装 wrangler@4.122.0 时其依赖 miniflare@5.20260811.0-alpha 在 npm 上不存在。失败点在最后的上传步骤——mike deploy 已成功把本版写入 gh-pages 版本树,因此 v0.2.1 发布时随全量聚合一并上线,/pro/0.2.0/ 现可正常访问。根因修复见 v0.2.1
  • Python SDK:0.2.0,wheel、sdist 与 twitter_sdk-0.2.0-copy.zip 三件产物;SDK 代码与 0.1.0 功能等价,版本号随 Tag 同步递增
  • 已知限制:iOS / Web 平台未实现;互动/媒体/读取类 13 个 RPC 未经真机验收(能力清单标 implemented_not_verified);生产 Environment 无审批规则,推送生产 Tag 即触发发布;不发布 PyPI
  • 升级与回滚:无迁移动作。回滚将 IMAGE_TAG 指回 0.1.0 并重跑 compose up;文档站回滚可用 /pro/0.1.0/ 固定版本地址
v0.1.0 · 2026-08-12
  • 提交:4aebf0a(Android 登录与业务 RPC 闭环)至 test-v0.3.5 对应提交,全部发布基础设施与文档站能力
  • 变更:首个生产版本。交付 Tag 驱动的发布流水线(本地十一步离线门禁 + CI 复核 + GHCR 镜像)、Python SDK 随 Tag 同版本发布(wheel/sdist/可复制包,干净环境安装校验)、Cloudflare 公开文档站与 AI 对接能力清单(llms.txt / llms-full.txt / capabilities.json,公开白名单 + 输出侧边界断言)、跨主机 TLS compose 变体与默认关闭的远程部署作业。业务能力为 Android 平台 5 个 service / 16 个 RPC
  • 兼容性:首个生产版本,无历史兼容性负担。gRPC 契约以本版 proto 为基线
  • 平台:android
  • 验证:本地门禁全绿(gofmt、go mod verify、go vet、go test -race、proto 生成物一致性、Python 离线契约测试 20 项、发布工具测试 55 项、llms 文档校验、SDK 三件产物干净环境安装、生产镜像构建);CI validate 复核同一套离线门禁;测试通道已连续发布 5 个版本验证全链路;经 code-reviewer 审查并修复全部 HIGH 级发现
  • E2E:skipped —— 本版发布内容为发布基础设施与文档站,业务代码自 4aebf0a 起未改动;登录与发帖/删帖已于 2026-08-11 完成真机验收(见 docs/llms-metadata.json 的 verified 标记),互动/媒体/读取类 RPC 标记为 implemented 而非 verified,其真机验收待测试账号频控冷却期结束
  • 部署:GHCR ghcr.io/robin528919/go-twitter-api:0.1.0 与 :latest;文档站 /pro/0.1.0/、/pro/latest/ 与站点根稳定入口;远程部署开关默认关闭
  • Python SDK:0.1.0,wheel、sdist 与 twitter_sdk-0.1.0-copy.zip 三件产物,附加到本 Tag 的正式 GitHub Release,并在文档站下载页公开提供(含 SHA256SUMS 与 manifest.json)
  • 已知限制:iOS / Web 平台未实现,其命名空间不可调用;互动/媒体/读取类 RPC 未经真机验收(能力清单标记 implemented_not_verified);生产 Environment 无审批规则(private 仓库免费计划限制),推送生产 Tag 即触发发布;自定义域 twitter-api.es007.com 待补 CNAME 记录,当前经 go-twitter-api-docs.pages.dev 访问;不发布 PyPI
  • 升级与回滚:首个版本无升级路径。回滚将 IMAGE_TAG 指回上一固定版本后重跑 compose up;SDK 回滚使用上一版本 Release 附件。已推送的生产 Tag 不可删除后复用,修复走递增新版本