feat(source): 外部音源迁 src/ 并新增 source-simple 进程内直载
- apis/source(.ivm).ts → src/source(.ivm).ts(音源属服务端职责,不入 SDK) - 新增 src/source-simple.ts:sources/*.mjs 进程内直载(getMusicUrl 极简契约), 与 *.js 的 IVM 沙箱(globalThis.lx 契约)按扩展名混排候选链,失败降权冷却 - server 挂载决策:SOURCE_ENABLED 非 0/false 启用,/url 取流分流到外部音源; 音源模式下登录态不参与取流,自动续期不启动(手动 /login/refresh 仍可用) - 新增 src/env.ts(.env 最先加载)与 test/source-simple-test.ts(并入 pnpm test)
This commit is contained in:
1 parent
66d06ef48a
commit
96f6a7fe91
13 files changed
+570
-94
No files matched your search
+2
-2
@@ -3,10 +3,10 @@
|
||||
## 架构
|
||||
|
||||
```
|
||||
调用方 ── source.kw.getMusicUrl(songInfo, type) ──▶ src/apis/source.js(懒加载单例)
|
||||
调用方 ── source.kw.getMusicUrl(songInfo, type) ──▶ src/source.ts(懒加载候选链)
|
||||
│
|
||||
▼
|
||||
src/apis/source-ivm.js
|
||||
src/source-ivm.ts
|
||||
isolated-vm 硬隔离沙箱
|
||||
┌──────────────────────────┐
|
||||
│ 音源脚本(升版即换文件) │
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# 外部音源 —— 简单 API 脚本(.mjs)
|
||||
|
||||
社区音源脚本之外的第二种脚本接入:脚本就是普通 Node ES 模块,**只需导出一个 `getMusicUrl` 函数**,无需 preload API 契约(`lx.on` / `lx.request` / crypto 桥接…)。适合自己写的取流脚本。
|
||||
|
||||
## 两种脚本对比
|
||||
|
||||
| | 社区音源脚本(`.js`) | 简单 API 脚本(`.mjs`) |
|
||||
|---|---|---|
|
||||
| 加载器 | `source-ivm.ts`,isolated-vm 硬隔离沙箱 | `source-simple.ts`,进程内直接 `import` |
|
||||
| 脚本契约 | `lx.on('request')` + `lx.send('inited')` 全套 preload API | 一个 `getMusicUrl` 函数 |
|
||||
| 能力 | 沙箱内桥接的 http/crypto/zlib | Node 全部能力(fetch / import 任意包) |
|
||||
| 适用 | 来源不明的第三方混淆脚本 | 自己写、自己审计过的脚本 |
|
||||
|
||||
两种脚本都放 `sources/` 目录(不入库),按**文件名混排**成候选链(顺序即优先级),失败自动切换下一个。
|
||||
|
||||
## 脚本契约
|
||||
|
||||
```js
|
||||
// sources/99-my-api.mjs
|
||||
export const sources = ['kw', 'kg', 'tx', 'wy'] // 可选,默认 kw/kg/tx/wy 四平台
|
||||
export const timeout = 15000 // 可选,单次取流超时 ms(默认 15000)
|
||||
|
||||
export async function getMusicUrl({ source, id, songInfo, quality, signal }) {
|
||||
// ...你的取流逻辑
|
||||
return 'https://…' // 播放地址字符串,或 { type, url }
|
||||
}
|
||||
```
|
||||
|
||||
`getMusicUrl` 参数:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `source` | 平台 id:`kw` / `kg` / `tx` / `wy`(或脚本自己声明的) |
|
||||
| `id` | 歌曲 id(已归一:`songmid` / `hash` / `copyrightId` 取第一个非空值) |
|
||||
| `songInfo` | 原始歌曲信息对象(含 songmid/hash 等原始字段) |
|
||||
| `quality` | 音质:`128k` / `320k` / `flac` / `flac24bit` |
|
||||
| `signal` | 取消/超时信号(`AbortSignal`)——调用方取消或超时都会触发;`fetch(url, { signal })` 透传即可支持取消 |
|
||||
|
||||
返回值:
|
||||
|
||||
- **字符串**:播放地址(最简单)
|
||||
- **`{ type, url }`**:对象(type 为实际音质,用于音质回退标注)
|
||||
|
||||
抛错即本次取流失败,由候选链切换下一候选,不影响服务器。
|
||||
|
||||
## 示例
|
||||
|
||||
### 最小示例
|
||||
|
||||
```js
|
||||
// sources/99-demo.mjs
|
||||
export async function getMusicUrl({ source, id, quality }) {
|
||||
return `https://your-api.example/get?source=${source}&id=${id}&quality=${quality}`
|
||||
}
|
||||
```
|
||||
|
||||
### 调用 JSON API 并透传取消信号
|
||||
|
||||
```js
|
||||
// sources/98-my-api.mjs
|
||||
const API = 'https://your-api.example'
|
||||
|
||||
export async function getMusicUrl({ source, id, quality, signal }) {
|
||||
const res = await fetch(`${API}/url?source=${source}&id=${id}&quality=${quality}`, { signal })
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const { code, msg, data } = await res.json()
|
||||
if (code !== 0) throw new Error(msg || `code=${code}`)
|
||||
return data.url
|
||||
}
|
||||
```
|
||||
|
||||
### 按平台区分逻辑
|
||||
|
||||
```js
|
||||
// sources/97-multi.mjs
|
||||
export const sources = ['kw', 'kg'] // 只接入 kw/kg,其余平台走候选链中的其它脚本
|
||||
|
||||
export async function getMusicUrl({ source, id, quality }) {
|
||||
if (source === 'kw') return kwLogic(id, quality)
|
||||
return kgLogic(id, quality)
|
||||
}
|
||||
```
|
||||
|
||||
## 候选链行为
|
||||
|
||||
- 顺序 = 文件名排序(`.js` 与 `.mjs` 混排),如 `01-api.js` 优先于 `99-my-api.mjs`
|
||||
- 某脚本的平台连续失败 **3 次** → 该脚本在该平台降权冷却 **5 分钟**(沉到候选末尾,恢复后自动探测)
|
||||
- 单脚本无处可切时不做降权,行为与直连失败一致(路由 502)
|
||||
- `SOURCE_SCRIPT_PATH` 指定单个社区脚本(`.js`)时跳过目录扫描,但 `.mjs` 仍参与候选链
|
||||
|
||||
## 启用方式与调试
|
||||
|
||||
- 环境变量 `SOURCE_ENABLED=1`(任意非 `0`/`false` 值)启用外部音源;不设置则走内置直连。音源模式下登录态不参与取流,登录自动续期不会启动
|
||||
- `pnpm dev` 热重载已监听 `sources/` 目录,改脚本保存即自动重启生效
|
||||
- 日志前缀 `[source-simple]`:加载(平台列表)、每次取流(▶ 开始 / ✔ 完成 / ✖ 失败,含耗时)
|
||||
- 离线测试:`node test/source-simple-test.ts`(已并入 `pnpm test`)
|
||||
|
||||
## 边界
|
||||
|
||||
- **无沙箱**:脚本运行在服务器进程内,可访问 fs/process 等——只放自己审计过的代码;不可信脚本用 `.js` 走 [isolated-vm 加载器](source-ivm.md)
|
||||
- 仅接入**取流**(getMusicUrl);歌词、封面、榜单等能力始终走内置实现
|
||||
- 脚本内部自起的长任务(如忽略 signal 的死循环请求)由超时兜底放弃等待,脚本侧仍可能继续在后台运行
|
||||
Reference in new issue
Block a user