959 lines
59 KiB
Markdown
959 lines
59 KiB
Markdown
# AGENTS.md — catbus(猫巴士)
|
||
|
||
> 本文件是本项目给所有 AI 编码代理(Claude Code / Codex / Cursor 等)的统一说明,也是 catbus 的规范本身。
|
||
> `CLAUDE.md` 通过 `@AGENTS.md` 引用本文件,**只维护这一份**。
|
||
|
||
## 1. 目标
|
||
|
||
做**一个 CLI —— 操作各大平台接口的统一入口**。
|
||
|
||
- 一个命令 `catbus`,按统一的命令规范调用 10 个平台:小红书、抖音、TikTok、B 站、快手、微博、闲鱼、淘宝、京东、X。
|
||
- 能力来源是作者 [cv-cat](https://github.com/cv-cat) 的 10 个开源仓库,已克隆到 `references/`,作为**只读的原始来源**。
|
||
- 所有平台都在本仓库里**用 TypeScript 重写**,不在运行时调用上游的 Python。
|
||
- **用户只需要安装 Node**(版本见 7.1)。不需要 Python,不需要 C/C++ 编译器,也不需要其他系统依赖。
|
||
- `npm i -g catbus-cli` 装完就能离线使用。
|
||
- 必须支持全部目标系统(见 7.2)。
|
||
- 以 **npm 包**分发,不发 PyPI。安装体积不是约束,优先保证开箱即用。
|
||
- 上游目前只有 **web 端**接口。规范为 **app 端**、**pc 端**预留位置,例如 `catbus xianyu item get <id> -e app`。
|
||
- 登录态统一保存在 `~/.catbus/`,**按 平台 × 端 × 账号** 隔离。web 端不登录几乎看不到内容,所以都需要登录;游客态留给 app / pc 端。
|
||
- 输出对人和对 Agent 都友好:stdout 只输出 JSON,字段跨平台统一。
|
||
- **同义同名**:含义相同的能力在所有平台上用同一个命令、同一组参数、同一种输出结构。例如商品一律叫 `item`,搜索一律叫 `search`。
|
||
- 将来的非 JS 上游(Go / Java……)通过 provider 协议接入(见 7.8)。
|
||
|
||
### 1.1 命名(已定)
|
||
|
||
| 项 | 取值 |
|
||
|---|---|
|
||
| 品牌 | **catbus(猫巴士)**:一只能载你去任何地方的猫,呼应 cv-cat |
|
||
| slogan | 上车,开往任何平台 / all aboard, every platform |
|
||
| 命令 | `catbus` |
|
||
| npm 主包 | `catbus-cli`(npm 上的 `catbus` 已被别的库占用) |
|
||
| npm scope | `@cv-cat`,用于 `@cv-cat/catbus-assets-jd`、`@cv-cat/catbus-assets-ocr` 和将来的 provider 包 |
|
||
| 仓库 | https://github.com/cv-cat/catbus ,默认分支 `master` |
|
||
| 许可证 | MIT(主包、assets-jd、assets-ocr 相同) |
|
||
| 数据目录 | `~/.catbus/`,可用 `CATBUS_HOME` 覆盖 |
|
||
| 环境变量前缀 | `CATBUS_` |
|
||
| Logo | `assets/logo.svg`(图标)、`assets/logo-wordmark.svg`(横版)、`assets/banner.txt`(终端字符画)。猫巴士:车身就是一只橘猫,车头是猫脸和耳朵,车尾是尾巴,轮毂是肉垫;车顶路牌是终端提示符 `>_`;车窗里坐着小红书红、B 站粉、抖音青三只小猫乘客。会颠簸、转轮子、摇尾巴、眨眼、抖耳朵;字符画沿用 `( >ω_ )`。配色取自 Catppuccin Mocha |
|
||
|
||
## 2. 范围
|
||
|
||
- 目前支持第 3 节的 10 个平台的 web 端;app / pc 端在注册表里为 planned(见 4.3)。
|
||
- 矩阵里的 ○(平台有这个概念、上游没有的能力)只占位:注册为 planned,执行时返回 NOT_IMPLEMENTED,有需要时再实现。移植中发现矩阵与上游不符(上游其实没有、或只是占位)的能力,按上游实际情况标为 ○ 或 ◐,以 `docs/capabilities.md` 为准。
|
||
- 新平台开始移植时,先把平台 id、别名、`item` 叫法等登记到第 3 节、4.2 和 4.5,再进注册表;在那之前 `catbus <平台>` 是未知平台(`USAGE`)。
|
||
- 改规范先改本文件,再写代码。
|
||
|
||
## 3. 参考仓库 `references/`
|
||
|
||
| 平台 id | 仓库 | 主要入口 |
|
||
|---|---|---|
|
||
| xhs | Spider_XHS | `apis/xhs_pc_apis.py`、`apis/xhs_creator_apis.py`、`xhs_utils/` |
|
||
| douyin | DouYin_Spider | `dy_apis/douyin_api.py`、`dy_apis/douyin_creator_api.py`、`dy_apis/login_api.py` |
|
||
| tiktok | TiktokApis | `api/tiktok.py`、`api/tiktok_web.py`、`signing/` |
|
||
| bilibili | BilibiliApis | `apis/bili_apis.py`、`apis/bili_*_apis.py`、`builder/auth.py` |
|
||
| kuaishou | KuaiShou-Spider | `ks_apis/kuaishou_api.py`、`ks_apis/publish_api.py`、`ks_apis/login_api.py` |
|
||
| weibo | WeiboApis | `apis/weibo_apis.py`、`apis/weibo_mobile_apis.py`、`apis/weibo_creator_apis.py` |
|
||
| xianyu | XianYuApis | `goofish_apis.py`、`goofish_live.py` |
|
||
| taobao | TaoBaoApis | `taobao_apis.py`、`taobao_live.py`、`utils/taobao_utils.py` |
|
||
| jd | JdApis | `jd_apis/jd_api.py`、`jd_apis/jd_login_api.py`、`builder/auth.py` |
|
||
| x | XApis | `x_apis/x_api.py`、`x_apis/x_write_api.py`、`x_apis/login_api.py` |
|
||
|
||
参考资料:
|
||
- 每个仓库的鉴权、登录、API 方法、JS 资产、游客态生成位置,以及 Python → JS 的替换对照,见 [docs/upstream-map.md](docs/upstream-map.md)。
|
||
- 各平台的能力矩阵见 [docs/capabilities.md](docs/capabilities.md)。
|
||
|
||
规则:
|
||
- `references/` 只读。
|
||
- 可以阅读,也可以运行(用来生成对拍数据,见 7.5)。
|
||
- 可以在上游 gitignore 的位置装依赖,例如 `node_modules/`。
|
||
- **不能修改其中任何受版本控制的文件**。
|
||
- `references/` 不进入本仓库的版本控制,它们各自是独立的 git 仓库。
|
||
- 每个平台的移植基于哪个上游 commit,记录在 `src/platforms/<p>/UPSTREAM`。
|
||
|
||
## 4. 命令规范
|
||
|
||
### 4.1 语法
|
||
|
||
```
|
||
catbus <platform> <resource> <action> [参数...] [选项...]
|
||
```
|
||
|
||
- 选项可以出现在任意位置。`--` 之后的内容一律按参数处理,用于以 `-` 开头的文本。
|
||
- 端用选项 `-e/--endpoint` 指定,默认 `web`(见 4.3)。
|
||
|
||
示例:
|
||
|
||
```bash
|
||
catbus xhs item get "https://www.xiaohongshu.com/explore/<id>?xsec_token=..."
|
||
catbus xhs user items <user> --all -o jsonl
|
||
catbus bilibili item get BV1xx411c7mD
|
||
catbus douyin comment list <item> --limit 100
|
||
catbus x user get elonmusk
|
||
catbus jd item search 机械键盘 --sort sales
|
||
catbus taobao msg send "在吗" --item <商品 url>
|
||
catbus bilibili live listen <room> --duration 10m
|
||
catbus xhs auth login
|
||
catbus kuaishou auth status -a work
|
||
catbus xianyu item get <id> -e app # app 端尚未实现:NOT_IMPLEMENTED,退出码 4
|
||
```
|
||
|
||
### 4.2 平台 id 与别名
|
||
|
||
| 规范 id | 别名 |
|
||
|---|---|
|
||
| `xhs` | `xiaohongshu`, `rednote` |
|
||
| `douyin` | `dy` |
|
||
| `tiktok` | `tt` |
|
||
| `bilibili` | `bili`, `b` |
|
||
| `kuaishou` | `ks` |
|
||
| `weibo` | `wb` |
|
||
| `xianyu` | `goofish`, `xy` |
|
||
| `taobao` | `tb` |
|
||
| `jd` | `jingdong` |
|
||
| `x` | `twitter` |
|
||
|
||
目录名、配置键、输出里的 `platform` 字段一律使用**规范 id**。只有平台有别名;resource 和 action 没有别名。
|
||
|
||
### 4.3 端(endpoint)
|
||
|
||
- `-e/--endpoint web|app|pc`,默认 `web`,没有配置项可以改默认值。
|
||
- 每个平台在注册表里声明各端的状态:目前 web 为 available,app / pc 为 planned。
|
||
|
||
命令无法执行时,按下面的顺序判断:
|
||
|
||
| 情况 | code | 退出码 |
|
||
|---|---|---|
|
||
| 端为 planned,命令在词表内(第 4.5、4.7 节) | `NOT_IMPLEMENTED` | 4 |
|
||
| 端可用,命令是矩阵里的 ○(规划中) | `NOT_IMPLEMENTED` | 4 |
|
||
| 命令是矩阵里的 —(平台没有这个概念),或者不在词表内 | `UNSUPPORTED` | 2 |
|
||
|
||
- `UNSUPPORTED` 时,如果别的端有这个命令,`hint` 里提示加 `-e <端>`。
|
||
- 用了平台原生叫法(如 `note`、`video`),`hint` 里提示对应的规范词(见 4.6)。
|
||
- 参数和选项的校验(`USAGE`,以及筛选取值的 `UNSUPPORTED`)先于 ○ 的判断:写错的命令先报用法错误。端为 planned 时不做校验。
|
||
|
||
### 4.4 动作语义
|
||
|
||
| 动作 | 语义 |
|
||
|---|---|
|
||
| `get` | 取单个对象 |
|
||
| `list` | 取一个集合,不需要关键词。默认是当前账号自己的,或者推荐流 |
|
||
| `search` | 按关键词搜索 |
|
||
| 成对动作 | `like/unlike`、`collect/uncollect`、`repost/unrepost`、`follow/unfollow` |
|
||
| 写入 | `add`、`send`、`publish`、`create`、`update`、`delete`、`upload` |
|
||
| 长连接 | `listen`:持续输出 jsonl,直到 Ctrl-C 或 `--duration` 到期 |
|
||
|
||
### 4.5 通用词表
|
||
|
||
| resource | 含义 | action |
|
||
|---|---|---|
|
||
| `auth` | 登录态 | `login` `status` `logout` `list` `use` |
|
||
| `user` | 用户 | `get` `search` `items` `likes` `collects` `reposts` `followers` `following` `follow` `unfollow` |
|
||
| `item` | 平台的**主内容单元**(见下表) | `get` `search` `related` `list` `media` `download` `like` `unlike` `collect` `uncollect` `repost` `unrepost` `publish` `delete` `categories` |
|
||
| `product` | 内容平台里挂的商品(抖音 / TikTok 商城等) | `get` |
|
||
| `comment` | 评论,也包括商品评价 | `list` `replies` `add` `delete` `like` `unlike` |
|
||
| `feed` | 推荐 / 热门 / 关注流 | `list` `categories` |
|
||
| `live` | 直播 | `get` `list` `search` `categories` `listen` `history` `send` `like` `rank` `gifts` `products` `media` `replays` `start` `stop` |
|
||
| `keyword` | 搜索词 | `suggest` `hot` |
|
||
| `notice` | 通知(评论 / @ / 赞 / 关注 / 系统) | `list` `count` |
|
||
| `msg` | 私信 / IM / 客服 | `list` `history` `send` `listen` `read` `revoke` `delete` |
|
||
| `media` | 媒体上传 | `upload` |
|
||
| `folder` | 收藏夹 | `list` `items` `create` `update` `delete` |
|
||
| `series` | 合集 / 播放列表 | `list` `items` |
|
||
| `history` | 浏览历史 | `list` |
|
||
| `topic` | 话题 | `search` |
|
||
| `poi` | 地点 | `search` |
|
||
|
||
`item` 在各平台的含义:
|
||
|
||
| 平台 | item = |
|
||
|---|---|
|
||
| xhs | 笔记 |
|
||
| douyin | 作品 |
|
||
| tiktok | 视频 |
|
||
| bilibili | 稿件 |
|
||
| kuaishou | 作品 |
|
||
| weibo | 微博 |
|
||
| x | 推文 |
|
||
| xianyu | 闲置商品 |
|
||
| taobao | 商品 |
|
||
| jd | 商品 SKU |
|
||
|
||
各动作的说明:
|
||
- `item list`:当前账号自己发布的内容,带审核状态,数据来自创作者中心。
|
||
- `item related`:相关推荐。
|
||
- `item media`:可播放 / 可下载的媒体地址。
|
||
- `item download`:下载到本地。
|
||
- `item categories`:发布和搜索时 `--category` 的取值。
|
||
- `feed list --kind recommend|hot|following`:默认 `recommend`。
|
||
- `live listen`:弹幕 / 礼物 / 进场等事件流。
|
||
- `live history`:最近的弹幕。
|
||
- `live media`:直播流地址。
|
||
- `live replays <user>`:回放列表。
|
||
- `live start` / `stop`:开播 / 下播。
|
||
|
||
### 4.6 平台原生叫法 → 规范词
|
||
|
||
| 平台原生叫法 | 规范词 |
|
||
|---|---|
|
||
| note / aweme / video / photo / work / post / status / tweet / goods / sku | `item` |
|
||
| digg、favorite(X 的点赞) | `like` |
|
||
| favour、bookmark、想要、关注商品 | `collect` |
|
||
| retweet | `repost` |
|
||
| board、collection(TikTok 收藏夹) | `folder` |
|
||
| mix、playlist、collection(快手合集) | `series` |
|
||
| reply、评价 | `comment` |
|
||
| DM、IM、咚咚 | `msg` |
|
||
| playurl | `item media` |
|
||
| location | `poi` |
|
||
| watch | `listen` |
|
||
|
||
原生叫法**不做别名**。用了原生叫法会报 `UNSUPPORTED`,`hint` 里给出规范词。
|
||
|
||
### 4.7 平台扩展
|
||
|
||
平台特有、没有跨平台对应物的能力,才用扩展:可以在通用 resource 上加 action,也可以新增 resource。**新增扩展前先登记到本节,再写代码。**
|
||
|
||
| 平台 | 命令 | 说明 | 输出 |
|
||
|---|---|---|---|
|
||
| tiktok | `folder add <folder> <item>` | 把视频加入收藏夹(一次一个视频)。TikTok 只能把已收藏的视频加进收藏夹 | `{id}`(视频 id) |
|
||
| tiktok | `folder create <name> [--visibility public\|private]` | 收藏夹公开 / 私密:用 4.9 的标准选项 `--visibility`,只取 `public`、`private`;默认 `private`(与上游一致) | Folder |
|
||
| tiktok | `folder update <folder> [--name <name>] [--visibility public\|private]` | 同上。`--name`、`--visibility` 至少给一个,没给的保持原值 | Folder |
|
||
| tiktok | `item publish [--allow-comment on\|off]` | 私有选项:允许评论,默认 `on` | Item |
|
||
| tiktok | `item publish [--allow-duet on\|off]` | 私有选项:允许合拍(Duet),默认视频 `off`、图文 `on`(照上游) | Item |
|
||
| tiktok | `item publish [--allow-stitch on\|off]` | 私有选项:允许拼接(Stitch),默认视频 `off`、图文 `on`(照上游) | Item |
|
||
| tiktok | `item publish [--allow-content-reuse on\|off]` | 私有选项:允许他人复用内容,默认 `on` | Item |
|
||
| tiktok | `item publish [--allow-ai-remix on\|off]` | 私有选项:允许 AI 改编,默认 `on` | Item |
|
||
| bilibili | `item coin <item> [--count 1\|2] [--like]` | 投币;`--like` 同时点赞 | `{id}` |
|
||
| bilibili | `item triple <item>` | 一键三连 | `{id}` |
|
||
| bilibili | `item subtitles <item>` | 字幕 | Subtitle[] |
|
||
| bilibili | `danmaku list <item>` | 视频弹幕 | Danmaku[] |
|
||
| bilibili | `danmaku send <item> <text> --offset <秒> [--color] [--font-size] [--position]` | 发视频弹幕。`--color` 为 `#RRGGBB` 或十进制,`--font-size` 默认 25,`--position scroll\|top\|bottom` 默认 `scroll` | `{id}` |
|
||
| bilibili | `dynamic publish --text [--image]` | 发动态 | `{id url}` |
|
||
| bilibili | `dynamic delete <id>` | 删动态。`<id>` 为动态 ID 或动态链接(`dynamic publish` 输出的 `id`、`url` 都可以) | `{id}` |
|
||
| bilibili | `article publish --title --text [--category] [--tag] [--summary] [--draft]` | 发专栏:先存草稿,再提交。`--category` 为专栏分区 ID(数字,默认 0),没有对应的 categories 命令;`--summary` 为摘要;`--draft` 只存草稿、不提交。上游没有专栏封面,所以没有 `--cover` | `{id url}` |
|
||
| bilibili | `draft get <id>` | 专栏草稿 | Item(`kind` 为 `article`,`status` 为 `draft`) |
|
||
| bilibili | `draft delete <id>` | 删专栏草稿 | `{id}` |
|
||
| bilibili | `user items <user> [--keyword <词>]` | 私有选项:只看投稿里匹配关键词的 | Item[] |
|
||
| bilibili | `item collect` / `uncollect <item> [--folder <收藏夹 id>]` | 私有选项:收藏进 / 移出指定收藏夹,多个用逗号分隔。不给时收藏进第一个收藏夹,取消时从所有收藏夹移出 | `{id}` |
|
||
| bilibili | `item publish [--source <来源>] [--dynamic <文案>] [--allow-reprint]` | 私有选项:`--source` 表示转载并给出来源,不给为自制;`--dynamic` 为同步到动态的文案;默认禁止转载,`--allow-reprint` 允许 | Item |
|
||
| bilibili | `comment add <item> <text> --reply-to <comment> [--root <comment>]` | 私有选项:回复楼中楼时 `--root` 给根评论、`--reply-to` 给被回复的那条;不给 `--root` 时 `--reply-to` 就是根评论 | Comment |
|
||
| bilibili | `live send <room> <text> [--color] [--font-size] [--position] [--reply-user <user>]` | 私有选项:弹幕样式同 `danmaku send`;`--reply-user` 为回复的观众(UID 或空间链接) | `{id}` |
|
||
| xianyu | `item publish [--original-price <金额>]` | 私有选项:原价(元),要和 `--price` 一起用 | Item |
|
||
| xianyu | `item publish [--shipping free\|distance\|fixed\|none]` | 私有选项:运费方式,默认 `free` 包邮;`distance` 按距离计费,`fixed` 一口价,`none` 无需邮寄 | Item |
|
||
| xianyu | `item publish [--postage <金额>]` | 私有选项:一口价运费(元),配合 `--shipping fixed` | Item |
|
||
| xianyu | `item publish [--pickup]` | 私有选项:支持自提 | Item |
|
||
| douyin | `item search <kw> [--length all\|short\|medium\|long] [--range all\|seen\|unseen\|following]` | 私有选项:视频时长(不限 / 1 分钟内 / 1–5 分钟 / 5 分钟以上)、搜索范围(不限 / 看过 / 没看过 / 关注的人)。`--type video` 走视频频道搜索,其余走综合频道(筛选值随 `filter_selected` 发出);`--type image` 只要图文 | Item[] |
|
||
| douyin | `user search <kw> [--fans 0_1k\|1k_1w\|1w_10w\|10w_100w\|100w_] [--user-type common\|enterprise\|personal]` | 私有选项:粉丝数区间、用户类型(普通 / 企业 / 个人认证) | User[] |
|
||
| douyin | `item publish [--poi-name <名称>] [--series <合集 id>] [--hotspot <热点词>] [--no-download]` | 私有选项:地点名称(配合 `--poi`)、加入合集、关联热点、不允许下载 | Item |
|
||
| douyin | `item collect <item> --folder <folder>`、`item uncollect <item> --folder <folder>` | 私有选项:收藏后移进某个收藏夹 / 从某个收藏夹移出(仍保留收藏)。`<folder>` 是收藏夹 ID 或名字 | `{id}` |
|
||
| douyin | `msg send [--file <path>] [--share <item\|user\|url>]` | 私有选项:发文件(≤10MB)、分享作品 / 用户名片 / 网页卡片 | Message |
|
||
| douyin | `live rank <room> [--ranking contribution\|thousand]` | 私有选项:榜单,默认贡献榜,`thousand` 为千票榜 | Rank[] |
|
||
| douyin | `live like <room> [--count N]` | 私有选项:一次点赞的次数 | `{id}` |
|
||
| douyin | `comment list <product> --product [--label <标签名或 id>]` | 私有选项:商品评价按标签筛选(好评 / 差评 / 有图等) | Comment[] |
|
||
| douyin | `notice list [--group all\|fans\|mention\|comment\|like\|danmaku]` | 私有选项:通知分组,不带时为默认分组 | Notice[] |
|
||
| xhs | `item publish [--poi-name <名称>] [--poi <id>]` | 私有选项:地点名称。小红书的地点只能按名称搜,搜不到 id,所以 `--poi` 要和 `--poi-name` 一起用(取 id 相同的那个);只给 `--poi-name` 时取名称完全相同的。都找不到时报 `USAGE` 并列出相近的地点 | Item |
|
||
| weibo | `item publish [--poi-name <名称>]` | 私有选项:地点名称,发成「#名称[地点]#」的地点标签。微博发布没有地点 id,不支持 `--poi` | Item |
|
||
| douyin | `auth login --method sms --sso` | 私有选项:短信登录改走 `login.douyin.com` 页的 SSO 链 | Account |
|
||
| jd | `order list` | 订单 | Order[] |
|
||
| jd | `order list [--range 3m\|this_year\|<年份>]` | 私有选项:订单的时间范围。`3m` 近三个月(默认),`this_year` 今年内,`2025` 这样的四位年份表示那一年 | Order[] |
|
||
| jd | `cart count` | 购物车数量 | `{count}` |
|
||
| jd | `coupon list <item>` | 商品可用优惠券 | Coupon[] |
|
||
| jd | `item get` / `item search` / `item related` / `coupon list` / `cart count` / `history list` / `user collects` `[--area <地区编码>]` | 私有选项:收货地区,影响价格和库存。取值是京东的地区编码 `省_市_区_镇`,用 `_` 或 `-` 分隔,例如 `1_2800_55812_0`;默认取登录态里的 `ipLoc-djd` cookie,没有时为 `1_2800_55812_0` | Item / Item[] / Coupon[] / `{count}` |
|
||
| jd | `msg send <text> --order <订单> [--item <item> \| --conversation <venderId>]` | 私有选项:按订单咨询客服,会话和消息都带上订单号。`<订单>` 是订单号(`order list` 输出的 id),也接受订单详情页 URL。可以和 `--item`、`--conversation` 之一一起用;单独用时联系京东自营客服 | Message |
|
||
| x | `item publish [--thread <text> ...]` | 私有选项:发 thread。`--text` 是第一条,每个 `--thread` 是后面的一条,依次回复上一条;`--image` / `--video` / `--quote` 只作用于第一条。返回第一条 | Item |
|
||
| x | `article publish --text [--title] [--cover]` | 发文章(Premium 长文):`--text` 是 Markdown,依次建草稿、写标题、正文、封面,再发布。不给 `--title` 时取正文第一行的 `# 标题`。独占一行的 `` 上传成插图,相对路径按 `--text @file` 的文件所在目录解析(直接给正文时按当前目录) | `{id url}` |
|
||
| x | `article delete <article>` | 删文章:草稿或已发布的都可以,已发布的连同文章推文一起删 | `{id}` |
|
||
|
||
扩展类型的字段见 6.2。
|
||
|
||
### 4.8 参数
|
||
|
||
目标参数同时接受以下几种形式,由平台实现归一化:
|
||
- ID;
|
||
- URL,包括分享短链;
|
||
- 平台上的自然标识,例如 B 站的 BV 号、X 的用户名;
|
||
- `me`:只用于用户,表示当前账号。
|
||
|
||
所有输出对象都带 `id` 和 `url`,可以直接作为下一条命令的参数。xhs 的 `url` 带 `xsec_token`,因此 xhs 优先传 URL。
|
||
|
||
各 resource 的位置参数:
|
||
|
||
| resource | 形式 |
|
||
|---|---|
|
||
| user | `get` / `items` / `follow` / `unfollow <user>`;`likes` / `collects` / `reposts` / `followers` / `following [user]`,省略时为 `me`;`search <kw>` |
|
||
| item | `get` / `related` / `media` / `download` / `like` / `unlike` / `collect` / `uncollect` / `repost` / `unrepost` / `delete <item>`;`search <kw>`;`list`、`categories`、`publish` 不带位置参数 |
|
||
| product | `get <product>` |
|
||
| comment | `list <item>`;`replies` / `delete` / `like` / `unlike <item> <comment>`;`add <item> <text> [--reply-to <comment>]` |
|
||
| feed | `list`、`categories` |
|
||
| live | `get` / `listen` / `history` / `like` / `rank` / `gifts` / `products` / `media <room>`;`send <room> <text>`;`send <room> --gift <gift> [--count N]`;`replays <user>`;`list`、`categories`;`search <kw>`;`start` / `stop` |
|
||
| keyword | `suggest <prefix>`、`hot` |
|
||
| notice | `list`、`count` |
|
||
| msg | `list`;`history` / `read` / `delete <conversation>`;`revoke <conversation> <message>`;`send <text> --to <user> \| --conversation <id> \| --item <item>`,三者用一个,只有 `--to` 与 `--item` 可以同时用;可加 `--image` / `--video` |
|
||
| media | `upload <file>` |
|
||
| folder | `list [user]`,省略时为 `me`;`items <folder>`;`create <name>`;`update <folder> --name <name>`;`delete <folder>` |
|
||
| series | `list [user]`,省略时为 `me`;`items <series>` |
|
||
| history | `list` |
|
||
| topic / poi | `search <kw>` |
|
||
|
||
- 商品评价用 `comment list <product>`。
|
||
- 传商品 URL 时,自动识别为商品。
|
||
- 传纯 ID 时要加 `--product`,否则按 item 处理。
|
||
- `msg send --item <item>`:对商品卖家 / 客服发消息(闲鱼、淘宝、京东)。
|
||
- `msg send --to <user> --item <item>`:就这件商品给指定用户发消息(闲鱼:卖家主动联系买家)。不支持的平台报 `UNSUPPORTED`。
|
||
- taobao 没有按用户查询、按用户发起会话的接口:`user get <user>` 的 `<user>` 只接受商品链接,返回这件商品的卖家;`msg send` 不支持 `--to`(报 `UNSUPPORTED`)。
|
||
|
||
### 4.9 选项
|
||
|
||
**全局选项**:
|
||
|
||
| 选项 | 说明 |
|
||
|---|---|
|
||
| `-a, --account <name>` | 使用哪个账号(见 5.2) |
|
||
| `-e, --endpoint web\|app\|pc` | 端,默认 `web` |
|
||
| `-o, --output json\|jsonl` | 输出格式,默认 `json`(见 6.1) |
|
||
| `--raw` | 用平台原始对象代替归一化对象 |
|
||
| `--proxy <url>` | 本次调用使用的代理 |
|
||
| `-v, --verbose` | stderr 输出调试日志(已脱敏) |
|
||
| `-q, --quiet` | 不输出提示和进度,只保留错误 |
|
||
| `-y, --yes` | 跳过危险操作的确认(见 4.11) |
|
||
| `-h, --help` | 帮助 |
|
||
|
||
**分页**:
|
||
|
||
| 选项 | 说明 |
|
||
|---|---|
|
||
| `--limit N` | 默认只取一页。指定 N 后自动翻页,直到取满 N 条。翻页间隔用平台默认值 |
|
||
| `--cursor <c>` | 从上次返回的 `page.cursor` 继续翻。cursor 是不透明字符串。`--limit` 截在一页中间时,`page.cursor` 指回这一页,续翻时跳过已经输出的,不丢数据 |
|
||
| `--all` | 一直翻到没有更多为止 |
|
||
|
||
**筛选**:各平台在注册表里声明支持哪些筛选、取哪些值。取值只能从下表里选;需要新取值时先改本表。
|
||
|
||
| 选项 | 标准取值 |
|
||
|---|---|
|
||
| `--sort` | `general` `latest` `popular` `views` `comments` `collects` `sales` `price_asc` `price_desc` |
|
||
| `--type` | `all` `video` `image` `text` `article` `goods` |
|
||
| `--time` | `all` `day` `week` `month` `half_year` `year` |
|
||
| `--kind` | `recommend` `hot` `following`(`feed list`,默认 `recommend`) |
|
||
| `--category <id>` | 取值来自对应的 `categories` 命令 |
|
||
|
||
取值是标准值、但该平台不支持时报 `UNSUPPORTED`;不是标准值时报 `USAGE`。
|
||
|
||
**发布**(`item publish`、`dynamic publish` 等):
|
||
|
||
| 选项 | 说明 |
|
||
|---|---|
|
||
| `--title` | 标题 |
|
||
| `--text <str\|@file>` | 正文。`@file` 表示从文件读取,正文里的相对路径(如 Markdown 插图)按该文件所在目录解析 |
|
||
| `--image <path\|url>` | 图片,可重复 |
|
||
| `--video <path\|url>` | 视频 |
|
||
| `--cover <path\|url>` | 封面 |
|
||
| `--tag`、`--topic`、`--mention` | 均可重复 |
|
||
| `--poi <id>` | 地点 |
|
||
| `--category <id>` | 分类 |
|
||
| `--visibility public\|private\|friends\|fans` | 默认 `public`。`friends` 为好友(互相关注)可见,`fans` 为粉丝可见。平台在注册表里声明支持哪些取值,不声明时为 `public` `private` `friends`;平台不支持时报 `UNSUPPORTED`,不是标准值时报 `USAGE` |
|
||
| `--schedule <ISO 时间>` | 定时发布 |
|
||
| `--price <金额>` | 商品价格(闲鱼) |
|
||
| `--quote <item>` | 引用一条内容(ID 或 URL),发成引用。目前只有 x 支持,其他平台没有这个选项 |
|
||
|
||
平台在注册表里用 `supports` 声明 `item publish` 支持上表中的哪些选项。没声明支持的选项不出现在帮助里,用了报 `UNSUPPORTED`(`hint` 列出可用的选项),不会被静默忽略。各平台的取舍见 capabilities.md。
|
||
|
||
本地文件会自动上传,不需要先调 `media upload`。
|
||
|
||
**下载**:
|
||
- `--dir <目录>`:默认当前目录。文件名为 `<platform>_<id>_<序号>.<ext>`。
|
||
- `--overwrite`:覆盖已有文件,默认跳过已有文件。
|
||
|
||
**长连接**:`listen` 总是输出 jsonl,断线自动重连。`--duration <秒|10m|1h>` 指定运行时长,Ctrl-C 正常退出,退出码 0。
|
||
|
||
平台私有选项在注册表里声明,**不能与上面的标准选项重名**。
|
||
|
||
### 4.10 全局命令与帮助
|
||
|
||
全局命令不带平台,名字与平台 id、别名互斥:
|
||
|
||
| 命令 | 输出 |
|
||
|---|---|
|
||
| `catbus platforms [platform]` | 能力矩阵:平台 × 端 × resource.action 及其状态,数据来自注册表 |
|
||
| `catbus doctor` | 逐项检查:Node 版本;原生依赖能否加载(HTTP 库、canvas、onnx);`@cv-cat/catbus-assets-jd`、`@cv-cat/catbus-assets-ocr` 是否就位;`~/.catbus` 的权限;签名 vm 能否运行 |
|
||
| `catbus auth list` | 所有平台、所有端下的账号 |
|
||
| `catbus config get\|set\|unset\|list` | 读写 `config.toml`(见 5.5) |
|
||
| `catbus version`(或 `--version`) | 版本、Node 版本、系统与架构 |
|
||
|
||
帮助:
|
||
- 不带参数的 `catbus` 等同于 `catbus --help`。
|
||
- 帮助分四层:`catbus --help`、`catbus xhs --help`、`catbus xhs item --help`、`catbus xhs item get --help`。内容由注册表生成,写到 stdout,这是 stdout 上唯一不是 JSON 的输出。
|
||
- 命令级帮助列出参数、选项、示例,以及下面两行:
|
||
```
|
||
端: web ✓ · app ○ planned · pc ○ planned
|
||
登录: 需要 | 可选
|
||
```
|
||
- 面向人的文字(帮助、`message`、`hint`、提示)一律用中文。
|
||
|
||
`doctor` 有检查项不通过时,`ok` 为 false、退出码 1,`data` 里仍然是完整的检查结果。
|
||
|
||
### 4.11 危险操作确认
|
||
|
||
以下操作执行前需要确认:
|
||
- 所有 `delete`;
|
||
- `msg revoke`;
|
||
- bilibili 的 `item coin` / `item triple`;
|
||
- `live send --gift`。
|
||
|
||
确认方式:
|
||
- stdin 和 stderr 都是 TTY 时,在 stderr 上询问 y/N。
|
||
- 否则必须带 `-y`,不带时报 `CONFIRM_REQUIRED`,退出码 2。
|
||
|
||
### 4.12 不提供的能力
|
||
|
||
以下上游能力**不暴露为命令**:
|
||
- 埋点 / 心跳 / 上报;
|
||
- 弹窗 / 合规 / 钱包类接口;
|
||
- 系统配置;
|
||
- 闲鱼的自动回复示例;
|
||
- B 站笔记;
|
||
- 关系查询;
|
||
- xhs 的 share_code;
|
||
- 抖音的 PK / 连麦 / 评论标签;
|
||
- TikTok 的 story 和收藏夹移动;
|
||
- xhs 的蒲公英(达人)和千帆(分销达人):只对开通了的品牌 / 机构账号有用,普通账号访问会被拒,被拒后几分钟内主站的评论等接口还会要求人机验证。
|
||
|
||
如果某条命令内部需要其中的能力(例如心跳),由实现内部调用。
|
||
|
||
## 5. 登录态
|
||
|
||
### 5.1 目录
|
||
|
||
```
|
||
~/.catbus/ # 可用 CATBUS_HOME 覆盖
|
||
├── config.toml # 见 5.5
|
||
├── auth/
|
||
│ └── <platform>/ # 规范 id
|
||
│ └── <endpoint>/ # web | app | pc
|
||
│ ├── <account>.json # 一个账号一个文件
|
||
│ ├── guest.json # 游客态:设备 cookie、guest token 等,自动生成
|
||
│ └── _current # 当前账号名
|
||
└── cache/<platform>/ # 可随时删除:二维码 PNG、短信登录的中间态
|
||
```
|
||
|
||
- 账号名匹配 `[a-z0-9][a-z0-9_-]{0,31}`。`guest` 是保留名。
|
||
- 权限:
|
||
- 目录 0700,文件 0600;
|
||
- Windows 依赖用户目录本身的 ACL。
|
||
- 所有写入都是原子的:先写临时文件,再 rename。
|
||
|
||
### 5.2 身份选择与游客态
|
||
|
||
身份按以下优先级选择:`-a <name>` > `_current` > 游客。
|
||
|
||
- `-a guest` 强制使用游客身份(只在支持游客态的端上有意义)。`auth login` / `logout` / `use` 不接受 `-a guest`(保留名),报 `USAGE`。
|
||
- `-a` 指定的账号不存在时,报 `AUTH_REQUIRED`,`hint` 为 `catbus <p> auth login -a <name>`。
|
||
|
||
**游客态只在声明了 `guest: true` 的端上存在**(见 7.6)。不支持游客态的端上没有 `guest.json`:`auth status` 等 auth 命令没有账号时,用一份只在内存里的空凭证,不落盘。
|
||
- **web 端都不支持游客态**:各平台的 web 端不登录几乎看不到内容,所以 web 端除 `auth` 命令外全部需要登录,未登录时直接报 `AUTH_REQUIRED` 并提示登录命令。游客态主要留给 app 端。
|
||
- 支持游客态的端上,很多平台匿名访问也需要设备 cookie 或 token(例如 xhs 的 `a1`、抖音的 `ttwid`、B 站的 `buvid`、X 的 guest token)。catbus 自动生成这些数据,缓存到 `guest.json`,过期后重新生成;各平台的生成方式见 upstream-map。
|
||
- 登录流程本身仍会先生成这些设备数据(很多平台的登录接口要求先有设备 cookie),这与游客态无关。
|
||
|
||
每个命令在注册表里标注 `auth: required | optional`;不支持游客态的端上一律是 `required`:
|
||
|
||
| 情况 | 行为 |
|
||
|---|---|
|
||
| `required`,当前是游客 | `AUTH_REQUIRED`,退出码 3,`hint: catbus xhs auth login`(非 web 端时带上 `-e`) |
|
||
| `optional`,当前是游客 | 照常执行(信封里 `account` 为 `"guest"`),同时在 stderr 提示一行(见下) |
|
||
| 平台返回登录墙 | `AUTH_REQUIRED`,退出码 3 |
|
||
| 登录态失效 | `AUTH_EXPIRED`,退出码 3,`hint` 为重新登录的命令 |
|
||
|
||
`optional` 时在 stderr 输出的提示:
|
||
|
||
```
|
||
[catbus] 未登录 xhs (web),本次以游客身份访问,结果可能不完整;很多操作需要登录。登录:catbus xhs auth login
|
||
```
|
||
|
||
- `-q` 或 `-a guest` 时不输出这行提示。
|
||
- 只提示,不在命令中途弹出交互式登录。
|
||
|
||
### 5.3 登录与登出
|
||
|
||
```bash
|
||
catbus <p> auth login [-a <name>] [--method qrcode|sms|password|cookie] [--scope <scope>]
|
||
```
|
||
|
||
**账号名**:
|
||
- 不带 `-a` 时:有当前账号就登录到当前账号,否则登录到 `default`。
|
||
- 登录成功后,如果还没有当前账号,就把它设为当前账号。
|
||
- 如果该名字下已经存了另一个用户,报 `USAGE`,`hint` 为"用 `-a <新名字>`,或者先 logout",新凭证不保存。
|
||
|
||
**登录方式**:每个平台在注册表里声明支持哪些方式、默认用哪种,见 capabilities.md。
|
||
|
||
| 方式 | 选项 | 流程 |
|
||
|---|---|---|
|
||
| `qrcode` | — | 二维码画到 stderr,同时把 PNG 存到 `cache/<p>/`,然后等待扫码 |
|
||
| `sms` | `--phone`、`--code` | TTY 下先发验证码,再交互输入。非 TTY 下分两步:`--phone` 发验证码并保存中间态(10 分钟有效),再单独用 `--code` 完成登录 |
|
||
| `password` | `--username`、`--password-stdin` | 密码只从 stdin 读取,TTY 下隐藏输入 |
|
||
| `cookie` | `--cookie <str\|@file\|->` | 导入后调一次 `me` 校验。也接受浏览器导出的 cookie JSON 数组;douyin、tiktok 另外接受一个 JSON 对象,把 ticket、证书、私钥等设备数据一并导入(这两个平台的写操作需要它们) |
|
||
|
||
- **验证码**:上游能自动过的就自动过,过不了报 `RISK_CONTROL`(`detail.kind = captcha`),退出码 5。
|
||
- **子站点**(创作者中心、直播、IM 等):
|
||
- 能自动换取的凭证,登录时或第一次用到时自动获取,不需要用户再登录一次(例如 xhs 的创作者中心:用主站登录态换取);
|
||
- 需要单独登录的,用 `--scope <name>` 登录,各平台可用的 scope 在注册表里声明;
|
||
- 命令缺少所需 scope 时,报 `AUTH_REQUIRED`,`hint` 带上 `--scope`。
|
||
- **`auth status`**:在线校验,返回 AuthStatus。未登录或已失效时返回 `logged_in: false`,不报错。
|
||
- **`auth logout`**:删除本地凭证文件(上游支持服务端登出的,同时调用服务端登出,目前有 B 站和快手),并清除指向它的 `_current`。
|
||
- **`auth use <account>`**:设置 `_current`。
|
||
- **`auth list`**:列出本平台、本端的账号。
|
||
- **自动刷新**:会自动刷新的 token(如 B 站的 refresh)和响应里的 Set-Cookie,都会合并回凭证文件并原子写入。
|
||
|
||
### 5.4 凭证文件
|
||
|
||
```jsonc
|
||
{
|
||
"schema": 1,
|
||
"platform": "xhs",
|
||
"endpoint": "web",
|
||
"account": "default", // 游客文件为 "guest"
|
||
"user": { "id": "5f...", "name": "...", "url": "..." }, // UserRef;游客为 null
|
||
"method": "qrcode", // qrcode | sms | password | cookie | guest
|
||
"created_at": "2026-09-27T12:00:00+08:00",
|
||
"updated_at": "2026-09-27T12:00:00+08:00",
|
||
"scopes": { // 子站点分区;单站点平台只有 main
|
||
"main": {
|
||
"cookies": [ { "name": "a1", "value": "...", "domain": ".xiaohongshu.com", "path": "/", "expires": null } ],
|
||
"tokens": {} // ticket / ct0 / access_token / refresh_token 等
|
||
},
|
||
"creator": { "cookies": [], "tokens": {} }
|
||
},
|
||
"device": {}, // 设备指纹、IM 私钥与证书、localStorage 等设备绑定数据
|
||
"extra": {} // 平台私有字段
|
||
}
|
||
```
|
||
|
||
- cookie 统一存成带 domain 的列表。只导入了一段 cookie 字符串时,domain 取平台默认域。
|
||
- 凭证文件**只由 core 读写**。平台实现从 core 拿凭证,更新后交回 core 落盘。
|
||
|
||
### 5.5 配置与代理
|
||
|
||
`config.toml` 只有这几项:
|
||
|
||
```toml
|
||
proxy = "http://127.0.0.1:7890" # 全局代理
|
||
timeout = 30 # 单次请求超时(秒)
|
||
|
||
[xhs]
|
||
proxy = "socks5://..." # 平台代理
|
||
```
|
||
|
||
- 代理的优先级:`--proxy` > `<platform>.proxy` > `proxy`。
|
||
- 不读取 `HTTP(S)_PROXY` 环境变量,代理只能显式配置。
|
||
- `catbus config set xhs.proxy <url>`,用点号表示平台段。
|
||
|
||
## 6. 输出
|
||
|
||
### 6.1 信封与格式
|
||
|
||
stdout 只输出结果。日志、提示、进度、二维码一律输出到 stderr。
|
||
|
||
信封:
|
||
|
||
```json
|
||
{ "ok": true, "platform": "xhs", "endpoint": "web", "resource": "item", "action": "get",
|
||
"account": "default", "data": {}, "page": null, "error": null }
|
||
```
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `account` | 使用的账号名,游客为 `"guest"` |
|
||
| `page` | 分页命令为 `{ "cursor": "...", "has_more": true }`,其他命令为 `null` |
|
||
| `error` | 出错时 `ok` 为 false,`data` 为 null,`error` 为 `{ "code", "message", "hint", "detail" }`。`hint` 是建议执行的命令或做法,可以为 null |
|
||
|
||
全局命令的信封:
|
||
- `platform`、`endpoint`、`account` 都为 null;
|
||
- `resource` 是命令名,`action` 是子命令,没有子命令时为 null。
|
||
- 例如 `catbus auth list` 的 resource 是 `auth`,action 是 `list`。
|
||
|
||
`-o` 的两种格式:
|
||
- **`json`**(默认):stdout 恰好输出一个信封,成功和失败都是。stdout 是 TTY 时缩进输出,否则输出紧凑的一行。
|
||
- **`jsonl`**:stdout 每行一个 `data` 条目。列表命令一行一条;非列表命令只有一行。
|
||
- 结束时在 stderr 写一行摘要信封,`data` 为 null,带 `page` 和 `error`,用来取 next cursor 或错误信息。
|
||
|
||
`--raw`:用平台原始对象代替每个归一化对象,信封不变。
|
||
|
||
### 6.2 归一化类型
|
||
|
||
所有平台的同一种对象都输出同一结构。
|
||
|
||
**基础类型**:
|
||
|
||
| 类型 | 字段 |
|
||
|---|---|
|
||
| UserRef | `id name url` |
|
||
| Price | `amount currency`。`amount` 用货币主单位(元)的十进制数。虚拟货币也用它,`currency` 写平台货币代码 |
|
||
| Media | `id type url width height duration`。`url` 在刚上传、平台还没给出可访问地址时(例如视频转码前)为 null |
|
||
|
||
**内容与互动**:
|
||
|
||
| 类型 | 字段 |
|
||
|---|---|
|
||
| User | `id name handle avatar url bio stats{followers following items likes}` |
|
||
| Item | `id kind url title text author:UserRef created_at cover media:Media[] stats{views likes comments collects shares} price:Price\|null status\|null` |
|
||
| Comment | `id item_id parent_id author:UserRef text created_at stats{likes replies}` |
|
||
| Folder / Series | `id name count url` |
|
||
| Category | `id name parent_id` |
|
||
| Keyword | `text heat` |
|
||
| Topic | `id name url stats{views items}` |
|
||
| Poi | `id name address` |
|
||
|
||
**直播**:
|
||
|
||
| 类型 | 字段 |
|
||
|---|---|
|
||
| Live | `id url title status host:UserRef cover stats{viewers}` |
|
||
| Event | `type time user:UserRef text gift{name count}\|null` |
|
||
| Gift | `id name price:Price` |
|
||
| Rank | `rank user:UserRef score` |
|
||
|
||
**消息与通知**:
|
||
|
||
| 类型 | 字段 |
|
||
|---|---|
|
||
| Conversation | `id peer:UserRef unread last_message updated_at` |
|
||
| Message | `id conversation_id from:UserRef type text media:Media[] created_at` |
|
||
| Notice | `id type user:UserRef target{id url}\|null text created_at` |
|
||
| NoticeCount | `total comment mention like follow system` |
|
||
|
||
**账号与本地**:
|
||
|
||
| 类型 | 字段 |
|
||
|---|---|
|
||
| AuthStatus | `logged_in user:UserRef\|null method expires_at` |
|
||
| Account | `platform endpoint account current user:UserRef\|null method updated_at` |
|
||
| File | `path type url size` |
|
||
|
||
**扩展类型**(见 4.7):
|
||
|
||
| 类型 | 字段 |
|
||
|---|---|
|
||
| Subtitle | `lang name url lines[{from to text}]`,`lang` 缺失时为 null |
|
||
| Danmaku | `id item_id offset text created_at`,`offset` 为视频内的秒数 |
|
||
| Order | `id status total:Price items:Item[] created_at` |
|
||
| Coupon | `id title discount:Price threshold:Price\|null start_at end_at` |
|
||
|
||
**枚举值**:
|
||
|
||
| 字段 | 取值 |
|
||
|---|---|
|
||
| Item.kind | `video` `image` `text` `article` `goods` |
|
||
| Item.status | `published` `reviewing` `rejected` `private` `draft`;商品另有 `on_sale` `sold` `off_shelf`。只有自己的内容和商品才有,其余为 null |
|
||
| Media.type | `image` `video` `audio` |
|
||
| Live.status | `live` `offline` |
|
||
| Event.type | `chat` `gift` `like` `enter` `follow` `other` |
|
||
| Message.type | `text` `image` `video` `card` `other` |
|
||
| Notice.type | `comment` `mention` `like` `follow` `system` |
|
||
|
||
规则:
|
||
- ID 一律是字符串。
|
||
- 计数是整数或 null,例如 "1.2万" → 12000。
|
||
- 时间用带时区的 ISO 8601。
|
||
- 字段总是存在,缺失时为 null 或 `[]`。
|
||
- 不追加平台私有字段;需要原始字段时用 `--raw`。
|
||
- 新增类型或字段,先改本节。
|
||
|
||
### 6.3 各命令的输出
|
||
|
||
读取类命令按资源返回对应类型。列表类命令返回数组,并带 `page`。
|
||
|
||
| 命令 | data |
|
||
|---|---|
|
||
| `user get` | User |
|
||
| `user search` / `followers` / `following` | User[] |
|
||
| `user items` / `likes` / `collects` / `reposts`,`item search` / `related` / `list`,`feed list`,`folder items`,`series items`,`history list`,`live replays` | Item[] |
|
||
| `item get`、`product get` | Item |
|
||
| `live products` | Item[](`kind` 为 `goods`) |
|
||
| `item media`、`live media` | Media[] |
|
||
| `comment list` / `replies` | Comment[] |
|
||
| `live get` | Live |
|
||
| `live list` / `search` | Live[] |
|
||
| `live listen`(流) | Event |
|
||
| `live history` | Event[] |
|
||
| `live gifts` | Gift[] |
|
||
| `live rank` | Rank[] |
|
||
| `msg list` | Conversation[] |
|
||
| `msg history` | Message[] |
|
||
| `msg listen`(流) | Message |
|
||
| `notice list` | Notice[] |
|
||
| `notice count` | NoticeCount |
|
||
| `*categories` | Category[] |
|
||
| `keyword suggest` / `hot` | Keyword[] |
|
||
| `topic search` | Topic[] |
|
||
| `poi search` | Poi[] |
|
||
| `folder list` | Folder[] |
|
||
| `series list` | Series[] |
|
||
| `auth status` | AuthStatus |
|
||
| `auth list` | Account[] |
|
||
| `auth login` / `use` / `logout` | Account |
|
||
|
||
写操作的返回:
|
||
|
||
| 动作 | data |
|
||
|---|---|
|
||
| 成对动作、`delete`、`msg read` / `revoke`、`live send` / `like` / `stop` | `{id}`,即目标对象的 id |
|
||
| `item publish` | Item |
|
||
| `comment add` | Comment |
|
||
| `msg send` | Message |
|
||
| `media upload` | Media |
|
||
| `item download` | File[] |
|
||
| `folder create` / `update` | Folder |
|
||
| `live start` | `{id push{url key}}`,推流地址 |
|
||
|
||
全局命令的返回:
|
||
|
||
| 命令 | data |
|
||
|---|---|
|
||
| `platforms` | Platform[] `{id name aliases endpoints{web app pc} commands[{endpoint resource action auth status upstream note}]}`;带平台参数时返回单个对象 |
|
||
| `doctor` | `{name ok message}[]` |
|
||
| `config list` | 对象:只含已设置的项,键用点号展开,例如 `xhs.proxy` |
|
||
| `config get` | 值 |
|
||
| `config set` / `unset` | `{key value}` |
|
||
| `version` | `{version node platform arch}` |
|
||
|
||
### 6.4 退出码
|
||
|
||
| 退出码 | code | 含义 |
|
||
|---|---|---|
|
||
| 0 | — | 成功 |
|
||
| 1 | `ERROR` | 未分类错误 |
|
||
| 2 | `USAGE` / `UNSUPPORTED` / `CONFIRM_REQUIRED` | 参数错误 / 平台或端不支持该命令 / 危险操作未确认 |
|
||
| 3 | `AUTH_REQUIRED` / `AUTH_EXPIRED` | 未登录或登录态失效 |
|
||
| 4 | `NOT_IMPLEMENTED` | planned 的端或能力 |
|
||
| 5 | `RISK_CONTROL` | 验证码或风控,`detail.kind` 为 `captcha` / `rate_limit` / `blocked` |
|
||
| 6 | `NETWORK` | 网络 / 代理错误 |
|
||
| 7 | `UPSTREAM` | 平台返回业务错误,原始错误码放在 `detail` 里 |
|
||
|
||
## 7. 架构
|
||
|
||
### 7.1 技术栈
|
||
|
||
- **语言**:TypeScript,strict,ESM。用 `tsc` 编译到 `dist/`,发布的是编译后的 JS。Node 拒绝对 `node_modules` 里的文件做类型剥离,所以必须有编译这一步。
|
||
- **Node 版本**:`engines` 为 `^22.22.2 || ^24.15.0 || >=26.0.0`,跟随 jsdom 30 的要求。
|
||
- **测试**:vitest。
|
||
- **开发**:用 npm scripts,不引入额外的构建工具。
|
||
|
||
### 7.2 打包与分发
|
||
|
||
| 包 | 内容 |
|
||
|---|---|
|
||
| `catbus-cli` | `dist/`(CLI、core、全部平台)和 `static/`(上游签名 JS 等)。bin 为 `catbus` → `dist/cli/main.js` |
|
||
| `@cv-cat/catbus-assets-jd` | JD 验证码(JCAP)的 onnx 模型,约 81 MB。作为 `catbus-cli` 的普通依赖,精确钉版本 |
|
||
| `@cv-cat/catbus-assets-ocr` | 验证码 OCR 模型:ddddocr 1.6.1 的检测模型 `common_det.onnx`、识别模型 `common_old.onnx` 和字符集,约 34 MB(B 站极验点选在用)。同样是精确钉版本的普通依赖 |
|
||
|
||
- 拆出 assets-jd / assets-ocr 是因为模型很少变。放在主包里的话,每次发版都要上传 100 多 MB。
|
||
- 两个包的源码在 `packages/assets-jd/`、`packages/assets-ocr/`,是 npm workspace,开发时直接链接。
|
||
- 模型文件不进 git,由 `npm run assets:jd`(`scripts/fetch-jd-models.mjs`)按 `src/platforms/jd/UPSTREAM` 的 commit 取回并校验 sha256:有 `references/JdApis` 时从本地复制,否则从 GitHub 下载。打包 assets-jd 时会自动执行。
|
||
- OCR 模型同样不进 git,由 `npm run assets:ocr`(`scripts/fetch-ocr-models.mjs`)从 PyPI 上固定版本的 ddddocr wheel 里取出,wheel 和每个文件都校验 sha256:本机装过同版本 ddddocr(如 `.golden/<p>` 虚拟环境)时直接复制,否则下载 wheel(也可用 `CATBUS_DDDDOCR_WHEEL` 指定本地 wheel)。打包 assets-ocr 时会自动执行。ddddocr 是 MIT 许可证,包里附带它的原许可证 `LICENSE-ddddocr`。
|
||
- catbus 自己**不做**按系统划分的运行时包。原生依赖(HTTP 库、`@napi-rs/canvas` 等)各自通过 `optionalDependencies` 带上本机的预编译包。
|
||
- `npm i` 之后完全离线可用,npmmirror 可以完整镜像。
|
||
- **目标系统**:
|
||
- win32-x64、win32-arm64;
|
||
- darwin-arm64、darwin-x64;
|
||
- linux-x64、linux-arm64,glibc 和 musl 都要支持。
|
||
- CI 在全部目标系统上跑冒烟测试,musl 用 Alpine。**任何一个目标系统缺预编译包的依赖都不能用。**
|
||
- 发布:
|
||
1. 改 `package.json` 的版本号。模型变了时同时改 `packages/assets-*/package.json` 的版本,并把主包里钉住的依赖改成同一个版本(测试会检查)。
|
||
2. 在 `CHANGELOG.md` 写好 `## [<版本>] - <日期>` 一节。
|
||
3. 推送 `v<版本>` tag,触发 `.github/workflows/release.yml`:先检查 tag 与版本号一致、CHANGELOG 里有这个版本;跑完整 CI;发布 CI 里测过的同一份 tarball;最后用 `scripts/release-notes.mjs` 从 CHANGELOG 生成 GitHub Release。
|
||
- 顺序:先发 assets-jd、assets-ocr,再发 `catbus-cli`。已经在 npm 上的版本跳过,失败后重跑不会重复发布。
|
||
- 认证:npm 上给三个包配了 trusted publishing(GitHub Actions,`cv-cat/catbus`,`release.yml`,允许 `npm publish`)时走 OIDC,否则用仓库 secret `NPM_TOKEN`。trusted publishing 配好后两天内要成功发布一次,否则失效。
|
||
- 仓库公开时发布带 `--provenance`,私有时不带(npm 只给公开仓库生成 provenance),由 release.yml 按仓库可见性自动判断。
|
||
- **Docker 镜像**(可选):`Dockerfile` 走与 npm 发布相同的路径,先打出三个包,再在 `node:<版本>-bookworm-slim` 里全局安装这三个 tarball;`CATBUS_HOME=/data`(卷),工作目录 `/work`;默认以 root 运行(挂进来的目录都能写),Linux 上可以 `--user "$(id -u):$(id -g)"` 配合挂自己的数据目录。`compose.yaml` 提供 `catbus`(一次性命令)和 `live-listen`(长时间监听直播)两个服务。CI 的 `.github/workflows/docker.yml` 在 x64 与 arm64 上构建并跑 `version` / `doctor`,不推送到任何镜像仓库。
|
||
- 以后要提供 SDK 时,通过 `package.json` 的 `exports` 暴露,现在不做。
|
||
|
||
### 7.3 依赖选型
|
||
|
||
原则:
|
||
- 优先纯 JS / WASM。
|
||
- 原生依赖必须在全部目标系统上都有预编译包,不能有 install 脚本,也不能需要 node-gyp。
|
||
- 一律用最新版,由 `package-lock.json` 锁定。
|
||
|
||
| 用途 | 选择 |
|
||
|---|---|
|
||
| 带浏览器 TLS / HTTP2 指纹的 HTTP(替代 curl_cffi) | wreq-js:JA4 / H2 指纹与真 Chrome 一致,可关闭默认头以精确控制头顺序。impit 指纹不符、没有 WebSocket,不用 |
|
||
| WebSocket | wreq-js 自带的 `websocket()`,与 HTTP 同指纹、共享 cookie |
|
||
| protobuf | protobufjs。它的 Reader 也用来做无 schema 解码,替代 blackboxprotobuf |
|
||
| onnx 推理 | onnxruntime-web(WASM)。太慢再换 onnxruntime-node |
|
||
| 图像处理 | @techstark/opencv-js |
|
||
| 图片编解码 | @napi-rs/canvas,不用 sharp |
|
||
| 验证码 OCR | 移植 ddddocr:模型跑在 onnxruntime-web 上,可参考 ddddocr-node。模型来自 ddddocr 1.6.1 的 wheel,放在 `@cv-cat/catbus-assets-ocr`(见 7.2) |
|
||
| 签名 JS 的依赖 | jsdom(JD)、crypto-js(XHS)、@napi-rs/canvas(JD、KS) |
|
||
| 校验 | zod |
|
||
| 参数解析 | `node:util` 的 `parseArgs`,加上注册表。不用 CLI 框架 |
|
||
| 配置 | smol-toml |
|
||
| 终端二维码 | qrcode |
|
||
| 日志 | 自己写的 stderr logger,负责给凭证打码 |
|
||
|
||
|
||
wreq-js 默认会读 `HTTP(S)_PROXY` 环境变量和 Windows 系统代理。为遵守 5.5,`core/http` 在启动时设置 `NO_PROXY=*`,代理只通过 per-request 的 `proxy` 传入。
|
||
|
||
### 7.4 签名 JS
|
||
|
||
- 上游的签名 JS **原样复制**到 `static/<p>/`,进版本控制,不手改。需要适配的地方在 TS 一侧包装。
|
||
- **执行方式**:放到 `node:vm` 的 context 里执行。
|
||
- context 按脚本懒创建,创建后复用。
|
||
- 需要 DOM 的脚本(JD)注入 jsdom。
|
||
- `require` 用 `createRequire` 指向 catbus 自己的 `node_modules`,这样 `crypto-js` / `jsdom` / `@napi-rs/canvas` 从主包依赖里解析。
|
||
- 原来按命令行脚本写的 JS(读 argv / stdin、往 stdout 打印),由 TS 包装层喂参数、收结果。
|
||
- vm 跑不了的脚本(依赖进程级状态等),退回到 `worker_threads`,或者用 `process.execPath` 起子进程。
|
||
- 上游 Python 里围绕 JS 的胶水代码,以及纯 Python 实现的签名(如 wbi、mtop sign),都移植成 TS。
|
||
|
||
### 7.5 移植与对拍测试
|
||
|
||
- **移植**:上游 Python 的请求构造、签名、解析逻辑都重写成 TS。行为以上游为准:同样的输入,产出同样的请求(URL、query、header、body、签名)。
|
||
- **对拍数据**:用 `scripts/golden/<p>/gen.py` 生成,框架是 `scripts/golden/catbus_golden.py`。
|
||
- 在开发机上用 Python 3.13 + uv 建一个不进 git 的虚拟环境 `.golden/<p>/`(`uv venv .golden/<p> --python 3.13`,再装上游的依赖),运行 `references/` 里的上游代码。
|
||
- 框架替换了 Python 的 `random` / `secrets` / `uuid` / `os.urandom` / `time`,并截获 curl_cffi 与 requests 的请求(不联网,按用例给的响应回复);上游起的 node 子进程通过 `NODE_OPTIONS` 预加载 `node_determinism.cjs`,固定 `Math.random` 与 `Date`。
|
||
- 时区固定为北京时间(`Asia/Shanghai`):框架会设置 `TZ`,TS 侧的测试在 `vitest.config.ts` 里固定同一时区。
|
||
- 输出到 `tests/golden/<p>/<case>.json`。**只能用假凭证。**
|
||
- **对拍测试**:`tests/<p>.test.ts` 用 `tests/golden.ts` 的 `replay()` 在同样的随机数与时钟下运行 TS 实现,`expectRequests()` 逐字节比较请求(URL、header 顺序、cookie、body)。请求构造和签名都必须有对拍测试。
|
||
- **平台实现的约定**(参考实现是 `src/platforms/bilibili/web/`):
|
||
- 文件:`client.ts`(会话:设备初始化、签名密钥、带重试的请求、业务码 → 错误)、`api.ts`(一个函数对应一个上游方法,字段与顺序照抄)、`commands.ts`(命令 handler)、`normalize.ts`(原始对象 → 6.2 的类型,挂上原始对象供 `--raw`)、`resolve.ts`(参数归一化)。
|
||
- 随机数和当前时间只经 `core/rand.ts`,等待用 `rand.sleep`;与 Python 对应的编码(`urlencode`、`quote`、`json.dumps`)用 `core/py.ts`;需要精确键序的 JSON 用 Map。解析平台响应用 `core/py.ts` 的 `jsonLoads`(与 `json.loads` 一样保留 64 位整数 ID),不直接用 `JSON.parse`。
|
||
- HTTP 用 `core/http.ts` 的 `HttpClient`(`toolkit.httpClient(ctx)`),请求头按上游顺序显式给出;cookie 在凭证的 cookie 罐里,Set-Cookie 自动写回,命令结束时由 core 落盘。
|
||
- 签名 JS 复制到 `static/<p>/`,用 `core/vm.ts` 的 `loadScript` / `callScript` 执行。
|
||
- 业务错误映射到 6.4 的错误码:登录墙 → `toolkit.authError`,风控 → `RISK_CONTROL`,其余 → `UPSTREAM`(原始错误码放 `detail`)。
|
||
- **上游基线**:`src/platforms/<p>/UPSTREAM` 记录上游仓库地址和 commit。
|
||
- **上游更新时**:
|
||
1. 用 `git -C references/<repo> log <commit>..` 查看变更;
|
||
2. 同步移植;
|
||
3. 重新生成对拍数据;
|
||
4. 更新 UPSTREAM。
|
||
- **在线测试**:`npm run test:e2e`,用单独的配置 `tests/e2e/vitest.config.ts`,不在 `npm test` 和 CI 里。使用开发者本机 `~/.catbus` 里的登录态,没登录的平台整组跳过;`npm run test:e2e -- -t <platform>` 只跑一个平台(`-t` 按名字前缀匹配,x 要写成 `-t '^x '`,否则会连 xhs、xianyu 一起跑)。
|
||
- 只跑只读命令:从注册表枚举每个平台已实现的读取类命令,前面命令返回的对象(item、用户、评论、直播间、会话、收藏夹……)的 `url` 或 `id` 作为后面命令的参数,逐条校验信封和 6.2 的字段结构。写操作、下载、长连接不跑。
|
||
- 命令之间默认间隔 1.5 秒,小红书 4 秒,避免触发风控。以下记为跳过:子站点没登录(`AUTH_REQUIRED`)、平台风控(`RISK_CONTROL`)、默认参数落在规划中的取值上(`NOT_IMPLEMENTED`)、注册表 note 写明的限制(有 note 的命令报 `UNSUPPORTED`)。
|
||
- 每条命令的信封(列表只留前 3 条)写到 `.e2e/<platform>.json`,不进版本控制,用来检查归一化的字段是否取到了值。
|
||
- **CI**(`.github/workflows/ci.yml`):只跑离线测试,再加上全部目标系统的冒烟测试(`scripts/smoke.mjs`),冒烟测试包括:
|
||
- 安装包;
|
||
- 运行 `version`、`doctor`、`platforms`;
|
||
- 跑一次签名 vm;
|
||
- 加载原生依赖。
|
||
|
||
### 7.6 能力注册表
|
||
|
||
每个平台的 `src/platforms/<p>/index.ts` 导出平台声明:
|
||
|
||
```ts
|
||
export default definePlatform({
|
||
id: 'xhs', name: '小红书', aliases: ['xiaohongshu', 'rednote'], item: '笔记',
|
||
endpoints: {
|
||
web: {
|
||
login: { methods: ['qrcode', 'sms', 'cookie'], default: 'qrcode', scopes: ['creator'] },
|
||
commands: {
|
||
'user get': 'full',
|
||
'item get': { upstream: 'full', handler: () => import('./web/item.js').then((m) => m.get) },
|
||
'feed list': { upstream: 'partial', options: { kind: filter.kind('recommend', 'following') } },
|
||
'order list': { upstream: 'full', summary: '订单列表', args: [], output: 'Order[]',
|
||
auth: 'required', paged: true }, // 扩展命令(示意):字段写全
|
||
},
|
||
},
|
||
app: 'planned',
|
||
pc: 'planned',
|
||
},
|
||
})
|
||
```
|
||
|
||
- 第 4.5 节词表里的命令,默认的 summary、args、选项、auth、output 都来自 `src/core/vocab.ts`(summary 里的 `{item}` 替换成平台的 item 叫法)。平台只写与默认不同的部分。
|
||
- 命令的值可以是 `'full' | 'partial' | 'none'` 的简写,也可以是对象。
|
||
- 矩阵里 — 的命令不写。
|
||
- `auth list` / `use` / `logout` 由 core 自动注册。
|
||
- 端的字段:`login`(登录方式与子站点)、`logout?`(服务端登出)、`guest?`(是否支持游客态,默认 false)、`commands`。`guest` 为 false 时,除 `auth` 外的命令一律按 `auth: required` 处理,声明 `optional` 会在启动时报错。web 端都不设 `guest`。
|
||
|
||
命令的字段:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `summary` | 一句话说明 |
|
||
| `args` | 位置参数 |
|
||
| `options` | 私有选项和筛选取值,用 zod 声明 |
|
||
| `supports?` | 词表给这个命令的标准选项里,平台支持哪些(如 `item publish` 的发布选项)。不写时全部支持;没列出的不出现在帮助里,用了报 `UNSUPPORTED` |
|
||
| `auth` | `required` / `optional`;只在 `guest: true` 的端上可以是 `optional` |
|
||
| `confirm?` | 是否需要危险操作确认 |
|
||
| `stream?` | 是否长连接 |
|
||
| `paged?` | 是否分页 |
|
||
| `output` | 输出类型名 |
|
||
| `upstream` | 上游支持程度:`full` / `partial` / `none`,即矩阵里的 ✓ / ◐ / ○ |
|
||
| `note?` | 能力说明,例如"只支持图文" |
|
||
| `handler?` | 动态 import,懒加载。有 handler 的命令 status 为 `implemented`,否则为 `planned` |
|
||
|
||
注册表是唯一的事实来源,以下都从它生成:
|
||
- argv 解析、`--help`;
|
||
- `catbus platforms`;
|
||
- NOT_IMPLEMENTED / UNSUPPORTED 的判定;
|
||
- `docs/capabilities.md`:用 `npm run gen:capabilities` 生成,不手改,测试会检查它是否最新;
|
||
- 将来的 MCP 工具,工具名为 `<platform>_<resource>_<action>`。
|
||
|
||
执行一条命令时只加载这一个平台的这一个 handler,不会连带加载别的平台的依赖(如 JD 的 onnx)。
|
||
|
||
### 7.7 目录结构
|
||
|
||
```
|
||
All-In-One/
|
||
├── AGENTS.md CLAUDE.md→AGENTS.md
|
||
├── README.md(另有 en、zh-TW、ja、ko 译本) CHANGELOG.md llms.txt
|
||
├── package.json tsconfig.json tsconfig.build.json # npm 主包 catbus-cli
|
||
├── Dockerfile compose.yaml Makefile .dockerignore
|
||
├── .github/ # 仓库治理:CONTRIBUTING、TESTING、SECURITY、PRIVACY、CODE_OF_CONDUCT
|
||
├── assets/ # logo、banner、README 用的图
|
||
├── docs/
|
||
│ ├── guide/ platforms/ # 用户指南、各平台说明
|
||
│ ├── examples/ # 组合 catbus 的示例脚本
|
||
│ ├── capabilities.md # 能力矩阵(由注册表生成,不手改)
|
||
│ ├── upstream-map.md # 上游移植对照
|
||
│ └── trouble.md roadmap.md # 内部文档(真机验证记录、路线图):只在本地,不进版本控制
|
||
├── .claude/skills/catbus/ # 给 AI Agent 的技能:SKILL.md 总纲,platforms/、workflows/ 按需读;与注册表的一致性由 tests/skill.test.ts 检查
|
||
├── references/ # 上游仓库:只读,不进版本控制
|
||
├── src/
|
||
│ ├── cli/ # 入口 main.ts、argv 解析、帮助、信封输出、确认
|
||
│ ├── core/ # registry、auth store、config、errors、http、schemas、signing(vm)、log
|
||
│ ├── platforms/_shared/ # 多个平台共用、但不属于 core 的实现,例如 impaas.ts(闲鱼、淘宝的钉钉 IMPaaS 私信长连)
|
||
│ └── platforms/<p>/
|
||
│ ├── index.ts # 平台声明与命令注册
|
||
│ ├── UPSTREAM # 移植所基于的上游仓库与 commit
|
||
│ └── web/ # web 端实现;app/、pc/ 等真正实现时再建
|
||
├── static/<p>/ # 上游签名 JS、proto 等,原样复制
|
||
├── packages/assets-jd/ # @cv-cat/catbus-assets-jd
|
||
├── packages/assets-ocr/ # @cv-cat/catbus-assets-ocr
|
||
├── scripts/golden/<p>/ # 调用上游 Python 生成对拍数据
|
||
└── tests/ # vitest;golden/<p>/ 放对拍数据
|
||
```
|
||
|
||
`.gitignore`:`references/`、`node_modules/`、`dist/`、`packages/assets-jd/`、`packages/assets-ocr/` 下的模型文件,以及内部文档 `docs/trouble.md`、`docs/roadmap.md`。
|
||
|
||
### 7.8 provider 协议(规划中)
|
||
|
||
给将来的非 JS 上游(Go / Java……)用:任意语言的可执行文件,以子进程形式运行,通过 stdin / stdout 传 JSON。
|
||
|
||
- **清单**:`provider.json` 声明 `id`、支持的 platform / endpoint、能力列表(`resource.action`,同样遵守第 4 节词表)和启动命令。
|
||
- **安装位置**:`~/.catbus/providers/<name>/`,或者 npm 包 `@cv-cat/catbus-provider-<name>`。
|
||
- **请求**:core 往 stdin 写一个 JSON:`{v, platform, endpoint, resource, action, args, options, account, credential}`。
|
||
- **响应**:provider 往 stdout 逐行写 JSON,消息类型有:
|
||
- `result`:最终结果;
|
||
- `item`:流式条目;
|
||
- `credential_update`:刷新后的凭证;
|
||
- `error`:错误,code 取 6.4。
|
||
- **日志**:写到 stderr,core 原样转发。
|
||
- **凭证**:provider 不直接读写 `~/.catbus/`。凭证由 core 放在请求里给它,更新后通过 `credential_update` 交回,由 core 落盘。
|
||
- **注册表**:provider 的能力合并进注册表,`platforms`、`--help`、NOT_IMPLEMENTED 对它一视同仁。
|
||
|
||
## 8. 开发约定
|
||
|
||
- **语言**:与用户沟通用中文;面向用户的文字用中文;代码标识符用英文。
|
||
- **工具链**:
|
||
- 用户侧只需要 Node(版本见 7.1)。
|
||
- 开发侧需要 Node、Python 3.13 和 `uv`。Python 只用来生成对拍数据。
|
||
- **依赖**:不引入需要现场编译的依赖(node-gyp、install 脚本)。新增原生依赖前,先确认全部目标系统都有预编译包。
|
||
- **凭证**:严禁把真实 cookie / token / 私钥写进代码、测试 fixture、日志或提交记录。凭证只在 `~/.catbus/` 里。logger 对 cookie、token、Authorization 等字段打码。
|
||
- **stdout 纯净**:调试输出一律走 stderr / logger。
|
||
- **新增命令**:
|
||
1. 先查第 4 节词表,不够用就先改本文件;
|
||
2. 在注册表里声明;
|
||
3. 再实现。
|
||
- **移植**:
|
||
- 移植以上游行为为准,请求构造和签名必须有对拍测试。
|
||
- 发现上游 bug 时,修到对应的上游仓库,再按新 commit 重新同步。
|
||
- **`references/`**:只读。可以运行,可以在 gitignore 的位置装依赖,但不能修改受版本控制的文件。
|