Merge branch 'feat-lottery' of https://github.com/ZxwyWebSite/api-enhanced into pr/215

This commit is contained in:
ElyPrism 2026-07-24 17:11:34 +08:00
commit a3287a0180
No known key found for this signature in database
25 changed files with 893 additions and 261 deletions

View File

@ -0,0 +1,13 @@
// 获取免费听时长状态
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
entrance: 'FREE_LISTEN_RN',
}
return request(
`/api/ad/homepage/free/tab/extend/v2`,
data,
createOption(query, 'xeapi'),
)
}

View File

@ -0,0 +1,13 @@
// 发送安全验证码
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
ctcode: query.ctcode || '86',
}
return request(
`/api/sms/captcha/safe/sent`,
data,
createOption(query, 'eapi'),
)
}

16
module/captcha_sent_v1.js Normal file
View File

@ -0,0 +1,16 @@
// 发送验证码
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
ctcode: query.ctcode || '86',
secrete: 'music_middleuser_pclogin',
cellphone: query.phone,
scene: '0',
}
return request(
`/api/middle/captcha/sent/v1`,
data,
createOption(query, 'eapi'),
)
}

View File

@ -6,5 +6,9 @@ module.exports = (query, request) => {
cellphone: query.phone, cellphone: query.phone,
countrycode: query.countrycode, countrycode: query.countrycode,
} }
return request(`/api/cellphone/existence/check`, data, createOption(query)) return request(
`/api/cellphone/existence/check`,
data,
createOption(query, 'eapi'),
)
} }

18
module/comment_add.js Normal file
View File

@ -0,0 +1,18 @@
const { resourceTypeMap } = require('../util/config.json')
// 发送评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
threadId: resourceTypeMap[query.type] + query.id,
content: query.content,
resourceType: '0',
expressionPicId: '-1',
bubbleId: '-1',
}
return request(
`/api/resource/comments/add`,
data,
createOption(query, 'xeapi', 'v3'),
)
}

15
module/comment_delete.js Normal file
View File

@ -0,0 +1,15 @@
const { resourceTypeMap } = require('../util/config.json')
// 删除评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
commentId: query.cid,
threadId: resourceTypeMap[query.type] + query.id,
}
return request(
`/api/resource/comments/delete`,
data,
createOption(query, 'xeapi'),
)
}

17
module/comment_reply.js Normal file
View File

@ -0,0 +1,17 @@
const { resourceTypeMap } = require('../util/config.json')
// 发送评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
threadId: resourceTypeMap[query.type] + query.id,
commentId: query.cid,
content: query.content,
resourceType: '0',
}
return request(
`/api/v1/resource/comments/reply`,
data,
createOption(query, 'xeapi', 'v3'),
)
}

14
module/device_kickoff.js Normal file
View File

@ -0,0 +1,14 @@
// 强制下线设备
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
key: query.deviceKey,
captcha: query.captcha || '',
}
return request(
`/api/middle/user/security/device/kickoff`,
data,
createOption(query, 'eapi'),
)
}

13
module/device_list.js Normal file
View File

@ -0,0 +1,13 @@
// 登录设备列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
excStatus: '9',
}
return request(
`/api/middle/user/device/list`,
data,
createOption(query, 'eapi'),
)
}

View File

@ -14,6 +14,7 @@ module.exports = async (query, request) => {
? query.captcha ? query.captcha
: query.md5_password || CryptoJS.MD5(query.password).toString(), : query.md5_password || CryptoJS.MD5(query.password).toString(),
remember: 'true', remember: 'true',
secureCaptcha: query.sca || '',
} }
let result = await request( let result = await request(
`/api/w/login/cellphone`, `/api/w/login/cellphone`,

View File

@ -16,15 +16,9 @@ module.exports = (query, request) => {
activityId: query.activityId || '6501202', activityId: query.activityId || '6501202',
drawCount: query.drawCount || '1', drawCount: query.drawCount || '1',
} }
// TODO: 如何传递 checkToken
const optionQuery = { ...query }
if (query.checkToken) {
data.checkToken = query.checkToken
delete query.checkToken
}
return request( return request(
`/api/middle/play/do/lottery`, `/api/middle/play/do/lottery`,
data, data,
createOption(optionQuery, 'eapi', 'v2'), createOption(query, 'eapi', 'v3'),
) )
} }

View File

@ -34,5 +34,24 @@ module.exports = async (query) => {
} }
} }
// 模块加载时自动获取一次 Token确保 request.js 使用时不为空
fetch()
.then((token) => {
_token = token
})
.catch(() => {
// 静默失败,后续请求时会重试
})
// 给 request.js 读取用 // 给 request.js 读取用
module.exports.getToken = () => _token module.exports.getToken = () => {
if (!_token) {
// 如果 Token 为空,异步触发获取
fetch()
.then((token) => {
_token = token
})
.catch(() => {})
}
return _token
}

View File

@ -0,0 +1,28 @@
// 云小编考试状态
//
// examType:
// 1. 歌曲曲风审核: musicalStyleEnter
// 2. 歌曲语种审核: languageEnter
// 3. 歌曲原唱审核: oriSingerEnter
// 4. 情绪标签审核: emotionEnter
const createOption = require('../util/option.js')
module.exports = (query, request) => {
if (!query.examType)
return Promise.reject({
status: 400,
body: {
code: 400,
message: '参数不足',
},
})
const data = {
examType: query.examType,
}
return request(
'/api/rep/ugc/exam/info/get',
data,
createOption(query, 'eapi'),
)
}

View File

@ -0,0 +1,32 @@
// 云小编考试取题
//
// examType:
// 1. 歌曲曲风审核: musicalStyleEnter
// 2. 歌曲语种审核: languageEnter
// 3. 歌曲原唱审核: oriSingerEnter
// 4. 情绪标签审核: emotionEnter
//
// taskId:
// 首次调用 rep/ugc/exam/start 获取,之后调用 rep/ugc/exam/info/get 获取
const createOption = require('../util/option.js')
module.exports = (query, request) => {
if (!query.examType || !query.taskId)
return Promise.reject({
status: 400,
body: {
code: 400,
message: '参数不足',
},
})
const data = {
examType: query.examType,
taskId: query.taskId,
}
return request(
'/api/rep/ugc/exam/question/single/get',
data,
createOption(query, 'eapi'),
)
}

View File

@ -0,0 +1,32 @@
// 云小编考试结果
//
// examType:
// 1. 歌曲曲风审核: musicalStyleEnter
// 2. 歌曲语种审核: languageEnter
// 3. 歌曲原唱审核: oriSingerEnter
// 4. 情绪标签审核: emotionEnter
//
// taskId:
// 首次调用 rep/ugc/exam/start 获取,之后调用 rep/ugc/exam/info/get 获取
const createOption = require('../util/option.js')
module.exports = (query, request) => {
if (!query.examType || !query.taskId)
return Promise.reject({
status: 400,
body: {
code: 400,
message: '参数不足',
},
})
const data = {
examType: query.examType,
taskId: query.taskId,
}
return request(
'/api/rep/ugc/exam/result/get',
data,
createOption(query, 'eapi'),
)
}

