@neteasecloudmusicapienhanced/api 4.30.3 → 4.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.MD CHANGED
@@ -130,7 +130,7 @@ $ sudo docker run -d -p 3000:3000 ncm-api
130
130
 
131
131
  | 变量名 | 默认值 | 说明 |
132
132
  |----------------------------|--------------------------------------|----------------------------------------------------|
133
- | **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。若需要限制,请指定具体域名(例如 `https://example.com`)。 |
133
+ | **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。可填写单个源,或使用逗号分隔多个源(例如 `https://a.com,https://b.com`)。 |
134
134
  | **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 |
135
135
  | **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 |
136
136
  | **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 |
@@ -0,0 +1,9 @@
1
+ // DIFM电台 - 分类
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ sources: query.sources || '[0]',
7
+ }
8
+ return request(`/api/dj/difm/all/style/channel/v2`, data, createOption(query))
9
+ }
@@ -0,0 +1,9 @@
1
+ // DIFM电台 - 收藏频道
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ id: query.id,
7
+ }
8
+ return request(`/api/dj/difm/channel/subscribe`, data, createOption(query))
9
+ }
@@ -0,0 +1,9 @@
1
+ // DIFM电台 - 取消收藏频道
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ id: query.id,
7
+ }
8
+ return request(`/api/dj/difm/channel/unsubscribe`, data, createOption(query))
9
+ }
@@ -0,0 +1,11 @@
1
+ // DIFM电台 - 播放列表
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ limit: query.limit || 5,
7
+ source: query.source || 0,
8
+ channelId: query.channelId,
9
+ }
10
+ return request(`/api/dj/difm/playing/tracks/list`, data, createOption(query))
11
+ }
@@ -0,0 +1,13 @@
1
+ // DIFM电台 - 收藏列表
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ sources: query.sources || '[0]',
7
+ }
8
+ return request(
9
+ `/api/dj/difm/subscribe/channels/get/v2`,
10
+ data,
11
+ createOption(query),
12
+ )
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neteasecloudmusicapienhanced/api",
3
- "version": "4.30.3",
3
+ "version": "4.31.0",
4
4
  "description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
5
5
  "scripts": {
6
6
  "dev": "nodemon app.js",
@@ -65,14 +65,15 @@
65
65
  "data"
66
66
  ],
67
67
  "dependencies": {
68
- "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.2.3",
69
- "axios": "^1.13.5",
68
+ "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.2.4",
69
+ "axios": "^1.13.6",
70
70
  "crypto-js": "^4.2.0",
71
71
  "dotenv": "^17.3.1",
72
72
  "express": "^5.2.1",
73
73
  "express-fileupload": "^1.5.2",
74
- "music-metadata": "^11.12.1",
75
- "node-forge": "^1.3.3",
74
+ "gzip": "^0.1.0",
75
+ "music-metadata": "^11.12.3",
76
+ "node-forge": "^1.4.0",
76
77
  "pac-proxy-agent": "^7.2.0",
77
78
  "qrcode": "^1.5.4",
78
79
  "safe-decode-uri-component": "^1.2.1",
@@ -81,22 +82,22 @@
81
82
  "yargs": "^18.0.0"
82
83
  },
83
84
  "devDependencies": {
84
- "@eslint/eslintrc": "^3.3.3",
85
- "@eslint/js": "^9.39.3",
85
+ "@eslint/eslintrc": "^3.3.5",
86
+ "@eslint/js": "^9.39.4",
86
87
  "@types/express": "^5.0.6",
87
88
  "@types/express-fileupload": "^1.5.1",
88
89
  "@types/mocha": "^10.0.10",
89
- "@types/node": "25.0.9",
90
- "@typescript-eslint/eslint-plugin": "^8.56.0",
91
- "@typescript-eslint/parser": "^8.56.0",
92
- "eslint": "^9.39.3",
90
+ "@types/node": "25.5.0",
91
+ "@typescript-eslint/eslint-plugin": "^8.57.2",
92
+ "@typescript-eslint/parser": "^8.57.2",
93
+ "eslint": "^9.39.4",
93
94
  "eslint-config-prettier": "^10.1.8",
94
95
  "eslint-plugin-html": "^8.1.4",
95
96
  "eslint-plugin-prettier": "^5.5.5",
96
- "globals": "^16.5.0",
97
+ "globals": "^17.4.0",
97
98
  "husky": "^9.1.7",
98
99
  "intelli-espower-loader": "^1.1.0",
99
- "lint-staged": "^16.2.7",
100
+ "lint-staged": "^16.4.0",
100
101
  "mocha": "^11.7.5",
101
102
  "nodemon": "^3.1.14",
102
103
  "pkg": "^5.8.1",
@@ -5143,6 +5143,72 @@ let data = encodeURIComponent(
5143
5143
 
5144
5144
  **调用例子 :** `/comment/reply?id=2058263032&commentId=123456789&content=我也觉得这首歌很棒!`
5145
5145
 
5146
+ ### DIFM电台 - 分类
5147
+
5148
+ 说明: 调用此接口, 获取DIFM电台分类
5149
+
5150
+ **必选参数 :**
5151
+
5152
+ `sources`: 来源列表, 0: 最嗨电音 1: 古典电台 2: 爵士电台
5153
+
5154
+ **接口地址:** `/dj/difm/all/style/channel`
5155
+
5156
+ **调用例子:** `/dj/difm/all/style/channel?sources=[0]`
5157
+
5158
+ ### DIFM电台 - 收藏列表
5159
+
5160
+ 说明: 调用此接口, 获取DIFM电台收藏列表
5161
+
5162
+ **必选参数 :**
5163
+
5164
+ `sources`: 来源列表, 0: 最嗨电音 1: 古典电台 2: 爵士电台
5165
+
5166
+ **接口地址:** `/dj/difm/subscribe/channels/get`
5167
+
5168
+ **调用例子:** `/dj/difm/subscribe/channels/get?sources=[0]`
5169
+
5170
+ ### DIFM电台 - 收藏频道
5171
+
5172
+ 说明: 调用此接口, 可收藏DIFM频道
5173
+
5174
+ **必选参数 :**
5175
+
5176
+ `id`: 频道id
5177
+
5178
+ **接口地址:** `/dj/difm/channel/subscribe`
5179
+
5180
+ **调用例子:** `/dj/difm/channel/subscribe?id=1`
5181
+
5182
+ ### DIFM电台 - 取消收藏频道
5183
+
5184
+ 说明: 调用此接口, 可取消收藏DIFM频道
5185
+
5186
+ **必选参数 :**
5187
+
5188
+ `id`: 频道id
5189
+
5190
+ **接口地址:** `/dj/difm/channel/unsubscribe`
5191
+
5192
+ **调用例子:** `/dj/difm/channel/unsubscribe?id=1`
5193
+
5194
+ ### DIFM电台 - 播放列表
5195
+
5196
+ 说明: 调用此接口, 获取DIFM播放列表
5197
+
5198
+ **必选参数 :**
5199
+
5200
+ `source`: 来源, 0: 最嗨电音 1: 古典电台 2: 爵士电台
5201
+
5202
+ `channelId`: 频道id
5203
+
5204
+ **可选参数 :**
5205
+
5206
+ `limit`: 返回数量, 默认为 5
5207
+
5208
+ **接口地址:** `/dj/difm/playing/tracks/list`
5209
+
5210
+ **调用例子:** `/dj/difm/playing/tracks/list?source=0&channelId=1012`
5211
+
5146
5212
  ## 离线访问此文档
5147
5213
 
5148
5214
  此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
package/server.js CHANGED
@@ -127,15 +127,45 @@ async function checkVersion() {
127
127
  })
128
128
  }
