mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-20 14:41:23 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddfd680301 | ||
|
|
c478367c6c |
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mark stale issues
|
||||
uses: actions/stale@v11
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: |
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
name: OpenCode Automation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: auto-${{ github.event.issue.number || github.event.pull_request.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
automate:
|
||||
# 排除其他机器人触发,防止自我循环;
|
||||
# 但允许 takanashi-hoshino-agent 创建的 PR,这样 /oc 生成的测试 PR 也能被自动评审
|
||||
if: |
|
||||
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: ${{ steps.cfg.outputs.app_id }}
|
||||
private-key: ${{ steps.cfg.outputs.app_private_key }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install opencode
|
||||
run: npm i -g opencode-ai@latest
|
||||
|
||||
- name: Run automation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
|
||||
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: |
|
||||
${{ 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
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')) &&
|
||||
github.actor != 'opencode-agent[bot]' &&
|
||||
github.actor != 'yukka-agent[bot]' &&
|
||||
github.actor != 'takanashi-hoshino-agent[bot]' &&
|
||||
github.actor != 'github-actions[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -33,9 +33,9 @@ jobs:
|
||||
|
||||
- name: Generate bot token
|
||||
id: token
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
client-id: ${{ steps.cfg.outputs.app_id }}
|
||||
app-id: ${{ steps.cfg.outputs.app_id }}
|
||||
private-key: ${{ steps.cfg.outputs.app_private_key }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
@@ -63,7 +63,4 @@ jobs:
|
||||
${{ steps.cfg.outputs.opencode_prompt }}
|
||||
|
||||
## 用户指令(来自触发评论,请严格按此执行)
|
||||
${{ github.event.comment.body }}
|
||||
|
||||
## 用户要求Review时, 严格执行下面指令
|
||||
${{ steps.cfg.outputs.pr_review_prompt }}
|
||||
${{ github.event.comment.body }}
|
||||
@@ -6,7 +6,6 @@ node_modules
|
||||
.history
|
||||
examples/moddef.json
|
||||
bin
|
||||
anonymous_token
|
||||
.vercel
|
||||
.env
|
||||
IFLOW.md
|
||||
|
||||
@@ -13,18 +13,13 @@
|
||||
- 文档格式化检查/修复:`pnpm docs:check` / `pnpm docs:format`
|
||||
- 打包独立二进制:`pnpm pkgwin` / `pkglinux` / `pkgmacos`
|
||||
|
||||
## 目录内非路由文件
|
||||
- `plugins/`——内部上传辅助(`upload.js`、`songUpload.js`),被 `module/*` require,本身不是路由。
|
||||
- `module_example/`——接口模板与 `main.js` 库调用示例(含 `test.js` 演示 `login_cellphone`→`song_url` 链路),不是运行时代码。
|
||||
- `examples/get_static_moddef.js`——用 `getModulesDefinitions(..., false)`(`doRequire=false`)把全部路由导出为 `moddef.json`。改路由注册逻辑时可跑它核对。
|
||||
- `module_types/` 为空目录;类型声明在根目录 `interface.d.ts`(`types` 字段)。
|
||||
|
||||
## 架构
|
||||
- `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`。
|
||||
@@ -45,7 +40,5 @@
|
||||
- **改 `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()`);首次启动先写空文件再刷新。
|
||||
- **`docs:check` 校验的是 `public/docs/home.md`(docsify 站点)**,不是 README;脚本默认只处理该文件,换行会被统一为 `\n`、连续空行压缩到 2 行,PR 改动它后记得跑 `pnpm docs:format`。
|
||||
- **ESLint 9 flat config**:`eslint.config.js`,风格由 `eslint-plugin-prettier` 强制(2 空格缩进、单引号、分号、`endOfLine: auto`),并 `globalIgnores(['**/public/'])`——`public/` 下的前端页面不参与 lint。
|
||||
- **`pnpm-workspace.yaml` 是单包工作区**(`packages: ['.']`),并带 `allowBuilds`(core-js/es5-ext)与 `minimumReleaseAgeExclude` 白名单;改依赖安装行为时先看它。
|
||||
- **运行时凭证在内存(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`)。
|
||||
|
||||
@@ -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
-42
@@ -1,55 +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 { loadNeapiKey } = require('./util/neapiKey')
|
||||
const registerNeapikey = require('./module/register_neapikey')
|
||||
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()
|
||||
if (!global.deviceId) {
|
||||
global.deviceId = generateDeviceId()
|
||||
}
|
||||
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',
|
||||
)
|
||||
}
|
||||
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',
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
try {
|
||||
const current = loadNeapiKey()
|
||||
await registerNeapikey(
|
||||
{
|
||||
version: current ? current.version : 0,
|
||||
behavior: current ? 'update' : 'open',
|
||||
},
|
||||
null,
|
||||
)
|
||||
await request.refreshAnonymousToken()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = generateConfig
|
||||
|
||||
Vendored
-29
@@ -1087,10 +1087,6 @@ export function register_checktoken_v3(
|
||||
params: { refresh?: boolean | string } & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function register_neapikey(
|
||||
params: { version?: string | number; behavior?: string } & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function register_xeapikey(
|
||||
params: { deviceId?: string; currentKeyVersion?: string } & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
@@ -2890,28 +2886,3 @@ export function voicelist_trans(
|
||||
} & MultiPageConfig &
|
||||
RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function fans_group_detail(
|
||||
params: {
|
||||
groupId: string
|
||||
scene?: string
|
||||
} & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function fans_group_feed_recommend(
|
||||
params: {
|
||||
fansGroupId: string
|
||||
cursor?: string
|
||||
size?: string | number
|
||||
} & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function fans_group_user_groups(
|
||||
params?: RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
export function fans_group_user_group_detail(
|
||||
params: {
|
||||
groupId: string | number
|
||||
} & RequestBaseConfig,
|
||||
): Promise<Response>
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// 乐迷团详情
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
groupId: query.groupId || query.id || '',
|
||||
scene: query.scene || '',
|
||||
}
|
||||
return request(
|
||||
'/api/social/fansgroup/bff/detail/get',
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// 乐迷团推荐笔记
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
artistSelf: query.artistSelf || '0',
|
||||
fansGroupId: query.fansGroupId || query.groupId || '',
|
||||
cursor: query.cursor || '0',
|
||||
size: String(query.size || 10),
|
||||
}
|
||||
return request(
|
||||
'/api/fans/group/feed/recommend/get',
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// 用户所处乐迷团详情
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
const data = {
|
||||
groupId: query.groupId || query.id || '',
|
||||
}
|
||||
return request(
|
||||
'/api/social/fansgroup/bff/user/group/detail/get',
|
||||
data,
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// 获取当前登录用户加入的全部歌手乐迷团列表
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
|
||||
module.exports = (query, request) => {
|
||||
return request(
|
||||
'/api/social/fansgroup/bff/user/groups/get',
|
||||
{},
|
||||
createOption(query, 'eapi'),
|
||||
)
|
||||
}
|
||||
@@ -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,48 +0,0 @@
|
||||
const { default: axios } = require('axios')
|
||||
const { APP_CONF } = require('../util/config.json')
|
||||
const { saveNeapiKey } = require('../util/neapiKey')
|
||||
const { decodeConfig } = require('../util/neapiConfig')
|
||||
|
||||
const USER_AGENT =
|
||||
'NeteaseMusic/9.5.61.260802021928(9005061);Dalvik/2.1.0 (Linux; U; Android 12; HBN-AL00 Build/cd737a2.0)'
|
||||
|
||||
module.exports = async (query, request) => {
|
||||
const version = Number(query.version) || 0
|
||||
const behavior = query.behavior === 'update' ? 'update' : 'open'
|
||||
|
||||
const res = await axios({
|
||||
method: 'GET',
|
||||
url: APP_CONF.apiDomain + '/api/gorilla/algorithm/record',
|
||||
params: { version, behavior },
|
||||
headers: { 'User-Agent': USER_AGENT },
|
||||
proxy: false,
|
||||
})
|
||||
|
||||
if (!res.data || res.data.code !== 200 || !res.data.data) {
|
||||
throw new Error('neapi config request failed')
|
||||
}
|
||||
|
||||
// file 为空表示本地已是最新,无需更新
|
||||
const { file, circleTime } = res.data.data
|
||||
const config = file
|
||||
? saveNeapiKey(decodeConfig(Buffer.from(file, 'base64')), circleTime)
|
||||
: null
|
||||
|
||||
// 只回版本与密钥偏移,密钥本身留在本地缓存
|
||||
const keyOffsets = {}
|
||||
if (config) {
|
||||
keyOffsets.encrypt = config.keyBlob.indexOf(config.encryptKey)
|
||||
keyOffsets.decrypt = config.keyBlob.indexOf(config.decryptKey)
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
version: config ? config.version : res.data.data.version,
|
||||
updated: Boolean(config),
|
||||
circleTime,
|
||||
keyOffsets,
|
||||
},
|
||||
cookie: [],
|
||||
}
|
||||
}
|
||||
@@ -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 = ''
|
||||
@@ -32,7 +33,7 @@ module.exports = async (query, request) => {
|
||||
|
||||
const res = await axios({
|
||||
method: 'POST',
|
||||
url: APP_CONF.apiDomain + '/api/bsr/sk/get',
|
||||
url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'NeteaseMusic/9.5.61.260802021928(9005061);Dalvik/2.1.0 (Linux; U; Android 12; HBN-AL00 Build/cd737a2.0)',
|
||||
@@ -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: {
|
||||
|
||||
+10
-11
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@neteasecloudmusicapienhanced/api",
|
||||
"version": "4.41.0",
|
||||
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced)",
|
||||
"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",
|
||||
"start": "node app.js",
|
||||
@@ -75,23 +75,22 @@
|
||||
"data"
|
||||
],
|
||||
"dependencies": {
|
||||
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.4.5",
|
||||
"@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",
|
||||
"fzstd": "^0.1.1",
|
||||
"gzip": "^0.1.0",
|
||||
"jsdom": "^24.1.3",
|
||||
"music-metadata": "^11.16.1",
|
||||
"music-metadata": "^11.15.0",
|
||||
"node-forge": "^1.4.0",
|
||||
"pac-proxy-agent": "^7.2.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"safe-decode-uri-component": "^1.2.1",
|
||||
"tunnel": "^0.0.6",
|
||||
"xml2js": "^0.6.2",
|
||||
"yargs": "^18.2.0"
|
||||
"yargs": "^18.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.7",
|
||||
@@ -100,13 +99,13 @@
|
||||
"@types/express-fileupload": "^1.5.1",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "25.9.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.71.0",
|
||||
"@typescript-eslint/parser": "^8.71.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.2.1",
|
||||
"eslint-plugin-html": "^8.2.0",
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"globals": "^17.13.0",
|
||||
"globals": "^17.12.0",
|
||||
"husky": "^9.1.7",
|
||||
"intelli-espower-loader": "^1.1.0",
|
||||
"lint-staged": "^16.4.0",
|
||||
@@ -114,7 +113,7 @@
|
||||
"nodemon": "^3.1.14",
|
||||
"pkg": "^5.8.1",
|
||||
"power-assert": "^1.6.1",
|
||||
"prettier": "^3.9.9",
|
||||
"prettier": "^3.9.6",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
Generated
+185
-183
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@neteasecloudmusicapienhanced/unblockmusic-utils':
|
||||
specifier: ^0.4.5
|
||||
version: 0.4.5
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
axios:
|
||||
specifier: ^1.20.0
|
||||
version: 1.20.0
|
||||
@@ -26,9 +26,6 @@ importers:
|
||||
express-fileupload:
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2
|
||||
fzstd:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1
|
||||
gzip:
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0
|
||||
@@ -36,8 +33,8 @@ importers:
|
||||
specifier: ^24.1.3
|
||||
version: 24.1.3
|
||||
music-metadata:
|
||||
specifier: ^11.16.1
|
||||
version: 11.16.1
|
||||
specifier: ^11.15.0
|
||||
version: 11.15.0
|
||||
node-forge:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
@@ -57,8 +54,8 @@ importers:
|
||||
specifier: ^0.6.2
|
||||
version: 0.6.2
|
||||
yargs:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
specifier: ^18.1.0
|
||||
version: 18.1.0
|
||||
devDependencies:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.3.7
|
||||
@@ -79,11 +76,11 @@ importers:
|
||||
specifier: 25.9.2
|
||||
version: 25.9.2
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.71.0
|
||||
version: 8.71.0(@typescript-eslint/parser@8.71.0([email protected])([email protected]))([email protected])([email protected])
|
||||
specifier: ^8.69.0
|
||||
version: 8.69.0(@typescript-eslint/parser@8.69.0([email protected])([email protected]))([email protected])([email protected])
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.71.0
|
||||
version: 8.71.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
|
||||
@@ -91,14 +88,14 @@ importers:
|
||||
specifier: ^10.1.8
|
||||
version: 10.1.8([email protected])
|
||||
eslint-plugin-html:
|
||||
specifier: ^8.2.1
|
||||
version: 8.2.1
|
||||
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].9)
|
||||
version: 5.5.6([email protected]([email protected]))([email protected])([email protected].6)
|
||||
globals:
|
||||
specifier: ^17.13.0
|
||||
version: 17.13.0
|
||||
specifier: ^17.12.0
|
||||
version: 17.12.0
|
||||
husky:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
@@ -121,8 +118,8 @@ importers:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1
|
||||
prettier:
|
||||
specifier: ^3.9.9
|
||||
version: 3.9.9
|
||||
specifier: ^3.9.6
|
||||
version: 3.9.6
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
@@ -259,8 +256,8 @@ packages:
|
||||
'@jridgewell/[email protected]':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@neteasecloudmusicapienhanced/[email protected].5':
|
||||
resolution: {integrity: sha512-8BVbf80pKLpVdK6M+TJYMYB8V3hzIrBy0iVY1SP4hxZTqZAVTowC/lVn0gMHV6uc6e6BVXzFYUL0H4CTLdNmKQ==}
|
||||
'@neteasecloudmusicapienhanced/[email protected].4':
|
||||
resolution: {integrity: sha512-ErQMlbG9LK2TuHr1ZVVsaHAyOkmY3MEDQ5/thtnEtX68YADkmcYt7ddgfnEVXGfGNCdvCV4k8viG2IdX9Qhoeg==}
|
||||
hasBin: true
|
||||
|
||||
'@nodelib/[email protected]':
|
||||
@@ -338,63 +335,63 @@ packages:
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.71.0':
|
||||
resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==}
|
||||
'@typescript-eslint/eslint-plugin@8.69.0':
|
||||
resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.71.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/parser@8.71.0':
|
||||
resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==}
|
||||
'@typescript-eslint/parser@8.69.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/project-service@8.71.0':
|
||||
resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==}
|
||||
'@typescript-eslint/project-service@8.69.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/scope-manager@8.71.0':
|
||||
resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==}
|
||||
'@typescript-eslint/scope-manager@8.69.0':
|
||||
resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.71.0':
|
||||
resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==}
|
||||
'@typescript-eslint/tsconfig-utils@8.69.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/type-utils@8.71.0':
|
||||
resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==}
|
||||
'@typescript-eslint/type-utils@8.69.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/types@8.71.0':
|
||||
resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==}
|
||||
'@typescript-eslint/types@8.69.0':
|
||||
resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.71.0':
|
||||
resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==}
|
||||
'@typescript-eslint/typescript-estree@8.69.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/utils@8.71.0':
|
||||
resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==}
|
||||
'@typescript-eslint/utils@8.69.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/visitor-keys@8.71.0':
|
||||
resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==}
|
||||
'@typescript-eslint/visitor-keys@8.69.0':
|
||||
resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@unblockneteasemusic/[email protected]':
|
||||
@@ -451,8 +448,8 @@ packages:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-regex@6.4.0:
|
||||
resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==}
|
||||
ansi-regex@6.3.0:
|
||||
resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]:
|
||||
@@ -546,22 +543,22 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
[email protected].8:
|
||||
resolution: {integrity: sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==}
|
||||
[email protected].6:
|
||||
resolution: {integrity: sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected].21:
|
||||
resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==}
|
||||
[email protected]8:
|
||||
resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
|
||||
|
||||
[email protected].7:
|
||||
resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
|
||||
[email protected].4:
|
||||
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
|
||||
|
||||
[email protected].12:
|
||||
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
|
||||
[email protected].9:
|
||||
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
[email protected]:
|
||||
@@ -1004,8 +1001,8 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: '>=7.0.0'
|
||||
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-Xn6QOifC3niiqDojZ/hgGRCj+YOefDHtT6ShwM65/+goFCNFcTTXUoXcjht+gvuds2I6r3tmM5XWQuTOOF5lhw==}
|
||||
[email protected].0:
|
||||
resolution: {integrity: sha512-L6F37uij4uq3cZah46BawXeZlZYW9IfFeep7BwkuxBZA++BbihvEpoJPhq0NpCruPRzVkG/tpgvQYM53Re4pVA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1122,8 +1119,8 @@ packages:
|
||||
resolution: {integrity: sha512-wxUJn2vTHvj/kZCVmc5/bJO15C7aSMyHeuXYY3geKpeKibaAoQGcEv5+sM6nHS2T7VF+QHS4hTWPiY2mKofEdg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
[email protected].3:
|
||||
resolution: {integrity: sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==}
|
||||
[email protected].2:
|
||||
resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1268,9 +1265,6 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-dkuVSOKKwh3eas5VkJy1AW1vFpet8TA/fGmVA5krThl8YcOVE/8ZIoEA1+U1vEn5ckxxhLirSdY837azmbaNHA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1279,8 +1273,8 @@ packages:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-east-asian-width@1.7.0:
|
||||
resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==}
|
||||
get-east-asian-width@1.6.0:
|
||||
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1319,8 +1313,8 @@ packages:
|
||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]3.0:
|
||||
resolution: {integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==}
|
||||
[email protected]2.0:
|
||||
resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1433,8 +1427,8 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
[email protected].11:
|
||||
resolution: {integrity: sha512-YChdK5txDjwGUvgR7oCJcLGkwi3LDh8Zx8tS7ndYciLEg/oOCL26VUBSUEtgS7EjiybcBqFxh5j0rAdDX7/bbg==}
|
||||
[email protected].8:
|
||||
resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1465,8 +1459,8 @@ packages:
|
||||
resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected].2:
|
||||
resolution: {integrity: sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==}
|
||||
[email protected].0:
|
||||
resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1501,8 +1495,8 @@ packages:
|
||||
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected]7.0:
|
||||
resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==}
|
||||
[email protected]6.2:
|
||||
resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1818,8 +1812,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==}
|
||||
|
||||
[email protected]6.1:
|
||||
resolution: {integrity: sha512-uR/mHK6eyfl8h3jVCobF5b38Mfg1IDJMiBxIVbmm5F+G1OXEFcUpQHveO84tJBAqvY93GCRY2R4kDyx7s06Rug==}
|
||||
[email protected]5.0:
|
||||
resolution: {integrity: sha512-TN+kO1/oOc8UzDW5N3vSDncBcv9WyNnQQe/NFMcFWq6G1+zVeYUkGvkYpQ/S8wb8vKtUD7i78dIaY0cv+cmPRA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -1872,8 +1866,8 @@ packages:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
[email protected]8:
|
||||
resolution: {integrity: sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==}
|
||||
[email protected]7:
|
||||
resolution: {integrity: sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
@@ -2089,8 +2083,8 @@ packages:
|
||||
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
[email protected].9:
|
||||
resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==}
|
||||
[email protected].6:
|
||||
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2104,8 +2098,8 @@ packages:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
[email protected].8:
|
||||
resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==}
|
||||
[email protected].7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2130,6 +2124,10 @@ packages:
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -2430,8 +2428,8 @@ packages:
|
||||
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
string-width@8.3.0:
|
||||
resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==}
|
||||
string-width@8.2.2:
|
||||
resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2594,8 +2592,8 @@ packages:
|
||||
resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected].5:
|
||||
resolution: {integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==}
|
||||
[email protected].4:
|
||||
resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2611,8 +2609,8 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uint8array-extras@1.6.0:
|
||||
resolution: {integrity: sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==}
|
||||
uint8array-extras@1.5.0:
|
||||
resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2699,8 +2697,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||
|
||||
[email protected]4:
|
||||
resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==}
|
||||
[email protected]2:
|
||||
resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected]:
|
||||
@@ -2737,8 +2735,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
[email protected]2.0:
|
||||
resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==}
|
||||
[email protected]1.3:
|
||||
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -2778,8 +2776,8 @@ packages:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==}
|
||||
[email protected].0:
|
||||
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2815,8 +2813,8 @@ packages:
|
||||
resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@18.2.0:
|
||||
resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==}
|
||||
yargs@18.1.0:
|
||||
resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=23}
|
||||
|
||||
[email protected]:
|
||||
@@ -2960,12 +2958,12 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@neteasecloudmusicapienhanced/[email protected].5':
|
||||
'@neteasecloudmusicapienhanced/[email protected].4':
|
||||
dependencies:
|
||||
'@unblockneteasemusic/server': 0.28.0
|
||||
axios: 1.20.0
|
||||
dotenv: 17.4.2
|
||||
express: 4.22.3
|
||||
express: 4.22.2
|
||||
https: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@@ -3055,57 +3053,57 @@ snapshots:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 25.9.2
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0([email protected])([email protected]))([email protected])([email protected])':
|
||||
'@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0([email protected])([email protected]))([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.71.0([email protected])([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.71.0
|
||||
'@typescript-eslint/type-utils': 8.71.0([email protected])([email protected])
|
||||
'@typescript-eslint/utils': 8.71.0([email protected])([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.71.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.11
|
||||
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/parser@8.71.0([email protected])([email protected])':
|
||||
'@typescript-eslint/parser@8.69.0([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.71.0
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.71.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/project-service@8.71.0([email protected])':
|
||||
'@typescript-eslint/project-service@8.69.0([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.71.0([email protected])
|
||||
'@typescript-eslint/types': 8.71.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/scope-manager@8.71.0':
|
||||
'@typescript-eslint/scope-manager@8.69.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/visitor-keys': 8.71.0
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
'@typescript-eslint/visitor-keys': 8.69.0
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.71.0([email protected])':
|
||||
'@typescript-eslint/tsconfig-utils@8.69.0([email protected])':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.71.0([email protected])([email protected])':
|
||||
'@typescript-eslint/type-utils@8.69.0([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
|
||||
'@typescript-eslint/utils': 8.71.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])
|
||||
@@ -3113,14 +3111,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/types@8.71.0': {}
|
||||
'@typescript-eslint/types@8.69.0': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.71.0([email protected])':
|
||||
'@typescript-eslint/typescript-estree@8.69.0([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.71.0([email protected])
|
||||
'@typescript-eslint/tsconfig-utils': 8.71.0([email protected])
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/visitor-keys': 8.71.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
|
||||
@@ -3130,20 +3128,20 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.71.0([email protected])([email protected])':
|
||||
'@typescript-eslint/utils@8.69.0([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.71.0
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/typescript-estree': 8.71.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/visitor-keys@8.71.0':
|
||||
'@typescript-eslint/visitor-keys@8.69.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/types': 8.69.0
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@unblockneteasemusic/[email protected]':
|
||||
@@ -3195,7 +3193,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
ansi-regex@6.4.0: {}
|
||||
ansi-regex@6.3.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -3286,7 +3284,7 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
[email protected].8:
|
||||
[email protected].6:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
@@ -3296,7 +3294,7 @@ snapshots:
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.4.24
|
||||
on-finished: 2.4.1
|
||||
qs: 6.16.0
|
||||
qs: 6.15.3
|
||||
raw-body: 2.5.3
|
||||
type-is: 1.6.18
|
||||
unpipe: 1.0.0
|
||||
@@ -3317,16 +3315,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected].21:
|
||||
[email protected]8:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
[email protected].7:
|
||||
[email protected].4:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
[email protected].12:
|
||||
[email protected].9:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
@@ -3417,7 +3415,7 @@ snapshots:
|
||||
[email protected]:
|
||||
dependencies:
|
||||
slice-ansi: 8.0.0
|
||||
string-width: 8.3.0
|
||||
string-width: 8.2.2
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -3741,10 +3739,10 @@ snapshots:
|
||||
string.prototype.trimstart: 1.0.8
|
||||
typed-array-buffer: 1.0.3
|
||||
typed-array-byte-length: 1.0.3
|
||||
typed-array-byte-offset: 1.0.5
|
||||
typed-array-byte-offset: 1.0.4
|
||||
typed-array-length: 1.0.8
|
||||
unbox-primitive: 1.1.0
|
||||
which-typed-array: 1.1.24
|
||||
which-typed-array: 1.1.22
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -3854,14 +3852,14 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 9.39.5
|
||||
|
||||
[email protected].1:
|
||||
[email protected].0:
|
||||
dependencies:
|
||||
htmlparser2: 10.1.0
|
||||
|
||||
[email protected]([email protected]([email protected]))([email protected])([email protected].9):
|
||||
[email protected]([email protected]([email protected]))([email protected])([email protected].6):
|
||||
dependencies:
|
||||
eslint: 9.39.5
|
||||
prettier: 3.9.9
|
||||
prettier: 3.9.6
|
||||
prettier-linter-helpers: 1.0.1
|
||||
synckit: 0.11.13
|
||||
optionalDependencies:
|
||||
@@ -4008,11 +4006,11 @@ snapshots:
|
||||
dependencies:
|
||||
busboy: 1.6.0
|
||||
|
||||
[email protected].3:
|
||||
[email protected].2:
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
array-flatten: 1.1.1
|
||||
body-parser: 1.20.8
|
||||
body-parser: 1.20.6
|
||||
content-disposition: 0.5.4
|
||||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
@@ -4030,8 +4028,8 @@ snapshots:
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 0.1.13
|
||||
proxy-addr: 2.0.8
|
||||
qs: 6.16.0
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.15.3
|
||||
range-parser: 1.2.1
|
||||
safe-buffer: 5.2.1
|
||||
send: 0.19.2
|
||||
@@ -4065,7 +4063,7 @@ snapshots:
|
||||
on-finished: 2.4.1
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.8
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.16.0
|
||||
range-parser: 1.3.0
|
||||
router: 2.2.0
|
||||
@@ -4118,7 +4116,7 @@ snapshots:
|
||||
'@tokenizer/inflate': 0.4.1
|
||||
strtok3: 10.3.5
|
||||
token-types: 6.1.2
|
||||
uint8array-extras: 1.6.0
|
||||
uint8array-extras: 1.5.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4228,13 +4226,11 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
get-east-asian-width@1.7.0: {}
|
||||
get-east-asian-width@1.6.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4289,7 +4285,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]3.0: {}
|
||||
[email protected]2.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4401,7 +4397,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].11: {}
|
||||
[email protected].8: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4431,7 +4427,7 @@ snapshots:
|
||||
from2: 2.3.0
|
||||
p-is-promise: 3.0.0
|
||||
|
||||
[email protected].2: {}
|
||||
[email protected].0: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -4469,7 +4465,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]7.0:
|
||||
[email protected]6.2:
|
||||
dependencies:
|
||||
hasown: 2.0.4
|
||||
|
||||
@@ -4502,7 +4498,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.7.0
|
||||
get-east-asian-width: 1.6.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4561,7 +4557,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
which-typed-array: 1.1.24
|
||||
which-typed-array: 1.1.22
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -4606,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.28
|
||||
nwsapi: 2.2.27
|
||||
parse5: 7.3.0
|
||||
rrweb-cssom: 0.7.1
|
||||
saxes: 6.0.0
|
||||
@@ -4617,7 +4613,7 @@ snapshots:
|
||||
whatwg-encoding: 3.1.1
|
||||
whatwg-mimetype: 4.0.0
|
||||
whatwg-url: 14.2.0
|
||||
ws: 8.22.0
|
||||
ws: 8.21.3
|
||||
xml-name-validator: 5.0.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@@ -4661,7 +4657,7 @@ snapshots:
|
||||
picomatch: 4.0.7
|
||||
string-argv: 0.3.2
|
||||
tinyexec: 1.3.1
|
||||
yaml: 2.9.1
|
||||
yaml: 2.9.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -4742,15 +4738,15 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.12
|
||||
brace-expansion: 5.0.9
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.21
|
||||
brace-expansion: 1.1.18
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
brace-expansion: 2.1.7
|
||||
brace-expansion: 2.1.4
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -4797,7 +4793,7 @@ snapshots:
|
||||
once: 1.4.0
|
||||
readable-stream: 3.6.2
|
||||
|
||||
[email protected]6.1:
|
||||
[email protected]5.0:
|
||||
dependencies:
|
||||
'@borewit/text-codec': 0.2.2
|
||||
'@tokenizer/token': 0.3.0
|
||||
@@ -4807,7 +4803,7 @@ snapshots:
|
||||
media-typer: 2.0.0
|
||||
strtok3: 10.3.5
|
||||
token-types: 6.1.2
|
||||
uint8array-extras: 1.6.0
|
||||
uint8array-extras: 1.5.0
|
||||
win-guid: 0.2.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -4856,7 +4852,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]8: {}
|
||||
[email protected]7: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -5150,7 +5146,7 @@ snapshots:
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
|
||||
[email protected].9: {}
|
||||
[email protected].6: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -5158,7 +5154,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].8:
|
||||
[email protected].7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
@@ -5184,6 +5180,11 @@ snapshots:
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
es-define-property: 1.0.1
|
||||
side-channel: 1.1.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
es-define-property: 1.0.1
|
||||
@@ -5277,7 +5278,7 @@ snapshots:
|
||||
[email protected]:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
is-core-module: 2.17.0
|
||||
is-core-module: 2.16.2
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
@@ -5499,7 +5500,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
ip-address: 10.7.2
|
||||
ip-address: 10.7.0
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
[email protected]:
|
||||
@@ -5558,12 +5559,12 @@ snapshots:
|
||||
[email protected]:
|
||||
dependencies:
|
||||
emoji-regex: 10.6.0
|
||||
get-east-asian-width: 1.7.0
|
||||
get-east-asian-width: 1.6.0
|
||||
strip-ansi: 7.2.0
|
||||
|
||||
string-width@8.3.0:
|
||||
string-width@8.2.2:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.7.0
|
||||
get-east-asian-width: 1.6.0
|
||||
strip-ansi: 7.2.0
|
||||
|
||||
[email protected]:
|
||||
@@ -5610,7 +5611,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
ansi-regex: 6.4.0
|
||||
ansi-regex: 6.3.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -5695,7 +5696,7 @@ snapshots:
|
||||
dependencies:
|
||||
gopd: 1.2.0
|
||||
typedarray.prototype.slice: 1.0.5
|
||||
which-typed-array: 1.1.24
|
||||
which-typed-array: 1.1.22
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
@@ -5746,12 +5747,13 @@ snapshots:
|
||||
has-proto: 1.2.0
|
||||
is-typed-array: 1.1.15
|
||||
|
||||
[email protected].5:
|
||||
[email protected].4:
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.9
|
||||
for-each: 0.3.5
|
||||
gopd: 1.2.0
|
||||
has-proto: 1.2.0
|
||||
is-typed-array: 1.1.15
|
||||
reflect.getprototypeof: 1.0.10
|
||||
|
||||
@@ -5773,11 +5775,11 @@ snapshots:
|
||||
get-proto: 1.0.1
|
||||
math-intrinsics: 1.1.0
|
||||
typed-array-buffer: 1.0.3
|
||||
typed-array-byte-offset: 1.0.5
|
||||
typed-array-byte-offset: 1.0.4
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
uint8array-extras@1.6.0: {}
|
||||
uint8array-extras@1.5.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -5863,7 +5865,7 @@ snapshots:
|
||||
isarray: 2.0.5
|
||||
which-boxed-primitive: 1.1.1
|
||||
which-collection: 1.0.2
|
||||
which-typed-array: 1.1.24
|
||||
which-typed-array: 1.1.22
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -5874,7 +5876,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]4:
|
||||
[email protected]2:
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.9
|
||||
@@ -5920,7 +5922,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]2.0: {}
|
||||
[email protected]1.3: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -5941,7 +5943,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].1: {}
|
||||
[email protected].0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -5995,12 +5997,12 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yargs@18.2.0:
|
||||
yargs@18.1.0:
|
||||
dependencies:
|
||||
cliui: 9.0.1
|
||||
escalade: 3.2.0
|
||||
get-caller-file: 2.0.5
|
||||
string-width: 8.3.0
|
||||
string-width: 8.2.2
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 22.0.0
|
||||
|
||||
|
||||
@@ -319,7 +319,6 @@
|
||||
<option value="api">api</option>
|
||||
<option value="linuxapi">linuxapi</option>
|
||||
<option value="xeapi">xeapi</option>
|
||||
<option value="neapi">neapi</option>
|
||||
<option value="" selected>(默认)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
+2
-46
@@ -216,7 +216,7 @@ $ sudo docker run -d -p 3000:3000 netease-music-api
|
||||
- 大部分请求参数或返回内容可在 `/api_decrypt.html` 里解析
|
||||
- 请求参数模式下, 解密结果可直接带到 `/api.html` 继续调试
|
||||
- 需要返回值加密时, 可传 `e_r=1`, `weapi` 和 `eapi` 都支持
|
||||
- 目前支持算法 有 `weapi`, `eapi`, `linuxapi`, `xeapi` 和 `neapi` (xeapi 是一种不加密的特殊算法, 主要用于调试加密前的原始请求参数; neapi 走独立的压缩加密通道, 配置由 `/register/neapikey` 自动获取与刷新)
|
||||
- 目前支持算法 有 `weapi`, `eapi`, `linuxapi` 和 `xeapi` (xeapi 是一种不加密的特殊算法, 主要用于调试加密前的原始请求参数)
|
||||
|
||||
|
||||
## 接口文档
|
||||
@@ -1307,7 +1307,7 @@ tags: 歌单标签
|
||||
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清臻音`, `dolby` => `杜比全景声`, `vivid` => `臻音全景声`, `jymaster` => `超清母带`, `sky` => `沉浸环绕声`
|
||||
`unblock`: 是否使用使用歌曲解锁, 分为`true`和`false`
|
||||
|
||||
**可选参数 :** `immerseType`: 沉浸声环绕声类型, 分为`c512` => `新版c51类型`, `ste2` => `新版环绕立体声类型`, `aac2` => `新版aac类型`, `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`
|
||||
|
||||
@@ -5878,50 +5878,6 @@ let data = encodeURIComponent(
|
||||
|
||||
**调用例子 :** `/song/simi/get?id=39227633`
|
||||
|
||||
### 获取用户乐迷团列表
|
||||
|
||||
说明 : 登录后调用此接口 , 可获取当前登录用户已加入的全部歌手乐迷团列表
|
||||
|
||||
**接口地址 :** `/fans/group/user/groups`
|
||||
|
||||
**调用例子 :** `/fans/group/user/groups`
|
||||
|
||||
### 获取乐迷团详情
|
||||
|
||||
说明 : 调用此接口 , 可获取乐迷团详情
|
||||
|
||||
**必选参数 :** `groupId` : 乐迷团 ID
|
||||
|
||||
**可选参数 :** `scene` : 场景标识
|
||||
|
||||
**接口地址 :** `/fans/group/detail`
|
||||
|
||||
**调用例子 :** `/fans/group/detail?groupId=1872529203038486609`
|
||||
|
||||
### 获取用户在乐迷团的详情
|
||||
|
||||
说明 : 登录后调用此接口 , 可获取当前用户在指定乐迷团的详细成员信息及铭牌等级
|
||||
|
||||
**必选参数 :** `groupId` : 乐迷团 ID
|
||||
|
||||
**接口地址 :** `/fans/group/user/group/detail`
|
||||
|
||||
**调用例子 :** `/fans/group/user/group/detail?groupId=1755777360858267711`
|
||||
|
||||
### 获取乐迷团推荐笔记
|
||||
|
||||
说明 : 调用此接口 , 可获取指定乐迷团的推荐笔记
|
||||
|
||||
**必选参数 :** `fansGroupId` : 乐迷团 ID
|
||||
|
||||
**可选参数 :** `cursor` : 游标 , 默认为 0
|
||||
|
||||
`size` : 返回数量 , 默认为 10
|
||||
|
||||
**接口地址 :** `/fans/group/feed/recommend`
|
||||
|
||||
**调用例子 :** `/fans/group/feed/recommend?fansGroupId=1872529203038486609&size=10&cursor=0`
|
||||
|
||||
## 离线访问此文档
|
||||
|
||||
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"apiDomain": "https://interface.music.163.com",
|
||||
"eapiDomain": "https://interfacepc.music.163.com",
|
||||
"xeapiDomain": "https://interface3.music.163.com",
|
||||
"neapiDomain": "https://interface3.music.163.com",
|
||||
"domain": "https://music.163.com",
|
||||
"clDomian": "https://clientlog.music.163.com",
|
||||
"clDomian3": "https://clientlog3.music.163.com",
|
||||
|
||||
+8
-106
@@ -2,7 +2,6 @@ const CryptoJS = require('crypto-js')
|
||||
const crypto = require('crypto')
|
||||
const forge = require('node-forge')
|
||||
const zlib = require('zlib')
|
||||
const zstd = require('./zstd')
|
||||
const iv = '0102030405060708'
|
||||
const presetKey = '0CoJUm6Qyw8W8jud'
|
||||
const linuxapiKey = 'rFgB&h#%2?^eDg:Q'
|
||||
@@ -152,12 +151,7 @@ const decrypt = (cipher) => {
|
||||
}
|
||||
|
||||
const aesEcbEncrypt = (key, plaintext) => {
|
||||
// 某些运行时不允许使用 null 作为 IV,因此使用 Buffer.alloc(0) ,同时与上下文deriveX25519AesKey等函数保持一致
|
||||
const cipher = crypto.createCipheriv(
|
||||
`aes-${key.length * 8}-ecb`,
|
||||
key,
|
||||
Buffer.alloc(0),
|
||||
)
|
||||
const cipher = crypto.createCipheriv(`aes-${key.length * 8}-ecb`, key, null)
|
||||
return Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()])
|
||||
}
|
||||
|
||||
@@ -165,7 +159,7 @@ const aesEcbDecrypt = (key, ciphertext) => {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
`aes-${key.length * 8}-ecb`,
|
||||
key,
|
||||
Buffer.alloc(0),
|
||||
null,
|
||||
)
|
||||
return Buffer.concat([decipher.update(ciphertext), decipher.final()])
|
||||
}
|
||||
@@ -206,19 +200,9 @@ const xeapiMidTransform = (ciphertext) => {
|
||||
for (let i = 0; i < ciphertext.length; i++) {
|
||||
xored[i] = ciphertext[i] ^ random[i & 0x0f]
|
||||
}
|
||||
const rot = xored.length ? (random[0] & 0x0f) % xored.length : 0
|
||||
return Buffer.concat([random, xored.subarray(rot), xored.subarray(0, rot)])
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder('utf-8', { fatal: true })
|
||||
|
||||
const isUtf8 = (buf) => {
|
||||
try {
|
||||
decoder.decode(buf)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
const b64 = Buffer.from(xored.toString('base64'))
|
||||
const rot = b64.length ? (random[0] & 0x0f) % b64.length : 0
|
||||
return Buffer.concat([random, b64.subarray(rot), b64.subarray(0, rot)])
|
||||
}
|
||||
|
||||
const xeapiEncryptS = (dynamicKey, publicKeyState, os) => {
|
||||
@@ -261,13 +245,7 @@ const buildXeapiPlaintext = (uri, data, options = {}) => {
|
||||
const bodyData = { ...data }
|
||||
delete bodyData.e_r
|
||||
const body = Buffer.from(new URLSearchParams(bodyData).toString())
|
||||
if (body.length) {
|
||||
if (mediaType === 'application/x-www-form-urlencoded' && isUtf8(body)) {
|
||||
fields.content = body.toString('utf8')
|
||||
} else {
|
||||
fields.body = body.toString('base64')
|
||||
}
|
||||
}
|
||||
fields.body = body.toString('base64')
|
||||
}
|
||||
|
||||
if (fields.queryString) {
|
||||
@@ -290,7 +268,7 @@ const xeapi = (uri, data, options = {}) => {
|
||||
const dynamicKey = activeSessionKey || crypto.randomBytes(16)
|
||||
const plaintext = Buffer.from(buildXeapiPlaintext(uri, data, options))
|
||||
|
||||
const c = aesEcbEncrypt(
|
||||
const b = aesEcbEncrypt(
|
||||
dynamicKey,
|
||||
xeapiMidTransform(aesEcbEncrypt(xeapiStaticKey, plaintext)),
|
||||
)
|
||||
@@ -303,7 +281,7 @@ const xeapi = (uri, data, options = {}) => {
|
||||
)
|
||||
|
||||
return {
|
||||
C: c.toString('base64url'),
|
||||
B: b.toString('base64'),
|
||||
S: s.toString('base64'),
|
||||
R: r.toString('base64'),
|
||||
}
|
||||
@@ -327,86 +305,11 @@ const xeapiDecryptPublicKey = (encryptedData) => {
|
||||
)
|
||||
}
|
||||
|
||||
const NEAPI_MAGIC = Buffer.from('CSJM')
|
||||
const NEAPI_NONCE_LEN = 12
|
||||
const NEAPI_AD_LEN = 16
|
||||
const NEAPI_TAG_LEN = 16
|
||||
const NEAPI_HEADER_LEN = 4 + 1 + 1 + 2 + NEAPI_NONCE_LEN + 1 + NEAPI_AD_LEN + 4
|
||||
|
||||
// Header: magic, the config version as two big-endian bytes, then the
|
||||
// nonce / associated data / sealed length.
|
||||
const neapi = (data, config) => {
|
||||
const nonce = crypto.randomBytes(NEAPI_NONCE_LEN)
|
||||
const ad = crypto.randomBytes(NEAPI_AD_LEN)
|
||||
const cipher = crypto.createCipheriv(
|
||||
'chacha20-poly1305',
|
||||
config.encryptKey,
|
||||
nonce,
|
||||
{ authTagLength: NEAPI_TAG_LEN },
|
||||
)
|
||||
cipher.setAAD(ad)
|
||||
|
||||
const sealed = Buffer.concat([
|
||||
cipher.update(zstd.compress(data)),
|
||||
cipher.final(),
|
||||
cipher.getAuthTag(),
|
||||
])
|
||||
|
||||
const header = Buffer.alloc(NEAPI_HEADER_LEN)
|
||||
NEAPI_MAGIC.copy(header, 0)
|
||||
header[4] = config.version >> 8
|
||||
header[5] = config.version & 0xff
|
||||
header.writeUInt16LE(nonce.length, 6)
|
||||
nonce.copy(header, 8)
|
||||
header[8 + nonce.length] = ad.length
|
||||
ad.copy(header, 9 + nonce.length)
|
||||
header.writeUInt32LE(sealed.length, 9 + nonce.length + ad.length)
|
||||
|
||||
return Buffer.concat([header, sealed]).toString('base64')
|
||||
}
|
||||
|
||||
// Some error responses repeat the same document back to back.
|
||||
const parseBody = (text) => {
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch (error) {
|
||||
const boundary = text.indexOf('}{')
|
||||
if (boundary < 0) throw error
|
||||
return JSON.parse(text.slice(0, boundary + 1))
|
||||
}
|
||||
}
|
||||
|
||||
const neapiResDecrypt = (body, config) => {
|
||||
const raw = Buffer.from(String(body).trim(), 'base64')
|
||||
const nonceLen = raw.readUInt16LE(6)
|
||||
const nonce = raw.subarray(8, 8 + nonceLen)
|
||||
const adLen = raw[8 + nonceLen]
|
||||
const ad = raw.subarray(9 + nonceLen, 9 + nonceLen + adLen)
|
||||
const offset = 9 + nonceLen + adLen
|
||||
const sealed = raw.subarray(offset + 4, offset + 4 + raw.readUInt32LE(offset))
|
||||
|
||||
const decipher = crypto.createDecipheriv(
|
||||
'chacha20-poly1305',
|
||||
config.decryptKey,
|
||||
nonce,
|
||||
{ authTagLength: NEAPI_TAG_LEN },
|
||||
)
|
||||
decipher.setAAD(ad)
|
||||
decipher.setAuthTag(sealed.subarray(sealed.length - NEAPI_TAG_LEN))
|
||||
|
||||
const compressed = Buffer.concat([
|
||||
decipher.update(sealed.subarray(0, sealed.length - NEAPI_TAG_LEN)),
|
||||
decipher.final(),
|
||||
])
|
||||
return parseBody(zstd.decompress(compressed).toString())
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
weapi,
|
||||
linuxapi,
|
||||
eapi,
|
||||
xeapi,
|
||||
neapi,
|
||||
decrypt,
|
||||
aesEncrypt,
|
||||
aesDecrypt,
|
||||
@@ -415,5 +318,4 @@ module.exports = {
|
||||
xeapiSign,
|
||||
xeapiResDecrypt,
|
||||
xeapiDecryptPublicKey,
|
||||
neapiResDecrypt,
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
const crypto = require('crypto')
|
||||
const zstd = require('./zstd')
|
||||
|
||||
// gorilla/algorithm/record 返回的 base64 解码后:
|
||||
// [0 .. 64) RSA 块 -> RSA_PKCS1 去填充得到 RC4 密钥
|
||||
// [64 .. 68) u32 LE 载荷长度
|
||||
// [68 .. ) RC4 载荷 -> zstd 解压得到 ECFG 容器
|
||||
const CONFIG_PUBLIC_KEY = Buffer.from(
|
||||
'3048024100e4e79c1cb27a9fab7d2c740e99e923ec51c009169ec3a380c60404' +
|
||||
'e01b060f0160cdc631d9fb7fe53b228a5ff11bee42b965d28c6c2036193f4ccd' +
|
||||
'c7ed853d110203010001',
|
||||
'hex',
|
||||
)
|
||||
|
||||
// 容器里的 Lua 区块对指令和字符串做了逐字节异或混淆
|
||||
const CODE_XOR = [
|
||||
0x94b4f825, 0x977c6795, 0x327de32b, 0x16da5ba1, 0xcf55a2eb, 0x080bb445,
|
||||
0x021af055, 0xfc56b8bd,
|
||||
]
|
||||
const STR_XOR = Buffer.from('aefda2d1832b518a7741c325fb2687f0', 'hex')
|
||||
const CHUNK_SIGNATURE = Buffer.from('7f454c46', 'hex')
|
||||
|
||||
// 脚本用这两个标记区分请求方向与响应方向
|
||||
const DIRECTION_ENCRYPT = 251
|
||||
const DIRECTION_DECRYPT = 503
|
||||
const KEY_LEN = 32
|
||||
|
||||
const T_NUMFLT = 3
|
||||
const T_NUMINT = 19
|
||||
const OP_LOADK = 1
|
||||
const OP_GETUPVAL = 5
|
||||
const OP_EQ = 31
|
||||
|
||||
const op = (ins) => ins & 0x3f
|
||||
const argA = (ins) => (ins >>> 6) & 0xff
|
||||
const argB = (ins) => (ins >>> 23) & 0x1ff
|
||||
const argC = (ins) => (ins >>> 14) & 0x1ff
|
||||
const argBx = (ins) => (ins >>> 14) & 0x3ffff
|
||||
|
||||
const rc4 = (key, data) => {
|
||||
const s = Buffer.alloc(256)
|
||||
for (let i = 0; i < 256; i++) s[i] = i
|
||||
|
||||
let j = 0
|
||||
for (let i = 0; i < 256; i++) {
|
||||
j = (j + s[i] + key[i % key.length]) & 0xff
|
||||
;[s[i], s[j]] = [s[j], s[i]]
|
||||
}
|
||||
|
||||
const out = Buffer.allocUnsafe(data.length)
|
||||
let i = 0
|
||||
j = 0
|
||||
for (let n = 0; n < data.length; n++) {
|
||||
i = (i + 1) & 0xff
|
||||
j = (j + s[i]) & 0xff
|
||||
;[s[i], s[j]] = [s[j], s[i]]
|
||||
out[n] = data[n] ^ s[(s[i] + s[j]) & 0xff]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const unwrapContainer = (raw) => {
|
||||
const key = crypto.publicDecrypt(
|
||||
crypto.createPublicKey({
|
||||
key: CONFIG_PUBLIC_KEY,
|
||||
format: 'der',
|
||||
type: 'pkcs1',
|
||||
}),
|
||||
raw.subarray(0, 64),
|
||||
)
|
||||
const size = raw.readUInt32LE(64)
|
||||
return zstd.decompress(rc4(key, raw.subarray(68, 68 + size)))
|
||||
}
|
||||
|
||||
class ChunkReader {
|
||||
constructor(buf, pos = 0) {
|
||||
this.buf = buf
|
||||
this.pos = pos
|
||||
}
|
||||
|
||||
u8() {
|
||||
return this.buf[this.pos++]
|
||||
}
|
||||
|
||||
u32() {
|
||||
const value = this.buf.readUInt32LE(this.pos)
|
||||
this.pos += 4
|
||||
return value
|
||||
}
|
||||
|
||||
i64() {
|
||||
const value = this.buf.readBigInt64LE(this.pos)
|
||||
this.pos += 8
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
f64() {
|
||||
const value = this.buf.readDoubleLE(this.pos)
|
||||
this.pos += 8
|
||||
return value
|
||||
}
|
||||
|
||||
string() {
|
||||
let size = this.u8()
|
||||
if (size === 0) return null
|
||||
if (size === 0xff) size = this.u32()
|
||||
// 长度比实际字节数多 1(标准 luac 的写法)
|
||||
const body = this.buf.subarray(this.pos, this.pos + size - 1)
|
||||
this.pos += size - 1
|
||||
const out = Buffer.allocUnsafe(body.length)
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
out[i] = body[i] ^ STR_XOR[i & 0x0f]
|
||||
}
|
||||
return out.toString('utf8')
|
||||
}
|
||||
}
|
||||
|
||||
const loadProto = (r) => {
|
||||
const proto = {
|
||||
source: r.string(),
|
||||
linedefined: r.u32(),
|
||||
lastlinedefined: r.u32(),
|
||||
numparams: r.u8(),
|
||||
isvararg: r.u8(),
|
||||
maxstack: r.u8(),
|
||||
}
|
||||
|
||||
let count = r.u32()
|
||||
proto.code = new Array(count)
|
||||
for (let i = 0; i < count; i++) {
|
||||
proto.code[i] = (r.u32() ^ CODE_XOR[i & 7]) >>> 0
|
||||
}
|
||||
|
||||
// 常量标签:0 nil / 1 boolean / 3 float / 19 integer / 4,20 string
|
||||
// 只保留用得到的整数与字符串
|
||||
count = r.u32()
|
||||
proto.consts = new Array(count)
|
||||
for (let i = 0; i < count; i++) {
|
||||
const tag = r.u8()
|
||||
if (tag === 0) proto.consts[i] = null
|
||||
else if (tag === 1) {
|
||||
r.u8()
|
||||
proto.consts[i] = null
|
||||
} else if (tag === T_NUMFLT) {
|
||||
r.f64()
|
||||
proto.consts[i] = null
|
||||
} else if (tag === T_NUMINT) proto.consts[i] = r.i64()
|
||||
else proto.consts[i] = r.string()
|
||||
}
|
||||
|
||||
count = r.u32()
|
||||
proto.upvals = new Array(count)
|
||||
for (let i = 0; i < count; i++) {
|
||||
proto.upvals[i] = [r.u8(), r.u8()]
|
||||
}
|
||||
|
||||
count = r.u32()
|
||||
proto.protos = new Array(count)
|
||||
for (let i = 0; i < count; i++) {
|
||||
proto.protos[i] = loadProto(r)
|
||||
}
|
||||
|
||||
// 调试段跳过即可
|
||||
count = r.u32()
|
||||
r.pos += 4 * count
|
||||
count = r.u32()
|
||||
for (let i = 0; i < count; i++) {
|
||||
r.string()
|
||||
r.u32()
|
||||
r.u32()
|
||||
}
|
||||
count = r.u32()
|
||||
for (let i = 0; i < count; i++) {
|
||||
r.string()
|
||||
}
|
||||
return proto
|
||||
}
|
||||
|
||||
const loadChunk = (buf) => {
|
||||
if (!buf.subarray(0, 4).equals(CHUNK_SIGNATURE)) {
|
||||
throw new Error('neapi config is not a chunk')
|
||||
}
|
||||
const r = new ChunkReader(buf, 33)
|
||||
r.u8() // 主闭包 upvalue 数量
|
||||
return loadProto(r)
|
||||
}
|
||||
|
||||
// 主函数用一连串 LOADK 装载所有标量,据此可以把 upvalue 追回常量值
|
||||
const mainRegisters = (main) => {
|
||||
const regs = new Map()
|
||||
for (const ins of main.code) {
|
||||
if (op(ins) === OP_LOADK) regs.set(argA(ins), main.consts[argBx(ins)])
|
||||
}
|
||||
return regs
|
||||
}
|
||||
|
||||
// create_ctx 里方向 -> 密钥偏移的分支:
|
||||
// if direction == <a> then offset = <upvalue>
|
||||
// elseif direction == <b> then offset = <upvalue>
|
||||
const extractKeyOffsets = (script) => {
|
||||
const createCtx = script.protos[0]
|
||||
const regs = mainRegisters(script)
|
||||
const offsets = {}
|
||||
let pending = null
|
||||
|
||||
for (const ins of createCtx.code) {
|
||||
const code = op(ins)
|
||||
if (code === OP_EQ) {
|
||||
const c = argC(ins)
|
||||
const constant = c & 0x100 && createCtx.consts[c & 0xff]
|
||||
if (Number.isInteger(constant)) pending = constant
|
||||
} else if (code === OP_GETUPVAL && pending !== null) {
|
||||
const up = createCtx.upvals[argB(ins)]
|
||||
const constant = up && up[0] === 1 && regs.get(up[1])
|
||||
if (Number.isInteger(constant)) {
|
||||
offsets[pending] = constant
|
||||
pending = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(DIRECTION_ENCRYPT in offsets) || !(DIRECTION_DECRYPT in offsets)) {
|
||||
throw new Error('neapi config script has an unexpected direction table')
|
||||
}
|
||||
return offsets
|
||||
}
|
||||
|
||||
const extractKeyBlob = (script) => {
|
||||
const blob = script.consts
|
||||
.filter(
|
||||
(c) => typeof c === 'string' && /^[A-Za-z0-9+/]{64,}={0,2}$/.test(c),
|
||||
)
|
||||
.sort((a, b) => b.length - a.length)[0]
|
||||
return Buffer.from(blob, 'base64')
|
||||
}
|
||||
|
||||
const decodeConfig = (raw) => {
|
||||
const ecfg = unwrapContainer(raw)
|
||||
// ECFG 头 4 字节 + 大端 u16 版本,之后是 Lua 区块
|
||||
const version = ecfg.readUInt16BE(4)
|
||||
const script = loadChunk(ecfg.subarray(6))
|
||||
|
||||
const offsets = extractKeyOffsets(script)
|
||||
const keyBlob = extractKeyBlob(script)
|
||||
const slice = (direction) =>
|
||||
keyBlob.subarray(offsets[direction], offsets[direction] + KEY_LEN)
|
||||
|
||||
return {
|
||||
version,
|
||||
keyBlob,
|
||||
encryptKey: slice(DIRECTION_ENCRYPT),
|
||||
decryptKey: slice(DIRECTION_DECRYPT),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
decodeConfig,
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// 配置会轮换,启动时由 /register/neapikey 拉取后缓存到这里
|
||||
const neapiKeyPath = path.resolve(require('os').tmpdir(), 'neapi_key')
|
||||
|
||||
let neapi_key = null
|
||||
|
||||
const loadNeapiKey = () => {
|
||||
if (!neapi_key && fs.existsSync(neapiKeyPath)) {
|
||||
try {
|
||||
const stored = JSON.parse(fs.readFileSync(neapiKeyPath, 'utf-8'))
|
||||
neapi_key = {
|
||||
version: stored.version,
|
||||
circleTime: stored.circleTime,
|
||||
fetchedAt: stored.fetchedAt,
|
||||
keyBlob: Buffer.from(stored.keyBlob, 'base64'),
|
||||
encryptKey: Buffer.from(stored.encryptKey, 'base64'),
|
||||
decryptKey: Buffer.from(stored.decryptKey, 'base64'),
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[ERR]', error)
|
||||
}
|
||||
}
|
||||
return neapi_key
|
||||
}
|
||||
|
||||
const saveNeapiKey = (config, circleTime) => {
|
||||
const fetchedAt = Date.now()
|
||||
fs.writeFileSync(
|
||||
neapiKeyPath,
|
||||
JSON.stringify({
|
||||
version: config.version,
|
||||
circleTime,
|
||||
fetchedAt,
|
||||
keyBlob: config.keyBlob.toString('base64'),
|
||||
encryptKey: config.encryptKey.toString('base64'),
|
||||
decryptKey: config.decryptKey.toString('base64'),
|
||||
}),
|
||||
'utf-8',
|
||||
)
|
||||
neapi_key = { ...config, circleTime, fetchedAt }
|
||||
return neapi_key
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadNeapiKey,
|
||||
saveNeapiKey,
|
||||
}
|
||||
+66
-127
@@ -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,
|
||||
@@ -18,34 +16,12 @@ const {
|
||||
} = require('./index')
|
||||
const { URLSearchParams, URL } = require('url')
|
||||
const { APP_CONF } = require('./config.json')
|
||||
const { loadNeapiKey } = require('./neapiKey')
|
||||
const {
|
||||
getToken: antiCheatTokenV2,
|
||||
} = require('../module/register_checktoken_v2')
|
||||
const {
|
||||
getToken: antiCheatTokenV3,
|
||||
} = require('../module/register_checktoken_v3')
|
||||
const registerNeapikey = require('../module/register_neapikey')
|
||||
|
||||
// 预先读取匿名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
|
||||
@@ -73,20 +49,6 @@ const WNMCID = (function () {
|
||||
let NMTID = ''
|
||||
let NMTID_RETRIES_LEFT = 3
|
||||
|
||||
// neapi 配置过期时只让一个请求去刷新,并发的其余请求共用同一次结果
|
||||
let neapiUserStatus = ''
|
||||
let neapiRefreshing = null
|
||||
const refreshNeapiKey = (version) => {
|
||||
if (!neapiRefreshing) {
|
||||
neapiRefreshing = registerNeapikey({ version, behavior: 'update' }, null)
|
||||
.catch((error) => console.log('[ERR]', error))
|
||||
.finally(() => {
|
||||
neapiRefreshing = null
|
||||
})
|
||||
}
|
||||
return neapiRefreshing
|
||||
}
|
||||
|
||||
// 预先定义osMap
|
||||
const osMap = {
|
||||
pc: {
|
||||
@@ -143,7 +105,6 @@ const DOMAIN = APP_CONF.domain
|
||||
const API_DOMAIN = APP_CONF.apiDomain
|
||||
const EAPI_DOMAIN = APP_CONF.eapiDomain
|
||||
const XEAPI_DOMAIN = APP_CONF.xeapiDomain
|
||||
const NEAPI_DOMAIN = APP_CONF.neapiDomain
|
||||
const ENCRYPT_RESPONSE = APP_CONF.encryptResponse
|
||||
const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
|
||||
|
||||
@@ -186,7 +147,8 @@ const processCookieObject = (cookie, crypto) => {
|
||||
}
|
||||
|
||||
if (!processedCookie.MUSIC_U) {
|
||||
processedCookie.MUSIC_A = processedCookie.MUSIC_A || anonymous_token
|
||||
processedCookie.MUSIC_A =
|
||||
processedCookie.MUSIC_A || runtimeState.anonymousToken
|
||||
}
|
||||
|
||||
return processedCookie
|
||||
@@ -213,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) {
|
||||
@@ -226,6 +215,32 @@ 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 } : {}
|
||||
@@ -290,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')
|
||||
}
|
||||
@@ -338,47 +353,6 @@ const createRequest = async (uri, data, options) => {
|
||||
})
|
||||
break
|
||||
|
||||
case 'neapi':
|
||||
const neapiKey = loadNeapiKey()
|
||||
if (!neapiKey) {
|
||||
throw new Error('neapi config is missing')
|
||||
}
|
||||
// 超过 circleTime 就后台刷新,本次仍用手上的配置
|
||||
if (now() - neapiKey.fetchedAt >= neapiKey.circleTime) {
|
||||
refreshNeapiKey(neapiKey.version)
|
||||
}
|
||||
const neapiCookie = {
|
||||
osver: cookie.osver,
|
||||
deviceId: cookie.deviceId,
|
||||
os: cookie.os,
|
||||
appver: cookie.appver,
|
||||
versioncode: cookie.versioncode || '140',
|
||||
mobilename: cookie.mobilename || '',
|
||||
buildver: cookie.buildver || now().toString().substr(0, 10),
|
||||
resolution: cookie.resolution || '1920x1080',
|
||||
__csrf: csrfToken,
|
||||
channel: cookie.channel,
|
||||
requestId: generateRequestId(),
|
||||
}
|
||||
if (cookie.MUSIC_U) neapiCookie['MUSIC_U'] = cookie.MUSIC_U
|
||||
if (cookie.MUSIC_A) neapiCookie['MUSIC_A'] = cookie.MUSIC_A
|
||||
headers['Cookie'] = createHeaderCookie(neapiCookie)
|
||||
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'android')
|
||||
headers['content-type'] =
|
||||
'application/x-www-form-urlencoded;charset=utf-8'
|
||||
// 服务端按 X-ER 选密钥,配置过期时由下面的 777/999 分支重拉并重试
|
||||
headers['X-ER'] = String(neapiKey.version)
|
||||
if (neapiUserStatus) headers['X-USER-STATUS'] = neapiUserStatus
|
||||
|
||||
const neapiBody = { ...data }
|
||||
delete neapiBody.e_r
|
||||
encryptData = encrypt.neapi(
|
||||
new URLSearchParams(neapiBody).toString(),
|
||||
neapiKey,
|
||||
)
|
||||
url = (options.domain || NEAPI_DOMAIN) + '/neapi/' + uri.substr(5)
|
||||
break
|
||||
|
||||
case 'eapi':
|
||||
case 'api':
|
||||
// header创建
|
||||
@@ -425,19 +399,12 @@ const createRequest = async (uri, data, options) => {
|
||||
console.log('[ERR]', 'Unknown Crypto:', crypto)
|
||||
break
|
||||
}
|
||||
const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r
|
||||
const use_xeapi = crypto === 'xeapi'
|
||||
const use_neapi = crypto === 'neapi'
|
||||
|
||||
// settings创建
|
||||
let settings = {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
headers: headers,
|
||||
// neapi 的请求体就是容器本身,不是表单字段
|
||||
data: use_neapi
|
||||
? encryptData
|
||||
: new URLSearchParams(encryptData).toString(),
|
||||
data: new URLSearchParams(encryptData).toString(),
|
||||
httpAgent: createHttpAgent(),
|
||||
httpsAgent: createHttpsAgent(),
|
||||
}
|
||||
@@ -448,7 +415,9 @@ const createRequest = async (uri, data, options) => {
|
||||
}
|
||||
|
||||
// 使用返回值加密
|
||||
if (use_e_r || use_xeapi || use_neapi) {
|
||||
const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r
|
||||
const use_xeapi = crypto === 'xeapi'
|
||||
if (use_e_r || use_xeapi) {
|
||||
settings.encoding = null
|
||||
settings.responseType = 'arraybuffer'
|
||||
}
|
||||
@@ -489,7 +458,7 @@ const createRequest = async (uri, data, options) => {
|
||||
}
|
||||
// console.log(settings.headers);
|
||||
axios(settings)
|
||||
.then(async (res) => {
|
||||
.then((res) => {
|
||||
const body = res.data
|
||||
const setCookies = res.headers['set-cookie'] || []
|
||||
|
||||
@@ -503,11 +472,7 @@ const createRequest = async (uri, data, options) => {
|
||||
!cookie.NMTID
|
||||
) {
|
||||
NMTID_RETRIES_LEFT--
|
||||
answer.cookie = (
|
||||
typeof res.headers['set-cookie'] === 'string'
|
||||
? [res.headers['set-cookie']]
|
||||
: res.headers['set-cookie'] || []
|
||||
).map((x) => {
|
||||
answer.cookie = setCookies.map((x) => {
|
||||
const cleaned = cleanCookie(x)
|
||||
const match = x.match(/(?:^|;\s*)NMTID=([^;]+)/)
|
||||
if (match) {
|
||||
@@ -520,30 +485,6 @@ const createRequest = async (uri, data, options) => {
|
||||
answer.cookie = setCookies.map(cleanCookie)
|
||||
}
|
||||
|
||||
// 空值同样要记下来,否则状态恢复后会一直带着上一次的值
|
||||
const userStatus = res.headers['x-user-status'] || ''
|
||||
if (use_neapi) {
|
||||
neapiUserStatus = userStatus
|
||||
if (
|
||||
(userStatus === '777' || userStatus === '999') &&
|
||||
!options.neapiRetried
|
||||
) {
|
||||
// 配置已轮换:刷新后重试一次
|
||||
await refreshNeapiKey(loadNeapiKey().version)
|
||||
try {
|
||||
resolve(
|
||||
await createRequest(uri, data, {
|
||||
...options,
|
||||
neapiRetried: true,
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// debug: 统一注释块,需要时取消注释查看请求/返回的原始密文
|
||||
|
||||
// logger.debug(`[${crypto}]`, uri)
|
||||
@@ -562,11 +503,6 @@ const createRequest = async (uri, data, options) => {
|
||||
xeapiSessionKey = res.headers['x-encr-sskey']
|
||||
}
|
||||
answer.body = encrypt.xeapiResDecrypt(Buffer.from(body))
|
||||
} else if (use_neapi) {
|
||||
answer.body = encrypt.neapiResDecrypt(
|
||||
Buffer.from(body).toString(),
|
||||
loadNeapiKey(),
|
||||
)
|
||||
} else if (use_e_r) {
|
||||
answer.body = encrypt.eapiResDecrypt(
|
||||
body.toString('hex').toUpperCase(),
|
||||
@@ -612,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,
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
const zlib = require('zlib')
|
||||
const { decompress: fzstdDecompress } = require('fzstd')
|
||||
|
||||
// Node 22.15+ 自带 zstd。更早的版本(CI 含 18)用 fzstd 解压,压缩则退回到
|
||||
// raw block 帧:内容原样存储,任何 zstd 解码器都能读,只是没有压缩率。
|
||||
const hasNative = typeof zlib.zstdCompressSync === 'function'
|
||||
|
||||
const MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd])
|
||||
const WINDOW_DESCRIPTOR = 0x50 // windowLog 20
|
||||
const BLOCK_MAX = 0x20000 // zstd 单块上限 128 KiB
|
||||
|
||||
const compressRawFrame = (input) => {
|
||||
const parts = [MAGIC, Buffer.from([0x00, WINDOW_DESCRIPTOR])]
|
||||
let offset = 0
|
||||
do {
|
||||
const block = input.subarray(offset, offset + BLOCK_MAX)
|
||||
const last = offset + BLOCK_MAX >= input.length
|
||||
const header = Buffer.alloc(3)
|
||||
header.writeUIntLE((block.length << 3) | (last ? 1 : 0), 0, 3)
|
||||
parts.push(header, block)
|
||||
offset += BLOCK_MAX
|
||||
} while (offset < input.length)
|
||||
return Buffer.concat(parts)
|
||||
}
|
||||
|
||||
const compress = (input) => {
|
||||
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input)
|
||||
return hasNative ? zlib.zstdCompressSync(buf) : compressRawFrame(buf)
|
||||
}
|
||||
|
||||
const decompress = (input) => {
|
||||
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input)
|
||||
return hasNative
|
||||
? zlib.zstdDecompressSync(buf)
|
||||
: Buffer.from(fzstdDecompress(buf))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compress,
|
||||
decompress,
|
||||
}
|
||||
Reference in New Issue
Block a user