jpoindexter/x-native

GitHub: jpoindexter/x-native

原生 TypeScript 实现的 X/Twitter GraphQL 客户端,通过 cookie 认证实现搜索、书签读取等功能,并具备自动刷新 query ID 的自愈能力。

Stars: 0 | Forks: 0

# x-native [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/e606e0922f100446.svg)](https://github.com/jpoindexter/x-native/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D18-green.svg)](https://nodejs.org) [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](https://www.typescriptlang.org/) [![dependencies](https://img.shields.io/badge/runtime%20deps-0-brightgreen.svg)](package.json) **原生 TypeScript X/Twitter GraphQL 客户端** —— 搜索、书签、读取推文。无密钥 **cookie 认证**,**零依赖**,无需 Python,并支持**自愈 query ID**。 X 没有像 Reddit 的 `.json` 那样的开放 API —— 它的 Web 应用与一个锁定的内部 GraphQL endpoint 通信,使用 bearer token、CSRF token 以及**每隔几周轮换一次的 query ID**。大多数工具将这种变动交由维护的 Python CLI 处理。而 `x-native` 则完全使用 TypeScript 实现:它通过你的浏览器 cookie 进行身份验证,并在 query ID 发生变化时,**从 X 自己的 Web bundle 中重新抓取当前的 query ID**。 ## 安装 ``` git clone && cd x-native npm install && npm run build # or: npx tsx src/cli.ts ``` ## 认证(你的 cookie) 使用 **Cookie-Editor** 浏览器扩展导出你的 `x.com` 会话(Export → JSON)—— 它必须包含 `auth_token` 和 `ct0`。然后: ``` export X_COOKIE='[{"name":"auth_token","value":"…"},{"name":"ct0","value":"…"}, …]' # the JSON export # 或者一个普通 header: export X_COOKIE='auth_token=…; ct0=…' # 或者指向一个文件: x-native search "…" --cookie-file ./x-cookies.json ``` cookie 保留在本地。`ct0` 同时用作 CSRF token。 ## 使用 ``` # 1) 获取 X 的当前 GraphQL query ID(传入你的 cookie 以便它能访问诸如 Bookmarks 之类的已登录路由) x-native heal # 2) go x-native search "manual invoicing" --latest --max 30 x-native bookmarks --max 50 --json ``` ### 库 ``` import { searchTimeline, getBookmarks, refreshQueryIds, toCookieHeader } from "x-native"; const cookie = toCookieHeader(process.env.X_COOKIE!)!; await refreshQueryIds({ cookie }); // populate query IDs const r = await getBookmarks({ cookie, max: 50 }); if (r.ok) for (const t of r.tweets) console.log(`@${t.handle} ♥${t.likes}: ${t.text}`); ``` 每次调用都会返回 `{ ok: true, tweets } | { ok: false, error }` —— 错误作为值返回,绝不跨越边界抛出异常。 ## 自愈的工作原理 `refreshQueryIds` 会获取 `x.com`,然后**深入两个层级**抓取其 client-web JS bundle(一个 bundle 可以引用另一个 bundle),将每个 `operationName → queryId` 对提取到一个小缓存中(`~/.x-native/qids.json`)。当 X 轮换某个 ID 时,重新运行 `x-native heal`。 **客观存在的边界。** `heal` 会从顶层 bundle(例如 `SearchTimeline`)中抓取约 150 个 `operationName → queryId` 对。但是 X 会向不同的会话/IP 提供不同的 bundle 构建,因此*抓取到的* ID 可能比实际运行的 API 落后一个构建版本,并返回 **`HTTP 404`**。而那些延迟加载的操作(如 `Bookmarks`)根本不在这些 bundle 中 —— 它们位于按路由划分的 chunk 中,背后是 X 内联的大约 940 个*不透明数字* chunk 的 manifest,其 URL 构造经过了混淆,要可靠地获取正确的 chunk 需要运行 X 的 webpack runtime(无头浏览器,而本库刻意避免了这一点)。 因此,为你调用的任何操作获取可用 ID 的**可靠**方法是**从 DevTools 中将其固定(pin)** —— 随后它将持续有效数周,直到 X 轮换它(你会知道的:`404`): `heal` 仍然是一个有用的初步尝试,也是刷新从 bundle 中抓取的 ID 的一种方式;只需将 `404` 视为“从 DevTools 中固定此项”。 ## 配置(逃生舱) | 环境变量 | 用途 | |-----|---------| | `X_COOKIE` | cookie header 或 Cookie-Editor JSON 导出文件 | | `X_NATIVE_HOME` | 缓存目录(默认为 `~/.x-native`) | | `X_NATIVE_QID_` | 固定 query ID(例如 `X_NATIVE_QID_BOOKMARKS`) | | `X_NATIVE_BEARER` | 覆盖 Web bearer token | ## API `searchTimeline({cookie, query, max?, latest?})` · `getBookmarks({cookie, max?})` · `refreshQueryIds({cookie?, cacheDir?})` · 纯辅助函数 `extractAuth` · `parseTimeline` · `extractQueryIds` · `bundleUrls` · `graphqlError` · `cookieFromEditorJson` · `toCookieHeader`。 ## 许可证 MIT
标签:API客户端, GraphQL, MITM代理, TypeScript, X/Twitter, 安全插件, 爬虫工具, 自动化攻击, 零依赖