jpoindexter/x-native
GitHub: jpoindexter/x-native
原生 TypeScript 实现的 X/Twitter GraphQL 客户端,通过 cookie 认证实现搜索、书签读取等功能,并具备自动刷新 query ID 的自愈能力。
Stars: 0 | Forks: 0
# x-native
[](https://github.com/jpoindexter/x-native/actions/workflows/ci.yml)
[](LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org/)
[](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, 安全插件, 爬虫工具, 自动化攻击, 零依赖