feat: 新增免费听接口

This commit is contained in:
MoeFurina
2026-07-15 16:14:19 +08:00
parent 6946dc8e14
commit 0a5a1242f2
10 changed files with 763 additions and 155 deletions
+43
View File
@@ -0,0 +1,43 @@
// 获取广告
// 基于逆向网易云音乐 v9.5.45 RN Hermes 源码:
// - adService.getAd() → Network.nativeRequest → /api/ad/get
// - 需要 X-antiCheatToken 易盾反作弊头
// - 使用 xeapi 加密
//
// 返回 extra.reqId 可用于 ad_listening_rights_gain 的 reqUid
//
const createOption = require('../util/option.js')
module.exports = async (query, request) => {
const data = {
type_ids: query.type_ids || '["400002_0"]',
}
const option = createOption(query, 'xeapi')
option.checkToken = true
const res = await request(`/api/ad/get`, data, option)
const raw = res.body
// 提取广告中的 req_id (用于领取权益)
let reqId = ''
try {
if (raw?.ads) {
const ad = Object.values(raw.ads)[0]
if (ad?.extJson) {
const ext = JSON.parse(ad.extJson)
reqId = ext?.contextInfo?.req_id || ''
}
}
} catch (_) {}
return {
status: 200,
body: {
code: 200,
ads: raw?.ads || null,
message: raw?.message || null,
extra: { reqId },
},
}
}
+88
View File
@@ -0,0 +1,88 @@
// 看广告免费听歌 - 领取免费听权益
// 请求流程(基于逆向网易云音乐 v9.5.45 RN Hermes 源码):
// 1. 从广告平台拉广告 → 用户看完/点击广告 → 获取 ad 对象的 extJson.contextInfo.req_id 作为 reqUid
// 2. 调用本接口传入 reqUid 及相关权益参数,领取免费听权益
// 3. 服务器返回 gainFlag 等标识,用于展示领取结果
const createOption = require('../util/option.js')
const adGet = require('./ad_get.js')
module.exports = async (query, request) => {
const time = Date.now()
// 如果未传入 reqUid,自动从 ad_get 获取最新广告的 req_id
let reqUid = query.reqUid || ''
if (!reqUid) {
try {
const adRes = await adGet(
{ ...query, type_ids: query.type_ids || '["400002_0"]' },
request,
)
reqUid = adRes?.body?.extra?.reqId || ''
console.log(`自动获取 reqUid: ${reqUid}`)
} catch (e) {
// 获取广告失败,后续请求会因缺少 reqUid 被拒绝
}
}
const rightsParam = {
// 必填: 广告请求 ID,自动从 ad_get 获取
reqUid,
// 广告创意类型 (默认 1)
creativeType: parseInt(query.creativeType || 2),
// 时间戳
exposureTime: query.exposureTime || time,
clickTime: query.clickTime || time,
// 权益领取方式
rightsGainMethod: parseInt(query.rightsGainMethod || 2),
// 权益时长相关
rightsGainDuration: query.rightsGainDuration
? parseInt(query.rightsGainDuration)
: undefined,
extraRightsGainMethod: query.extraRightsGainMethod
? parseInt(query.extraRightsGainMethod)
: undefined,
extraRightsGainDuration: query.extraRightsGainDuration
? parseInt(query.extraRightsGainDuration)
: undefined,
nextRightsGainDuration: query.nextRightsGainDuration
? parseInt(query.nextRightsGainDuration)
: undefined,
// 来源标识 | 权益扩展信息
source: query.source || undefined,
rightsExtJson: query.rightsExtJson || undefined,
// 应用信息(下载类广告)
appInfo: query.appInfo ? JSON.parse(query.appInfo) : undefined,
installed: query.installed ? parseInt(query.installed) : undefined,
}
// 清理 undefined 字段
Object.keys(rightsParam).forEach((key) => {
if (rightsParam[key] === undefined) delete rightsParam[key]
})
// 将参数序列化为 reqParam 字符串 (与 RN 源码完全一致)
const data = {
reqParam: JSON.stringify(rightsParam),
}
const option = createOption(query, 'xeapi')
// 关键: 开启 X-antiCheatToken 头
option.checkToken = true
const res = await request(`/api/ad/listening/rights/gain`, data, option)
return {
status: 200,
body: {
code: 200,
data: res.body,
},
}
}
+37
View File
@@ -0,0 +1,37 @@
// 易盾反作弊 Token 注册端点
// 调用后获取实时 token 并存入共享存储,供后续带 checkToken 的请求使用
//
// GET /register/checktoken → 返回当前 token(尚无则自动获取)
// POST /register/checktoken → 强制刷新 token
// GET /register/checktoken?refresh=1 → 强制刷新
//
const { default: axios } = require('axios')
const { APP_CONF } = require('../util/config.json')
const URL = APP_CONF.dunDomain + '/v3/b?pn=YD00000558929251'
let _token = ''
async function fetch() {
const res = await axios.get(URL, { timeout: 10000 })
const body = String(res.data)
const m = body.match(/null\(\[(\d+),\d+,\"([^\"]+)\"\]\)/)
if (m && m[1] === '200') return m[2]
throw new Error('易盾返回异常: ' + body.substring(0, 100))
}
// 端点处理
module.exports = async (query) => {
const refresh = query.refresh === '1' || query.refresh === 'true'
let token = refresh ? null : _token
if (!token) {
token = await fetch()
_token = token
}
return {
status: 200,
body: { code: 200, token, registered: !!token },
}
}
// 给 request.js 读取用
module.exports.getToken = () => _token