mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-20 14:41:23 +08:00
feat: add NCBL encrypted scrobble interface and update related documentation
- Implemented new scrobble endpoint for NCBL encrypted logs in `scrobble_v1.js`. - Updated `scrobble.js` to use domain from config. - Enhanced `scrobble.html` with tabbed interface for original and NCBL versions, including new input fields. - Added cookie handling and quick fill functionality for both versions. - Updated `config.json` to include new domain configurations. - Introduced `ncbl.js` for NCBL encryption utilities. - Improved documentation in `home.md` to reflect changes in API usage.
This commit is contained in:
+3
-1
@@ -1,6 +1,8 @@
|
||||
// 听歌打卡
|
||||
|
||||
const createOption = require('../util/option.js')
|
||||
const { APP_CONF } = require('../util/config.json')
|
||||
const DOMAIN = APP_CONF.clDomian
|
||||
module.exports = async (query, request) => {
|
||||
// 注入 os=osx 的 cookie
|
||||
let cookie = query.cookie || ''
|
||||
@@ -56,7 +58,7 @@ module.exports = async (query, request) => {
|
||||
}
|
||||
|
||||
const option = createOption(query, 'eapi')
|
||||
option.domain = 'https://clientlog.music.163.com'
|
||||
option.domain = DOMAIN
|
||||
|
||||
// 发送两次请求
|
||||
const res1 = await request(`/api/feedback/weblog`, startplayData, option)
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// 听歌打卡 - NCBL 加密版 (仿桌面客户端 PLV/PLD 上报)
|
||||
// 复制自 https://github.com/folltoshe/netease-report-listen-song 的 PC 端日志上报方式
|
||||
//
|
||||
// PLV 和 PLD 分两次独立上传
|
||||
|
||||
const {
|
||||
buildPlv,
|
||||
buildPld,
|
||||
buildRecords,
|
||||
extractContext,
|
||||
parseCookie,
|
||||
buildCookieStr,
|
||||
buildMetaJson,
|
||||
doUpload,
|
||||
} = require('../util/ncbl')
|
||||
|
||||
module.exports = async (query, request) => {
|
||||
// --- 参数校验 ---
|
||||
const songId = Number(query.id)
|
||||
if (!songId || isNaN(songId)) {
|
||||
return { status: 400, body: { code: 400, msg: '缺少有效的 id (歌曲ID)' } }
|
||||
}
|
||||
const playTime = Number(query.time)
|
||||
if (isNaN(playTime) || playTime <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: { code: 400, msg: '缺少有效的 time (播放时长)' },
|
||||
}
|
||||
}
|
||||
const totalTime = Number(query.total) || playTime
|
||||
const sourceId = String(query.sourceid || query.sourceId || '')
|
||||
const sourceName = query.source || 'list'
|
||||
|
||||
// --- 解析认证上下文 ---
|
||||
const rawCookie = query.cookie || ''
|
||||
const cookieObj = parseCookie(rawCookie)
|
||||
cookieObj.os = 'pc'
|
||||
const ctx = extractContext(cookieObj)
|
||||
|
||||
// 兜底取 token
|
||||
if (!ctx.auth.token && rawCookie) {
|
||||
const parsed =
|
||||
typeof rawCookie === 'string' ? parseCookie(rawCookie) : rawCookie
|
||||
ctx.auth.token = parsed.MUSIC_U || ''
|
||||
}
|
||||
|
||||
if (!ctx.auth.token) {
|
||||
return { status: 401, body: { code: 401, msg: '缺少 MUSIC_U 鉴权令牌' } }
|
||||
}
|
||||
|
||||
// --- 构建歌曲和来源 ---
|
||||
const song = {
|
||||
id: songId,
|
||||
name: query.name || '',
|
||||
artist: query.artist || '',
|
||||
bitrate: Number(query.bitrate) || 320,
|
||||
level: query.level || 'exhigh',
|
||||
vip: query.vip === 'true' || query.vip === true,
|
||||
time: totalTime,
|
||||
}
|
||||
const source = {
|
||||
id: sourceId || String(songId),
|
||||
type: 'track',
|
||||
name: sourceName,
|
||||
}
|
||||
|
||||
const metaJson = buildMetaJson(ctx)
|
||||
const cookieStr = buildCookieStr(ctx)
|
||||
|
||||
const ts = Math.floor(Date.now() / 1000)
|
||||
const played = Math.min(playTime, totalTime)
|
||||
|
||||
const plvBody = buildRecords([
|
||||
{ time: ts, action: '_plv', data: buildPlv(ctx, song, source) },
|
||||
])
|
||||
const pldBody = buildRecords([
|
||||
{ time: ts, action: '_pld', data: buildPld(ctx, song, source, played) },
|
||||
])
|
||||
|
||||
try {
|
||||
// 1) 上传 PLV
|
||||
const plv = await doUpload(ctx, metaJson, plvBody, cookieStr, 'PLV')
|
||||
if (!plv.success) {
|
||||
const rateMsg =
|
||||
plv.respBody?.data?.rate != null
|
||||
? ` (rate=${plv.respBody.data.rate})`
|
||||
: ''
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
code: plv.respBody?.code || -1,
|
||||
msg: `PLV 上报失败${rateMsg}`,
|
||||
details: plv.respBody,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 上传 PLD
|
||||
const pld = await doUpload(ctx, metaJson, pldBody, cookieStr, 'PLD')
|
||||
if (!pld.success) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
code: pld.respBody?.code || -1,
|
||||
msg: 'PLV 成功但 PLD 失败',
|
||||
details: { plv: plv.respBody, pld: pld.respBody },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
code: 200,
|
||||
data: 'scrobble_v1 上报成功',
|
||||
details: {
|
||||
plv: { fileName: plv.fileName, payloadSize: plv.payload.length },
|
||||
pld: { fileName: pld.fileName, payloadSize: pld.payload.length },
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 502,
|
||||
body: { code: 502, msg: `请求异常: ${err.message || err}` },
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user