mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-08-12 08:50:38 +00:00
Compare commits
7 Commits
5b780addba
...
7753b5de41
| Author | SHA1 | Date | |
|---|---|---|---|
| 7753b5de41 | |||
| c1fe1eb925 | |||
| 3357317a1d | |||
| a08f425ae7 | |||
| 5859944563 | |||
| aaa6459e55 | |||
| a898e1d86c |
17
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
17
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -14,15 +14,16 @@ body:
|
|||||||
id: terms
|
id: terms
|
||||||
attributes:
|
attributes:
|
||||||
label: 确认事项
|
label: 确认事项
|
||||||
description: 在提交功能请求前,请确认以下事项
|
description: |
|
||||||
|
在提交功能请求前,请确认以下事项
|
||||||
|
|
||||||
|
- 我已经搜索了现有的issues,确认这不是重复请求
|
||||||
|
- 我已经查看了项目文档
|
||||||
|
- 该项目符合该项目的目标和范围
|
||||||
|
- 我确认我提供的接口请求是合理且有意义的
|
||||||
|
|
||||||
options:
|
options:
|
||||||
- label: 我已经搜索了现有的issues,确认这不是重复请求
|
- label: 我已确认以上事项
|
||||||
required: true
|
|
||||||
- label: 我已经查看了项目文档
|
|
||||||
required: true
|
|
||||||
- label: 该项目符合该项目的目标和范围
|
|
||||||
required: true
|
|
||||||
- label: 我确认我提供的接口请求是合理且有意义的
|
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
31
interface.d.ts
vendored
31
interface.d.ts
vendored
@ -48,18 +48,26 @@ export function ad_listening_rights(
|
|||||||
export function ad_listening_rights_gain(
|
export function ad_listening_rights_gain(
|
||||||
params: {
|
params: {
|
||||||
reqUid?: string
|
reqUid?: string
|
||||||
creativeType?: string | number
|
uid?: string | number
|
||||||
exposureTime?: string | number
|
exposureTime?: string | number
|
||||||
clickTime?: string | number
|
clickTime?: string | number
|
||||||
|
extraRightsType?: string | number
|
||||||
|
playContinuously?: boolean | string
|
||||||
|
source?: string | number
|
||||||
|
creativeType?: string | number
|
||||||
rightsGainMethod?: string | number
|
rightsGainMethod?: string | number
|
||||||
rightsGainDuration?: string | number
|
|
||||||
extraRightsGainMethod?: string | number
|
extraRightsGainMethod?: string | number
|
||||||
extraRightsGainDuration?: string | number
|
extraRightsGainDuration?: string | number
|
||||||
nextRightsGainDuration?: string | number
|
nextRightsGainDuration?: string | number
|
||||||
source?: string
|
rightsGainType?: string | number
|
||||||
|
rightsGainDuration?: string | number
|
||||||
|
gainMethodStep?: string | number
|
||||||
|
generalRightsInfo?: string
|
||||||
rightsExtJson?: string
|
rightsExtJson?: string
|
||||||
appInfo?: string
|
appInfo?: string
|
||||||
|
contextInfo?: string
|
||||||
installed?: string | number
|
installed?: string | number
|
||||||
|
sniffTime?: string | number
|
||||||
type_ids?: string
|
type_ids?: string
|
||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
@ -1662,6 +1670,23 @@ export function yunbei_task_finish(
|
|||||||
} & RequestBaseConfig,
|
} & RequestBaseConfig,
|
||||||
): Promise<Response>
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function yunbei_ad_task_list(
|
||||||
|
params: RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function yunbei_ad_task_recommend_song(
|
||||||
|
params: {
|
||||||
|
offset?: number | string
|
||||||
|
limit?: number | string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
|
export function yunbei_ad_task_finish(
|
||||||
|
params: {
|
||||||
|
yunbeiAmount?: number | string
|
||||||
|
} & RequestBaseConfig,
|
||||||
|
): Promise<Response>
|
||||||
|
|
||||||
export function msg_recentcontact(params: RequestBaseConfig): Promise<Response>
|
export function msg_recentcontact(params: RequestBaseConfig): Promise<Response>
|
||||||
|
|
||||||
export function hug_comment(
|
export function hug_comment(
|
||||||
|
|||||||
@ -17,9 +17,24 @@ module.exports = async (query, request) => {
|
|||||||
try {
|
try {
|
||||||
if (raw?.ads) {
|
if (raw?.ads) {
|
||||||
const ad = Object.values(raw.ads)[0]
|
const ad = Object.values(raw.ads)[0]
|
||||||
if (ad?.extJson) {
|
// 逆向 v9.5.61:客户端从 ad.adExtMap["req_id"] 取 reqUid
|
||||||
const ext = JSON.parse(ad.extJson)
|
if (ad?.adExtMap) {
|
||||||
reqId = ext?.contextInfo?.req_id || ''
|
if (typeof ad.adExtMap === 'string') {
|
||||||
|
try {
|
||||||
|
reqId = JSON.parse(ad.adExtMap).req_id || ''
|
||||||
|
} catch (_) {}
|
||||||
|
} else {
|
||||||
|
reqId = ad.adExtMap.req_id || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 兜底:adLogId.requestId / ad.reqId / extJson.contextInfo.req_id
|
||||||
|
if (!reqId && ad?.adLogId?.requestId) reqId = ad.adLogId.requestId
|
||||||
|
if (!reqId && ad?.reqId) reqId = ad.reqId
|
||||||
|
if (!reqId && ad?.extJson) {
|
||||||
|
try {
|
||||||
|
const ext = JSON.parse(ad.extJson)
|
||||||
|
reqId = ext?.contextInfo?.req_id || ''
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|||||||
@ -1,65 +1,229 @@
|
|||||||
// 看广告免费听歌 - 领取免费听权益
|
// 看广告免费听歌 - 领取免费听权益
|
||||||
// 请求流程(基于逆向网易云音乐 v9.5.45 RN Hermes 源码):
|
// 请求流程(基于逆向网易云音乐 v9.5.61 原生 Kotlin 源码,classes5/18/19.dex):
|
||||||
// 1. 从广告平台拉广告 → 用户看完/点击广告 → 获取 ad 对象的 extJson.contextInfo.req_id 作为 reqUid
|
// 1. 从广告平台拉广告 → 用户看完/点击广告 → 获取 ad 对象的 extJson.contextInfo.req_id 作为 reqUid
|
||||||
// 2. 调用本接口传入 reqUid 及相关权益参数,领取免费听权益
|
// 2. 调用本接口传入 reqUid 及相关权益参数,领取权益
|
||||||
// 3. 服务器返回 gainFlag 等标识,用于展示领取结果
|
// 3. 服务器返回 gainFlag 等标识,用于展示领取结果
|
||||||
|
//
|
||||||
|
// 调用链(逆向还原):
|
||||||
|
// AdDSLIncentiveVideoRightsHelper.requestRightsGainInner
|
||||||
|
// → AdDSLUtils.requestRightGain(ad, exposeTime, clickTime, creativeType, cb)
|
||||||
|
// → 构造 ListeningRightRequestParams(21 字段)→ JSON.stringify
|
||||||
|
// → body = { "reqParam": "..." } → POST /api/ad/listening/rights/gain
|
||||||
|
// (注意:客户端 AdDSLIncentiveVideoRightsHelper 写死 creativeType=36)
|
||||||
|
//
|
||||||
|
// 接口不只领取听歌时长,还包含"看视频得云贝"等权益:
|
||||||
|
// rightsGainMethod=6 (LAXIN_EXPOSE_OR_DOWNLOAD 拉新曝光/下载分段权益) 时,
|
||||||
|
// 弹窗 AdLaxinSegmentedGuideDialog 展示 "获得X云贝"(2000 云贝等),
|
||||||
|
// 权益数值由广告下发的 AdLaxinSegmentedRightsGuidePopup.rightsValue 决定,
|
||||||
|
// extraRightsType 作为权益类型随本接口上传。
|
||||||
|
//
|
||||||
|
// rightsGainMethod 枚举(h80/a):
|
||||||
|
// 1=EXPOSE 曝光, 2=EXPOSE_CLICK 曝光+点击, 3=EXPOSE_DOWNLOAD 曝光+下载,
|
||||||
|
// 4=CLICK_STAY 点击+停留, 5=EXPOSURE_OR_CLICK_STAY 曝光或点击停留,
|
||||||
|
// 6=LAXIN_EXPOSE_OR_DOWNLOAD 拉新曝光/下载分段权益
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
const adGet = require('./ad_get.js')
|
const adGet = require('./ad_get.js')
|
||||||
|
|
||||||
|
// 安全 JSON 解析(字符串字段可能是 JSON 文本)
|
||||||
|
function safeParse(str, fallback) {
|
||||||
|
if (typeof str !== 'string') return fallback
|
||||||
|
try {
|
||||||
|
return JSON.parse(str)
|
||||||
|
} catch (_) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const time = Date.now()
|
const time = Date.now()
|
||||||
|
|
||||||
// 如果未传入 reqUid,自动从 ad_get 获取最新广告的 req_id
|
// 从广告对象自动补齐请求字段。
|
||||||
|
// 实测 v9.5.61 真实 API 返回(/ad/get)确认的字段路径:
|
||||||
|
// reqUid = ad.extJson.contextInfo.req_id(真实响应中唯一存在)
|
||||||
|
// contextInfo = ad.extJson.contextInfo(完整对象序列化)
|
||||||
|
// generalRightsInfo = ad.generalRightsInfo(字符串 JSON)
|
||||||
|
// creativeType = ad.creativeType
|
||||||
|
// rightsGainMethod / extraRightsType / rightsGainDuration / rightsGainType /
|
||||||
|
// extraRightsGainMethod / extraRightsGainDuration / nextRightsGainDuration /
|
||||||
|
// rightsExtJson / source / rightsUpperLimit / qualified
|
||||||
|
// = ad.generalRightsInfo(字符串)解析后取值
|
||||||
|
// (逆向类字段 adExtMap/adLogId/listeningRightHintInfo 真实响应中不存在,仅兜底)
|
||||||
|
// sniffTime = method==3 或 6 时 currentTimeMillis
|
||||||
let reqUid = query.reqUid || ''
|
let reqUid = query.reqUid || ''
|
||||||
if (!reqUid) {
|
let contextInfo = query.contextInfo
|
||||||
|
let creativeType = query.creativeType
|
||||||
|
let generalRightsInfo = query.generalRightsInfo
|
||||||
|
|
||||||
|
// 广告 hint 配置:仅当调用方未显式传参时用广告下发值兜底
|
||||||
|
const hint = {}
|
||||||
|
|
||||||
|
if (!reqUid || contextInfo === undefined || creativeType === undefined) {
|
||||||
try {
|
try {
|
||||||
const adRes = await adGet(
|
const adRes = await adGet(
|
||||||
{ ...query, type_ids: query.type_ids || '["400002_0"]' },
|
{ ...query, type_ids: query.type_ids || '["400002_0"]' },
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
reqUid = adRes?.body?.extra?.reqId || ''
|
const ad = Object.values(adRes?.body?.ads || {})[0]
|
||||||
|
if (!ad) throw new Error('ads 为空(未登录或广告位无广告)')
|
||||||
|
|
||||||
|
// reqUid:真实 API 返回中 req_id 在 ad.extJson.contextInfo.req_id(实测 v9.5.61)
|
||||||
|
// (逆向类字段 adExtMap/adLogId 在真实响应中不存在,仅作兜底)
|
||||||
|
if (!reqUid) {
|
||||||
|
const ext =
|
||||||
|
typeof ad?.extJson === 'string'
|
||||||
|
? safeParse(ad.extJson, {})
|
||||||
|
: ad?.extJson || {}
|
||||||
|
const extMap =
|
||||||
|
typeof ad?.adExtMap === 'string'
|
||||||
|
? safeParse(ad.adExtMap, {})
|
||||||
|
: ad?.adExtMap
|
||||||
|
reqUid =
|
||||||
|
ext?.contextInfo?.req_id ||
|
||||||
|
extMap?.req_id ||
|
||||||
|
ad?.adLogId?.requestId ||
|
||||||
|
ad?.reqId ||
|
||||||
|
adRes?.body?.extra?.reqId ||
|
||||||
|
''
|
||||||
|
}
|
||||||
|
// contextInfo:真实 API 返回中在 ad.extJson.contextInfo(实测 v9.5.61)
|
||||||
|
if (contextInfo === undefined) {
|
||||||
|
const ext =
|
||||||
|
typeof ad?.extJson === 'string'
|
||||||
|
? safeParse(ad.extJson, {})
|
||||||
|
: ad?.extJson || {}
|
||||||
|
const ci =
|
||||||
|
ext?.contextInfo || ad?.adLogId?.contextInfo || ad?.showContext
|
||||||
|
if (ci) {
|
||||||
|
contextInfo = typeof ci === 'string' ? ci : JSON.stringify(ci)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (creativeType === undefined && ad?.creativeType !== undefined) {
|
||||||
|
creativeType = ad.creativeType
|
||||||
|
}
|
||||||
|
if (generalRightsInfo === undefined && ad?.generalRightsInfo) {
|
||||||
|
generalRightsInfo =
|
||||||
|
typeof ad.generalRightsInfo === 'string'
|
||||||
|
? ad.generalRightsInfo
|
||||||
|
: JSON.stringify(ad.generalRightsInfo)
|
||||||
|
}
|
||||||
|
// 缓存广告下发的领取配置(后续用于兜底)
|
||||||
|
// 实测 v9.5.61:rightsGainMethod/rightsUpperLimit/qualified 等在
|
||||||
|
// ad.generalRightsInfo(字符串)里,ad.listeningRightHintInfo 真实响应中不存在
|
||||||
|
const hintCfg =
|
||||||
|
typeof ad?.listeningRightHintInfo === 'string'
|
||||||
|
? safeParse(ad.listeningRightHintInfo, {})
|
||||||
|
: ad?.listeningRightHintInfo || {}
|
||||||
|
const gri =
|
||||||
|
typeof ad?.generalRightsInfo === 'string'
|
||||||
|
? safeParse(ad.generalRightsInfo, {})
|
||||||
|
: ad?.generalRightsInfo || {}
|
||||||
|
Object.assign(hint, hintCfg, gri)
|
||||||
console.log(`自动获取 reqUid: ${reqUid}`)
|
console.log(`自动获取 reqUid: ${reqUid}`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 获取广告失败,后续请求会因缺少 reqUid 被拒绝
|
// 获取广告失败,后续请求会因缺少 reqUid 被拒绝
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rightsGainMethod = query.rightsGainMethod
|
||||||
|
? parseInt(query.rightsGainMethod)
|
||||||
|
: hint.rightsGainMethod || 2
|
||||||
|
|
||||||
const rightsParam = {
|
const rightsParam = {
|
||||||
// 必填: 广告请求 ID,自动从 ad_get 获取
|
// 必填: 广告请求 ID,自动从 ad_get 获取
|
||||||
reqUid,
|
reqUid,
|
||||||
|
|
||||||
// 广告创意类型 (默认 1)
|
// 曝光时间戳
|
||||||
creativeType: parseInt(query.creativeType || 2),
|
exposureTime: query.exposureTime ? parseInt(query.exposureTime) : time,
|
||||||
|
|
||||||
// 时间戳
|
// 当前登录用户 ID(原版客户端从 Profile.getUserId() 获取)
|
||||||
exposureTime: query.exposureTime || time,
|
userId: query.uid ? parseInt(query.uid) : undefined,
|
||||||
clickTime: query.clickTime || time,
|
|
||||||
|
|
||||||
// 权益领取方式
|
// 点击时间戳
|
||||||
rightsGainMethod: parseInt(query.rightsGainMethod || 2),
|
clickTime: query.clickTime ? parseInt(query.clickTime) : time,
|
||||||
|
|
||||||
// 权益时长相关
|
// 额外权益类型(拉新分段权益等,决定发放云贝/时长/下载,来自广告配置)
|
||||||
rightsGainDuration: query.rightsGainDuration
|
extraRightsType: query.extraRightsType
|
||||||
? parseInt(query.rightsGainDuration)
|
? parseInt(query.extraRightsType)
|
||||||
: undefined,
|
: hint.extraRightsType !== undefined
|
||||||
|
? parseInt(hint.extraRightsType)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
// 是否连续播放(默认 false)
|
||||||
|
playContinuously: query.playContinuously ? true : false,
|
||||||
|
|
||||||
|
// 来源标识(原版从 ad.listeningRightHintInfo.source 读取)
|
||||||
|
source: query.source
|
||||||
|
? parseInt(query.source)
|
||||||
|
: hint.source !== undefined
|
||||||
|
? parseInt(hint.source)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
// 广告创意类型(激励视频场景=36,优先取 query / 广告对象)
|
||||||
|
creativeType: creativeType !== undefined ? parseInt(creativeType) : 36,
|
||||||
|
|
||||||
|
// 权益领取方式(1~6 枚举,见文件头注释)
|
||||||
|
rightsGainMethod,
|
||||||
|
|
||||||
|
// 权益扩展方式与时长
|
||||||
extraRightsGainMethod: query.extraRightsGainMethod
|
extraRightsGainMethod: query.extraRightsGainMethod
|
||||||
? parseInt(query.extraRightsGainMethod)
|
? parseInt(query.extraRightsGainMethod)
|
||||||
: undefined,
|
: hint.extraRightsGainMethod !== undefined
|
||||||
|
? parseInt(hint.extraRightsGainMethod)
|
||||||
|
: undefined,
|
||||||
extraRightsGainDuration: query.extraRightsGainDuration
|
extraRightsGainDuration: query.extraRightsGainDuration
|
||||||
? parseInt(query.extraRightsGainDuration)
|
? parseInt(query.extraRightsGainDuration)
|
||||||
: undefined,
|
: hint.extraRightsGainDuration !== undefined
|
||||||
|
? parseInt(hint.extraRightsGainDuration)
|
||||||
|
: undefined,
|
||||||
nextRightsGainDuration: query.nextRightsGainDuration
|
nextRightsGainDuration: query.nextRightsGainDuration
|
||||||
? parseInt(query.nextRightsGainDuration)
|
? parseInt(query.nextRightsGainDuration)
|
||||||
|
: hint.nextRightsGainDuration !== undefined
|
||||||
|
? parseInt(hint.nextRightsGainDuration)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
// 权益类型(含 RIGHTS_GAIN_TYPE_CURRENT_DAY 等取值)
|
||||||
|
rightsGainType: query.rightsGainType
|
||||||
|
? parseInt(query.rightsGainType)
|
||||||
|
: hint.rightsGainType !== undefined
|
||||||
|
? parseInt(hint.rightsGainType)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
// 权益时长
|
||||||
|
rightsGainDuration: query.rightsGainDuration
|
||||||
|
? parseInt(query.rightsGainDuration)
|
||||||
|
: hint.rightsGainDuration !== undefined
|
||||||
|
? parseInt(hint.rightsGainDuration)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
// 领取步骤(UNGAIN/GAINING/GAIN_FINISHED 对应的值)
|
||||||
|
gainMethodStep: query.gainMethodStep
|
||||||
|
? parseInt(query.gainMethodStep)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
// 来源标识 | 权益扩展信息
|
// 通用权益信息(ad.generalRightsInfo 序列化 JSON)
|
||||||
source: query.source || undefined,
|
generalRightsInfo,
|
||||||
rightsExtJson: query.rightsExtJson || undefined,
|
|
||||||
|
// 权益扩展信息
|
||||||
|
rightsExtJson: query.rightsExtJson || hint.rightsExtJson || undefined,
|
||||||
|
|
||||||
// 应用信息(下载类广告)
|
// 应用信息(下载类广告)
|
||||||
appInfo: query.appInfo ? JSON.parse(query.appInfo) : undefined,
|
appInfo: query.appInfo ? JSON.parse(query.appInfo) : undefined,
|
||||||
|
|
||||||
|
// 广告上下文(ad.adLogId.contextInfo,客户端真实来源)
|
||||||
|
contextInfo,
|
||||||
|
|
||||||
|
// 应用是否已安装(下载类广告)
|
||||||
installed: query.installed ? parseInt(query.installed) : undefined,
|
installed: query.installed ? parseInt(query.installed) : undefined,
|
||||||
|
|
||||||
|
// 嗅探时间:逆向确认(classes5.dex AdDSLUtils.requestRightGain):
|
||||||
|
// rightsGainMethod==3(曝光+下载) 或 6(LAXIN) 时传 currentTimeMillis
|
||||||
|
sniffTime:
|
||||||
|
rightsGainMethod === 3 || rightsGainMethod === 6
|
||||||
|
? query.sniffTime
|
||||||
|
? parseInt(query.sniffTime)
|
||||||
|
: time
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理 undefined 字段
|
// 清理 undefined 字段
|
||||||
@ -67,7 +231,7 @@ module.exports = async (query, request) => {
|
|||||||
if (rightsParam[key] === undefined) delete rightsParam[key]
|
if (rightsParam[key] === undefined) delete rightsParam[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
// 将参数序列化为 reqParam 字符串 (与 RN 源码完全一致)
|
// 将参数序列化为 reqParam 字符串 (与原生源码一致)
|
||||||
const data = {
|
const data = {
|
||||||
reqParam: JSON.stringify(rightsParam),
|
reqParam: JSON.stringify(rightsParam),
|
||||||
}
|
}
|
||||||
@ -75,7 +239,7 @@ module.exports = async (query, request) => {
|
|||||||
const res = await request(
|
const res = await request(
|
||||||
`/api/ad/listening/rights/gain`,
|
`/api/ad/listening/rights/gain`,
|
||||||
data,
|
data,
|
||||||
createOption(query, 'xeapi', 'v3'),
|
createOption(query, 'xeapi', 'v2'),
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
// 易盾反作弊 Token 注册端点
|
// 易盾反作弊 Token 注册端点
|
||||||
// 调用后获取实时 token 并存入共享存储,供后续带 checkToken 的请求使用
|
// 调用后获取实时 token 并存入共享存储,供后续带 checkToken 的请求使用
|
||||||
//
|
//
|
||||||
// GET /register/checktoken → 返回当前 token(尚无则自动获取)
|
// GET /register/checktoken → 实时获取新 token(不缓存)
|
||||||
// POST /register/checktoken → 强制刷新 token
|
// POST /register/checktoken → 实时获取新 token
|
||||||
// GET /register/checktoken?refresh=1 → 强制刷新
|
//
|
||||||
|
// 注意:每次获取都不缓存,模拟真实客户端每次请求使用新鲜 token,
|
||||||
|
// 避免反作弊 token 复用触发风控。
|
||||||
//
|
//
|
||||||
const { default: axios } = require('axios')
|
const { default: axios } = require('axios')
|
||||||
const { APP_CONF } = require('../util/config.json')
|
const { APP_CONF } = require('../util/config.json')
|
||||||
|
|
||||||
const URL = APP_CONF.dunDomainV2 + '/v2/config/js?pn=YD00000558929251'
|
const URL = APP_CONF.dunDomainV2 + '/v2/config/js?pn=YD00000558929251'
|
||||||
let _token = ''
|
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const res = await axios.get(URL, { timeout: 10000 })
|
const res = await axios.get(URL, { timeout: 10000 })
|
||||||
@ -20,13 +21,13 @@ async function fetch() {
|
|||||||
throw new Error('易盾返回异常: ' + JSON.stringify(data).substring(0, 200))
|
throw new Error('易盾返回异常: ' + JSON.stringify(data).substring(0, 200))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 端点处理
|
// 端点处理:每次实时获取新 token
|
||||||
module.exports = async (query) => {
|
module.exports = async () => {
|
||||||
const refresh = query.refresh === '1' || query.refresh === 'true'
|
let token = ''
|
||||||
let token = refresh ? null : _token
|
try {
|
||||||
if (!token) {
|
|
||||||
token = await fetch()
|
token = await fetch()
|
||||||
_token = token
|
} catch (e) {
|
||||||
|
// token 获取失败时返回空,由调用方决定是否重试
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -34,24 +35,11 @@ module.exports = async (query) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模块加载时自动获取一次 Token,确保 request.js 使用时不为空
|
// 给 request.js 读取用:每次调用实时获取新 token,不缓存
|
||||||
fetch()
|
module.exports.getToken = async () => {
|
||||||
.then((token) => {
|
try {
|
||||||
_token = token
|
return await fetch()
|
||||||
})
|
} catch (e) {
|
||||||
.catch(() => {
|
return ''
|
||||||
// 静默失败,后续请求时会重试
|
|
||||||
})
|
|
||||||
|
|
||||||
// 给 request.js 读取用
|
|
||||||
module.exports.getToken = () => {
|
|
||||||
if (!_token) {
|
|
||||||
// 如果 Token 为空,异步触发获取
|
|
||||||
fetch()
|
|
||||||
.then((token) => {
|
|
||||||
_token = token
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
}
|
||||||
return _token
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
// 易盾反作弊 Token 注册端点
|
// 易盾反作弊 Token 注册端点
|
||||||
// 调用后获取实时 token 并存入共享存储,供后续带 checkToken 的请求使用
|
// 调用后获取实时 token 并存入共享存储,供后续带 checkToken 的请求使用
|
||||||
//
|
//
|
||||||
// GET /register/checktoken → 返回当前 token(尚无则自动获取)
|
// GET /register/checktoken/v3 → 实时获取新 token(不缓存)
|
||||||
// POST /register/checktoken → 强制刷新 token
|
// POST /register/checktoken/v3 → 实时获取新 token
|
||||||
// GET /register/checktoken?refresh=1 → 强制刷新
|
//
|
||||||
|
// 注意:每次获取都不缓存,模拟真实客户端每次请求使用新鲜 token,
|
||||||
|
// 避免反作弊 token 复用触发风控。
|
||||||
//
|
//
|
||||||
const { default: axios } = require('axios')
|
const { default: axios } = require('axios')
|
||||||
const { APP_CONF } = require('../util/config.json')
|
const { APP_CONF } = require('../util/config.json')
|
||||||
|
|
||||||
const URL = APP_CONF.dunDomainV3 + '/v3/b?pn=YD00000558929251'
|
const URL = APP_CONF.dunDomainV3 + '/v3/b?pn=YD00000558929251'
|
||||||
let _token = ''
|
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const res = await axios.get(URL, { timeout: 10000 })
|
const res = await axios.get(URL, { timeout: 10000 })
|
||||||
@ -19,13 +20,13 @@ async function fetch() {
|
|||||||
throw new Error('易盾返回异常: ' + body.substring(0, 100))
|
throw new Error('易盾返回异常: ' + body.substring(0, 100))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 端点处理
|
// 端点处理:每次实时获取新 token
|
||||||
module.exports = async (query) => {
|
module.exports = async () => {
|
||||||
const refresh = query.refresh === '1' || query.refresh === 'true'
|
let token = ''
|
||||||
let token = refresh ? null : _token
|
try {
|
||||||
if (!token) {
|
|
||||||
token = await fetch()
|
token = await fetch()
|
||||||
_token = token
|
} catch (e) {
|
||||||
|
// token 获取失败时返回空,由调用方决定是否重试
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -33,5 +34,11 @@ module.exports = async (query) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 给 request.js 读取用
|
// 给 request.js 读取用:每次调用实时获取新 token,不缓存
|
||||||
module.exports.getToken = () => _token
|
module.exports.getToken = async () => {
|
||||||
|
try {
|
||||||
|
return await fetch()
|
||||||
|
} catch (e) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ module.exports = async (query, request) => {
|
|||||||
const currentKeyVersion = query.currentKeyVersion || ''
|
const currentKeyVersion = query.currentKeyVersion || ''
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
appVersion: '9.1.65',
|
appVersion: '9.5.61',
|
||||||
currentKeyVersion,
|
currentKeyVersion,
|
||||||
deviceId,
|
deviceId,
|
||||||
nonce,
|
nonce,
|
||||||
@ -35,7 +35,7 @@ module.exports = async (query, request) => {
|
|||||||
url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get',
|
url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get',
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent':
|
'User-Agent':
|
||||||
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
'NeteaseMusic/9.5.61.260802021928(9005061);Dalvik/2.1.0 (Linux; U; Android 12; HBN-AL00 Build/cd737a2.0)',
|
||||||
Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '',
|
Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '',
|
||||||
},
|
},
|
||||||
data: new URLSearchParams(data).toString(),
|
data: new URLSearchParams(data).toString(),
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {}
|
const data = {}
|
||||||
return request(`/api/pointmall/user/sign`, data, createOption(query, 'weapi'))
|
return request(
|
||||||
|
`/api/pointmall/user/sign`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'xeapi', 'v3'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
26
module/yunbei_task_finish_v1.js
Normal file
26
module/yunbei_task_finish_v1.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 云贝广告任务 - 完成任务领取云贝
|
||||||
|
// 逆向来源: 云贝任务中心 H5 (st.music.163.com/yunbei-listen) main.js
|
||||||
|
// POST /api/ad/power/yunbei/distribution/create
|
||||||
|
// 参数: yunbeiAmount (单次可得云贝, 客户端从 list 接口的 singleAmount 取值, 当前为 150)
|
||||||
|
// 返回: true (领取成功)
|
||||||
|
//
|
||||||
|
// 实测验证 (2026-08-05):
|
||||||
|
// - 仅传 yunbeiAmount 即可成功领取, 无需真实听歌/看视频
|
||||||
|
// - 单日上限 10 次 × 150 云贝 = 1500 云贝/天
|
||||||
|
// - 超限返回 code:400 "单日完成任务数已达上限"
|
||||||
|
// - 无频率限制, 800ms 间隔连续调用均成功
|
||||||
|
//
|
||||||
|
// 建议: 领取前先调 yunbei_ad_task_list 查询 times, 达到 10 次即停止
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
yunbeiAmount: query.yunbeiAmount || 150,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/ad/power/yunbei/distribution/create`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
16
module/yunbei_task_list_v1.js
Normal file
16
module/yunbei_task_list_v1.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 云贝广告任务 - 查询今日任务状态
|
||||||
|
// 逆向来源: 云贝任务中心 H5 (st.music.163.com/yunbei-listen) main.js
|
||||||
|
// GET /api/ad/power/yunbei/distribution/list
|
||||||
|
// 返回: { times: 今日已完成次数, amount: 今日累计云贝, singleAmount: 单次可得云贝 }
|
||||||
|
// 注意: 单日上限 10 次, 单次 150 云贝 (每天最多 1500)
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {}
|
||||||
|
return request(
|
||||||
|
`/api/ad/power/yunbei/distribution/list`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
20
module/yunbei_task_recommend_song.js
Normal file
20
module/yunbei_task_recommend_song.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 云贝广告任务 - 获取推荐歌曲
|
||||||
|
// 逆向来源: 云贝任务中心 H5 (st.music.163.com/yunbei-listen) main.js
|
||||||
|
// POST /api/ad/power/yunbei/distribution/recommend/song
|
||||||
|
// 参数: offset (默认 0), limit (默认 10, 客户端一次 10 首)
|
||||||
|
// 返回: 推荐歌曲数组 [{ songId, songName, artistName, albumUrl, songChorusStartTime, likeFlag, alg }]
|
||||||
|
// 注意: alg 均为 alg_payrec_yunBei_*, 为"听歌得云贝"任务专属推荐
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
offset: query.offset || 0,
|
||||||
|
limit: query.limit || 10,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
`/api/ad/power/yunbei/distribution/recommend/song`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'weapi'),
|
||||||
|
)
|
||||||
|
}
|
||||||
48
module_example/yunbei_ad_task.js
Normal file
48
module_example/yunbei_ad_task.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const {
|
||||||
|
login_cellphone,
|
||||||
|
yunbei_ad_task_list,
|
||||||
|
yunbei_ad_task_recommend_song,
|
||||||
|
yunbei_ad_task_finish,
|
||||||
|
} = require('../main')
|
||||||
|
|
||||||
|
// 云贝广告任务(听歌/看视频得云贝)完整流程示例
|
||||||
|
// 1. list - 查询今日任务状态(次数/云贝)
|
||||||
|
// 2. finish - 领取云贝, 仅需传 yunbeiAmount(单次 150), 无需真实听歌/看视频
|
||||||
|
// 3. recommend/song - 可选, 获取推荐歌曲列表
|
||||||
|
// 注意: 单日上限 10 次 x 150 = 1500 云贝/天
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const login = await login_cellphone({
|
||||||
|
phone: '手机号',
|
||||||
|
password: '密码',
|
||||||
|
})
|
||||||
|
const cookie = login.body.cookie
|
||||||
|
|
||||||
|
// 1. 查询今日任务状态
|
||||||
|
const list = await yunbei_ad_task_list({ cookie })
|
||||||
|
const { times, amount, singleAmount } = list.body
|
||||||
|
console.log(
|
||||||
|
`今日已完成 ${times} 次, 累计 ${amount} 云贝, 单次可得 ${singleAmount} 云贝`,
|
||||||
|
)
|
||||||
|
if (times >= 10) {
|
||||||
|
console.log('已达单日上限, 明天再来吧')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 领取云贝(单次 150)
|
||||||
|
const finish = await yunbei_ad_task_finish({
|
||||||
|
yunbeiAmount: singleAmount,
|
||||||
|
cookie,
|
||||||
|
})
|
||||||
|
console.log('领取结果:', finish.body)
|
||||||
|
|
||||||
|
// 3. 获取推荐歌曲(听歌任务专属推荐, 可选)
|
||||||
|
const rcmd = await yunbei_ad_task_recommend_song({
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
cookie,
|
||||||
|
})
|
||||||
|
console.log('推荐歌曲:', rcmd.body.map((s) => s.songName).join(', '))
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@ -1264,7 +1264,6 @@ tags: 歌单标签
|
|||||||
|
|
||||||
> 如果你设置 limit=50&offset=100,你就会得到第 101-150 首歌曲
|
> 如果你设置 limit=50&offset=100,你就会得到第 101-150 首歌曲
|
||||||
|
|
||||||
|
|
||||||
### 歌单详情动态
|
### 歌单详情动态
|
||||||
|
|
||||||
说明 : 调用后可获取歌单详情动态部分,如评论数,是否收藏,播放数
|
说明 : 调用后可获取歌单详情动态部分,如评论数,是否收藏,播放数
|
||||||
@ -3626,6 +3625,36 @@ type='1009' 获取其 id, 如`/search?keywords= 代码时间 &type=1009`
|
|||||||
|
|
||||||
**调用例子 :** `/yunbei/task/finish?userTaskId=5146243240&depositCode=0`
|
**调用例子 :** `/yunbei/task/finish?userTaskId=5146243240&depositCode=0`
|
||||||
|
|
||||||
|
### 云贝广告任务 - 今日任务状态
|
||||||
|
|
||||||
|
说明 :登录后调用此接口可查询云贝广告任务("听歌/看视频得云贝")今日状态。逆向自云贝任务中心 H5 页面(st.music.163.com/yunbei-listen)。返回 `times`(今日已完成次数)、`amount`(今日累计云贝)、`singleAmount`(单次可得云贝)。单日上限 10 次。
|
||||||
|
|
||||||
|
**接口地址 :** `/yunbei/task/list/v1`
|
||||||
|
|
||||||
|
**调用例子 :** `/yunbei/task/list/v1`
|
||||||
|
|
||||||
|
### 云贝广告任务 - 获取推荐歌曲
|
||||||
|
|
||||||
|
说明 :登录后调用此接口可获取云贝广告任务的推荐歌曲列表。返回数组项含 `songId`、`songName`、`artistName`、`albumUrl`、`songChorusStartTime`、`likeFlag`、`alg`(均为 `alg_payrec_yunBei_*`)。
|
||||||
|
|
||||||
|
**可选参数 :** `offset`: 偏移数量,默认为 0
|
||||||
|
|
||||||
|
`limit`: 取出数量,默认为 10(客户端每次固定取 10 首)
|
||||||
|
|
||||||
|
**接口地址 :** `/yunbei/task/recommend/song`
|
||||||
|
|
||||||
|
**调用例子 :** `/yunbei/task/recommend/song` `/yunbei/task/recommend/song?offset=0&limit=10`
|
||||||
|
|
||||||
|
### 云贝广告任务 - 完成任务领取云贝
|
||||||
|
|
||||||
|
说明 :登录后调用此接口可完成任务并领取云贝。实测仅需传 `yunbeiAmount`(单次云贝数,客户端从 `list` 接口的 `singleAmount` 取值,当前为 150)即可成功领取,无需真实听歌/看视频。单日上限 10 次 × 150 = 1500 云贝/天,超限返回 `code:400 "单日完成任务数已达上限"`。建议领取前先调用 `/yunbei/task/list/v1` 查询今日剩余次数。
|
||||||
|
|
||||||
|
**可选参数 :** `yunbeiAmount`: 单次云贝数,默认为 150
|
||||||
|
|
||||||
|
**接口地址 :** `/yunbei/task/finish/v1`
|
||||||
|
|
||||||
|
**调用例子 :** `/yunbei/task/finish/v1?yunbeiAmount=150`
|
||||||
|
|
||||||
### 云贝收入
|
### 云贝收入
|
||||||
|
|
||||||
说明 :登录后调用此接口可获取云贝收入
|
说明 :登录后调用此接口可获取云贝收入
|
||||||
@ -5194,7 +5223,6 @@ let data = encodeURIComponent(
|
|||||||
|
|
||||||
**调用例子:** `/broadcast/sub?id=5&t=1`
|
**调用例子:** `/broadcast/sub?id=5&t=1`
|
||||||
|
|
||||||
|
|
||||||
### 用户的创建歌单列表
|
### 用户的创建歌单列表
|
||||||
|
|
||||||
说明 : 调用此接口, 传入用户id, 获取用户的创建歌单列表
|
说明 : 调用此接口, 传入用户id, 获取用户的创建歌单列表
|
||||||
@ -5269,7 +5297,6 @@ let data = encodeURIComponent(
|
|||||||
|
|
||||||
**调用例子 :** `/voicelist/my/created`
|
**调用例子 :** `/voicelist/my/created`
|
||||||
|
|
||||||
|
|
||||||
### DIFM电台 - 分类
|
### DIFM电台 - 分类
|
||||||
|
|
||||||
说明: 调用此接口, 获取DIFM电台分类
|
说明: 调用此接口, 获取DIFM电台分类
|
||||||
@ -5448,7 +5475,7 @@ let data = encodeURIComponent(
|
|||||||
|
|
||||||
**接口地址 :** `/comment/report`
|
**接口地址 :** `/comment/report`
|
||||||
|
|
||||||
**调用例子 :* `/comment/report?id=2058263032&cid=123456789&reason=人身攻击`
|
*_调用例子 :_ `/comment/report?id=2058263032&cid=123456789&reason=人身攻击`
|
||||||
|
|
||||||
### 多级行政区划数据
|
### 多级行政区划数据
|
||||||
|
|
||||||
@ -5538,13 +5565,23 @@ let data = encodeURIComponent(
|
|||||||
|
|
||||||
**调用例子 :** `/ad/get`
|
**调用例子 :** `/ad/get`
|
||||||
|
|
||||||
### 获取30分钟免费听歌时长
|
### 看广告领取权益(免费听歌时长 / 云贝等)
|
||||||
|
|
||||||
说明 : 登录后调用此接口, 获取30分钟免费听歌时长
|
说明 : 登录后调用此接口, 领取广告权益。权益类型由广告平台下发的配置决定, 不仅限于 30 分钟免费听歌时长, 还包括"看视频获得最高 2000 云贝"等拉新分段权益(`rightsGainMethod=6`)。除下方常用参数外, 权益类型/时长/扩展权益等其余字段会自动从广告下发配置补齐, 无需手动传入。
|
||||||
|
|
||||||
!> 警告: 通过调取接口出现的任何问题由调用者自行承担
|
!> 警告: 通过调取接口出现的任何问题由调用者自行承担
|
||||||
|
|
||||||
**可选参数 :** `reqUid` 通过`/ad/get` 获取的广告ID
|
**可选参数 :**
|
||||||
|
|
||||||
|
`reqUid` : 广告请求 ID, 通过 `/ad/get` 获取, 未传时自动获取
|
||||||
|
|
||||||
|
`uid` : 当前登录用户 ID, 不传时服务端从 Cookie 识别
|
||||||
|
|
||||||
|
`rightsGainMethod` : 权益领取方式, `1`: 曝光, `2`: 曝光+点击(默认), `3`: 曝光+下载, `4`: 点击+停留, `5`: 曝光或点击停留, `6`: 拉新曝光/下载分段权益(看视频得云贝)
|
||||||
|
|
||||||
|
`type_ids` : 广告位类型, 默认 `["400002_0"]`
|
||||||
|
|
||||||
|
`creativeType` : 广告创意类型, 激励视频场景为 `36`, 默认 `36`
|
||||||
|
|
||||||
**接口地址 :** `/ad/listening/rights/gain`
|
**接口地址 :** `/ad/listening/rights/gain`
|
||||||
|
|
||||||
|
|||||||
@ -99,6 +99,7 @@ curl -s {origin}/search?keywords=网易云</code></pre>
|
|||||||
<a href="/scrobble.html">听歌打卡示例</a> ·
|
<a href="/scrobble.html">听歌打卡示例</a> ·
|
||||||
<a href="/yidun.html">获取 CheckToken</a> ·
|
<a href="/yidun.html">获取 CheckToken</a> ·
|
||||||
<a href="/free_listen.html">免费听权益</a> ·
|
<a href="/free_listen.html">免费听权益</a> ·
|
||||||
|
<a href="/yunbei_task.html">云贝广告任务</a> ·
|
||||||
<a href="/ugc.html">云小编任务中心</a> ·
|
<a href="/ugc.html">云小编任务中心</a> ·
|
||||||
<a href="/ugc_lottery.html">云小编每日抽奖</a>
|
<a href="/ugc_lottery.html">云小编每日抽奖</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
622
public/yunbei_task.html
Normal file
622
public/yunbei_task.html
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>云贝广告任务 — 网易云音乐 API Enhanced</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--fg: #333;
|
||||||
|
--muted: #666;
|
||||||
|
--border: #ddd;
|
||||||
|
--bg: #f5f5f5;
|
||||||
|
--panel: #ffffff;
|
||||||
|
--accent: #333;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
color: var(--fg);
|
||||||
|
background: var(--bg);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.container {
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
header.site-header h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.block {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header.site-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
header.site-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.sub {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.block {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.block h3 {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.block .desc {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
pre.data {
|
||||||
|
max-height: 360px;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
button.danger {
|
||||||
|
background: #a11;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #a11;
|
||||||
|
}
|
||||||
|
button.danger:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.tag.ok {
|
||||||
|
background: #e6f7e6;
|
||||||
|
color: #1a7a1a;
|
||||||
|
border-color: #b7e6b7;
|
||||||
|
}
|
||||||
|
.tag.no {
|
||||||
|
background: #ffe6e6;
|
||||||
|
color: #a11;
|
||||||
|
border-color: #f5c6c6;
|
||||||
|
}
|
||||||
|
.tag.busy {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
border-color: #ffeeba;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.flex.sb {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.step {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.step .num {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--panel);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
.step .num.done {
|
||||||
|
background: #1a7a1a;
|
||||||
|
}
|
||||||
|
.step .body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 80px 1fr;
|
||||||
|
gap: 4px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.info-grid .lbl {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
footer.site-footer {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
color: var(--muted);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
footer.site-footer a {
|
||||||
|
color: var(--fg);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
footer.site-footer a:hover {
|
||||||
|
border-bottom: 1px dotted var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 今日进度条 ---- */
|
||||||
|
.progress-wrap {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #1a7a1a;
|
||||||
|
transition: width 0.4s ease;
|
||||||
|
}
|
||||||
|
.progress-text {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 歌曲列表 ---- */
|
||||||
|
.song-list {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.song-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.song-item img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
.song-item .song-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.song-item .song-artist {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.song-item .song-like {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="container">
|
||||||
|
<header class="site-header">
|
||||||
|
<h1>🪙 云贝广告任务 · 看视频得云贝</h1>
|
||||||
|
<p class="sub">
|
||||||
|
云贝任务中心「看广告视频得云贝」— 查询今日状态、领取云贝、获取推荐歌曲
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 步骤 1: 查询今日任务状态 -->
|
||||||
|
<div class="block">
|
||||||
|
<div class="step">
|
||||||
|
<div class="num" id="s1num">1</div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="flex sb">
|
||||||
|
<h3>查询今日任务状态</h3>
|
||||||
|
<span id="s1tag" class="tag no">未开始</span>
|
||||||
|
</div>
|
||||||
|
<p class="desc">
|
||||||
|
调用
|
||||||
|
/yunbei/task/list/v1,查看今日已完成次数、累计云贝与单次可得云贝
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<button id="step1" class="primary">① 查询状态</button>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid" id="s1info" style="display: none">
|
||||||
|
<div class="lbl">已完成</div>
|
||||||
|
<div id="s1times">-</div>
|
||||||
|
<div class="lbl">累计云贝</div>
|
||||||
|
<div id="s1amount">-</div>
|
||||||
|
<div class="lbl">单次可得</div>
|
||||||
|
<div id="s1single">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-wrap" id="s1progress" style="display: none">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" id="s1fill"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-text" id="s1ptext"></div>
|
||||||
|
</div>
|
||||||
|
<pre id="s1pre">(尚未执行)</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤 2: 领取云贝 -->
|
||||||
|
<div class="block">
|
||||||
|
<div class="step">
|
||||||
|
<div class="num" id="s2num">2</div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="flex sb">
|
||||||
|
<h3>领取云贝</h3>
|
||||||
|
<span id="s2tag" class="tag no">未开始</span>
|
||||||
|
</div>
|
||||||
|
<p class="desc">
|
||||||
|
调用 /yunbei/task/finish/v1 完成任务并领取云贝。实测仅需传
|
||||||
|
yunbeiAmount(单次 150),无需真实听歌/看视频。单日上限 10 次 ×
|
||||||
|
150 = 1500 云贝/天。
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<button id="step2" class="primary" disabled>
|
||||||
|
② 领取一次 (+150)
|
||||||
|
</button>
|
||||||
|
<button id="step2x" disabled>连续领取至上限</button>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid" id="s2info" style="display: none">
|
||||||
|
<div class="lbl">本次结果</div>
|
||||||
|
<div id="s2result">-</div>
|
||||||
|
<div class="lbl">今日累计</div>
|
||||||
|
<div id="s2amount">-</div>
|
||||||
|
</div>
|
||||||
|
<pre id="s2pre">(尚未执行)</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤 3: 获取推荐歌曲 -->
|
||||||
|
<div class="block">
|
||||||
|
<div class="step">
|
||||||
|
<div class="num" id="s3num">3</div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="flex sb">
|
||||||
|
<h3>获取推荐歌曲</h3>
|
||||||
|
<span id="s3tag" class="tag no">未开始</span>
|
||||||
|
</div>
|
||||||
|
<p class="desc">
|
||||||
|
调用
|
||||||
|
/yunbei/task/recommend/song,获取「听歌得云贝」任务专属推荐(alg
|
||||||
|
均为 alg_payrec_yunBei_*)
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<button id="step3" class="primary" disabled>③ 获取推荐</button>
|
||||||
|
</div>
|
||||||
|
<div class="song-list" id="s3list" style="display: none"></div>
|
||||||
|
<pre id="s3pre">(尚未执行)</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 一键全流程 -->
|
||||||
|
<div class="block">
|
||||||
|
<div class="flex sb">
|
||||||
|
<h3>一键运行</h3>
|
||||||
|
<span id="allTag" class="tag no">就绪</span>
|
||||||
|
</div>
|
||||||
|
<p class="desc" style="margin-top: 4px">
|
||||||
|
查询今日状态 → 领取一次云贝 → 获取推荐歌曲
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<button id="runAll" class="primary">▶ 一键运行</button>
|
||||||
|
<button id="clearAll" class="danger">✕ 清空</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<a href="/">← 返回首页</a>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const $ = (id) => document.getElementById(id)
|
||||||
|
const base = location.origin
|
||||||
|
|
||||||
|
function setTag(id, type, text) {
|
||||||
|
const el = $(id)
|
||||||
|
el.className = 'tag ' + type
|
||||||
|
el.textContent = text
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNum(id, done) {
|
||||||
|
$(id).className = 'num' + (done ? ' done' : '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function nocache(url) {
|
||||||
|
const sep = url.indexOf('?') > -1 ? '&' : '?'
|
||||||
|
return url + sep + '_t=' + Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 今日进度展示: times/10 */
|
||||||
|
function renderProgress(times, singleAmount) {
|
||||||
|
const pct = Math.min(100, Math.round((times / 10) * 100))
|
||||||
|
$('s1fill').style.width = pct + '%'
|
||||||
|
$('s1ptext').textContent =
|
||||||
|
'今日进度: ' +
|
||||||
|
times +
|
||||||
|
' / 10 次' +
|
||||||
|
(times >= 10
|
||||||
|
? '(已达上限,明天再来吧)'
|
||||||
|
: '(剩余 ' + (10 - times) + ' 次 × ' + singleAmount + ' 云贝)')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 步骤 1: 查询状态 =====
|
||||||
|
async function step1() {
|
||||||
|
const btn = $('step1')
|
||||||
|
btn.disabled = true
|
||||||
|
setTag('s1tag', 'busy', '查询中…')
|
||||||
|
setNum('s1num', false)
|
||||||
|
try {
|
||||||
|
const res = await fetch(nocache(base + '/yunbei/task/list/v1'))
|
||||||
|
const data = await res.json()
|
||||||
|
$('s1pre').textContent = JSON.stringify(data, null, 2)
|
||||||
|
const body = data.data || {}
|
||||||
|
if (data.code === 200 && typeof body.times === 'number') {
|
||||||
|
setTag('s1tag', 'ok', '已查询')
|
||||||
|
setNum('s1num', true)
|
||||||
|
$('s1info').style.display = 'grid'
|
||||||
|
$('s1times').textContent = body.times
|
||||||
|
$('s1amount').textContent = body.amount + ' 云贝'
|
||||||
|
$('s1single').textContent = body.singleAmount + ' 云贝'
|
||||||
|
$('s1progress').style.display = 'block'
|
||||||
|
renderProgress(body.times, body.singleAmount)
|
||||||
|
|
||||||
|
$('step2').disabled = body.times >= 10
|
||||||
|
$('step2x').disabled = body.times >= 10
|
||||||
|
$('step3').disabled = false
|
||||||
|
window.__singleAmount = body.singleAmount || 150
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
setTag('s1tag', 'no', '查询失败')
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
$('s1pre').textContent = JSON.stringify({ error: e.message }, null, 2)
|
||||||
|
setTag('s1tag', 'no', e.message)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('step1').onclick = step1
|
||||||
|
|
||||||
|
// ===== 步骤 2: 领取云贝 =====
|
||||||
|
async function claimOnce() {
|
||||||
|
const btn = $('step2')
|
||||||
|
btn.disabled = true
|
||||||
|
$('step2x').disabled = true
|
||||||
|
setTag('s2tag', 'busy', '领取中…')
|
||||||
|
setNum('s2num', false)
|
||||||
|
try {
|
||||||
|
const amount = window.__singleAmount || 150
|
||||||
|
const res = await fetch(
|
||||||
|
nocache(base + '/yunbei/task/finish/v1?yunbeiAmount=' + amount),
|
||||||
|
)
|
||||||
|
const data = await res.json()
|
||||||
|
$('s2pre').textContent = JSON.stringify(data, null, 2)
|
||||||
|
if (data.code === 200) {
|
||||||
|
setTag('s2tag', 'ok', '已领取 +' + amount)
|
||||||
|
setNum('s2num', true)
|
||||||
|
$('s2info').style.display = 'grid'
|
||||||
|
$('s2result').textContent = String(data.data)
|
||||||
|
await step1()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
setTag('s2tag', 'no', '领取失败: ' + (data.message || data.code))
|
||||||
|
$('s2info').style.display = 'none'
|
||||||
|
return false
|
||||||
|
} catch (e) {
|
||||||
|
$('s2pre').textContent = JSON.stringify({ error: e.message }, null, 2)
|
||||||
|
setTag('s2tag', 'no', e.message)
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false
|
||||||
|
$('step2x').disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('step2').onclick = claimOnce
|
||||||
|
|
||||||
|
// 连续领取至上限
|
||||||
|
$('step2x').onclick = async function () {
|
||||||
|
const btn = this
|
||||||
|
btn.disabled = true
|
||||||
|
$('step2').disabled = true
|
||||||
|
setTag('s2tag', 'busy', '连续领取中…')
|
||||||
|
let okCount = 0
|
||||||
|
let failed = false
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const state = await step1()
|
||||||
|
if (!state) {
|
||||||
|
failed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (state.times >= 10) break
|
||||||
|
const ok = await claimOnce()
|
||||||
|
if (!ok) {
|
||||||
|
failed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
okCount++
|
||||||
|
}
|
||||||
|
setTag(
|
||||||
|
's2tag',
|
||||||
|
failed ? 'no' : 'ok',
|
||||||
|
failed ? '中断' : '已领满 ' + okCount + ' 次',
|
||||||
|
)
|
||||||
|
btn.disabled = false
|
||||||
|
$('step2').disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 步骤 3: 获取推荐歌曲 =====
|
||||||
|
$('step3').onclick = async function () {
|
||||||
|
const btn = this
|
||||||
|
btn.disabled = true
|
||||||
|
setTag('s3tag', 'busy', '获取中…')
|
||||||
|
setNum('s3num', false)
|
||||||
|
$('s3list').style.display = 'none'
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
nocache(base + '/yunbei/task/recommend/song?offset=0&limit=10'),
|
||||||
|
)
|
||||||
|
const data = await res.json()
|
||||||
|
$('s3pre').textContent = JSON.stringify(data, null, 2)
|
||||||
|
const list = data.data || []
|
||||||
|
if (data.code === 200 && Array.isArray(list) && list.length) {
|
||||||
|
setTag('s3tag', 'ok', '共 ' + list.length + ' 首')
|
||||||
|
setNum('s3num', true)
|
||||||
|
const box = $('s3list')
|
||||||
|
box.style.display = 'block'
|
||||||
|
box.innerHTML = ''
|
||||||
|
list.forEach((s) => {
|
||||||
|
const item = document.createElement('div')
|
||||||
|
item.className = 'song-item'
|
||||||
|
item.innerHTML =
|
||||||
|
'<img src="' +
|
||||||
|
(s.albumUrl || '') +
|
||||||
|
'" alt="" onerror="this.style.visibility=\'hidden\'">' +
|
||||||
|
'<div><div class="song-name">' +
|
||||||
|
(s.songName || '-') +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="song-artist">' +
|
||||||
|
(s.artistName || '-') +
|
||||||
|
'</div></div>' +
|
||||||
|
'<div class="song-like">' +
|
||||||
|
(s.likeFlag ? '♥ 已喜欢' : '♡ 未喜欢') +
|
||||||
|
'</div>'
|
||||||
|
box.appendChild(item)
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
setTag('s3tag', 'no', '获取失败')
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
$('s3pre').textContent = JSON.stringify({ error: e.message }, null, 2)
|
||||||
|
setTag('s3tag', 'no', e.message)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 一键运行 =====
|
||||||
|
$('runAll').onclick = async function () {
|
||||||
|
const btn = this
|
||||||
|
btn.disabled = true
|
||||||
|
setTag('allTag', 'busy', '运行中…')
|
||||||
|
await step1()
|
||||||
|
const state = window.__singleAmount ? true : false
|
||||||
|
if (state) await claimOnce()
|
||||||
|
await $('step3').onclick()
|
||||||
|
setTag('allTag', 'ok', '完成')
|
||||||
|
btn.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 清空 =====
|
||||||
|
$('clearAll').onclick = function () {
|
||||||
|
const steps = ['s1', 's2', 's3']
|
||||||
|
const tags = { s1: '未开始', s2: '未开始', s3: '未开始' }
|
||||||
|
steps.forEach((s) => {
|
||||||
|
setTag(s + 'tag', 'no', tags[s])
|
||||||
|
setNum(s + 'num', false)
|
||||||
|
$(s + 'pre').textContent = '(尚未执行)'
|
||||||
|
const info = $(s + 'info')
|
||||||
|
if (info) info.style.display = 'none'
|
||||||
|
})
|
||||||
|
$('s1progress').style.display = 'none'
|
||||||
|
$('s3list').style.display = 'none'
|
||||||
|
$('step1').disabled = false
|
||||||
|
$('step2').disabled = true
|
||||||
|
$('step2x').disabled = true
|
||||||
|
$('step3').disabled = true
|
||||||
|
setTag('allTag', 'no', '就绪')
|
||||||
|
$('runAll').disabled = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -17,7 +17,7 @@ const {
|
|||||||
generateRandomChineseIP,
|
generateRandomChineseIP,
|
||||||
} = require('./index')
|
} = require('./index')
|
||||||
const { URLSearchParams, URL } = require('url')
|
const { URLSearchParams, URL } = require('url')
|
||||||
const { APP_CONF } = require('../util/config.json')
|
const { APP_CONF } = require('./config.json')
|
||||||
const {
|
const {
|
||||||
getToken: antiCheatTokenV2,
|
getToken: antiCheatTokenV2,
|
||||||
} = require('../module/register_checktoken_v2')
|
} = require('../module/register_checktoken_v2')
|
||||||
@ -114,7 +114,7 @@ const userAgentMap = {
|
|||||||
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.1.29.205117',
|
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.5.61.260802021928(9005061);Dalvik/2.1.0 (Linux; U; Android 12; HBN-AL00 Build/cd737a2.0)',
|
||||||
iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)',
|
iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -187,14 +187,16 @@ const generateRequestId = () => {
|
|||||||
.padStart(4, '0')}`
|
.padStart(4, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRequest = (uri, data, options) => {
|
const createRequest = async (uri, data, options) => {
|
||||||
let token = ''
|
let token = ''
|
||||||
switch (options.checkToken) {
|
switch (options.checkToken) {
|
||||||
case 'v2':
|
case 'v2':
|
||||||
token = antiCheatTokenV2()
|
// 每次实时获取反作弊 token,不缓存
|
||||||
|
token = await antiCheatTokenV2()
|
||||||
break
|
break
|
||||||
case 'v3':
|
case 'v3':
|
||||||
token = antiCheatTokenV3()
|
// 每次实时获取反作弊 token,不缓存
|
||||||
|
token = await antiCheatTokenV3()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user