diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index da5ef7c..b9c3c06 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -59,7 +59,7 @@ jobs: GITHUB_TOKEN: ${{ steps.token.outputs.token }} OPENCODE_PERMISSION: '{"permission":{"*":"allow","question":"deny"}}' with: - model: opencode/muse-spark-1.3-contributor-free + model: opencode/glm-5.3-flash-free use_github_token: true prompt: | ${{ steps.cfg.outputs.opencode_prompt }} diff --git a/interface.d.ts b/interface.d.ts index 28c8226..bb67d7b 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -1,5 +1,6 @@ export interface RequestBaseConfig { cookie?: string + headers?: Record realIP?: string // IPv4/IPv6 filled in X-Real-IP proxy?: string // HTTP proxy randomCNIP?: boolean // Whether to use a random Chinese IP address filled in X-Real-IP diff --git a/main.js b/main.js index 6c7634b..dacceb6 100644 --- a/main.js +++ b/main.js @@ -25,9 +25,7 @@ moduleFiles.forEach((file) => { obj[fn] = function (data = {}) { const cookie = - typeof data.cookie === 'string' - ? cookieToJson(data.cookie) - : data.cookie || {} + typeof data.cookie === 'string' ? cookieToJson(data.cookie) : data.cookie return fileModule( { diff --git a/module/banner_v3.js b/module/banner_v3.js new file mode 100644 index 0000000..77583d7 --- /dev/null +++ b/module/banner_v3.js @@ -0,0 +1,9 @@ +// 首页轮播(v3) + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + clientType: query.clientType || 'android', + } + return request(`/api/banner/get/v3`, data, createOption(query, 'weapi')) +} diff --git a/module/comment_default_text.js b/module/comment_default_text.js new file mode 100644 index 0000000..37107fc --- /dev/null +++ b/module/comment_default_text.js @@ -0,0 +1,13 @@ +// 评论默认文案 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + threadId: query.threadId, + } + return request( + `/api/comment/default/text`, + data, + createOption(query, 'weapi'), + ) +} diff --git a/module/comment_hotwall_square_info.js b/module/comment_hotwall_square_info.js new file mode 100644 index 0000000..8c1d8f9 --- /dev/null +++ b/module/comment_hotwall_square_info.js @@ -0,0 +1,11 @@ +// 热评墙广场信息 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = {} + return request( + `/api/comment/hotwall/today/squareinfo/get`, + data, + createOption(query, 'weapi'), + ) +} diff --git a/module/event_search.js b/module/event_search.js new file mode 100644 index 0000000..b196b04 --- /dev/null +++ b/module/event_search.js @@ -0,0 +1,15 @@ +// 动态搜索 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + keyword: query.keyword || query.keywords || '', + limit: query.limit || 30, + offset: query.offset || 0, + } + return request( + `/api/event/search/list/get/v1`, + data, + createOption(query, 'weapi'), + ) +} diff --git a/module/login_qr_create.js b/module/login_qr_create.js index d94cbec..709d5ee 100644 --- a/module/login_qr_create.js +++ b/module/login_qr_create.js @@ -1,10 +1,15 @@ const QRCode = require('qrcode') -const { generateChainId } = require('../util/index') +const { generateChainId, getCookieHeader, hasCookie } = require('../util/index') module.exports = (query) => { return new Promise(async (resolve) => { 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 let url = `https://music.163.com/login?codekey=${query.key}` diff --git a/module/scrobble.js b/module/scrobble.js index 7adf91e..2f19ffe 100644 --- a/module/scrobble.js +++ b/module/scrobble.js @@ -1,18 +1,26 @@ // 听歌打卡 const createOption = require('../util/option.js') +const { getCookieHeader, hasCookie } = require('../util/index') const { APP_CONF } = require('../util/config.json') const DOMAIN = APP_CONF.clDomian module.exports = async (query, request) => { // 注入 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') { cookie = Object.assign({ os: 'osx' }, cookie) } else if (typeof cookie === 'string') { if (cookie.indexOf('os=') > -1) { cookie = cookie.replace(/os=[^;]+/g, 'os=osx') - } else { + } else if (cookie) { cookie = cookie + '; os=osx' + } else { + cookie = 'os=osx' } } else { cookie = 'os=osx' diff --git a/module/scrobble_v1.js b/module/scrobble_v1.js index 84c3658..5c2f60a 100644 --- a/module/scrobble_v1.js +++ b/module/scrobble_v1.js @@ -13,6 +13,7 @@ const { buildMetaJson, doUpload, } = require('../util/ncbl') +const { getCookieHeader, hasCookie } = require('../util/index') module.exports = async (query, request) => { // --- 参数校验 --- @@ -32,7 +33,12 @@ module.exports = async (query, request) => { 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) cookieObj.os = 'pc' const ctx = extractContext(cookieObj) diff --git a/module/search_lyric.js b/module/search_lyric.js new file mode 100644 index 0000000..a49477d --- /dev/null +++ b/module/search_lyric.js @@ -0,0 +1,15 @@ +// 按歌词搜索 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + keyword: query.keyword || query.keywords || '', + limit: query.limit || 30, + offset: query.offset || 0, + } + return request( + `/api/search/resource/lyric`, + data, + createOption(query, 'weapi'), + ) +} diff --git a/module/search_song_list_page.js b/module/search_song_list_page.js new file mode 100644 index 0000000..4a8def6 --- /dev/null +++ b/module/search_song_list_page.js @@ -0,0 +1,16 @@ +// 新版单曲搜索(分页) + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + keyword: query.keyword || query.keywords || '', + limit: query.limit || 30, + offset: query.offset || 0, + scene: query.scene || 'normal', + } + return request( + `/api/search/song/list/page`, + data, + createOption(query, 'weapi'), + ) +} diff --git a/module/search_suggest_mobile.js b/module/search_suggest_mobile.js new file mode 100644 index 0000000..b046c1b --- /dev/null +++ b/module/search_suggest_mobile.js @@ -0,0 +1,15 @@ +// 搜索联想(App 移动端版:直hit歌曲卡 + 联想词 + 热搜/原唱标签) +// 注意:参数名为 keyword(传 s 会返回 HTTP 400 空体), +// 与 search_suggest 的旧版 /api/search/suggest/keyword 不同。 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + keyword: query.keyword || query.keywords || '', + } + return request( + `/api/search/suggest/keyword/get`, + data, + createOption(query, 'weapi'), + ) +} diff --git a/package.json b/package.json index ebec1bc..868bc14 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "axios": "^1.20.0", "crypto-js": "^4.2.0", "dotenv": "^17.4.2", - "express": "^5.2.1", + "express": "^5.3.0", "express-fileupload": "^1.5.2", "fzstd": "^0.1.1", "gzip": "^0.1.0", @@ -100,12 +100,12 @@ "@types/express-fileupload": "^1.5.1", "@types/mocha": "^10.0.10", "@types/node": "25.9.2", - "@typescript-eslint/eslint-plugin": "^8.71.0", - "@typescript-eslint/parser": "^8.71.0", + "@typescript-eslint/eslint-plugin": "^8.72.0", + "@typescript-eslint/parser": "^8.72.0", "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", "eslint-plugin-html": "^8.2.1", - "eslint-plugin-prettier": "^5.5.6", + "eslint-plugin-prettier": "^5.5.7", "globals": "^17.13.0", "husky": "^9.1.7", "intelli-espower-loader": "^1.1.0", @@ -114,7 +114,7 @@ "nodemon": "^3.1.14", "@yao-pkg/pkg": "^6.23.0", "power-assert": "^1.6.1", - "prettier": "^3.9.9", + "prettier": "^3.9.10", "typescript": "^5.9.3" }, "publishConfig": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb8507f..c5b6a32 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^17.4.2 version: 17.4.2 express: - specifier: ^5.2.1 - version: 5.2.1 + specifier: ^5.3.0 + version: 5.3.0 express-fileupload: specifier: ^1.5.2 version: 1.5.2 @@ -79,11 +79,11 @@ importers: specifier: 25.9.2 version: 25.9.2 '@typescript-eslint/eslint-plugin': - specifier: ^8.71.0 - version: 8.71.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + specifier: ^8.72.0 + version: 8.72.0(@typescript-eslint/parser@8.72.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.71.0 - version: 8.71.0(eslint@9.39.5)(typescript@5.9.3) + specifier: ^8.72.0 + version: 8.72.0(eslint@9.39.5)(typescript@5.9.3) '@yao-pkg/pkg': specifier: ^6.23.0 version: 6.23.0 @@ -97,8 +97,8 @@ importers: specifier: ^8.2.1 version: 8.2.1 eslint-plugin-prettier: - specifier: ^5.5.6 - version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.9) + specifier: ^5.5.7 + version: 5.5.7(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.10) globals: specifier: ^17.13.0 version: 17.13.0 @@ -121,8 +121,8 @@ importers: specifier: ^1.6.1 version: 1.6.1 prettier: - specifier: ^3.9.9 - version: 3.9.9 + specifier: ^3.9.10 + version: 3.9.10 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -200,6 +200,9 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} + '@esbuild/aix-ppc64@0.28.2': resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} @@ -506,63 +509,63 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@typescript-eslint/eslint-plugin@8.71.0': - resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} + '@typescript-eslint/eslint-plugin@8.72.0': + resolution: {integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.71.0 + '@typescript-eslint/parser': ^8.72.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.71.0': - resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} + '@typescript-eslint/parser@8.72.0': + resolution: {integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.71.0': - resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} + '@typescript-eslint/project-service@8.72.0': + resolution: {integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.71.0': - resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} + '@typescript-eslint/scope-manager@8.72.0': + resolution: {integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.71.0': - resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} + '@typescript-eslint/tsconfig-utils@8.72.0': + resolution: {integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.71.0': - resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} + '@typescript-eslint/type-utils@8.72.0': + resolution: {integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.71.0': - resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} + '@typescript-eslint/types@8.72.0': + resolution: {integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.71.0': - resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} + '@typescript-eslint/typescript-estree@8.72.0': + resolution: {integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.71.0': - resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} + '@typescript-eslint/utils@8.72.0': + resolution: {integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.71.0': - resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} + '@typescript-eslint/visitor-keys@8.72.0': + resolution: {integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unblockneteasemusic/server@0.28.0': @@ -914,8 +917,8 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} engines: {node: '>=18'} content-type@1.0.5: @@ -1237,8 +1240,8 @@ packages: resolution: {integrity: sha512-Xn6QOifC3niiqDojZ/hgGRCj+YOefDHtT6ShwM65/+goFCNFcTTXUoXcjht+gvuds2I6r3tmM5XWQuTOOF5lhw==} engines: {node: '>=16.0.0'} - eslint-plugin-prettier@5.5.6: - resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} + eslint-plugin-prettier@5.5.7: + resolution: {integrity: sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -1358,8 +1361,8 @@ packages: resolution: {integrity: sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==} engines: {node: '>= 0.10.0'} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + express@5.3.0: + resolution: {integrity: sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==} engines: {node: '>= 18'} ext@1.7.0: @@ -2280,8 +2283,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.9: - resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} + prettier@3.9.10: + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} engines: {node: '>=14'} hasBin: true @@ -2678,8 +2681,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - synckit@0.11.13: - resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + synckit@0.12.3: + resolution: {integrity: sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==} engines: {node: ^14.18.0 || >=16.0.0} tar-fs@2.1.5: @@ -3100,6 +3103,8 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@dual-bundle/import-meta-resolve@4.2.1': {} + '@esbuild/aix-ppc64@0.28.2': optional: true @@ -3352,14 +3357,14 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 25.9.2 - '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.71.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.71.0 - '@typescript-eslint/type-utils': 8.71.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/utils': 8.71.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.71.0 + '@typescript-eslint/parser': 8.72.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/type-utils': 8.72.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.72.0 eslint: 9.39.5 ignore: 7.0.11 natural-compare: 1.4.0 @@ -3368,41 +3373,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.71.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/parser@8.72.0(eslint@9.39.5)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.71.0 - '@typescript-eslint/types': 8.71.0 - '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.71.0 + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.72.0 debug: 4.4.3 eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.71.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.72.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) - '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/tsconfig-utils': 8.72.0(typescript@5.9.3) + '@typescript-eslint/types': 8.72.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.71.0': + '@typescript-eslint/scope-manager@8.72.0': dependencies: - '@typescript-eslint/types': 8.71.0 - '@typescript-eslint/visitor-keys': 8.71.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/visitor-keys': 8.72.0 - '@typescript-eslint/tsconfig-utils@8.71.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.72.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.71.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.72.0(eslint@9.39.5)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.71.0 - '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.71.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.0(eslint@9.39.5)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.5 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -3410,14 +3415,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.71.0': {} + '@typescript-eslint/types@8.72.0': {} - '@typescript-eslint/typescript-estree@8.71.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.72.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.71.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) - '@typescript-eslint/types': 8.71.0 - '@typescript-eslint/visitor-keys': 8.71.0 + '@typescript-eslint/project-service': 8.72.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.72.0(typescript@5.9.3) + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/visitor-keys': 8.72.0 debug: 4.4.3 minimatch: 10.2.6 semver: 7.8.5 @@ -3427,20 +3432,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.71.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/utils@8.72.0(eslint@9.39.5)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) - '@typescript-eslint/scope-manager': 8.71.0 - '@typescript-eslint/types': 8.71.0 - '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(typescript@5.9.3) eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.71.0': + '@typescript-eslint/visitor-keys@8.72.0': dependencies: - '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/types': 8.72.0 eslint-visitor-keys: 5.0.1 '@unblockneteasemusic/server@0.28.0': @@ -3839,7 +3844,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.1.0: {} + content-disposition@2.0.1: {} content-type@1.0.5: {} @@ -4257,12 +4262,12 @@ snapshots: dependencies: htmlparser2: 10.1.0 - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.9): + eslint-plugin-prettier@5.5.7(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.10): dependencies: eslint: 9.39.5 - prettier: 3.9.9 + prettier: 3.9.10 prettier-linter-helpers: 1.0.1 - synckit: 0.11.13 + synckit: 0.12.3 optionalDependencies: eslint-config-prettier: 10.1.8(eslint@9.39.5) @@ -4449,12 +4454,12 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.2.1: + express@5.3.0: dependencies: accepts: 2.0.0 body-parser: 2.3.0 - content-disposition: 1.1.0 - content-type: 1.0.5 + content-disposition: 2.0.1 + content-type: 2.1.0 cookie: 0.7.2 cookie-signature: 1.2.2 debug: 4.4.3 @@ -5484,7 +5489,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.9: {} + prettier@3.9.10: {} process-nextick-args@2.0.1: {} @@ -5971,8 +5976,9 @@ snapshots: symbol-tree@3.2.4: {} - synckit@0.11.13: + synckit@0.12.3: dependencies: + '@dual-bundle/import-meta-resolve': 4.2.1 '@pkgr/core': 0.3.6 tar-fs@2.1.5: diff --git a/public/docs/home.md b/public/docs/home.md index 87ef977..b5b6f34 100644 --- a/public/docs/home.md +++ b/public/docs/home.md @@ -5291,6 +5291,102 @@ let data = encodeURIComponent( **调用例子 :** `/user/playlist/collect?uid=32953014` +### 搜索联想词(App 移动端版) + +说明 : 调用此接口 , 传入搜索关键词可获得 App 搜索框下拉的联想结果:第 0 条为直hit歌曲卡(含完整歌曲数据 simpleSongData), 其后为联想词列表(首条带"热搜"标签, 另有"原唱"标签卡), 每条带 highLightInfo 高亮分段。与 v9.6.13 客户端截图逐字段对齐。 + +**必选参数 :** + +`keyword` : 搜索关键词(注意: 参数名必须是 keyword, 传 keywords/s 会返回 HTTP 400 空体) + +**接口地址 :** `/search/suggest/mobile` + +**调用例子 :** `/search/suggest/mobile?keyword=如果呢` + +### 新版单曲搜索(分页) + +说明 : 调用此接口 , 使用 v9.6.13 客户端新版单曲搜索接口 , 返回 resources 数组(含歌曲信息、 royaltyInfo、 高亮词 hlWords)与 hasMore 分页标记 + +**必选参数 :** + +`keyword` : 搜索关键词 + +**可选参数 :** + +`limit` : 返回数量, 默认 30 +`offset` : 偏移量 +`scene` : 场景, 默认 normal + +**接口地址 :** `/search/song/list/page` + +**调用例子 :** `/search/song/list/page?keyword=如果呢&limit=30` + +### 按歌词搜索 + +说明 : 调用此接口 , 传入歌词片段可搜索包含该歌词的歌曲, 返回 resources 数组(含 lyric 匹配行、 highlightText 高亮文本、 歌曲详情) + +**必选参数 :** + +`keyword` : 歌词片段 + +**可选参数 :** + +`limit` : 返回数量, 默认 30 +`offset` : 偏移量 + +**接口地址 :** `/search/lyric` + +**调用例子 :** `/search/lyric?keyword=天青色等烟雨` + +### 动态搜索 + +说明 : 调用此接口 , 传入关键词可搜索用户发布的动态 + +**必选参数 :** + +`keyword` : 关键词 + +**可选参数 :** + +`limit` : 返回数量, 默认 30 +`offset` : 偏移量 + +**接口地址 :** `/event/search` + +**调用例子 :** `/event/search?keyword=周杰伦` + +### 评论默认文案 + +说明 : 调用此接口 , 传入评论区 threadId 可获取该评论区输入框的默认提示文案数组 + +**必选参数 :** + +`threadId` : 评论区 ID(如 `A_EV_2_6559519868_32953014`) + +**接口地址 :** `/comment/default/text` + +**调用例子 :** `/comment/default/text?threadId=A_EV_2_6559519868_32953014` + +### 热评墙广场信息 + +说明 : 调用此接口 , 可获取今天热评墙广场的配置信息 + +**接口地址 :** `/comment/hotwall/square/info` + +**调用例子 :** `/comment/hotwall/square/info` + +### 首页轮播(v3) + +说明 : 调用此接口 , 获取 v3 版首页轮播图(v9.6.13 客户端使用版本) + +**可选参数 :** + +`clientType` : 客户端类型, 默认 android + +**接口地址 :** `/banner/v3` + +**调用例子 :** `/banner/v3?clientType=android` + ### 搜索建议 - PC端 说明 : 调用此接口, 传入搜索关键词, 获取搜索建议 diff --git a/server.js b/server.js index 1c72cf8..7af7f6a 100644 --- a/server.js +++ b/server.js @@ -6,7 +6,7 @@ const request = require('./util/request') const packageJSON = require('./package.json') const exec = require('child_process').exec const cache = require('./util/apicache').middleware -const { cookieToJson } = require('./util/index') +const { cookieToJson, getCookieHeader, hasCookie } = require('./util/index') const fileUpload = require('express-fileupload') const decode = require('safe-decode-uri-component') const logger = require('./util/logger.js') @@ -225,14 +225,15 @@ async function constructServer(moduleDefs) { */ app.use((req, _, next) => { req.cookies = {} - //;(req.headers.cookie || '').split(/\s*;\s*/).forEach((pair) => { // Polynomial regular expression // - ;(req.headers.cookie || '').split(/;\s+|(? { - let crack = pair.indexOf('=') - if (crack < 1 || crack == pair.length - 1) return - req.cookies[decode(pair.slice(0, crack)).trim()] = decode( - pair.slice(crack + 1), - ).trim() - }) + getCookieHeader(req.headers) + .split(';') + .forEach((pair) => { + let crack = pair.indexOf('=') + if (crack < 1 || crack == pair.length - 1) return + req.cookies[decode(pair.slice(0, crack)).trim()] = decode( + pair.slice(crack + 1), + ).trim() + }) next() }) @@ -291,13 +292,19 @@ async function constructServer(moduleDefs) { } }) - let query = Object.assign( - {}, - { cookie: req.cookies }, - req.query, - req.body, - req.files, - ) + const queryCookie = req.query && req.query.cookie + const bodyCookie = req.body && req.body.cookie + const fileCookie = req.files && req.files.cookie + const cookie = hasCookie(fileCookie) + ? fileCookie + : hasCookie(bodyCookie) + ? bodyCookie + : hasCookie(queryCookie) + ? queryCookie + : req.cookies + const query = Object.assign({}, req.query, req.body, req.files, { + cookie, + }) try { let usedCrypto = '' diff --git a/util/index.js b/util/index.js index e76d665..d87dd94 100644 --- a/util/index.js +++ b/util/index.js @@ -94,15 +94,42 @@ module.exports = { // 优化:使用for循环替代forEach,性能更好 for (let i = 0, len = cookieArr.length; i < len; i++) { let item = cookieArr[i] - let arr = item.split('=') - // 优化:使用严格等于 - if (arr.length === 2) { - obj[arr[0].trim()] = arr[1].trim() + let separator = item.indexOf('=') + // 只按第一个等号拆分,保留 Cookie 值中的等号 + if (separator > 0) { + obj[item.slice(0, separator).trim()] = item.slice(separator + 1).trim() } } 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) { // 优化:使用预绑定的keys函数和for循环 const cookieKeys = keys(cookie) @@ -226,6 +253,8 @@ const cookieToJsonOptimized = (function() { function getCookieValue(cookieStr, name) { if (!cookieStr) return '' + if (typeof cookieStr === 'object') return cookieStr[name] || '' + const cookies = '; ' + cookieStr const parts = cookies.split('; ' + name + '=') if (parts.length === 2) return parts.pop().split(';').shift() diff --git a/util/option.js b/util/option.js index 9796234..317e40d 100644 --- a/util/option.js +++ b/util/option.js @@ -1,7 +1,15 @@ +const { getCookieHeader, hasCookie } = require('./index') + const createOption = (query, crypto = '', checkToken = false) => { + const headerCookie = getCookieHeader(query.headers) + return { 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 || '', proxy: query.proxy, realIP: query.realIP, diff --git a/util/request.js b/util/request.js index a0c9020..2bfd35d 100644 --- a/util/request.js +++ b/util/request.js @@ -13,6 +13,8 @@ const tmpPath = require('os').tmpdir() const { cookieToJson, cookieObjToString, + getCookieHeader, + hasCookie, toBoolean, generateRandomChineseIP, } = require('./index') @@ -243,13 +245,19 @@ const createRequest = async (uri, data, options) => { 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') { cookie = cookieToJson(cookie) } if (typeof cookie === 'object') { cookie = processCookieObject(cookie, crypto) + Object.keys(headers).forEach((name) => { + if (name.toLowerCase() === 'cookie') delete headers[name] + }) headers['Cookie'] = cookieObjToString(cookie) } let url = ''