# 架构说明 一份代码,两个调用方:Agent 工具与面板 UI,最终落到同一组函数。 ``` ┌──────────────┐ ┌────────────────┐ │ Agent 工具 │ │ 面板 UI │ │ media_fetch │ │ client.js │ └──────┬───────┘ └───────┬────────┘ │ 进程内调用 │ 同源 fetch │ │ POST /bili-dl/* ┌──────▼────────────────────────▼────────┐ │ index.js(宿主) │ │ 路由 / 工具注册 / 图片代理 / 登录协调 │ └──────┬───────────┬───────────┬─────────┘ │ │ │ ┌──────▼──┐ ┌─────▼────┐ ┌───▼──────┐ │ bili.mjs│ │douyin.mjs│ │ xhs.mjs │ │ B 站 │ │ 抖音 │ │ 小红书 │ └─────────┘ └──────────┘ └──────────┘ └── ytdlp.mjs(其余站点) ``` ## 模块职责 | 文件 | 职责 | |---|---| | `index.js` | 注册 web 路由与工具;`probeUniversal` 按链接分流到四个引擎;图片代理(白名单校验) | | `lib/bili.mjs` | B 站:WBI 签名、取 DASH 流、ffmpeg 合流、封面多比例、二维码登录 | | `lib/douyin.mjs` | 抖音:分享页 SSR 解析、匿名 `ttwid` 签发与缓存 | | `lib/xhs.mjs` | 小红书:移动端分享页解析、图文与视频 | | `lib/ytdlp.mjs` | yt-dlp 封装:探测、下载、cookie 传递、代理 | | `lib/browser-login.mjs` | 登录窗口:独立 profile 的浏览器实例 + CDP 读 cookie | | `lib/qr.mjs` | 零依赖二维码编码(B 站扫码登录用) | | `client.js` | 面板 UI,使用宿主提供的受限 React 子集 | ## 三个原生解析器的数据入口 | 平台 | 入口 | 拿到的字段 | |---|---|---| | B 站 | `x/web-interface/view` + `x/player/wbi/playurl` | `dash.video[]` / `dash.audio[]` / `pic` | | 抖音 | `iesdouyin.com/share/video/` 的 `_ROUTER_DATA` | `video.play_addr.url_list` / `images[]` / `author` | | 小红书 | `xiaohongshu.com/discovery/item/?xsec_token=…` 的 `__INITIAL_STATE__` | `noteData.data.noteData.imageList` / `video.media.stream` | 三者都不依赖浏览器:抖音只需一个匿名 `ttwid`(插件自行签发并缓存 7 天),小红书只需链接自带的一次性访问令牌。 ## 下载任务模型 所有引擎共用 `createJob` / `publicJob` 的任务结构: ```js { id, status: 'running' | 'done' | 'error' | 'canceled', items: [{ kind: 'video' | 'audio' | 'cover' | 'image' | 'avatar', label, status, file, error, parts: [{ received, total }] }] } ``` 进度通过 `parts` 上报,面板轮询 `/bili-dl/job?id=` 更新。 ## 状态与文件位置 ``` ~/.dsh/bili-dl/ ├── state.json 下载目录、代理、ffmpeg 路径、B 站登录态 ├── cookies.txt Netscape 格式,供 yt-dlp 使用 ├── names.json 各平台账号昵称(仅用于展示) ├── ttwid.json 抖音匿名访客标识(7 天有效) └── browser-profile/ 登录窗口的独立浏览器 profile ``` ## 分流规则 `probeUniversal(url)` 按顺序判断: 1. `looksLikeBilibili` → `bili.mjs` 2. `looksLikeDouyin` → `douyin.mjs` 3. `looksLikeXhs` → `xhs.mjs` 4. 其余 → `ytdlp.mjs` ## 错误处理约定 - 解析失败抛错并给出可读原因,**不返回空数据充数** - 平台改版导致字段缺失时,明确报「没取到 X」,而不是静默显示 0 - 下载任务里每个 item 独立记录成败,一项失败不影响其它项