View File

@ -0,0 +1,24 @@
// 云小编考试开始
//
// examType:
// 1. 歌曲曲风审核: musicalStyleEnter
// 2. 歌曲语种审核: languageEnter
// 3. 歌曲原唱审核: oriSingerEnter
// 4. 情绪标签审核: emotionEnter
const createOption = require('../util/option.js')
module.exports = (query, request) => {
if (!query.examType)
return Promise.reject({
status: 400,
body: {
code: 400,
message: '参数不足',
},
})
const data = {
examType: query.examType,
}
return request('/api/rep/ugc/exam/start', data, createOption(query, 'eapi'))
}

View File

@ -0,0 +1,36 @@
// 云小编考试提交
//
// examType:
// 1. 歌曲曲风审核: musicalStyleEnter
// 2. 歌曲语种审核: languageEnter
// 3. 歌曲原唱审核: oriSingerEnter
// 4. 情绪标签审核: emotionEnter
//
// taskId:
// 首次调用 rep/ugc/exam/start 获取,之后调用 rep/ugc/exam/info/get 获取
//
// questionId:
// 调用 rep/ugc/exam/question/single/get 获取
//
// answer:
// A 对, B 错
const createOption = require('../util/option.js')
module.exports = (query, request) => {
if (!query.examType || !query.taskId || !query.questionId || !query.answer)
return Promise.reject({
status: 400,
body: {
code: 400,
message: '参数不足',
},
})
const data = {
examType: query.examType,
taskId: query.taskId,
questionId: query.questionId,
answer: query.answer,
}
return request('/api/rep/ugc/exam/submit', data, createOption(query, 'eapi'))
}

View File

