mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-20 22:51:23 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60ee927e32 | ||
|
|
eb07a525eb | ||
|
|
6fc4231142 | ||
|
|
fa69736300 | ||
|
|
73a5e9d788 | ||
|
|
dc6f56e8a8 | ||
|
|
2c9da8507d | ||
|
|
1db354464f | ||
|
|
395a80e74b | ||
|
|
6550c7c827 | ||
|
|
c959866436 | ||
|
|
e3a1c041b6 | ||
|
|
e2324e26da | ||
|
|
f01e770c35 | ||
|
|
abb56ec014 | ||
|
|
0300c1b51e | ||
|
|
9896242333 | ||
|
|
e3812be97b | ||
|
|
e7ff0da4ba | ||
|
|
65261fa658 | ||
|
|
cb5b2e7d0f | ||
|
|
f58fd4b50d | ||
|
|
1a5f91a192 | ||
|
|
50cc26f297 | ||
|
|
a9c33de182 | ||
|
|
39e0e0718d | ||
|
|
6175d55f89 |
@@ -260,13 +260,13 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
# Step 2: run the sync action
|
# Step 2: run the sync action
|
||||||
- name: Sync upstream changes
|
- name: Sync upstream changes
|
||||||
id: sync
|
id: sync
|
||||||
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.2
|
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.3
|
||||||
with:
|
with:
|
||||||
upstream_sync_repo: NeteaseCloudMusicApiEnhanced/api-enhanced
|
upstream_sync_repo: NeteaseCloudMusicApiEnhanced/api-enhanced
|
||||||
upstream_sync_branch: main
|
upstream_sync_branch: main
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署(如果原版诈尸, 我会及时同步 or 归档)。
|
网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>
|
>
|
||||||
@@ -215,7 +215,7 @@ pnpm test
|
|||||||
|
|
||||||
原作者 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 项目为本项目基础 (该项目在`npmjs`网站上仍持续维护, 但 github 仓库已不再更新)
|
原作者 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 项目为本项目基础 (该项目在`npmjs`网站上仍持续维护, 但 github 仓库已不再更新)
|
||||||
|
|
||||||
感谢大佬们为逆向eapi, weapi等加密算法所做的贡献
|
感谢大佬们为逆向eapi, weapi, xeapi等加密算法所做的贡献
|
||||||
|
|
||||||
项目参考:
|
项目参考:
|
||||||
|
|
||||||
@@ -244,6 +244,8 @@ pnpm test
|
|||||||
|
|
||||||
- [Yueby/music-together](https://github.com/Yueby/music-together)
|
- [Yueby/music-together](https://github.com/Yueby/music-together)
|
||||||
|
|
||||||
|
- [chthollyphlie/folia-major](https://github.com/chthollyphile/folia-major)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT License](https://github.com/MoeFurina/NeteaseCloudMusicApiEnhanced/blob/main/LICENSE)
|
[MIT License](https://github.com/MoeFurina/NeteaseCloudMusicApiEnhanced/blob/main/LICENSE)
|
||||||
|
|||||||
Vendored
+9
-27
@@ -2053,9 +2053,7 @@ export function listen_data_today_song(
|
|||||||
params: RequestBaseConfig,
|
params: RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function listen_data_total(
|
export function listen_data_total(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function listen_data_year_report(
|
export function listen_data_year_report(
|
||||||
params: RequestBaseConfig,
|
params: RequestBaseConfig,
|
||||||
@@ -2192,9 +2190,7 @@ export function radio_sport_get(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function recent_listen_list(
|
export function recent_listen_list(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function recommend_songs_dislike(
|
export function recommend_songs_dislike(
|
||||||
params: {
|
params: {
|
||||||
@@ -2202,9 +2198,7 @@ export function recommend_songs_dislike(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function register_anonimous(
|
export function register_anonimous(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function sati_resource_list(
|
export function sati_resource_list(
|
||||||
params: {
|
params: {
|
||||||
@@ -2229,9 +2223,7 @@ export function sati_resource_sub_list(
|
|||||||
params: RequestBaseConfig,
|
params: RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function sati_tag_list(
|
export function sati_tag_list(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function sati_timescene_resources_get(
|
export function sati_timescene_resources_get(
|
||||||
params: RequestBaseConfig,
|
params: RequestBaseConfig,
|
||||||
@@ -2363,9 +2355,7 @@ export function song_url_match(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function song_url_ncmget(
|
export function song_url_ncmget(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function song_url_v1_302(
|
export function song_url_v1_302(
|
||||||
params: {
|
params: {
|
||||||
@@ -2388,9 +2378,7 @@ export function threshold_detail_get(
|
|||||||
params: RequestBaseConfig,
|
params: RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function toplist_detail_v2(
|
export function toplist_detail_v2(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function ugc_album_get(
|
export function ugc_album_get(
|
||||||
params: {
|
params: {
|
||||||
@@ -2433,9 +2421,7 @@ export function ugc_song_get(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function ugc_user_devote(
|
export function ugc_user_devote(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function user_detail_new(
|
export function user_detail_new(
|
||||||
params: {
|
params: {
|
||||||
@@ -2516,13 +2502,9 @@ export function verify_qrcodestatus(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
export function vip_sign(
|
export function vip_sign(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function vip_sign_info(
|
export function vip_sign_info(params: RequestBaseConfig): Promise<Response>
|
||||||
params: RequestBaseConfig,
|
|
||||||
): Promise<Response>
|
|
||||||
|
|
||||||
export function vip_tasks_v1(
|
export function vip_tasks_v1(
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// 私信和通知接口
|
// 私信和通知接口
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {}
|
const data = {}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// 获取游客cookie
|
||||||
|
|
||||||
const CryptoJS = require('crypto-js')
|
const CryptoJS = require('crypto-js')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
@@ -37,7 +39,7 @@ module.exports = async (query, request) => {
|
|||||||
let result = await request(
|
let result = await request(
|
||||||
`/api/register/anonimous`,
|
`/api/register/anonimous`,
|
||||||
data,
|
data,
|
||||||
createOption(query, 'weapi'),
|
createOption(query, 'xeapi'),
|
||||||
)
|
)
|
||||||
if (result.body.code === 200) {
|
if (result.body.code === 200) {
|
||||||
result = {
|
result = {
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
const { default: axios } = require('axios')
|
||||||
|
const encrypt = require('../util/crypto')
|
||||||
|
const { APP_CONF } = require('../util/config.json')
|
||||||
|
|
||||||
|
const generateNonce = () => {
|
||||||
|
let nonce = ''
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
nonce += Math.floor(Math.random() * 10).toString()
|
||||||
|
}
|
||||||
|
return nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const nonce = generateNonce()
|
||||||
|
const timestamp = String(Date.now())
|
||||||
|
const deviceId = query.deviceId || global.deviceId || ''
|
||||||
|
const currentKeyVersion = query.currentKeyVersion || ''
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
appVersion: '9.1.65',
|
||||||
|
currentKeyVersion,
|
||||||
|
deviceId,
|
||||||
|
nonce,
|
||||||
|
os: 'android',
|
||||||
|
requestType: 'active',
|
||||||
|
signature: encrypt.xeapiSign(timestamp, nonce),
|
||||||
|
t1: '',
|
||||||
|
t2: '',
|
||||||
|
timestamp,
|
||||||
|
uid: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await axios({
|
||||||
|
method: 'POST',
|
||||||
|
url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get',
|
||||||
|
headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
||||||
|
Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '',
|
||||||
|
},
|
||||||
|
data: new URLSearchParams(data).toString(),
|
||||||
|
proxy: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
!res.data ||
|
||||||
|
res.data.code !== 200 ||
|
||||||
|
!res.data.data ||
|
||||||
|
!res.data.data.encryptedData
|
||||||
|
) {
|
||||||
|
throw new Error('xeapi public key request failed')
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!res.data.data.signature ||
|
||||||
|
encrypt.xeapiSign(res.data.data.timestamp, nonce) !==
|
||||||
|
res.data.data.signature
|
||||||
|
) {
|
||||||
|
throw new Error('xeapi public key response signature mismatch')
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = encrypt.xeapiDecryptPublicKey(res.data.data.encryptedData)
|
||||||
|
if (!publicKey.sk) {
|
||||||
|
throw new Error('xeapi public key response missing sk')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
...publicKey,
|
||||||
|
deviceId,
|
||||||
|
},
|
||||||
|
cookie: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// 提交歌曲播放状态
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
const generateSessionId = () =>
|
||||||
|
Array.from(
|
||||||
|
{ length: 12 },
|
||||||
|
() =>
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'[Math.floor(Math.random() * 36)],
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
sessionId,
|
||||||
|
progress = 0,
|
||||||
|
playMode = 'list_loop',
|
||||||
|
type = 'song',
|
||||||
|
} = query
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return Promise.reject({
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
code: 400,
|
||||||
|
msg: '缺少必要参数:id',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const playStateSubmitReq = JSON.stringify({
|
||||||
|
resource: {
|
||||||
|
id: String(id),
|
||||||
|
type: type,
|
||||||
|
},
|
||||||
|
progress: Number(progress) || 0,
|
||||||
|
sessionId: sessionId || generateSessionId(),
|
||||||
|
playMode: playMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
playStateSubmitReq: playStateSubmitReq,
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(
|
||||||
|
'/api/relay/play/state/submit',
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
+55
-5
@@ -1,8 +1,40 @@
|
|||||||
// 听歌打卡
|
// 听歌打卡
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const data = {
|
// 注入 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 +47,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 = 'https://clientlog.music.163.com'
|
||||||
|
|
||||||
|
// 发送两次请求
|
||||||
|
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,9 @@
|
|||||||
|
// 从云盘获取歌曲下载链接
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
songId: query.id,
|
||||||
|
}
|
||||||
|
return request(`/api/cloud/dowonload`, data, createOption(query, 'eapi'))
|
||||||
|
}
|
||||||
+8
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neteasecloudmusicapienhanced/api",
|
"name": "@neteasecloudmusicapienhanced/api",
|
||||||
"version": "4.34.3",
|
"version": "4.35.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"
|
||||||
@@ -74,13 +76,13 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.2",
|
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.2",
|
||||||
"axios": "^1.16.1",
|
"axios": "^1.17.0",
|
||||||
"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",
|
||||||
"express-fileupload": "^1.5.2",
|
"express-fileupload": "^1.5.2",
|
||||||
"gzip": "^0.1.0",
|
"gzip": "^0.1.0",
|
||||||
"music-metadata": "^11.12.3",
|
"music-metadata": "^11.13.0",
|
||||||
"node-forge": "^1.4.0",
|
"node-forge": "^1.4.0",
|
||||||
"pac-proxy-agent": "^7.2.0",
|
"pac-proxy-agent": "^7.2.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
@@ -95,9 +97,9 @@
|
|||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@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.5.0",
|
"@types/node": "25.9.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.60.0",
|
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
||||||
"@typescript-eslint/parser": "^8.60.0",
|
"@typescript-eslint/parser": "^8.60.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",
|
||||||
|
|||||||
Generated
+130
-123
@@ -12,8 +12,8 @@ importers:
|
|||||||
specifier: ^0.3.2
|
specifier: ^0.3.2
|
||||||
version: 0.3.2
|
version: 0.3.2
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.16.1
|
specifier: ^1.17.0
|
||||||
version: 1.16.1
|
version: 1.17.0
|
||||||
crypto-js:
|
crypto-js:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
@@ -30,8 +30,8 @@ importers:
|
|||||||
specifier: ^0.1.0
|
specifier: ^0.1.0
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
music-metadata:
|
music-metadata:
|
||||||
specifier: ^11.12.3
|
specifier: ^11.13.0
|
||||||
version: 11.12.3
|
version: 11.13.0
|
||||||
node-forge:
|
node-forge:
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
@@ -70,14 +70,14 @@ importers:
|
|||||||
specifier: ^10.0.10
|
specifier: ^10.0.10
|
||||||
version: 10.0.10
|
version: 10.0.10
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 25.5.0
|
specifier: 25.9.2
|
||||||
version: 25.5.0
|
version: 25.9.2
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ^8.60.0
|
specifier: ^8.60.1
|
||||||
version: 8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)
|
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)
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: ^8.60.0
|
specifier: ^8.60.1
|
||||||
version: 8.60.0(eslint@9.39.4)(typescript@5.9.3)
|
version: 8.60.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
|
||||||
@@ -286,8 +286,8 @@ packages:
|
|||||||
'@types/mocha@10.0.10':
|
'@types/mocha@10.0.10':
|
||||||
resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==}
|
resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==}
|
||||||
|
|
||||||
'@types/node@25.5.0':
|
'@types/node@25.9.2':
|
||||||
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
|
resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==}
|
||||||
|
|
||||||
'@types/qs@6.15.1':
|
'@types/qs@6.15.1':
|
||||||
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
|
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
|
||||||
@@ -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.0':
|
'@typescript-eslint/eslint-plugin@8.60.1':
|
||||||
resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==}
|
resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==}
|
||||||
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.0
|
'@typescript-eslint/parser': ^8.60.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.0':
|
'@typescript-eslint/parser@8.60.1':
|
||||||
resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==}
|
resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==}
|
||||||
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.0':
|
'@typescript-eslint/project-service@8.60.1':
|
||||||
resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==}
|
resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==}
|
||||||
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.0':
|
'@typescript-eslint/scope-manager@8.60.1':
|
||||||
resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==}
|
resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==}
|
||||||
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.0':
|
'@typescript-eslint/tsconfig-utils@8.60.1':
|
||||||
resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==}
|
resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==}
|
||||||
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.0':
|
'@typescript-eslint/type-utils@8.60.1':
|
||||||
resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==}
|
resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==}
|
||||||
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.0':
|
'@typescript-eslint/types@8.60.1':
|
||||||
resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==}
|
resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==}
|
||||||
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.0':
|
'@typescript-eslint/typescript-estree@8.60.1':
|
||||||
resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==}
|
resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==}
|
||||||
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.0':
|
'@typescript-eslint/utils@8.60.1':
|
||||||
resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==}
|
resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==}
|
||||||
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.0':
|
'@typescript-eslint/visitor-keys@8.60.1':
|
||||||
resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==}
|
resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==}
|
||||||
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':
|
||||||
@@ -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.16.1:
|
axios@1.17.0:
|
||||||
resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==}
|
resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
@@ -1557,8 +1557,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.1.1:
|
js-yaml@4.2.0:
|
||||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
jsesc@2.5.2:
|
jsesc@2.5.2:
|
||||||
@@ -1636,6 +1636,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
media-typer@2.0.0:
|
||||||
|
resolution: {integrity: sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
merge-descriptors@1.0.3:
|
merge-descriptors@1.0.3:
|
||||||
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
||||||
|
|
||||||
@@ -1729,8 +1733,8 @@ packages:
|
|||||||
multistream@4.1.0:
|
multistream@4.1.0:
|
||||||
resolution: {integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==}
|
resolution: {integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==}
|
||||||
|
|
||||||
music-metadata@11.12.3:
|
music-metadata@11.13.0:
|
||||||
resolution: {integrity: sha512-n6hSTZkuD59qWgHh6IP5dtDlDZQXoxk/bcA85Jywg8Z1iFrlNgl2+GTFgjZyn52W5UgQpV42V4XqrQZZAMbZTQ==}
|
resolution: {integrity: sha512-uXRaov9dfjSpQufXIU7sMxVZnh+FilCQv2mXn+K5EJ/decP3dTWrgvPYa5r6MtRbieNSCE708Da4J0u1UGfQIw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
napi-build-utils@1.0.2:
|
napi-build-utils@1.0.2:
|
||||||
@@ -2149,8 +2153,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.1:
|
semver@7.8.3:
|
||||||
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
|
resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2316,12 +2320,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==}
|
resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
string.prototype.trim@1.2.10:
|
string.prototype.trim@1.2.11:
|
||||||
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
|
resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
string.prototype.trimend@1.0.9:
|
string.prototype.trimend@1.0.10:
|
||||||
resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
|
resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
string.prototype.trimstart@1.0.8:
|
string.prototype.trimstart@1.0.8:
|
||||||
@@ -2384,12 +2388,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
tinyexec@1.2.3:
|
tinyexec@1.2.4:
|
||||||
resolution: {integrity: sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==}
|
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
tinyglobby@0.2.16:
|
tinyglobby@0.2.17:
|
||||||
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
to-fast-properties@2.0.0:
|
to-fast-properties@2.0.0:
|
||||||
@@ -2493,8 +2497,8 @@ packages:
|
|||||||
undefsafe@2.0.5:
|
undefsafe@2.0.5:
|
||||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
||||||
|
|
||||||
undici-types@7.18.2:
|
undici-types@7.24.6:
|
||||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
||||||
|
|
||||||
universal-deep-strict-equal@1.2.2:
|
universal-deep-strict-equal@1.2.2:
|
||||||
resolution: {integrity: sha512-UpnFi3/IF3jZHIHTdQXTHLCqpBP3805OFFRPHgvCS7k0oob2YVXxMTjS0U0g9qJTzqFRMwEnFFSlFLqt6zwjTQ==}
|
resolution: {integrity: sha512-UpnFi3/IF3jZHIHTdQXTHLCqpBP3805OFFRPHgvCS7k0oob2YVXxMTjS0U0g9qJTzqFRMwEnFFSlFLqt6zwjTQ==}
|
||||||
@@ -2542,8 +2546,8 @@ packages:
|
|||||||
which-module@2.0.1:
|
which-module@2.0.1:
|
||||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||||
|
|
||||||
which-typed-array@1.1.21:
|
which-typed-array@1.1.22:
|
||||||
resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==}
|
resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
@@ -2702,7 +2706,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.1.1
|
js-yaml: 4.2.0
|
||||||
minimatch: 3.1.5
|
minimatch: 3.1.5
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -2759,7 +2763,7 @@ snapshots:
|
|||||||
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.2':
|
'@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unblockneteasemusic/server': 0.28.0
|
'@unblockneteasemusic/server': 0.28.0
|
||||||
axios: 1.16.1
|
axios: 1.17.0
|
||||||
dotenv: 17.4.2
|
dotenv: 17.4.2
|
||||||
express: 4.22.2
|
express: 4.22.2
|
||||||
https: 1.0.0
|
https: 1.0.0
|
||||||
@@ -2798,15 +2802,15 @@ snapshots:
|
|||||||
'@types/body-parser@1.19.6':
|
'@types/body-parser@1.19.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/node': 25.5.0
|
'@types/node': 25.9.2
|
||||||
|
|
||||||
'@types/busboy@1.5.4':
|
'@types/busboy@1.5.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.5.0
|
'@types/node': 25.9.2
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.5.0
|
'@types/node': 25.9.2
|
||||||
|
|
||||||
'@types/estree@1.0.9': {}
|
'@types/estree@1.0.9': {}
|
||||||
|
|
||||||
@@ -2817,7 +2821,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/express-serve-static-core@5.1.1':
|
'@types/express-serve-static-core@5.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.5.0
|
'@types/node': 25.9.2
|
||||||
'@types/qs': 6.15.1
|
'@types/qs': 6.15.1
|
||||||
'@types/range-parser': 1.2.7
|
'@types/range-parser': 1.2.7
|
||||||
'@types/send': 1.2.1
|
'@types/send': 1.2.1
|
||||||
@@ -2834,9 +2838,9 @@ snapshots:
|
|||||||
|
|
||||||
'@types/mocha@10.0.10': {}
|
'@types/mocha@10.0.10': {}
|
||||||
|
|
||||||
'@types/node@25.5.0':
|
'@types/node@25.9.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.18.2
|
undici-types: 7.24.6
|
||||||
|
|
||||||
'@types/qs@6.15.1': {}
|
'@types/qs@6.15.1': {}
|
||||||
|
|
||||||
@@ -2844,21 +2848,21 @@ snapshots:
|
|||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.5.0
|
'@types/node': 25.9.2
|
||||||
|
|
||||||
'@types/serve-static@2.2.0':
|
'@types/serve-static@2.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/node': 25.5.0
|
'@types/node': 25.9.2
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
|
'@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)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.60.0(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/scope-manager': 8.60.0
|
'@typescript-eslint/scope-manager': 8.60.1
|
||||||
'@typescript-eslint/type-utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.60.1(eslint@9.39.4)(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.60.0
|
'@typescript-eslint/visitor-keys': 8.60.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
|
||||||
@@ -2867,41 +2871,41 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.60.1(eslint@9.39.4)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.60.0
|
'@typescript-eslint/scope-manager': 8.60.1
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.1
|
||||||
'@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.60.0
|
'@typescript-eslint/visitor-keys': 8.60.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.0(typescript@5.9.3)':
|
'@typescript-eslint/project-service@8.60.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.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.0':
|
'@typescript-eslint/scope-manager@8.60.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.1
|
||||||
'@typescript-eslint/visitor-keys': 8.60.0
|
'@typescript-eslint/visitor-keys': 8.60.1
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)':
|
'@typescript-eslint/tsconfig-utils@8.60.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.60.0(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.60.1(eslint@9.39.4)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.1
|
||||||
'@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.60.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)
|
||||||
@@ -2909,37 +2913,37 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/types@8.60.0': {}
|
'@typescript-eslint/types@8.60.1': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.60.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.60.0(typescript@5.9.3)
|
'@typescript-eslint/project-service': 8.60.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.1
|
||||||
'@typescript-eslint/visitor-keys': 8.60.0
|
'@typescript-eslint/visitor-keys': 8.60.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
semver: 7.8.1
|
semver: 7.8.3
|
||||||
tinyglobby: 0.2.16
|
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.0(eslint@9.39.4)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.60.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.0
|
'@typescript-eslint/scope-manager': 8.60.1
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.1
|
||||||
'@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.60.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.0':
|
'@typescript-eslint/visitor-keys@8.60.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.60.0
|
'@typescript-eslint/types': 8.60.1
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
'@unblockneteasemusic/server@0.28.0':
|
'@unblockneteasemusic/server@0.28.0':
|
||||||
@@ -3056,7 +3060,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
|
|
||||||
axios@1.16.1:
|
axios@1.17.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.16.0
|
follow-redirects: 1.16.0
|
||||||
form-data: 4.0.5
|
form-data: 4.0.5
|
||||||
@@ -3511,15 +3515,15 @@ snapshots:
|
|||||||
safe-regex-test: 1.1.0
|
safe-regex-test: 1.1.0
|
||||||
set-proto: 1.0.0
|
set-proto: 1.0.0
|
||||||
stop-iteration-iterator: 1.1.0
|
stop-iteration-iterator: 1.1.0
|
||||||
string.prototype.trim: 1.2.10
|
string.prototype.trim: 1.2.11
|
||||||
string.prototype.trimend: 1.0.9
|
string.prototype.trimend: 1.0.10
|
||||||
string.prototype.trimstart: 1.0.8
|
string.prototype.trimstart: 1.0.8
|
||||||
typed-array-buffer: 1.0.3
|
typed-array-buffer: 1.0.3
|
||||||
typed-array-byte-length: 1.0.3
|
typed-array-byte-length: 1.0.3
|
||||||
typed-array-byte-offset: 1.0.4
|
typed-array-byte-offset: 1.0.4
|
||||||
typed-array-length: 1.0.8
|
typed-array-length: 1.0.8
|
||||||
unbox-primitive: 1.1.0
|
unbox-primitive: 1.1.0
|
||||||
which-typed-array: 1.1.21
|
which-typed-array: 1.1.22
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
@@ -4314,7 +4318,7 @@ snapshots:
|
|||||||
|
|
||||||
is-typed-array@1.1.15:
|
is-typed-array@1.1.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
which-typed-array: 1.1.21
|
which-typed-array: 1.1.22
|
||||||
|
|
||||||
is-unicode-supported@0.1.0: {}
|
is-unicode-supported@0.1.0: {}
|
||||||
|
|
||||||
@@ -4345,7 +4349,7 @@ snapshots:
|
|||||||
|
|
||||||
joycon@3.1.1: {}
|
joycon@3.1.1: {}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
@@ -4385,7 +4389,7 @@ snapshots:
|
|||||||
listr2: 9.0.5
|
listr2: 9.0.5
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
string-argv: 0.3.2
|
string-argv: 0.3.2
|
||||||
tinyexec: 1.2.3
|
tinyexec: 1.2.4
|
||||||
yaml: 2.9.0
|
yaml: 2.9.0
|
||||||
|
|
||||||
listr2@9.0.5:
|
listr2@9.0.5:
|
||||||
@@ -4428,6 +4432,8 @@ snapshots:
|
|||||||
|
|
||||||
media-typer@1.1.0: {}
|
media-typer@1.1.0: {}
|
||||||
|
|
||||||
|
media-typer@2.0.0: {}
|
||||||
|
|
||||||
merge-descriptors@1.0.3: {}
|
merge-descriptors@1.0.3: {}
|
||||||
|
|
||||||
merge-descriptors@2.0.0: {}
|
merge-descriptors@2.0.0: {}
|
||||||
@@ -4492,7 +4498,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.1.1
|
js-yaml: 4.2.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
|
||||||
@@ -4520,14 +4526,14 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
music-metadata@11.12.3:
|
music-metadata@11.13.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@borewit/text-codec': 0.2.2
|
'@borewit/text-codec': 0.2.2
|
||||||
'@tokenizer/token': 0.3.0
|
'@tokenizer/token': 0.3.0
|
||||||
content-type: 1.0.5
|
content-type: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
file-type: 21.3.4
|
file-type: 21.3.4
|
||||||
media-typer: 1.1.0
|
media-typer: 2.0.0
|
||||||
strtok3: 10.3.5
|
strtok3: 10.3.5
|
||||||
token-types: 6.1.2
|
token-types: 6.1.2
|
||||||
uint8array-extras: 1.5.0
|
uint8array-extras: 1.5.0
|
||||||
@@ -4549,7 +4555,7 @@ snapshots:
|
|||||||
|
|
||||||
node-abi@3.92.0:
|
node-abi@3.92.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.8.1
|
semver: 7.8.3
|
||||||
|
|
||||||
node-fetch@2.7.0:
|
node-fetch@2.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4569,7 +4575,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.1
|
semver: 7.8.3
|
||||||
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
|
||||||
@@ -4744,7 +4750,7 @@ 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.1
|
semver: 7.8.3
|
||||||
tar-fs: 2.1.4
|
tar-fs: 2.1.4
|
||||||
yargs: 16.2.0
|
yargs: 16.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5038,7 +5044,7 @@ snapshots:
|
|||||||
|
|
||||||
secure-json-parse@2.7.0: {}
|
secure-json-parse@2.7.0: {}
|
||||||
|
|
||||||
semver@7.8.1: {}
|
semver@7.8.3: {}
|
||||||
|
|
||||||
send@0.19.2:
|
send@0.19.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5168,7 +5174,7 @@ snapshots:
|
|||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
simple-update-notifier@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.8.1
|
semver: 7.8.3
|
||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|
||||||
@@ -5261,7 +5267,7 @@ snapshots:
|
|||||||
get-east-asian-width: 1.6.0
|
get-east-asian-width: 1.6.0
|
||||||
strip-ansi: 7.2.0
|
strip-ansi: 7.2.0
|
||||||
|
|
||||||
string.prototype.trim@1.2.10:
|
string.prototype.trim@1.2.11:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.9
|
call-bind: 1.0.9
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -5270,8 +5276,9 @@ snapshots:
|
|||||||
es-abstract: 1.24.2
|
es-abstract: 1.24.2
|
||||||
es-object-atoms: 1.1.2
|
es-object-atoms: 1.1.2
|
||||||
has-property-descriptors: 1.0.2
|
has-property-descriptors: 1.0.2
|
||||||
|
safe-regex-test: 1.1.0
|
||||||
|
|
||||||
string.prototype.trimend@1.0.9:
|
string.prototype.trimend@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.9
|
call-bind: 1.0.9
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@@ -5347,9 +5354,9 @@ snapshots:
|
|||||||
inherits: 2.0.4
|
inherits: 2.0.4
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
tinyexec@1.2.3: {}
|
tinyexec@1.2.4: {}
|
||||||
|
|
||||||
tinyglobby@0.2.16:
|
tinyglobby@0.2.17:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.4)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
@@ -5376,7 +5383,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
typedarray.prototype.slice: 1.0.5
|
typedarray.prototype.slice: 1.0.5
|
||||||
which-typed-array: 1.1.21
|
which-typed-array: 1.1.22
|
||||||
|
|
||||||
ts-api-utils@2.5.0(typescript@5.9.3):
|
ts-api-utils@2.5.0(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5470,7 +5477,7 @@ snapshots:
|
|||||||
|
|
||||||
undefsafe@2.0.5: {}
|
undefsafe@2.0.5: {}
|
||||||
|
|
||||||
undici-types@7.18.2: {}
|
undici-types@7.24.6: {}
|
||||||
|
|
||||||
universal-deep-strict-equal@1.2.2:
|
universal-deep-strict-equal@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5521,7 +5528,7 @@ snapshots:
|
|||||||
isarray: 2.0.5
|
isarray: 2.0.5
|
||||||
which-boxed-primitive: 1.1.1
|
which-boxed-primitive: 1.1.1
|
||||||
which-collection: 1.0.2
|
which-collection: 1.0.2
|
||||||
which-typed-array: 1.1.21
|
which-typed-array: 1.1.22
|
||||||
|
|
||||||
which-collection@1.0.2:
|
which-collection@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5532,7 +5539,7 @@ snapshots:
|
|||||||
|
|
||||||
which-module@2.0.1: {}
|
which-module@2.0.1: {}
|
||||||
|
|
||||||
which-typed-array@1.1.21:
|
which-typed-array@1.1.22:
|
||||||
dependencies:
|
dependencies:
|
||||||
available-typed-arrays: 1.0.7
|
available-typed-arrays: 1.0.7
|
||||||
call-bind: 1.0.9
|
call-bind: 1.0.9
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
# 网易云音乐 API Enhanced
|
# NeteaseCloudMusicAPI Enhanced
|
||||||
|
|
||||||
> 🔍 网易云音乐API Node.js服务的复兴项目
|
> 🎉 全网收集最全的网易云音乐api接口 基于[NeteaseCloudMusicAPI](https://github.com/binaryify/NeteaseCloudMusicApi)的复刻版本
|
||||||
|
|
||||||
- 基于原版网易云API新增更多有趣的功能
|
- ⚡ 四种加密模式 · 后端代理
|
||||||
- 具备登录接口,多达200多个接口
|
- 🪛 具备多达200多个接口
|
||||||
- 更完善的文档
|
- 📄 更完善的文档
|
||||||
|
|
||||||
|
|
||||||
[Github](https://github.com/neteasecloudmusicapienhanced/api-enhanced)
|
[Github](https://github.com/neteasecloudmusicapienhanced/api-enhanced)
|
||||||
[Get Started](#neteasecloudmusicapienhanced)
|
[前往本家](https://github.com/binaryify/NeteaseCloudMusicApi)
|
||||||
|
[快速开始](#neteasecloudmusicapienhanced)
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
|
### 提交歌曲播放状态
|
||||||
|
|
||||||
|
说明 : 调用此接口可提交歌曲播放状态,支持会话追踪和播放模式记录,未传入 `sessionId` 时后端会自动生成
|
||||||
|
|
||||||
|
**必选参数 :** `id`: 歌曲 id
|
||||||
|
|
||||||
|
**可选参数 :** `sessionId`: 播放会话 ID(12 位大写字母和数字),不传则自动生成, `progress`: 播放进度(秒),默认 0, `playMode`: 播放模式,默认 `list_loop`, `type`: 资源类型,默认 `song`
|
||||||
|
|
||||||
|
**接口地址 :** `/relay/play/state/submit`
|
||||||
|
|
||||||
|
**调用例子 :** `/relay/play/state/submit?id=518066366&progress=30`
|
||||||
|
|
||||||
### 热门歌手
|
### 热门歌手
|
||||||
|
|
||||||
说明 : 调用此接口 , 可获取热门歌手数据
|
说明 : 调用此接口 , 可获取热门歌手数据
|
||||||
@@ -2873,6 +2893,7 @@ type : 地区
|
|||||||
- 适合 Vercel、Netlify 等有请求体限制的平台
|
- 适合 Vercel、Netlify 等有请求体限制的平台
|
||||||
- 需要前端配合实现
|
- 需要前端配合实现
|
||||||
|
|
||||||
|
|
||||||
#### 客户端直传相关接口
|
#### 客户端直传相关接口
|
||||||
|
|
||||||
**获取上传凭证**
|
**获取上传凭证**
|
||||||
@@ -2919,6 +2940,7 @@ type : 地区
|
|||||||
- `artist`: 艺术家
|
- `artist`: 艺术家
|
||||||
- `album`: 专辑名
|
- `album`: 专辑名
|
||||||
|
|
||||||
|
|
||||||
#### 客户端直传流程
|
#### 客户端直传流程
|
||||||
|
|
||||||
1. 客户端计算文件 MD5
|
1. 客户端计算文件 MD5
|
||||||
@@ -2926,6 +2948,7 @@ type : 地区
|
|||||||
3. 如果 `needUpload` 为 true,直接 PUT 文件到 `uploadUrl`
|
3. 如果 `needUpload` 为 true,直接 PUT 文件到 `uploadUrl`
|
||||||
4. 调用 `/cloud/upload/complete` 完成导入
|
4. 调用 `/cloud/upload/complete` 完成导入
|
||||||
|
|
||||||
|
|
||||||
### 云盘歌曲信息匹配纠正
|
### 云盘歌曲信息匹配纠正
|
||||||
|
|
||||||
说明 : 登录后调用此接口,可对云盘歌曲信息匹配纠正,如需取消匹配,asid 需要传 0
|
说明 : 登录后调用此接口,可对云盘歌曲信息匹配纠正,如需取消匹配,asid 需要传 0
|
||||||
@@ -4198,6 +4221,7 @@ ONLINE 已发布
|
|||||||
- `voiceFeeType: 0`:返回免费的声音
|
- `voiceFeeType: 0`:返回免费的声音
|
||||||
- `voiceFeeType: 1`:返回收费的声音
|
- `voiceFeeType: 1`:返回收费的声音
|
||||||
|
|
||||||
|
|
||||||
### 播客声音详情
|
### 播客声音详情
|
||||||
|
|
||||||
说明: 获取播客里的声音详情
|
说明: 获取播客里的声音详情
|
||||||
@@ -5378,6 +5402,16 @@ let data = encodeURIComponent(
|
|||||||
|
|
||||||
**调用例子 :** `/vip/sign/history`
|
**调用例子 :** `/vip/sign/history`
|
||||||
|
|
||||||
|
### 直接获取云盘歌曲下载链接
|
||||||
|
|
||||||
|
说明 : 调用此接口, 传入云盘歌曲 id, 可直接获取云盘歌曲下载链接
|
||||||
|
|
||||||
|
**必选参数 :** `id`: 云盘歌曲 id
|
||||||
|
|
||||||
|
**接口地址 :** `/song/cloud/download`
|
||||||
|
|
||||||
|
**调用例子 :** `/song/cloud/download?id=123456789`
|
||||||
|
|
||||||
## 离线访问此文档
|
## 离线访问此文档
|
||||||
|
|
||||||
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
|
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"APP_CONF": {
|
"APP_CONF": {
|
||||||
"apiDomain": "https://interface.music.163.com",
|
"apiDomain": "https://interface.music.163.com",
|
||||||
|
"xeapiDomain": "https://interface3.music.163.com",
|
||||||
"domain": "https://music.163.com",
|
"domain": "https://music.163.com",
|
||||||
"encrypt": true,
|
"encrypt": true,
|
||||||
"encryptResponse": false,
|
"encryptResponse": false,
|
||||||
|
|||||||
+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)
|
||||||
|
|||||||
+12
-2
@@ -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
|
||||||
@@ -110,7 +116,7 @@ const userAgentMap = {
|
|||||||
// 预先定义常量
|
// 预先定义常量
|
||||||
const DOMAIN = APP_CONF.domain
|
const DOMAIN = APP_CONF.domain
|
||||||
const API_DOMAIN = APP_CONF.apiDomain
|
const API_DOMAIN = APP_CONF.apiDomain
|
||||||
const XEAPI_DOMAIN = 'https://interface3.music.163.com'
|
const XEAPI_DOMAIN = APP_CONF.xeapiDomain
|
||||||
const ENCRYPT_RESPONSE = APP_CONF.encryptResponse
|
const ENCRYPT_RESPONSE = APP_CONF.encryptResponse
|
||||||
const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
|
const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
|
||||||
|
|
||||||
@@ -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压缩返回值
|
||||||
|
|||||||
+8
-52
@@ -1,59 +1,15 @@
|
|||||||
const { default: axios } = require('axios')
|
const registerXeapiKey = require('../module/register_xeapikey')
|
||||||
const encrypt = require('./crypto')
|
|
||||||
const { APP_CONF } = require('./config.json')
|
|
||||||
|
|
||||||
const generateNonce = () => {
|
|
||||||
let nonce = ''
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
nonce += Math.floor(Math.random() * 10).toString()
|
|
||||||
}
|
|
||||||
return nonce
|
|
||||||
}
|
|
||||||
|
|
||||||
const getXeapiPublicKey = async (currentPublicKey = {}, deviceId = '') => {
|
const getXeapiPublicKey = async (currentPublicKey = {}, deviceId = '') => {
|
||||||
const nonce = generateNonce()
|
const result = await registerXeapiKey(
|
||||||
const timestamp = String(Date.now())
|
{
|
||||||
const data = {
|
deviceId,
|
||||||
appVersion: '9.1.65',
|
currentKeyVersion: currentPublicKey.version || '',
|
||||||
currentKeyVersion: currentPublicKey.version || '',
|
|
||||||
deviceId,
|
|
||||||
nonce,
|
|
||||||
os: 'android',
|
|
||||||
requestType: 'active',
|
|
||||||
signature: encrypt.xeapiSign(timestamp, nonce),
|
|
||||||
t1: '',
|
|
||||||
t2: '',
|
|
||||||
timestamp,
|
|
||||||
uid: '',
|
|
||||||
}
|
|
||||||
const res = await axios({
|
|
||||||
method: 'POST',
|
|
||||||
url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get',
|
|
||||||
headers: {
|
|
||||||
'User-Agent':
|
|
||||||
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
|
||||||
Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '',
|
|
||||||
},
|
},
|
||||||
data: new URLSearchParams(data).toString(),
|
null,
|
||||||
proxy: false,
|
)
|
||||||
})
|
|
||||||
if (
|
|
||||||
!res.data ||
|
|
||||||
res.data.code !== 200 ||
|
|
||||||
!res.data.data ||
|
|
||||||
!res.data.data.encryptedData
|
|
||||||
) {
|
|
||||||
throw new Error('xeapi public key request failed')
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!res.data.data.signature ||
|
|
||||||
encrypt.xeapiSign(res.data.data.timestamp, nonce) !==
|
|
||||||
res.data.data.signature
|
|
||||||
) {
|
|
||||||
throw new Error('xeapi public key response signature mismatch')
|
|
||||||
}
|
|
||||||
|
|
||||||
const publicKey = encrypt.xeapiDecryptPublicKey(res.data.data.encryptedData)
|
const publicKey = result.body
|
||||||
if (!publicKey.sk && currentPublicKey.sk) {
|
if (!publicKey.sk && currentPublicKey.sk) {
|
||||||
publicKey.sk = currentPublicKey.sk
|
publicKey.sk = currentPublicKey.sk
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user