资讯动态

OpenCLI 的 Twitter/X Sitemap 端点清单(apis.md)解析:GraphQL 契约分层与 internal-unstable 实战应对

发布时间:2026/9/20 18:12:10 来源:尧图企业网站定制
OpenCLI 的 Twitter/X Sitemap 端点清单apis.md解析GraphQL 契约分层与 internal-unstable 实战应对【免费下载链接】OpenCLIMake Any Website into CLI Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI导读本文围绕 OpenCLI 仓库中 sitemaps/twitter/apis.md 这份端点索引文档展开讲清楚三个核心问题Twitter/Xx.com没有任何公开官方 API 可依赖时sitemap 如何用endpoint_idcontract_strength描述 GraphQL 端点为什么当前 endpoints.json 只有 2 条、而 adapter 族实际引用 34 个 GraphQL operation这一 gap 是如何被 schema §2.4 严格规则暴露出来的以及 task-executing agent 在全端点 internal-unstable的现实下应如何选择 adapter 优先、DOM Fallback 兜底的路径决策。读完你既能理解 OpenCLI sitemap 规范里apis.md文件的设计动机与引用规则也能掌握 twitter adapter 底层 queryId 滚动机制与契约强度分层的判断方法。一、apis.md 在 sitemap 体系中的定位OpenCLI 把把任意网站变成 CLI的能力拆成两层独立演进的描述文件参见 sitemap-schema.md 的 §2.4navigation layersitemap描述页面pages/*.md、工作流workflows/*.md、端点索引apis.md、站点坑pitfalls.mdAPI layerendpoints.json集中存放端点的 URL / method / params / response shape是全站端点的单一来源single source of truth。apis.md 的文件头注释直接给出了引用规则schema §2.4endpoint_id必须存在于~/.opencli/sites/twitter/endpoints.json。本文件只放 endpoint_id 触发关系 contract_strengthURL/method/params/response 是 endpoints.json 单一来源。这意味着 apis.md不允许复制 endpoint 的 URL、method、参数和响应结构——这些细节重复写在两个文件里只会造成双 stale一处更新、一处遗忘这正是 sitemap 与 endpoints.json 分离设计的核心理由。apis.md 里每条 endpoint 只记录四种信息字段含义示例endpoint_id与 endpoints.json 同 id 的唯一标识UserByScreenNametriggers_on_pages该端点被哪些 page 触发[profile, status]triggered_by_actions该端点被哪些 action 触发[resolve_handle_to_userid]contract_strength契约强度分级internal-unstablenotes可选meta 信息如 GraphQL queryId 路径、已知 schema 变化/i/api/graphql/{queryId}/UserByScreenName对应的 sitemap schema 约束还包括Linked APIs引用的endpoint_id必须存在于 apis.md间接也在 endpoints.json如果某站的 endpoints.json 还在收集中page 的Linked APIs留空是合理的不要塞 fake id 占位见 sitemap-schema.md。二、endpoint 清单两个已登记的 GraphQL 端点当前 apis.md 只登记了 2 条端点它们是所有 user-keyed adapter 的第一步endpoint:UserByScreenNametriggers_on_pages: [profile, status]triggered_by_actions: [resolve_handle_to_userid]contract_strength: internal-unstablenotes: GraphQL /i/api/graphql/{queryId}/UserByScreenNamequeryId 滚动见 pitfalls。所有 user-keyed adaptertweets/likes/following/followers的第一步它完成handle → 数字 userId的解析。在源码 clis/twitter/user-timeline.js 中可以看到这条端点的真实调用链resolveUserTimelineContext会先通过 shared.js 的buildUserByScreenNameUrl构造请求再从data?.data?.user?.result?.rest_id取出 userId。请求 URL 形态为/i/api/graphql/{queryId}/UserByScreenName ?variables{screen_name:jack,withSafetyModeUserFields:true} features{...USER_BY_SCREEN_NAME_FEATURES}同时请求头携带Authorization: Bearer token、X-Csrf-Token: ct0、X-Twitter-Auth-Type: OAuth2Session、X-Twitter-Active-User: yes——其中ct0来自登录 cookie缺失时直接抛AuthRequiredError见 user-timeline.js。endpoint:UserTweetstriggers_on_pages: [profile]triggered_by_actions: [load_user_timeline, scroll_for_more]contract_strength: internal-unstablenotes: data.user.result.timeline.timeline.instructions[].entries 结构TimelinePinEntry 必须 skip 才是 chronological feed它是 profile Posts tab 的数据源。源码中buildUserTweetsUrl构造的 variables 包括userId、count、includePromotedContent: false、withQuickPromoteEligibilityTweetFields: true、withVoice: true可选cursor用于分页fetchUserTimelinePage通过page.evaluate内的fetch携带 same-origin session 发起请求见 user-timeline.js 与 L211-L217。notes里提到的TimelinePinEntry对应 profile.md 页面坑中的 pinned tweet 永远在最顶不按时间排——pinned 推文由TimelinePinEntry指令承载adapter 内部必须 skip 它才能得到真正按时间排序的 chronological feedtask agent 无需自行区分这是 adapter-author 实现层的事。三、v1 gap34 GraphQL operation 与 2 条登记的巨大落差apis.md 用一节 v1 gapintentional 明确承认了当前的不完整状态twitter 的 adapter family (clis/twitter/*.js) 引用了34 个 GraphQL operationHomeTimeline / HomeLatestTimeline / SearchTimeline / CreateTweet / DeleteTweet / FavoriteTweet / UnfavoriteTweet / CreateRetweet / DeleteRetweet / CreateBookmark / Bookmarks / UserFollowing / Followers / NotificationsTimeline / TweetDetail / 等但endpoints.json当前只有2 条。这个落差是 schema §2.4 endpoint_id 必须存在于 endpoints.json 严格规则 surface 出来的真实 gap文档明确排除了两种误判不是 sitemap 缺sitemap 不应该重复 endpoint detail是 endpoints.json 缺adapter-author 应该回填这些 operation 到 endpoints.json。行动建议不在本 PoC scope 内跑opencli browser network抓 twitter session 一次拿到当前 queryId map全量回写 endpoints.jsonstep 3 数据显示 twitter COOKIE_API adapter9 fixes/30 天→ queryId rotation 是主因集中维护 endpoints.json 比 34 个 adapter 各自硬编码 fallback 价值更高。待 endpoints.json 补齐后本文件 endpoint 列表会扩到完整。这一设计体现的工程哲学是宁可承认清单不完整也不允许在 sitemap 里复制 endpoint detail 造成双源漂移。四、contract_strength 分布twitter 全站 internal-unstableapis.md 给出了基于 step 3 数据的契约强度分布预期表强度endpoint 类型twitter 实际stable公开签名 API无无官方 v2 API 可用visible-uiRSS / public profile HTML/handle/status/id公开 HTML无登录可见 metadata但 PoC v1 不依赖internal-unstableGraphQL/i/api/graphql/...全部 34 operation 都在此档三个强度档位的定义在 strategy-selection.md 中给出stable公开签名 APIvisible-ui用户可见的 DOM、a11y / semantic 标记对应 UI_SELECTOR、DOM_STATE 两种 adapter strategyinternal-unstable站内未文档化 endpoint、签名 / queryId 漂移、字段 silent rename对应 PAGE_FETCH、INTERCEPT。twitter 的结论性事实是全 endpoint 都是 internal-unstable。这是因为 Twitter 官方 v2 API 需要付费、且对 agent 场景不开放网页版只暴露未文档化的 GraphQL 内部接口queryId 随前端 bundle hash 滚动字段也可能 silent rename。从 strategy-selection.md 的维护成本数据看这个选择是有代价的PAGE_FETCH/INTERCEPTinternal-unstable的 adapter 平均 fix 频率约为PUBLIC_APIstable的7-8 倍。这正是文档把 queryId rotation 列为 twitter 最大维护痛点的底层依据。五、路径决策信号adapter 优先DOM Fallback 兜底apis.md 的收尾给出了一个明确的 agent 路径决策信号twitter 实战上全 endpoint 都是 internal-unstable——这本身是 agent 路径决策的信号能用 adapter 走 adapteradapter broken 时 fallback 用 DOMworkflows/*.md的 Fallback path而不是手解 GraphQL。5.1 为什么不让 task agent 手解 GraphQLtask-executing agent 的任务是完成用户意图读 timeline、发推、回复、搜索、bookmark不是维护 Twitter 内部契约。手解 GraphQL 意味着要自己处理 queryId 滚动、features/fieldToggles 参数、envelope 解包、bigint id 精度等一系列易腐细节。这些维护成本应该由 adapter-author 层集中承担维护 endpoints.json / adapter 内的 fallback queryIdtask agent 只负责按健康状态选择路径。5.2 adapter 的健康状态是路由依据workflows/post.md 展示了这套决策的完整形态Best path 是adapter: opencli twitter postadapter_health: healthy预估 1 turn当 adapter 抛 typed errorEmptyResultError | CommandExecutionError或返回 unexpected empty 时执行 Fallback path预估 4 turnson_adapter_fail: - adapter_health_update: opencli twitter post - suspect - opencli browser state (verify current page) - if not on /home: goto /home - action:open_compose in pages/home.md - type content into [data-testidtweetTextarea_0] - action:submit_post in pages/home.md - verify timeline top shows new post within 5s对应地pitfalls.md 的pitfall:adapter_returns_empty_after_api_drift明确指出adapter 抛queryId expired类内部错或返回data:{}空时把 workflow 的adapter_health标为suspect走 Fallback pathbrowser DOM。实现层根因是 GraphQL queryId rotation30 天 9 fixes但task agent 不需要知道这一层只需切 Fallback。5.3 DOM 兜底的稳定性锚点Fallback 依赖的 DOM 锚点也是跨 locale 稳定、有语义的a11y 锚点rolemainroleregion nameTimeline: handleprofile、Conversation或handles poststatus、Timeline: Your Home Timeline | Timeline: For Youhometestid 锚点[data-testidUserName]、[data-testid*-follow]、[data-testidtweetTextarea_0]、[data-testidtweetButtonInline]、[data-testidcellInnerDiv]、[data-testidprimaryColumn]。注意两种 composer 形态的差异home.md 指出 inline composer 用tweetButtonInlinemodal 形态用tweetButtonstatus.md 则提醒 detail page 主 tweet 的 testid 是tweet而 listing page 用cellInnerDiv用 a11yrolearticle更通用。这些细节正是 SITE.md 里 site-wide pitfalls 的落地支撑。六、结合源码看 queryId 滚动为什么 internal-unstable 这么难维护apis.md 反复强调 queryId 滚动是 twitter 维护成本主因这里结合 clis/twitter/shared.js 的源码把机制讲透便于理解 internal-unstable 档位的真实复杂度。6.1 queryId 的两种解析来源resolveTwitterOperationMetadatashared.js按顺序尝试两种方式GitHub placeholder.json 优先更可靠在页面上下文fetch一个跟踪 twitter queryId 滚动的公开配置文件命中data[operationName].queryId直接返回bundle 扫描兜底offline fallback遍历页面脚本head 15 tail 15个候选对每个 bundle 文本执行parseOperationFromBundleText提取queryId与operationName的配对。6.2 bundle 解析为什么容易出错parseOperationFromBundleTextshared.js的注释解释了旧实现的坑minified bundle 中e.exports密集分布按 marker 切 snippet 经常横跨多个 operation module导致取到的 queryId 属于另一个 operationTwitter 侧会以 queryId expired 拒绝。修复方式是用两个锚定正则要求 queryId 与 operationName 相距 ≤400 个非}字符queryId:([A-Za-z0-9_-])[^}]{0,400}operationName:name operationName:name[^}]{0,400}queryId:([A-Za-z0-9_-])6.3 features 缺失会导致 400 而非 queryId expiredsanitizeTwitterOperationMetadatashared.js处理另一种隐蔽情况bundle parser 可能找到 queryId 却漏掉featureSwitches:[...]minification 变化或 2500 字符截断窗口导致features为空 map。此时如果直接发给 Twitter会收到 400 并误报为 queryId expired。因此代码规定resolved 的 features 为空时必须回退到 baked fallback 的 features。6.4 HTTP 错误的精确归因describeTwitterApiErrorshared.js把非 200 响应翻译成可行动的失败模式HTTP 状态含义建议动作429会话配额被限流cooldown 后重试典型 15-30 分钟401cookie 过期/失效重新登录403cookie 缺 scope 或资源私有检查权限404资源不存在删除/封禁/私有停止重试5xxTwitter 服务端错误稍后重试其他可能 queryId expired / schema 变化 / 瞬时错误走 Fallback这套归因正是 pitfalls.md 中adapter_returns_empty_after_api_drift的实现层支撑让调用方能区分该等、该重登、该放弃还是该切 DOM Fallback而不是把所有失败一律当成 queryId expired。七、给 Agent 与读者的实操要点结合 apis.md 与仓库现状整理成可直接复用的决策清单优先 adapter所有 twitter 高频操作读 timeline / 发推 / 回复 / 搜索 / bookmark / 关注先走opencli twitter commandadapter 内部处理 COOKIE 注入、queryId 滚动、envelope 解包与 verifyadapter 失败就切 Fallback不要反复 retryworkflows/post.md 的 Avoid 清单明确说 adapter 失败时不要反复 retry adapter——直接走 Fallback否则浪费 turn识别 internal-unstable 信号返回空data:{}、抛queryId expired/EmptyResultError、30 天内该 adapter fix 频繁都指向端点契约漂移把adapter_health标suspect后走 DOMDOM 兜底优先用 a11y 与 testidrolearticle/data-testid跨 locale 稳定避免text: Post这类 visible-text selector 在非英文 locale 下失效见 pitfalls.md 的button_not_found_in_non_english_locale维护者视角如果要在本仓库补全 twitter 端点正确动作是opencli browser network抓一次 session 拿到当前 queryId map 后全量回写~/.opencli/sites/twitter/endpoints.json再回来扩充 apis.md 的 endpoint 列表——而不是在 sitemap 里复制 endpoint detail。结语sitemaps/twitter/apis.md 虽然只有 2 条已登记端点却是理解 OpenCLI sitemap 规范在无官方 API 网站上如何运作的最佳切片它以 §2.4 的严格引用规则守住 navigation layer 与 API layer 的分层边界用contract_strength把twitter 全站 internal-unstable这一现实显式化并把它转化为 agent 的路径决策信号——adapter 优先、DOM Fallback 兜底、queryId 维护集中到 endpoints.json。对于任何需要在无契约内部接口上构建 agent 工作流的场景这份文件的取舍方式都值得直接借鉴。【免费下载链接】OpenCLIMake Any Website into CLI Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价