@ -2,6 +2,7 @@
// 此版本不再采用 br 作为音质区分的标准 // 此版本不再采用 br 作为音质区分的标准
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断 // 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
// 当unblock为true时, 会尝试使用unblockmusic-utils进行解锁, 同时音质设置不会生效, 但仍然为必须传入参数 // 当unblock为true时, 会尝试使用unblockmusic-utils进行解锁, 同时音质设置不会生效, 但仍然为必须传入参数
// 当level为sky时, 可通过 immerseType 选择沉浸声类型, 支持 c51(c51类型)、ste(环绕立体声类型)、aac(aac类型), 默认为 c51
const logger = require('../util/logger.js') const logger = require('../util/logger.js')
const createOption = require('../util/option.js') const createOption = require('../util/option.js')
@ -51,7 +52,7 @@ module.exports = async (query, request) => {
} }
} }
if (data.level == 'sky') { if (data.level == 'sky') {
data.immerseType = 'c51' data.immerseType = query.immerseType || 'c51'
} }
return request( return request(
`/api/song/enhance/player/url/v1`, `/api/song/enhance/player/url/v1`,

View File

@ -1,6 +1,6 @@
{ {
"name": "@neteasecloudmusicapienhanced/api", "name": "@neteasecloudmusicapienhanced/api",
"version": "4.37.0", "version": "4.38.0",
"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",
@ -98,8 +98,8 @@
"@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.64.0", "@typescript-eslint/eslint-plugin": "^8.65.0",
"@typescript-eslint/parser": "^8.64.0", "@typescript-eslint/parser": "^8.65.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.1.4", "eslint-plugin-html": "^8.1.4",
@ -112,7 +112,7 @@
"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.5", "prettier": "^3.9.6",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"publishConfig": { "publishConfig": {

163
pnpm-lock.yaml generated
View File

@ -73,11 +73,11 @@ importers:
specifier: 25.9.2 specifier: 25.9.2
version: 25.9.2 version: 25.9.2
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^8.64.0 specifier: ^8.65.0
version: 8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) version: 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^8.64.0 specifier: ^8.65.0
version: 8.64.0(eslint@9.39.5)(typescript@5.9.3) version: 8.65.0(eslint@9.39.5)(typescript@5.9.3)
eslint: eslint:
specifier: ^9.39.5 specifier: ^9.39.5
version: 9.39.5 version: 9.39.5
@ -89,7 +89,7 @@ importers:
version: 8.1.4 version: 8.1.4
eslint-plugin-prettier: eslint-plugin-prettier:
specifier: ^5.5.6 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.5) version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6)
globals: globals:
specifier: ^17.7.0 specifier: ^17.7.0
version: 17.7.0 version: 17.7.0
@ -115,8 +115,8 @@ importers:
specifier: ^1.6.1 specifier: ^1.6.1
version: 1.6.1 version: 1.6.1
prettier: prettier:
specifier: ^3.9.5 specifier: ^3.9.6
version: 3.9.5 version: 3.9.6
typescript: typescript:
specifier: ^5.9.3 specifier: ^5.9.3
version: 5.9.3 version: 5.9.3
@ -147,9 +147,9 @@ packages:
'@borewit/text-codec@0.2.2': '@borewit/text-codec@0.2.2':
resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==}
'@eslint-community/eslint-utils@4.9.1': '@eslint-community/eslint-utils@4.10.0':
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} resolution: {integrity: sha512-rd6n9Sefya1R1Vd594TRwzeHq2VnmICbvh3jebyt1Hs/6OoCSYNVpjGRRsKSTwAq3SKl09w0AfRCU57hoDLmxg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
@ -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.64.0': '@typescript-eslint/eslint-plugin@8.65.0':
resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==}
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.64.0 '@typescript-eslint/parser': ^8.65.0
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.64.0': '@typescript-eslint/parser@8.65.0':
resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==}
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.64.0': '@typescript-eslint/project-service@8.65.0':
resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==}
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.64.0': '@typescript-eslint/scope-manager@8.65.0':
resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==}
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.64.0': '@typescript-eslint/tsconfig-utils@8.65.0':
resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==}
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.64.0': '@typescript-eslint/type-utils@8.65.0':
resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==}
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.64.0': '@typescript-eslint/types@8.65.0':
resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==}
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.64.0': '@typescript-eslint/typescript-estree@8.65.0':
resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==}
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.64.0': '@typescript-eslint/utils@8.65.0':
resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==}
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.64.0': '@typescript-eslint/visitor-keys@8.65.0':
resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==}
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':
@ -973,10 +973,6 @@ packages:
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@4.2.1: eslint-visitor-keys@4.2.1:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1833,8 +1829,8 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
own-keys@1.0.1: own-keys@1.0.2:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
p-is-promise@3.0.0: p-is-promise@3.0.0:
@ -2006,8 +2002,8 @@ packages:
resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
prettier@3.9.5: prettier@3.9.6:
resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
@ -2687,10 +2683,10 @@ snapshots:
'@borewit/text-codec@0.2.2': {} '@borewit/text-codec@0.2.2': {}
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.5)': '@eslint-community/eslint-utils@4.10.0(eslint@9.39.5)':
dependencies: dependencies:
eslint: 9.39.5 eslint: 9.39.5
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 5.0.1
'@eslint-community/regexpp@4.12.2': {} '@eslint-community/regexpp@4.12.2': {}
@ -2867,14 +2863,14 @@ snapshots:
'@types/http-errors': 2.0.5 '@types/http-errors': 2.0.5
'@types/node': 25.9.2 '@types/node': 25.9.2
'@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.64.0(eslint@9.39.5)(typescript@5.9.3) '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/scope-manager': 8.65.0
'@typescript-eslint/type-utils': 8.64.0(eslint@9.39.5)(typescript@5.9.3) '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5)(typescript@5.9.3)
'@typescript-eslint/utils': 8.64.0(eslint@9.39.5)(typescript@5.9.3) '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/visitor-keys': 8.65.0
eslint: 9.39.5 eslint: 9.39.5
ignore: 7.0.6 ignore: 7.0.6
natural-compare: 1.4.0 natural-compare: 1.4.0
@ -2883,41 +2879,41 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3)': '@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/scope-manager': 8.65.0
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
'@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/visitor-keys': 8.65.0
debug: 4.4.3 debug: 4.4.3
eslint: 9.39.5 eslint: 9.39.5
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/project-service@8.64.0(typescript@5.9.3)': '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/tsconfig-utils': 8.64.0(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
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.64.0': '@typescript-eslint/scope-manager@8.65.0':
dependencies: dependencies:
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
'@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/visitor-keys': 8.65.0
'@typescript-eslint/tsconfig-utils@8.64.0(typescript@5.9.3)': '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)':
dependencies: dependencies:
typescript: 5.9.3 typescript: 5.9.3
'@typescript-eslint/type-utils@8.64.0(eslint@9.39.5)(typescript@5.9.3)': '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5)(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
'@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.64.0(eslint@9.39.5)(typescript@5.9.3) '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@5.9.3)
debug: 4.4.3 debug: 4.4.3
eslint: 9.39.5 eslint: 9.39.5
ts-api-utils: 2.5.0(typescript@5.9.3) ts-api-utils: 2.5.0(typescript@5.9.3)
@ -2925,14 +2921,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/types@8.64.0': {} '@typescript-eslint/types@8.65.0': {}
'@typescript-eslint/typescript-estree@8.64.0(typescript@5.9.3)': '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/project-service': 8.64.0(typescript@5.9.3) '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.64.0(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
'@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/visitor-keys': 8.65.0
debug: 4.4.3 debug: 4.4.3
minimatch: 10.2.5 minimatch: 10.2.5
semver: 7.8.5 semver: 7.8.5
@ -2942,20 +2938,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/utils@8.64.0(eslint@9.39.5)(typescript@5.9.3)': '@typescript-eslint/utils@8.65.0(eslint@9.39.5)(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5) '@eslint-community/eslint-utils': 4.10.0(eslint@9.39.5)
'@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/scope-manager': 8.65.0
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
'@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3)
eslint: 9.39.5 eslint: 9.39.5
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/visitor-keys@8.64.0': '@typescript-eslint/visitor-keys@8.65.0':
dependencies: dependencies:
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.65.0
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 5.0.1
'@unblockneteasemusic/server@0.28.0': '@unblockneteasemusic/server@0.28.0':
@ -3527,7 +3523,7 @@ snapshots:
object-inspect: 1.13.4 object-inspect: 1.13.4
object-keys: 1.1.1 object-keys: 1.1.1
object.assign: 4.1.7 object.assign: 4.1.7
own-keys: 1.0.1 own-keys: 1.0.2
regexp.prototype.flags: 1.5.4 regexp.prototype.flags: 1.5.4
safe-array-concat: 1.1.4 safe-array-concat: 1.1.4
safe-push-apply: 1.0.0 safe-push-apply: 1.0.0
@ -3656,10 +3652,10 @@ snapshots:
dependencies: dependencies:
htmlparser2: 10.1.0 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.5): eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6):
dependencies: dependencies:
eslint: 9.39.5 eslint: 9.39.5
prettier: 3.9.5 prettier: 3.9.6
prettier-linter-helpers: 1.0.1 prettier-linter-helpers: 1.0.1
synckit: 0.11.13 synckit: 0.11.13
optionalDependencies: optionalDependencies:
@ -3670,15 +3666,13 @@ snapshots:
esrecurse: 4.3.0 esrecurse: 4.3.0
estraverse: 5.3.0 estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.2.1: {} eslint-visitor-keys@4.2.1: {}
eslint-visitor-keys@5.0.1: {} eslint-visitor-keys@5.0.1: {}
eslint@9.39.5: eslint@9.39.5:
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5) '@eslint-community/eslint-utils': 4.10.0(eslint@9.39.5)
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.21.2 '@eslint/config-array': 0.21.2
'@eslint/config-helpers': 0.4.2 '@eslint/config-helpers': 0.4.2
@ -4662,8 +4656,9 @@ snapshots:
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.5 word-wrap: 1.2.5
own-keys@1.0.1: own-keys@1.0.2:
dependencies: dependencies:
call-bound: 1.0.4
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
object-keys: 1.1.1 object-keys: 1.1.1
safe-push-apply: 1.0.0 safe-push-apply: 1.0.0
@ -4899,7 +4894,7 @@ snapshots:
dependencies: dependencies:
fast-diff: 1.3.0 fast-diff: 1.3.0
prettier@3.9.5: {} prettier@3.9.6: {}
process-nextick-args@2.0.1: {} process-nextick-args@2.0.1: {}

View File

