mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-24 16:41:22 +08:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddfd680301 | ||
|
|
c478367c6c | ||
|
|
a8c781fd64 | ||
|
|
690b462577 | ||
|
|
25ac0c041f | ||
|
|
edef19c3fb | ||
|
|
d55d92cd00 | ||
|
|
f5ce55bcb4 | ||
|
|
4480bd4359 | ||
|
|
e43b123c8a | ||
|
|
ed9bdd6ffa | ||
|
|
7b733e54ab | ||
|
|
edfaf6c0d1 | ||
|
|
f83ad951f6 | ||
|
|
22003c2c32 | ||
|
|
8376699f5e | ||
|
|
5c98a9a1ea | ||
|
|
7c17de7f09 | ||
|
|
a7e8d485f3 | ||
|
|
0957be6e76 | ||
|
|
4a91f77e62 | ||
|
|
a7437f7ca4 | ||
|
|
498945c401 | ||
|
|
7b17a08628 | ||
|
|
0b5c3927c9 | ||
|
|
881267c56d | ||
|
|
d7c0db9455 |
@@ -34,10 +34,11 @@ jobs:
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Setup pnpm
|
||||
run: npm install -g pnpm@9
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build for ${{ matrix.platform }}
|
||||
run: |
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
name: OpenCode Automation
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
@@ -17,19 +15,25 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
automate:
|
||||
# 排除机器人自己的触发,防止自我循环
|
||||
# 排除其他机器人触发,防止自我循环;
|
||||
# 但允许 takanashi-hoshino-agent 创建的 PR,这样 /oc 生成的测试 PR 也能被自动评审
|
||||
if: |
|
||||
github.actor != 'takanashi-hoshino-agent[bot]' &&
|
||||
github.actor != 'github-actions[bot]' &&
|
||||
github.actor != 'opencode-agent[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch config from CF Worker
|
||||
id: cfg
|
||||
uses: MoeFurina/pull-oc-config@v1
|
||||
with:
|
||||
url: https://bot-storage.interface.focalors.ltd/config
|
||||
|
||||
- name: Generate bot token
|
||||
id: token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
app-id: ${{ steps.cfg.outputs.app_id }}
|
||||
private-key: ${{ steps.cfg.outputs.app_private_key }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout
|
||||
@@ -37,7 +41,7 @@ jobs:
|
||||
with:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
persist-credentials: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -50,10 +54,14 @@ jobs:
|
||||
- name: Run automation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
|
||||
MODEL: opencode/deepseek-v4-flash-free
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
ISSUE_PROMPT: ${{ secrets.OPENCODE_PROMPT }}
|
||||
MODEL: opencode/muse-spark-1.3-contributor-free
|
||||
OPENCODE_API_KEY: ${{ steps.cfg.outputs.opencode_api_key }}
|
||||
ISSUE_PROMPT: ${{ steps.cfg.outputs.opencode_prompt }}
|
||||
PR_PROMPT: |
|
||||
${{ secrets.OPENCODE_PROMPT }}
|
||||
${{ secrets.PR_REVIEW_PROMPT }}
|
||||
run: node scripts/opencode-automation.mjs
|
||||
${{ steps.cfg.outputs.opencode_prompt }}
|
||||
${{ steps.cfg.outputs.pr_review_prompt }}
|
||||
run: |
|
||||
mkdir -p scripts
|
||||
curl -fsSL "https://raw.githubusercontent.com/MoeFurina/pull-oc-config/v1/scripts/opencode-automation.mjs" \
|
||||
-o scripts/opencode-automation.mjs
|
||||
node scripts/opencode-automation.mjs
|
||||
|
||||
@@ -25,32 +25,42 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Fetch config from CF Worker
|
||||
id: cfg
|
||||
uses: MoeFurina/pull-oc-config@v1
|
||||
with:
|
||||
url: https://bot-storage.interface.focalors.ltd/config
|
||||
|
||||
- name: Generate bot token
|
||||
id: token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
app-id: ${{ steps.cfg.outputs.app_id }}
|
||||
private-key: ${{ steps.cfg.outputs.app_private_key }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
# 需要持久化凭证: opencode action 内部会 git fetch origin <分支> 拉取 PR 代码
|
||||
persist-credentials: true
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.name "takanashi-hoshino-agent[bot]"
|
||||
git config --global user.email "takanashi-hoshino-agent[bot]@users.noreply.github.com"
|
||||
git config --global user.name "yukka-agent[bot]"
|
||||
git config --global user.email "yukka-agent[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Run opencode
|
||||
uses: anomalyco/opencode/github@latest
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ steps.cfg.outputs.opencode_api_key }}
|
||||
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
|
||||
with:
|
||||
model: opencode/deepseek-v4-flash-free
|
||||
model: opencode/muse-spark-1.3-contributor-free
|
||||
use_github_token: true
|
||||
prompt: |
|
||||
${{ secrets.OPENCODE_PROMPT }}
|
||||
${{ steps.cfg.outputs.opencode_prompt }}
|
||||
|
||||
## 用户指令(来自触发评论,请严格按此执行)
|
||||
${{ github.event.comment.body }}
|
||||
@@ -6,7 +6,6 @@ node_modules
|
||||
.history
|
||||
examples/moddef.json
|
||||
bin
|
||||
anonymous_token
|
||||
.vercel
|
||||
.env
|
||||
IFLOW.md
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
- 打包独立二进制:`pnpm pkgwin` / `pkglinux` / `pkgmacos`
|
||||
|
||||
## 架构
|
||||
- `app.js`(也是 `bin`)——服务入口。先确保 `os.tmpdir()` 里存在 `anonymous_token`,执行 `generateConfig()` 刷新匿名 cookie 与 xeapi 公钥,再调用 `server.serveNcmApi()`。
|
||||
- `app.js`(也是 `bin`)——服务入口。执行 `generateConfig()` 预热运行时凭证(匿名令牌与 xeapi 公钥,存于内存),再调用 `server.serveNcmApi()`。不依赖 `/tmp` 等可写文件系统。
|
||||
- `server.js`——Express 工厂。`constructServer()` 自动扫描 `module/*.js`,每个文件注册一条路由(文件名 `_` 转 `/`,如 `album_new.js` → `/album/new`;特例 `daily_signin`/`fm_trash`/`personal_fm` 硬编码在 `server.js` 的 `special` 对象里)。`serveNcmApi()` 监听 `PORT`(默认 3000)/`HOST`。
|
||||
- `main.js`——作为依赖被引入时的入口(`main` 字段)。把每个 `module/*` 导出为同名函数 `name(data)`,另导出 `server`、`serveNcmApi`、`getModulesDefinitions`。
|
||||
- `main.js`——作为依赖被引入时的入口(`main` 字段)。把每个 `module/*` 导出为同名函数 `name(data)`,另导出 `server`、`serveNcmApi`、`getModulesDefinitions`、`generateConfig`(库用户可 `await main.generateConfig()` 预热/续期凭证;不调用也行,首个请求会在凭证缺失时自动引导)。
|
||||
- `module/*.js`——每个接口一个文件,标准写法:`module.exports = (query, request) => request(path, data, createOption(query))`。`createOption` 在 `util/option.js`,负责 crypto、cookie(回退到 `NETEASE_COOKIE`)、proxy、realIP/randomCNIP、headers、timeout。
|
||||
- `util/request.js`——唯一的对外 HTTP 层(axios)。按 `crypto`(`api`/`eapi`/`weapi`/`linuxapi`/`xeapi`)加密并设置 IP 头;在 require 时同步读取 `os.tmpdir()` 里的 `anonymous_token` 与 `xeapi_public_key`。
|
||||
- `util/request.js`——唯一的对外 HTTP 层(axios)。按 `crypto`(`api`/`eapi`/`weapi`/`linuxapi`/`xeapi`)加密并设置 IP 头;匿名令牌与 xeapi 公钥每次请求实时读取 `util/runtimeState.js`,缺失时按需引导(并发共享同一刷新任务,刷新自身的请求带 `skipTokenEnsure` 防自等待死锁)。另导出 `refreshAnonymousToken` / `refreshXeapiPublicKey` 供 `generateConfig` 复用。
|
||||
- `util/runtimeState.js`——统一的进程内运行时动态状态(`anonymousToken` / `xeapiPublicKey`)。纯内存、零文件 I/O;`register_anonimous` / `register_xeapikey` 成功后写入,`util/request.js` 读取,`ensure()` 提供缺失时按需刷新。以后新增动态配置放这里,不要再往 `os.tmpdir()` 写文件。
|
||||
- `util/config.json`——运行时配置:网易域名 + `APP_CONF.encrypt: true`(默认走 eapi 加密)。已被 git 跟踪,改动会改变全局默认行为。
|
||||
- `index.js` / `index.mjs`——`require('./app.js')` 的薄包装,供 Vercel(`vercel.json`)和 ESM 导入使用。
|
||||
- `server.js` 里有一段非显而易见的逻辑:环境变量 `ENABLE_GENERAL_UNBLOCK=true` 时,`/song/url/v1` 的响应会被自动解灰(走 `@neteasecloudmusicapienhanced/unblockmusic-utils`);另外 `query.noCookie` 为真时不向响应写 `Set-Cookie`。
|
||||
|
||||
## 新增/修改接口
|
||||
- 新建 `module/xxx.js` 会自动挂载路由,无需注册;**文件名即路由**。
|
||||
@@ -28,13 +30,15 @@
|
||||
- 改文件名/路由会破坏已有客户端,尽量保持旧路径兼容。
|
||||
|
||||
## 测试
|
||||
- `pnpm test` 跑 `server.test.js` + `main.test.js`。`server.test.js` 在 `before()` 里启动真实服务器,`test/*.test.js` 全部请求**真实网易云 API**——必须联网,且可能因上游风控/限流偶发失败。`main.test.js` 是纯单测。
|
||||
- `pnpm test` 跑 `server.test.js` + `main.test.js`。`server.test.js` 在 `before()` 里启动真实服务器,`test/*.test.js` 全部请求**真实网易云 API**——必须联网,且可能因上游风控/限流偶发失败。`main.test.js` 是纯单测,不需要网络。
|
||||
- **CI 门禁只看稳定项**:`ci-check.yml` 在 Node 18/22/24 上跑 `pnpm lint` + `main.test.js` + `pnpm docs:check`;完整集成测试(`server.test.js`)是 `continue-on-error`,不阻塞合并。本地跑全量测试失败先怀疑上游风控。
|
||||
- 测试使用 `power-assert`(经 `intelli-espower-loader`),普通 `assert` 写法也会输出详细 diff。
|
||||
- 只跑单个用例:`pnpm exec mocha -r intelli-espower-loader -t 60000 --grep "<describe/it 名字>" server.test.js main.test.js --exit`
|
||||
- 只跑离线单测:`pnpm exec mocha -r intelli-espower-loader -t 60000 main.test.js --exit`
|
||||
|
||||
## 坑与注意
|
||||
- **改 `package.json` 的 `version` 会触发自动发布**:push 到 `main` 后会自动打 GitHub Release(`pkg` 二进制)、构建并推送 Docker 镜像(Docker Hub + GHCR)、`pnpm publish` 到 npm。别顺手改版本号。
|
||||
- **没有实际 git hooks**:`package.json` 里配了 `lint-staged`,但 `.husky/` 下没有真正的 hook,commit 时不会自动跑任何检查,自己记得 `pnpm lint-fix`。
|
||||
- **改 `package.json` 的 `version` 会触发自动发布**:`release-on-version-change.yml` 按 paths 过滤——只要 push 到 `main` 时 `package.json` 有改动且 version 变了,就自动打 tag + GitHub Release(`pkg` 三平台二进制)、推送 Docker 镜像(Docker Hub + GHCR 多架构)、`pnpm publish` 到 npm;tag 已存在则跳过。别顺手改版本号,也别往 `package.json` 里写无关改动凑提交。
|
||||
- **没有实际 git hooks**:`package.json` 里配了 `lint-staged` 和 husky,但 `.husky/` 下只有 `_` 脚手架目录、没有真正的 hook 文件,commit 时不会自动跑任何检查,自己记得 `pnpm lint-fix`。
|
||||
- **代理环境变量已失效**:README 里关于 `http_proxy`/`https_proxy` 的警告来自旧 `request` 库时代;现在 `util/request.js` 用 axios + 自定义 keep-alive agent,且显式 `proxy: false`,环境变量代理不会生效。按请求走 `query.proxy` 参数(支持 PAC 和 http 隧道)。
|
||||
- **启动令牌在系统临时目录**:`anonymous_token`、`xeapi_public_key` 存放在 `os.tmpdir()`,`util/request.js` 在 require 时同步读取。文件过期或被清空就重启服务(或调用 `generateConfig()`);首次启动先写空文件再刷新。
|
||||
- **运行时凭证在内存(runtimeState)**:`anonymous_token` 与 `xeapi_public_key` 存于 `util/runtimeState.js`,不做任何文件 I/O(历史上曾以文件形式放 `os.tmpdir()`,为可移植性已移除——只读 FS / Android / iOS / serverless 均可运行)。启动时 `generateConfig()` 先取 xeapi 公钥、再注册匿名令牌(顺序不能反,后者依赖前者);运行中过期可再调 `generateConfig()` 或对应 `register_*` 接口,立即生效、无需重启。
|
||||
- **ESLint 9 flat config**:`eslint.config.js`,风格由 `eslint-plugin-prettier` 强制(2 空格缩进、单引号、分号、`endOfLine: auto`)。
|
||||
|
||||
+4
-2
@@ -2,7 +2,7 @@ FROM node:lts-alpine
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm install -g pnpm@9
|
||||
|
||||
@@ -12,7 +12,9 @@ WORKDIR /app
|
||||
|
||||
COPY --chown=node:node . ./
|
||||
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
# --prod 模式下 husky 不会被安装,prepare 脚本会因找不到 husky 而失败,
|
||||
# 故显式跳过生命周期脚本;本项目生产运行也不依赖任何 postinstall 步骤。
|
||||
RUN pnpm install --frozen-lockfile --prod --ignore-scripts
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const tmpPath = require('os').tmpdir()
|
||||
|
||||
async function start() {
|
||||
// 检测是否存在 anonymous_token 文件,没有则生成
|
||||
if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) {
|
||||
fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8')
|
||||
}
|
||||
// 启动时更新anonymous_token
|
||||
// 启动时预热运行时凭证(匿名令牌 / xeapi 公钥,均存于内存中的 runtimeState)
|
||||
const generateConfig = require('./generateConfig')
|
||||
await generateConfig()
|
||||
require('./server').serveNcmApi({
|
||||
|
||||
+17
-28
@@ -1,41 +1,30 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { register_anonimous } = require('./main')
|
||||
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
|
||||
const { getXeapiPublicKey } = require('./util/xeapiKey')
|
||||
const tmpPath = require('os').tmpdir()
|
||||
const request = require('./util/request')
|
||||
const { generateRandomChineseIP, generateDeviceId } = require('./util/index')
|
||||
|
||||
/**
|
||||
* 启动 / 手动刷新运行时动态配置。
|
||||
*
|
||||
* 凭证均存于内存(util/runtimeState.js),不做任何文件 I/O:
|
||||
* - 设备 ID 先行 —— xeapi 公钥注册需携带 deviceId(服务端对无
|
||||
* deviceId 的请求不下发 sk),匿名注册也复用同一设备身份;
|
||||
* - xeapi 公钥其次 —— 注册匿名令牌走 xeapi 加密,依赖该公钥;
|
||||
* - 匿名令牌最后注册,三者就绪后冷启动一次即可完成引导。
|
||||
*/
|
||||
async function generateConfig() {
|
||||
global.cnIp = generateRandomChineseIP()
|
||||
try {
|
||||
const res = await register_anonimous()
|
||||
const cookie = res.body.cookie
|
||||
if (cookie) {
|
||||
const cookieObj = cookieToJson(cookie)
|
||||
fs.writeFileSync(
|
||||
path.resolve(tmpPath, 'anonymous_token'),
|
||||
cookieObj.MUSIC_A,
|
||||
'utf-8',
|
||||
)
|
||||
if (!global.deviceId) {
|
||||
global.deviceId = generateDeviceId()
|
||||
}
|
||||
try {
|
||||
await request.refreshXeapiPublicKey()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
try {
|
||||
let currentPublicKey = {}
|
||||
try {
|
||||
currentPublicKey = JSON.parse(
|
||||
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'),
|
||||
)
|
||||
} catch (_) {}
|
||||
const publicKey = await getXeapiPublicKey(currentPublicKey, global.deviceId)
|
||||
fs.writeFileSync(
|
||||
path.resolve(tmpPath, 'xeapi_public_key'),
|
||||
JSON.stringify(publicKey),
|
||||
'utf-8',
|
||||
)
|
||||
await request.refreshAnonymousToken()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = generateConfig
|
||||
|
||||
Vendored
+5
@@ -584,6 +584,10 @@ export function device_kickoff(
|
||||
|
||||
export function device_list(params: RequestBaseConfig): Promise<Response>
|
||||
|
||||
export function deviceinfo_center_upload(
|
||||
params: { deviceName: string; name?: string } & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function digitalAlbum_ordering(
|
||||
params: {
|
||||
payment: string
|
||||
@@ -1378,6 +1382,7 @@ export const enum SoundQualityType {
|
||||
jyeffect = 'jyeffect',
|
||||
jymaster = 'jymaster',
|
||||
sky = 'sky',
|
||||
vivid = 'vivid',
|
||||
}
|
||||
|
||||
export function song_url_v1(
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const tmpPath = require('os').tmpdir()
|
||||
const { cookieToJson } = require('./util')
|
||||
|
||||
const anonymousTokenPath = path.resolve(tmpPath, 'anonymous_token')
|
||||
if (!fs.existsSync(anonymousTokenPath)) {
|
||||
fs.writeFileSync(anonymousTokenPath, '', 'utf-8')
|
||||
}
|
||||
|
||||
/** @type {Record<string, any>} */
|
||||
let obj = {}
|
||||
|
||||
@@ -46,6 +40,7 @@ moduleFiles.forEach((file) => {
|
||||
})
|
||||
|
||||
let serverModule = null
|
||||
let generateConfigModule = null
|
||||
|
||||
/**
|
||||
* @type {Record<string, any> & import("./server")}
|
||||
@@ -57,6 +52,17 @@ module.exports = {
|
||||
}
|
||||
return serverModule
|
||||
},
|
||||
/**
|
||||
* 刷新运行时动态配置(匿名令牌 / xeapi 公钥,存于内存 runtimeState)。
|
||||
* 作为依赖使用时可以不调用——首个请求会在凭证缺失时自动引导;
|
||||
* 需要预热或手动续期时再 `await main.generateConfig()`。
|
||||
*/
|
||||
get generateConfig() {
|
||||
if (!generateConfigModule) {
|
||||
generateConfigModule = require('./generateConfig')
|
||||
}
|
||||
return generateConfigModule
|
||||
},
|
||||
...obj,
|
||||
}
|
||||
|
||||
|
||||
@@ -16,3 +16,63 @@ describe('methods in module', () => {
|
||||
assert.strictEqual(typeof main.activate_init_profile, 'function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('runtimeState', () => {
|
||||
const runtimeState = require('./util/runtimeState')
|
||||
|
||||
it('has generateConfig for bootstrapping in-memory credentials', () => {
|
||||
assert.strictEqual(typeof main.generateConfig, 'function')
|
||||
})
|
||||
|
||||
it('exposes anonymousToken / xeapiPublicKey in memory', () => {
|
||||
assert.ok('anonymousToken' in runtimeState)
|
||||
assert.ok('xeapiPublicKey' in runtimeState)
|
||||
})
|
||||
|
||||
describe('ensure()', () => {
|
||||
it('returns existing value without refreshing', async () => {
|
||||
runtimeState.ensureProbe = 'ready'
|
||||
let called = 0
|
||||
const value = await runtimeState.ensure('ensureProbe', async () => {
|
||||
called++
|
||||
})
|
||||
assert.strictEqual(value, 'ready')
|
||||
assert.strictEqual(called, 0)
|
||||
delete runtimeState.ensureProbe
|
||||
})
|
||||
|
||||
it('refreshes when missing and shares one task among concurrent calls', async () => {
|
||||
let refreshes = 0
|
||||
const refresh = () =>
|
||||
new Promise((resolve) =>
|
||||
setTimeout(() => {
|
||||
refreshes++
|
||||
runtimeState.ensureProbe = 'fresh'
|
||||
resolve()
|
||||
}, 20),
|
||||
)
|
||||
const results = await Promise.all([
|
||||
runtimeState.ensure('ensureProbe', refresh),
|
||||
runtimeState.ensure('ensureProbe', refresh),
|
||||
runtimeState.ensure('ensureProbe', refresh),
|
||||
])
|
||||
assert.deepStrictEqual(results, ['fresh', 'fresh', 'fresh'])
|
||||
assert.strictEqual(refreshes, 1)
|
||||
delete runtimeState.ensureProbe
|
||||
})
|
||||
|
||||
it('propagates refresh failure and allows retry', async () => {
|
||||
let attempts = 0
|
||||
const fail = async () => {
|
||||
attempts++
|
||||
throw new Error('boom')
|
||||
}
|
||||
await assert.rejects(runtimeState.ensure('ensureProbe', fail), /boom/)
|
||||
assert.strictEqual(attempts, 1)
|
||||
// 失败后任务被清理,下一次调用会重试
|
||||
await assert.rejects(runtimeState.ensure('ensureProbe', fail), /boom/)
|
||||
assert.strictEqual(attempts, 2)
|
||||
delete runtimeState.ensureProbe
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// 上报设备中心设备名称
|
||||
// 官方 PC 端在登录成功后调用此接口,将当前登录设备在“登录设备管理”中的名称设置为自定义名称
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = async (query, request) => {
|
||||
const deviceName = String(query.deviceName || query.name || '').trim()
|
||||
|
||||
if (!deviceName) {
|
||||
return Promise.reject({
|
||||
status: 400,
|
||||
body: {
|
||||
code: 400,
|
||||
msg: '缺少必要参数: deviceName',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const data = {
|
||||
deviceName,
|
||||
}
|
||||
|
||||
return request(
|
||||
`/api/deviceinfo/center/upload`,
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 红心与取消红心歌曲- v1
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
module.exports = (query, request) => {
|
||||
query.like = query.like == 'false' ? false : true
|
||||
const data = {
|
||||
alg: 'itembased',
|
||||
trackId: query.id,
|
||||
like: query.like,
|
||||
time: '3',
|
||||
}
|
||||
return request(`/api/v1/radio/like`, data, createOption(query, 'xeapi', 'v3'))
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
// 私人FM - 模式选择
|
||||
|
||||
// aidj, DEFAULT, FAMILIAR, EXPLORE, SCENE_RCMD ( EXERCISE, FOCUS, NIGHT_EMO )
|
||||
// 来不及解释这几个了
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
|
||||
@@ -7,7 +7,8 @@ const ID_XOR_KEY_1 = '3go8&$8*3*3h0k(2)2'
|
||||
const logger = require('../util/logger.js')
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
const { generateDeviceId } = require('../util/index')
|
||||
const { generateDeviceId, cookieToJson } = require('../util/index')
|
||||
const runtimeState = require('../util/runtimeState')
|
||||
|
||||
// function getRandomFromList(list) {
|
||||
// return list[Math.floor(Math.random() * list.length)]
|
||||
@@ -25,7 +26,9 @@ function cloudmusic_dll_encode_id(some_id) {
|
||||
}
|
||||
|
||||
module.exports = async (query, request) => {
|
||||
const deviceId = generateDeviceId()
|
||||
// 复用进程内已有的设备身份(真实客户端的设备 ID 是稳定的);
|
||||
// xeapi 公钥注册同样依赖该 deviceId
|
||||
const deviceId = global.deviceId || generateDeviceId()
|
||||
logger.info(`Successfully registered anonimous token, deviceId: ${deviceId}`)
|
||||
global.deviceId = deviceId
|
||||
const encodedId = CryptoJS.enc.Base64.stringify(
|
||||
@@ -42,6 +45,11 @@ module.exports = async (query, request) => {
|
||||
createOption(query, 'xeapi'),
|
||||
)
|
||||
if (result.body.code === 200) {
|
||||
// 注册成功后立即写入运行时状态,后续请求直接使用新令牌(无需重启)
|
||||
const cookieObj = cookieToJson(result.cookie.join(';'))
|
||||
if (cookieObj.MUSIC_A) {
|
||||
runtimeState.anonymousToken = cookieObj.MUSIC_A
|
||||
}
|
||||
result = {
|
||||
status: 200,
|
||||
body: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { default: axios } = require('axios')
|
||||
const encrypt = require('../util/crypto')
|
||||
const { APP_CONF } = require('../util/config.json')
|
||||
const runtimeState = require('../util/runtimeState')
|
||||
|
||||
const generateNonce = () => {
|
||||
let nonce = ''
|
||||
@@ -63,6 +64,9 @@ module.exports = async (query, request) => {
|
||||
throw new Error('xeapi public key response missing sk')
|
||||
}
|
||||
|
||||
// 获取成功后立即写入运行时状态,供 util/request.js 的 xeapi 请求使用
|
||||
runtimeState.xeapiPublicKey = { ...publicKey, deviceId }
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
// 获取客户端歌曲下载链接 - v1
|
||||
// 此版本不再采用 br 作为音质区分的标准
|
||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清臻音), vivid(臻音全景声), jymaster(超清母带), sky(沉浸环绕声) 进行音质判断
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
const { cookieToJson } = require('../util/index.js')
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
id: query.id,
|
||||
immerseType: 'c51',
|
||||
level: query.level,
|
||||
}
|
||||
return request(`/api/song/enhance/download/url/v1`, data, createOption(query))
|
||||
const options = createOption(query)
|
||||
if (query.level === 'vivid') {
|
||||
const cookie = options.cookie
|
||||
options.cookie = {
|
||||
...(typeof cookie === 'string' ? cookieToJson(cookie) : cookie),
|
||||
os: 'android',
|
||||
appver: '9.5.61',
|
||||
}
|
||||
}
|
||||
return request(`/api/song/enhance/download/url/v1`, data, options)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 插播相似歌曲
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
positionCode: 'toolBarRcmdSong',
|
||||
resourceId: query.id,
|
||||
resourceType: 'song',
|
||||
}
|
||||
return request(
|
||||
`/api/link/position/show/resource`,
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
+14
-7
@@ -1,11 +1,12 @@
|
||||
// 歌曲链接 - v1
|
||||
// 此版本不再采用 br 作为音质区分的标准
|
||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清臻音), vivid(臻音全景声), jymaster(超清母带), sky(沉浸环绕声) 进行音质判断
|
||||
// 当unblock为true时, 会尝试使用unblockmusic-utils进行解锁, 同时音质设置不会生效, 但仍然为必须传入参数
|
||||
// 当level为sky时, 可通过 immerseType 选择沉浸声类型, 支持 c51(c51类型)、ste(环绕立体声类型)、aac(aac类型), 默认为 c51
|
||||
// 当level为sky时, 可通过 immerseType 选择沉浸声类型, 支持 c512(新版c51类型)、ste2(新版环绕立体声类型)、aac2(新版aac类型)、c51(c51类型)、ste(环绕立体声类型)、aac(aac类型), 默认为 c51
|
||||
|
||||
const logger = require('../util/logger.js')
|
||||
const createOption = require('../util/option.js')
|
||||
const { cookieToJson } = require('../util/index.js')
|
||||
module.exports = async (query, request) => {
|
||||
const {
|
||||
matchID,
|
||||
@@ -16,6 +17,7 @@ module.exports = async (query, request) => {
|
||||
level: query.level,
|
||||
encodeType: 'flac',
|
||||
}
|
||||
const options = createOption(query, 'xeapi')
|
||||
if (query.unblock === 'true') {
|
||||
try {
|
||||
const result = await matchID(query.id, query.source)
|
||||
@@ -54,9 +56,14 @@ module.exports = async (query, request) => {
|
||||
if (data.level == 'sky') {
|
||||
data.immerseType = query.immerseType || 'c51'
|
||||
}
|
||||
return request(
|
||||
`/api/song/enhance/player/url/v1`,
|
||||
data,
|
||||
createOption(query, 'xeapi'),
|
||||
)
|
||||
if (data.level == 'vivid') {
|
||||
data.encodeType = 'mp3'
|
||||
const cookie = options.cookie
|
||||
options.cookie = {
|
||||
...(typeof cookie === 'string' ? cookieToJson(cookie) : cookie),
|
||||
os: 'android',
|
||||
appver: '9.5.61',
|
||||
}
|
||||
}
|
||||
return request(`/api/song/enhance/player/url/v1`, data, options)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
// 获取客户端歌曲下载链接 - v1
|
||||
// 此版本不再采用 br 作为音质区分的标准
|
||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清臻音), vivid(臻音全景声), jymaster(超清母带), sky(沉浸环绕声) 进行音质判断
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
const { cookieToJson } = require('../util/index.js')
|
||||
module.exports = async (query, request) => {
|
||||
const data = {
|
||||
id: query.id,
|
||||
immerseType: 'c51',
|
||||
level: query.level,
|
||||
}
|
||||
const options = createOption(query)
|
||||
if (query.level === 'vivid') {
|
||||
const cookie = options.cookie
|
||||
options.cookie = {
|
||||
...(typeof cookie === 'string' ? cookieToJson(cookie) : cookie),
|
||||
os: 'android',
|
||||
appver: '9.5.61',
|
||||
}
|
||||
}
|
||||
const response = await request(
|
||||
`/api/song/enhance/download/url/v1`,
|
||||
data,
|
||||
createOption(query),
|
||||
options,
|
||||
)
|
||||
let url = response?.body?.data?.[0]?.url
|
||||
let url = response?.body?.data?.url || response?.body?.data?.[0]?.url
|
||||
|
||||
if (!url) {
|
||||
const fallbackData = {
|
||||
@@ -25,10 +35,13 @@ module.exports = async (query, request) => {
|
||||
if (query.level === 'sky') {
|
||||
fallbackData.immerseType = 'c51'
|
||||
}
|
||||
if (query.level === 'vivid') {
|
||||
fallbackData.encodeType = 'mp3'
|
||||
}
|
||||
const fallback = await request(
|
||||
`/api/song/enhance/player/url/v1`,
|
||||
fallbackData,
|
||||
createOption(query),
|
||||
options,
|
||||
)
|
||||
url = fallback?.body?.data?.[0]?.url
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// 歌曲百科
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
module.exports = (query, request) => {
|
||||
const extJson = {
|
||||
states: {
|
||||
playingResource: {
|
||||
current: query.id,
|
||||
scene: 'songWiki',
|
||||
},
|
||||
},
|
||||
}
|
||||
const data = {
|
||||
extJson: JSON.stringify(extJson),
|
||||
positionCode: 'songWikiMainPosition',
|
||||
}
|
||||
return request(
|
||||
`/api/link/page/parent/relation/construct/info`,
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@neteasecloudmusicapienhanced/api",
|
||||
"version": "4.40.0",
|
||||
"version": "4.40.1",
|
||||
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
|
||||
"scripts": {
|
||||
"dev": "nodemon app.js",
|
||||
@@ -11,9 +11,9 @@
|
||||
"prepare": "husky install",
|
||||
"docs:format": "node scripts/format-docs.js",
|
||||
"docs:check": "node scripts/format-docs.js --check",
|
||||
"pkgwin": "pkg . -t node18-win-x64 -C GZip -o precompiled/app",
|
||||
"pkglinux": "pkg . -t node18-linux-x64 -C GZip -o precompiled/app",
|
||||
"pkgmacos": "pkg . -t node18-macos-x64 -C GZip -o precompiled/app"
|
||||
"pkgwin": "node scripts/build-pkg.js . -t node18-win-x64 -C GZip -o precompiled/app",
|
||||
"pkglinux": "node scripts/build-pkg.js . -t node18-linux-x64 -C GZip -o precompiled/app",
|
||||
"pkgmacos": "node scripts/build-pkg.js . -t node18-macos-x64 -C GZip -o precompiled/app"
|
||||
},
|
||||
"bin": "./app.js",
|
||||
"pkg": {
|
||||
@@ -75,15 +75,15 @@
|
||||
"data"
|
||||
],
|
||||
"dependencies": {
|
||||
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.4.0",
|
||||
"axios": "^1.19.0",
|
||||
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.4.4",
|
||||
"axios": "^1.20.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1",
|
||||
"express-fileupload": "^1.5.2",
|
||||
"gzip": "^0.1.0",
|
||||
"jsdom": "^24.1.3",
|
||||
"music-metadata": "^11.14.0",
|
||||
"music-metadata": "^11.15.0",
|
||||
"node-forge": "^1.4.0",
|
||||
"pac-proxy-agent": "^7.2.0",
|
||||
"qrcode": "^1.5.4",
|
||||
@@ -93,19 +93,19 @@
|
||||
"yargs": "^18.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.6",
|
||||
"@eslint/eslintrc": "^3.3.7",
|
||||
"@eslint/js": "^9.39.5",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/express-fileupload": "^1.5.1",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "25.9.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.66.0",
|
||||
"@typescript-eslint/parser": "^8.66.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.69.0",
|
||||
"@typescript-eslint/parser": "^8.69.0",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-html": "^8.1.4",
|
||||
"eslint-plugin-html": "^8.2.0",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"globals": "^17.9.0",
|
||||
"globals": "^17.12.0",
|
||||
"husky": "^9.1.7",
|
||||
"intelli-espower-loader": "^1.1.0",
|
||||
"lint-staged": "^16.4.0",
|
||||
|
||||
Generated
+171
-158
@@ -9,11 +9,11 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@neteasecloudmusicapienhanced/unblockmusic-utils':
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
axios:
|
||||
specifier: ^1.19.0
|
||||
version: 1.19.0
|
||||
specifier: ^1.20.0
|
||||
version: 1.20.0
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@@ -33,8 +33,8 @@ importers:
|
||||
specifier: ^24.1.3
|
||||
version: 24.1.3
|
||||
music-metadata:
|
||||
specifier: ^11.14.0
|
||||
version: 11.14.0
|
||||
specifier: ^11.15.0
|
||||
version: 11.15.0
|
||||
node-forge:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
@@ -58,8 +58,8 @@ importers:
|
||||
version: 18.1.0
|
||||
devDependencies:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.3.6
|
||||
version: 3.3.6
|
||||
specifier: ^3.3.7
|
||||
version: 3.3.7
|
||||
'@eslint/js':
|
||||
specifier: ^9.39.5
|
||||
version: 9.39.5
|
||||
@@ -76,11 +76,11 @@ importers:
|
||||
specifier: 25.9.2
|
||||
version: 25.9.2
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.66.0
|
||||
version: 8.66.0(@typescript-eslint/[email protected]6.0([email protected])([email protected]))([email protected])([email protected])
|
||||
specifier: ^8.69.0
|
||||
version: 8.69.0(@typescript-eslint/[email protected]9.0([email protected])([email protected]))([email protected])([email protected])
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.66.0
|
||||
version: 8.66.0([email protected])([email protected])
|
||||
specifier: ^8.69.0
|
||||
version: 8.69.0([email protected])([email protected])
|
||||
eslint:
|
||||
specifier: ^9.39.5
|
||||
version: 9.39.5
|
||||
@@ -88,14 +88,14 @@ importers:
|
||||
specifier: ^10.1.8
|
||||
version: 10.1.8([email protected])
|
||||
eslint-plugin-html:
|
||||
specifier: ^8.1.4
|
||||
version: 8.1.4
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.5.6
|
||||
version: 5.5.6([email protected]([email protected]))([email protected])([email protected])
|
||||
globals:
|
||||
specifier: ^17.9.0
|
||||
version: 17.9.0
|
||||
specifier: ^17.12.0
|
||||
version: 17.12.0
|
||||
husky:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
@@ -203,8 +203,8 @@ packages:
|
||||
resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@eslint/[email protected].6':
|
||||
resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==}
|
||||
'@eslint/[email protected].7':
|
||||
resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@eslint/[email protected]':
|
||||
@@ -250,14 +250,14 @@ packages:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
'@jridgewell/sourcemap-codec@1.6.0':
|
||||
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
|
||||
|
||||
'@jridgewell/[email protected]':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@neteasecloudmusicapienhanced/[email protected].0':
|
||||
resolution: {integrity: sha512-KUDdBEe3IK4tPSt2UHdFweYbhoZUYGBxbt8FuqQu5bZdpymfpecZxLf+a8G/JvIYHph4H+KxYlq6+MD9oXCfjg==}
|
||||
'@neteasecloudmusicapienhanced/[email protected].4':
|
||||
resolution: {integrity: sha512-ErQMlbG9LK2TuHr1ZVVsaHAyOkmY3MEDQ5/thtnEtX68YADkmcYt7ddgfnEVXGfGNCdvCV4k8viG2IdX9Qhoeg==}
|
||||
hasBin: true
|
||||
|
||||
'@nodelib/[email protected]':
|
||||
@@ -335,63 +335,63 @@ packages:
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.66.0
|
||||
'@typescript-eslint/parser': ^8.69.0
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==}
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@unblockneteasemusic/[email protected]':
|
||||
@@ -448,8 +448,8 @@ packages:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-regex@6.2.2:
|
||||
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
|
||||
ansi-regex@6.3.0:
|
||||
resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]:
|
||||
@@ -519,8 +519,8 @@ packages:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
axios@1.19.0:
|
||||
resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==}
|
||||
axios@1.20.0:
|
||||
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
@@ -694,8 +694,8 @@ packages:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
content-type@2.0.0:
|
||||
resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
|
||||
content-type@2.1.0:
|
||||
resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -725,6 +725,7 @@ packages:
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||
deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained.
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
|
||||
@@ -1000,8 +1001,8 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: '>=7.0.0'
|
||||
|
||||
eslint-plugin-html@8.1.4:
|
||||
resolution: {integrity: sha512-Eno3oPEj3s6AhvDJ5zHhnHPDvXp6LNFXuy3w51fNebOKYuTrfjOHUGwP+mOrGFpR6eOJkO1xkB8ivtbfMjbMjg==}
|
||||
eslint-plugin-html@8.2.0:
|
||||
resolution: {integrity: sha512-L6F37uij4uq3cZah46BawXeZlZYW9IfFeep7BwkuxBZA++BbihvEpoJPhq0NpCruPRzVkG/tpgvQYM53Re4pVA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1037,6 +1038,7 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
jiti: '*'
|
||||
@@ -1151,8 +1153,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
||||
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||
[email protected].3:
|
||||
resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
@@ -1311,8 +1313,8 @@ packages:
|
||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
globals@17.9.0:
|
||||
resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==}
|
||||
globals@17.12.0:
|
||||
resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1425,8 +1427,8 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
[email protected].6:
|
||||
resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
|
||||
[email protected].8:
|
||||
resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1457,8 +1459,8 @@ packages:
|
||||
resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ip-address@10.4.0:
|
||||
resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==}
|
||||
ip-address@10.7.0:
|
||||
resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1625,8 +1627,8 @@ packages:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
|
||||
[email protected].2:
|
||||
resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
@@ -1810,8 +1812,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==}
|
||||
|
||||
[email protected]4.0:
|
||||
resolution: {integrity: sha512-RyOSq98kuVfXB1emJ+NjBF0av8Ph3oBuqNy+Z5sFFfLhjYrkBQEB53V8u+U0RNTVwNo20WoPUwNkfKwZfrOqmQ==}
|
||||
[email protected]5.0:
|
||||
resolution: {integrity: sha512-TN+kO1/oOc8UzDW5N3vSDncBcv9WyNnQQe/NFMcFWq6G1+zVeYUkGvkYpQ/S8wb8vKtUD7i78dIaY0cv+cmPRA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1824,9 +1826,9 @@ packages:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
negotiator@1.1.0:
|
||||
resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==}
|
||||
@@ -1835,8 +1837,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
|
||||
[email protected]4.0:
|
||||
resolution: {integrity: sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==}
|
||||
[email protected]6.0:
|
||||
resolution: {integrity: sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1864,8 +1866,8 @@ packages:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
[email protected]4:
|
||||
resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==}
|
||||
[email protected]7:
|
||||
resolution: {integrity: sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
@@ -1991,8 +1993,8 @@ packages:
|
||||
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
[email protected].5:
|
||||
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
|
||||
[email protected].7:
|
||||
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2126,6 +2128,10 @@ packages:
|
||||
resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
@@ -2359,8 +2365,8 @@ packages:
|
||||
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
[email protected].9:
|
||||
resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==}
|
||||
[email protected].10:
|
||||
resolution: {integrity: sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2497,8 +2503,8 @@ packages:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
[email protected].0:
|
||||
resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2729,8 +2735,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
[email protected].2:
|
||||
resolution: {integrity: sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==}
|
||||
[email protected].3:
|
||||
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -2890,7 +2896,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@eslint/[email protected].6':
|
||||
'@eslint/[email protected].7':
|
||||
dependencies:
|
||||
ajv: 6.15.0
|
||||
debug: 4.4.3
|
||||
@@ -2898,7 +2904,7 @@ snapshots:
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.2
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.3.1
|
||||
js-yaml: 4.3.2
|
||||
minimatch: 3.1.5
|
||||
strip-json-comments: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
@@ -2940,22 +2946,22 @@ snapshots:
|
||||
|
||||
'@jridgewell/[email protected]':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/[email protected]': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
'@jridgewell/sourcemap-codec@1.6.0': {}
|
||||
|
||||
'@jridgewell/[email protected]':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@neteasecloudmusicapienhanced/[email protected].0':
|
||||
'@neteasecloudmusicapienhanced/[email protected].4':
|
||||
dependencies:
|
||||
'@unblockneteasemusic/server': 0.28.0
|
||||
axios: 1.19.0
|
||||
axios: 1.20.0
|
||||
dotenv: 17.4.2
|
||||
express: 4.22.2
|
||||
https: 1.0.0
|
||||
@@ -2973,7 +2979,7 @@ snapshots:
|
||||
'@nodelib/[email protected]':
|
||||
dependencies:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.20.1
|
||||
fastq: 1.20.3
|
||||
|
||||
'@pkgjs/[email protected]':
|
||||
optional: true
|
||||
@@ -3047,57 +3053,57 @@ snapshots:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 25.9.2
|
||||
|
||||
'@typescript-eslint/[email protected]6.0(@typescript-eslint/[email protected]6.0([email protected])([email protected]))([email protected])([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0(@typescript-eslint/[email protected]9.0([email protected])([email protected]))([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.66.0([email protected])([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.66.0
|
||||
'@typescript-eslint/type-utils': 8.66.0([email protected])([email protected])
|
||||
'@typescript-eslint/utils': 8.66.0([email protected])([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.66.0
|
||||
'@typescript-eslint/parser': 8.69.0([email protected])([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.69.0
|
||||
'@typescript-eslint/type-utils': 8.69.0([email protected])([email protected])
|
||||
'@typescript-eslint/utils': 8.69.0([email protected])([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.69.0
|
||||
eslint: 9.39.5
|
||||
ignore: 7.0.6
|
||||
ignore: 7.0.8
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.5.0([email protected])
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected]6.0([email protected])([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.66.0
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/typescript-estree': 8.66.0([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.66.0
|
||||
'@typescript-eslint/scope-manager': 8.69.0
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
'@typescript-eslint/typescript-estree': 8.69.0([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.69.0
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.5
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected]6.0([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.66.0([email protected])
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/tsconfig-utils': 8.69.0([email protected])
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
debug: 4.4.3
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/visitor-keys': 8.66.0
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
'@typescript-eslint/visitor-keys': 8.69.0
|
||||
|
||||
'@typescript-eslint/[email protected]6.0([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0([email protected])':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/[email protected]6.0([email protected])([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/typescript-estree': 8.66.0([email protected])
|
||||
'@typescript-eslint/utils': 8.66.0([email protected])([email protected])
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
'@typescript-eslint/typescript-estree': 8.69.0([email protected])
|
||||
'@typescript-eslint/utils': 8.69.0([email protected])([email protected])
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.5
|
||||
ts-api-utils: 2.5.0([email protected])
|
||||
@@ -3105,14 +3111,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected]6.0': {}
|
||||
'@typescript-eslint/[email protected]9.0': {}
|
||||
|
||||
'@typescript-eslint/[email protected]6.0([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.66.0([email protected])
|
||||
'@typescript-eslint/tsconfig-utils': 8.66.0([email protected])
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/visitor-keys': 8.66.0
|
||||
'@typescript-eslint/project-service': 8.69.0([email protected])
|
||||
'@typescript-eslint/tsconfig-utils': 8.69.0([email protected])
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
'@typescript-eslint/visitor-keys': 8.69.0
|
||||
debug: 4.4.3
|
||||
minimatch: 10.2.6
|
||||
semver: 7.8.5
|
||||
@@ -3122,20 +3128,20 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected]6.0([email protected])([email protected])':
|
||||
'@typescript-eslint/[email protected]9.0([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.66.0
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/typescript-estree': 8.66.0([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.69.0
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
'@typescript-eslint/typescript-estree': 8.69.0([email protected])
|
||||
eslint: 9.39.5
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected]6.0':
|
||||
'@typescript-eslint/[email protected]9.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.66.0
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@unblockneteasemusic/[email protected]':
|
||||
@@ -3152,7 +3158,7 @@ snapshots:
|
||||
[email protected]:
|
||||
dependencies:
|
||||
mime-types: 3.0.2
|
||||
negotiator: 1.0.0
|
||||
negotiator: 1.1.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -3187,7 +3193,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
ansi-regex@6.2.2: {}
|
||||
ansi-regex@6.3.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -3252,7 +3258,7 @@ snapshots:
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.1.0
|
||||
|
||||
axios@1.19.0:
|
||||
axios@1.20.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.6
|
||||
@@ -3298,12 +3304,12 @@ snapshots:
|
||||
[email protected]:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 2.0.0
|
||||
content-type: 2.1.0
|
||||
debug: 4.4.3
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.3
|
||||
on-finished: 2.4.1
|
||||
qs: 6.15.3
|
||||
qs: 6.16.0
|
||||
raw-body: 3.0.2
|
||||
type-is: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
@@ -3465,7 +3471,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
content-type@2.0.0: {}
|
||||
content-type@2.1.0: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -3846,7 +3852,7 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 9.39.5
|
||||
|
||||
eslint-plugin-html@8.1.4:
|
||||
eslint-plugin-html@8.2.0:
|
||||
dependencies:
|
||||
htmlparser2: 10.1.0
|
||||
|
||||
@@ -3877,7 +3883,7 @@ snapshots:
|
||||
'@eslint/config-array': 0.21.2
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.6
|
||||
'@eslint/eslintrc': 3.3.7
|
||||
'@eslint/js': 9.39.5
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@humanfs/node': 0.16.8
|
||||
@@ -4058,7 +4064,7 @@ snapshots:
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.15.3
|
||||
qs: 6.16.0
|
||||
range-parser: 1.3.0
|
||||
router: 2.2.0
|
||||
send: 1.2.1
|
||||
@@ -4093,13 +4099,13 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].1:
|
||||
[email protected].3:
|
||||
dependencies:
|
||||
reusify: 1.1.0
|
||||
|
||||
[email protected]([email protected].5):
|
||||
[email protected]([email protected].7):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.5
|
||||
picomatch: 4.0.7
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4279,7 +4285,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
globals@17.9.0: {}
|
||||
globals@17.12.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4391,7 +4397,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].6: {}
|
||||
[email protected].8: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4421,7 +4427,7 @@ snapshots:
|
||||
from2: 2.3.0
|
||||
p-is-promise: 3.0.0
|
||||
|
||||
ip-address@10.4.0: {}
|
||||
ip-address@10.7.0: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -4582,7 +4588,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].1:
|
||||
[email protected].2:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -4596,7 +4602,7 @@ snapshots:
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
is-potential-custom-element-name: 1.0.1
|
||||
nwsapi: 2.2.24
|
||||
nwsapi: 2.2.27
|
||||
parse5: 7.3.0
|
||||
rrweb-cssom: 0.7.1
|
||||
saxes: 6.0.0
|
||||
@@ -4607,7 +4613,7 @@ snapshots:
|
||||
whatwg-encoding: 3.1.1
|
||||
whatwg-mimetype: 4.0.0
|
||||
whatwg-url: 14.2.0
|
||||
ws: 8.21.2
|
||||
ws: 8.21.3
|
||||
xml-name-validator: 5.0.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@@ -4648,9 +4654,9 @@ snapshots:
|
||||
dependencies:
|
||||
commander: 14.0.3
|
||||
listr2: 9.0.5
|
||||
picomatch: 4.0.5
|
||||
picomatch: 4.0.7
|
||||
string-argv: 0.3.2
|
||||
tinyexec: 1.3.0
|
||||
tinyexec: 1.3.1
|
||||
yaml: 2.9.0
|
||||
|
||||
[email protected]:
|
||||
@@ -4759,7 +4765,7 @@ snapshots:
|
||||
glob: 10.5.0
|
||||
he: 1.2.0
|
||||
is-path-inside: 3.0.3
|
||||
js-yaml: 4.3.1
|
||||
js-yaml: 4.3.2
|
||||
log-symbols: 4.1.0
|
||||
minimatch: 9.0.9
|
||||
ms: 2.1.3
|
||||
@@ -4787,11 +4793,11 @@ snapshots:
|
||||
once: 1.4.0
|
||||
readable-stream: 3.6.2
|
||||
|
||||
[email protected]4.0:
|
||||
[email protected]5.0:
|
||||
dependencies:
|
||||
'@borewit/text-codec': 0.2.2
|
||||
'@tokenizer/token': 0.3.0
|
||||
content-type: 2.0.0
|
||||
content-type: 2.1.0
|
||||
debug: 4.4.3
|
||||
file-type: 21.3.4
|
||||
media-typer: 2.0.0
|
||||
@@ -4808,13 +4814,15 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
negotiator@1.1.0:
|
||||
dependencies:
|
||||
content-type: 2.1.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]4.0:
|
||||
[email protected]6.0:
|
||||
dependencies:
|
||||
semver: 7.8.5
|
||||
|
||||
@@ -4844,7 +4852,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]4: {}
|
||||
[email protected]7: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -4976,7 +4984,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].5: {}
|
||||
[email protected].7: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -5123,7 +5131,7 @@ snapshots:
|
||||
minimist: 1.2.8
|
||||
mkdirp-classic: 0.5.3
|
||||
napi-build-utils: 1.0.2
|
||||
node-abi: 3.94.0
|
||||
node-abi: 3.96.0
|
||||
pump: 3.0.4
|
||||
rc: 1.2.8
|
||||
simple-get: 4.0.1
|
||||
@@ -5177,6 +5185,11 @@ snapshots:
|
||||
es-define-property: 1.0.1
|
||||
side-channel: 1.1.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
es-define-property: 1.0.1
|
||||
side-channel: 1.1.1
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -5481,13 +5494,13 @@ snapshots:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
socks: 2.8.9
|
||||
socks: 2.8.10
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected].9:
|
||||
[email protected].10:
|
||||
dependencies:
|
||||
ip-address: 10.4.0
|
||||
ip-address: 10.7.0
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
[email protected]:
|
||||
@@ -5598,7 +5611,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
ansi-regex: 6.2.2
|
||||
ansi-regex: 6.3.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -5643,12 +5656,12 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
[email protected].0: {}
|
||||
[email protected].1: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
fdir: 6.5.0([email protected].5)
|
||||
picomatch: 4.0.5
|
||||
fdir: 6.5.0([email protected].7)
|
||||
picomatch: 4.0.7
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -5712,7 +5725,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
content-type: 2.0.0
|
||||
content-type: 2.1.0
|
||||
media-typer: 1.1.1
|
||||
mime-types: 3.0.2
|
||||
|
||||
@@ -5909,7 +5922,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].2: {}
|
||||
[email protected].3: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
allowBuilds:
|
||||
core-js: true
|
||||
es5-ext: true
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- '@neteasecloudmusicapienhanced/[email protected]'
|
||||
+52
-8
@@ -1304,14 +1304,14 @@ tags: 歌单标签
|
||||
|
||||
**必选参数 :** `id` : 音乐 id
|
||||
`level`: 播放音质等级, 分为 `standard` => `标准`,`higher` => `较高`, `exhigh`=>`极高`,
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清环绕声`, `sky` => `沉浸环绕声`, `dolby` => `杜比全景声`, `jymaster` => `超清母带`
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清臻音`, `dolby` => `杜比全景声`, `vivid` => `臻音全景声`, `jymaster` => `超清母带`, `sky` => `沉浸环绕声`
|
||||
`unblock`: 是否使用使用歌曲解锁, 分为`true`和`false`
|
||||
|
||||
**可选参数 :** `immerseType`: 沉浸声环绕声类型, 分为 `c51` => `c51类型`, `ste` => `环绕立体声类型`, `aac` => `aac类型`, 仅在 `level=sky` 时生效, 默认为 `c51`
|
||||
**可选参数 :** `immerseType`: 沉浸声环绕声类型, 分为`c512` => `新版c51类型`, `ste2` => `新版环绕立体声类型`, `aac2` => `新版aac类型`, `c51` => `c51类型`, `ste` => `环绕立体声类型`, `aac` => `aac类型`, 仅在 `level=sky` 时生效, 默认为 `c51`
|
||||
|
||||
**接口地址 :** `/song/url/v1`
|
||||
|
||||
**调用例子 :** `/song/url/v1?id=1969519579&level=exhigh` `/song/url/v1?id=1969519579,33894312&level=lossless` `/song/url/v1?id=1969519579&level=sky&immerseType=ste`
|
||||
**调用例子 :** `/song/url/v1?id=1969519579&level=exhigh` `/song/url/v1?id=1969519579,33894312&level=lossless` `/song/url/v1?id=1969519579&level=sky&immerseType=ste` `/song/url/v1?id=1969519579&level=vivid`
|
||||
|
||||
说明:`杜比全景声`音质需要设备支持,不同的设备可能会返回不同码率的 url。cookie 需要传入`os=pc`保证返回正常码率的 url。
|
||||
|
||||
@@ -1321,7 +1321,7 @@ tags: 歌单标签
|
||||
|
||||
**必选参数 :** `id` : 音乐 id
|
||||
`level`: 播放音质等级, 分为 `standard` => `标准`,`higher` => `较高`, `exhigh`=>`极高`,
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清环绕声`, `sky` => `沉浸环绕声`, `dolby` => `杜比全景声`, `jymaster` => `超清母带`
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清臻音`, `dolby` => `杜比全景声`, `vivid` => `臻音全景声`, `jymaster` => `超清母带`, `sky` => `沉浸环绕声`
|
||||
`unblock`: 是否使用使用歌曲解锁, 分为`true`和`false`
|
||||
|
||||
**接口地址 :** `/song/url/v1/302`
|
||||
@@ -2473,6 +2473,18 @@ privilege:权限相关信息
|
||||
|
||||
喜欢成功则返回数据的 code 为 200, 其余为失败
|
||||
|
||||
### 喜欢音乐 - 新版
|
||||
|
||||
说明: 调用此接口 , 传入音乐 id, 可喜欢该音乐
|
||||
|
||||
**必选参数 :** `id`: 歌曲 id
|
||||
|
||||
**可选参数 :** `like`: 布尔值 , 默认为 true 即喜欢 , 若传 false, 则取消喜欢
|
||||
|
||||
**接口地址 :** `/like/v1`
|
||||
|
||||
**调用例子 :** `/like/v1?id=347230`
|
||||
|
||||
### 喜欢音乐列表
|
||||
|
||||
说明 : 调用此接口 , 传入用户 id, 可获取已喜欢音乐 id 列表(id 数组)
|
||||
@@ -4138,6 +4150,16 @@ type='1009' 获取其 id, 如`/search?keywords= 代码时间 &type=1009`
|
||||
|
||||
**调用例子:** `/song/wiki/summary?id=1958384591`
|
||||
|
||||
### 音乐百科
|
||||
|
||||
说明: 调用此接口可以获取歌曲的音乐百科信息
|
||||
|
||||
**接口地址:** `/song/wiki/info`
|
||||
|
||||
**必选参数:** `id`: 歌曲 ID
|
||||
|
||||
**调用例子:** `/song/wiki/info?id=1958384591`
|
||||
|
||||
### 乐谱列表
|
||||
|
||||
说明: 调用此接口可以获取歌曲的乐谱列表
|
||||
@@ -4653,7 +4675,7 @@ qrCodeStatus:20,detailReason:0 验证成功 qrCodeStatus:21,detailReason:0 二
|
||||
|
||||
### 歌曲音质详情
|
||||
|
||||
说明: 调用此接口获取歌曲各个音质的文件信息,与 `获取歌曲详情` 接口相比,多出 `高清环绕声`、`沉浸环绕声`、`超清母带`等音质的信息
|
||||
说明: 调用此接口获取歌曲各个音质的文件信息,与 `获取歌曲详情` 接口相比,多出`高清臻音`、`杜比全景声`、`臻音全景声`、`超清母带`、`沉浸环绕声`等音质的信息
|
||||
|
||||
**必选参数:**
|
||||
|
||||
@@ -4690,11 +4712,11 @@ qrCodeStatus:20,detailReason:0 验证成功 qrCodeStatus:21,detailReason:0 二
|
||||
|
||||
**必选参数:**
|
||||
|
||||
`mode`: 模式 (aidj, DEFAULT, FAMILIAR, EXPLORE, SCENE_RCMD)
|
||||
`mode`: 模式 (`DEFAULT`: 默认模式,`FAMILIAR`: 熟悉模式,`EXPLORE`: 探索模式,`SCENE_RCMD`: 场景模式, 需要传入`submode`参数,`PUZZLE_MODE_RCMD`: 拼图模式)
|
||||
|
||||
**可选参数:**
|
||||
|
||||
`submode`: 当 mode 为 SCENE_RCMD 是可为 ( EXERCISE, FOCUS, NIGHT_EMO )
|
||||
`submode`: 当 mode 为 SCENE_RCMD 时传入 ( `EXERCISE`: 运动, `FOCUS`: 专注, `NIGHT_EMO`: 伤感, `SLEEP_HELP`: 助眠, `RELAX`: 放松, `CHEERFUL`: 欢快, `LYRICAL`: 抒情, `CURE`: 治愈, `SWEET`: 情歌, `RHYTHM_BLUES`: R&B, `RAINY`: 雨天, `GAMES`: 游戏, `RAP`: 说唱, `K_POP`: K-Pop, `ORIGINAL_MUSICIAL`: 宝藏原创, `ELECTRONIC`: 电音, `COMMUTE`: 出行, `TAKE_SHOWER`: 洗澡, `COFFEE_SHOP`: 咖啡馆, `ROCK`: 摇滚, `INSPIRATIONAL`: 励志, `CHINESE`: 华语, `ENGLISH`: 欧美, `YUEYU`: 粤语, `MANYAO`: 慢摇DJ, `JINGDIAN`: 经典, `LIGHT`: 轻音乐, `GUOFENG`: 国风, `FOLK`: 民谣, `ACG`: 二次元, `GUDIAN`: 古典, `JAZZ`: 爵士, `JAPANESE`: 日语, `GLOBAL`: 全球, `FRANCH`: 法语, `BLUE`: 蓝调, `DANCE`: 舞蹈, `LATIN`: 拉丁, `PUNK`: 放克, `COUNTRY`: 乡村乐, `MUSICAL`: 音乐剧, `YINGSHI`: 影视)
|
||||
|
||||
**接口地址:** `/personal/fm/mode`
|
||||
|
||||
@@ -4774,7 +4796,7 @@ bitrate = Math.floor(br / 1000)
|
||||
|
||||
**必选参数 :** `id` : 音乐 id
|
||||
`level`: 播放音质等级, 分为 `standard` => `标准`,`higher` => `较高`, `exhigh`=>`极高`,
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清环绕声`, `sky` => `沉浸环绕声`, `dolby` => `杜比全景声`, `jymaster` => `超清母带`
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清臻音`, `dolby` => `杜比全景声`, `vivid` => `臻音全景声`, `jymaster` => `超清母带`, `sky` => `沉浸环绕声`
|
||||
|
||||
**接口地址 :** `/song/download/url/v1`
|
||||
|
||||
@@ -5834,6 +5856,28 @@ let data = encodeURIComponent(
|
||||
|
||||
**调用例子 :** `/device/kickoff?key=00ALDFGEXXXXXXXXXXXXXXXXX&captcha=1234`
|
||||
|
||||
### 上报设备中心设备名称
|
||||
|
||||
说明: 登录后调用此接口, 传入 `deviceName`, 可修改当前设备在网易云登录设备管理列表里显示的名称
|
||||
|
||||
**必选参数 :** `deviceName`: 期望展示的设备名称
|
||||
|
||||
`name`: `deviceName` 的兼容别名, 二选一即可
|
||||
|
||||
**接口地址 :** `/deviceinfo/center/upload`
|
||||
|
||||
**调用例子 :** `/deviceinfo/center/upload?deviceName=XXXXX`
|
||||
|
||||
### 插播相似歌曲
|
||||
|
||||
说明: 调用此接口, 传入歌曲 id, 可获取插播相似歌曲
|
||||
|
||||
**必选参数 :** `id`: 歌曲 id
|
||||
|
||||
**接口地址 :** `/song/simi/get`
|
||||
|
||||
**调用例子 :** `/song/simi/get?id=39227633`
|
||||
|
||||
## 离线访问此文档
|
||||
|
||||
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Invoke the pkg packager (cross-platform, independent of node_modules layout).
|
||||
*
|
||||
* Background: on Windows, pnpm links node_modules/pkg to
|
||||
* node_modules/.pnpm/pkg@<ver>/node_modules/pkg via a junction. Some CI
|
||||
* environments (pnpm 11 + Windows runner) may fail to create that link,
|
||||
* so executing node_modules/pkg/lib-es5/bin.js directly throws
|
||||
* MODULE_NOT_FOUND.
|
||||
*
|
||||
* This script first resolves pkg via require.resolve and falls back to
|
||||
* scanning the .pnpm physical directory, so pkg is found whether the
|
||||
* junction exists or not.
|
||||
*/
|
||||
const { spawnSync } = require('child_process')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
function resolvePkgBin() {
|
||||
try {
|
||||
return require.resolve('pkg/lib-es5/bin.js')
|
||||
} catch (err) {
|
||||
// require.resolve failed (e.g. missing node_modules/pkg link): scan .pnpm
|
||||
const pnpmDir = path.join(process.cwd(), 'node_modules', '.pnpm')
|
||||
if (fs.existsSync(pnpmDir)) {
|
||||
const candidates = fs
|
||||
.readdirSync(pnpmDir)
|
||||
.filter((dir) => dir.startsWith('pkg@'))
|
||||
.map((dir) =>
|
||||
path.join(pnpmDir, dir, 'node_modules', 'pkg', 'lib-es5', 'bin.js'),
|
||||
)
|
||||
.filter((file) => fs.existsSync(file))
|
||||
if (candidates.length) {
|
||||
return candidates[0]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const bin = resolvePkgBin()
|
||||
if (!bin) {
|
||||
console.error(
|
||||
'[build-pkg] pkg executable not found; run `pnpm install --frozen-lockfile` first',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const result = spawnSync(process.execPath, [bin, ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
process.exit(result.status === null ? 1 : result.status)
|
||||
@@ -1,204 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const eventName = process.env.GITHUB_EVENT_NAME
|
||||
const eventPath = process.env.GITHUB_EVENT_PATH
|
||||
const token = process.env.GITHUB_TOKEN
|
||||
const repo = process.env.GITHUB_REPOSITORY
|
||||
const model = process.env.MODEL || 'opencode/deepseek-v4-flash-free'
|
||||
const botLogin = process.env.BOT_LOGIN || 'takanashi-hoshino-agent[bot]'
|
||||
|
||||
if (!eventPath || !token || !repo) {
|
||||
console.error(
|
||||
'Missing required env: GITHUB_EVENT_PATH / GITHUB_TOKEN / GITHUB_REPOSITORY',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
if (!process.env.OPENCODE_API_KEY) {
|
||||
console.error('Missing OPENCODE_API_KEY')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const event = JSON.parse(readFileSync(eventPath, 'utf8'))
|
||||
const [owner, repoName] = repo.split('/')
|
||||
const api = `https://api.github.com/repos/${owner}/${repoName}`
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'User-Agent': 'opencode-automation',
|
||||
}
|
||||
|
||||
async function get(path) {
|
||||
const res = await fetch(`${api}${path}`, { headers })
|
||||
if (!res.ok) throw new Error(`GET ${path}: HTTP ${res.status}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
async function post(path, body) {
|
||||
const res = await fetch(`${api}${path}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
const text = await res.text()
|
||||
if (!res.ok) throw new Error(`POST ${path}: HTTP ${res.status} ${text}`)
|
||||
return text ? JSON.parse(text) : null
|
||||
}
|
||||
|
||||
/** 无头模式跑 opencode,返回模型输出的文本 */
|
||||
function runOpencode(prompt, contextFile) {
|
||||
const args = ['run', '--auto', '-m', model, '--format', 'default', prompt]
|
||||
if (contextFile) args.push('-f', contextFile)
|
||||
console.log(
|
||||
`Running: opencode run --auto -m ${model} (prompt ${prompt.length} chars${contextFile ? ', context attached' : ''})`,
|
||||
)
|
||||
const res = spawnSync('opencode', args, {
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 128 * 1024 * 1024,
|
||||
})
|
||||
if (res.error) throw res.error
|
||||
if (res.status !== 0) {
|
||||
if (res.stderr) console.error('opencode stderr:\n' + res.stderr)
|
||||
throw new Error(`opencode exited with code ${res.status}`)
|
||||
}
|
||||
return (res.stdout || '').trim()
|
||||
}
|
||||
|
||||
/** 该 issue/PR 是否已有 bot 评论(用于去重,避免 synchronize 重复评论) */
|
||||
async function hasBotComment(target) {
|
||||
const comments = await get(`/issues/${target}/comments?per_page=100`)
|
||||
return comments.some((c) => c.user?.login === botLogin)
|
||||
}
|
||||
|
||||
async function comment(target, body) {
|
||||
if (!body) {
|
||||
console.log('No output from agent, skip comment')
|
||||
return
|
||||
}
|
||||
if (body.length > 60000) body = body.slice(0, 60000) + '\n\n_...(truncated)_'
|
||||
await post(`/issues/${target}/comments`, { body })
|
||||
console.log(`Commented on #${target}`)
|
||||
}
|
||||
|
||||
async function handleIssue(issue) {
|
||||
const n = issue.number
|
||||
console.log(`Handling issue #${n}`)
|
||||
if (await hasBotComment(n)) {
|
||||
console.log(`Already handled by bot, skip`)
|
||||
return
|
||||
}
|
||||
|
||||
const comments = await get(`/issues/${n}/comments?per_page=100`)
|
||||
const thread = comments
|
||||
.filter((c) => c.user?.login !== botLogin)
|
||||
.map(
|
||||
(c) =>
|
||||
`- **${c.user?.login}** (${c.created_at}):\n ${(c.body || '').replace(/\n/g, '\n ')}`,
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
const ctx = [
|
||||
`# Issue #${n}: ${issue.title}`,
|
||||
``,
|
||||
`- State: ${issue.state}`,
|
||||
`- Author: ${issue.user?.login}`,
|
||||
`- Created: ${issue.created_at}`,
|
||||
``,
|
||||
`## Body`,
|
||||
``,
|
||||
issue.body || '(empty)',
|
||||
...(thread ? [`\n## Comments\n\n${thread}`] : []),
|
||||
].join('\n')
|
||||
|
||||
const ctxFile = join(tmpdir(), 'opencode-issue-context.md')
|
||||
writeFileSync(ctxFile, ctx)
|
||||
|
||||
const prompt =
|
||||
process.env.ISSUE_PROMPT ||
|
||||
[
|
||||
`You are a maintainer bot for the ${repo} repository.`,
|
||||
`Read the attached file for the issue context.`,
|
||||
`If the issue has a clear fix or points to relevant docs, reply with documentation links and/or`,
|
||||
`error-handling guidance for code examples. If nothing actionable, reply with exactly "NO_ACTION".`,
|
||||
`Do NOT modify any files, do NOT commit, do NOT push. Output only the comment text.`,
|
||||
].join('\n')
|
||||
|
||||
const output = runOpencode(prompt, ctxFile)
|
||||
if (output === 'NO_ACTION' || output.startsWith('NO_ACTION')) {
|
||||
console.log('Agent decided no action needed')
|
||||
return
|
||||
}
|
||||
await comment(n, output)
|
||||
}
|
||||
|
||||
async function handlePullRequest(pr) {
|
||||
const n = pr.number
|
||||
console.log(`Handling PR #${n}`)
|
||||
if (await hasBotComment(n)) {
|
||||
console.log(`Already handled by bot, skip`)
|
||||
return
|
||||
}
|
||||
|
||||
let files = []
|
||||
try {
|
||||
files = await get(`/pulls/${n}/files?per_page=100`)
|
||||
} catch (e) {
|
||||
console.warn(`Failed to fetch changed files: ${e.message}`)
|
||||
}
|
||||
const fileList = files
|
||||
.map((f) => `- ${f.status} ${f.filename} (+${f.additions}/-${f.deletions})`)
|
||||
.join('\n')
|
||||
|
||||
const ctx = [
|
||||
`# PR #${n}: ${pr.title}`,
|
||||
``,
|
||||
`- State: ${pr.state}`,
|
||||
`- Author: ${pr.user?.login}`,
|
||||
`- Base: ${pr.base?.ref} <- Head: ${pr.head?.ref}`,
|
||||
`- Created: ${pr.created_at}`,
|
||||
`- Stats: +${pr.additions}/-${pr.deletions}, ${pr.changed_files} files`,
|
||||
``,
|
||||
`## Body`,
|
||||
``,
|
||||
pr.body || '(empty)',
|
||||
...(fileList ? [`\n## Changed files\n\n${fileList}`] : []),
|
||||
].join('\n')
|
||||
|
||||
const ctxFile = join(tmpdir(), 'opencode-pr-context.md')
|
||||
writeFileSync(ctxFile, ctx)
|
||||
|
||||
const prompt =
|
||||
process.env.PR_PROMPT ||
|
||||
[
|
||||
`You are a maintainer bot for the ${repo} repository.`,
|
||||
`Read the attached file for the PR context. The PR branch is already checked out.`,
|
||||
`Run \`git diff HEAD^1 HEAD\` (or \`git diff origin/${pr.base?.ref}...HEAD\`) to see the changes.`,
|
||||
`Review the code changes: point out bugs, risks and improvements, and suggest fixes for obvious issues.`,
|
||||
`If the PR looks good, say so concisely.`,
|
||||
`Do NOT modify any files, do NOT commit, do NOT push. Output only the review comment text.`,
|
||||
].join('\n')
|
||||
|
||||
const output = runOpencode(prompt, ctxFile)
|
||||
await comment(n, output)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Event: ${eventName} on ${repo}`)
|
||||
if (eventName === 'issues') {
|
||||
await handleIssue(event.issue)
|
||||
} else if (eventName === 'pull_request') {
|
||||
await handlePullRequest(event.pull_request)
|
||||
} else {
|
||||
console.log(`Unhandled event type: ${eventName}`)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(`Automation failed: ${e.message}`)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,11 +1,7 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const tmpPath = require('os').tmpdir()
|
||||
/** @type {import("express").Express & serverMod.ExpressExtension} */
|
||||
let app
|
||||
if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) {
|
||||
fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8')
|
||||
}
|
||||
const serverMod = require('./server')
|
||||
before(async () => {
|
||||
const generateConfig = require('./generateConfig')
|
||||
|
||||
+105
-41
@@ -7,9 +7,7 @@ const { PacProxyAgent } = require('pac-proxy-agent')
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const tunnel = require('tunnel')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const tmpPath = require('os').tmpdir()
|
||||
const runtimeState = require('./runtimeState')
|
||||
const {
|
||||
cookieToJson,
|
||||
cookieObjToString,
|
||||
@@ -25,26 +23,6 @@ const {
|
||||
getToken: antiCheatTokenV3,
|
||||
} = require('../module/register_checktoken_v3')
|
||||
|
||||
// 预先读取匿名token并缓存
|
||||
const anonymous_token = fs.readFileSync(
|
||||
path.resolve(tmpPath, './anonymous_token'),
|
||||
'utf-8',
|
||||
)
|
||||
const xeapiPublicKeyPath = path.resolve(tmpPath, './xeapi_public_key')
|
||||
let xeapi_public_key = null
|
||||
const loadXeapiPublicKey = () => {
|
||||
if (!xeapi_public_key && fs.existsSync(xeapiPublicKeyPath)) {
|
||||
try {
|
||||
xeapi_public_key = JSON.parse(
|
||||
fs.readFileSync(xeapiPublicKeyPath, 'utf-8'),
|
||||
)
|
||||
} catch (error) {
|
||||
console.log('[ERR]', error)
|
||||
}
|
||||
}
|
||||
return xeapi_public_key
|
||||
}
|
||||
|
||||
// 预先绑定常用函数和常量
|
||||
const floor = Math.floor
|
||||
const random = Math.random
|
||||
@@ -68,6 +46,9 @@ const WNMCID = (function () {
|
||||
return `${randomString}.${now().toString()}.01.0`
|
||||
})()
|
||||
|
||||
let NMTID = ''
|
||||
let NMTID_RETRIES_LEFT = 3
|
||||
|
||||
// 预先定义osMap
|
||||
const osMap = {
|
||||
pc: {
|
||||
@@ -136,7 +117,7 @@ const chooseUserAgent = (crypto, uaType = 'pc') => {
|
||||
}
|
||||
|
||||
// cookie处理
|
||||
const processCookieObject = (cookie, uri) => {
|
||||
const processCookieObject = (cookie, crypto) => {
|
||||
const _ntes_nuid = CryptoJS.lib.WordArray.random(32).toString()
|
||||
const os = osMap[cookie.os] || osMap['pc']
|
||||
|
||||
@@ -155,12 +136,19 @@ const processCookieObject = (cookie, uri) => {
|
||||
appver: cookie.appver || os.appver,
|
||||
}
|
||||
|
||||
if (uri.indexOf('login') === -1) {
|
||||
processedCookie['NMTID'] = CryptoJS.lib.WordArray.random(16).toString()
|
||||
// 服务端下发条件为不带NMTID请求任意eapi加密方式接口
|
||||
if (cookie.NMTID) {
|
||||
processedCookie['NMTID'] = cookie.NMTID
|
||||
} else if (NMTID) {
|
||||
processedCookie['NMTID'] = NMTID
|
||||
} else if (NMTID_RETRIES_LEFT <= 0 || crypto !== 'eapi') {
|
||||
processedCookie['NMTID'] =
|
||||
'00O' + CryptoJS.lib.WordArray.random(19).toString()
|
||||
}
|
||||
|
||||
if (!processedCookie.MUSIC_U) {
|
||||
processedCookie.MUSIC_A = processedCookie.MUSIC_A || anonymous_token
|
||||
processedCookie.MUSIC_A =
|
||||
processedCookie.MUSIC_A || runtimeState.anonymousToken
|
||||
}
|
||||
|
||||
return processedCookie
|
||||
@@ -187,6 +175,33 @@ const generateRequestId = () => {
|
||||
.padStart(4, '0')}`
|
||||
}
|
||||
|
||||
// ---- 运行时凭证的强制刷新 ----
|
||||
// 匿名令牌与 xeapi 公钥存于 util/runtimeState(纯内存、零文件 I/O),
|
||||
// 由对应的 register_* 模块在成功获取后写入,此处只负责编排调用。
|
||||
|
||||
// 刷新自身的请求带 skipTokenEnsure 标记:跳过下方匿名令牌的按需引导,
|
||||
// 避免“刷新任务等待自己”的死锁(注册匿名账号本就不依赖旧令牌)
|
||||
const refreshTokenRequest = (uri, data, options = {}) =>
|
||||
createRequest(uri, data, { ...options, skipTokenEnsure: true })
|
||||
|
||||
// 强制刷新匿名令牌(成功后由 register_anonimous 写入 runtimeState)
|
||||
const refreshAnonymousToken = async () => {
|
||||
const registerAnonimous = require('../module/register_anonimous')
|
||||
await registerAnonimous({}, refreshTokenRequest)
|
||||
}
|
||||
|
||||
// 强制刷新 xeapi 公钥(register_xeapikey 直接走 axios,无重入问题;
|
||||
// 成功后由其写入 runtimeState)
|
||||
const refreshXeapiPublicKey = async () => {
|
||||
const registerXeapikey = require('../module/register_xeapikey')
|
||||
await registerXeapikey(
|
||||
{
|
||||
currentKeyVersion: (runtimeState.xeapiPublicKey || {}).version || '',
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
const createRequest = async (uri, data, options) => {
|
||||
let token = ''
|
||||
switch (options.checkToken) {
|
||||
@@ -200,11 +215,43 @@ const createRequest = async (uri, data, options) => {
|
||||
break
|
||||
}
|
||||
|
||||
// 匿名令牌缺失时按需引导:并发请求共享同一次刷新任务;
|
||||
// 刷新自身的请求(skipTokenEnsure)跳过,避免任务等待自己造成死锁
|
||||
if (!runtimeState.anonymousToken && !options.skipTokenEnsure) {
|
||||
try {
|
||||
await runtimeState.ensure('anonymousToken', refreshAnonymousToken)
|
||||
} catch (error) {
|
||||
console.log('[ERR]', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 本请求走 xeapi 加密且公钥缺失时按需获取
|
||||
// (须在进入下方同步 executor 前完成,加密过程需要同步拿到公钥)
|
||||
const pendingCrypto =
|
||||
options.crypto === undefined || options.crypto === ''
|
||||
? APP_CONF.encrypt
|
||||
? 'eapi'
|
||||
: 'api'
|
||||
: options.crypto
|
||||
if (pendingCrypto === 'xeapi' && !runtimeState.xeapiPublicKey) {
|
||||
try {
|
||||
await runtimeState.ensure('xeapiPublicKey', refreshXeapiPublicKey)
|
||||
} catch (error) {
|
||||
console.log('[ERR]', error)
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 变量声明和初始化
|
||||
const headers = options.headers ? { ...options.headers } : {}
|
||||
const ip = options.realIP || options.ip || ''
|
||||
|
||||
// 加密方式选择
|
||||
let crypto = options.crypto
|
||||
if (crypto === '') {
|
||||
crypto = APP_CONF.encrypt ? 'eapi' : 'api'
|
||||
}
|
||||
|
||||
// IP头设置
|
||||
if (ip) {
|
||||
headers['X-Real-IP'] = ip
|
||||
@@ -217,19 +264,13 @@ const createRequest = async (uri, data, options) => {
|
||||
}
|
||||
|
||||
if (typeof cookie === 'object') {
|
||||
cookie = processCookieObject(cookie, uri)
|
||||
cookie = processCookieObject(cookie, crypto)
|
||||
headers['Cookie'] = cookieObjToString(cookie)
|
||||
}
|
||||
let url = ''
|
||||
let encryptData = ''
|
||||
let crypto = options.crypto
|
||||
const csrfToken = cookie['__csrf'] || ''
|
||||
|
||||
// 加密方式选择
|
||||
if (crypto === '') {
|
||||
crypto = APP_CONF.encrypt ? 'eapi' : 'api'
|
||||
}
|
||||
|
||||
const answer = { status: 500, body: {}, cookie: [] }
|
||||
|
||||
data.e_r = toBoolean(
|
||||
@@ -264,7 +305,7 @@ const createRequest = async (uri, data, options) => {
|
||||
break
|
||||
|
||||
case 'xeapi':
|
||||
const xeapiPublicKey = loadXeapiPublicKey()
|
||||
const xeapiPublicKey = runtimeState.xeapiPublicKey
|
||||
if (!xeapiPublicKey) {
|
||||
throw new Error('xeapi public key is missing')
|
||||
}
|
||||
@@ -332,9 +373,8 @@ const createRequest = async (uri, data, options) => {
|
||||
|
||||
if (cookie.MUSIC_U) header['MUSIC_U'] = cookie.MUSIC_U
|
||||
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
||||
if (options.checkToken) {
|
||||
header['X-antiCheatToken'] = token
|
||||
}
|
||||
if (options.checkToken) header['X-antiCheatToken'] = token
|
||||
if (crypto === 'eapi' && cookie.NMTID) header['NMTID'] = cookie.NMTID
|
||||
|
||||
headers['Cookie'] = createHeaderCookie(header)
|
||||
headers['User-Agent'] =
|
||||
@@ -420,9 +460,30 @@ const createRequest = async (uri, data, options) => {
|
||||
axios(settings)
|
||||
.then((res) => {
|
||||
const body = res.data
|
||||
answer.cookie = (res.headers['set-cookie'] || []).map((x) =>
|
||||
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
||||
)
|
||||
const setCookies = res.headers['set-cookie'] || []
|
||||
|
||||
const cleanCookie = (x) => x.replace(/\s*Domain=[^(;|$)]+;*/, '')
|
||||
|
||||
// 仅对真正未携带 NMTID 的探测请求采集并消耗重试次数
|
||||
if (
|
||||
crypto === 'eapi' &&
|
||||
!NMTID &&
|
||||
NMTID_RETRIES_LEFT > 0 &&
|
||||
!cookie.NMTID
|
||||
) {
|
||||
NMTID_RETRIES_LEFT--
|
||||
answer.cookie = setCookies.map((x) => {
|
||||
const cleaned = cleanCookie(x)
|
||||
const match = x.match(/(?:^|;\s*)NMTID=([^;]+)/)
|
||||
if (match) {
|
||||
//不需要处理竞争, 官方客户端真实操作
|
||||
NMTID = match[1]
|
||||
}
|
||||
return cleaned
|
||||
})
|
||||
} else {
|
||||
answer.cookie = setCookies.map(cleanCookie)
|
||||
}
|
||||
|
||||
// debug: 统一注释块,需要时取消注释查看请求/返回的原始密文
|
||||
|
||||
@@ -487,3 +548,6 @@ const createRequest = async (uri, data, options) => {
|
||||
}
|
||||
|
||||
module.exports = createRequest
|
||||
// 供 generateConfig 等复用的强制刷新入口(成功后写入 runtimeState)
|
||||
module.exports.refreshAnonymousToken = refreshAnonymousToken
|
||||
module.exports.refreshXeapiPublicKey = refreshXeapiPublicKey
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 统一的运行时动态状态存储(仅内存,不落盘)
|
||||
//
|
||||
// 存放需要在进程内共享、且会在运行期间变化的动态配置,
|
||||
// 目前包括匿名令牌(MUSIC_A)与 xeapi 公钥。
|
||||
//
|
||||
// 设计约定:
|
||||
// - 只存在于内存中,不做任何文件 I/O —— 在没有 /tmp 或临时目录
|
||||
// 不可写的平台(Android / iOS / 嵌入式 / serverless)上同样可用
|
||||
// - 写入方(module/register_anonimous.js、module/register_xeapikey.js)
|
||||
// 在成功获取新值后立即写入,读取方(util/request.js)每次请求实时读取,
|
||||
// 进程内即时生效,无需重启
|
||||
// - 凭证缺失时可通过 ensure() 按需刷新,并发调用共享同一次刷新任务
|
||||
// - 以后新增的动态配置请放在这里,不要再往 os.tmpdir() 写文件
|
||||
|
||||
const runtimeState = {
|
||||
/** 匿名令牌(MUSIC_A cookie),由 module/register_anonimous.js 成功后写入 */
|
||||
anonymousToken: '',
|
||||
|
||||
/**
|
||||
* xeapi 公钥(含 sk / version / deviceId 等字段),
|
||||
* 由 module/register_xeapikey.js 成功后写入
|
||||
*/
|
||||
xeapiPublicKey: null,
|
||||
}
|
||||
|
||||
/** 进行中的按需刷新任务:并发调用共享同一次刷新,避免重复请求 */
|
||||
const refreshTasks = new Map()
|
||||
|
||||
/**
|
||||
* 确保某项动态状态可用:已有值时直接返回;缺失时执行 refresh 刷新
|
||||
* (refresh 成功后需自行把新值写入 runtimeState[key])。
|
||||
* 并发调用会共享同一个进行中的刷新任务。
|
||||
*
|
||||
* @param {string} key runtimeState 上的字段名
|
||||
* @param {() => Promise} refresh 刷新函数
|
||||
* @returns {Promise<*>} 该状态当前的值(刷新失败时抛出错误)
|
||||
*/
|
||||
runtimeState.ensure = async (key, refresh) => {
|
||||
if (runtimeState[key]) return runtimeState[key]
|
||||
|
||||
if (!refreshTasks.has(key)) {
|
||||
refreshTasks.set(
|
||||
key,
|
||||
Promise.resolve()
|
||||
.then(refresh)
|
||||
.finally(() => {
|
||||
refreshTasks.delete(key)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
await refreshTasks.get(key)
|
||||
return runtimeState[key]
|
||||
}
|
||||
|
||||
module.exports = runtimeState
|
||||
@@ -1,24 +0,0 @@
|
||||
const registerXeapiKey = require('../module/register_xeapikey')
|
||||
|
||||
const getXeapiPublicKey = async (currentPublicKey = {}, deviceId = '') => {
|
||||
const result = await registerXeapiKey(
|
||||
{
|
||||
deviceId,
|
||||
currentKeyVersion: currentPublicKey.version || '',
|
||||
},
|
||||
null,
|
||||
)
|
||||
|
||||
const publicKey = result.body
|
||||
if (!publicKey.sk && currentPublicKey.sk) {
|
||||
publicKey.sk = currentPublicKey.sk
|
||||
}
|
||||
if (!publicKey.sk) {
|
||||
throw new Error('xeapi public key response missing sk')
|
||||
}
|
||||
return publicKey
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getXeapiPublicKey,
|
||||
}
|
||||
Reference in New Issue
Block a user