Compare commits

..

11 Commits

Author SHA1 Message Date
5b5f380cd5
Merge pull request #136 from laoshuikaixue/feature/cors-multi-domain 2026-03-20 23:39:06 +08:00
ff7c5518d1
Merge pull request #138 from NeteaseCloudMusicApiEnhanced/up-4.31.0 2026-03-20 20:14:14 +08:00
bbb2b9d4a0
docs: 更新文档 2026-03-20 20:08:47 +08:00
3188238678
feat: 添加DIFM相关接口 2026-03-20 20:05:40 +08:00
8e60324452
chore: update deps 2026-03-20 20:02:04 +08:00
c9909cddd3
Merge remote-tracking branch 'origin/dependabot/github_actions/actions/upload-artifact-7' into up-4.31.0 2026-03-20 19:58:27 +08:00
dbdbba3f98
Squashed commit of the following:
commit ed0208eb8958e27f16e6e7f83b8158ba9d90366b
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Sun Mar 1 12:48:11 2026 +0000

    chore(deps): bump actions/download-artifact from 7 to 8

    Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
    - [Release notes](https://github.com/actions/download-artifact/releases)
    - [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

    ---
    updated-dependencies:
    - dependency-name: actions/download-artifact
      dependency-version: '8'
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
2026-03-20 19:58:19 +08:00
LaoShui
27aa9a01cb fix(cors): 修复CORS源验证逻辑
- 移除无效的请求源回退逻辑
- 简化Vary头设置条件判断
- 优化CORS允许源验证流程
2026-03-14 21:14:18 +08:00
LaoShui
30e522018f feat(server): 支持多域名CORS配置
- 实现了逗号分隔的多域名CORS配置解析功能
- 新增parseCorsAllowOrigins函数处理域名列表解析
- 新增getCorsAllowOrigin函数实现动态域名匹配逻辑
- 更新CORS响应头设置逻辑支持多域名验证
- 添加Vary: Origin响应头优化缓存策略
- 修改README文档说明多域名配置方式
2026-03-14 21:03:58 +08:00
LaoShui
7b2507e43d refactor(server): 修复函数名拼写错误
- 将 consturctServer 函数名更正为 constructServer
- 更新调用处的函数名称以保持一致性
2026-03-14 19:24:52 +08:00
dependabot[bot]
f61554714c
chore(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-01 12:48:05 +00:00
12 changed files with 422 additions and 249 deletions

View File

@ -46,7 +46,7 @@ jobs:
PKG_TARGET: ${{ matrix.target }} PKG_TARGET: ${{ matrix.target }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: app-${{ matrix.platform }} name: app-${{ matrix.platform }}
path: ${{ matrix.output }} path: ${{ matrix.output }}
@ -62,7 +62,7 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Download all artifacts - name: Download all artifacts
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
path: precompiled path: precompiled

View File

@ -130,7 +130,7 @@ $ sudo docker run -d -p 3000:3000 ncm-api
| 变量名 | 默认值 | 说明 | | 变量名 | 默认值 | 说明 |
|----------------------------|--------------------------------------|----------------------------------------------------| |----------------------------|--------------------------------------|----------------------------------------------------|
| **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。若需要限制,请指定具体域名(例如 `https://example.com`)。 | | **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。可填写单个源,或使用逗号分隔多个源(例如 `https://a.com,https://b.com`)。 |
| **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 | | **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 |
| **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 | | **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 |
| **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 | | **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 |

View File

@ -0,0 +1,9 @@
// DIFM电台 - 分类
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
sources: query.sources || '[0]',
}
return request(`/api/dj/difm/all/style/channel/v2`, data, createOption(query))
}

View File

@ -0,0 +1,9 @@
// DIFM电台 - 收藏频道
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(`/api/dj/difm/channel/subscribe`, data, createOption(query))
}

View File

@ -0,0 +1,9 @@
// DIFM电台 - 取消收藏频道
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(`/api/dj/difm/channel/unsubscribe`, data, createOption(query))
}

