Files
catbus/.github/CONTRIBUTING.md

16 KiB
Raw Permalink Blame History

贡献指南

感谢你愿意帮 catbus(猫巴士)变得更好。本文说明怎么搭开发环境、怎么新增或修改平台命令、怎么写对拍测试,以及提交和 PR 的约定。

项目的规范本身在 AGENTS.md:命令语法(第 4 节)、登录态(第 5 节)、输出(第 6 节)、架构(第 7 节)、开发约定(第 8 节)。本文与它冲突时以 AGENTS.md 为准。改规范时先改 AGENTS.md,再改代码。

测试的细节见 TESTING.md,安全问题的报告方式见 SECURITY.md。

1. 开发环境

工具 版本 用途
Node ^22.22.2 || ^24.15.0 || >=26.0.0 构建、测试、运行(见 AGENTS 7.1)
npm 随 Node 附带 依赖与 workspace
git 任意较新版本
Python 3.13 + uv 3.13 只用于生成对拍数据(见第 5 节)。只改文档、CLI、core 或者不涉及请求构造的代码时不需要

用户侧只需要 Node。不要引入需要 Python、C/C++ 编译器、node-gyp 或 install 脚本的依赖(AGENTS 7.3、8)。

2. 拉代码与构建

git clone https://github.com/cv-cat/catbus.git
cd catbus
npm ci
npm run assets:jd      # 取回京东验证码模型(约 81 MB),放到 packages/assets-jd/models/
npm run assets:ocr     # 取回 ddddocr 的 OCR 模型(约 34 MB),放到 packages/assets-ocr/models/
npm run build          # tsc 编译到 dist/
npm link               # 可选:把 catbus 命令链到全局,之后可以直接运行 catbus
npm run typecheck      # 类型检查(vitest 不做类型检查,要单独跑)
npm test               # 离线测试:单元测试 + 对拍测试
  • 模型文件不进 git。离线测试里的京东验证码求解和 B 站极验点选识别要用到它们,所以跑 npm test 之前先取回。assets:jd 在有 references/JdApis 时从本地复制,否则从 GitHub 下载;assets:ocr 从 PyPI 上固定版本的 ddddocr wheel 里取出。两者都校验 sha256。
  • 不想 npm link 时,也可以直接运行 node dist/cli/main.js <参数>。
  • 改了 src/ 之后要重新 npm run build,npm link 出来的 catbus 才会生效。测试直接跑 TypeScript 源码,不需要先构建。

常用 npm scripts(见 package.json):

命令 作用
npm run build 清理 dist/ 后编译
npm run typecheck tsc --noEmit,包括 tests/
npm test 离线测试(vitest)
npm run test:e2e 在线测试,用本机 ~/.catbus 的登录态,只跑只读命令(见 TESTING.md)
npm run gen:capabilities 由注册表重新生成 docs/capabilities.md
npm run assets:jd / assets:ocr 取回模型文件

3. 项目结构

catbus/
├── AGENTS.md                       # 规范(CLAUDE.md 通过 @AGENTS.md 引用它)
├── docs/
│   ├── capabilities.md             # 能力矩阵,由注册表生成,不手改
│   └── upstream-map.md             # 上游移植对照
├── src/
│   ├── cli/                        # 入口 main.ts、argv 解析、帮助、信封输出、危险操作确认、全局命令
│   ├── core/                       # 注册表、词表、auth store、config、errors、http、vm、rand、py、log……
│   └── platforms/
│       ├── <p>/index.ts            # 平台声明与命令注册
│       ├── <p>/UPSTREAM            # 移植所基于的上游仓库与 commit
│       ├── <p>/web/                # web 端实现
│       └── _shared/                # 几个平台共用的实现(例如闲鱼、淘宝共用的钉钉 IMPaaS 私信长连)
├── static/<p>/                     # 上游签名 JS、proto 等,按字节原样复制
├── packages/assets-jd/             # @cv-cat/catbus-assets-jd(京东验证码模型)
├── packages/assets-ocr/            # @cv-cat/catbus-assets-ocr(ddddocr 模型)
├── scripts/
│   ├── golden/                     # 调用上游 Python 生成对拍数据
│   ├── smoke.mjs                   # CI 冒烟测试
│   ├── release-notes.mjs           # 从 CHANGELOG 生成 GitHub Release 的说明
│   └── fetch-*-models.mjs          # 取回模型
├── tests/                          # vitest;golden/<p>/ 放对拍数据,e2e/ 放在线测试
└── references/                     # 上游仓库的只读副本,不进版本控制