@ -283,7 +283,9 @@ AI 生成的图,仅供娱乐()
`md5_password`: md5 加密后的密码,传入后 `password` 参数将失效 `md5_password`: md5 加密后的密码,传入后 `password` 参数将失效
`captcha`: 验证码,使用 [`/captcha/sent`](#发送验证码)接口传入手机号获取验证码,调用此接口传入验证码,可使用验证码登录,传入后 `password` 参数将失效 `captcha`: 验证码,使用 `/captcha/sent``/captcha/sent/v1`接口传入手机号获取验证码,调用此接口传入验证码,可使用验证码登录,传入后 `password` 参数将失效
`sca`: 网易易盾滑块验证token, 获取方式未知
**接口地址 :** `/login/cellphone` **接口地址 :** `/login/cellphone`
@ -397,6 +399,19 @@ body {
**调用例子 :** `/captcha/sent?phone=13xxx` **调用例子 :** `/captcha/sent?phone=13xxx`
### 新版发送验证码
说明 : 调用此接口 ,传入手机号码, 可发送验证码
**必选参数 :** `phone`: 手机号码
**可选参数 :**
`ctcode`: 国家区号,默认 86 即中国
**接口地址 :** `/captcha/sent/v1`
**调用例子 :** `/captcha/sent/v1?phone=13xxx`
### 验证验证码 ### 验证验证码
说明 : 调用此接口 ,传入手机号码和验证码, 可校验验证码是否正确 说明 : 调用此接口 ,传入手机号码和验证码, 可校验验证码是否正确
@ -1259,9 +1274,11 @@ tags: 歌单标签
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清环绕声`, `sky` => `沉浸环绕声`, `dolby` => `杜比全景声`, `jymaster` => `超清母带` `lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清环绕声`, `sky` => `沉浸环绕声`, `dolby` => `杜比全景声`, `jymaster` => `超清母带`
`unblock`: 是否使用使用歌曲解锁, 分为`true``false` `unblock`: 是否使用使用歌曲解锁, 分为`true``false`
**可选参数 :** `immerseType`: 沉浸声环绕声类型, 分为 `c51` => `c51类型`, `ste` => `环绕立体声类型`, `aac` => `aac类型`, 仅在 `level=sky` 时生效, 默认为 `c51`
**接口地址 :** `/song/url/v1` **接口地址 :** `/song/url/v1`
**调用例子 :** `/song/url/v1?id=1969519579&level=exhigh` `/song/url/v1?id=1969519579,33894312&level=lossless` **调用例子 :** `/song/url/v1?id=1969519579&level=exhigh` `/song/url/v1?id=1969519579,33894312&level=lossless` `/song/url/v1?id=1969519579&level=sky&immerseType=ste`
说明:`杜比全景声`音质需要设备支持,不同的设备可能会返回不同码率的 url。cookie 需要传入`os=pc`保证返回正常码率的 url。 说明:`杜比全景声`音质需要设备支持,不同的设备可能会返回不同码率的 url。cookie 需要传入`os=pc`保证返回正常码率的 url。
@ -5493,6 +5510,14 @@ let data = encodeURIComponent(
**调用例子 :** `/ad/listening/rights/gain` **调用例子 :** `/ad/listening/rights/gain`
### 获取免费听时长状态
说明 : 登录后调用此接口, 获取免费听剩余时长
**接口地址 :** `/ad/listening/rights`
**调用例子 :** `/ad/listening/rights`
### 云小编 - 获取用户详情 ### 云小编 - 获取用户详情
说明: 登录后调用此接口, 获取云小编用户详情 说明: 登录后调用此接口, 获取云小编用户详情
@ -5603,12 +5628,203 @@ let data = encodeURIComponent(
`drawCount`: 未知, 默认 `1` `drawCount`: 未知, 默认 `1`
`checkToken`: 易盾反作弊 Token, 默认自动获取
**接口地址:** `/middle/play/do/lottery` **接口地址:** `/middle/play/do/lottery`
**调用例子:** `/middle/play/do/lottery?activityId=6501202&drawCount=1` **调用例子:** `/middle/play/do/lottery?activityId=6501202&drawCount=1`
### 云小编 - 考试状态
说明: 登录后调用此接口, 查询云小编入站考试状态, 进度 (`data.process`): no: 未开始, process: 考试中, whole_exam_end: 已结束, 此时可检查 `data.hasPassExamination` 确认是否通过
**必选参数:**
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
**接口地址:** `/rep/ugc/exam/info/get`
**调用例子:** `/rep/ugc/exam/info/get?examType=emotionEnter`
### 云小编 - 考试开始
说明: 未考试或考试失败时调用此接口, 开始新一轮考试
**必选参数:**
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
**接口地址:** `/rep/ugc/exam/start`
**调用例子:** `/rep/ugc/exam/start?examType=emotionEnter`
### 云小编 - 考试取题
说明: 考试开始后调用此接口, 获取当前试题
**必选参数:**
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
`taskId`: 任务 ID, 首次调用 `/rep/ugc/exam/start` 获取,之后调用 `/rep/ugc/exam/info/get` 获取
**接口地址:** `/rep/ugc/exam/question/single/get`
**调用例子:** `/rep/ugc/exam/question/single/get?examType=emotionEnter&taskId=123456`
### 云小编 - 考试提交
说明: 取题后调用此接口, 提交作答结果
**必选参数:**
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
`taskId`: 任务 ID, 首次调用 `/rep/ugc/exam/start` 获取,之后调用 `/rep/ugc/exam/info/get` 获取
`questionId`: 试题 ID, 调用 `/rep/ugc/exam/question/single/get` 获取
`answer`: 判断结果, A: 对, B: 错
**接口地址:** `/rep/ugc/exam/submit`
**调用例子:** `/rep/ugc/exam/submit?examType=emotionEnter&taskId=123456&questionId=123456&answer=A`
### 云小编 - 考试结果
说明: 考试结束后调用此接口, 获取考试正确数与正确率
**必选参数:**
`examType`: 考试类型, musicalStyleEnter: 歌曲曲风审核, languageEnter: 歌曲语种审核, oriSingerEnter: 歌曲原唱审核, emotionEnter: 情绪标签审核
`taskId`: 任务 ID, 首次调用 `/rep/ugc/exam/start` 获取,之后调用 `/rep/ugc/exam/info/get` 获取
**接口地址:** `/rep/ugc/exam/result/get`
**调用例子:** `/rep/ugc/exam/result/get?examType=emotionEnter&taskId=123456`
### 发送/删除评论
说明 : 调用此接口,可发送评论或者删除评论
1. 发送评论
**必选参数**
`type`: 数字,资源类型,对应歌曲,mv,专辑,歌单,电台,视频对应以下类型
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台
5: 视频
6: 动态
```
`id`: 对应资源 id
`content`: 要发送的内容
**调用例子** : `/comment/add?type=1&id=5436712&content=test` (往广岛之恋 mv 发送评论: test)
2. 回复评论
**必选参数**
`type`: 数字,资源类型,对应歌曲,mv,专辑,歌单,电台,视频对应以下类型
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台
5: 视频
6: 动态
```
`id`: 对应资源 id
`cid`: 评论 id
`content`: 要发送的内容
**调用例子** : `/comment/add?type=1&id=5436712&cid=1535550516319&content=test` (往广岛之恋 mv 回复test评论: test)
3. 删除评论
**必选参数**
`type`: 数字,资源类型,对应歌曲,mv,专辑,歌单,电台,视频对应以下类型
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台节目
5: 视频
6: 动态
7: 电台
```
`id`: 对应资源 id
`cid`: 评论 id
**调用例子** : `/comment?type=1&id=5436712&cid=1535550516319` (在广岛之恋 mv 删除评论)
### 获取在线设备列表
说明: 登录后调用此接口, 获取在线设备列表
**接口地址:** `/device/list`
**调用例子:** `/device/list`
### 发送安全验证码
说明: 登录后调用此接口, 传入手机号, 可发送安全验证码
**必选参数 :** `phone`: 手机号
**接口地址 :** `/captcha/safe/sent`
**调用例子 :** `/captcha/safe/sent?phone=13XXXXXXXXX`
### 强制下线设备
说明: 登录后调用此接口, 传入设备 id, 可强制下线设备的登录会话
**必选参数 :** `key`: 设备的 `deviceKey`, 可通过 `/device/list` 获取
`captcha`: 安全验证码, 可通过 `/captcha/safe/sent` 获取
**接口地址 :** `/device/kickoff`
**调用例子 :** `/device/kickoff?key=00ALDFGEXXXXXXXXXXXXXXXXX&captcha=1234`
## 离线访问此文档 ## 离线访问此文档
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问 此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问

View File

@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="no-referrer" /> <meta name="referrer" content="no-referrer" />
<link rel="icon" href="docs/netease.png" /> <link rel="icon" href="docs/netease.png" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="docs/netease.png" />
<title>云小编任务中心 - 网易云音乐 API Enhanced</title> <title>云小编任务中心 - 网易云音乐 API Enhanced</title>
<style> <style>
* { * {
@ -327,6 +329,10 @@
flex-shrink: 0; flex-shrink: 0;
} }
#userPoint {
font-size: 14px;
}
/* ---- Song Review ---- */ /* ---- Song Review ---- */
.song-review { .song-review {
margin-top: 16px; margin-top: 16px;
@ -379,7 +385,7 @@
border-radius: 6px; border-radius: 6px;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
white-space: pre-wrap; white-space: pre-wrap;
max-height: 120px; max-height: 160px;
overflow-y: auto; overflow-y: auto;
font-family: monospace; font-family: monospace;
line-height: 1.6; line-height: 1.6;
@ -419,6 +425,7 @@
<div class="info-card" id="user"> <div class="info-card" id="user">
<img src="docs/netease.png" alt="头像" class="avatar" id="userAvatar" /> <img src="docs/netease.png" alt="头像" class="avatar" id="userAvatar" />
<span class="info-text" id="userName">未登录</span> <span class="info-text" id="userName">未登录</span>
<span id="userPoint">0</span>
<button class="btn-sm green" id="userSign" disabled>签到</button> <button class="btn-sm green" id="userSign" disabled>签到</button>
</div> </div>
</div> </div>
@ -440,7 +447,7 @@
<button class="btn-sm" id="taskGet">获取任务详情</button> <button class="btn-sm" id="taskGet">获取任务详情</button>
</div> </div>
<div class="info-card" id="task"> <div class="info-card" id="task">
<span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+66)</span> <span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+<span id="taskPoint">0</span>)</span>
<button class="btn-sm" id="taskStart" disabled>去完成</button> <button class="btn-sm" id="taskStart" disabled>去完成</button>
</div> </div>
@ -556,6 +563,7 @@
/// 用户信息 /// /// 用户信息 ///
var userAvatar = document.getElementById('userAvatar'); var userAvatar = document.getElementById('userAvatar');
var userName = document.getElementById('userName'); var userName = document.getElementById('userName');
var userPoint = document.getElementById('userPoint');
var userSign = document.getElementById('userSign'); var userSign = document.getElementById('userSign');
var userGet = document.getElementById('userGet'); var userGet = document.getElementById('userGet');
@ -564,6 +572,7 @@
var d = res.data; var d = res.data;
if (d.imgUrl) userAvatar.src = d.imgUrl + '?param=50y50'; if (d.imgUrl) userAvatar.src = d.imgUrl + '?param=50y50';
userName.textContent = d.name || '游客' + d.id; userName.textContent = d.name || '游客' + d.id;
userPoint.textContent = d.availablePoints;
userSign.disabled = d.signed; userSign.disabled = d.signed;
showResult('用户信息加载成功', 'success'); showResult('用户信息加载成功', 'success');
}); });
@ -576,6 +585,7 @@
var d = res.data; var d = res.data;
userSign.disabled = d; userSign.disabled = d;
showResult('签到' + (d ? '成功 ✓' : '失败 ✗'), d ? 'success' : 'error'); showResult('签到' + (d ? '成功 ✓' : '失败 ✗'), d ? 'success' : 'error');
if (d) userGet.click();
}); });
} }
}; };
@ -608,21 +618,36 @@
/// 审核任务 /// /// 审核任务 ///
var taskCount = document.getElementById('taskCount'); var taskCount = document.getElementById('taskCount');
var taskGoal = document.getElementById('taskGoal'); var taskGoal = document.getElementById('taskGoal');
var taskPoint = document.getElementById('taskPoint');
var taskStartBtn = document.getElementById('taskStart'); var taskStartBtn = document.getElementById('taskStart');
var taskGetBtn = document.getElementById('taskGet'); var taskGetBtn = document.getElementById('taskGet');
taskGetBtn.onclick = function () { taskGetBtn.onclick = function () {
request('/rep/ugc/activity/get', {}, function (res) { request('rep/ugc/activity/get', {}, function (res) {
var d = res.data; var d = res.data;
taskCount.textContent = d.count; taskCount.textContent = d.count;
taskGoal.textContent = d.goalCount; taskGoal.textContent = d.goalCount;
taskStartBtn.disabled = d.activityId !== 5001; taskPoint.textContent = d.points;
// 注:超过 100 可能有 bug导致 thinktank/audit/resource/detail 获取的任务 ID 一直是 5002目标数量也不对暂时限制前两个任务
taskStartBtn.disabled = d.activityId !== 5001 && d.activityId !== 5002; // 5001: 1-50, 5002: 51-100
showResult('任务详情加载成功', 'success'); showResult('任务详情加载成功', 'success');
// 任务完成,领取积分
if (d.count >= d.goalCount) {
request('rep/ugc/activity/collect', { activityId: d.activityId }, function (res) {
var d = res.data;
showResult('积分领取' + (d ? '成功,可以兑换会员了!' : '失败'), d ? 'success' : 'error');
// 刷新任务状态
if (d) loadAll();
});
}
}); });
}; };
taskStartBtn.onclick = function () { taskStartBtn.onclick = function () {
if (!taskStartBtn.disabled) taskStart(); if (!taskStartBtn.disabled) {
songAudio.pause();
examInit();
}
}; };
/// 歌曲审核区域 /// /// 歌曲审核区域 ///
@ -636,12 +661,93 @@
var songSkip = document.getElementById('songSkip'); var songSkip = document.getElementById('songSkip');
var songLyric = document.getElementById('songLyric'); var songLyric = document.getElementById('songLyric');
var examType = 'emotionEnter';
// 考试初始化、检查状态
function examInit() {
request('rep/ugc/exam/info/get', { examType: examType }, function (res) {
var d = res.data;
switch (d.process) {
case 'whole_exam_end':
// 通过考试
if (d.hasPassExamination) {
taskStart();
return;
}
request('rep/ugc/exam/result/get', { examType: examType, taskId: d.taskId }, function (res) {
var d = res.data;
showResult('考试未通过:正确数:' + d.wrightNum + ',正确率:' + d.wrightRate, 'error');
});
case 'no':
// 未通过或未开始
request('rep/ugc/exam/start', { examType: examType }, function (res) {
examStart(res.data.taskId);
});
break;
case 'process':
// 正在考试
examStart(d.taskId);
break;
default:
showResult('未知考试进度:' + d.process, 'error');
return;
}
// 在上个信息不为异常时显示考试中提示
if (d.process !== 'whole_exam_end' && !resultDiv.className.endsWith('error')) showResult('正在考试,请认真审题!', 'info');
});
}
// 考试取题开始
function examStart(taskId) {
request('rep/ugc/exam/question/single/get', { examType: examType, taskId: taskId }, function (res) {
var d = res.data;
songCover.src = d.coverUrl + '?param=100y100';
songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists;
songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.questionContent + '';
songLyric.textContent = d.lyric ? d.transLyric ? lrctran(d.lyric, d.transLyric) : d.lyric : '[00:00.00] 暂无歌词';
songLyric.scrollTop = 0;
// 有时候就是没有播放链接,只能根据歌词硬审
if (d.songUrl) {
songAudio.src = d.songUrl;
songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ });
} else {
songAudio.src = ''; // 清理播放器
}
songApprove.onclick = function () { if (!songApprove.disabled) examSubmit(taskId, d.questionId, 'A') };
songReject.onclick = function () { if (!songReject.disabled) examSubmit(taskId, d.questionId, 'B') };
songSkip.onclick = null;
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
song.style.display = 'block';
setTimeout(function () {
songApprove.disabled = songReject.disabled = false;
}, 3e3);
});
}
// 考试提交
function examSubmit(taskId, questionId, answer) {
songAudio.pause();
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
request(
'rep/ugc/exam/submit',
{ examType: examType, taskId: taskId, questionId: questionId, answer: answer },
function (res) {
var d = res.data;
if (d.result) {
showResult('回答正确,加载下一首...', 'success');
} else {
showResult('回答错误:' + d.analysis, 'error');
}
examInit();
},
);
}
/** /**
* 获取任务 * 获取任务
*/ */
function taskStart() { function taskStart() {
song.style.display = 'none'; request('thinktank/audit/resource/detail', { type: '4' }, function (res) {
request('/thinktank/audit/resource/detail', { type: '4' }, function (res) {
var d = res.data; var d = res.data;
if (d.auditCount >= 50) { if (d.auditCount >= 50) {
@ -661,8 +767,8 @@
songCover.src = d.coverUrl + '?param=100y100'; songCover.src = d.coverUrl + '?param=100y100';
songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists; songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists;
songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.initResult + ''; songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.initResult + '';
songLyric.textContent = (d.lyric || '') + (d.transLyric || ''); songLyric.textContent = d.lyric ? d.transLyric ? lrctran(d.lyric, d.transLyric) : d.lyric : '[00:00.00] 暂无歌词';
if (!songLyric.textContent.trim()) songLyric.textContent = '[00:00.00] 暂无歌词'; songLyric.scrollTop = 0;
songAudio.src = d.songUrl; songAudio.src = d.songUrl;
songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ }); songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ });
@ -685,6 +791,7 @@
* @param {'1'|'2'|'3'} judgement * @param {'1'|'2'|'3'} judgement
*/ */
function taskSubmit(taskId, judgement) { function taskSubmit(taskId, judgement) {
songAudio.pause();
songApprove.disabled = songReject.disabled = songSkip.disabled = true; songApprove.disabled = songReject.disabled = songSkip.disabled = true;
request( request(
'thinktank/audit/resource/update', 'thinktank/audit/resource/update',
@ -692,7 +799,10 @@
function (res) { function (res) {
var d = res.data; var d = res.data;
if (d.result) { if (d.result) {
showResult('审核提交成功,加载下一首...', 'success'); showResult((d.test ? '抽查' : '审核') + '提交成功,加载下一首...', 'success');
taskStart();
} else if (d.test) {
showResult('抽查失败: [' + d.dayErrorNum + '/' + d.dayErrorNumStandard + '] ' + d.errorAnalysis, 'error');
taskStart(); taskStart();
} else { } else {
showResult('未知状态: ' + JSON.stringify(d), 'error'); showResult('未知状态: ' + JSON.stringify(d), 'error');
@ -701,6 +811,61 @@
); );
} }
/**解析歌词时间戳和文本 */
function lrctrim(lyrics) {
var lines = lyrics.split('\n');
var data = [];
lines.forEach(function (line, index) {
var matches = line.match(/\[(\d{2}):(\d{2}[\.:]?\d*)\]/);
if (matches) {
var minutes = parseInt(matches[1], 10);
var seconds = parseFloat(matches[2].replace('.', ':')) || 0;
var timestamp = minutes * 6e4 + seconds * 1e3;
var text = line.replace(/\[\d{2}:\d{2}[\.:]?\d*\]/g, '').trim();
text = text.replace(/\s\s+/g, ' '); // Replace multiple spaces with a single space
data.push([timestamp, index, text]);
}
});
data.sort(function (a, b) {
return a[0] - b[0];
});
return data;
}
/**合并原文歌词和翻译歌词 */
function lrctran(lyric, tlyric) {
lyric = lrctrim(lyric);
tlyric = lrctrim(tlyric);
var len1 = lyric.length;
var len2 = tlyric.length;
var result = '';
for (var i = 0, j = 0; i < len1 && j < len2; i++) {
while (lyric[i][0] > tlyric[j][0] && j + 1 < len2) {
j++;
}
if (lyric[i][0] === tlyric[j][0]) {
tlyric[j][2] = tlyric[j][2].replace('/', '');
if (tlyric[j][2]) {
lyric[i][2] += ' (' + tlyric[j][2] + ')';
}
j++;
}
}
for (var k = 0; k < len1; k++) {
var t = lyric[k][0];
result += '['.concat(
String(Math.floor(t / 6e4)).padStart(2, '0'),
':',
String(Math.floor((t % 6e4) / 1e3)).padStart(2, '0'),
'.',
String(t % 1e3).padStart(3, '0'),
']',
lyric[k][2],
'\n',
);
}
return result;
}
/// 一键载入 /// /// 一键载入 ///
window.loadAll = function () { window.loadAll = function () {
userGet.click(); userGet.click();

View File

@ -9,20 +9,13 @@
<title>云小编每日抽奖 - 网易云音乐 API Enhanced</title> <title>云小编每日抽奖 - 网易云音乐 API Enhanced</title>
<style> <style>
:root { :root {
--bg-start: #f3f0ff;
--bg-end: #e8f4fd;
--primary: #7c6cf0;
--primary-light: #ede9fe;
--accent: #fd79a8;
--gold: #fdcb6e; --gold: #fdcb6e;
--text: #2d3436;
--text-light: #636e72; --text-light: #636e72;
--card-bg: #ffffff; --fg: #333;
--border: #e8e8f0; --muted: #666;
--shadow: 0 8px 30px rgba(108, 92, 231, 0.08); --border: #ddd;
--radius: 20px; --bg: #f5f5f5;
--radius-sm: 12px; --panel: #ffffff;
--transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
} }
* { * {
@ -33,13 +26,13 @@
body { body {
font-family: 'Segoe UI', system-ui, -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif; font-family: 'Segoe UI', system-ui, -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, var(--bg-start) 0%, var(--bg-end) 50%, #fce4ec 100%);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
align-items: center; /* align-items: center; */
justify-content: center; justify-content: center;
padding: 1.5rem; padding: 1.5rem;
color: var(--text); color: var(--fg);
background: var(--bg);
line-height: 1.5; line-height: 1.5;
} }
@ -51,65 +44,77 @@
gap: 1.5rem; gap: 1.5rem;
} }
/* 标题编辑卡片 */ /* 标题 */
.header h1 {
font-size: 24px;
font-weight: 600;
}
.header p {
margin-top: 4px;
color: var(--muted);
font-size: 14px;
}
/* 卡片 */
.card { .card {
background: var(--card-bg); background: var(--panel);
border-radius: var(--radius);
padding: 1.8rem 2rem;
box-shadow: var(--shadow);
border: 1px solid var(--border); border: 1px solid var(--border);
transition: var(--transition); border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
} }
.title-section { /* 按钮 */
text-align: center; button {
} padding: 8px 18px;
border-radius: 8px;
.title-input {
width: 100%;
border: 2px solid transparent;
border-radius: var(--radius-sm);
font-size: 1.6rem;
font-weight: 700;
text-align: center;
padding: 0.6rem 1rem;
color: var(--text);
background: #fafafa;
transition: var(--transition);
outline: none;
font-family: inherit;
}
.title-input:hover {
background: #f5f3ff;
}
.title-input:focus {
border-color: var(--primary-light);
background: #fff;
box-shadow: 0 0 0 4px rgba(124, 108, 240, 0.06);
}
.title-hint {
font-size: 0.8rem;
color: #b2bec3;
margin-top: 0.4rem;
}
/* 抽奖主卡片 */
.lottery-card {
background: var(--card-bg);
border-radius: var(--radius);
padding: 2rem 2rem 2.2rem;
box-shadow: var(--shadow);
border: 1px solid var(--border); border: 1px solid var(--border);
font-size: 14px;
font-weight: 500;
cursor: pointer;
background: var(--panel);
color: var(--fg);
transition: all 0.15s;
}
button:hover {
background: #eee;
}
button.primary {
background: var(--fg);
color: var(--panel);
border-color: var(--fg);
}
button.primary:hover {
opacity: 0.85;
}
/* 页脚 */
.footer {
padding-top: 12px;
border-top: 1px solid var(--border);
color: var(--muted);
text-align: center; text-align: center;
font-size: 13px;
}
.footer a {
color: var(--fg);
text-decoration: none;
}
.footer a:hover {
border-bottom: 1px dotted var(--fg);
} }
.rolling-display { .rolling-display {
font-size: 2.4rem; font-size: 2.4rem;
font-weight: 800; font-weight: 800;
color: var(--primary); color: #c4b5fd;
min-height: 4rem; min-height: 4rem;
display: flex; display: flex;
align-items: center; align-items: center;
@ -121,7 +126,6 @@
} }
.rolling-display.idle { .rolling-display.idle {
color: #c4b5fd;
font-size: 1.3rem; font-size: 1.3rem;
font-weight: 500; font-weight: 500;
} }
@ -134,6 +138,7 @@
padding: 1.5rem; padding: 1.5rem;
margin: 1rem 0 1.5rem; margin: 1rem 0 1.5rem;
animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
text-align: center;
} }
.winner-label { .winner-label {
@ -152,6 +157,12 @@
word-break: break-all; word-break: break-all;
} }
.winner-img {
width: 200px;
height: 200px;
margin: -20px;
}
.confetti-row { .confetti-row {
margin-top: 0.5rem; margin-top: 0.5rem;
display: flex; display: flex;
@ -198,59 +209,6 @@
} }
} }
/* 按钮 */
.btn {
padding: 0.9rem 2.5rem;
border: none;
border-radius: 50px;
font-weight: 700;
font-size: 1.05rem;
cursor: pointer;
transition: var(--transition);
letter-spacing: 0.5px;
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.btn-lottery {
background: linear-gradient(135deg, #fd79a8, #e84393);
color: #fff;
box-shadow: 0 6px 20px rgba(253, 121, 168, 0.35);
}
.btn-lottery:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 28px rgba(253, 121, 168, 0.45);
}
.btn-lottery:active:not(:disabled) {
transform: translateY(0);
}
.btn-lottery:disabled {
background: #b2bec3;
box-shadow: none;
cursor: not-allowed;
transform: none;
}
.btn-reset {
background: transparent;
border: 2px solid var(--border);
color: var(--text-light);
margin-top: 0.8rem;
font-weight: 600;
}
.btn-reset:hover {
border-color: var(--primary-light);
color: var(--primary);
background: #f8f7ff;
}
/* 状态提示 */ /* 状态提示 */
.status-bar { .status-bar {
display: flex; display: flex;
@ -288,16 +246,6 @@
/* 响应式 */ /* 响应式 */
@media (max-width: 500px) { @media (max-width: 500px) {
.card,
.lottery-card {
padding: 1.4rem 1.2rem;
}
.title-input {
font-size: 1.3rem;
}
.rolling-display { .rolling-display {
font-size: 1.8rem; font-size: 1.8rem;
min-height: 3rem; min-height: 3rem;
@ -312,20 +260,19 @@
<body> <body>
<div class="container"> <div class="container">
<!-- 标题编辑区 --> <!-- 标题 -->
<div class="card title-section"> <div class="header">
<input type="text" class="title-input" id="titleInput" value="🎉 云小编每日抽奖 🎉" autocomplete="off" disabled> <h1>🎉 云小编每日抽奖</h1>
<div class="title-hint">每次抽奖消耗 <b>200 个曲库编辑部积分</b>,每日最多可抽奖 3 次</div> <p>每次抽奖消耗 <b>200 个曲库编辑部积分</b>,每日最多可抽奖 3 次</p>
</div> </div>
<!-- 抽奖 -->
<!-- 抽奖主区域 --> <div class="card">
<div class="lottery-card">
<div class="rolling-display idle" id="rollingDisplay">等待抽奖</div> <div class="rolling-display idle" id="rollingDisplay">等待抽奖</div>
<div id="resultContainer"></div> <div id="resultContainer"></div>
<button class="btn btn-lottery" id="lotteryBtn"> <button id="lotteryBtn" class="primary">
<span>🎲</span> 开始抽奖 <span>🎲</span> 开始抽奖
</button> </button>
<button class="btn btn-reset" id="resetBtn"> <button id="resetBtn">
<span>🔄</span> 编辑令牌 <span>🔄</span> 编辑令牌
</button> </button>
<div class="status-bar" id="statusBar"> <div class="status-bar" id="statusBar">
@ -333,12 +280,15 @@
<span id="statusText">就绪,点击按钮开始抽奖</span> <span id="statusText">就绪,点击按钮开始抽奖</span>
</div> </div>
</div> </div>
<!-- 页脚 -->
<div class="footer">
<a href="/">← 返回首页</a>
</div>
</div> </div>
<script> <script>
(function () { (function () {
// ============ DOM 元素 ============ // ============ DOM 元素 ============
const titleInput = document.getElementById('titleInput');
const rollingDisplay = document.getElementById('rollingDisplay'); const rollingDisplay = document.getElementById('rollingDisplay');
const resultContainer = document.getElementById('resultContainer'); const resultContainer = document.getElementById('resultContainer');
const lotteryBtn = document.getElementById('lotteryBtn'); const lotteryBtn = document.getElementById('lotteryBtn');
@ -420,19 +370,21 @@
const res = await request('middle/play/do/lottery', '&activityId=6501202&drawCount=1'); const res = await request('middle/play/do/lottery', '&activityId=6501202&drawCount=1');
// 根据你的后端返回格式提取中奖者名称 // 根据你的后端返回格式提取中奖者名称
const winner = res.data?.prizeDetailInfoMap?.['8186002']?.prizeName; const prizeMap = res.data?.prizeDetailInfoMap || {};
if (!winner) { for (var k in prizeMap) break; // 取第一个奖品 ID
const prize = k ? prizeMap[k] : null;
if (!prize) {
throw new Error('无法解析响应数据'); throw new Error('无法解析响应数据');
} }
// [抽奖结果, 剩余机会] // [抽奖结果, 剩余机会, 奖品图片]
return [winner, res.data.restChance]; return [prize.prizeName, res.data.restChance, prize.prizeImgList[0] || ''];
} }
// ============ 滚动动画 ============ // ============ 滚动动画 ============
function startRollingAnimation(finalWinner) { function startRollingAnimation(finalWinner, img) {
const placeholderNames = ['🎰', '🎲', '🎯', '✨', '💫', '🌟', '🎪', '🎭']; const placeholderNames = ['🎰', '🎲', '🎯', '✨', '💫', '🌟', '🎪', '🎭'];
const totalDuration = 2800; const totalDuration = 2000;
const startInterval = 50; const startInterval = 50;
const endInterval = 280; const endInterval = 280;
const startTime = Date.now(); const startTime = Date.now();
@ -454,7 +406,8 @@
// 动画结束,显示真实结果 // 动画结束,显示真实结果
clearTimeout(rollingTimer); clearTimeout(rollingTimer);
rollingDisplay.textContent = finalWinner; rollingDisplay.textContent = finalWinner;
showWinnerResult(finalWinner); rollingDisplay.style.display = 'none';
showWinnerResult(finalWinner, img);
finishDrawing(); finishDrawing();
return; return;
} }
@ -465,11 +418,12 @@
roll(); roll();
} }
function showWinnerResult(winner) { function showWinnerResult(winner, img) {
resultContainer.innerHTML = ` resultContainer.innerHTML = `
<div class="winner-display"> <div class="winner-display">
<div class="winner-label">🏆 恭喜中奖</div> <div class="winner-label">🏆 恭喜中奖</div>
<div class="winner-name">${escapeHTML(winner)}</div> <div class="winner-name">${escapeHTML(winner)}</div>
<img src="${img}" alt="奖品图片" class="winner-img" />
<div class="confetti-row"> <div class="confetti-row">
<span class="confetti-emoji">🎉</span> <span class="confetti-emoji">🎉</span>
<span class="confetti-emoji">🎊</span> <span class="confetti-emoji">🎊</span>
@ -503,6 +457,7 @@
resultContainer.innerHTML = ''; resultContainer.innerHTML = '';
rollingDisplay.className = 'rolling-display'; rollingDisplay.className = 'rolling-display';
rollingDisplay.textContent = '🎰'; rollingDisplay.textContent = '🎰';
rollingDisplay.style.display = 'flex';
setStatus('info', '正在获取抽奖结果...'); setStatus('info', '正在获取抽奖结果...');
try { try {
@ -510,7 +465,7 @@
const res = await fetchLotteryResult(); const res = await fetchLotteryResult();
setStatus('info', '抽奖成功,剩余次数 ' + res[1]); setStatus('info', '抽奖成功,剩余次数 ' + res[1]);
// 启动滚动动画,最终定格在中奖者 // 启动滚动动画,最终定格在中奖者
startRollingAnimation(res[0]); startRollingAnimation(res[0], res[2]);
} catch (error) { } catch (error) {
// 请求失败处理 // 请求失败处理
rollingDisplay.textContent = '抽奖失败'; rollingDisplay.textContent = '抽奖失败';
@ -528,7 +483,7 @@
// 空格键快捷抽奖 // 空格键快捷抽奖
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === ' ' && document.activeElement !== titleInput) { if (e.key === ' ') {
e.preventDefault(); e.preventDefault();
if (!isDrawing && !lotteryBtn.disabled) { if (!isDrawing && !lotteryBtn.disabled) {
startLottery(); startLottery();
@ -538,6 +493,12 @@
// ============ 初始化 ============ // ============ 初始化 ============
resetResult(); resetResult();
window.demo = function (name, chance, img) {
rollingDisplay.style.display = 'flex';
setStatus('info', '抽奖成功,剩余次数 ' + (chance || '0'));
startRollingAnimation(name || '演示', img || 'https://p5.music.126.net/obj/worDmcOCw6HCnzzCmsKx/61891591760/8d1c/528a/e977/10cbaa3fb39e8057be5d2560182cc5c7.png');
};
})(); })();
</script> </script>
</body> </body>

View File

@ -11,6 +11,7 @@
}, },
"APP_CONF": { "APP_CONF": {
"apiDomain": "https://interface.music.163.com", "apiDomain": "https://interface.music.163.com",
"eapiDomain": "https://interfacepc.music.163.com",
"xeapiDomain": "https://interface3.music.163.com", "xeapiDomain": "https://interface3.music.163.com",
"domain": "https://music.163.com", "domain": "https://music.163.com",
"clDomian": "https://clientlog.music.163.com", "clDomian": "https://clientlog.music.163.com",

View File

@ -112,7 +112,7 @@ const userAgentMap = {
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
}, },
api: { api: {
pc: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/3.0.18.203152', pc: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/3.1.29.205117',
android: android:
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)', 'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)', iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)',
@ -122,6 +122,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 EAPI_DOMAIN = APP_CONF.eapiDomain
const XEAPI_DOMAIN = APP_CONF.xeapiDomain 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])
@ -242,6 +243,9 @@ const createRequest = (uri, data, options) => {
headers['Referer'] = options.domain || DOMAIN headers['Referer'] = options.domain || DOMAIN
headers['User-Agent'] = options.ua || chooseUserAgent('weapi') headers['User-Agent'] = options.ua || chooseUserAgent('weapi')
data.csrf_token = csrfToken data.csrf_token = csrfToken
if (options.checkToken) {
headers['X-antiCheatToken'] = token
}
encryptData = encrypt.weapi(data) encryptData = encrypt.weapi(data)
url = (options.domain || DOMAIN) + '/weapi/' + uri.substr(5) url = (options.domain || DOMAIN) + '/weapi/' + uri.substr(5)
break break
@ -342,7 +346,7 @@ const createRequest = (uri, data, options) => {
data.header = header data.header = header
encryptData = encrypt.eapi(uri, data) encryptData = encrypt.eapi(uri, data)
url = (options.domain || API_DOMAIN) + '/eapi/' + uri.substr(5) url = (options.domain || EAPI_DOMAIN) + '/eapi/' + uri.substr(5)
} else if (crypto === 'api') { } else if (crypto === 'api') {
url = (options.domain || API_DOMAIN) + uri url = (options.domain || API_DOMAIN) + uri
encryptData = data encryptData = data