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
+312
View File
@@ -0,0 +1,312 @@
<!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 .15s; }
button:hover { background: #eee; }
button.primary { background: var(--fg); color: var(--panel); border-color: var(--fg); }
button.primary:hover { opacity: .85; }
button:disabled { opacity: .4; cursor: not-allowed; }
button.danger { background: #a11; color: #fff; border-color: #a11; }
button.danger:hover { opacity: .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; }
input[type=text] { width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); font-size: 14px; font-family: inherit; margin-top: 6px; }
input[type=text]:focus { outline: 2px solid var(--fg); outline-offset: -1px; border-color: transparent; }
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); }
.info-grid { display: grid; grid-template-columns: 80px 1fr; gap: 4px 12px; font-size: 13px; margin-top: 8px; }
.info-grid .lbl { color: var(--muted); }
</style>
</head>
<body>
<main class="container">
<header class="site-header">
<h1>🎧 免费听 · 看广告获取时长</h1>
<p class="sub">模拟 App 内看激励视频广告 → 领取免费听权益的完整流程</p>
</header>
<!-- 步骤 1: 注册 Token -->
<div class="block">
<div class="step">
<div class="num" id="s1num">1</div>
<div class="body">
<div class="flex sb">
<h3>注册反作弊 Token</h3>
<span id="s1tag" class="tag no">未开始</span>
</div>
<p class="desc">调用易盾接口获取实时 token,广告接口需要此 token 才能正常响应</p>
<pre id="s1pre">(尚未执行)</pre>
<div class="row">
<button id="step1" class="primary">① 注册 Token</button>
</div>
</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>获取广告 &amp; 提取 reqId</h3>
<span id="s2tag" class="tag no">未开始</span>
</div>
<p class="desc">请求广告位,从返回的 extJson.contextInfo.req_id 提取用于领取权益的标识</p>
<pre id="s2pre" class="data">(尚未执行)</pre>
<div class="row">
<button id="step2" class="primary" disabled>② 获取广告</button>
<button id="step2b" disabled>换一个广告位</button>
</div>
<div class="info-grid" id="s2info" style="display:none">
<div class="lbl">广告 ID</div><div id="s2adId">-</div>
<div class="lbl">reqId</div><div id="s2reqId">-</div>
<div class="lbl">文案</div><div id="s2text">-</div>
</div>
</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">传入 reqId 及相关参数,领取免费听权益(模拟点击广告后的领取请求)</p>
<pre id="s3pre" class="data">(尚未执行)</pre>
<div class="row">
<button id="step3" class="primary" disabled>③ 领取权益</button>
</div>
<div class="info-grid" id="s3info" style="display:none">
<div class="lbl">结果</div><div id="s3result">-</div>
</div>
</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()
}
// ===== 步骤 1: 注册 Token =====
async function step1() {
const btn = $('step1')
btn.disabled = true
setTag('s1tag', 'busy', '注册中…')
setNum('s1num', false)
try {
const res = await fetch(nocache(base + '/register/checktoken?refresh=1'))
const data = await res.json()
$('s1pre').textContent = JSON.stringify(data, null, 2)
if (data.token) {
setTag('s1tag', 'ok', '已注册')
setNum('s1num', true)
$('step2').disabled = false
$('step2b').disabled = false
return data.token
}
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
}
}
// ===== 步骤 2: 获取广告 =====
async function step2(typeId) {
const btn = $('step2')
const btnB = $('step2b')
btn.disabled = btnB.disabled = true
setTag('s2tag', 'busy', '获取中…')
setNum('s2num', false)
$('s2info').style.display = 'none'
try {
let url = base + '/ad/get' + (typeId ? '?type_ids=' + encodeURIComponent(typeId) : '')
const res = await fetch(nocache(url))
const data = await res.json()
$('s2pre').textContent = JSON.stringify(data, null, 2)
if (data.code === 200 && data.extra && data.extra.reqId) {
setTag('s2tag', 'ok', '已获取')
setNum('s2num', true)
$('s2info').style.display = 'grid'
const adKeys = data.ads ? Object.keys(data.ads) : []
let adId = '-', text = '-'
if (adKeys.length > 0) {
const ad = data.ads[adKeys[0]]
adId = ad.ad_id || '-'
text = ad.text || '-'
}
$('s2adId').textContent = adId
$('s2reqId').textContent = data.extra.reqId
$('s2text').textContent = text
$('step3').disabled = false
return data.extra.reqId
}
setTag('s2tag', 'no', '未获取到 reqId')
return null
} catch (e) {
$('s2pre').textContent = JSON.stringify({ error: e.message }, null, 2)
setTag('s2tag', 'no', e.message)
return null
} finally {
btn.disabled = false
btnB.disabled = false
}
}
$('step2').onclick = () => step2()
$('step2b').onclick = () => step2('["400002_0"]')
// ===== 步骤 3: 领取权益 =====
async function step3() {
const btn = $('step3')
btn.disabled = true
setTag('s3tag', 'busy', '领取中…')
setNum('s3num', false)
$('s3info').style.display = 'none'
try {
const res = await fetch(nocache(base + '/ad/listening/rights/gain'))
const data = await res.json()
$('s3pre').textContent = JSON.stringify(data, null, 2)
if (data.code === 200) {
setTag('s3tag', 'ok', '已领取')
setNum('s3num', true)
$('s3info').style.display = 'grid'
const body = data.data || {}
$('s3result').textContent = JSON.stringify(body).substring(0, 200)
return true
}
setTag('s3tag', 'no', '领取失败')
return false
} catch (e) {
$('s3pre').textContent = JSON.stringify({ error: e.message }, null, 2)
setTag('s3tag', 'no', e.message)
return false
} finally {
btn.disabled = false
}
}
$('step3').onclick = step3
// ===== 一键运行 =====
$('runAll').onclick = async function () {
const btn = this
btn.disabled = true
setTag('allTag', 'busy', '运行中…')
await step1()
await step2()
await step3()
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'
})
$('step1').disabled = false
$('step2').disabled = true
$('step2b').disabled = true
$('step3').disabled = true
setTag('allTag', 'no', '就绪')
$('runAll').disabled = false
}
</script>
</body>
</html>
+3 -1
View File
@@ -96,7 +96,9 @@ curl -s {origin}/search?keywords=网易云</code></pre>
<a href="/listen_together_host.html">一起听示例</a> ·
<a href="/playlist_cover_update.html">更新歌单封面示例</a> ·
<a href="/avatar_update.html">头像更新示例</a> ·
<a href="/scrobble.html">听歌打卡示例</a>
<a href="/scrobble.html">听歌打卡示例</a> ·
<a href="/yidun.html">获取 CheckToken</a> ·
<a href="/free_listen.html">免费听权益</a>
</div>
</section>
+119
View File
@@ -0,0 +1,119 @@
<!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>Token 注册器 — 网易云音乐 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: 720px; 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 12px; font-size: 15px; font-weight: 600; }
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: 160px; overflow: auto; }
.row { display: flex; gap: 8px; margin-top: 14px; 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 .15s; }
button:hover { background: #eee; }
button.primary { background: var(--fg); color: var(--panel); border-color: var(--fg); }
button.primary:hover { opacity: .85; }
button:disabled { opacity: .4; cursor: not-allowed; }
.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; }
.mt-2 { margin-top: 14px; }
footer.site-footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); text-align: center; font-size: 13px; }
</style>
</head>
<body>
<main class="container">
<header class="site-header">
<h1>🔑 Token 注册器</h1>
<p class="sub">从易盾获取反作弊 token 并注册到服务器,供广告等需要 checkToken 的接口使用</p>
</header>
<div class="block">
<div class="flex sb">
<h3>当前 Token</h3>
<span id="statusTag" class="tag no">未注册</span>
</div>
<pre id="tokenDisplay">(空)</pre>
<div class="row">
<button id="btnGet" class="primary">获取 Token</button>
<button id="btnRefresh">强制刷新</button>
</div>
</div>
<div class="block">
<h3>服务器响应</h3>
<pre id="respDisplay">{}</pre>
</div>
<footer class="site-footer">
<a href="/">← 返回首页</a>
</footer>
</main>
<script>
const $ = (id) => document.getElementById(id)
const base = location.origin
async function call(refresh) {
const btn = $('btnGet')
const btnR = $('btnRefresh')
btn.disabled = btnR.disabled = true
const tag = $('statusTag')
tag.className = 'tag busy'
tag.textContent = '获取中…'
try {
const url = base + '/register/checktoken' + (refresh ? '?refresh=1' : '')
const res = await fetch(url)
const data = await res.json()
$('respDisplay').textContent = JSON.stringify(data, null, 2)
if (data.token) {
$('tokenDisplay').textContent = data.token
tag.className = 'tag ok'
tag.textContent = '已注册'
} else {
$('tokenDisplay').textContent = '(获取失败)'
tag.className = 'tag no'
tag.textContent = '失败'
}
} catch (e) {
$('respDisplay').textContent = JSON.stringify({ error: e.message }, null, 2)
tag.className = 'tag no'
tag.textContent = e.message
}
btn.disabled = btnR.disabled = false
}
$('btnGet').onclick = () => call(false)
$('btnRefresh').onclick = () => call(true)
call(false)
</script>
</body>
</html>