references/:上游仓库,只读

references/ 里是作者 cv-cat 的 10 个上游仓库(平台 id 与仓库名的对应见 AGENTS 第 3 节)。它不进本仓库的版本控制,需要时自己克隆,并切到 src/platforms/<p>/UPSTREAM 记录的 commit(记有 branch 的,就在那个分支上):

git clone https://github.com/cv-cat/BilibiliApis references/BilibiliApis
git -C references/BilibiliApis checkout 7d80150893b429651deee76929af760ea642b844   # 取自 src/platforms/bilibili/UPSTREAM

规则(AGENTS 3、8):

  • 可以读,也可以运行(用来生成对拍数据);
  • 可以在上游 gitignore 的位置装依赖,例如 node_modules/;
  • 不能修改其中任何受版本控制的文件。发现上游 bug 时,修到对应的上游仓库,再按新 commit 重新同步(第 6 节)。

只跑 npm test 不需要 references/:对拍数据已经生成好、放在 tests/golden/ 里。

4. 新增或修改平台命令

4.1 三步走(AGENTS 8「新增命令」)

  1. 先查词表。 看 AGENTS 第 4 节:通用词表(4.5)、原生叫法对照(4.6)、参数(4.8)、标准选项(4.9)。含义相同的能力在所有平台上用同一个命令、同一组参数、同一种输出结构(「同义同名」)。

    • 词表不够用时,先改 AGENTS.md:平台特有的命令或私有选项登记到 4.7「平台扩展」;新的筛选取值加到 4.9;新的输出类型或字段加到 6.2。
    • 私有选项不能与标准选项重名。平台原生叫法(note、video……)不做别名。
  2. 在注册表里声明。 编辑 src/platforms/<p>/index.ts。词表里的命令只写与默认不同的部分,扩展命令把字段写全:

    'item related': impl('full', 'itemRelated'),
    'danmaku list': { upstream: 'full', summary: '视频弹幕', args: [item], auth: 'required', output: 'Danmaku[]', handler: h('danmakuList') },
    
    • upstream 取 full / partial / none,即矩阵里的 ✓ / ◐ / ○;上游只部分支持时在 note 里写清限制。
    • handler 名必须是 <resource><Action>(例如 draft delete → draftDelete),测试会检查。
    • 改完运行 npm run gen:capabilities,把重新生成的 docs/capabilities.md 一起提交。它不手改,测试会检查它是否最新。
  3. 再实现。 见下一节。

4.2 平台实现的约定(AGENTS 7.5)

参考实现是 src/platforms/bilibili/web/,新平台或新命令照它的结构写:

文件 内容
client.ts 会话:设备初始化、签名密钥、带重试的请求、业务码 → 错误
api.ts 一个函数对应一个上游方法,字段与顺序照抄上游
commands.ts 命令 handler,导出名与注册表里的 handler 名一致
normalize.ts 原始对象 → AGENTS 6.2 的归一化类型,挂上原始对象供 --raw
resolve.ts 参数归一化:ID、URL、分享短链、自然标识、me

