Compare commits

..
Author SHA1 Message Date
dependabot[bot] 85942d2168 build(packages): bump mocha from 11.8.0 to 12.0.3
Bumps [mocha](https://github.com/mochajs/mocha) from 11.8.0 to 12.0.3.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v11.8.0...v12.0.3)

---
updated-dependencies:
- dependency-name: mocha
  dependency-version: 12.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-24 14:25:02 +00:00
Takanashi Hoshino f01f99eece build(package): update dependences 2026-10-24 22:22:43 +08:00
7N21 0260a8a534 Merge pull request #292 from 70CentsApple/main
fix: 修复一些 cookie 传递问题
2026-10-23 20:14:01 +08:00
70CentsApple f1acc2fda3 fix: prettier 2026-10-23 19:40:11 +08:00
70CentsApple 29c48e2ef9 fix: 修复 cookie 解析器并也尝试从请求头读 cookie 2026-10-19 14:23:57 +08:00
70CentsApple 896a62ec96 fix: 修复空 cookie 对象处理 2026-10-19 14:04:57 +08:00
Takanashi Hoshino 19c7dad6db docs: 修正文档 2026-10-17 19:29:09 +08:00
Takanashi Hoshino 2db6684453 feat: 添加日推风格相关接口 (90de64140f) 2026-10-05 22:41:37 +08:00
Takanashi Hoshino 90de64140f feat: 添加日推风格相关接口 2026-10-05 22:38:41 +08:00
17 changed files with 554 additions and 499 deletions
+3 -1
View File
@@ -22,6 +22,7 @@ jobs:
github.actor != 'yukka-agent[bot]' && github.actor != 'yukka-agent[bot]' &&
github.actor != 'github-actions[bot]' github.actor != 'github-actions[bot]'
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -56,8 +57,9 @@ jobs:
env: env:
OPENCODE_API_KEY: ${{ steps.cfg.outputs.opencode_api_key }} OPENCODE_API_KEY: ${{ steps.cfg.outputs.opencode_api_key }}
GITHUB_TOKEN: ${{ steps.token.outputs.token }} GITHUB_TOKEN: ${{ steps.token.outputs.token }}
OPENCODE_PERMISSION: '{"permission":{"*":"allow","question":"deny"}}'
with: with:
model: opencode/muse-spark-1.3-contributor-free model: opencode/glm-5.3-flash-free
use_github_token: true use_github_token: true
prompt: | prompt: |
${{ steps.cfg.outputs.opencode_prompt }} ${{ steps.cfg.outputs.opencode_prompt }}
+1
View File
@@ -1,5 +1,6 @@
export interface RequestBaseConfig { export interface RequestBaseConfig {
cookie?: string cookie?: string
headers?: Record<string, string | string[]>
realIP?: string // IPv4/IPv6 filled in X-Real-IP realIP?: string // IPv4/IPv6 filled in X-Real-IP
proxy?: string // HTTP proxy proxy?: string // HTTP proxy
randomCNIP?: boolean // Whether to use a random Chinese IP address filled in X-Real-IP randomCNIP?: boolean // Whether to use a random Chinese IP address filled in X-Real-IP
+1 -3
View File
@@ -25,9 +25,7 @@ moduleFiles.forEach((file) => {
obj[fn] = function (data = {}) { obj[fn] = function (data = {}) {
const cookie = const cookie =
typeof data.cookie === 'string' typeof data.cookie === 'string' ? cookieToJson(data.cookie) : data.cookie
? cookieToJson(data.cookie)
: data.cookie || {}
return fileModule( return fileModule(
{ {
+7 -2
View File
@@ -1,10 +1,15 @@
const QRCode = require('qrcode') const QRCode = require('qrcode')
const { generateChainId } = require('../util/index') const { generateChainId, getCookieHeader, hasCookie } = require('../util/index')
module.exports = (query) => { module.exports = (query) => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const platform = query.platform || 'pc' const platform = query.platform || 'pc'
const cookie = query.cookie || '' const headerCookie = getCookieHeader(query.headers)
const cookie = hasCookie(query.cookie)
? query.cookie
: hasCookie(headerCookie)
? headerCookie
: ''
// 构建基础URL // 构建基础URL
let url = `https://music.163.com/login?codekey=${query.key}` let url = `https://music.163.com/login?codekey=${query.key}`
+11
View File
@@ -0,0 +1,11 @@
// 每日风格推荐场景配置列表获取
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {}
return request(
`/api/homepage/daily/song/config/get`,
data,
createOption(query, 'xeapi'),
)
}
+17
View File
@@ -0,0 +1,17 @@
// 每日风格推荐场景配置设置
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
tags: JSON.stringify({
tagIds: String(query.tags).split(',').map(Number),
categoryId: Number(query.category),
}),
}
console.log(data)
return request(
`/api/homepage/daily/song/tag/save`,
data,
createOption(query, 'xeapi'),
)
}
+11
View File
@@ -0,0 +1,11 @@
// 每日推荐风格歌曲
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {}
return request(
`/api/homepage/category/daily/song/list`,
data,
createOption(query, 'xeapi'),
)
}
+13
View File
@@ -0,0 +1,13 @@
// 每日推荐歌曲 - v1
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
ispush: query.ispush || false,
}
return request(
`/api/v3/discovery/recommend/songs`,
data,
createOption(query, 'xeapi'),
)
}
+10 -2
View File
@@ -1,18 +1,26 @@
// 听歌打卡 // 听歌打卡
const createOption = require('../util/option.js') const createOption = require('../util/option.js')
const { getCookieHeader, hasCookie } = require('../util/index')
const { APP_CONF } = require('../util/config.json') const { APP_CONF } = require('../util/config.json')
const DOMAIN = APP_CONF.clDomian const DOMAIN = APP_CONF.clDomian
module.exports = async (query, request) => { module.exports = async (query, request) => {
// 注入 os=osx 的 cookie // 注入 os=osx 的 cookie
let cookie = query.cookie || '' const headerCookie = getCookieHeader(query.headers)
let cookie = hasCookie(query.cookie)
? query.cookie
: hasCookie(headerCookie)
? headerCookie
: process.env.NETEASE_COOKIE || ''
if (typeof cookie === 'object') { if (typeof cookie === 'object') {
cookie = Object.assign({ os: 'osx' }, cookie) cookie = Object.assign({ os: 'osx' }, cookie)
} else if (typeof cookie === 'string') { } else if (typeof cookie === 'string') {
if (cookie.indexOf('os=') > -1) { if (cookie.indexOf('os=') > -1) {
cookie = cookie.replace(/os=[^;]+/g, 'os=osx') cookie = cookie.replace(/os=[^;]+/g, 'os=osx')
} else { } else if (cookie) {
cookie = cookie + '; os=osx' cookie = cookie + '; os=osx'
} else {
cookie = 'os=osx'
} }
} else { } else {
cookie = 'os=osx' cookie = 'os=osx'
+7 -1
View File
@@ -13,6 +13,7 @@ const {
buildMetaJson, buildMetaJson,
doUpload, doUpload,
} = require('../util/ncbl') } = require('../util/ncbl')
const { getCookieHeader, hasCookie } = require('../util/index')
module.exports = async (query, request) => { module.exports = async (query, request) => {
// --- 参数校验 --- // --- 参数校验 ---
@@ -32,7 +33,12 @@ module.exports = async (query, request) => {
const sourceName = query.source || 'list' const sourceName = query.source || 'list'
// --- 解析认证上下文 --- // --- 解析认证上下文 ---
const rawCookie = query.cookie || '' const headerCookie = getCookieHeader(query.headers)
const rawCookie = hasCookie(query.cookie)
? query.cookie
: hasCookie(headerCookie)
? headerCookie
: process.env.NETEASE_COOKIE || ''
const cookieObj = parseCookie(rawCookie) const cookieObj = parseCookie(rawCookie)
cookieObj.os = 'pc' cookieObj.os = 'pc'
const ctx = extractContext(cookieObj) const ctx = extractContext(cookieObj)
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@neteasecloudmusicapienhanced/api", "name": "@neteasecloudmusicapienhanced/api",
"version": "4.41.0", "version": "4.41.1",
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced)", "description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced)",
"scripts": { "scripts": {
"dev": "nodemon app.js", "dev": "nodemon app.js",
@@ -79,7 +79,7 @@
"axios": "^1.20.0", "axios": "^1.20.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.3.0",
"express-fileupload": "^1.5.2", "express-fileupload": "^1.5.2",
"fzstd": "^0.1.1", "fzstd": "^0.1.1",
"gzip": "^0.1.0", "gzip": "^0.1.0",
@@ -100,21 +100,21 @@
"@types/express-fileupload": "^1.5.1", "@types/express-fileupload": "^1.5.1",
"@types/mocha": "^10.0.10", "@types/mocha": "^10.0.10",
"@types/node": "25.9.2", "@types/node": "25.9.2",
"@typescript-eslint/eslint-plugin": "^8.71.0", "@typescript-eslint/eslint-plugin": "^8.72.0",
"@typescript-eslint/parser": "^8.71.0", "@typescript-eslint/parser": "^8.72.0",
"eslint": "^9.39.5", "eslint": "^9.39.5",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-html": "^8.2.1", "eslint-plugin-html": "^8.2.1",
"eslint-plugin-prettier": "^5.5.6", "eslint-plugin-prettier": "^5.5.7",
"globals": "^17.13.0", "globals": "^17.13.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"intelli-espower-loader": "^1.1.0", "intelli-espower-loader": "^1.1.0",
"lint-staged": "^16.4.0", "lint-staged": "^16.4.0",
"mocha": "^11.8.0", "mocha": "^12.0.3",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"pkg": "^5.8.1", "pkg": "^5.8.1",
"power-assert": "^1.6.1", "power-assert": "^1.6.1",
"prettier": "^3.9.9", "prettier": "^3.9.10",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"publishConfig": { "publishConfig": {
+350 -461
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -2391,6 +2391,16 @@ privilege:权限相关信息
**调用例子 :** `/recommend/songs` **调用例子 :** `/recommend/songs`
### 获取每日推荐歌曲 - v1
说明 : 调用此接口 , 可获得每日推荐歌曲 ( 需要登录 )
**可选参数 :** `ispush`: 是否刷新日推 , 默认为 false
**接口地址 :** `/recommend/songs/v1`
**调用例子 :** `/recommend/songs/v1`
### 每日推荐歌曲-不感兴趣 ### 每日推荐歌曲-不感兴趣
说明 : 日推歌曲标记为不感兴趣( 同时会返回一个新推荐歌曲, 需要登录 ) 说明 : 日推歌曲标记为不感兴趣( 同时会返回一个新推荐歌曲, 需要登录 )
@@ -5922,6 +5932,38 @@ let data = encodeURIComponent(
**调用例子 :** `/fans/group/feed/recommend?fansGroupId=1872529203038486609&size=10&cursor=0` **调用例子 :** `/fans/group/feed/recommend?fansGroupId=1872529203038486609&size=10&cursor=0`
### 每日指定风格歌曲推荐
说明 : 调用此接口 , 可获取每日指定风格歌曲推荐
**接口地址 :** `/recommend/category/songs`
**调用例子 :** `/recommend/category/songs`
### 获取每日指定风格歌曲推荐的风格列表
说明 : 调用此接口 , 可获取每日指定风格歌曲推荐的风格列表
**接口地址 :** `/recommend/category/configs`
**调用例子 :** `/recommend/category/configs`
### 设置每日指定风格歌曲推荐的风格
说明 : 调用此接口 , 可设置每日指定风格歌曲推荐的风格
**必选参数 :**
`tags`: 风格标签, 可通过 `/recommend/category/configs` 获取
`category`: 风格标签的父ID, 可通过 `/recommend/category/configs` 获取
!> 只能设置当前CategoryId下的tags, 不能设置其他CategoryId下的tags, 否则会报错
**接口地址 :** `/recommend/category/config`
**调用例子 :** `/recommend/category/config?tags=10004,10015&categoryId=1000`
## 离线访问此文档 ## 离线访问此文档
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问 此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
+17 -10
View File
@@ -6,7 +6,7 @@ const request = require('./util/request')
const packageJSON = require('./package.json') const packageJSON = require('./package.json')
const exec = require('child_process').exec const exec = require('child_process').exec
const cache = require('./util/apicache').middleware const cache = require('./util/apicache').middleware
const { cookieToJson } = require('./util/index') const { cookieToJson, getCookieHeader, hasCookie } = 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')
@@ -225,8 +225,9 @@ async function constructServer(moduleDefs) {
*/ */
app.use((req, _, next) => { app.use((req, _, next) => {
req.cookies = {} req.cookies = {}
//;(req.headers.cookie || '').split(/\s*;\s*/).forEach((pair) => { // Polynomial regular expression // getCookieHeader(req.headers)
;(req.headers.cookie || '').split(/;\s+|(?<!\s)\s+$/g).forEach((pair) => { .split(';')
.forEach((pair) => {
let crack = pair.indexOf('=') let crack = pair.indexOf('=')
if (crack < 1 || crack == pair.length - 1) return if (crack < 1 || crack == pair.length - 1) return
req.cookies[decode(pair.slice(0, crack)).trim()] = decode( req.cookies[decode(pair.slice(0, crack)).trim()] = decode(
@@ -291,13 +292,19 @@ async function constructServer(moduleDefs) {
} }
}) })
let query = Object.assign( const queryCookie = req.query && req.query.cookie
{}, const bodyCookie = req.body && req.body.cookie
{ cookie: req.cookies }, const fileCookie = req.files && req.files.cookie
req.query, const cookie = hasCookie(fileCookie)
req.body, ? fileCookie
req.files, : hasCookie(bodyCookie)
) ? bodyCookie
: hasCookie(queryCookie)
? queryCookie
: req.cookies
const query = Object.assign({}, req.query, req.body, req.files, {
cookie,
})
try { try {
let usedCrypto = '' let usedCrypto = ''
+33 -4
View File
@@ -94,15 +94,42 @@ module.exports = {
// 优化:使用for循环替代forEach,性能更好 // 优化:使用for循环替代forEach,性能更好
for (let i = 0, len = cookieArr.length; i < len; i++) { for (let i = 0, len = cookieArr.length; i < len; i++) {
let item = cookieArr[i] let item = cookieArr[i]
let arr = item.split('=') let separator = item.indexOf('=')
// 优化:使用严格等于 // 只按第一个等号拆分,保留 Cookie 值中的等号
if (arr.length === 2) { if (separator > 0) {
obj[arr[0].trim()] = arr[1].trim() obj[item.slice(0, separator).trim()] = item.slice(separator + 1).trim()
} }
} }
return obj return obj
}, },
hasCookie(cookie) {
if (typeof cookie === 'string') return cookie.length > 0
return (
cookie !== null &&
typeof cookie === 'object' &&
Object.keys(cookie).length > 0
)
},
getCookieHeader(headers) {
if (!headers || typeof headers !== 'object') return ''
if (typeof headers.get === 'function') {
const cookie = headers.get('cookie')
if (typeof cookie === 'string') return cookie
}
const headerName = Object.keys(headers).find(
(name) => name.toLowerCase() === 'cookie',
)
if (!headerName) return ''
const cookie = headers[headerName]
if (Array.isArray(cookie)) return cookie.join('; ')
return typeof cookie === 'string' ? cookie : ''
},
cookieObjToString(cookie) { cookieObjToString(cookie) {
// 优化:使用预绑定的keys函数和for循环 // 优化:使用预绑定的keys函数和for循环
const cookieKeys = keys(cookie) const cookieKeys = keys(cookie)
@@ -226,6 +253,8 @@ const cookieToJsonOptimized = (function() {
function getCookieValue(cookieStr, name) { function getCookieValue(cookieStr, name) {
if (!cookieStr) return '' if (!cookieStr) return ''
if (typeof cookieStr === 'object') return cookieStr[name] || ''
const cookies = '; ' + cookieStr const cookies = '; ' + cookieStr
const parts = cookies.split('; ' + name + '=') const parts = cookies.split('; ' + name + '=')
if (parts.length === 2) return parts.pop().split(';').shift() if (parts.length === 2) return parts.pop().split(';').shift()
+9 -1
View File
@@ -1,7 +1,15 @@
const { getCookieHeader, hasCookie } = require('./index')
const createOption = (query, crypto = '', checkToken = false) => { const createOption = (query, crypto = '', checkToken = false) => {
const headerCookie = getCookieHeader(query.headers)
return { return {
crypto: query.crypto || crypto || '', crypto: query.crypto || crypto || '',
cookie: query.cookie || process.env.NETEASE_COOKIE, cookie: hasCookie(query.cookie)
? query.cookie
: hasCookie(headerCookie)
? headerCookie
: process.env.NETEASE_COOKIE,
ua: query.ua || '', ua: query.ua || '',
proxy: query.proxy, proxy: query.proxy,
realIP: query.realIP, realIP: query.realIP,
+9 -1
View File
@@ -13,6 +13,8 @@ const tmpPath = require('os').tmpdir()
const { const {
cookieToJson, cookieToJson,
cookieObjToString, cookieObjToString,
getCookieHeader,
hasCookie,
toBoolean, toBoolean,
generateRandomChineseIP, generateRandomChineseIP,
} = require('./index') } = require('./index')
@@ -243,13 +245,19 @@ const createRequest = async (uri, data, options) => {
headers['X-Forwarded-For'] = ip headers['X-Forwarded-For'] = ip
} }
let cookie = options.cookie || {} let cookie = hasCookie(options.cookie)
? options.cookie
: getCookieHeader(options.headers)
if (!hasCookie(cookie)) cookie = {}
if (typeof cookie === 'string') { if (typeof cookie === 'string') {
cookie = cookieToJson(cookie) cookie = cookieToJson(cookie)
} }
if (typeof cookie === 'object') { if (typeof cookie === 'object') {
cookie = processCookieObject(cookie, crypto) cookie = processCookieObject(cookie, crypto)
Object.keys(headers).forEach((name) => {
if (name.toLowerCase() === 'cookie') delete headers[name]
})
headers['Cookie'] = cookieObjToString(cookie) headers['Cookie'] = cookieObjToString(cookie)
} }
let url = '' let url = ''