生产环境版本更新¶
当前浏览的是 生产环境 文档,本页只列出生产版本的更新记录。
环境说明
用页面顶部的版本选择器切换到 测试通道后,这里会显示测试版本的更新记录。
当前版本¶
v0.4.1 · 2026-09-17¶
- 提交:
v0.4.0之后的 1 个功能提交——b64fd32WebUsersApi新增Followers/Following/UserByRestID,外加本 CHANGELOG - 变更:①
WebUsersApi新增 3 个只读 RPC:UserByRestID(按数字 id 查用户,GraphQLUserByRestId,走api.x.com/graphql且只发variables)、Followers/Following(拉一页粉丝 / 关注列表,不自动翻页,调用方用next_cursor继续)。新增 messageWebUserByRestIDRequest、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 分钟)Followers50、Following500、UserByRestId500。证据留本地 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已改用共用解析器,本版只有离线双形态测试覆盖,未单独真机复测(列表条目走同一解析器并已真机通过,但UserByScreenNameRPC 本身未重跑)。协议来源为外部参考实现,无原始抓包;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接入规则、a73e6c1proto 契约、a151bfd实现、47f9dfd接口登记、501411c下游配合清单、c529bb0E2E 环境模板、16f6910transaction-id 预热下沉),以及四轮真机验收(6d56165关注链、af5a1b0五条行为链与配额实测、dfc7de3裸 RPC 与 cursor 翻页、f829bb57 个裸单端点收口) - 变更:① 新增 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/xnetfork 补入proxy子包 - 兼容性:wire 层无破坏性变更,编译期有一处破坏性变更。★
ProxyConfig迁移后 Goandroidpb.ProxyConfig→commonpb.ProxyConfig、Pythonandroid_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 分钟):followUserMutation50、update_profile15、update_profile_banner30、UserByScreenName150、settings.json180、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移除服务端内部登录重试、c4f1df3NOT_SENT非确定性失败允许有界重连、a3ed89f内置默认重试改为 2 次、255ed90TLS 层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_pb2stub 一并更新;调用方无需改代码即可读到新值 - 已知限制:
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 与本地配置错误不换机。③ ProtoLoginRequest新增 optionalmax_attempts(字段号 8,取值 1..3)并同步 Go/Python stub、Pythonlogin(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%,RandomSeedModels100% - 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 状态码取值集合未扩大。PythonFailureInfo新增只读属性并导出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(JFX-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 无改动;
AndroidDeviceJSON 新增字段(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/-modelflag 与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 对齐真机十进制格式——jadxcom/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 内部指纹对齐。AndroidDeviceJSON 无新字段。调用方无 API 变更。行为变更仅限发出的 wire:新登录产生的 castle field 31 与既有会话的 OAuthoauth_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 处补丁:SETTINGS4: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 内部与工具变更);AndroidDeviceJSON 新增字段(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、媒体上传端点与 GraphQLerrors[]用的是同一套数字错误码,但这张表原先只写在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/WriteStatusRPC(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 的处置是账号级且不可逆,需确认处置逻辑就绪。③ 服务端内部超时不再表现为 gRPCDEADLINE_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;attempts2/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 读写),以及是否有按 gRPCDEADLINE_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逐字段对齐 jadxPollCardDataJsonAdapter.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,错误契约与 Pythontwitter_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.6state);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_controlPascalCase 与国家码、三入口字段一致性、未传时保持 wire null、gqlErrSummarymessage 优先。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。已逐一排除:字段名与形状(legacyJsonEditOptionsInput与 Apollo adaptera2双向核实均为{previous_tweet_id: String})、编辑资格(实测is_edit_eligible=true、edits_remaining=5、窗口未过)、时序(0/20/60s 均被拒)、features(带与不带相同)。最可能的剩余解释是 12.13 真机编辑并不走 ApolloCreatePost,需真机抓一次编辑请求才能定论;该字段不得作为可用能力使用。定时发帖与长文 features 仍缺,两者都需要能看到对应入口的 Premium 账号。semantic_core_ids(含社区帖)代码就绪但未真机验收(需先加入社区)。图上 @ 人(tagged_users)形状已知但未接 proto(需把repeated string media_ids扩成 media 实体)。另记录一处与真机的画像差异:真机CreatePostbody 无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、发的是同一个CreatePostoperation,字段层面没有任何理由不一致。改为共享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,Pythonposts.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(广播给粉丝),proto3optional,不传时 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_LIMITEDat 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/httpsCONNECT 把目标 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 metadatax-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 发布时[email protected]依赖的[email protected]在 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未钉版本,安装[email protected]时其依赖[email protected]在 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 不可删除后复用,修复走递增新版本