232 lines
8.8 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>易盾 Watchman 调试器 — 网易云音乐 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: 760px; 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; }
.field { margin-bottom: 10px; }
.field label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; }
input[type="text"] { width: 100%; padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 13px; font-family: 'Courier New', monospace; color: var(--fg); background: #fafafa; }
input[type="text"]:focus { outline: none; border-color: #888; background: #fff; }
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: 220px; overflow: auto; }
.row { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; align-items: center; }
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; }
.hint { font-size: 12px; color: var(--muted); margin-top: 8px; }
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>🛡️ 易盾 Watchman 调试器</h1>
<p class="sub">
直接调用易盾官方 Web SDKtool.min.js获取反作弊 token。
<code>module/register_checktoken_v2.js</code> 内部jsdom 模拟同一套 SDK逻辑一致可用于对照验证。
</p>
</header>
<!-- Watchman 初始化 -->
<div class="block">
<div class="flex sb">
<h3>① 初始化 Watchman</h3>
<span id="initTag" class="tag no">未初始化</span>
</div>
<div class="field">
<label for="pnInput">productNumber易盾产品号</label>
<input type="text" id="pnInput" value="YD00000558929251" spellcheck="false">
</div>
<div class="row">
<button id="btnInit" class="primary">初始化</button>
<button id="btnReset">重置</button>
</div>
</div>
<!-- 获取 Token -->
<div class="block">
<div class="flex sb">
<h3>② 获取 Token</h3>
<span id="getTag" class="tag no"></span>
</div>
<div class="field">
<label for="bidInput">businessId业务 ID来自网易 clientcfg 下发)</label>
<input type="text" id="bidInput" value="bd5d2f973ef74cd2a61325a412ae54d9" spellcheck="false">
</div>
<div class="row">
<button id="btnGet" class="primary" disabled>获取 Token</button>
<button id="btnCopy" disabled>复制</button>
<span class="hint">覆盖 /api/playlist/subscribe、/api/song/like、/api/user/follow 等接口</span>
</div>
<pre id="tokenDisplay">(尚未获取)</pre>
</div>
<!-- 服务器端对照 -->
<div class="block">
<div class="flex sb">
<h3>③ 服务器端对照(/register/checktoken/v2</h3>
<span id="srvTag" class="tag no"></span>
</div>
<div class="row">
<button id="btnSrv">获取服务器端 Token</button>
<span class="hint">node + jsdom 模拟同一套 SDK 的实时结果</span>
</div>
<pre id="srvDisplay">(尚未获取)</pre>
</div>
<footer class="site-footer">
<a href="/">← 返回首页</a>
</footer>
</main>
<script src="https://acstatic-dun.126.net/tool.min.js"></script>
<script>
const $ = (id) => document.getElementById(id)
const DEFAULTS = {
pn: 'YD00000558929251',
bid: 'bd5d2f973ef74cd2a61325a412ae54d9',
}
let wm = null // Watchman 实例
const setTag = (id, text, state) => {
const tag = $(id)
tag.textContent = text
tag.className = 'tag ' + state // ok / no / busy
}
// ① 初始化
function initShield() {
if (typeof window.initWatchman !== 'function') {
alert('未找到 initWatchman请确认 tool.min.js 已加载CDN 是否可访问)')
return
}
const pn = $('pnInput').value.trim() || DEFAULTS.pn
setTag('initTag', '初始化中…', 'busy')
$('btnInit').disabled = true
window.initWatchman({
auto: true,
productNumber: pn,
onload(instance) {
wm = instance
setTag('initTag', '已就绪', 'ok')
$('btnInit').disabled = false
$('btnGet').disabled = false
},
onerror() {
wm = null
setTag('initTag', '初始化失败', 'no')
$('btnInit').disabled = false
$('btnGet').disabled = true
},
})
}
function resetShield() {
wm = null
setTag('initTag', '未初始化', 'no')
setTag('getTag', '—', 'no')
$('btnGet').disabled = true
$('btnCopy').disabled = true
$('tokenDisplay').textContent = '(尚未获取)'
}
// ② 获取 token
function getToken() {
if (!wm) {
alert('请先完成初始化')
return
}
const bid = $('bidInput').value.trim() || DEFAULTS.bid
setTag('getTag', '获取中…', 'busy')
$('btnGet').disabled = true
wm.getToken(bid, (tk) => {
if (typeof tk === 'string' && tk) {
$('tokenDisplay').textContent = tk
setTag('getTag', 'OK', 'ok')
$('btnCopy').disabled = false
} else {
$('tokenDisplay').textContent = '(获取失败)'
setTag('getTag', '失败', 'no')
$('btnCopy').disabled = true
}
$('btnGet').disabled = false
})
}
// ③ 服务器端对照
async function fetchServerToken() {
setTag('srvTag', '获取中…', 'busy')
$('btnSrv').disabled = true
try {
const res = await fetch(
location.origin + '/register/checktoken/v2?t=' + Date.now(),
)
const data = await res.json()
$('srvDisplay').textContent = data.token
? data.token
: JSON.stringify(data, null, 2)
setTag('srvTag', data.token ? 'OK' : '失败', data.token ? 'ok' : 'no')
} catch (e) {
$('srvDisplay').textContent = JSON.stringify({ error: e.message }, null, 2)
setTag('srvTag', e.message, 'no')
}
$('btnSrv').disabled = false
}
// 复制
async function copyToken() {
const txt = $('tokenDisplay').textContent
try {
await navigator.clipboard.writeText(txt)
setTag('getTag', '已复制', 'ok')
} catch (e) {
setTag('getTag', '复制失败', 'no')
}
}
$('btnInit').onclick = initShield
$('btnReset').onclick = resetShield
$('btnGet').onclick = getToken
$('btnCopy').onclick = copyToken
$('btnSrv').onclick = fetchServerToken
// 自动初始化
initShield()
</script>
</body>
</html>