sharoon7171/vidup-stream-solver
GitHub: sharoon7171/vidup-stream-solver
一个 Node.js 实现的 vidup.to HLS 流媒体解析器,通过 VM 沙箱复现前端解码逻辑并提供本地代理播放 API。
Stars: 0 | Forks: 0
# VidUP Stream Resolver
针对 [vidup.to](https://vidup.to) 的 Node.js **HLS stream resolver**。它将电影和电视内容 ID 映射到标题路径,提取页面 token,在 **VM sandbox** 中通过 VidUP 客户端逻辑解码流媒体主机,并通过本地 **HTTP API** 提供代理后的 **m3u8** 播放。零 npm 运行时依赖——仅基于 Node.js 内置模块的 **ESM** 实现。
## 流解析器工作原理
- 根据 TMDB 格式的数字 ID 构建 `/movie/{id}` 和 `/tv/{id}/{season}/{episode}` 路径
- 获取 VidUP 标题页面,并从嵌入的页面数据中提取 `en` token
- 加载内置的 webpack chunk,并在**服务器端**的 **JavaScript** sandbox 中运行与网站相同的解码路径
- 通过从客户端 bundle 中提取的静态字符串解码器,解析被混淆的 API 路径片段
- 列出 VM 中可用的流媒体主机,随后**并行探测服务器**,并竞速解码,直到第一个可播放的 **HLS** URL 准备就绪
- 重写 **m3u8** manifest,并通过 `/api/hls` 转发切片,同时携带必需的 referer header;当遇到 PNG 封装的传输切片时会自动解封装
- 附带一个轻量级的浏览器 UI,提供实时 SSE 进度、hls.js 播放、服务器状态标签以及可复制的代理链接
## 快速开始
```
git clone https://github.com/sharoon7171/vidup-stream-solver.git
cd vidup-stream-solver
npm start
```
打开 `http://127.0.0.1:8787`,输入 TMDB ID,选择电影或电视(需要时填写剧集/集数),然后点击 **Play**。
要求 Node.js 18+(原生 `fetch`)。默认在本地 **HTTP server** 的 `8787` 端口运行(可通过 `PORT` 环境变量覆盖)。
## 内容路径
| 类型 | 路径 |
| ----- | ---- |
| Movie | `/movie/{id}` |
| TV | `/tv/{id}/{season}/{episode}` |
`{id}` 是 VidUP URL 中使用的 TMDB 数字 ID(与 [themoviedb.org](https://www.themoviedb.org) 上的数字相同)。
## 环境变量
| 变量 | 默认值 | 用途 |
| -------- | ------- | ------- |
| `PORT` | `8787` | HTTP 监听端口 |
| `VIDUP_ORIGIN` | `https://vidup.to` | 用于页面和流媒体请求的站点源 |
| `VIDUP_CSRF_TOKEN` | 站点默认值 | 用于上游 POST 请求的 `X-Csrf-Token` header |
## Streaming API
所有路由均由根 HTTP 服务器提供。JSON 响应成功时使用 `ok: true`,失败时使用 `ok: false` 并附带 `stage` 和 `error`。
### `GET /api/stream`
将内容解析为第一个可播放的流(阻塞直到探测/解码完成)。
| 参数 | 必需 | 描述 |
| --- | --- | --- |
| `id` | 是 | TMDB 数字 ID |
| `type` | 否 | `movie` 或 `tv`(省略时根据 season/episode 推断) |
| `season` | 仅限 TV | 季数 |
| `episode` | 仅限 TV | 集数 |
| `server` | 否 | 探测时优先选择的服务器索引或名称 |
**示例**
```
GET /api/stream?id=533535&type=movie
```
**成功**
```
{
"ok": true,
"type": "movie",
"contentPath": "/movie/533535",
"streamUrl": "https://…/master.m3u8",
"selectedServer": { "index": 0, "name": "ServerName" },
"servers": [{ "name": "…", "description": "…", "image": "…", "data": "…" }]
}
```
### `GET /api/stream/live`
查询参数与 `/api/stream` 相同,但通过 **Server-Sent Events** 流式传输进度:
| 事件 | Payload |
| --- | --- |
| `status` | `{ step, text }` — 当前 pipeline 步骤 |
| `found` | 通过上游探测的服务器 |
| `ready` | 第一个解码后的 `streamUrl` 以及服务器列表 |
| `fail` | `{ ok: false, stage, error, … }` |
| `done` | 所有探测完成后的最终服务器列表 |
内置的 UI 会连接到这里以获取逐步反馈,并在收到第一个 `ready` 事件时开始播放。
### `POST /api/server`
当您已经从之前的解析中获得了 `contentPath` 和 `data` 时,解码特定的服务器 slug。
```
{
"contentPath": "/movie/533535",
"type": "movie",
"data": "server-slug-from-vm"
}
```
针对单个主机,返回与 `/api/stream` 相同的结构。
### `GET /api/hls?url={streamUrl}`
用于 HLS 播放的 **Manifest 代理**。Playlist 会被重写,以便相对路径的切片和密钥 URL 能够通过此 endpoint 回环。二进制切片在获取时会携带 VidUP referer header;封装为 PNG 的 MPEG-TS payload 将被剥离为原始的 `0x47` 同步字节。
代理播放 URL:
```
http://127.0.0.1:8787/api/hls?url={encoded_streamUrl}
```
## 架构
```
flowchart LR
Browser["Browser UI\npublic/"]
Server["HTTP server\nsrc/server.mjs"]
Handler["Routes\nsrc/http/handler.js"]
Path["Content paths\nsrc/stream/path.js"]
Resolve["Stream resolve\nsrc/stream/resolve.js"]
VM["VM sandbox\nsrc/vm/"]
Vendor["Vendored chunk\nvendor/"]
Proxy["HLS proxy\nsrc/hls/proxy.js"]
Vidup["vidup.to"]
Browser -->|"/api/stream/live"| Server
Browser -->|"/api/hls"| Server
Server --> Handler
Handler --> Path
Handler --> Resolve
Handler --> Proxy
Resolve --> VM
VM --> Vendor
Resolve --> Vidup
Proxy --> Vidup
```
### 解析流程
1. **内容解析** — `src/stream/path.js` 将 TMDB ID + 类型映射为 VidUP 标题路径;被混淆的流 POST 路径来自 `vendor/extracts/decoder.js`
2. **Token 提取** — 获取标题页面的 HTML;从嵌入的 JSON 中解析出 `en` token
3. **VM 加载** — `src/vm/extract.js` 切分 VidUP webpack chunk;`src/vm/runtime.js` 构建一个包含 fetch、crypto 和 DOM shim 的 sandbox,然后暴露 `runServers` 和 `runDecode`
4. **服务器列表** — VM 针对页面 token 运行,并返回带有不透明 `data` slug 的命名主机
5. **并行探测** — worker 将每个 slug POST 到上游;一旦发现可达主机即立即发出
6. **流解码** — VM 将上游响应主体解码为 HLS master 或 media playlist URL
7. **播放** — 浏览器通过 `/api/hls` 播放,确保 manifest 和切片携带 CDN 期望的 header
## 项目布局
```
src/server.mjs HTTP entry
public/
index.html local player and resolve UI
src/
cfg/constants.js origin, user-agent, CSRF headers
http/handler.js route dispatcher
stream/
path.js content path builder and query parser
resolve.js probe, decode, SSE live resolve
hls/proxy.js m3u8 rewrite and segment proxy
vm/
runtime.js sandbox construction and VM invoke
extract.js chunk slice, patches, runner glue
vendor/
chunks/294-….js vendored VidUP client chunk
extracts/decoder.js static string table decoder
```
## 逆向工程说明
VidUP 将 stream discovery 和 **stream decode** 封装在一个带编号的 webpack bundle 中,而不是使用普通的 REST handler。本仓库内置了该 chunk,提取出 VM 区域,并重放了浏览器在 **token 提取** 之后调用的相同 `oz` / `oP` 入口点。当 VidUP 更新其客户端时,请刷新 `vendor/chunks/` 并针对 `src/vm/extract.js` 中新的 bundle 边界重新运行提取步骤。
标签:GNU通用公共许可证, HLS代理, JavaScript沙箱, MITM代理, Node.js, Web爬虫, 去混淆, 流媒体解析, 自定义脚本