写代码时:

  • 行为以上游为准:同样的输入,产出同样的请求(URL、query、header 顺序、cookie、body、签名)。
  • 随机数和当前时间只经 core/rand.ts,等待用 rand.sleep。否则对拍时随机数序列会错位。
  • 与 Python 对应的编码(urlencode、quote、json.dumps)用 core/py.ts;需要精确键序的 JSON 用 Map。
  • 解析平台响应用 core/py.ts 的 jsonLoads,不直接用 JSON.parse(它和 Python 的 json.loads 一样保留 64 位整数 ID)。
  • HTTP 用 toolkit.httpClient(ctx) 拿到的 HttpClient,请求头按上游顺序显式给出。cookie 在凭证的 cookie 罐里,Set-Cookie 自动写回,命令结束时由 core 落盘。平台代码不直接读写 ~/.catbus/。
  • 上游的签名 JS 原样复制到 static/<p>/,不手改(.gitattributes 让它按字节保存),用 core/vm.ts 的 loadScript / callScript 执行,需要适配的地方在 TS 一侧包装。
  • 业务错误映射到 AGENTS 6.4 的错误码:登录墙用 toolkit.authError,验证码或风控报 RISK_CONTROL(detail.kind 为 captcha / rate_limit / blocked),其余报 UPSTREAM,原始错误码放进 detail。
  • stdout 只输出信封,调试信息走 ctx.log(stderr,已打码)。面向用户的文字(message、hint、提示)用中文。
  • 上游没有对应方法、catbus 自己补的请求,也放进 api.ts 并在注释里注明「非上游」。这类请求没法对拍,要用命令测试覆盖(见 TESTING.md)。
  • 上游方法与 catbus 命令、选项的对应关系变了,同步更新 docs/upstream-map.md。

5. 对拍测试

请求构造和签名必须有对拍测试(AGENTS 7.5):先用上游 Python 代码在固定的随机数与时钟下生成请求序列,再让 TS 实现在同样的条件下运行,逐字节比较。

5.1 准备对拍环境

每个平台一个不进 git 的虚拟环境 .golden/<p>/:

uv venv .golden/<p> --python 3.13
VIRTUAL_ENV=.golden/<p> uv pip install -r references/<repo>/requirements.txt

各平台还要装哪些额外依赖,写在 scripts/golden/<p>/gen.py 开头的说明里。例如 douyin 的 blackboxprotobuf 要单独 --no-deps 安装;xhs、jd 还要在 references/<repo>/ 下 npm install(装在上游 gitignore 的 node_modules/ 里)。

5.2 生成对拍数据

在仓库根目录运行(macOS / Linux 是 bin/python,Windows 是 Scripts\python):

.golden/bilibili/bin/python scripts/golden/bilibili/gen.py

框架 scripts/golden/catbus_golden.py 会:

  • 把 Python 的 random / secrets / uuid / os.urandom / time 换成与 src/core/rand.ts 一一对应的确定性序列,时区固定为北京时间;
  • 截获 curl_cffi 与 requests 的请求,不联网,按用例给的响应回复;
  • 上游起的 node 子进程通过 NODE_OPTIONS 预加载 node_determinism.cjs,同样固定 Math.random 与 Date;
  • 每个用例写成 tests/golden/<p>/<case>.json,内容包括 input、seed、now、requests、responses、result、error。

在 gen.py 里加一个用例,例如:

case('user_info', logged(lambda a: BiliApi.get_user_info(a, '2')), mid='2')

respond(req) 按请求的 URL 返回假响应,不写时默认回 {"code": 0, "data": {}}。

只能用假凭证。 cookie、token、私钥、账号 ID 一律写成 fake-sessdata、10001 这样的假值,不要从浏览器复制真实值,哪怕「只是临时试一下」。对拍数据会进版本控制。

5.3 在 TS 侧对拍

在 tests/<p>.test.ts 里给用例加上 TS 侧的等价调用,由 tests/golden.ts 的 replay() 在同样的随机数与时钟下运行,expectRequests() 比较请求序列:

const CASES: Record<string, () => Promise<unknown>> = {
  user_info: () => logged((b) => api.userInfo(b, '2')),
  // ...
}

describe('bilibili 对拍:请求构造与签名', () => {
  for (const [name, run] of Object.entries(CASES)) {
    it(name, async () => {
      const c = loadCase('bilibili', name)
      const { requests, error } = await replay(c, run)
      if (error) throw error
      expectRequests(requests, c.requests)
    })
  }
})

对拍数据由 gen.py 生成,不要手改 tests/golden/ 里的 JSON。重新生成后用 git diff tests/golden/<p> 检查:只应该有你打算改的用例变化。大量无关用例一起变了,通常是随机数的消耗顺序变了,或者 references/ 不在 UPSTREAM 记录的 commit 上。

对拍失败时怎么排查,见 TESTING.md 第 2.5 节。

6. 上游更新时的同步流程