129
129
 
130
+ function parseCorsAllowOrigins(corsAllowOrigin) {
131
+ if (!corsAllowOrigin) {
132
+ return null
133
+ }
134
+
135
+ const origins = corsAllowOrigin
136
+ .split(',')
137
+ .map((origin) => origin.trim())
138
+ .filter(Boolean)
139
+
140
+ return origins.length > 0 ? origins : null
141
+ }
142
+
143
+ function getCorsAllowOrigin(allowOrigins, requestOrigin) {
144
+ if (!allowOrigins) {
145
+ return requestOrigin || '*'
146
+ }
147
+
148
+ if (allowOrigins.includes('*')) {
149
+ return '*'
150
+ }
151
+
152
+ if (requestOrigin && allowOrigins.includes(requestOrigin)) {
153
+ return requestOrigin
154
+ }
155
+
156
+ return null
157
+ }
158
+
130
159
  /**
131
160
  * Construct the server of NCM API.
132
161
  *
133
162
  * @param {ModuleDefinition[]} [moduleDefs] Customized module definitions [advanced]
134
163
  * @returns {Promise<import("express").Express>} The server instance.
135
164
  */
136
- async function consturctServer(moduleDefs) {
165
+ async function constructServer(moduleDefs) {
137
166
  const app = express()
138
167
  const { CORS_ALLOW_ORIGIN } = process.env
168
+ const allowOrigins = parseCorsAllowOrigins(CORS_ALLOW_ORIGIN)
139
169
  app.set('trust proxy', true)
140
170
 
141
171
  /**
@@ -147,10 +177,17 @@ async function consturctServer(moduleDefs) {
147
177
  */
148
178
  app.use((req, res, next) => {
149
179
  if (req.path !== '/' && !req.path.includes('.')) {
180
+ const corsAllowOrigin = getCorsAllowOrigin(
181
+ allowOrigins,
182
+ req.headers.origin,
183
+ )
184
+ const shouldSetVaryHeader = corsAllowOrigin && corsAllowOrigin !== '*'
150
185
  res.set({
151
186
  'Access-Control-Allow-Credentials': true,
152
- 'Access-Control-Allow-Origin':
153
- CORS_ALLOW_ORIGIN || req.headers.origin || '*',
187
+ ...(corsAllowOrigin
188
+ ? { 'Access-Control-Allow-Origin': corsAllowOrigin }
189
+ : {}),
190
+ ...(shouldSetVaryHeader ? { Vary: 'Origin' } : {}),
154
191
  'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
155
192
  'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
156
193
  'Content-Type': 'application/json; charset=utf-8',
@@ -359,7 +396,7 @@ async function serveNcmApi(options) {
359
396
  )
360
397
  }
361
398
  })
362
- const constructServerSubmission = consturctServer(options.moduleDefs)
399
+ const constructServerSubmission = constructServer(options.moduleDefs)
363
400
 
364
401
  const [_, app] = await Promise.all([
365
402
  checkVersionSubmission,
package/util/request.js CHANGED
@@ -240,6 +240,7 @@ const createRequest = (uri, data, options) => {
240
240
  headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
241
241
 
242
242
  if (crypto === 'eapi') {
243
+ // headers['x-aeapi'] = true // 服务器会使用gzip压缩返回值
243
244
  data.header = header
244
245
  data.e_r = toBoolean(
245
246
  options.e_r !== undefined
@@ -323,6 +324,7 @@ const createRequest = (uri, data, options) => {
323
324
  if (crypto === 'eapi' && data.e_r) {
324
325
  answer.body = encrypt.eapiResDecrypt(
325
326
  body.toString('hex').toUpperCase(),
327
+ headers['x-aeapi'],
326
328
  )
327
329
  } else {
328
330
  answer.body =