2026-08-02 21:48:01 +08:00

615 lines
21 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">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="docs/netease.png" />
<link rel="stylesheet" href="/static/shell.css" />
<title>云小编任务中心 - 网易云音乐 API Enhanced</title>
<style>
body { padding: 1.25rem; }
.section {
margin-bottom: 1.5rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--border);
}
.section:last-of-type {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.section-title {
font-family: var(--font-mono);
font-size: 1rem;
font-weight: 600;
}
.info-card {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.info-card .avatar {
width: 3.125rem;
height: 3.125rem;
border-radius: 9999px;
object-fit: cover;
background: var(--gray-4);
}
.info-card .info-text {
flex: 1;
font-size: 0.875rem;
color: var(--foreground);
}
.info-card .btn-sm { flex-shrink: 0; }
#userPoint {
font-size: 0.875rem;
font-family: var(--font-mono);
color: var(--success);
}
/* ---- Song Review ---- */
.song-review {
margin-top: 1rem;
padding: 1rem;
background: var(--gray-2);
border: 1px solid var(--border);
}
.song-review .cover {
width: 6.25rem;
height: 6.25rem;
border-radius: 2px;
object-fit: cover;
background: var(--gray-4);
display: block;
margin-bottom: 0.75rem;
}
.song-review .song-title {
font-size: 0.875rem;
font-weight: 600;
color: var(--foreground);
margin-bottom: 0.5rem;
}
.song-review audio { width: 100%; margin-bottom: 0.5rem; }
.song-review .song-tag {
font-size: 0.8125rem;
color: var(--muted-foreground);
margin-bottom: 0.625rem;
}
.song-review .btn-group {
display: flex;
gap: 0.5rem;
margin-bottom: 0.625rem;
flex-wrap: wrap;
}
.song-review .lyric {
font-size: 0.8125rem;
color: var(--muted-foreground);
background: var(--gray-1);
padding: 0.625rem 0.75rem;
border: 1px solid var(--border);
white-space: pre-wrap;
max-height: 10rem;
overflow-y: auto;
font-family: var(--font-mono);
line-height: 1.6;
}
.status-text { font-size: 0.875rem; color: var(--muted-foreground); }
.footer-link {
text-align: center;
margin-top: 1.5rem;
font-size: 0.8125rem;
}
.footer-link a { color: var(--muted-foreground); }
.footer-link a:hover { color: var(--foreground); text-decoration: underline; }
</style>
</head>
<body>
<header class="shell-topbar">
<a class="brand" href="/"><img src="docs/netease.png" alt="logo">云小编任务中心</a>
<nav>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/qrlogin-nocookie.html">登录</a>
<a class="shell-btn shell-btn--ghost shell-btn--sm" href="/">返回首页</a>
</nav>
</header>
<main class="shell-main">
<div class="shell-container shell-narrow">
<header class="shell-header">
<h1>云小编任务中心</h1>
<p class="sub">管理云音乐审核任务、签到与会员权益领取</p>
</header>
<section class="shell-card">
<!-- Cookie -->
<div class="form-group">
<label for="cookie">Cookie <span class="tag o">可选</span></label>
<textarea id="cookie" placeholder="留空则默认读取本地存储的登录态" rows="2"></textarea>
<div class="quick-fill">
<span onclick="loadLocalCookie()">读取本地 Cookie</span>
<span onclick="clearCookie()">清除 Cookie</span>
<span onclick="saveCookie()">保存 Cookie</span>
<span onclick="loadAll()">一键载入</span>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">用户信息</span>
<button class="btn-sm" id="userGet">获取用户详情</button>
</div>
<div class="info-card" id="user">
<img src="docs/netease.png" alt="头像" class="avatar" id="userAvatar" />
<span class="info-text" id="userName">未登录</span>
<span id="userPoint">0</span>
<button class="btn-sm green" id="userSign" disabled>签到</button>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">会员领取</span>
<button class="btn-sm" id="vipGet">获取会员详情</button>
</div>
<div class="info-card" id="vip">
<span class="info-text" id="vipStatus">未载入</span>
<button class="btn-sm" id="vipCollect" disabled>领取</button>
</div>
</div>
<div class="section">
<div class="section-header">
<span class="section-title">审核任务</span>
<button class="btn-sm" id="taskGet">获取任务详情</button>
</div>
<div class="info-card" id="task">
<span class="info-text" id="taskInfo">[<span id="taskCount">0</span>/<span id="taskGoal">0</span>] 审核歌曲赚贡献分 (+<span id="taskPoint">0</span>)</span>
<button class="btn-sm" id="taskStart" disabled>去完成</button>
</div>
<!-- Song Review -->
<div class="song-review" id="song" style="display: none;">
<img alt="封面" class="cover" id="songCover" />
<div class="song-title" id="songTitle">未载入</div>
<audio controls id="songAudio"></audio>
<div class="song-tag" id="songTag"></div>
<div class="btn-group">
<button class="btn-sm green" id="songApprove" disabled>同意</button>
<button class="btn-sm red" id="songReject" disabled>否决</button>
<button class="btn-sm gray" id="songSkip" disabled>跳过 (不计次数)</button>
</div>
<div class="lyric" id="songLyric">[00:00.00] 暂无歌词</div>
</div>
</div>
<div id="result" class="result" style="display: none;"></div>
<div class="footer-link">
<a href="/">返回首页</a>
</div>
</section>
</div>
</main>
<script>
(function () {
var k_cookie = 'ncm_cookie';
var cookie = localStorage.getItem(k_cookie) || '';
/* ---- DOM refs ---- */
var cookieInput = document.getElementById('cookie');
var resultDiv = document.getElementById('result');
/* ---- Init ---- */
if (cookie) {
cookieInput.value = cookie;
}
/* ---- Cookie ---- */
window.loadLocalCookie = function () {
var local = localStorage.getItem(k_cookie);
if (local) {
cookie = local;
cookieInput.value = local;
showResult('已成功读取本地 Cookie', 'success');
} else {
showResult('未在本地发现登录 Cookie请先登录或手动粘贴', 'error');
}
};
window.clearCookie = function () {
cookie = '';
cookieInput.value = '';
localStorage.removeItem(k_cookie);
showResult('已清除本地 Cookie', 'info');
};
window.saveCookie = function () {
var ck = getCookie()
if (ck) localStorage.setItem(k_cookie, ck)
showResult('已保存本地 Cookie', 'info');
}
/* ---- Result ---- */
function hideResult() {
resultDiv.style.display = 'none';
}
function showResult(message, type) {
resultDiv.textContent = message;
resultDiv.className = 'result ' + type;
resultDiv.style.display = 'block';
}
function getCookie() {
var ck = cookieInput.value.trim();
if (ck) cookie = ck;
return ck || cookie || '';
}
/**
* 发起请求 (兼容旧版)
* @param {string} url
* @param {Record<string,string>} params
* @param {(resp:Record<string,any>)=>void} done
*/
function request(url, params, done) {
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.timeout = 1e4;
xhr.onload = function () {
var r = xhr.response;
if (r.code !== 200) {
showResult('错误 ' + r.code + ': ' + (r.message || '未知错误'), 'error');
} else {
done(r);
}
};
xhr.onerror = function () {
showResult('网络请求失败: ' + xhr.statusText, 'error');
};
var ck = getCookie();
if (ck) params.cookie = ck;
var s = '?_=' + Date.now();
for (var k in params) {
s += '&' + k + '=' + encodeURIComponent(params[k]);
}
xhr.open('GET', url + s);
xhr.send();
}
/// 用户信息 ///
var userAvatar = document.getElementById('userAvatar');
var userName = document.getElementById('userName');
var userPoint = document.getElementById('userPoint');
var userSign = document.getElementById('userSign');
var userGet = document.getElementById('userGet');
userGet.onclick = function () {
request('rep/ugc/user/get', {}, function (res) {
var d = res.data;
if (d.imgUrl) userAvatar.src = d.imgUrl + '?param=50y50';
userName.textContent = d.name || '游客' + d.id;
userPoint.textContent = d.availablePoints;
userSign.disabled = d.signed;
showResult('用户信息加载成功', 'success');
});
};
userSign.onclick = function () {
if (!userSign.disabled) {
userSign.disabled = true;
request('rep/ugc/user/sign', {}, function (res) {
var d = res.data;
userSign.disabled = d;
showResult('签到' + (d ? '成功 ✓' : '失败 ✗'), d ? 'success' : 'error');
if (d) userGet.click();
});
}
};
/// 会员领取 ///
var vipStatus = document.getElementById('vipStatus');
var vipCollect = document.getElementById('vipCollect');
var vipGet = document.getElementById('vipGet');
vipGet.onclick = function () {
request('rep/ugc/user/vip', {}, function (res) {
var d = res.data;
vipStatus.textContent = d.status + ': ' + d.title;
vipCollect.disabled = d.status !== 20;
showResult('会员详情加载成功', 'success');
});
};
vipCollect.onclick = function () {
if (!vipCollect.disabled) {
vipCollect.disabled = true;
request('rep/ugc/user/collect-vip', {}, function (res) {
var d = res.data;
vipCollect.disabled = d;
showResult('领取' + (d ? '成功 ✓' : '失败 ✗'), d ? 'success' : 'error');
});
}
};
/// 审核任务 ///
var taskCount = document.getElementById('taskCount');
var taskGoal = document.getElementById('taskGoal');
var taskPoint = document.getElementById('taskPoint');
var taskStartBtn = document.getElementById('taskStart');
var taskGetBtn = document.getElementById('taskGet');
taskGetBtn.onclick = function () {
request('rep/ugc/activity/get', {}, function (res) {
var d = res.data;
taskCount.textContent = d.count;
taskGoal.textContent = d.goalCount;
taskPoint.textContent = d.points;
// 注:超过 100 可能有 bug导致 thinktank/audit/resource/detail 获取的任务 ID 一直是 5002目标数量也不对暂时限制前两个任务
taskStartBtn.disabled = d.activityId !== 5001 && d.activityId !== 5002; // 5001: 1-50, 5002: 51-100
showResult('任务详情加载成功', 'success');
// 任务完成,领取积分
if (d.count >= d.goalCount) {
request('rep/ugc/activity/collect', { activityId: d.activityId }, function (res) {
var d = res.data;
showResult('积分领取' + (d ? '成功,可以兑换会员了!' : '失败'), d ? 'success' : 'error');
// 刷新任务状态
if (d) loadAll();
});
}
});
};
taskStartBtn.onclick = function () {
if (!taskStartBtn.disabled) {
songAudio.pause();
examInit();
}
};
/// 歌曲审核区域 ///
var song = document.getElementById('song');
var songCover = document.getElementById('songCover');
var songTitle = document.getElementById('songTitle');
var songAudio = document.getElementById('songAudio');
var songTag = document.getElementById('songTag');
var songApprove = document.getElementById('songApprove');
var songReject = document.getElementById('songReject');
var songSkip = document.getElementById('songSkip');
var songLyric = document.getElementById('songLyric');
var examType = 'emotionEnter';
// 考试初始化、检查状态
function examInit() {
request('rep/ugc/exam/info/get', { examType: examType }, function (res) {
var d = res.data;
switch (d.process) {
case 'whole_exam_end':
// 通过考试
if (d.hasPassExamination) {
taskStart();
return;
}
request('rep/ugc/exam/result/get', { examType: examType, taskId: d.taskId }, function (res) {
var d = res.data;
showResult('考试未通过:正确数:' + d.wrightNum + ',正确率:' + d.wrightRate, 'error');
});
case 'no':
// 未通过或未开始
request('rep/ugc/exam/start', { examType: examType }, function (res) {
examStart(res.data.taskId);
});
break;
case 'process':
// 正在考试
examStart(d.taskId);
break;
default:
showResult('未知考试进度:' + d.process, 'error');
return;
}
// 在上个信息不为异常时显示考试中提示
if (d.process !== 'whole_exam_end' && !resultDiv.className.endsWith('error')) showResult('正在考试,请认真审题!', 'info');
});
}
// 考试取题开始
function examStart(taskId) {
request('rep/ugc/exam/question/single/get', { examType: examType, taskId: taskId }, function (res) {
var d = res.data;
songCover.src = d.coverUrl + '?param=100y100';
songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists;
songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.questionContent + '';
songLyric.textContent = d.lyric ? d.transLyric ? lrctran(d.lyric, d.transLyric) : d.lyric : '[00:00.00] 暂无歌词';
songLyric.scrollTop = 0;
// 有时候就是没有播放链接,只能根据歌词硬审
if (d.songUrl) {
songAudio.src = d.songUrl;
songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ });
} else {
songAudio.src = ''; // 清理播放器
}
songApprove.onclick = function () { if (!songApprove.disabled) examSubmit(taskId, d.questionId, 'A') };
songReject.onclick = function () { if (!songReject.disabled) examSubmit(taskId, d.questionId, 'B') };
songSkip.onclick = null;
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
song.style.display = 'block';
setTimeout(function () {
songApprove.disabled = songReject.disabled = false;
}, 3e3);
});
}
// 考试提交
function examSubmit(taskId, questionId, answer) {
songAudio.pause();
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
request(
'rep/ugc/exam/submit',
{ examType: examType, taskId: taskId, questionId: questionId, answer: answer },
function (res) {
var d = res.data;
if (d.result) {
showResult('回答正确,加载下一首...', 'success');
} else {
showResult('回答错误:' + d.analysis, 'error');
}
examInit();
},
);
}
/**
* 获取任务
*/
function taskStart() {
request('thinktank/audit/resource/detail', { type: '4' }, function (res) {
var d = res.data;
if (d.auditCount >= 50) {
request('rep/ugc/activity/collect', { activityId: '5001' }, function (res) {
var d = res.data;
showResult('积分领取' + (d ? '成功,可以兑换会员了!' : '失败'), d ? 'success' : 'error');
// 刷新任务状态
if (d) {
vipGet.click();
taskGetBtn.click();
}
});
songAudio.pause()
return;
}
songCover.src = d.coverUrl + '?param=100y100';
songTitle.textContent = '[' + d.resId + '] ' + d.resName + ' - ' + d.artists;
songTag.textContent = '[' + (d.auditCount + 1) + '/' + d.auditTaskCount + '] 当前结果: ' + d.initResult + '';
songLyric.textContent = d.lyric ? d.transLyric ? lrctran(d.lyric, d.transLyric) : d.lyric : '[00:00.00] 暂无歌词';
songLyric.scrollTop = 0;
songAudio.src = d.songUrl;
songAudio.play().catch(function () { /* 自动播放被浏览器阻止 */ });
songApprove.onclick = function () { if (!songApprove.disabled) taskSubmit(d.taskId, '1'); };
songReject.onclick = function () { if (!songReject.disabled) taskSubmit(d.taskId, '2'); };
songSkip.onclick = function () { if (!songSkip.disabled) taskSubmit(d.taskId, '3'); };
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
song.style.display = 'block';
setTimeout(function () {
songApprove.disabled = songReject.disabled = songSkip.disabled = false;
}, 3e3);
});
}
/**
* 提交任务
* @param {string} taskId
* @param {'1'|'2'|'3'} judgement
*/
function taskSubmit(taskId, judgement) {
songAudio.pause();
songApprove.disabled = songReject.disabled = songSkip.disabled = true;
request(
'thinktank/audit/resource/update',
{ type: '4', taskId: taskId, judgement: judgement },
function (res) {
var d = res.data;
if (d.result) {
showResult((d.test ? '抽查' : '审核') + '提交成功,加载下一首...', 'success');
taskStart();
} else if (d.test) {
showResult('抽查失败: [' + d.dayErrorNum + '/' + d.dayErrorNumStandard + '] ' + d.errorAnalysis, 'error');
taskStart();
} else {
showResult('未知状态: ' + JSON.stringify(d), 'error');
}
},
);
}
/**解析歌词时间戳和文本 */
function lrctrim(lyrics) {
var lines = lyrics.split('\n');
var data = [];
lines.forEach(function (line, index) {
var matches = line.match(/\[(\d{2}):(\d{2}[\.:]?\d*)\]/);
if (matches) {
var minutes = parseInt(matches[1], 10);
var seconds = parseFloat(matches[2].replace('.', ':')) || 0;
var timestamp = minutes * 6e4 + seconds * 1e3;
var text = line.replace(/\[\d{2}:\d{2}[\.:]?\d*\]/g, '').trim();
text = text.replace(/\s\s+/g, ' '); // Replace multiple spaces with a single space
data.push([timestamp, index, text]);
}
});
data.sort(function (a, b) {
return a[0] - b[0];
});
return data;
}
/**合并原文歌词和翻译歌词 */
function lrctran(lyric, tlyric) {
lyric = lrctrim(lyric);
tlyric = lrctrim(tlyric);
var len1 = lyric.length;
var len2 = tlyric.length;
var result = '';
for (var i = 0, j = 0; i < len1 && j < len2; i++) {
while (lyric[i][0] > tlyric[j][0] && j + 1 < len2) {
j++;
}
if (lyric[i][0] === tlyric[j][0]) {
tlyric[j][2] = tlyric[j][2].replace('/', '');
if (tlyric[j][2]) {
lyric[i][2] += ' (' + tlyric[j][2] + ')';
}
j++;
}
}
for (var k = 0; k < len1; k++) {
var t = lyric[k][0];
result += '['.concat(
String(Math.floor(t / 6e4)).padStart(2, '0'),
':',
String(Math.floor((t % 6e4) / 1e3)).padStart(2, '0'),
'.',
String(t % 1e3).padStart(3, '0'),
']',
lyric[k][2],
'\n',
);
}
return result;
}
/// 一键载入 ///
window.loadAll = function () {
userGet.click();
vipGet.click();
taskGetBtn.click();
showResult('正在载入所有数据...', 'info');
};
window.loadLocalCookie();
})();
</script>
</body>
</html>