上游仓库更新后,按 AGENTS 7.5 的四步同步:

  1. 查看变更:

    git -C references/<repo> pull
    git -C references/<repo> log <UPSTREAM 里的 commit>..
    git -C references/<repo> diff <UPSTREAM 里的 commit>.. -- <关心的目录>
    
  2. 同步移植:改 api.ts / client.ts 等;签名 JS 变了就把新文件原样复制到 static/<p>/。

  3. 重新生成对拍数据:运行 scripts/golden/<p>/gen.py,检查 git diff tests/golden/<p>,再跑 npm test。

  4. 更新 src/platforms/<p>/UPSTREAM:改成新的 commit(跟的是分支的,同时更新 branch)。

提交信息写成 <p>:同步上游 <commit 或分支>,<改了什么>,例如 x:同步上游 3fe6ea7——正文超过 280 权重自动发长推……。

7. 提交规范

看 git log 就能看出现有风格,请保持一致:

  • 用中文写,标题格式为 <范围>:<改了什么>,冒号用全角「:」。不用 feat: / fix: 之类的前缀。
  • 范围:
    • 平台 id:xhs:、douyin:;多个平台用顿号:xianyu、taobao:;
    • 模块或文件:core:、core/http:、core/vm:、docs/platforms/xhs.md:;
    • 规范小节:AGENTS 7.5:;
    • 其他:在线测试:、对拍框架:、release:。
  • 把原因写进去:修了什么现象、为什么这样改,引用上游时注明方法名或 commit,例如 (上游 get_following)。
  • 标题里有几件事时用「;」分开。改动大时,正文用 - 列要点。

真实的例子:

core/http:响应头声明了非 UTF-8 的 charset(如京东的 gbk)时按它解码,与上游 resp.text 一致;修京东登录后用户名乱码
xhs:user following(上游 XHSLiveAPI.get_following,私信页的关注列表),只支持 me,查别人报 UNSUPPORTED
AGENTS 7.5:解析平台响应用 jsonLoads
在线测试:平台风控(RISK_CONTROL)记为跳过

提交信息、PR 描述里不要出现 cookie、token 或真实账号数据。

8. PR 检查清单

提交 PR 前逐项确认:

  • npm run typecheck 和 npm test 都通过(本地已取回模型)。
  • 改了规范(命令、选项、输出字段、错误码)时,已经先改了 AGENTS.md。
  • 新命令已在注册表里声明,npm run gen:capabilities 已重新生成 docs/capabilities.md。
  • 请求构造和签名有对拍测试;对拍数据由 gen.py 生成,没有手改。非上游的请求有命令测试。
  • 代码、测试 fixture、对拍数据、日志、提交信息里只有假凭证。
  • 没有修改 references/ 里受版本控制的文件;static/ 里的上游 JS 是原样复制的。
  • stdout 只输出信封;面向用户的文字是中文。
  • 同步上游时,已更新 src/platforms/<p>/UPSTREAM;映射有变化时,已更新 docs/upstream-map.md。
  • 新增依赖在全部目标系统(AGENTS 7.2)上都有预编译包,没有 install 脚本,也不需要 node-gyp。
  • 做过真机验证的,写明验证了哪些命令、结果如何;新发现的限制记进 docs/platforms/<p>.md 的「已知限制」。

9. 凭证安全红线

以下几条没有例外(AGENTS 8):

  • 严禁把真实 cookie、token、私钥、证书写进代码、测试 fixture、对拍数据、日志、issue、PR 或提交记录。
  • 凭证只存在本机的 ~/.catbus/,只由 core 读写。平台实现从 core 拿凭证,更新后交回 core 落盘。
  • 日志一律走 logger,它会给 cookie、token、Authorization、密码等字段打码。不要用 console.log 或 process.stdout.write 输出调试信息。
  • 在线测试的报告 .e2e/<p>.json 里有真实账号返回的数据,不进版本控制,也不要贴到 issue 里。
  • 贴 -v 的调试日志之前,自己再检查一遍有没有漏掉的敏感信息。

发现安全问题请按 SECURITY.md 私下报告,不要公开发 issue。

10. 行为准则

参与本项目即表示你同意遵守 行为准则。