View File

@ -0,0 +1,11 @@
// DIFM电台 - 播放列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 5,
source: query.source || 0,
channelId: query.channelId,
}
return request(`/api/dj/difm/playing/tracks/list`, data, createOption(query))
}

View File

@ -0,0 +1,13 @@
// DIFM电台 - 收藏列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
sources: query.sources || '[0]',
}
return request(
`/api/dj/difm/subscribe/channels/get/v2`,
data,
createOption(query),
)
}

View File

@ -65,13 +65,14 @@
"data" "data"
], ],
"dependencies": { "dependencies": {
"@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.2.3", "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.2.4",
"axios": "^1.13.5", "axios": "^1.13.6",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"express": "^5.2.1", "express": "^5.2.1",
"express-fileupload": "^1.5.2", "express-fileupload": "^1.5.2",
"music-metadata": "^11.12.1", "gzip": "^0.1.0",
"music-metadata": "^11.12.3",
"node-forge": "^1.3.3", "node-forge": "^1.3.3",
"pac-proxy-agent": "^7.2.0", "pac-proxy-agent": "^7.2.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
@ -81,22 +82,22 @@
"yargs": "^18.0.0" "yargs": "^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.3", "@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^9.39.3", "@eslint/js": "^9.39.4",
"@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.0.9", "@types/node": "25.5.0",
"@typescript-eslint/eslint-plugin": "^8.56.0", "@typescript-eslint/eslint-plugin": "^8.57.1",
"@typescript-eslint/parser": "^8.56.0", "@typescript-eslint/parser": "^8.57.1",
"eslint": "^9.39.3", "eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-html": "^8.1.4", "eslint-plugin-html": "^8.1.4",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"globals": "^16.5.0", "globals": "^17.4.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"intelli-espower-loader": "^1.1.0", "intelli-espower-loader": "^1.1.0",
"lint-staged": "^16.2.7", "lint-staged": "^16.4.0",
"mocha": "^11.7.5", "mocha": "^11.7.5",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"pkg": "^5.8.1", "pkg": "^5.8.1",

478
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -5143,6 +5143,72 @@ let data = encodeURIComponent(
**调用例子 :** `/comment/reply?id=2058263032&commentId=123456789&content=我也觉得这首歌很棒!` **调用例子 :** `/comment/reply?id=2058263032&commentId=123456789&content=我也觉得这首歌很棒!`
### DIFM电台 - 分类
说明: 调用此接口, 获取DIFM电台分类
**必选参数 :**
`sources`: 来源列表, 0: 最嗨电音 1: 古典电台 2: 爵士电台
**接口地址:** `/dj/difm/all/style/channel`
**调用例子:** `/dj/difm/all/style/channel?sources=[0]`
### DIFM电台 - 收藏列表
说明: 调用此接口, 获取DIFM电台收藏列表
**必选参数 :**
`sources`: 来源列表, 0: 最嗨电音 1: 古典电台 2: 爵士电台
**接口地址:** `/dj/difm/subscribe/channels/get`
**调用例子:** `/dj/difm/subscribe/channels/get?sources=[0]`
### DIFM电台 - 收藏频道
说明: 调用此接口, 可收藏DIFM频道
**必选参数 :**
`id`: 频道id
**接口地址:** `/dj/difm/channel/subscribe`
**调用例子:** `/dj/difm/channel/subscribe?id=1`
### DIFM电台 - 取消收藏频道
说明: 调用此接口, 可取消收藏DIFM频道
**必选参数 :**
`id`: 频道id
**接口地址:** `/dj/difm/channel/unsubscribe`
**调用例子:** `/dj/difm/channel/unsubscribe?id=1`
### DIFM电台 - 播放列表
说明: 调用此接口, 获取DIFM播放列表
**必选参数 :**
`source`: 来源, 0: 最嗨电音 1: 古典电台 2: 爵士电台
`channelId`: 频道id
**可选参数 :**
`limit`: 返回数量, 默认为 5
**接口地址:** `/dj/difm/playing/tracks/list`
**调用例子:** `/dj/difm/playing/tracks/list?source=0&channelId=1012`
## 离线访问此文档 ## 离线访问此文档
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问 此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问

View File

@ -127,15 +127,45 @@ async function checkVersion() {
}) })
} }
function parseCorsAllowOrigins(corsAllowOrigin) {
if (!corsAllowOrigin) {
return null
}
const origins = corsAllowOrigin
.split(',')
.map((origin) => origin.trim())
.filter(Boolean)
return origins.length > 0 ? origins : null
}
function getCorsAllowOrigin(allowOrigins, requestOrigin) {
if (!allowOrigins) {
return requestOrigin || '*'
}
if (allowOrigins.includes('*')) {
return '*'
}
if (requestOrigin && allowOrigins.includes(requestOrigin)) {
return requestOrigin
}
return null
}
/** /**
* Construct the server of NCM API. * Construct the server of NCM API.
* *
* @param {ModuleDefinition[]} [moduleDefs] Customized module definitions [advanced] * @param {ModuleDefinition[]} [moduleDefs] Customized module definitions [advanced]
* @returns {Promise<import("express").Express>} The server instance. * @returns {Promise<import("express").Express>} The server instance.
*/ */
async function consturctServer(moduleDefs) { async function constructServer(moduleDefs) {
const app = express() const app = express()
const { CORS_ALLOW_ORIGIN } = process.env const { CORS_ALLOW_ORIGIN } = process.env
const allowOrigins = parseCorsAllowOrigins(CORS_ALLOW_ORIGIN)
app.set('trust proxy', true) app.set('trust proxy', true)
/** /**
@ -147,10 +177,17 @@ async function consturctServer(moduleDefs) {
*/ */
app.use((req, res, next) => { app.use((req, res, next) => {
if (req.path !== '/' && !req.path.includes('.')) { if (req.path !== '/' && !req.path.includes('.')) {
const corsAllowOrigin = getCorsAllowOrigin(
allowOrigins,
req.headers.origin,
)
const shouldSetVaryHeader = corsAllowOrigin && corsAllowOrigin !== '*'
res.set({ res.set({
'Access-Control-Allow-Credentials': true, 'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Origin': ...(corsAllowOrigin
CORS_ALLOW_ORIGIN || req.headers.origin || '*', ? { 'Access-Control-Allow-Origin': corsAllowOrigin }
: {}),
...(shouldSetVaryHeader ? { Vary: 'Origin' } : {}),
'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type', 'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS', 'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
'Content-Type': 'application/json; charset=utf-8', 'Content-Type': 'application/json; charset=utf-8',
@ -359,7 +396,7 @@ async function serveNcmApi(options) {
) )
} }
}) })
const constructServerSubmission = consturctServer(options.moduleDefs) const constructServerSubmission = constructServer(options.moduleDefs)
const [_, app] = await Promise.all([ const [_, app] = await Promise.all([
checkVersionSubmission, checkVersionSubmission,

View File

@ -240,6 +240,7 @@ const createRequest = (uri, data, options) => {
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone') headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
if (crypto === 'eapi') { if (crypto === 'eapi') {
// headers['x-aeapi'] = true // 服务器会使用gzip压缩返回值
data.header = header data.header = header
data.e_r = toBoolean( data.e_r = toBoolean(
options.e_r !== undefined options.e_r !== undefined
@ -323,6 +324,7 @@ const createRequest = (uri, data, options) => {
if (crypto === 'eapi' && data.e_r) { if (crypto === 'eapi' && data.e_r) {
answer.body = encrypt.eapiResDecrypt( answer.body = encrypt.eapiResDecrypt(
body.toString('hex').toUpperCase(), body.toString('hex').toUpperCase(),
headers['x-aeapi'],
) )
} else { } else {
answer.body = answer.body =