mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-20 22:51:23 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6946dc8e14 | ||
|
|
22b78779ca | ||
|
|
35607f5050 | ||
|
|
60af95ac06 | ||
|
|
46073e5f50 | ||
|
|
bf12abe2f1 | ||
|
|
1491b91829 | ||
|
|
63f669d7f8 | ||
|
|
d525204dda | ||
|
|
6eb31984fd | ||
|
|
9faeb0aa25 | ||
|
|
f6db3e76e9 | ||
|
|
9163d48f0a | ||
|
|
1454ee216c | ||
|
|
83ee2cda34 | ||
|
|
3c352b5752 | ||
|
|
60ee927e32 | ||
|
|
eb07a525eb | ||
|
|
6fc4231142 | ||
|
|
fa69736300 | ||
|
|
73a5e9d788 | ||
|
|
dc6f56e8a8 | ||
|
|
2c9da8507d | ||
|
|
1db354464f | ||
|
|
395a80e74b | ||
|
|
6550c7c827 | ||
|
|
c959866436 |
@@ -0,0 +1,23 @@
|
|||||||
|
# Agent Instructions for NeteaseCloudMusicApiEnhanced
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
- **Package Manager**: Use `pnpm` (not npm or yarn).
|
||||||
|
- **Node Version**: Requires Node.js 18 or later.
|
||||||
|
|
||||||
|
## Developer Commands
|
||||||
|
- **Install dependencies**: `pnpm i`
|
||||||
|
- **Start server**: `pnpm start` or `node app.js`
|
||||||
|
- **Start dev server**: `pnpm dev` (uses nodemon)
|
||||||
|
- **Run tests**: `pnpm test` (uses Mocha)
|
||||||
|
- **Linting**: `pnpm lint` (check) or `pnpm lint-fix` (auto-fix)
|
||||||
|
|
||||||
|
## Architecture & Entrypoints
|
||||||
|
- **Executable Server**: `app.js` is the main entrypoint for running the API server.
|
||||||
|
- **Module Exports**: `main.js` is the entrypoint when the project is imported as a Node.js dependency.
|
||||||
|
- **API Endpoints**: Located in the `module/` directory. Each file typically corresponds to an API route.
|
||||||
|
- **Core Utilities**: Request handling, encryption, and core utilities are found in the `util/` directory.
|
||||||
|
|
||||||
|
## Important Gotchas & Quirks
|
||||||
|
- **Environment Variables**: The server defaults to port 3000 but can be overridden with the `PORT` environment variable.
|
||||||
|
- **Proxy Variables**: Be very careful with proxy environment variables (`http_proxy`, `https_proxy`, `no_proxy`). The request library (like axios) will automatically pick these up. If they point to an unavailable proxy (especially common in Docker environments), requests will fail silently or throw connection errors.
|
||||||
|
- **Code Style**: The project uses ESLint and Prettier. Always run `pnpm lint-fix` before committing changes to ensure formatting consistency.
|
||||||
@@ -45,14 +45,14 @@
|
|||||||
|
|
||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
- Node.js 18 及以上
|
- Node.js 22 及以上
|
||||||
- 推荐使用 pnpm 进行依赖管理
|
- 推荐使用 pnpm 进行依赖管理
|
||||||
|
|
||||||
### 安装
|
### 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/neteasecloudmusicapienhanced/api-enhanced.git
|
git clone https://github.com/neteasecloudmusicapienhanced/api-enhanced.git
|
||||||
cd api
|
cd api-enhanced
|
||||||
pnpm i
|
pnpm i
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -225,6 +225,8 @@ pnpm test
|
|||||||
|
|
||||||
[chaunsin/netease-cloud-music](https://github.com/chaunsin/netease-cloud-music)
|
[chaunsin/netease-cloud-music](https://github.com/chaunsin/netease-cloud-music)
|
||||||
|
|
||||||
|
[folltoshe/netease-report-listen-song](https://github.com/folltoshe/netease-report-listen-song)
|
||||||
|
|
||||||
### SDK 生态
|
### SDK 生态
|
||||||
|
|
||||||
| 语言 | 作者 | 地址 | 类型 |
|
| 语言 | 作者 | 地址 | 类型 |
|
||||||
|
|||||||
Vendored
+25
@@ -1474,6 +1474,20 @@ export function artist_new_song(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function artist_new_song_playall(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function artist_new_song_mv_list_v2(
|
||||||
|
params: {
|
||||||
|
startTimestamp?: number | string
|
||||||
|
before?: number | string
|
||||||
|
sourceType?: number | string
|
||||||
|
limit?: number | string
|
||||||
|
firstRequest?: boolean | string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function artist_detail(
|
export function artist_detail(
|
||||||
params: {
|
params: {
|
||||||
id: number | string
|
id: number | string
|
||||||
@@ -1594,6 +1608,10 @@ export function vip_growthpoint_get(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function vip_growthpoint_getall(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function artist_fans(
|
export function artist_fans(
|
||||||
params: { id: number | string } & MultiPageConfig & RequestBaseConfig,
|
params: { id: number | string } & MultiPageConfig & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
@@ -2049,6 +2067,13 @@ export function listen_data_report(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function listen_data_song_play_rank(
|
||||||
|
params: {
|
||||||
|
type?: string
|
||||||
|
endTime?: string | number
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function listen_data_today_song(
|
export function listen_data_today_song(
|
||||||
params: RequestBaseConfig,
|
params: RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// 获取关注歌手的新歌曲和 MV
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
startTimestamp: query.startTimestamp || query.before || Date.now(),
|
||||||
|
sourceType: query.sourceType || 1,
|
||||||
|
limit: query.limit || 10,
|
||||||
|
firstRequest: query.firstRequest ?? true,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/sub/artist/new/works/song-mv/list/v2`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 获取所有关注歌手最近的 50 首新歌
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
return request(
|
||||||
|
`/api/sub/artist/new/works/song/playall`,
|
||||||
|
{},
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// 听歌足迹 - 歌曲播放排行 (Top20)
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
return request(
|
||||||
|
`/api/content/activity/listen/data/song/play/rank`,
|
||||||
|
{
|
||||||
|
type: query.type || 'month', //周 week 月 month
|
||||||
|
endTime: query.endTime, // 不填就是本周/月的
|
||||||
|
},
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
+57
-5
@@ -1,8 +1,42 @@
|
|||||||
// 听歌打卡
|
// 听歌打卡
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
const { APP_CONF } = require('../util/config.json')
|
||||||
const data = {
|
const DOMAIN = APP_CONF.clDomian
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
// 注入 os=osx 的 cookie
|
||||||
|
let cookie = query.cookie || ''
|
||||||
|
if (typeof cookie === 'object') {
|
||||||
|
cookie = Object.assign({ os: 'osx' }, cookie)
|
||||||
|
} else if (typeof cookie === 'string') {
|
||||||
|
if (cookie.indexOf('os=') > -1) {
|
||||||
|
cookie = cookie.replace(/os=[^;]+/g, 'os=osx')
|
||||||
|
} else {
|
||||||
|
cookie = cookie + '; os=osx'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cookie = 'os=osx'
|
||||||
|
}
|
||||||
|
query.cookie = cookie
|
||||||
|
|
||||||
|
// 1) startplay → 进「最近播放」
|
||||||
|
const startplayData = {
|
||||||
|
logs: JSON.stringify([
|
||||||
|
{
|
||||||
|
action: 'startplay',
|
||||||
|
json: {
|
||||||
|
id: query.id,
|
||||||
|
type: 'song',
|
||||||
|
mainsite: '1',
|
||||||
|
mainsiteWeb: '1',
|
||||||
|
content: `id=${query.sourceid}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) play → 涨「听歌排行」计数
|
||||||
|
const playData = {
|
||||||
logs: JSON.stringify([
|
logs: JSON.stringify([
|
||||||
{
|
{
|
||||||
action: 'play',
|
action: 'play',
|
||||||
@@ -15,12 +49,30 @@ module.exports = (query, request) => {
|
|||||||
type: 'song',
|
type: 'song',
|
||||||
wifi: 0,
|
wifi: 0,
|
||||||
source: 'list',
|
source: 'list',
|
||||||
mainsite: 1,
|
mainsite: '1',
|
||||||
content: '',
|
mainsiteWeb: '1',
|
||||||
|
content: `id=${query.sourceid}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
return request(`/api/feedback/weblog`, data, createOption(query, 'weapi'))
|
const option = createOption(query, 'eapi')
|
||||||
|
option.domain = DOMAIN
|
||||||
|
|
||||||
|
// 发送两次请求
|
||||||
|
const res1 = await request(`/api/feedback/weblog`, startplayData, option)
|
||||||
|
const res2 = await request(`/api/feedback/weblog`, playData, option)
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: 'success',
|
||||||
|
details: {
|
||||||
|
startplay: res1.body,
|
||||||
|
play: res2.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// 听歌打卡 - NCBL 加密版 (仿桌面客户端 PLV/PLD 上报)
|
||||||
|
// 复制自 https://github.com/folltoshe/netease-report-listen-song 的 PC 端日志上报方式
|
||||||
|
//
|
||||||
|
// PLV 和 PLD 分两次独立上传
|
||||||
|
|
||||||
|
const {
|
||||||
|
buildPlv,
|
||||||
|
buildPld,
|
||||||
|
buildRecords,
|
||||||
|
extractContext,
|
||||||
|
parseCookie,
|
||||||
|
buildCookieStr,
|
||||||
|
buildMetaJson,
|
||||||
|
doUpload,
|
||||||
|
} = require('../util/ncbl')
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
// --- 参数校验 ---
|
||||||
|
const songId = Number(query.id)
|
||||||
|
if (!songId || isNaN(songId)) {
|
||||||
|
return { status: 400, body: { code: 400, msg: '缺少有效的 id (歌曲ID)' } }
|
||||||
|
}
|
||||||
|
const playTime = Number(query.time)
|
||||||
|
if (isNaN(playTime) || playTime <= 0) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
body: { code: 400, msg: '缺少有效的 time (播放时长)' },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const totalTime = Number(query.total) || playTime
|
||||||
|
const sourceId = String(query.sourceid || query.sourceId || '')
|
||||||
|
const sourceName = query.source || 'list'
|
||||||
|
|
||||||
|
// --- 解析认证上下文 ---
|
||||||
|
const rawCookie = query.cookie || ''
|
||||||
|
const cookieObj = parseCookie(rawCookie)
|
||||||
|
cookieObj.os = 'pc'
|
||||||
|
const ctx = extractContext(cookieObj)
|
||||||
|
|
||||||
|
// 兜底取 token
|
||||||
|
if (!ctx.auth.token && rawCookie) {
|
||||||
|
const parsed =
|
||||||
|
typeof rawCookie === 'string' ? parseCookie(rawCookie) : rawCookie
|
||||||
|
ctx.auth.token = parsed.MUSIC_U || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.auth.token) {
|
||||||
|
return { status: 401, body: { code: 401, msg: '缺少 MUSIC_U 鉴权令牌' } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 构建歌曲和来源 ---
|
||||||
|
const song = {
|
||||||
|
id: songId,
|
||||||
|
name: query.name || '',
|
||||||
|
artist: query.artist || '',
|
||||||
|
bitrate: Number(query.bitrate) || 320,
|
||||||
|
level: query.level || 'exhigh',
|
||||||
|
vip: query.vip === 'true' || query.vip === true,
|
||||||
|
time: totalTime,
|
||||||
|
}
|
||||||
|
const source = {
|
||||||
|
id: sourceId || String(songId),
|
||||||
|
type: 'track',
|
||||||
|
name: sourceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
const metaJson = buildMetaJson(ctx)
|
||||||
|
const cookieStr = buildCookieStr(ctx)
|
||||||
|
|
||||||
|
const ts = Math.floor(Date.now() / 1000)
|
||||||
|
const played = Math.min(playTime, totalTime)
|
||||||
|
|
||||||
|
const plvBody = buildRecords([
|
||||||
|
{ time: ts, action: '_plv', data: buildPlv(ctx, song, source) },
|
||||||
|
])
|
||||||
|
const pldBody = buildRecords([
|
||||||
|
{ time: ts, action: '_pld', data: buildPld(ctx, song, source, played) },
|
||||||
|
])
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1) 上传 PLV
|
||||||
|
const plv = await doUpload(ctx, metaJson, plvBody, cookieStr, 'PLV')
|
||||||
|
if (!plv.success) {
|
||||||
|
const rateMsg =
|
||||||
|
plv.respBody?.data?.rate != null
|
||||||
|
? ` (rate=${plv.respBody.data.rate})`
|
||||||
|
: ''
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: plv.respBody?.code || -1,
|
||||||
|
msg: `PLV 上报失败${rateMsg}`,
|
||||||
|
details: plv.respBody,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 上传 PLD
|
||||||
|
const pld = await doUpload(ctx, metaJson, pldBody, cookieStr, 'PLD')
|
||||||
|
if (!pld.success) {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: pld.respBody?.code || -1,
|
||||||
|
msg: 'PLV 成功但 PLD 失败',
|
||||||
|
details: { plv: plv.respBody, pld: pld.respBody },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: 'scrobble_v1 上报成功',
|
||||||
|
details: {
|
||||||
|
plv: { fileName: plv.fileName, payloadSize: plv.payload.length },
|
||||||
|
pld: { fileName: pld.fileName, payloadSize: pld.payload.length },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
status: 502,
|
||||||
|
body: { code: 502, msg: `请求异常: ${err.message || err}` },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 一键领取所有会员成长值
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(
|
||||||
|
`/api/vipnewcenter/app/level/task/reward/getall`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'xeapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
+34
-5
@@ -1,11 +1,40 @@
|
|||||||
// 黑胶乐签打卡
|
// 黑胶乐签打卡
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
|
||||||
const data = {}
|
module.exports = async (query, request) => {
|
||||||
return request(
|
const results = {}
|
||||||
`/api/vip-center-bff/task/sign`,
|
|
||||||
data,
|
const taskSign = await request(
|
||||||
|
'/api/vip-center-bff/task/sign',
|
||||||
|
{},
|
||||||
createOption(query, 'weapi'),
|
createOption(query, 'weapi'),
|
||||||
)
|
)
|
||||||
|
results.taskSign = taskSign.body
|
||||||
|
|
||||||
|
const checkinDetail = await request(
|
||||||
|
'/api/vipnewcenter/app/level/user/checkin/history/detail',
|
||||||
|
{
|
||||||
|
signDayTime: Date.now(),
|
||||||
|
type: 1,
|
||||||
|
},
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
results.checkinDetail = checkinDetail.body
|
||||||
|
|
||||||
|
// 两个接口都返回 code=200 即视为打卡成功
|
||||||
|
const signed =
|
||||||
|
Number(results.taskSign?.code) === 200 &&
|
||||||
|
Number(results.checkinDetail?.code) === 200
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
...results,
|
||||||
|
signed,
|
||||||
|
message: signed ? '黑胶乐签打卡成功' : '黑胶乐签打卡失败',
|
||||||
|
},
|
||||||
|
cookie: taskSign.cookie,
|
||||||
|
status: 200,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// 黑胶乐签打卡历史
|
// 黑胶乐签打卡历史 / 状态查询
|
||||||
|
// 支持传入 type=0(用户信息栏)或 type=1(黑胶乐签)
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {
|
const data = {
|
||||||
type: '0',
|
type: query.type || '0',
|
||||||
}
|
}
|
||||||
return request(
|
return request(
|
||||||
`/api/vipnewcenter/app/minidesk/music/sign/pc`,
|
`/api/vipnewcenter/app/minidesk/music/sign/pc`,
|
||||||
data,
|
data,
|
||||||
createOption(query, 'xeapi'),
|
createOption(query, 'eapi'),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neteasecloudmusicapienhanced/api",
|
"name": "@neteasecloudmusicapienhanced/api",
|
||||||
"version": "4.35.1",
|
"version": "4.36.2",
|
||||||
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
|
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon app.js",
|
"dev": "nodemon app.js",
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
"lint": "eslint \"**/*.{js,ts}\"",
|
"lint": "eslint \"**/*.{js,ts}\"",
|
||||||
"lint-fix": "eslint --fix \"**/*.{js,ts}\"",
|
"lint-fix": "eslint --fix \"**/*.{js,ts}\"",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
|
"docs:format": "node scripts/format-docs.js",
|
||||||
|
"docs:check": "node scripts/format-docs.js --check",
|
||||||
"pkgwin": "pkg . -t node18-win-x64 -C GZip -o precompiled/app",
|
"pkgwin": "pkg . -t node18-win-x64 -C GZip -o precompiled/app",
|
||||||
"pkglinux": "pkg . -t node18-linux-x64 -C GZip -o precompiled/app",
|
"pkglinux": "pkg . -t node18-linux-x64 -C GZip -o precompiled/app",
|
||||||
"pkgmacos": "pkg . -t node18-macos-x64 -C GZip -o precompiled/app"
|
"pkgmacos": "pkg . -t node18-macos-x64 -C GZip -o precompiled/app"
|
||||||
@@ -73,8 +75,8 @@
|
|||||||
"data"
|
"data"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.2",
|
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.4",
|
||||||
"axios": "^1.17.0",
|
"axios": "^1.18.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
@@ -96,13 +98,13 @@
|
|||||||
"@types/express-fileupload": "^1.5.1",
|
"@types/express-fileupload": "^1.5.1",
|
||||||
"@types/mocha": "^10.0.10",
|
"@types/mocha": "^10.0.10",
|
||||||
"@types/node": "25.9.2",
|
"@types/node": "25.9.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
"@typescript-eslint/eslint-plugin": "^8.62.1",
|
||||||
"@typescript-eslint/parser": "^8.60.1",
|
"@typescript-eslint/parser": "^8.62.1",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-html": "^8.1.4",
|
"eslint-plugin-html": "^8.1.4",
|
||||||
"eslint-plugin-prettier": "^5.5.6",
|
"eslint-plugin-prettier": "^5.5.6",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.7.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"intelli-espower-loader": "^1.1.0",
|
"intelli-espower-loader": "^1.1.0",
|
||||||
"lint-staged": "^16.4.0",
|
"lint-staged": "^16.4.0",
|
||||||
@@ -110,7 +112,7 @@
|
|||||||
"nodemon": "^3.1.14",
|
"nodemon": "^3.1.14",
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
"power-assert": "^1.6.1",
|
"power-assert": "^1.6.1",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.9.4",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
Generated
+190
-158
@@ -9,11 +9,11 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@neteasecloudmusicapienhanced/unblockmusic-utils':
|
'@neteasecloudmusicapienhanced/unblockmusic-utils':
|
||||||
specifier: ^0.3.2
|
specifier: ^0.3.4
|
||||||
version: 0.3.2
|
version: 0.3.4
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.17.0
|
specifier: ^1.18.1
|
||||||
version: 1.17.0
|
version: 1.18.1
|
||||||
crypto-js:
|
crypto-js:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
@@ -73,11 +73,11 @@ importers:
|
|||||||
specifier: 25.9.2
|
specifier: 25.9.2
|
||||||
version: 25.9.2
|
version: 25.9.2
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ^8.60.1
|
specifier: ^8.62.1
|
||||||
version: 8.60.1(@typescript-eslint/parser@8.60.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)
|
version: 8.62.1(@typescript-eslint/parser@8.62.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: ^8.60.1
|
specifier: ^8.62.1
|
||||||
version: 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
version: 8.62.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.4
|
specifier: ^9.39.4
|
||||||
version: 9.39.4
|
version: 9.39.4
|
||||||
@@ -89,10 +89,10 @@ importers:
|
|||||||
version: 8.1.4
|
version: 8.1.4
|
||||||
eslint-plugin-prettier:
|
eslint-plugin-prettier:
|
||||||
specifier: ^5.5.6
|
specifier: ^5.5.6
|
||||||
version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3)
|
version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.9.4)
|
||||||
globals:
|
globals:
|
||||||
specifier: ^17.6.0
|
specifier: ^17.7.0
|
||||||
version: 17.6.0
|
version: 17.7.0
|
||||||
husky:
|
husky:
|
||||||
specifier: ^9.1.7
|
specifier: ^9.1.7
|
||||||
version: 9.1.7
|
version: 9.1.7
|
||||||
@@ -115,8 +115,8 @@ importers:
|
|||||||
specifier: ^1.6.1
|
specifier: ^1.6.1
|
||||||
version: 1.6.1
|
version: 1.6.1
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.8.3
|
specifier: ^3.9.4
|
||||||
version: 3.8.3
|
version: 3.9.4
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -222,8 +222,8 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.2':
|
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.4':
|
||||||
resolution: {integrity: sha512-H1ckEDXxR+sLUZKzCPhlP8kfSKgEZZp8GSL4+2BZcmCyerwCbSZzMxX9doacHfFjThRjmq90DbsrRnhnKAvrzA==}
|
resolution: {integrity: sha512-3Lu/eYnDqf9/rHxa5G38TZxM7QVUBZpzKMWBmT6STHaMb6ztFRfrVrejIC1r7IUvtCWkoRulD+8UlllNTEsd6g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
@@ -301,63 +301,63 @@ packages:
|
|||||||
'@types/serve-static@2.2.0':
|
'@types/serve-static@2.2.0':
|
||||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.60.1':
|
'@typescript-eslint/eslint-plugin@8.62.1':
|
||||||
resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==}
|
resolution: {integrity: sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^8.60.1
|
'@typescript-eslint/parser': ^8.62.1
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.60.1':
|
'@typescript-eslint/parser@8.62.1':
|
||||||
resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==}
|
resolution: {integrity: sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.60.1':
|
'@typescript-eslint/project-service@8.62.1':
|
||||||
resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==}
|
resolution: {integrity: sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.60.1':
|
'@typescript-eslint/scope-manager@8.62.1':
|
||||||
resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==}
|
resolution: {integrity: sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.60.1':
|
'@typescript-eslint/tsconfig-utils@8.62.1':
|
||||||
resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==}
|
resolution: {integrity: sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.60.1':
|
'@typescript-eslint/type-utils@8.62.1':
|
||||||
resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==}
|
resolution: {integrity: sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/types@8.60.1':
|
'@typescript-eslint/types@8.62.1':
|
||||||
resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==}
|
resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.60.1':
|
'@typescript-eslint/typescript-estree@8.62.1':
|
||||||
resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==}
|
resolution: {integrity: sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.60.1':
|
'@typescript-eslint/utils@8.62.1':
|
||||||
resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==}
|
resolution: {integrity: sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.60.1':
|
'@typescript-eslint/visitor-keys@8.62.1':
|
||||||
resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==}
|
resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@unblockneteasemusic/server@0.28.0':
|
'@unblockneteasemusic/server@0.28.0':
|
||||||
@@ -386,8 +386,8 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
acorn@8.16.0:
|
acorn@8.17.0:
|
||||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -485,8 +485,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
axios@1.17.0:
|
axios@1.18.1:
|
||||||
resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==}
|
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
@@ -513,8 +513,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==}
|
resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==}
|
||||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
|
||||||
body-parser@2.2.2:
|
body-parser@2.3.0:
|
||||||
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
|
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
brace-expansion@1.1.15:
|
brace-expansion@1.1.15:
|
||||||
@@ -523,8 +523,8 @@ packages:
|
|||||||
brace-expansion@2.1.1:
|
brace-expansion@2.1.1:
|
||||||
resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==}
|
resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==}
|
||||||
|
|
||||||
brace-expansion@5.0.6:
|
brace-expansion@5.0.7:
|
||||||
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
braces@3.0.3:
|
braces@3.0.3:
|
||||||
@@ -864,6 +864,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
es-abstract-get@1.0.0:
|
||||||
|
resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
es-abstract@1.24.2:
|
es-abstract@1.24.2:
|
||||||
resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
|
resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -884,8 +888,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
es-to-primitive@1.3.0:
|
es-to-primitive@1.3.4:
|
||||||
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
es5-ext@0.10.64:
|
es5-ext@0.10.64:
|
||||||
@@ -1169,8 +1173,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
form-data@4.0.5:
|
form-data@4.0.6:
|
||||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
forwarded@0.2.0:
|
forwarded@0.2.0:
|
||||||
@@ -1203,8 +1207,8 @@ packages:
|
|||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
function.prototype.name@1.1.8:
|
function.prototype.name@1.2.0:
|
||||||
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
|
resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
functions-have-names@1.2.3:
|
functions-have-names@1.2.3:
|
||||||
@@ -1258,8 +1262,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
globals@17.6.0:
|
globals@17.7.0:
|
||||||
resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==}
|
resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
globalthis@1.0.4:
|
globalthis@1.0.4:
|
||||||
@@ -1447,6 +1451,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
|
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-document.all@1.0.0:
|
||||||
|
resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
is-extglob@2.1.1:
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1557,8 +1565,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
js-yaml@4.2.0:
|
js-yaml@4.3.0:
|
||||||
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
|
resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
jsesc@2.5.2:
|
jsesc@2.5.2:
|
||||||
@@ -1758,8 +1766,8 @@ packages:
|
|||||||
next-tick@1.1.0:
|
next-tick@1.1.0:
|
||||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||||
|
|
||||||
node-abi@3.92.0:
|
node-abi@3.93.0:
|
||||||
resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==}
|
resolution: {integrity: sha512-Cu6yUpX5Iavugm8BeX7c0wgU9CvOqfd1yM6A1d2q2ZMjym7GjpASv2GdRcTq3Fx+Sb5OgBkEEpw4VnAbY6Y5RA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
node-fetch@2.7.0:
|
||||||
@@ -1998,8 +2006,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
|
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
prettier@3.8.3:
|
prettier@3.9.4:
|
||||||
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
resolution: {integrity: sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2036,8 +2044,8 @@ packages:
|
|||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
qs@6.15.2:
|
qs@6.15.3:
|
||||||
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
@@ -2053,6 +2061,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
range-parser@1.3.0:
|
||||||
|
resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
raw-body@2.5.3:
|
raw-body@2.5.3:
|
||||||
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
|
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -2153,8 +2165,8 @@ packages:
|
|||||||
secure-json-parse@2.7.0:
|
secure-json-parse@2.7.0:
|
||||||
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||||
|
|
||||||
semver@7.8.3:
|
semver@7.8.5:
|
||||||
resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==}
|
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2215,8 +2227,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
side-channel@1.1.0:
|
side-channel@1.1.1:
|
||||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
signal-exit@4.1.0:
|
signal-exit@4.1.0:
|
||||||
@@ -2381,8 +2393,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
|
resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
||||||
tar-fs@2.1.4:
|
tar-fs@2.1.5:
|
||||||
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
|
resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==}
|
||||||
|
|
||||||
tar-stream@2.2.0:
|
tar-stream@2.2.0:
|
||||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||||
@@ -2635,12 +2647,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
yargs@16.2.0:
|
yargs@16.2.2:
|
||||||
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
|
resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.3:
|
||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
yargs@18.0.0:
|
yargs@18.0.0:
|
||||||
@@ -2706,7 +2718,7 @@ snapshots:
|
|||||||
globals: 14.0.0
|
globals: 14.0.0
|
||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
import-fresh: 3.3.1
|
import-fresh: 3.3.1
|
||||||
js-yaml: 4.2.0
|
js-yaml: 4.3.0
|
||||||
minimatch: 3.1.5
|
minimatch: 3.1.5
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -2760,10 +2772,10 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.2':
|
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unblockneteasemusic/server': 0.28.0
|
'@unblockneteasemusic/server': 0.28.0
|
||||||
axios: 1.17.0
|
axios: 1.18.1
|
||||||
dotenv: 17.4.2
|
dotenv: 17.4.2
|
||||||
express: 4.22.2
|
express: 4.22.2
|
||||||
https: 1.0.0
|
https: 1.0.0
|
||||||
@@ -2855,14 +2867,14 @@ snapshots:
|
|||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/node': 25.9.2
|
'@types/node': 25.9.2
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.62.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/scope-manager': 8.60.1
|
'@typescript-eslint/scope-manager': 8.62.1
|
||||||
'@typescript-eslint/type-utils': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.62.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.62.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.62.1
|
||||||
eslint: 9.39.4
|
eslint: 9.39.4
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
@@ -2871,41 +2883,41 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.60.1(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.62.1(eslint@9.39.4)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.60.1
|
'@typescript-eslint/scope-manager': 8.62.1
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.62.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.4
|
eslint: 9.39.4
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.60.1(typescript@5.9.3)':
|
'@typescript-eslint/project-service@8.62.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.60.1':
|
'@typescript-eslint/scope-manager@8.62.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.62.1
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.60.1(typescript@5.9.3)':
|
'@typescript-eslint/tsconfig-utils@8.62.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.60.1(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.62.1(eslint@9.39.4)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.62.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.4
|
eslint: 9.39.4
|
||||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||||
@@ -2913,37 +2925,37 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/types@8.60.1': {}
|
'@typescript-eslint/types@8.62.1': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.60.1(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.62.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.60.1(typescript@5.9.3)
|
'@typescript-eslint/project-service': 8.62.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
'@typescript-eslint/visitor-keys': 8.60.1
|
'@typescript-eslint/visitor-keys': 8.62.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
semver: 7.8.3
|
semver: 7.8.5
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.60.1(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.62.1(eslint@9.39.4)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
|
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
|
||||||
'@typescript-eslint/scope-manager': 8.60.1
|
'@typescript-eslint/scope-manager': 8.62.1
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
'@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
|
||||||
eslint: 9.39.4
|
eslint: 9.39.4
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.60.1':
|
'@typescript-eslint/visitor-keys@8.62.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.1
|
'@typescript-eslint/types': 8.62.1
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
'@unblockneteasemusic/server@0.28.0':
|
'@unblockneteasemusic/server@0.28.0':
|
||||||
@@ -2964,13 +2976,13 @@ snapshots:
|
|||||||
|
|
||||||
acorn-es7-plugin@1.1.7: {}
|
acorn-es7-plugin@1.1.7: {}
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.16.0):
|
acorn-jsx@5.3.2(acorn@8.17.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.16.0
|
acorn: 8.17.0
|
||||||
|
|
||||||
acorn@5.7.4: {}
|
acorn@5.7.4: {}
|
||||||
|
|
||||||
acorn@8.16.0: {}
|
acorn@8.17.0: {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3060,10 +3072,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
|
|
||||||
axios@1.17.0:
|
axios@1.18.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.16.0
|
follow-redirects: 1.16.0
|
||||||
form-data: 4.0.5
|
form-data: 4.0.6
|
||||||
https-proxy-agent: 5.0.1
|
https-proxy-agent: 5.0.1
|
||||||
proxy-from-env: 2.1.0
|
proxy-from-env: 2.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3096,22 +3108,22 @@ snapshots:
|
|||||||
http-errors: 2.0.1
|
http-errors: 2.0.1
|
||||||
iconv-lite: 0.4.24
|
iconv-lite: 0.4.24
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
qs: 6.15.2
|
qs: 6.15.3
|
||||||
raw-body: 2.5.3
|
raw-body: 2.5.3
|
||||||
type-is: 1.6.18
|
type-is: 1.6.18
|
||||||
unpipe: 1.0.0
|
unpipe: 1.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
body-parser@2.2.2:
|
body-parser@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
bytes: 3.1.2
|
bytes: 3.1.2
|
||||||
content-type: 1.0.5
|
content-type: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
http-errors: 2.0.1
|
http-errors: 2.0.1
|
||||||
iconv-lite: 0.7.2
|
iconv-lite: 0.7.2
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
qs: 6.15.2
|
qs: 6.15.3
|
||||||
raw-body: 3.0.2
|
raw-body: 3.0.2
|
||||||
type-is: 2.1.0
|
type-is: 2.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3126,7 +3138,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
|
|
||||||
brace-expansion@5.0.6:
|
brace-expansion@5.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 4.0.4
|
balanced-match: 4.0.4
|
||||||
|
|
||||||
@@ -3468,6 +3480,13 @@ snapshots:
|
|||||||
|
|
||||||
environment@1.1.0: {}
|
environment@1.1.0: {}
|
||||||
|
|
||||||
|
es-abstract-get@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.2
|
||||||
|
is-callable: 1.2.7
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
|
||||||
es-abstract@1.24.2:
|
es-abstract@1.24.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
array-buffer-byte-length: 1.0.2
|
array-buffer-byte-length: 1.0.2
|
||||||
@@ -3482,8 +3501,8 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
es-object-atoms: 1.1.2
|
es-object-atoms: 1.1.2
|
||||||
es-set-tostringtag: 2.1.0
|
es-set-tostringtag: 2.1.0
|
||||||
es-to-primitive: 1.3.0
|
es-to-primitive: 1.3.4
|
||||||
function.prototype.name: 1.1.8
|
function.prototype.name: 1.2.0
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
get-proto: 1.0.1
|
get-proto: 1.0.1
|
||||||
get-symbol-description: 1.1.0
|
get-symbol-description: 1.1.0
|
||||||
@@ -3540,8 +3559,11 @@ snapshots:
|
|||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
hasown: 2.0.4
|
hasown: 2.0.4
|
||||||
|
|
||||||
es-to-primitive@1.3.0:
|
es-to-primitive@1.3.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
es-abstract-get: 1.0.0
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
is-date-object: 1.1.0
|
is-date-object: 1.1.0
|
||||||
is-symbol: 1.1.1
|
is-symbol: 1.1.1
|
||||||
@@ -3634,10 +3656,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
htmlparser2: 10.1.0
|
htmlparser2: 10.1.0
|
||||||
|
|
||||||
eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3):
|
eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.9.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.4
|
eslint: 9.39.4
|
||||||
prettier: 3.8.3
|
prettier: 3.9.4
|
||||||
prettier-linter-helpers: 1.0.1
|
prettier-linter-helpers: 1.0.1
|
||||||
synckit: 0.11.13
|
synckit: 0.11.13
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -3743,8 +3765,8 @@ snapshots:
|
|||||||
|
|
||||||
espree@10.4.0:
|
espree@10.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.16.0
|
acorn: 8.17.0
|
||||||
acorn-jsx: 5.3.2(acorn@8.16.0)
|
acorn-jsx: 5.3.2(acorn@8.17.0)
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
esprima@2.7.3: {}
|
esprima@2.7.3: {}
|
||||||
@@ -3807,7 +3829,7 @@ snapshots:
|
|||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
path-to-regexp: 0.1.13
|
path-to-regexp: 0.1.13
|
||||||
proxy-addr: 2.0.7
|
proxy-addr: 2.0.7
|
||||||
qs: 6.15.2
|
qs: 6.15.3
|
||||||
range-parser: 1.2.1
|
range-parser: 1.2.1
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
send: 0.19.2
|
send: 0.19.2
|
||||||
@@ -3823,7 +3845,7 @@ snapshots:
|
|||||||
express@5.2.1:
|
express@5.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
accepts: 2.0.0
|
accepts: 2.0.0
|
||||||
body-parser: 2.2.2
|
body-parser: 2.3.0
|
||||||
content-disposition: 1.1.0
|
content-disposition: 1.1.0
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
@@ -3842,8 +3864,8 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
proxy-addr: 2.0.7
|
proxy-addr: 2.0.7
|
||||||
qs: 6.15.2
|
qs: 6.15.3
|
||||||
range-parser: 1.2.1
|
range-parser: 1.3.0
|
||||||
router: 2.2.0
|
router: 2.2.0
|
||||||
send: 1.2.1
|
send: 1.2.1
|
||||||
serve-static: 2.2.1
|
serve-static: 2.2.1
|
||||||
@@ -3957,7 +3979,7 @@ snapshots:
|
|||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
form-data@4.0.5:
|
form-data@4.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
asynckit: 0.4.0
|
asynckit: 0.4.0
|
||||||
combined-stream: 1.0.8
|
combined-stream: 1.0.8
|
||||||
@@ -3990,14 +4012,17 @@ snapshots:
|
|||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
function.prototype.name@1.1.8:
|
function.prototype.name@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.9
|
call-bind: 1.0.9
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
define-properties: 1.2.1
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
functions-have-names: 1.2.3
|
functions-have-names: 1.2.3
|
||||||
|
has-property-descriptors: 1.0.2
|
||||||
hasown: 2.0.4
|
hasown: 2.0.4
|
||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
|
is-document.all: 1.0.0
|
||||||
|
|
||||||
functions-have-names@1.2.3: {}
|
functions-have-names@1.2.3: {}
|
||||||
|
|
||||||
@@ -4060,7 +4085,7 @@ snapshots:
|
|||||||
|
|
||||||
globals@14.0.0: {}
|
globals@14.0.0: {}
|
||||||
|
|
||||||
globals@17.6.0: {}
|
globals@17.7.0: {}
|
||||||
|
|
||||||
globalthis@1.0.4:
|
globalthis@1.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4187,7 +4212,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
hasown: 2.0.4
|
hasown: 2.0.4
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.1
|
||||||
|
|
||||||
into-stream@6.0.0:
|
into-stream@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4251,6 +4276,10 @@ snapshots:
|
|||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
|
|
||||||
|
is-document.all@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
is-extglob@2.1.1: {}
|
||||||
|
|
||||||
is-finalizationregistry@1.1.1:
|
is-finalizationregistry@1.1.1:
|
||||||
@@ -4349,7 +4378,7 @@ snapshots:
|
|||||||
|
|
||||||
joycon@3.1.1: {}
|
joycon@3.1.1: {}
|
||||||
|
|
||||||
js-yaml@4.2.0:
|
js-yaml@4.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
@@ -4471,7 +4500,7 @@ snapshots:
|
|||||||
|
|
||||||
minimatch@10.2.5:
|
minimatch@10.2.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.6
|
brace-expansion: 5.0.7
|
||||||
|
|
||||||
minimatch@3.1.5:
|
minimatch@3.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4498,7 +4527,7 @@ snapshots:
|
|||||||
glob: 10.5.0
|
glob: 10.5.0
|
||||||
he: 1.2.0
|
he: 1.2.0
|
||||||
is-path-inside: 3.0.3
|
is-path-inside: 3.0.3
|
||||||
js-yaml: 4.2.0
|
js-yaml: 4.3.0
|
||||||
log-symbols: 4.1.0
|
log-symbols: 4.1.0
|
||||||
minimatch: 9.0.9
|
minimatch: 9.0.9
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@@ -4507,7 +4536,7 @@ snapshots:
|
|||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
workerpool: 9.3.4
|
workerpool: 9.3.4
|
||||||
yargs: 17.7.2
|
yargs: 17.7.3
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
yargs-unparser: 2.0.0
|
yargs-unparser: 2.0.0
|
||||||
|
|
||||||
@@ -4553,9 +4582,9 @@ snapshots:
|
|||||||
|
|
||||||
next-tick@1.1.0: {}
|
next-tick@1.1.0: {}
|
||||||
|
|
||||||
node-abi@3.92.0:
|
node-abi@3.93.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.8.3
|
semver: 7.8.5
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
node-fetch@2.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4566,7 +4595,7 @@ snapshots:
|
|||||||
node-windows@1.0.0-beta.8:
|
node-windows@1.0.0-beta.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
xml: 1.0.1
|
xml: 1.0.1
|
||||||
yargs: 17.7.2
|
yargs: 17.7.3
|
||||||
|
|
||||||
nodemon@3.1.14:
|
nodemon@3.1.14:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4575,7 +4604,7 @@ snapshots:
|
|||||||
ignore-by-default: 1.0.1
|
ignore-by-default: 1.0.1
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
pstree.remy: 1.1.8
|
pstree.remy: 1.1.8
|
||||||
semver: 7.8.3
|
semver: 7.8.5
|
||||||
simple-update-notifier: 2.0.0
|
simple-update-notifier: 2.0.0
|
||||||
supports-color: 5.5.0
|
supports-color: 5.5.0
|
||||||
touch: 3.1.1
|
touch: 3.1.1
|
||||||
@@ -4750,9 +4779,9 @@ snapshots:
|
|||||||
https-proxy-agent: 5.0.1
|
https-proxy-agent: 5.0.1
|
||||||
node-fetch: 2.7.0
|
node-fetch: 2.7.0
|
||||||
progress: 2.0.3
|
progress: 2.0.3
|
||||||
semver: 7.8.3
|
semver: 7.8.5
|
||||||
tar-fs: 2.1.4
|
tar-fs: 2.1.5
|
||||||
yargs: 16.2.0
|
yargs: 16.2.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -4855,11 +4884,11 @@ snapshots:
|
|||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
mkdirp-classic: 0.5.3
|
mkdirp-classic: 0.5.3
|
||||||
napi-build-utils: 1.0.2
|
napi-build-utils: 1.0.2
|
||||||
node-abi: 3.92.0
|
node-abi: 3.93.0
|
||||||
pump: 3.0.4
|
pump: 3.0.4
|
||||||
rc: 1.2.8
|
rc: 1.2.8
|
||||||
simple-get: 4.0.1
|
simple-get: 4.0.1
|
||||||
tar-fs: 2.1.4
|
tar-fs: 2.1.5
|
||||||
tunnel-agent: 0.6.0
|
tunnel-agent: 0.6.0
|
||||||
|
|
||||||
prelude-ls@1.1.2: {}
|
prelude-ls@1.1.2: {}
|
||||||
@@ -4870,7 +4899,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
fast-diff: 1.3.0
|
||||||
|
|
||||||
prettier@3.8.3: {}
|
prettier@3.9.4: {}
|
||||||
|
|
||||||
process-nextick-args@2.0.1: {}
|
process-nextick-args@2.0.1: {}
|
||||||
|
|
||||||
@@ -4900,9 +4929,10 @@ snapshots:
|
|||||||
pngjs: 5.0.0
|
pngjs: 5.0.0
|
||||||
yargs: 15.4.1
|
yargs: 15.4.1
|
||||||
|
|
||||||
qs@6.15.2:
|
qs@6.15.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
es-define-property: 1.0.1
|
||||||
|
side-channel: 1.1.1
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
@@ -4914,6 +4944,8 @@ snapshots:
|
|||||||
|
|
||||||
range-parser@1.2.1: {}
|
range-parser@1.2.1: {}
|
||||||
|
|
||||||
|
range-parser@1.3.0: {}
|
||||||
|
|
||||||
raw-body@2.5.3:
|
raw-body@2.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
bytes: 3.1.2
|
bytes: 3.1.2
|
||||||
@@ -5044,7 +5076,7 @@ snapshots:
|
|||||||
|
|
||||||
secure-json-parse@2.7.0: {}
|
secure-json-parse@2.7.0: {}
|
||||||
|
|
||||||
semver@7.8.3: {}
|
semver@7.8.5: {}
|
||||||
|
|
||||||
send@0.19.2:
|
send@0.19.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5075,7 +5107,7 @@ snapshots:
|
|||||||
mime-types: 3.0.2
|
mime-types: 3.0.2
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
range-parser: 1.2.1
|
range-parser: 1.3.0
|
||||||
statuses: 2.0.2
|
statuses: 2.0.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -5154,7 +5186,7 @@ snapshots:
|
|||||||
object-inspect: 1.13.4
|
object-inspect: 1.13.4
|
||||||
side-channel-map: 1.0.1
|
side-channel-map: 1.0.1
|
||||||
|
|
||||||
side-channel@1.1.0:
|
side-channel@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
object-inspect: 1.13.4
|
object-inspect: 1.13.4
|
||||||
@@ -5174,7 +5206,7 @@ snapshots:
|
|||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
simple-update-notifier@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.8.3
|
semver: 7.8.5
|
||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|
||||||
@@ -5339,7 +5371,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@pkgr/core': 0.3.6
|
'@pkgr/core': 0.3.6
|
||||||
|
|
||||||
tar-fs@2.1.4:
|
tar-fs@2.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
chownr: 1.1.4
|
chownr: 1.1.4
|
||||||
mkdirp-classic: 0.5.3
|
mkdirp-classic: 0.5.3
|
||||||
@@ -5517,7 +5549,7 @@ snapshots:
|
|||||||
which-builtin-type@1.2.1:
|
which-builtin-type@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
function.prototype.name: 1.1.8
|
function.prototype.name: 1.2.0
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
is-async-function: 2.1.1
|
is-async-function: 2.1.1
|
||||||
is-date-object: 1.1.0
|
is-date-object: 1.1.0
|
||||||
@@ -5634,7 +5666,7 @@ snapshots:
|
|||||||
y18n: 4.0.3
|
y18n: 4.0.3
|
||||||
yargs-parser: 18.1.3
|
yargs-parser: 18.1.3
|
||||||
|
|
||||||
yargs@16.2.0:
|
yargs@16.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 7.0.4
|
cliui: 7.0.4
|
||||||
escalade: 3.2.0
|
escalade: 3.2.0
|
||||||
@@ -5644,7 +5676,7 @@ snapshots:
|
|||||||
y18n: 5.0.8
|
y18n: 5.0.8
|
||||||
yargs-parser: 20.2.9
|
yargs-parser: 20.2.9
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 8.0.1
|
cliui: 8.0.1
|
||||||
escalade: 3.2.0
|
escalade: 3.2.0
|
||||||
|
|||||||
+494
-44
@@ -115,6 +115,182 @@
|
|||||||
textarea:focus {
|
textarea:focus {
|
||||||
border-color: #333;
|
border-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 自定义选项区域 ===== */
|
||||||
|
.section-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-container + .section-container {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title .count-badge {
|
||||||
|
font-size: 11px;
|
||||||
|
background: #ddd;
|
||||||
|
color: #666;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-row .key-input {
|
||||||
|
flex: 0 0 200px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-row .value-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-row .key-input:focus,
|
||||||
|
.dynamic-row .value-input:focus {
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
color: #999;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn:hover {
|
||||||
|
background: #fee;
|
||||||
|
color: #e33;
|
||||||
|
border-color: #fcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn:hover {
|
||||||
|
border-color: #999;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 参数编辑区 Tab ===== */
|
||||||
|
.param-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-header-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
padding: 4px 14px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn:hover {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn.active {
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-panel textarea {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 350px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-panel textarea:focus {
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -125,6 +301,16 @@
|
|||||||
<label for="uri">URI</label>
|
<label for="uri">URI</label>
|
||||||
<input type="text" id="uri" name="uri" value="/api/song/lyric/v1">
|
<input type="text" id="uri" name="uri" value="/api/song/lyric/v1">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="method">请求方法</label>
|
||||||
|
<select id="method" name="method">
|
||||||
|
<option value="POST" selected>POST</option>
|
||||||
|
<option value="GET">GET</option>
|
||||||
|
<option value="PUT">PUT</option>
|
||||||
|
<option value="DELETE">DELETE</option>
|
||||||
|
<option value="PATCH">PATCH</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="crypto">加密方式</label>
|
<label for="crypto">加密方式</label>
|
||||||
<select id="crypto" name="crypto">
|
<select id="crypto" name="crypto">
|
||||||
@@ -136,19 +322,41 @@
|
|||||||
<option value="" selected>(默认)</option>
|
<option value="" selected>(默认)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="timeout">超时(ms)</label>
|
||||||
|
<input type="number" id="timeout" name="timeout" value="30000" min="0" step="1000" placeholder="0 表示无超时">
|
||||||
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label></label>
|
<label></label>
|
||||||
<button type="submit">发送请求</button>
|
<button type="submit">发送请求</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- ===== 自定义请求头 ===== -->
|
||||||
|
<div class="section-container" id="headersSection">
|
||||||
|
<div class="section-title">
|
||||||
|
请求头 (Headers)
|
||||||
|
<span class="count-badge" id="headerCount">4</span>
|
||||||
|
</div>
|
||||||
|
<div id="headersList"></div>
|
||||||
|
<button class="add-btn" onclick="addHeaderRow('', '')">+ 添加请求头</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="data-result">
|
<div class="data-result">
|
||||||
<div>
|
<div>
|
||||||
<label for="result">响应结果</label>
|
<label for="result">响应结果</label>
|
||||||
<textarea id="result" name="result" readonly></textarea>
|
<textarea id="result" name="result" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="param-container">
|
||||||
<label for="data">请求数据</label>
|
<div class="param-header">
|
||||||
<textarea id="data" name="data">
|
<span class="param-header-title">请求参数</span>
|
||||||
|
<div class="param-tabs">
|
||||||
|
<button class="tab-btn active" data-mode="json" onclick="switchParamTab('json')">JSON</button>
|
||||||
|
<button class="tab-btn" data-mode="kv" onclick="switchParamTab('kv')">键值对</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="param-panel" id="jsonPanel">
|
||||||
|
<textarea id="data" name="data">
|
||||||
{
|
{
|
||||||
"cp": false,
|
"cp": false,
|
||||||
"id": "2058263032",
|
"id": "2058263032",
|
||||||
@@ -160,64 +368,306 @@
|
|||||||
"ytv": 0,
|
"ytv": 0,
|
||||||
"yv": 0
|
"yv": 0
|
||||||
}</textarea>
|
}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="param-panel" id="kvPanel" style="display:none">
|
||||||
|
<div id="kvList"></div>
|
||||||
|
<button class="add-btn" onclick="addKvRow('', '')">+ 添加参数</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://fastly.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
async function sendRequest() {
|
// ===== 动态行管理 =====
|
||||||
document.getElementById('result').value = ""
|
function escapeHtml(str) {
|
||||||
|
if (!str) return '';
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
const uri = document.getElementById('uri').value;
|
function addHeaderRow(key, value) {
|
||||||
const crypto = document.getElementById('crypto').value;
|
const list = document.getElementById('headersList');
|
||||||
const data = document.getElementById('data').value;
|
const row = document.createElement('div');
|
||||||
|
row.className = 'dynamic-row';
|
||||||
|
row.innerHTML =
|
||||||
|
'<input type="text" class="key-input" placeholder="Header 名称" value="' +
|
||||||
|
escapeHtml(key) +
|
||||||
|
'">' +
|
||||||
|
'<input type="text" class="value-input" placeholder="值" value="' +
|
||||||
|
escapeHtml(value) +
|
||||||
|
'">' +
|
||||||
|
'<button class="remove-btn" onclick="removeRow(this)" title="删除">✕</button>';
|
||||||
|
list.appendChild(row);
|
||||||
|
updateCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addKvRow(key, value) {
|
||||||
|
const list = document.getElementById('kvList');
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'dynamic-row';
|
||||||
|
row.innerHTML =
|
||||||
|
'<input type="text" class="key-input" placeholder="参数名" value="' +
|
||||||
|
escapeHtml(key) +
|
||||||
|
'">' +
|
||||||
|
'<input type="text" class="value-input" placeholder="值" value="' +
|
||||||
|
escapeHtml(value) +
|
||||||
|
'">' +
|
||||||
|
'<button class="remove-btn" onclick="removeRow(this)" title="删除">✕</button>';
|
||||||
|
list.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeRow(btn) {
|
||||||
|
btn.parentElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectHeaders() {
|
||||||
|
const headers = {};
|
||||||
|
document.querySelectorAll('#headersList .dynamic-row').forEach(function (row) {
|
||||||
|
var key = row.querySelector('.key-input').value.trim();
|
||||||
|
var value = row.querySelector('.value-input').value;
|
||||||
|
if (key) headers[key] = value;
|
||||||
|
});
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectKvData() {
|
||||||
|
var obj = {};
|
||||||
|
document.querySelectorAll('#kvList .dynamic-row').forEach(function (row) {
|
||||||
|
var key = row.querySelector('.key-input').value.trim();
|
||||||
|
var val = row.querySelector('.value-input').value;
|
||||||
|
if (!key) return;
|
||||||
|
// 自动类型转换
|
||||||
|
if (val === 'true') obj[key] = true;
|
||||||
|
else if (val === 'false') obj[key] = false;
|
||||||
|
else if (val === 'null') obj[key] = null;
|
||||||
|
else if (/^-?\d+(\.\d+)?$/.test(val) && !isNaN(Number(val))) obj[key] = Number(val);
|
||||||
|
else obj[key] = val;
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Tab 切换 =====
|
||||||
|
function syncJsonToKv() {
|
||||||
try {
|
try {
|
||||||
const res = await axios({
|
var data = JSON.parse(document.getElementById('data').value);
|
||||||
url: `/api?timestamp=${Date.now()}`,
|
var list = document.getElementById('kvList');
|
||||||
method: 'post',
|
list.innerHTML = '';
|
||||||
data: {
|
Object.keys(data).forEach(function (k) {
|
||||||
uri: uri,
|
addKvRow(k, String(data[k]));
|
||||||
data: JSON.parse(data),
|
|
||||||
crypto: crypto,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
document.getElementById('result').value = JSON.stringify(res.data, null, 2);
|
} catch (e) {
|
||||||
} catch (error) {
|
return false;
|
||||||
document.getElementById('result').value = 'Request failed: ' + error.message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(function fillFromQuery() {
|
function syncKvToJson() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
var obj = collectKvData();
|
||||||
if (!params.toString()) return;
|
document.getElementById('data').value = JSON.stringify(obj, null, 2);
|
||||||
const uri = params.get('uri');
|
}
|
||||||
const crypto = params.get('crypto');
|
|
||||||
const dataParam = params.get('data');
|
function switchParamTab(mode) {
|
||||||
if (uri) {
|
var jsonPanel = document.getElementById('jsonPanel');
|
||||||
document.getElementById('uri').value = uri;
|
var kvPanel = document.getElementById('kvPanel');
|
||||||
|
|
||||||
|
if (mode === 'kv') {
|
||||||
|
if (!syncJsonToKv()) {
|
||||||
|
alert('JSON 格式有误,无法切换到键值对模式,请先修正 JSON');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jsonPanel.style.display = 'none';
|
||||||
|
kvPanel.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
syncKvToJson();
|
||||||
|
kvPanel.style.display = 'none';
|
||||||
|
jsonPanel.style.display = 'block';
|
||||||
}
|
}
|
||||||
if (crypto) {
|
|
||||||
const cryptoSelect = document.getElementById('crypto');
|
document.querySelectorAll('.tab-btn').forEach(function (btn) {
|
||||||
if ([...cryptoSelect.options].some((opt) => opt.value === crypto)) {
|
btn.classList.toggle('active', btn.dataset.mode === mode);
|
||||||
cryptoSelect.value = crypto;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 默认请求头(直接套用后端预设值)=====
|
||||||
|
var defaultHeaders = [
|
||||||
|
{ key: 'Cookie', value: '' },
|
||||||
|
{
|
||||||
|
key: 'User-Agent',
|
||||||
|
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
|
||||||
|
},
|
||||||
|
{ key: 'Referer', value: 'https://music.163.com' },
|
||||||
|
{ key: 'Content-Type', value: 'application/json' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ===== 发送请求 =====
|
||||||
|
async function sendRequest() {
|
||||||
|
document.getElementById('result').value = '';
|
||||||
|
|
||||||
|
var uri = document.getElementById('uri').value;
|
||||||
|
var method = document.getElementById('method').value;
|
||||||
|
var crypto = document.getElementById('crypto').value;
|
||||||
|
var timeout = parseInt(document.getElementById('timeout').value, 10) || 0;
|
||||||
|
var headers = collectHeaders();
|
||||||
|
|
||||||
|
// 根据当前激活的 tab 读取数据
|
||||||
|
var parsedData;
|
||||||
|
var activeTab = document.querySelector('.tab-btn.active');
|
||||||
|
if (activeTab && activeTab.dataset.mode === 'kv') {
|
||||||
|
parsedData = collectKvData();
|
||||||
|
} else {
|
||||||
|
var rawData = document.getElementById('data').value;
|
||||||
|
try {
|
||||||
|
parsedData = rawData.trim() ? JSON.parse(rawData) : {};
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('result').value =
|
||||||
|
'请求数据格式错误:' + e.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (dataParam) {
|
|
||||||
const decoded = dataParam;
|
var axiosConfig = {
|
||||||
try {
|
url: '/api?timestamp=' + Date.now(),
|
||||||
document.getElementById('data').value = JSON.stringify(
|
method: method,
|
||||||
JSON.parse(decoded),
|
};
|
||||||
null,
|
|
||||||
2,
|
if (timeout > 0) {
|
||||||
|
axiosConfig.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'GET') {
|
||||||
|
axiosConfig.params = {
|
||||||
|
uri: uri,
|
||||||
|
crypto: crypto,
|
||||||
|
data: JSON.stringify(parsedData),
|
||||||
|
headers: Object.keys(headers).length ? JSON.stringify(headers) : undefined,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
axiosConfig.data = {
|
||||||
|
uri: uri,
|
||||||
|
crypto: crypto,
|
||||||
|
data: parsedData,
|
||||||
|
headers: Object.keys(headers).length ? headers : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var res = await axios(axiosConfig);
|
||||||
|
document.getElementById('result').value = JSON.stringify(
|
||||||
|
res.data,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
document.getElementById('data').value = decoded;
|
document.getElementById('result').value =
|
||||||
}
|
'Request failed: ' + error.message;
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
|
|
||||||
|
// ===== 从 URL 查询参数恢复 =====
|
||||||
|
(function fillFromQuery() {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
if (!params.toString()) {
|
||||||
|
// 没有查询参数,用默认 header
|
||||||
|
defaultHeaders.forEach(function (h) {
|
||||||
|
addHeaderRow(h.key, h.value);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = params.get('uri');
|
||||||
|
var crypto = params.get('crypto');
|
||||||
|
var dataParam = params.get('data');
|
||||||
|
var method = params.get('method');
|
||||||
|
var timeout = params.get('timeout');
|
||||||
|
var headersParam = params.get('headers');
|
||||||
|
var kvParams = params.get('kvParams');
|
||||||
|
var paramMode = params.get('paramMode');
|
||||||
|
|
||||||
|
if (uri) document.getElementById('uri').value = uri;
|
||||||
|
if (method) {
|
||||||
|
var methodSelect = document.getElementById('method');
|
||||||
|
if (
|
||||||
|
[].slice.call(methodSelect.options).some(function (opt) {
|
||||||
|
return opt.value === method;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
methodSelect.value = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeout) document.getElementById('timeout').value = timeout;
|
||||||
|
|
||||||
|
if (crypto) {
|
||||||
|
var cryptoSelect = document.getElementById('crypto');
|
||||||
|
if (
|
||||||
|
[].slice.call(cryptoSelect.options).some(function (opt) {
|
||||||
|
return opt.value === crypto;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
cryptoSelect.value = crypto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataParam) {
|
||||||
|
try {
|
||||||
|
document.getElementById('data').value = JSON.stringify(
|
||||||
|
JSON.parse(dataParam),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
document.getElementById('data').value = dataParam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headersParam) {
|
||||||
|
try {
|
||||||
|
var parsed = JSON.parse(headersParam);
|
||||||
|
Object.keys(parsed).forEach(function (k) {
|
||||||
|
addHeaderRow(k, parsed[k]);
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// fallback: 用默认 header
|
||||||
|
defaultHeaders.forEach(function (h) {
|
||||||
|
addHeaderRow(h.key, h.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultHeaders.forEach(function (h) {
|
||||||
|
addHeaderRow(h.key, h.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kvParams) {
|
||||||
|
try {
|
||||||
|
var parsed = JSON.parse(kvParams);
|
||||||
|
// 先填充 kv 列表,再切换到 kv 模式
|
||||||
|
Object.keys(parsed).forEach(function (k) {
|
||||||
|
addKvRow(k, String(parsed[k]));
|
||||||
|
});
|
||||||
|
if (paramMode === 'kv') {
|
||||||
|
switchParamTab('kv');
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
} else if (paramMode === 'kv' && dataParam) {
|
||||||
|
// 从 data 参数同步到键值对模式
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(dataParam);
|
||||||
|
Object.keys(data).forEach(function (k) {
|
||||||
|
addKvRow(k, String(data[k]));
|
||||||
|
});
|
||||||
|
switchParamTab('kv');
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ v4.29.9 加入了生成随机中国 IP 功能, 在请求时加上 `randomCNIP=tr
|
|||||||
5. 直接点`Continue`
|
5. 直接点`Continue`
|
||||||
6. `PROJECT NAME`自己填,`FRAMEWORK PRESET` 选 `Other` 然后直接点 `Deploy` 接着等部署完成即可
|
6. `PROJECT NAME`自己填,`FRAMEWORK PRESET` 选 `Other` 然后直接点 `Deploy` 接着等部署完成即可
|
||||||
|
|
||||||
|
|
||||||
## 腾讯云 serverless 部署
|
## 腾讯云 serverless 部署
|
||||||
|
|
||||||
因 `Vercel` 在国内访问太慢(不绑定自己的域名的情况下),在此提供腾讯云 serverless 部署方法
|
因 `Vercel` 在国内访问太慢(不绑定自己的域名的情况下),在此提供腾讯云 serverless 部署方法
|
||||||
@@ -101,6 +102,7 @@ v4.29.9 加入了生成随机中国 IP 功能, 在请求时加上 `randomCNIP=tr
|
|||||||
5. 输入`应用名`,上传方式选择`代码仓库`,进行 GitHub 授权(如已授权可跳过这一步),代码仓库选择刚刚 fork 的项目
|
5. 输入`应用名`,上传方式选择`代码仓库`,进行 GitHub 授权(如已授权可跳过这一步),代码仓库选择刚刚 fork 的项目
|
||||||
6. 启动文件填入:
|
6. 启动文件填入:
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export PORT=9000
|
export PORT=9000
|
||||||
@@ -113,6 +115,7 @@ export PORT=9000
|
|||||||
- 腾讯云 serverless 并不是免费的,前三个月有免费额度,之后收费
|
- 腾讯云 serverless 并不是免费的,前三个月有免费额度,之后收费
|
||||||
- 当前(2024-08-24), 用此法创建的话, 会`默认`关联一个"日志服务-日志主题"(创建过程中没有提醒), 此服务是计量收费的
|
- 当前(2024-08-24), 用此法创建的话, 会`默认`关联一个"日志服务-日志主题"(创建过程中没有提醒), 此服务是计量收费的
|
||||||
|
|
||||||
|
|
||||||
## 可以使用代理
|
## 可以使用代理
|
||||||
|
|
||||||
在 query 参数中加上 proxy=your-proxy 即可让这一次的请求使用 proxy
|
在 query 参数中加上 proxy=your-proxy 即可让这一次的请求使用 proxy
|
||||||
@@ -180,6 +183,7 @@ request 相关的环境变量
|
|||||||
5. no_proxy
|
5. no_proxy
|
||||||
6. NO_PROXY
|
6. NO_PROXY
|
||||||
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker pull moefurina/ncm-api
|
docker pull moefurina/ncm-api
|
||||||
|
|
||||||
@@ -214,6 +218,7 @@ $ sudo docker run -d -p 3000:3000 netease-music-api
|
|||||||
- 需要返回值加密时, 可传 `e_r=1`, `weapi` 和 `eapi` 都支持
|
- 需要返回值加密时, 可传 `e_r=1`, `weapi` 和 `eapi` 都支持
|
||||||
- 目前支持算法 有 `weapi`, `eapi`, `linuxapi` 和 `xeapi` (xeapi 是一种不加密的特殊算法, 主要用于调试加密前的原始请求参数)
|
- 目前支持算法 有 `weapi`, `eapi`, `linuxapi` 和 `xeapi` (xeapi 是一种不加密的特殊算法, 主要用于调试加密前的原始请求参数)
|
||||||
|
|
||||||
|
|
||||||
## 接口文档
|
## 接口文档
|
||||||
|
|
||||||
### 调用前须知
|
### 调用前须知
|
||||||
@@ -1210,6 +1215,7 @@ tags: 歌单标签
|
|||||||
|
|
||||||
> 如果你设置 limit=50&offset=100,你就会得到第 101-150 首歌曲
|
> 如果你设置 limit=50&offset=100,你就会得到第 101-150 首歌曲
|
||||||
|
|
||||||
|
|
||||||
### 歌单详情动态
|
### 歌单详情动态
|
||||||
|
|
||||||
说明 : 调用后可获取歌单详情动态部分,如评论数,是否收藏,播放数
|
说明 : 调用后可获取歌单详情动态部分,如评论数,是否收藏,播放数
|
||||||
@@ -1496,6 +1502,7 @@ tags: 歌单标签
|
|||||||
|
|
||||||
- (可能存在)JSON 歌曲元数据
|
- (可能存在)JSON 歌曲元数据
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
{"t":0,"c":[{"tx":"作曲: "},{"tx":"柳重言","li":"http://p1.music.126.net/Icj0IcaOjH2ZZpyAM-QGoQ==/6665239487822533.jpg","or":"orpheus://nm/artist/home?id=228547&type=artist"}]}
|
{"t":0,"c":[{"tx":"作曲: "},{"tx":"柳重言","li":"http://p1.music.126.net/Icj0IcaOjH2ZZpyAM-QGoQ==/6665239487822533.jpg","or":"orpheus://nm/artist/home?id=228547&type=artist"}]}
|
||||||
{"t":5403,"c":[{"tx":"编曲: "},{"tx":"Alex San","li":"http://p1.music.126.net/pSbvYkrzZ1RFKqoh-fA9AQ==/109951166352922615.jpg","or":"orpheus://nm/artist/home?id=28984845&type=artist"}]}
|
{"t":5403,"c":[{"tx":"编曲: "},{"tx":"Alex San","li":"http://p1.music.126.net/pSbvYkrzZ1RFKqoh-fA9AQ==/109951166352922615.jpg","or":"orpheus://nm/artist/home?id=28984845&type=artist"}]}
|
||||||
@@ -1512,6 +1519,7 @@ tags: 歌单标签
|
|||||||
|
|
||||||
* 逐字歌词
|
* 逐字歌词
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
[16210,3460](16210,670,0)还(16880,410,0)没...
|
[16210,3460](16210,670,0)还(16880,410,0)没...
|
||||||
~~~~1 ~~~2 ~~~~3 ~~4 5 ~6 (...)
|
~~~~1 ~~~2 ~~~~3 ~~4 5 ~6 (...)
|
||||||
@@ -2492,6 +2500,18 @@ privilege:权限相关信息
|
|||||||
|
|
||||||
**调用例子 :** `/scrobble?id=518066366&sourceid=36780169&time=291`
|
**调用例子 :** `/scrobble?id=518066366&sourceid=36780169&time=291`
|
||||||
|
|
||||||
|
#### 听歌打卡 V2 (NCBL 加密版)
|
||||||
|
|
||||||
|
说明 : 调用此接口,使用桌面客户端 NCBL 加密日志格式上报听歌记录
|
||||||
|
|
||||||
|
**必选参数 :** `id`: 歌曲 id, `time`: 播放时长(秒)
|
||||||
|
|
||||||
|
**可选参数 :** `sourceid`: 来源列表 id, `source`: 来源名称(默认 list), `name`: 歌曲名, `artist`: 艺术家, `bitrate`: 码率(默认 320), `level`: 音质等级(默认 exhigh), `total`: 歌曲总时长(秒)
|
||||||
|
|
||||||
|
**接口地址 :** `/scrobble/v1`
|
||||||
|
|
||||||
|
**调用例子 :** `/scrobble/v1?id=518066366&sourceid=36780169&time=291`
|
||||||
|
|
||||||
### 提交歌曲播放状态
|
### 提交歌曲播放状态
|
||||||
|
|
||||||
说明 : 调用此接口可提交歌曲播放状态,支持会话追踪和播放模式记录,未传入 `sessionId` 时后端会自动生成
|
说明 : 调用此接口可提交歌曲播放状态,支持会话追踪和播放模式记录,未传入 `sessionId` 时后端会自动生成
|
||||||
@@ -2885,6 +2905,7 @@ type : 地区
|
|||||||
- 适合 Vercel、Netlify 等有请求体限制的平台
|
- 适合 Vercel、Netlify 等有请求体限制的平台
|
||||||
- 需要前端配合实现
|
- 需要前端配合实现
|
||||||
|
|
||||||
|
|
||||||
#### 客户端直传相关接口
|
#### 客户端直传相关接口
|
||||||
|
|
||||||
**获取上传凭证**
|
**获取上传凭证**
|
||||||
@@ -2931,6 +2952,7 @@ type : 地区
|
|||||||
- `artist`: 艺术家
|
- `artist`: 艺术家
|
||||||
- `album`: 专辑名
|
- `album`: 专辑名
|
||||||
|
|
||||||
|
|
||||||
#### 客户端直传流程
|
#### 客户端直传流程
|
||||||
|
|
||||||
1. 客户端计算文件 MD5
|
1. 客户端计算文件 MD5
|
||||||
@@ -2938,6 +2960,7 @@ type : 地区
|
|||||||
3. 如果 `needUpload` 为 true,直接 PUT 文件到 `uploadUrl`
|
3. 如果 `needUpload` 为 true,直接 PUT 文件到 `uploadUrl`
|
||||||
4. 调用 `/cloud/upload/complete` 完成导入
|
4. 调用 `/cloud/upload/complete` 完成导入
|
||||||
|
|
||||||
|
|
||||||
### 云盘歌曲信息匹配纠正
|
### 云盘歌曲信息匹配纠正
|
||||||
|
|
||||||
说明 : 登录后调用此接口,可对云盘歌曲信息匹配纠正,如需取消匹配,asid 需要传 0
|
说明 : 登录后调用此接口,可对云盘歌曲信息匹配纠正,如需取消匹配,asid 需要传 0
|
||||||
@@ -3589,6 +3612,32 @@ type='1009' 获取其 id, 如`/search?keywords= 代码时间 &type=1009`
|
|||||||
|
|
||||||
**调用例子 :** `/artist/new/song?limit=1` `/artist/new/song?limit=1&before=1602777625000`
|
**调用例子 :** `/artist/new/song?limit=1` `/artist/new/song?limit=1&before=1602777625000`
|
||||||
|
|
||||||
|
### 关注歌手最近新歌 - 播放全部
|
||||||
|
|
||||||
|
说明 : 登录后调用此接口可获取所有关注歌手最近的 50 首新歌
|
||||||
|
|
||||||
|
**接口地址 :** `/artist/new/song/playall`
|
||||||
|
|
||||||
|
**调用例子 :** `/artist/new/song/playall`
|
||||||
|
|
||||||
|
### 关注歌手新作品(歌曲/MV)
|
||||||
|
|
||||||
|
说明 : 登录后调用此接口可分页获取关注歌手的新歌曲和 MV
|
||||||
|
|
||||||
|
**可选参数 :** `limit`: 返回数量,默认为 10
|
||||||
|
|
||||||
|
`startTimestamp`: 时间游标,首次请求默认为当前时间,后续请求传入上一页返回的时间游标
|
||||||
|
|
||||||
|
`before`: `startTimestamp` 的别名
|
||||||
|
|
||||||
|
`sourceType`: 作品来源类型,已确认 `1` 表示新歌,其他取值待确认,默认为 `1`
|
||||||
|
|
||||||
|
`firstRequest`: 是否首次请求,默认为 `true`,翻页时传 `false`
|
||||||
|
|
||||||
|
**接口地址 :** `/artist/new/song/mv/list/v2`
|
||||||
|
|
||||||
|
**调用例子 :** `/artist/new/song/mv/list/v2` `/artist/new/song/mv/list/v2?startTimestamp=1783680576099&sourceType=1&limit=10&firstRequest=false`
|
||||||
|
|
||||||
### 关注歌手新 MV
|
### 关注歌手新 MV
|
||||||
|
|
||||||
说明 :登录后调用此接口可获取关注歌手新 MV
|
说明 :登录后调用此接口可获取关注歌手新 MV
|
||||||
@@ -3719,6 +3768,14 @@ type='1009' 获取其 id, 如`/search?keywords= 代码时间 &type=1009`
|
|||||||
|
|
||||||
**调用例子 :** `/vip/growthpoint/get?ids=7043206830_7` `/vip/growthpoint/get?ids=8613118351_1,8607552957_1`
|
**调用例子 :** `/vip/growthpoint/get?ids=7043206830_7` `/vip/growthpoint/get?ids=8613118351_1,8607552957_1`
|
||||||
|
|
||||||
|
### 一键领取所有 vip 成长值
|
||||||
|
|
||||||
|
说明 : 登录后调用此接口 , 一键领取所有已完成的会员任务成长值奖励
|
||||||
|
|
||||||
|
**接口地址 :** `/vip/growthpoint/getall`
|
||||||
|
|
||||||
|
**调用例子 :** `/vip/growthpoint/getall`
|
||||||
|
|
||||||
### 歌手粉丝
|
### 歌手粉丝
|
||||||
|
|
||||||
说明 : 调用此接口 , 传入歌手 id, 可获取歌手粉丝
|
说明 : 调用此接口 , 传入歌手 id, 可获取歌手粉丝
|
||||||
@@ -4210,6 +4267,7 @@ ONLINE 已发布
|
|||||||
- `voiceFeeType: 0`:返回免费的声音
|
- `voiceFeeType: 0`:返回免费的声音
|
||||||
- `voiceFeeType: 1`:返回收费的声音
|
- `voiceFeeType: 1`:返回收费的声音
|
||||||
|
|
||||||
|
|
||||||
### 播客声音详情
|
### 播客声音详情
|
||||||
|
|
||||||
说明: 获取播客里的声音详情
|
说明: 获取播客里的声音详情
|
||||||
@@ -4786,6 +4844,23 @@ bitrate = Math.floor(br / 1000)
|
|||||||
|
|
||||||
**接口地址 :** `/listen/data/today/song`
|
**接口地址 :** `/listen/data/today/song`
|
||||||
|
|
||||||
|
### 听歌足迹 - 歌曲播放排行
|
||||||
|
|
||||||
|
说明 : 登录后调用此接口, 获取歌曲播放排行, 返回每首歌的播放次数 (playCount); 默认返回前 20 名 (Top20), 不足则返回全部
|
||||||
|
|
||||||
|
**必选参数 :**
|
||||||
|
|
||||||
|
`type`: 维度类型 周 week 月 month
|
||||||
|
|
||||||
|
**可选参数 :**
|
||||||
|
|
||||||
|
`endTime` : 周: 每周周六 0 点的时间戳 月: 每月最后一天 0 点的时间戳
|
||||||
|
不填就是本周/月的
|
||||||
|
|
||||||
|
**接口地址 :** `/listen/data/song/play/rank`
|
||||||
|
|
||||||
|
**调用例子 :** `/listen/data/song/play/rank?type=month`
|
||||||
|
|
||||||
### 听歌足迹 - 总收听时长
|
### 听歌足迹 - 总收听时长
|
||||||
|
|
||||||
说明 : 登录后调用此接口, 获取总收听时长; 相关接口可能需要 vip 权限
|
说明 : 登录后调用此接口, 获取总收听时长; 相关接口可能需要 vip 权限
|
||||||
|
|||||||
+2
-1
@@ -95,7 +95,8 @@ curl -s {origin}/search?keywords=网易云</code></pre>
|
|||||||
<a href="/api_decrypt.html">API 解密</a> ·
|
<a href="/api_decrypt.html">API 解密</a> ·
|
||||||
<a href="/listen_together_host.html">一起听示例</a> ·
|
<a href="/listen_together_host.html">一起听示例</a> ·
|
||||||
<a href="/playlist_cover_update.html">更新歌单封面示例</a> ·
|
<a href="/playlist_cover_update.html">更新歌单封面示例</a> ·
|
||||||
<a href="/avatar_update.html">头像更新示例</a>
|
<a href="/avatar_update.html">头像更新示例</a> ·
|
||||||
|
<a href="/scrobble.html">听歌打卡示例</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -148,12 +148,12 @@
|
|||||||
updateStatus('二维码已过期,请刷新页面', 'error')
|
updateStatus('二维码已过期,请刷新页面', 'error')
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
} else if (statusRes.code === 801) {
|
} else if (statusRes.code === 801) {
|
||||||
updateStatus('二维码已扫描,请在手机上确认', 'waiting')
|
updateStatus('等待手机扫码...', 'waiting')
|
||||||
} else if (statusRes.code === 802) {
|
} else if (statusRes.code === 802) {
|
||||||
updateStatus('登录成功,正在保存信息...', 'waiting')
|
updateStatus('二维码已扫描,请在手机上确认', 'waiting')
|
||||||
} else if (statusRes.code === 803) {
|
} else if (statusRes.code === 803) {
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
updateStatus('授权登录成功!', 'success')
|
updateStatus('授权登录成功,正在保存信息...', 'success')
|
||||||
await getLoginStatus(statusRes.cookie)
|
await getLoginStatus(statusRes.cookie)
|
||||||
localStorage.setItem('cookie', statusRes.cookie)
|
localStorage.setItem('cookie', statusRes.cookie)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -148,12 +148,12 @@
|
|||||||
updateStatus('二维码已过期,请刷新页面', 'error')
|
updateStatus('二维码已过期,请刷新页面', 'error')
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
} else if (statusRes.code === 801) {
|
} else if (statusRes.code === 801) {
|
||||||
updateStatus('二维码已扫描,请在手机上确认', 'waiting')
|
updateStatus('等待手机扫码...', 'waiting')
|
||||||
} else if (statusRes.code === 802) {
|
} else if (statusRes.code === 802) {
|
||||||
updateStatus('登录成功,正在保存信息...', 'waiting')
|
updateStatus('二维码已扫描,请在手机上确认', 'waiting')
|
||||||
} else if (statusRes.code === 803) {
|
} else if (statusRes.code === 803) {
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
updateStatus('授权登录成功!', 'success')
|
updateStatus('授权登录成功,正在保存信息...', 'success')
|
||||||
await getLoginStatus(statusRes.cookie)
|
await getLoginStatus(statusRes.cookie)
|
||||||
localStorage.setItem('cookie', statusRes.cookie)
|
localStorage.setItem('cookie', statusRes.cookie)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,480 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>听歌打卡 - 网易云音乐 API Enhanced</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 520px;
|
||||||
|
margin: 40px auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-link {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-link:hover {
|
||||||
|
color: #333;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Tabs ---- */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
border-bottom: 2px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn:hover {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn.active {
|
||||||
|
color: #333;
|
||||||
|
border-bottom-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Form ---- */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label .tag {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label .tag.r {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
label .tag.o {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="number"]:focus,
|
||||||
|
textarea:focus {
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row .form-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-fill {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-fill span {
|
||||||
|
color: #0066cc;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-fill span:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.success {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.error {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result.info {
|
||||||
|
background: #e0f2fe;
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link a {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>听歌打卡</h1>
|
||||||
|
<p class="subtitle">同步听歌记录至网易云音乐,增加听歌排行计数</p>
|
||||||
|
|
||||||
|
<a href="/qrlogin-nocookie.html" class="login-link">还没登录?点击登录</a>
|
||||||
|
|
||||||
|
<!-- Cookie (共享) -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cookie">Cookie <span class="tag o">可选</span></label>
|
||||||
|
<textarea id="cookie" placeholder="留空则默认读取本地存储的登录态" rows="2"></textarea>
|
||||||
|
<div class="quick-fill">
|
||||||
|
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="tabs">
|
||||||
|
<button class="tab-btn active" data-tab="v0" onclick="switchTab('v0')">原版</button>
|
||||||
|
<button class="tab-btn" data-tab="v1" onclick="switchTab('v1')">V2 (NCBL 加密版)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ Tab: v0 (原版 /scrobble) ============ -->
|
||||||
|
<div class="tab-content active" id="tab-v0">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>歌曲 ID <span class="tag r">必填</span></label>
|
||||||
|
<input type="text" id="v0-songId" placeholder="例如: 2756058128" value="2756058128" />
|
||||||
|
<div class="quick-fill">
|
||||||
|
示例:
|
||||||
|
<span onclick="fillV0('2756058128', '288651229')">妖精小姐的魔法邀约</span>
|
||||||
|
<span onclick="fillV0('2637402867', '251025018')">Echoes of Memoria</span>
|
||||||
|
<span onclick="fillV0('36307815', '3394198')">Lose Control</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>来源 ID (歌单或专辑 ID) <span class="tag r">必填</span></label>
|
||||||
|
<input type="text" id="v0-sourceId" placeholder="例如: 2756058128" value="2756058128" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>播放时间 (秒) <span class="tag o">可选</span></label>
|
||||||
|
<input type="number" id="v0-time" placeholder="300" value="300" />
|
||||||
|
</div>
|
||||||
|
<button class="btn" onclick="submitV0()">立即打卡 (原版)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ Tab: v1 (NCBL 加密版 /scrobble/v1) ============ -->
|
||||||
|
<div class="tab-content" id="tab-v1">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>歌曲 ID <span class="tag r">必填</span></label>
|
||||||
|
<input type="text" id="v1-songId" placeholder="例如: 518066366" value="518066366" />
|
||||||
|
<div class="quick-fill">
|
||||||
|
示例:
|
||||||
|
<span onclick="fillV1('518066366', '36780169', '291')">默认示例</span>
|
||||||
|
<span onclick="fillV1('2756058128', '288651229', '300')">妖精小姐的魔法邀约</span>
|
||||||
|
<span onclick="fillV1('2637402867', '251025018', '300')">Echoes of Memoria</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>播放时间 (秒) <span class="tag r">必填</span></label>
|
||||||
|
<input type="number" id="v1-time" placeholder="例如: 291" value="291" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>来源 ID <span class="tag o">可选</span></label>
|
||||||
|
<input type="text" id="v1-sourceId" placeholder="歌单或专辑 ID" value="36780169" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>歌曲名 <span class="tag o">可选</span></label>
|
||||||
|
<input type="text" id="v1-name" placeholder="歌曲名称" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>艺术家 <span class="tag o">可选</span></label>
|
||||||
|
<input type="text" id="v1-artist" placeholder="歌手名" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>码率 <span class="tag o">可选</span></label>
|
||||||
|
<input type="number" id="v1-bitrate" placeholder="默认 320" value="320" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>音质等级 <span class="tag o">可选</span></label>
|
||||||
|
<input type="text" id="v1-level" placeholder="默认 exhigh" value="exhigh" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>歌曲总时长 (秒) <span class="tag o">可选</span></label>
|
||||||
|
<input type="number" id="v1-total" placeholder="留空则与播放时间相同" />
|
||||||
|
</div>
|
||||||
|
<button class="btn" onclick="submitV1()">立即打卡 (NCBL 加密版)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Result -->
|
||||||
|
<div id="result" class="result" style="display: none;"></div>
|
||||||
|
|
||||||
|
<div class="footer-link">
|
||||||
|
<a href="/">返回首页</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://fastly.jsdelivr.net/npm/axios@0.26.1/dist/axios.min.js"></script>
|
||||||
|
<script>
|
||||||
|
/* ---- DOM refs ---- */
|
||||||
|
const cookieInput = document.getElementById('cookie')
|
||||||
|
const resultDiv = document.getElementById('result')
|
||||||
|
|
||||||
|
/* ---- init ---- */
|
||||||
|
if (localStorage.getItem('cookie')) {
|
||||||
|
cookieInput.value = localStorage.getItem('cookie')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Tab switching ---- */
|
||||||
|
function switchTab(name) {
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === name))
|
||||||
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.toggle('active', c.id === 'tab-' + name))
|
||||||
|
hideResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Cookie ---- */
|
||||||
|
function loadLocalCookie() {
|
||||||
|
const local = localStorage.getItem('cookie')
|
||||||
|
if (local) {
|
||||||
|
cookieInput.value = local
|
||||||
|
showResult('已成功读取本地 Cookie', 'success')
|
||||||
|
} else {
|
||||||
|
showResult('未在本地发现登录 Cookie,请先登录或手动粘贴', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Quick fill ---- */
|
||||||
|
function fillV0(id, sourceId) {
|
||||||
|
document.getElementById('v0-songId').value = id
|
||||||
|
document.getElementById('v0-sourceId').value = sourceId
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillV1(id, sourceId, time) {
|
||||||
|
document.getElementById('v1-songId').value = id
|
||||||
|
document.getElementById('v1-sourceId').value = sourceId
|
||||||
|
document.getElementById('v1-time').value = time
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Result ---- */
|
||||||
|
function hideResult() {
|
||||||
|
resultDiv.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResult(message, type) {
|
||||||
|
resultDiv.textContent = message
|
||||||
|
resultDiv.className = 'result ' + type
|
||||||
|
resultDiv.style.display = 'block'
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableAll(disabled) {
|
||||||
|
document.querySelectorAll('.btn').forEach(b => b.disabled = disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Submit: 原版 /scrobble ---- */
|
||||||
|
async function submitV0() {
|
||||||
|
const id = document.getElementById('v0-songId').value.trim()
|
||||||
|
const sourceid = document.getElementById('v0-sourceId').value.trim()
|
||||||
|
const time = document.getElementById('v0-time').value.trim() || '300'
|
||||||
|
|
||||||
|
if (!id) { showResult('请输入歌曲 ID !', 'error'); return }
|
||||||
|
if (!sourceid) { showResult('请输入来源 ID !', 'error'); return }
|
||||||
|
|
||||||
|
disableAll(true)
|
||||||
|
showResult('打卡中,请稍候...', 'info')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = { id, sourceid: sourceid || id, time, timestamp: Date.now() }
|
||||||
|
const cookie = cookieInput.value.trim()
|
||||||
|
if (cookie) params.cookie = cookie
|
||||||
|
|
||||||
|
const res = await axios({ method: 'get', url: '/scrobble', params })
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
showResult('✅ 打卡成功! \n\n' + JSON.stringify(res.data, null, 2), 'success')
|
||||||
|
} else {
|
||||||
|
showResult('请求已发送,返回:\n\n' + JSON.stringify(res.data, null, 2), 'info')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errData = error.response ? error.response.data : { error: error.message }
|
||||||
|
showResult('❌ 打卡失败\n\n' + JSON.stringify(errData, null, 2), 'error')
|
||||||
|
} finally {
|
||||||
|
disableAll(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Submit: NCBL 加密版 /scrobble/v1 ---- */
|
||||||
|
async function submitV1() {
|
||||||
|
const id = document.getElementById('v1-songId').value.trim()
|
||||||
|
const time = document.getElementById('v1-time').value.trim()
|
||||||
|
|
||||||
|
if (!id) { showResult('请输入歌曲 ID !', 'error'); return }
|
||||||
|
if (!time || isNaN(+time) || +time <= 0) { showResult('请输入有效的播放时长(秒)!', 'error'); return }
|
||||||
|
|
||||||
|
disableAll(true)
|
||||||
|
showResult('打卡中(NCBL 加密),请稍候...', 'info')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
id,
|
||||||
|
time,
|
||||||
|
sourceid: document.getElementById('v1-sourceId').value.trim() || undefined,
|
||||||
|
name: document.getElementById('v1-name').value.trim() || undefined,
|
||||||
|
artist: document.getElementById('v1-artist').value.trim() || undefined,
|
||||||
|
bitrate: document.getElementById('v1-bitrate').value.trim() || undefined,
|
||||||
|
level: document.getElementById('v1-level').value.trim() || undefined,
|
||||||
|
total: document.getElementById('v1-total').value.trim() || undefined,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
const cookie = cookieInput.value.trim()
|
||||||
|
if (cookie) params.cookie = cookie
|
||||||
|
|
||||||
|
// 清理 undefined 参数
|
||||||
|
Object.keys(params).forEach(k => params[k] === undefined && delete params[k])
|
||||||
|
|
||||||
|
const res = await axios({ method: 'get', url: '/scrobble/v1', params })
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
showResult('✅ NCBL 加密打卡成功! \n\n' + JSON.stringify(res.data, null, 2), 'success')
|
||||||
|
} else {
|
||||||
|
showResult('请求已发送,返回:\n\n' + JSON.stringify(res.data, null, 2), 'info')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errData = error.response ? error.response.data : { error: error.message }
|
||||||
|
showResult('❌ 打卡失败\n\n' + JSON.stringify(errData, null, 2), 'error')
|
||||||
|
} finally {
|
||||||
|
disableAll(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,577 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* 📝 Markdown 文档格式化工具喵~
|
||||||
|
* 支持格式化标题、代码块、空行和缩进!
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* node scripts/format-docs.js # 格式化默认文档 (public/docs/home.md)
|
||||||
|
* node scripts/format-docs.js <文件路径> # 格式化指定文件
|
||||||
|
* node scripts/format-docs.js --dir <目录> # 格式化整个目录的 .md 文件
|
||||||
|
* node scripts/format-docs.js --check # 只检查不写入 (dry-run)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
// ======================== 配置 ========================
|
||||||
|
const CONFIG = {
|
||||||
|
maxConsecutiveBlankLines: 2, // 最大连续空行数
|
||||||
|
codeBlockLang: true, // 代码块是否保留语言标记
|
||||||
|
headingSpaceBefore: true, // 标题前是否确保空行
|
||||||
|
headingSpaceAfter: true, // 标题后是否确保空行
|
||||||
|
listIndent: 2, // 列表缩进空格数
|
||||||
|
encodeSpecialChars: false, // 是否编码特殊字符
|
||||||
|
removeTrailingSpaces: true, // 是否删除行尾空格
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_FILE = path.resolve(__dirname, '..', 'public', 'docs', 'home.md')
|
||||||
|
|
||||||
|
// ======================== 颜色工具 ========================
|
||||||
|
const color = (code) => (s) => `\x1b[${code}m${s}\x1b[0m`
|
||||||
|
const green = color('32')
|
||||||
|
const cyan = color('36')
|
||||||
|
const yellow = color('33')
|
||||||
|
const red = color('31')
|
||||||
|
const bold = color('1')
|
||||||
|
const dim = color('2')
|
||||||
|
|
||||||
|
// ======================== 核心格式化函数 ========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 Markdown 的块结构,返回块数组
|
||||||
|
* 块类型: 'heading', 'code', 'list', 'paragraph', 'empty', 'hr', 'blockquote', 'table', 'html'
|
||||||
|
*/
|
||||||
|
function parseBlocks(lines) {
|
||||||
|
const blocks = []
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (i < lines.length) {
|
||||||
|
const line = lines[i]
|
||||||
|
const trimmed = line.trim()
|
||||||
|
|
||||||
|
// 空行
|
||||||
|
if (trimmed === '') {
|
||||||
|
blocks.push({ type: 'empty', lines: [line], raw: line })
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码块 (``` 或 ~~~)
|
||||||
|
if (/^```/.test(trimmed) || /^~~~/.test(trimmed)) {
|
||||||
|
const marker = trimmed.match(/^(```|~~~)/)[1]
|
||||||
|
const lang = trimmed.slice(marker.length).trim()
|
||||||
|
const start = i
|
||||||
|
i++
|
||||||
|
while (i < lines.length && !lines[i].trim().startsWith(marker)) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if (i < lines.length) i++ // 跳过结束标记
|
||||||
|
const codeLines = lines.slice(start, i)
|
||||||
|
blocks.push({
|
||||||
|
type: 'code',
|
||||||
|
lines: codeLines,
|
||||||
|
lang,
|
||||||
|
marker,
|
||||||
|
raw: codeLines.join('\n'),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/)
|
||||||
|
if (headingMatch) {
|
||||||
|
const level = headingMatch[1].length
|
||||||
|
blocks.push({
|
||||||
|
type: 'heading',
|
||||||
|
lines: [line],
|
||||||
|
level,
|
||||||
|
text: headingMatch[2],
|
||||||
|
raw: line,
|
||||||
|
})
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割线
|
||||||
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(trimmed)) {
|
||||||
|
blocks.push({ type: 'hr', lines: [line], raw: line })
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML 注释或标签
|
||||||
|
if (/^<!--/.test(trimmed) || /^<\w+/.test(trimmed)) {
|
||||||
|
const start = i
|
||||||
|
i++
|
||||||
|
while (i < lines.length) {
|
||||||
|
const t = lines[i].trim()
|
||||||
|
if (/-->/.test(t) || /<\/\w+>/.test(t)) {
|
||||||
|
i++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
const htmlLines = lines.slice(start, i)
|
||||||
|
blocks.push({ type: 'html', lines: htmlLines, raw: htmlLines.join('\n') })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 引用块 (注: 空行不吞噬,留给后续解析,避免干扰上下文的空行判断)
|
||||||
|
if (/^>/.test(trimmed)) {
|
||||||
|
const start = i
|
||||||
|
i++
|
||||||
|
while (i < lines.length && lines[i].trimStart().startsWith('>')) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
const quoteLines = lines.slice(start, i)
|
||||||
|
blocks.push({
|
||||||
|
type: 'blockquote',
|
||||||
|
lines: quoteLines,
|
||||||
|
raw: quoteLines.join('\n'),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格
|
||||||
|
if (
|
||||||
|
/\|/.test(trimmed) &&
|
||||||
|
lines[i + 1] &&
|
||||||
|
/^\|[\s\-:]+\|/.test(lines[i + 1].trim())
|
||||||
|
) {
|
||||||
|
const start = i
|
||||||
|
i += 2
|
||||||
|
while (i < lines.length && /\|/.test(lines[i].trim())) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
const tableLines = lines.slice(start, i)
|
||||||
|
blocks.push({
|
||||||
|
type: 'table',
|
||||||
|
lines: tableLines,
|
||||||
|
raw: tableLines.join('\n'),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列表项(有序或无序)
|
||||||
|
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line)) {
|
||||||
|
const start = i
|
||||||
|
i++
|
||||||
|
while (i < lines.length) {
|
||||||
|
const t = lines[i].trim()
|
||||||
|
if (t === '') {
|
||||||
|
i++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(lines[i])) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 缩进 continuation
|
||||||
|
if (/^\s{2,}/.test(lines[i])) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const listLines = lines.slice(start, i)
|
||||||
|
blocks.push({ type: 'list', lines: listLines, raw: listLines.join('\n') })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通段落
|
||||||
|
const start = i
|
||||||
|
i++
|
||||||
|
while (i < lines.length && lines[i].trim() !== '') {
|
||||||
|
// 如果遇到新的块元素则停止
|
||||||
|
const t = lines[i].trim()
|
||||||
|
if (
|
||||||
|
/^#{1,6}\s/.test(t) ||
|
||||||
|
/^```/.test(t) ||
|
||||||
|
/^~~~/.test(t) ||
|
||||||
|
/^(\s*[-*+]\s|\s*\d+\.\s)/.test(lines[i])
|
||||||
|
)
|
||||||
|
break
|
||||||
|
// 分割线
|
||||||
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(t)) break
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
const paraLines = lines.slice(start, i)
|
||||||
|
blocks.push({
|
||||||
|
type: 'paragraph',
|
||||||
|
lines: paraLines,
|
||||||
|
raw: paraLines.join('\n'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文档
|
||||||
|
*/
|
||||||
|
function formatMarkdown(input, options = {}) {
|
||||||
|
const cfg = { ...CONFIG, ...options }
|
||||||
|
const lines = input.split('\n')
|
||||||
|
let blocks = parseBlocks(lines)
|
||||||
|
|
||||||
|
// ---------- 格式化步骤 ----------
|
||||||
|
|
||||||
|
// 1. 确保标题前后有空行
|
||||||
|
if (cfg.headingSpaceBefore || cfg.headingSpaceAfter) {
|
||||||
|
blocks = blocks.map((block, idx) => {
|
||||||
|
if (block.type !== 'heading') return block
|
||||||
|
|
||||||
|
const newLines = [...block.lines]
|
||||||
|
|
||||||
|
// 标题前加空行(如果不是第一个块且前一个不是空行)
|
||||||
|
if (
|
||||||
|
cfg.headingSpaceBefore &&
|
||||||
|
idx > 0 &&
|
||||||
|
blocks[idx - 1].type !== 'empty'
|
||||||
|
) {
|
||||||
|
newLines.unshift('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题后加空行(如果不是最后一个块且后一个不是空行)
|
||||||
|
if (
|
||||||
|
cfg.headingSpaceAfter &&
|
||||||
|
idx < blocks.length - 1 &&
|
||||||
|
blocks[idx + 1].type !== 'empty'
|
||||||
|
) {
|
||||||
|
newLines.push('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...block, lines: newLines }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 格式化代码块
|
||||||
|
blocks = blocks.map((block, idx) => {
|
||||||
|
if (block.type !== 'code') return block
|
||||||
|
|
||||||
|
const newLines = [...block.lines]
|
||||||
|
|
||||||
|
// 先提取代码块标记信息 (第一行是 ``` 或 ~~~)
|
||||||
|
const markerMatch = newLines[0].trim().match(/^(```|~~~)/)
|
||||||
|
if (!markerMatch) return block // 安全兜底
|
||||||
|
const marker = markerMatch[1]
|
||||||
|
const lang = newLines[0].trim().slice(marker.length).trim().toLowerCase()
|
||||||
|
|
||||||
|
// 标准化语言标记
|
||||||
|
const indent = newLines[0].match(/^\s*/)[0]
|
||||||
|
newLines[0] = lang ? `${indent}${marker}${lang}` : `${indent}${marker}`
|
||||||
|
|
||||||
|
// 确保代码块前后有空行(插在标准化之后,因为只动第一行)
|
||||||
|
if (
|
||||||
|
idx > 0 &&
|
||||||
|
blocks[idx - 1].type !== 'empty' &&
|
||||||
|
blocks[idx - 1].type !== 'code'
|
||||||
|
) {
|
||||||
|
newLines.unshift('')
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
idx < blocks.length - 1 &&
|
||||||
|
blocks[idx + 1].type !== 'empty' &&
|
||||||
|
blocks[idx + 1].type !== 'code'
|
||||||
|
) {
|
||||||
|
newLines.push('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...block, lines: newLines }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. 压缩多余空行
|
||||||
|
blocks = compressEmptyLines(blocks, cfg.maxConsecutiveBlankLines)
|
||||||
|
|
||||||
|
// 4. 删除行尾空格
|
||||||
|
if (cfg.removeTrailingSpaces) {
|
||||||
|
blocks = blocks.map((block) => ({
|
||||||
|
...block,
|
||||||
|
lines: block.lines.map((l) => l.replace(/[ \t]+$/, '')),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 修复列表缩进
|
||||||
|
blocks = blocks.map((block) => {
|
||||||
|
if (block.type !== 'list') return block
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
lines: block.lines.map((line) => {
|
||||||
|
const trimmed = line.trimStart()
|
||||||
|
// 检测列表标记
|
||||||
|
if (/^[-*+]\s/.test(trimmed) || /^\d+\.\s/.test(trimmed)) {
|
||||||
|
const indent = line.length - line.trimStart().length
|
||||||
|
// 如果是顶层列表项,确保缩进为0
|
||||||
|
if (indent % cfg.listIndent !== 0) {
|
||||||
|
const normalizedIndent =
|
||||||
|
Math.round(indent / cfg.listIndent) * cfg.listIndent
|
||||||
|
return ' '.repeat(normalizedIndent) + trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重新拼装
|
||||||
|
const resultLines = blocks.flatMap((b) => b.lines)
|
||||||
|
|
||||||
|
// 处理文件开头和结尾的空行
|
||||||
|
while (resultLines.length > 0 && resultLines[0] === '') resultLines.shift()
|
||||||
|
while (resultLines.length > 0 && resultLines[resultLines.length - 1] === '')
|
||||||
|
resultLines.pop()
|
||||||
|
resultLines.push('') // 文件结尾留一个空行
|
||||||
|
|
||||||
|
return resultLines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩连续空行块
|
||||||
|
*/
|
||||||
|
function compressEmptyLines(blocks, maxBlank) {
|
||||||
|
const result = []
|
||||||
|
let blankCount = 0
|
||||||
|
|
||||||
|
for (const block of blocks) {
|
||||||
|
if (block.type === 'empty') {
|
||||||
|
blankCount++
|
||||||
|
if (blankCount <= maxBlank) {
|
||||||
|
result.push(block)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blankCount = 0
|
||||||
|
result.push(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 统计信息 ========================
|
||||||
|
|
||||||
|
function getStats(input) {
|
||||||
|
const lines = input.split('\n')
|
||||||
|
const blocks = parseBlocks(lines)
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalLines: lines.length,
|
||||||
|
nonEmptyLines: lines.filter((l) => l.trim() !== '').length,
|
||||||
|
headings: blocks.filter((b) => b.type === 'heading').length,
|
||||||
|
codeBlocks: blocks.filter((b) => b.type === 'code').length,
|
||||||
|
lists: blocks.filter((b) => b.type === 'list').length,
|
||||||
|
tables: blocks.filter((b) => b.type === 'table').length,
|
||||||
|
blockquotes: blocks.filter((b) => b.type === 'blockquote').length,
|
||||||
|
hr: blocks.filter((b) => b.type === 'hr').length,
|
||||||
|
characters: input.length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printStats(stats) {
|
||||||
|
console.log(bold('\n📊 文档统计信息:'))
|
||||||
|
console.log(` 总行数: ${cyan(String(stats.totalLines))}`)
|
||||||
|
console.log(` 非空行数: ${cyan(String(stats.nonEmptyLines))}`)
|
||||||
|
console.log(` 字符数: ${cyan(String(stats.characters))}`)
|
||||||
|
console.log(` 标题数: ${cyan(String(stats.headings))}`)
|
||||||
|
console.log(` 代码块数: ${cyan(String(stats.codeBlocks))}`)
|
||||||
|
console.log(` 列表数: ${cyan(String(stats.lists))}`)
|
||||||
|
console.log(` 表格数: ${cyan(String(stats.tables))}`)
|
||||||
|
console.log(` 引用块数: ${cyan(String(stats.blockquotes))}`)
|
||||||
|
console.log(` 分割线数: ${cyan(String(stats.hr))}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 文件处理 ========================
|
||||||
|
|
||||||
|
function readFile(filePath) {
|
||||||
|
try {
|
||||||
|
let content = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
// 统一换行符为 \n,避免 Windows 的 \r\n 导致 diff 不稳定
|
||||||
|
content = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||||
|
return content
|
||||||
|
} catch (err) {
|
||||||
|
console.error(red(`❌ 无法读取文件: ${filePath}`))
|
||||||
|
console.error(dim(err.message))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(filePath, content) {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(filePath, content, 'utf-8')
|
||||||
|
console.log(green(`✅ 已写入: ${filePath}`))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(red(`❌ 写入失败: ${filePath}`))
|
||||||
|
console.error(dim(err.message))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFile(filePath, options) {
|
||||||
|
const relativePath = path.relative(process.cwd(), filePath)
|
||||||
|
console.log(bold(`\n📄 处理文件: ${cyan(relativePath)}`))
|
||||||
|
|
||||||
|
const input = readFile(filePath)
|
||||||
|
const statsBefore = getStats(input)
|
||||||
|
|
||||||
|
if (options.verbose) {
|
||||||
|
console.log(dim(' 格式化前:'))
|
||||||
|
printStats(statsBefore)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatted = formatMarkdown(input, options)
|
||||||
|
const statsAfter = getStats(formatted)
|
||||||
|
|
||||||
|
const hasChanges = input !== formatted
|
||||||
|
|
||||||
|
if (options.check) {
|
||||||
|
if (hasChanges) {
|
||||||
|
console.log(yellow(' ⚠️ 文件需要格式化 (dry-run, 未写入)'))
|
||||||
|
const added = statsAfter.totalLines - statsBefore.totalLines
|
||||||
|
console.log(dim(` 行数变化: ${added > 0 ? '+' : ''}${added}`))
|
||||||
|
} else {
|
||||||
|
console.log(green(' ✅ 文件已格式良好'))
|
||||||
|
}
|
||||||
|
return hasChanges ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
writeFile(filePath, formatted)
|
||||||
|
console.log(green(' ✨ 格式化完成!'))
|
||||||
|
if (options.verbose) {
|
||||||
|
printStats(statsAfter)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(green(' ✅ 文档已经格式良好,无需修改'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function processDirectory(dirPath, options) {
|
||||||
|
let exitCode = 0
|
||||||
|
const files = fs
|
||||||
|
.readdirSync(dirPath)
|
||||||
|
.filter((f) => f.endsWith('.md'))
|
||||||
|
.map((f) => path.join(dirPath, f))
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log(yellow(`⚠️ 在 ${dirPath} 中未找到 .md 文件`))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
bold(`\n📁 扫描目录: ${cyan(dirPath)} (${files.length} 个文件)\n`),
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const ret = processFile(file, options)
|
||||||
|
if (ret !== 0) exitCode = ret
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== CLI ========================
|
||||||
|
|
||||||
|
function printHelp() {
|
||||||
|
console.log(
|
||||||
|
bold(`
|
||||||
|
📝 Markdown 文档格式化工具 v1.0.0
|
||||||
|
|
||||||
|
${cyan('用法:')}
|
||||||
|
node scripts/format-docs.js [文件路径] [选项]
|
||||||
|
|
||||||
|
${cyan('参数:')}
|
||||||
|
文件路径 要格式化的 .md 文件 (默认: public/docs/home.md)
|
||||||
|
|
||||||
|
${cyan('选项:')}
|
||||||
|
--dir, -d <目录> 格式化整个目录下的所有 .md 文件
|
||||||
|
--check, -c dry-run 模式,只检查不写入
|
||||||
|
--verbose, -v 显示详细统计信息
|
||||||
|
--help, -h 显示帮助信息
|
||||||
|
|
||||||
|
${cyan('示例:')}
|
||||||
|
node scripts/format-docs.js
|
||||||
|
node scripts/format-docs.js README.md
|
||||||
|
node scripts/format-docs.js --dir docs/
|
||||||
|
node scripts/format-docs.js --check
|
||||||
|
`),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs() {
|
||||||
|
const args = process.argv.slice(2)
|
||||||
|
const options = {
|
||||||
|
file: null,
|
||||||
|
dir: null,
|
||||||
|
check: false,
|
||||||
|
verbose: false,
|
||||||
|
help: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
switch (args[i]) {
|
||||||
|
case '--dir':
|
||||||
|
case '-d':
|
||||||
|
options.dir = args[++i]
|
||||||
|
break
|
||||||
|
case '--check':
|
||||||
|
case '-c':
|
||||||
|
options.check = true
|
||||||
|
break
|
||||||
|
case '--verbose':
|
||||||
|
case '-v':
|
||||||
|
options.verbose = true
|
||||||
|
break
|
||||||
|
case '--help':
|
||||||
|
case '-h':
|
||||||
|
options.help = true
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
if (!args[i].startsWith('--') && !args[i].startsWith('-')) {
|
||||||
|
options.file = path.resolve(process.cwd(), args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 主函数 ========================
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const options = parseArgs()
|
||||||
|
|
||||||
|
if (options.help) {
|
||||||
|
printHelp()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(bold(`\n${cyan('🐱 文档格式化工具')} ${dim('(用❤️制作)')}\n`))
|
||||||
|
|
||||||
|
if (options.dir) {
|
||||||
|
const dirPath = path.resolve(process.cwd(), options.dir)
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
console.error(red(`❌ 目录不存在: ${dirPath}`))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return processDirectory(dirPath, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = options.file || DEFAULT_FILE
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error(red(`❌ 文件不存在: ${filePath}`))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return processFile(filePath, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== 启动 ========================
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const exitCode = main()
|
||||||
|
process.exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { formatMarkdown, getStats, parseBlocks }
|
||||||
@@ -10,6 +10,7 @@ const { cookieToJson } = require('./util/index')
|
|||||||
const fileUpload = require('express-fileupload')
|
const fileUpload = require('express-fileupload')
|
||||||
const decode = require('safe-decode-uri-component')
|
const decode = require('safe-decode-uri-component')
|
||||||
const logger = require('./util/logger.js')
|
const logger = require('./util/logger.js')
|
||||||
|
const { APP_CONF } = require('./util/config.json')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version check result.
|
* The version check result.
|
||||||
@@ -299,15 +300,15 @@ async function constructServer(moduleDefs) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let usedCrypto = ''
|
||||||
const moduleResponse = await moduleDef.module(query, (...params) => {
|
const moduleResponse = await moduleDef.module(query, (...params) => {
|
||||||
// 参数注入客户端IP
|
|
||||||
const obj = [...params]
|
const obj = [...params]
|
||||||
const options = obj[2] || {}
|
const options = obj[2] || {}
|
||||||
|
usedCrypto = options.crypto || ''
|
||||||
let ip = ''
|
let ip = ''
|
||||||
|
|
||||||
if (options.randomCNIP) {
|
if (options.randomCNIP) {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
// logger.info('Using random Chinese IP for request:', ip)
|
|
||||||
} else {
|
} else {
|
||||||
ip = req.ip
|
ip = req.ip
|
||||||
|
|
||||||
@@ -317,7 +318,6 @@ async function constructServer(moduleDefs) {
|
|||||||
if (ip == '::1') {
|
if (ip == '::1') {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
}
|
}
|
||||||
// logger.info('Requested from ip:', ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
obj[2] = {
|
obj[2] = {
|
||||||
@@ -327,7 +327,10 @@ async function constructServer(moduleDefs) {
|
|||||||
|
|
||||||
return request(...obj)
|
return request(...obj)
|
||||||
})
|
})
|
||||||
logger.info(`Request Success: ${decode(req.originalUrl)}`)
|
const displayCrypto = usedCrypto || (APP_CONF.encrypt ? 'eapi' : 'api')
|
||||||
|
logger.info(
|
||||||
|
`Request Success: [${displayCrypto}] ${decode(req.originalUrl)}`,
|
||||||
|
)
|
||||||
|
|
||||||
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
||||||
if (
|
if (
|
||||||
@@ -422,7 +425,7 @@ async function serveNcmApi(options) {
|
|||||||
options.checkVersion &&
|
options.checkVersion &&
|
||||||
checkVersion().then(({ npmVersion, ourVersion, status }) => {
|
checkVersion().then(({ npmVersion, ourVersion, status }) => {
|
||||||
if (status == VERSION_CHECK_RESULT.NOT_LATEST) {
|
if (status == VERSION_CHECK_RESULT.NOT_LATEST) {
|
||||||
logger.info(
|
logger.warn(
|
||||||
`最新版本: ${npmVersion}, 当前版本: ${ourVersion}, 请及时更新`,
|
`最新版本: ${npmVersion}, 当前版本: ${ourVersion}, 请及时更新`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -444,11 +447,9 @@ async function serveNcmApi(options) {
|
|||||||
╠═╣╠═╝║ ║╣ ║║║╠═╣╠═╣║║║║ ║╣ ║║
|
╠═╣╠═╝║ ║╣ ║║║╠═╣╠═╣║║║║ ║╣ ║║
|
||||||
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
||||||
`)
|
`)
|
||||||
logger.info(`
|
logger.info(
|
||||||
- Server started successfully @ http://${host ? host : 'localhost'}:${port}
|
`Server started successfully @ http://${host ? host : 'localhost'}:${port}`,
|
||||||
- Environment: ${process.env.NODE_ENV || 'development'}
|
)
|
||||||
- Node Version: ${process.version}
|
|
||||||
- Process ID: ${process.pid}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return appExt
|
return appExt
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"apiDomain": "https://interface.music.163.com",
|
"apiDomain": "https://interface.music.163.com",
|
||||||
"xeapiDomain": "https://interface3.music.163.com",
|
"xeapiDomain": "https://interface3.music.163.com",
|
||||||
"domain": "https://music.163.com",
|
"domain": "https://music.163.com",
|
||||||
|
"clDomian": "https://clientlog.music.163.com",
|
||||||
|
"clDomian3": "https://clientlog3.music.163.com",
|
||||||
"encrypt": true,
|
"encrypt": true,
|
||||||
"encryptResponse": false,
|
"encryptResponse": false,
|
||||||
"clientSign": "18:C0:4D:B9:8F:FE@@@453832335F384641365F424635335F303030315F303031425F343434415F343643365F333638332@@@@@@6ff673ef74955b38bce2fa8562d95c976ed4758b1227c4e9ee345987cee17bc9",
|
"clientSign": "18:C0:4D:B9:8F:FE@@@453832335F384641365F424635335F303030315F303031425F343434415F343643365F333638332@@@@@@6ff673ef74955b38bce2fa8562d95c976ed4758b1227c4e9ee345987cee17bc9",
|
||||||
|
|||||||
+3
-3
@@ -62,9 +62,9 @@ const chinaIPRanges = (function loadChinaIPRanges() {
|
|||||||
// attach total for convenience
|
// attach total for convenience
|
||||||
arr.totalCount = total
|
arr.totalCount = total
|
||||||
|
|
||||||
logger.info(
|
// logger.info(
|
||||||
`Loaded ${arr.length} Chinese IP ranges from china_ip_ranges.txt, total ${total} IPs`,
|
// `Loaded ${arr.length} Chinese IP ranges from china_ip_ranges.txt, total ${total} IPs`,
|
||||||
)
|
// )
|
||||||
return arr
|
return arr
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to load china_ip_ranges.txt:', error.message)
|
logger.error('Failed to load china_ip_ranges.txt:', error.message)
|
||||||
|
|||||||
+481
@@ -0,0 +1,481 @@
|
|||||||
|
// NCBL 加密工具 —— 复制自 @netease-report-listen-song
|
||||||
|
// 提供 ChaCha20 / RSA-256 / NCBL 加密 / PLV+PLD 构建 / 设备上下文提取
|
||||||
|
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const zlib = require('zlib')
|
||||||
|
const axios = require('axios')
|
||||||
|
const { APP_CONF } = require('./config.json')
|
||||||
|
const DOMAIN3 = APP_CONF.clDomian3
|
||||||
|
|
||||||
|
// ---- ChaCha20 纯 JS 实现 ----
|
||||||
|
const SIGMA = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
|
||||||
|
|
||||||
|
const rotl = (x, n) => ((x << n) | (x >>> (32 - n))) >>> 0
|
||||||
|
|
||||||
|
const quarterRound = (s, a, b, c, d) => {
|
||||||
|
s[a] = (s[a] + s[b]) >>> 0
|
||||||
|
s[d] ^= s[a]
|
||||||
|
s[d] = rotl(s[d], 16)
|
||||||
|
s[c] = (s[c] + s[d]) >>> 0
|
||||||
|
s[b] ^= s[c]
|
||||||
|
s[b] = rotl(s[b], 12)
|
||||||
|
s[a] = (s[a] + s[b]) >>> 0
|
||||||
|
s[d] ^= s[a]
|
||||||
|
s[d] = rotl(s[d], 8)
|
||||||
|
s[c] = (s[c] + s[d]) >>> 0
|
||||||
|
s[b] ^= s[c]
|
||||||
|
s[b] = rotl(s[b], 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chachaBlock = (key, counter, nonce) => {
|
||||||
|
const state = new Uint32Array(16)
|
||||||
|
state[0] = SIGMA[0]
|
||||||
|
state[1] = SIGMA[1]
|
||||||
|
state[2] = SIGMA[2]
|
||||||
|
state[3] = SIGMA[3]
|
||||||
|
for (let i = 0; i < 8; i++) state[4 + i] = key.readUInt32LE(i * 4)
|
||||||
|
state[12] = counter >>> 0
|
||||||
|
state[13] = nonce.readUInt32LE(0)
|
||||||
|
state[14] = nonce.readUInt32LE(4)
|
||||||
|
state[15] = nonce.readUInt32LE(8)
|
||||||
|
|
||||||
|
const work = state.slice()
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
quarterRound(work, 0, 4, 8, 12)
|
||||||
|
quarterRound(work, 1, 5, 9, 13)
|
||||||
|
quarterRound(work, 2, 6, 10, 14)
|
||||||
|
quarterRound(work, 3, 7, 11, 15)
|
||||||
|
quarterRound(work, 0, 5, 10, 15)
|
||||||
|
quarterRound(work, 1, 6, 11, 12)
|
||||||
|
quarterRound(work, 2, 7, 8, 13)
|
||||||
|
quarterRound(work, 3, 4, 9, 14)
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = Buffer.allocUnsafe(64)
|
||||||
|
for (let i = 0; i < 16; i++)
|
||||||
|
out.writeUInt32LE((work[i] + state[i]) >>> 0, i * 4)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
const chacha20 = (key, counter, nonce, data) => {
|
||||||
|
const out = Buffer.allocUnsafe(data.length)
|
||||||
|
for (let off = 0; off < data.length; off += 64) {
|
||||||
|
const ks = chachaBlock(key, (counter + (off >>> 6)) >>> 0, nonce)
|
||||||
|
const end = Math.min(off + 64, data.length)
|
||||||
|
for (let i = off; i < end; i++) out[i] = data[i] ^ ks[i - off]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- RSA-256 Key Wrap (网易 NCBL 专用) ----
|
||||||
|
// 256-bit 模数 N 已被因式分解,故可直接计算私钥
|
||||||
|
const RSA_N =
|
||||||
|
0xfd90bd466ff9bc8a3fec2fbcf263b90d5c564879fa5d7aab89b31c1d5cb4139dn
|
||||||
|
const RSA_E = 65537n
|
||||||
|
|
||||||
|
const beToBig = (buf) => {
|
||||||
|
let n = 0n
|
||||||
|
for (const b of buf) n = (n << 8n) | BigInt(b)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
const bigToBe = (n, len) => {
|
||||||
|
const out = Buffer.alloc(len)
|
||||||
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
|
out[i] = Number(n & 0xffn)
|
||||||
|
n >>= 8n
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
const modPow = (base, exp, mod) => {
|
||||||
|
let result = 1n
|
||||||
|
base %= mod
|
||||||
|
while (exp > 0n) {
|
||||||
|
if (exp & 1n) result = (result * base) % mod
|
||||||
|
base = (base * base) % mod
|
||||||
|
exp >>= 1n
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsaWrap = (keyA) => {
|
||||||
|
return bigToBe(modPow(beToBig(keyA), RSA_E, RSA_N), 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- NCBL 加密格式 ----
|
||||||
|
const MAGIC = Buffer.from('NCBL', 'ascii')
|
||||||
|
const NCBL_VERSION = 3
|
||||||
|
const HEADER_FIXED_LEN = 70
|
||||||
|
const META_BLOCK_TYPE = 0x4343
|
||||||
|
const DEFAULT_MAX_FRAME = 0x8000
|
||||||
|
|
||||||
|
const getCompress = () => {
|
||||||
|
if (typeof zlib.zstdCompressSync === 'function') {
|
||||||
|
return { compress: (buf) => zlib.zstdCompressSync(buf), name: 'zstd' }
|
||||||
|
}
|
||||||
|
return { compress: (buf) => zlib.gzipSync(buf), name: 'gzip' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptNCBL = (meta, body, opts = {}) => {
|
||||||
|
const metaBuf = Buffer.isBuffer(meta) ? meta : Buffer.from(meta, 'utf-8')
|
||||||
|
const bodyBuf = Buffer.isBuffer(body) ? body : Buffer.from(body, 'utf-8')
|
||||||
|
const maxFrame = opts.maxFrame || DEFAULT_MAX_FRAME
|
||||||
|
|
||||||
|
const keyA = opts.keyA || crypto.randomBytes(32)
|
||||||
|
if (keyA[0] >= 0xa3) keyA[0] = 0xa2
|
||||||
|
|
||||||
|
const keyB = rsaWrap(keyA)
|
||||||
|
|
||||||
|
const uuid = opts.uuid || crypto.randomBytes(16)
|
||||||
|
if (!opts.uuid) {
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80
|
||||||
|
}
|
||||||
|
const nonce = uuid.subarray(0, 12)
|
||||||
|
const counter = uuid.readUInt32LE(12) >>> 2
|
||||||
|
const baseSeq = opts.baseSeq || crypto.randomBytes(2).readUInt16LE(0)
|
||||||
|
|
||||||
|
const metaCipher = chacha20(keyB, counter, nonce, metaBuf)
|
||||||
|
const metaBlock = Buffer.concat([
|
||||||
|
(() => {
|
||||||
|
const h = Buffer.allocUnsafe(4)
|
||||||
|
h.writeUInt16LE(META_BLOCK_TYPE, 0)
|
||||||
|
h.writeUInt16LE(metaCipher.length, 2)
|
||||||
|
return h
|
||||||
|
})(),
|
||||||
|
metaCipher,
|
||||||
|
])
|
||||||
|
const headerLen = HEADER_FIXED_LEN + metaBlock.length
|
||||||
|
|
||||||
|
const { compress } = getCompress()
|
||||||
|
const compressed = compress(bodyBuf)
|
||||||
|
|
||||||
|
const frames = []
|
||||||
|
let seq = baseSeq
|
||||||
|
for (let off = 0; off < compressed.length || off === 0; off += maxFrame) {
|
||||||
|
const slice = compressed.subarray(off, off + maxFrame)
|
||||||
|
const cipher = chacha20(keyA, counter, nonce, slice)
|
||||||
|
const head = Buffer.allocUnsafe(6)
|
||||||
|
head.writeUInt16LE(cipher.length, 0)
|
||||||
|
head.writeUInt32LE(seq >>> 0, 2)
|
||||||
|
frames.push(head, cipher)
|
||||||
|
seq++
|
||||||
|
if (compressed.length === 0) break
|
||||||
|
}
|
||||||
|
|
||||||
|
const trailing = Buffer.concat(frames)
|
||||||
|
const frameCount = seq - baseSeq
|
||||||
|
|
||||||
|
const header = Buffer.alloc(HEADER_FIXED_LEN)
|
||||||
|
MAGIC.copy(header, 0)
|
||||||
|
header.writeUInt32LE(NCBL_VERSION, 4)
|
||||||
|
header.writeUInt16LE(headerLen, 8)
|
||||||
|
uuid.copy(header, 10)
|
||||||
|
keyB.copy(header, 26)
|
||||||
|
header.writeUInt32LE(baseSeq >>> 0, 58)
|
||||||
|
header.writeUInt32LE((baseSeq + frameCount - 1) >>> 0, 62)
|
||||||
|
header.writeUInt32LE(trailing.length, 66)
|
||||||
|
|
||||||
|
return Buffer.concat([header, metaBlock, trailing])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 日志记录格式 ----
|
||||||
|
const FIELD_SEP = '\x01'
|
||||||
|
|
||||||
|
const buildRecord = ({ time, action, data }) => {
|
||||||
|
const json = typeof data === 'string' ? data : JSON.stringify(data)
|
||||||
|
return [time, action, json].join(FIELD_SEP)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildRecords = (records) => records.map(buildRecord).join('')
|
||||||
|
|
||||||
|
// ---- PLV / PLD 构建器 (桌面客户端格式) ----
|
||||||
|
const buildPlv = (ctx, song, source) => {
|
||||||
|
const now = Date.now()
|
||||||
|
const addRefer = `[F:63][${now}#933#${ctx.app.version}#${ctx.app.versionCode}#c9156c3][e][2][23][cell_pc_songlist_song:2|page_pc_songlist_songflow|page_mine_like_music][${song.id}:song:x:x|:::|${source.id}:list::]`
|
||||||
|
const multiRefers = [
|
||||||
|
'[F:26][s][18][_ai]',
|
||||||
|
'[F:26][s][12][_ai]',
|
||||||
|
`[F:63][${now}#933#${ctx.app.version}#${ctx.app.versionCode}#c9156c3][e][2][8][cell_pc_main_tab_entrance:6|page_pc_main_tab][我喜欢的音乐:spm::|:::]`,
|
||||||
|
'[F:26][s][5][_ai]',
|
||||||
|
'[F:26][s][0][_ai]',
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: 'circulation',
|
||||||
|
download: 0,
|
||||||
|
alg: '',
|
||||||
|
status: 'front',
|
||||||
|
id: String(song.id),
|
||||||
|
bitrate: song.bitrate,
|
||||||
|
type: 'song',
|
||||||
|
is_listentogether: 0,
|
||||||
|
source: source.name,
|
||||||
|
is_heart: 0,
|
||||||
|
resource_ratio: '',
|
||||||
|
resource_time: song.time,
|
||||||
|
musiceffect_id: '',
|
||||||
|
app_mode: 2,
|
||||||
|
bitrate_level: song.level,
|
||||||
|
_addrefer: addRefer,
|
||||||
|
_multirefers: multiRefers,
|
||||||
|
vipType: ctx.auth.vipType,
|
||||||
|
fee: 1,
|
||||||
|
file: 4,
|
||||||
|
rightSource: 0,
|
||||||
|
sourceId: source.id,
|
||||||
|
sourcetype: source.type,
|
||||||
|
libra_abt: '',
|
||||||
|
channel: ctx.app.channel,
|
||||||
|
curStartChannel: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildPld = (ctx, song, source, played) => {
|
||||||
|
const now = Date.now()
|
||||||
|
const addRefer = `[F:63][${now}#616#${ctx.app.version}#${ctx.app.versionCode}#c9156c3][e][2][92][btn_pc_cover_play|cell_pc_songlist_song:6|page_pc_songlist_songflow|page_mine_like_music][:::|${song.id}:song:x:x|:::|${source.id}:list::]`
|
||||||
|
const multiRefers = [
|
||||||
|
'[F:26][s][87][_ai]',
|
||||||
|
'[F:26][s][81][_ai]',
|
||||||
|
'[F:26][s][75][_ai]',
|
||||||
|
'[F:26][s][69][_ai]',
|
||||||
|
'[F:26][s][63][_ai]',
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: 'circulation',
|
||||||
|
download: 0,
|
||||||
|
alg: '',
|
||||||
|
status: 'front',
|
||||||
|
id: String(song.id),
|
||||||
|
time: played,
|
||||||
|
type: 'song',
|
||||||
|
is_listentogether: 0,
|
||||||
|
source: source.name,
|
||||||
|
is_heart: 0,
|
||||||
|
realtime: played,
|
||||||
|
resource_ratio: '',
|
||||||
|
resource_time: song.time,
|
||||||
|
musiceffect_id: '1001',
|
||||||
|
app_mode: 1,
|
||||||
|
lyriceffect: 'default',
|
||||||
|
displayMode: 'classic',
|
||||||
|
bitrate: song.bitrate,
|
||||||
|
bitrate_level: song.level,
|
||||||
|
_addrefer: addRefer,
|
||||||
|
_multirefers: multiRefers,
|
||||||
|
vipType: ctx.auth.vipType,
|
||||||
|
fee: 8,
|
||||||
|
file: 4,
|
||||||
|
rightSource: 0,
|
||||||
|
sourceId: source.id,
|
||||||
|
sourcetype: source.type,
|
||||||
|
end: 'interrupt',
|
||||||
|
libra_abt: '',
|
||||||
|
channel: ctx.app.channel,
|
||||||
|
curStartChannel: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Cookie → 设备/认证上下文 转换 ----
|
||||||
|
const extractContext = (cookieObj) => {
|
||||||
|
return {
|
||||||
|
app: {
|
||||||
|
id: cookieObj.appid || '',
|
||||||
|
urs: '',
|
||||||
|
pid: '',
|
||||||
|
nsm: cookieObj.WEVNSM || '1.0.0',
|
||||||
|
cid:
|
||||||
|
cookieObj.WNMCID ||
|
||||||
|
`${crypto.randomBytes(3).toString('hex')}.${Date.now()}.01.0`,
|
||||||
|
channel: cookieObj.channel || 'netease',
|
||||||
|
version: cookieObj.appver || '3.1.35',
|
||||||
|
versionCode: cookieObj.versioncode || '205293',
|
||||||
|
buildCode: cookieObj.buildver || '',
|
||||||
|
buildType: 'release',
|
||||||
|
packageId: '',
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
id: cookieObj.deviceId || cookieObj.sDeviceId || '',
|
||||||
|
ti: cookieObj.NMTID || '',
|
||||||
|
sign: cookieObj.clientSign || '',
|
||||||
|
model: cookieObj.mode || cookieObj.mobilename || '',
|
||||||
|
nnid: cookieObj._ntes_nnid || ',',
|
||||||
|
nuid: cookieObj._ntes_nuid || '',
|
||||||
|
csrf: cookieObj.__csrf || '',
|
||||||
|
systemType: cookieObj.os || 'pc',
|
||||||
|
systemVersion:
|
||||||
|
cookieObj.osver ||
|
||||||
|
'Microsoft-Windows-10-Professional-build-19045-64bit',
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
id: cookieObj.uid || '',
|
||||||
|
token: cookieObj.MUSIC_U || '',
|
||||||
|
sessionId: cookieObj['JSESSIONID-WYYY'] || '',
|
||||||
|
vipType: cookieObj.vipType || '',
|
||||||
|
},
|
||||||
|
startTime: Date.now(),
|
||||||
|
processId: Math.floor(Math.random() * 90000) + 10000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 query.cookie 解析 cookie 对象
|
||||||
|
// 支持字符串 ("key=val; key2=val2") 或已解析的对象
|
||||||
|
// 注意:不做 URL decode!MUSIC_U 中的 '+' 会被 decodeURIComponent 错误地转为空格
|
||||||
|
const parseCookie = (cookie) => {
|
||||||
|
if (typeof cookie === 'object' && cookie !== null) return cookie
|
||||||
|
if (typeof cookie === 'string') {
|
||||||
|
const obj = {}
|
||||||
|
cookie.split(';').forEach((part) => {
|
||||||
|
const idx = part.indexOf('=')
|
||||||
|
if (idx > 0) {
|
||||||
|
const key = part.substring(0, idx).trim()
|
||||||
|
const val = part.substring(idx + 1).trim()
|
||||||
|
if (key) obj[key] = val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 辅助函数 ----
|
||||||
|
const randomUUID = () => {
|
||||||
|
if (typeof crypto.randomUUID === 'function') {
|
||||||
|
return crypto.randomUUID().replace(/-/g, '')
|
||||||
|
}
|
||||||
|
return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/x/g, () =>
|
||||||
|
Math.floor(Math.random() * 16).toString(16),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomHex = (len) => crypto.randomBytes(len / 2).toString('hex')
|
||||||
|
|
||||||
|
// ---- HTTP 上传工具 (NCBL 专用) ----
|
||||||
|
const buildMultipart = (payload) => {
|
||||||
|
const boundary = randomUUID()
|
||||||
|
const fileName = `op_${Math.floor(Math.random() * 90000) + 10000}_0_${Math.floor(Math.random() * 4294967295) + 1}`
|
||||||
|
|
||||||
|
const CRLF = '\r\n'
|
||||||
|
const headerLines = [
|
||||||
|
`--${boundary}`,
|
||||||
|
`Content-Disposition: form-data; name="file"; filename="${fileName}"`,
|
||||||
|
'Content-Type: multipart/form-data',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
].join(CRLF)
|
||||||
|
const footer = `${CRLF}--${boundary}--${CRLF}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
boundary,
|
||||||
|
fileName,
|
||||||
|
multipartBody: Buffer.concat([
|
||||||
|
Buffer.from(headerLines, 'utf-8'),
|
||||||
|
payload,
|
||||||
|
Buffer.from(footer, 'utf-8'),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildCookieStr = (ctx) => {
|
||||||
|
const parts = [
|
||||||
|
`JSESSIONID-WYYY=${ctx.auth.sessionId}`,
|
||||||
|
`MUSIC_U=${ctx.auth.token}`,
|
||||||
|
`NMTID=${ctx.device.ti}`,
|
||||||
|
`WEVNSM=${ctx.app.nsm}`,
|
||||||
|
`WNMCID=${ctx.app.cid}`,
|
||||||
|
`__csrf=${ctx.device.csrf}`,
|
||||||
|
`__remember_me=true`,
|
||||||
|
`_iuqxldmzr_=33`,
|
||||||
|
`_ntes_nnid=${ctx.device.nnid}`,
|
||||||
|
`_ntes_nuid=${ctx.device.nuid}`,
|
||||||
|
`appver=${ctx.app.version}.${ctx.app.versionCode}`,
|
||||||
|
`channel=${ctx.app.channel}`,
|
||||||
|
`clientSign=${ctx.device.sign}`,
|
||||||
|
`deviceId=${ctx.device.id}`,
|
||||||
|
`mode=${ctx.device.model}`,
|
||||||
|
`ntes_kaola_ad=1`,
|
||||||
|
`os=${ctx.device.systemType}`,
|
||||||
|
`osver=${ctx.device.systemVersion}`,
|
||||||
|
]
|
||||||
|
return parts.join('; ')
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMetaJson = (ctx) =>
|
||||||
|
JSON.stringify({
|
||||||
|
'JSESSIONID-WYYY': ctx.auth.sessionId,
|
||||||
|
MUSIC_U: ctx.auth.token,
|
||||||
|
NMTID: ctx.device.ti,
|
||||||
|
WEVNSM: ctx.app.nsm,
|
||||||
|
WNMCID: ctx.app.cid,
|
||||||
|
__csrf: ctx.device.csrf,
|
||||||
|
_iuqxldmzr_: '33',
|
||||||
|
_ntes_nnid: ctx.device.nnid,
|
||||||
|
_ntes_nuid: ctx.device.nuid,
|
||||||
|
appver: `${ctx.app.version}.${ctx.app.versionCode}`,
|
||||||
|
channel: ctx.app.channel,
|
||||||
|
clientSign: ctx.device.sign,
|
||||||
|
deviceId: ctx.device.id,
|
||||||
|
mode: ctx.device.model,
|
||||||
|
ntes_kaola_ad: '1',
|
||||||
|
os: ctx.device.systemType,
|
||||||
|
osver: ctx.device.systemVersion,
|
||||||
|
})
|
||||||
|
|
||||||
|
const doUpload = async (ctx, metaJson, body, cookieStr, label) => {
|
||||||
|
const uploadUrl = DOMAIN3 + '/api/clientlog/encrypt/upload?multiupload=true'
|
||||||
|
|
||||||
|
const payload = encryptNCBL(metaJson, body)
|
||||||
|
const { boundary, fileName, multipartBody } = buildMultipart(payload)
|
||||||
|
|
||||||
|
const resp = await axios({
|
||||||
|
method: 'POST',
|
||||||
|
url: uploadUrl,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||||
|
Referer: 'https://music.163.com/di',
|
||||||
|
'User-Agent': `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/${ctx.app.version}`,
|
||||||
|
'Accept-Encoding': 'gzip,deflate',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.8',
|
||||||
|
Cookie: cookieStr,
|
||||||
|
},
|
||||||
|
data: multipartBody,
|
||||||
|
maxBodyLength: 10 * 1024 * 1024,
|
||||||
|
timeout: 15000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const respBody = resp.data
|
||||||
|
const code = respBody?.code
|
||||||
|
const success =
|
||||||
|
code === 200 && respBody?.data?.successfiles?.includes?.(fileName)
|
||||||
|
|
||||||
|
return { success, fileName, payload, respBody }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 导出 ----
|
||||||
|
module.exports = {
|
||||||
|
chacha20,
|
||||||
|
rsaWrap,
|
||||||
|
encryptNCBL,
|
||||||
|
getCompress,
|
||||||
|
MAGIC,
|
||||||
|
NCBL_VERSION,
|
||||||
|
HEADER_FIXED_LEN,
|
||||||
|
META_BLOCK_TYPE,
|
||||||
|
DEFAULT_MAX_FRAME,
|
||||||
|
buildRecord,
|
||||||
|
buildRecords,
|
||||||
|
FIELD_SEP,
|
||||||
|
buildPlv,
|
||||||
|
buildPld,
|
||||||
|
extractContext,
|
||||||
|
parseCookie,
|
||||||
|
randomUUID,
|
||||||
|
randomHex,
|
||||||
|
buildMultipart,
|
||||||
|
buildCookieStr,
|
||||||
|
buildMetaJson,
|
||||||
|
doUpload,
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ const createOption = (query, crypto = '') => {
|
|||||||
e_r: query.e_r || undefined,
|
e_r: query.e_r || undefined,
|
||||||
domain: query.domain || '',
|
domain: query.domain || '',
|
||||||
checkToken: query.checkToken || false,
|
checkToken: query.checkToken || false,
|
||||||
|
headers: query.headers || {},
|
||||||
|
timeout: query.timeout || 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = createOption
|
module.exports = createOption
|
||||||
|
|||||||
+16
-1
@@ -88,6 +88,12 @@ const osMap = {
|
|||||||
osver: '16.2',
|
osver: '16.2',
|
||||||
channel: 'distribution',
|
channel: 'distribution',
|
||||||
},
|
},
|
||||||
|
osx: {
|
||||||
|
os: 'osx',
|
||||||
|
appver: '3.1.10.5100',
|
||||||
|
osver: '15.5',
|
||||||
|
channel: 'netease',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预先定义userAgentMap
|
// 预先定义userAgentMap
|
||||||
@@ -306,7 +312,11 @@ const createRequest = (uri, data, options) => {
|
|||||||
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
||||||
|
|
||||||
headers['Cookie'] = createHeaderCookie(header)
|
headers['Cookie'] = createHeaderCookie(header)
|
||||||
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
|
headers['User-Agent'] =
|
||||||
|
options.ua ||
|
||||||
|
(cookie.os === 'osx'
|
||||||
|
? 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
||||||
|
: chooseUserAgent('api', 'iphone'))
|
||||||
|
|
||||||
if (crypto === 'eapi') {
|
if (crypto === 'eapi') {
|
||||||
// headers['x-aeapi'] = true // 服务器会使用gzip压缩返回值
|
// headers['x-aeapi'] = true // 服务器会使用gzip压缩返回值
|
||||||
@@ -334,6 +344,11 @@ const createRequest = (uri, data, options) => {
|
|||||||
httpsAgent: createHttpsAgent(),
|
httpsAgent: createHttpsAgent(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自定义超时
|
||||||
|
if (options.timeout > 0) {
|
||||||
|
settings.timeout = options.timeout
|
||||||
|
}
|
||||||
|
|
||||||
// 使用返回值加密
|
// 使用返回值加密
|
||||||
const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r
|
const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r
|
||||||
const use_xeapi = crypto === 'xeapi'
|
const use_xeapi = crypto === 'xeapi'
|
||||||
|
|||||||
Reference in New Issue
Block a user