管理员两步验证,绑定邮箱:-
@@ -3070,7 +3070,7 @@
(function () {
'use strict';
- /* P2-11:Promise.finally 老内核 polyfill(MDN 标准实现,ES5 语法)。
+ /* Promise.finally 老内核 polyfill(MDN 标准实现,ES5 语法)。
finally 需 Chrome 63+;支持矩阵承诺 Chromium 57-85 可用降级,
20 处业务代码的 .finally 依赖此垫片在 57-62 上正常工作 */
if (!Promise.prototype.finally) {
@@ -3116,7 +3116,7 @@
cookiePrefs: { functional: true, accepted: false } // 新用户默认未同意
},
adminAuthed: false, // 管理员是否已通过密码验证
- adminPassword: '', // 已废弃(旧口令 2026-09-15 废除;F9 清理后前端不再发送)
+ adminPassword: '', // 已废弃(旧口令 2026-09-15 废除;清理后前端不再发送)
adminQrTimer: null, // 二维码轮询定时器
adminUnikey: '',
personal: { // 个人歌单(游客扫码登录自己的账号,Cookie 保存在本机浏览器)
@@ -3183,13 +3183,13 @@
];
function loadSettings() {
- // F96/F104:可读部分先应用;单条目损坏不阻断整体恢复;
+ // 可读部分先应用;单条目损坏不阻断整体恢复;
// primaryColor 校验为合法 #rrggbb(数值等坏值不再炸 applyTheme)
var s = {};
try { s = JSON.parse(localStorage.getItem('netease_settings') || '{}') || {}; } catch (e) { s = {}; }
if (typeof s !== 'object') s = {};
// 一次性迁移:批量下载默认改为 ZIP 打包(老用户未主动开启过打包也升级,之后可在设置中关闭)
- // F103:迁移结果必须持久化(原实现只写完成标志,第二次加载回退旧值)
+ // 迁移结果必须持久化(原实现只写完成标志,第二次加载回退旧值)
try {
if (!localStorage.getItem('netease_batchzip_migrated')) {
if (s.batchZip !== true) { delete s.batchZip; }
@@ -3223,7 +3223,7 @@
}
function saveSettings() {
- // F96:返回是否真的持久化成功(调用方据此给出准确提示,不再一律"已保存")
+ // 返回是否真的持久化成功(调用方据此给出准确提示,不再一律"已保存")
try {
localStorage.setItem('netease_settings', JSON.stringify(state.settings));
return true;
@@ -3293,7 +3293,7 @@
$('#switch-cookie-func').classList.toggle('on', state.settings.cookiePrefs.functional);
$('#switch-cookie-func').setAttribute('aria-checked', state.settings.cookiePrefs.functional ? 'true' : 'false');
$('#cookie-func-tag').textContent = state.settings.cookiePrefs.functional ? '已启用' : '已禁用';
- // 第 97 条(P3):主题色点键盘可达(F109 同型——动态生成后统一补属性)
+ // 主题色点键盘可达(同型——动态生成后统一补属性)
$$('#color-palette .color-dot').forEach(function (el) {
el.setAttribute('tabindex', '0');
el.setAttribute('role', 'button');
@@ -3307,7 +3307,7 @@
renderSettingsUI();
applyTheme();
});
- // 第 97 条(P3):主题色点 Enter/空格激活
+ // 主题色点 Enter/空格激活
$('#color-palette').addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
var dot = e.target.closest ? e.target.closest('.color-dot') : null;
@@ -3323,7 +3323,7 @@
renderSettingsUI();
applyTheme();
});
- // 第 97 条(P3):外观模式分段键盘可达
+ // 外观模式分段键盘可达
$('#mode-seg').addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
var item = e.target.closest ? e.target.closest('.seg-item') : null;
@@ -3335,7 +3335,7 @@
function bindSwitch(id, key) {
var el = $(id);
if (!el) return;
- // F109:自定义开关补全键盘语义(tabindex/role/aria + Enter/空格激活)
+ // 自定义开关补全键盘语义(tabindex/role/aria + Enter/空格激活)
el.setAttribute('tabindex', '0');
el.setAttribute('role', 'switch');
var activate = function () {
@@ -3357,7 +3357,7 @@
bindSwitch('#switch-comments', 'showComments');
bindSwitch('#switch-smtc', 'smtc');
- // F109:配置区开关同样支持键盘操作
+ // 配置区开关同样支持键盘操作
function bindCfgSwitch(id, fn) {
var el = $(id);
if (!el) return;
@@ -3401,7 +3401,7 @@
state.settings.metaFields[cb.getAttribute('data-field')] = cb.checked;
});
// 各分区保存按钮
- // F96:保存提示反映真实持久化结果(存储被禁/配额满时不再谎报成功)
+ // 保存提示反映真实持久化结果(存储被禁/配额满时不再谎报成功)
function saveSettingsToast(okMsg) {
toast(saveSettings() ? okMsg : '设置未能保存(浏览器存储不可用),仅本次生效', 'warning');
}
@@ -3427,13 +3427,13 @@
// 评论区显隐
$('#comment-panel').hidden = !(state.settings.showComments && state.currentSong);
updateCommentComposer();
- // F105:评论从关到开时,为当前歌曲补加载评论(原来只显示面板,
+ // 评论从关到开时,为当前歌曲补加载评论(原来只显示面板,
// 可能展示旧歌评论或空白)
if (state.settings.showComments && state.currentSong
&& !state.commentTotal && !state._commentsLoading) {
loadComments(state.currentSong.id, 1, true);
}
- // F113:KTV 从开到关时终止已启动的动画帧循环(原来旧帧继续更新并排下一帧)
+ // KTV 从开到关时终止已启动的动画帧循环(原来旧帧继续更新并排下一帧)
if (!state.settings.ktv && state.ktvRunning) {
state.ktvRunning = false;
}
@@ -3523,7 +3523,7 @@
$('#stats-modal').addEventListener('click', function (e) { if (e.target === this) closeStatsModal(); });
/* ================= 隐私与协议(Cookie 偏好) ================= */
- // 第 81/82 条:mode='register' 为注册门槛模式——打开即自动计时 16 秒强制阅读,
+ // mode='register' 为注册门槛模式——打开即自动计时 16 秒强制阅读,
// 计时归零且勾选同意后才允许「同意并继续注册」进入注册弹窗(同意一次永久记住,
// 撤回/清浏览器数据后重新计时)。普通模式(首访/设置入口)行为不变。
var _cookieRegMode = false;
@@ -3557,7 +3557,7 @@
}
}
function openCookieModal(mode) {
- // 第 98 条(对抗复审):反向叠窗守卫——公告先开时 Cookie 窗(强制同意门)
+ // 反向叠窗守卫——公告先开时 Cookie 窗(强制同意门)
// 优先,收起公告弹窗再开(游客 once 模式已在打开时记已读,属可接受取舍)
var _am = document.getElementById('announce-modal');
if (_am && _am.classList.contains('show')) _am.classList.remove('show');
@@ -3570,7 +3570,7 @@
_cookieStopReadTimer();
if (isReg) {
// 16 秒强制阅读:弹窗被任何方式关闭(Escape/遮罩/关闭钮)由 tick
- // 自检停止——不碰 MutationObserver(第 53 条渲染器活锁教训)
+ // 自检停止——不碰 MutationObserver(渲染器活锁教训)
_cookieReadLeft = 16;
_cookieUpdateContinueBtn();
_cookieReadTimer = setInterval(function () {
@@ -3587,7 +3587,7 @@
$('#cookie-modal').classList.remove('show');
_cookieSetRegMode(false); // 复位为常规 footer,普通入口打开状态正确
}
- $('#cfg-cookie-prefs').addEventListener('click', function () { openCookieModal(); }); // 第 87 条:显式包裹,避免事件对象被误当 mode 实参
+ $('#cfg-cookie-prefs').addEventListener('click', function () { openCookieModal(); }); // 显式包裹,避免事件对象被误当 mode 实参
$('#cookie-close').addEventListener('click', closeCookieModal);
$('#cookie-modal').addEventListener('click', function (e) { if (e.target === this) closeCookieModal(); });
$('#cookie-agree').addEventListener('change', function () {
@@ -3600,7 +3600,7 @@
toast(state.settings.cookiePrefs.functional ? '功能性 Cookie 已启用' : '功能性 Cookie 已禁用(解析与下载将不可用)', 'warning');
};
$('#switch-cookie-func').addEventListener('click', _toggleCookieFunc);
- // 第 97 条(P3):功能性 Cookie 开关键盘可达(F109 同型补齐)
+ // 功能性 Cookie 开关键盘可达
$('#switch-cookie-func').addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _toggleCookieFunc(); }
});
@@ -3616,7 +3616,7 @@
closeCookieModal();
toast('Cookie 偏好已保存', 'success');
});
- // 第 81 条:注册门槛模式的「同意并继续注册」——保存同意与功能性偏好,
+ // 注册门槛模式的「同意并继续注册」——保存同意与功能性偏好,
// 关闭本弹窗并放行进入注册弹窗(聚焦用户名框)
$('#cookie-agree-continue').addEventListener('click', function () {
if (this.disabled) return;
@@ -3660,12 +3660,12 @@ function cookieGate(mode) {
}
return true;
}
-// 第 80 条:暴露给 site_auth.js——注册提交前强制隐私同意(未同意则弹协议窗并中止)
+// 暴露给 site_auth.js——注册提交前强制隐私同意(未同意则弹协议窗并中止)
window.SITE_COOKIE_GATE = cookieGate;
var _drawerPrevFocus = null;
function openDrawer() {
- // F110:打开抽屉时把焦点移入抽屉(键盘/读屏用户不再滞留在页面背后)
+ // 打开抽屉时把焦点移入抽屉(键盘/读屏用户不再滞留在页面背后)
_drawerPrevFocus = document.activeElement;
$('#settings-drawer').classList.add('show');
$('#drawer-mask').classList.add('show');
@@ -3675,29 +3675,29 @@ window.SITE_COOKIE_GATE = cookieGate;
function closeDrawer() {
$('#settings-drawer').classList.remove('show');
$('#drawer-mask').classList.remove('show');
- // F110:关闭后把焦点还给触发控件
+ // 关闭后把焦点还给触发控件
if (_drawerPrevFocus && typeof _drawerPrevFocus.focus === 'function') {
try { _drawerPrevFocus.focus(); } catch (e) {}
}
_drawerPrevFocus = null;
}
- // F110:Escape 关闭抽屉/弹窗(键盘可达性)
+ // Escape 关闭抽屉/弹窗(键盘可达性)
document.addEventListener('keydown', function (e) {
if (e.key !== 'Escape') return;
- // 第 98 条:先关视觉最上层的弹窗(z-index 300 > 抽屉 201)——
+ // 先关视觉最上层的弹窗(z-index 300 > 抽屉 201)——
// 抽屉内打开统计/Cookie 偏好弹窗时,旧顺序会关掉下层抽屉留下上层弹窗
var openModalMask = document.querySelector('.modal-mask.show');
if (openModalMask) {
- /* 第 95 条:取最后一个 .show = 视觉最上层 */
+ /* 取最后一个 .show = 视觉最上层 */
var _stack = document.querySelectorAll('.modal-mask.show');
if (_stack.length > 1) openModalMask = _stack[_stack.length - 1];
- // F2:统计弹窗的 10 秒轮询定时器只有 closeStatsModal 会清——
+ // 统计弹窗的 10 秒轮询定时器只有 closeStatsModal 会清——
// Escape 走通用分支时必须先走专用关闭,否则定时器泄漏持续后台请求
if (openModalMask.id === 'stats-modal') {
closeStatsModal();
return;
}
- // 第 87 条:登录/协议弹窗 Escape 走专用关闭——login-modal 需复位
+ // 登录/协议弹窗 Escape 走专用关闭——login-modal 需复位
// 找回/两步验证视图,cookie-modal 需复位注册门槛模式的 footer/标题
if (openModalMask.id === 'login-modal') {
if (window.resetAuthViews) window.resetAuthViews();
@@ -3712,7 +3712,7 @@ window.SITE_COOKIE_GATE = cookieGate;
if (openModalMask.getAttribute('data-dynamic') === '1') openModalMask.remove();
return;
}
- // 第 98 条:无弹窗时 Escape 仍关抽屉(弹窗优先于抽屉的顺序修正)
+ // 无弹窗时 Escape 仍关抽屉(弹窗优先于抽屉的顺序修正)
if ($('#settings-drawer').classList.contains('show')) closeDrawer();
});
$('#btn-settings').addEventListener('click', openDrawer);
@@ -3789,9 +3789,9 @@ window.SITE_COOKIE_GATE = cookieGate;
});
/* ================= 解析账号栏 ================= */
- var _acctGen = 0; // P3-32:账号栏代次令牌——连点刷新/快速切账号时迟到响应不再覆盖新状态
+ var _acctGen = 0; // 账号栏代次令牌——连点刷新/快速切账号时迟到响应不再覆盖新状态
function renderAccountBar() {
- // F48:个人账号时携带本机保存的个人 Cookie(原实现只发 account:user,
+ // 个人账号时携带本机保存的个人 Cookie(原实现只发 account:user,
// 后端拿不到个人凭据,永远显示"未登录")
var gen = ++_acctGen;
apiPost('/account/vip', {
@@ -3799,7 +3799,7 @@ window.SITE_COOKIE_GATE = cookieGate;
user_cookie: state.parseAccount === 'user' ? state.personal.cookie : ''
})
.then(function (data) {
- if (gen !== _acctGen) return; // P3-32:已有新一轮刷新,丢弃迟到响应
+ if (gen !== _acctGen) return; // 已有新一轮刷新,丢弃迟到响应
if (data.status !== 200) throw new Error(data.message || '获取账号信息失败');
var d = data.data || {};
var badge = $('#account-vip-badge');
@@ -3843,7 +3843,7 @@ window.SITE_COOKIE_GATE = cookieGate;
.catch(function (err) {
if (gen !== _acctGen) return;
$('#account-vip-badge').textContent = '获取失败';
- // P3-31:探测失败清空 levelsOk——避免 auto 音质阶梯继续用
+ // 探测失败清空 levelsOk——避免 auto 音质阶梯继续用
// 旧账号(全局↔个人切换窗口)的能力列表逐级白试
state.levelsOk = [];
toast(err.message || '获取账号信息失败', 'error');
@@ -3861,7 +3861,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function switchTab(name) {
state.currentTab = name;
- // R5-3 收口:切页签(含键盘回车激活,不产生 click)时关掉字号弹层,
+ // 切页签(含键盘回车激活,不产生 click)时关掉字号弹层,
// 否则 fs-pop-open 卡在 html 上,排队/任务浮窗在其他页签一直隐藏
if (typeof setLyricFsPop === 'function') setLyricFsPop(false);
tabs.forEach(function (t) {
@@ -3902,7 +3902,7 @@ window.SITE_COOKIE_GATE = cookieGate;
// 进入个人中心:需登录
if (name === 'usercenter' && window.renderSITEUserCenter) window.renderSITEUserCenter();
// 进入管理后台:权限门由 site_auth.js 的 uaShowGate 处理
- //(F54:原这里调用不存在的 renderUserAdmin(),浏览器抛 ReferenceError)
+ //(原这里调用不存在的 renderUserAdmin(),浏览器抛 ReferenceError)
if (name === 'useradmin' && window.renderSITEUserCenter) window.renderSITEUserCenter();
}
@@ -3982,7 +3982,7 @@ window.SITE_COOKIE_GATE = cookieGate;
body: body,
signal: ctl ? ctl.signal : undefined
}).then(function (r) {
- // F52:超时计时器在"响应正文读完"之后再清除
+ // 超时计时器在"响应正文读完"之后再清除
//(原实现在 r.json() 之前 clear,正文读取阶段失去超时保护)
return r.json().catch(function () { throw new Error('服务响应异常'); })
.then(function (data) { if (timer) clearTimeout(timer); return data; },
@@ -4001,7 +4001,7 @@ window.SITE_COOKIE_GATE = cookieGate;
});
};
// 网络层失败自动重试 1 次(丢包/断流/超时):
- // F52:仅限幂等查询端点——下载提交/任务操作/评论/收藏/Cookie/扫码/登录一律不重试,
+ // 仅限幂等查询端点——下载提交/任务操作/评论/收藏/Cookie/扫码/登录一律不重试,
// 防止写操作重复执行;服务端有明确响应(4xx/5xx)不重试。
var retriable = url.indexOf('download') === -1
&& url.indexOf('/auth/') !== 0
@@ -4032,7 +4032,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function setBtnLoading(btn, loading, loadingText) {
if (!btn) return;
if (loading) {
- // F107:重入保护——已在加载中不覆盖原始标签(重复提交后原标签丢失)
+ // 重入保护——已在加载中不覆盖原始标签(重复提交后原标签丢失)
if (!btn.dataset.originText) btn.dataset.originText = btn.textContent;
btn.dataset.loadingCount = String((parseInt(btn.dataset.loadingCount, 10) || 0) + 1);
btn.disabled = true;
@@ -4052,7 +4052,7 @@ window.SITE_COOKIE_GATE = cookieGate;
/* ================= 解析历史 ================= */
var HISTORY_KEY = 'netease_parse_history';
function getHistory() {
- // F104:结构校验——null/对象/坏成员一律回退为空数组,
+ // 结构校验——null/对象/坏成员一律回退为空数组,
// 损坏历史不再中断页面初始化或播放器更新
var raw = null;
try { raw = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch (e) { return []; }
@@ -4108,7 +4108,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var keyword = $('#search-input').value.trim();
if (!keyword) { toast('请输入搜索关键词', 'warning'); $('#search-input').focus(); return; }
var btn = $('#search-btn');
- var gen = ++_searchGen; // 第 97 条(P2):代次守卫——回车入口绕过按钮禁用,迟到响应不得覆盖新结果
+ var gen = ++_searchGen; // 代次守卫——回车入口绕过按钮禁用,迟到响应不得覆盖新结果
setBtnLoading(btn, true, '搜索中...');
apiPost('/search', {
keyword: keyword,
@@ -4122,7 +4122,7 @@ window.SITE_COOKIE_GATE = cookieGate;
if (gen !== _searchGen) return;
toast(err.message || '搜索失败,请重试', 'error');
}).finally(function () {
- // 第 97 条(对抗复审):setBtnLoading 是重入计数器——Enter 连发时两次
+ // setBtnLoading 是重入计数器——Enter 连发时两次
// 调用各自 +1,finally 必须无条件配对复位,否则旧代次被 gen 拦下后
// 计数永远无法归零、按钮永久卡死;代次条件只保留在 then/catch 的状态写入
setBtnLoading(btn, false);
@@ -4226,7 +4226,7 @@ window.SITE_COOKIE_GATE = cookieGate;
});
}
- var _parseGen = 0; // P2-19:单曲解析代次令牌(B5 家族补漏——回车/搜索行/历史标签
+ var _parseGen = 0; // 单曲解析代次令牌(家族补漏——回车/搜索行/历史标签
// 三个入口绕过按钮禁用,连点两首歌时迟到响应覆盖新结果)
function parseSong() {
if (!requireLogin()) return;
@@ -4236,7 +4236,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var btn = $('#parse-btn');
var gen = ++_parseGen;
setBtnLoading(btn, true, '解析中...');
- // R5-5(F1 修订):新解析一旦提交即收起上一首的结果——但 #player/#lyric-panel/
+ // 新解析一旦提交即收起上一首的结果——但 #player/#lyric-panel/
// #comment-panel 都嵌套在 #song-result 卡片内部,整卡隐藏会把正在播放的
// 旧歌播放器条一起藏掉(失败路径音频"隐身"继续播)。改为只藏 .song-detail
// 信息块(旧歌封面/名称/下载按钮),播放器照常可见可操作,歌词/评论照旧收起
@@ -4278,7 +4278,7 @@ window.SITE_COOKIE_GATE = cookieGate;
state.rawTlyric = null;
state.commentPage = 1;
state.commentTotal = 0;
- state.commentReply = null; // F88:切歌清掉旧评论回复对象(防跨歌错配)
+ state.commentReply = null; // 切歌清掉旧评论回复对象(防跨歌错配)
$('#detail-cover').src = d.pic || '';
$('#detail-name').textContent = d.name || '未知歌曲';
$('#detail-artist').textContent = d.ar_name || '未知歌手';
@@ -4295,7 +4295,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
$('#song-result').hidden = false;
- $('#song-result .song-detail').hidden = false; // R5-5/F1:恢复提交时收起的信息块
+ $('#song-result .song-detail').hidden = false; // 恢复提交时收起的信息块
window.scrollTo({ top: $('#song-result').offsetTop - 80, behavior: 'smooth' });
// 重置歌词偏移校准
@@ -4332,7 +4332,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var lines = String(lyrics || '').split('\n');
var data = [];
lines.forEach(function (line, index) {
- // P3-35:分钟放宽到 3 位(F37 同型漏网——后端 SRT 当年修了,
+ // 分钟放宽到 3 位(同型漏网——后端 SRT 当年修了,
// 前端没修:超 100 分钟曲目第 100 分钟起歌词行全部丢失)
var matches = line.match(/\[(\d{1,3}):(\d{1,2}[.:]?\d*)]/);
if (matches) {
@@ -4351,7 +4351,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function mergeTranslation(lyric, tlyric) {
var trans = lrctrim(tlyric);
if (!trans.length) return lyric;
- // F116:精确匹配优先,其次取时间最近者;每条译文只配一行原文,
+ // 精确匹配优先,其次取时间最近者;每条译文只配一行原文,
// 不再被"第一个邻近"遮蔽精确配对或重复使用
var usedTrans = {};
return lyric.map(function (l) {
@@ -4368,7 +4368,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
if (best && best.text) {
usedTrans[bestIdx] = true;
- // F6:保留纯原文——渲染层还原原文时不再依赖对拼接串做正则截断
+ // 保留纯原文——渲染层还原原文时不再依赖对拼接串做正则截断
//(原文/译文本身含 " (翻译:" 时贪婪正则会吃掉原文尾部)
var merged = { time: l.time, end: l.end, words: l.words, trans: best.text, index: l.index, orig: l.text };
if (state.settings.lyric2line) {
@@ -4383,7 +4383,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
// 解析网易云 YRC 逐字歌词:行头 [startMs,durMs],每字 (startMs,durMs,0)字
- // F115:字与字之间的正文允许包含半角括号——只把"合法时间标记"当分隔符,
+ // 字与字之间的正文允许包含半角括号——只把"合法时间标记"当分隔符,
// 不再把 (world) 这类正文当标记吃掉
function parseYrc(text) {
var out = [];
@@ -4411,14 +4411,14 @@ window.SITE_COOKIE_GATE = cookieGate;
if (!words.length || !chars) return;
out.push({ time: start, end: start + dur, text: chars, words: words, index: out.length });
});
- // F82:YRC 时间轴排序,覆盖已排序 LRC 前先规范时间轴
+ // YRC 时间轴排序,覆盖已排序 LRC 前先规范时间轴
out.sort(function (a, b) { return a.time - b.time; });
out.forEach(function (l, i) { l.index = i; });
return out;
}
// 由原始歌词构建行数据:LRC 为底,YRC 逐字轴覆盖过半时替换,
- // 未被覆盖的 LRC 行保留(F82:不再整体丢弃剩余歌词行),再合并翻译
+ // 未被覆盖的 LRC 行保留(不再整体丢弃剩余歌词行),再合并翻译
function buildLyricLines() {
var lines = lrctrim(state.rawLyric || '');
var wordLines = parseYrc(state.rawYrc || '');
@@ -4470,7 +4470,7 @@ window.SITE_COOKIE_GATE = cookieGate;
} else {
// 译文统一走 .ktr 弱化样式(与 KTV 分支同构,所有模式层级一致)。
// 数据层把译文并进了 text(双行"原文\n译文"/单行"原文 (翻译:…)"),
- // F6:优先用 mergeTranslation 保留的纯原文 orig,避免正则截断的边角误伤
+ // 优先用 mergeTranslation 保留的纯原文 orig,避免正则截断的边角误伤
if (l.trans) {
var base = l.orig || text;
var orig = twoLine ? base.split('\n')[0] : base.replace(/ \(翻译:.*\)$/, '');
@@ -4486,7 +4486,7 @@ window.SITE_COOKIE_GATE = cookieGate;
// 新歌歌词从顶部开始,避免残留上一首歌的滚动位置
box.scrollTop = 0;
// 重绘后行元素已重建,KTV 填充缓存与行高亮状态一并重置
- // P3-14(O-8):行元素随重绘缓存——applyActiveLyric 由 rAF 以 60fps 调用,
+ // 行元素随重绘缓存——applyActiveLyric 由 rAF 以 60fps 调用,
// 原实现每帧 querySelectorAll 全部歌词行(百行 × 60fps),低端机无谓 CPU
state.lastActiveLine = -1;
state.ktvFillIdx = -1;
@@ -4500,10 +4500,10 @@ window.SITE_COOKIE_GATE = cookieGate;
var i = parseInt(line.getAttribute('data-i'), 10);
var audio = $('#player audio');
if (audio && audio.src && state.lyrics[i]) {
- // F111:点击定位与高亮使用互逆变换——高亮是 currentTime+offset,
+ // 点击定位与高亮使用互逆变换——高亮是 currentTime+offset,
// 定位就必须 currentTime = 行时间 - offset(原实现同向相加,点哪句跳错句)
audio.currentTime = Math.max(0, state.lyrics[i].time - state.lyricOffset);
- // P2-13:裸 play() 的 Promise 拒绝会喂错误上报(token 过期后点歌词必现)
+ // 裸 play() 的 Promise 拒绝会喂错误上报(token 过期后点歌词必现)
if (audio.paused) audio.play().catch(function () {});
}
});
@@ -4537,7 +4537,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function setLyricFsPop(open) {
$('#lyric-fs-pop').hidden = !open;
$('#lyric-fontsize').setAttribute('aria-expanded', open ? 'true' : 'false');
- // R5-3:弹层开着时隐藏排队/任务浮窗,贴顶场景不再被盖住
+ // 弹层开着时隐藏排队/任务浮窗,贴顶场景不再被盖住
document.documentElement.classList.toggle('fs-pop-open', open);
if (open) applyLyricFont();
}
@@ -4561,7 +4561,7 @@ window.SITE_COOKIE_GATE = cookieGate;
if (e.target.closest('#lyric-fs-pop') || e.target.closest('#lyric-fontsize')) return;
setLyricFsPop(false);
}, true);
- // R5-12:捕获期监听(先于 F110 的冒泡处理器执行)——抽屉/模态开着时让上层
+ // 捕获期监听(先于冒泡处理器执行)——抽屉/模态开着时让上层
// 先消费 Escape 并跳过,避免同一次按键把键盘焦点偷换到遮罩后面的 A± 按钮
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && lyricFsPopOpen()) {
@@ -4608,7 +4608,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
function applyActiveLyric(active, t) {
- // P3-14:优先用缓存行(renderLyrics 时重建),缓存缺失(外部清空 lyric-box 等)才回退查询
+ // 优先用缓存行(renderLyrics 时重建),缓存缺失(外部清空 lyric-box 等)才回退查询
var lines = state.ktvLineEls && state.ktvLineEls.length ? state.ktvLineEls
: Array.prototype.slice.call($('#lyric-box').children);
for (var j = 0; j < lines.length; j++) {
@@ -4655,16 +4655,16 @@ window.SITE_COOKIE_GATE = cookieGate;
state.commentTotal = 0;
}
var offset = (page - 1) * 10;
- state._commentsLoading = true; // F105:防止评论开关快速切换时重复加载
- // 第 97 条(P3):评论翻页代次守卫——快速连点下一页时迟到旧页响应
- // 不得把页码与内容一起回滚(F51 只防了切歌,这里补同歌翻页)
+ state._commentsLoading = true; // 防止评论开关快速切换时重复加载
+ // 评论翻页代次守卫——快速连点下一页时迟到旧页响应
+ // 不得把页码与内容一起回滚(只防了切歌,这里补同歌翻页)
state._commentGen = (state._commentGen || 0) + 1;
var gen = state._commentGen;
apiPost('/comments', { id: songId, limit: 10, offset: offset })
.then(function (data) {
if (gen !== state._commentGen) return; // 迟到响应丢弃
if (data.status !== 200) throw new Error(data.message || '获取评论失败');
- // F51:迟到响应不得覆盖已切歌的评论区(评论对象以 songId 为准)
+ // 迟到响应不得覆盖已切歌的评论区(评论对象以 songId 为准)
if (state.currentSong && String(state.currentSong.id) !== String(songId)) return;
var d = data.data;
state.commentTotal = d.total || 0;
@@ -4861,7 +4861,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function initPlayer(url, name, artist, cover) {
var player = $('#player');
player.hidden = false;
- // B6:与播客试听互斥——开播音乐先停播客音频(反向在 podcastRenderProgram 处理)
+ // 与播客试听互斥——开播音乐先停播客音频(反向在 podcastRenderProgram 处理)
try {
var pa = $('#podcast-audio');
if (pa && !pa.paused) pa.pause();
@@ -4881,7 +4881,7 @@ window.SITE_COOKIE_GATE = cookieGate;
$('#player-current').textContent = '00:00';
$('#player-progress-fill').style.width = '0%';
playerState.url = url;
- // F56:保留用户上一次的音量/静音选择(原实现每次切歌都重置为 0.9/取消静音)
+ // 保留用户上一次的音量/静音选择(原实现每次切歌都重置为 0.9/取消静音)
if (typeof playerState.volume !== 'number') { playerState.volume = 0.9; playerState.muted = false; }
updateVolumeUI();
updateMuteUI();
@@ -4894,7 +4894,7 @@ window.SITE_COOKIE_GATE = cookieGate;
album: (state.currentSong && state.currentSong.al_name) || '',
artwork: cover ? [{ src: cover, sizes: '512x512' }] : []
});
- // F56:系统媒体的 play/pause 分别对应明确的播放/暂停(toggle 不幂等,
+ // 系统媒体的 play/pause 分别对应明确的播放/暂停(toggle 不幂等,
// 面板点"暂停"曾被切换成继续播放)
navigator.mediaSession.setActionHandler('play', function () {
var a = ensureAudio();
@@ -4910,7 +4910,7 @@ window.SITE_COOKIE_GATE = cookieGate;
});
} catch (e) {}
} else if ('mediaSession' in navigator) {
- // F56:关闭 SMTC 设置时撤销既有处理器
+ // 关闭 SMTC 设置时撤销既有处理器
try {
navigator.mediaSession.setActionHandler('play', null);
navigator.mediaSession.setActionHandler('pause', null);
@@ -4953,7 +4953,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var last = parseFloat(localStorage.getItem(key) || '0');
if (Math.abs(audio.currentTime - last) > 5) {
localStorage.setItem(key, audio.currentTime);
- // P3-15:进度键无界清理(跳过场景永不删,长期用户无限累积)
+ // 进度键无界清理(跳过场景永不删,长期用户无限累积)
// 超过 100 个键时清掉当前曲之外的全部(进度记忆为锦上添花)
var pk = [];
for (var i = 0; i < localStorage.length; i++) {
@@ -4995,11 +4995,11 @@ window.SITE_COOKIE_GATE = cookieGate;
state.global.playQueueIndex = (state.global.playQueueIndex || 0) + 1;
if (state.global.playQueueIndex < gq.length) {
var nx = gq[state.global.playQueueIndex];
- // F56:连播切歌同步 currentSong/进度键——原实现 currentSong
+ // 连播切歌同步 currentSong/进度键——原实现 currentSong
// 仍是上一首,B 的播放进度会写进 A 的存储键
state.currentSong = { id: nx.id, name: nx.name, ar_name: nx.artists || '', al_name: nx.album || '' };
initPlayer(nx.url || '', nx.name || '', nx.artists || '', nx.picUrl || '');
- // P3-36:连播不带歌词/评论数据——清空旧歌词与评论区(原实现
+ // 连播不带歌词/评论数据——清空旧歌词与评论区(原实现
// 第二首起歌词还是上一首且 KTV 错位高亮、评论区不换)
state.rawLyric = null;
state.rawTlyric = null;
@@ -5007,8 +5007,8 @@ window.SITE_COOKIE_GATE = cookieGate;
state.lyrics = [];
renderLyrics();
$('#comment-panel').hidden = true;
- // F3/F7:连播换歌同样清评论计数并重置歌词偏移——否则 A 的旧
- // commentTotal 挡住 F105 补加载(开关一扳就复活 A 的评论区),
+ // 连播换歌同样清评论计数并重置歌词偏移——否则 A 的旧
+ // commentTotal 挡住补加载(开关一扳就复活 A 的评论区),
// A 校准过的偏移也会带进 B 造成整片高亮错位
state.commentTotal = 0;
state.commentPage = 1;
@@ -5017,7 +5017,7 @@ window.SITE_COOKIE_GATE = cookieGate;
$('#comment-all-list').innerHTML = '';
state.lyricOffset = 0;
$('#lyric-offset-label').textContent = '偏移 0.00s';
- // P3-37:连播语义=顺序完整播放,跳过该曲的历史进度续播
+ // 连播语义=顺序完整播放,跳过该曲的历史进度续播
// (一次性标志,togglePlay 内消费;用户进度键保留不动)
state._queueNoResume = true;
toast('▶ 正在播放:' + (nx.name || ''), 'info');
@@ -5051,7 +5051,7 @@ window.SITE_COOKIE_GATE = cookieGate;
if (audio.paused && !audio.src) {
audio.src = playerState.url;
if (state.settings.progressMemory && !state._queueNoResume) {
- // P3-37:连播场景(_queueNoResume)跳过续播,从头完整播放
+ // 连播场景(_queueNoResume)跳过续播,从头完整播放
try {
var saved = parseFloat(localStorage.getItem(key));
if (saved > 0) audio.currentTime = saved; // 续播:恢复到上次播放位置
@@ -5080,7 +5080,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var audio = ensureAudio();
if (audio.src) {
audio.currentTime = 0;
- // P2-13:裸 play() 同样会喂 unhandledrejection 上报
+ // 裸 play() 同样会喂 unhandledrejection 上报
if (audio.paused) audio.play().catch(function () {});
} else {
togglePlay();
@@ -5129,8 +5129,8 @@ window.SITE_COOKIE_GATE = cookieGate;
}
function doDownload(id, name, level) {
- // P3-39:同曲在途去重——双击「下载」会提交两个任务扣两次费
- // (批量入口当年做了 S10 去重,单曲入口漏了同型)
+ // 同曲在途去重——双击「下载」会提交两个任务扣两次费
+ // (批量入口当年做了去重,单曲入口漏了同型)
var inFlight = tasks.some(function (t) {
return t.label === ('单曲 · ' + (name || id)) &&
(t.status === 'queued' || t.status === 'running' || t.status === 'paused');
@@ -5190,10 +5190,10 @@ window.SITE_COOKIE_GATE = cookieGate;
}
return lines.map(function (l, i) {
var end = lines[i + 1] ? lines[i + 1].time : l.time + 5;
- // P3-40:同时间多行给 1 秒最小区间(F37 前端同型漏网——
+ // 同时间多行给 1 秒最小区间(前端同型漏网——
// 后端当年修了倒挂区间,前端 lyricToFormat 没修,部分播放器拒收)
if (end <= l.time) end = l.time + 1;
- // R5-2:SRT 支持块内多行——双行模式原文/译文各占一行,不再删换行粘连
+ // SRT 支持块内多行——双行模式原文/译文各占一行,不再删换行粘连
return (i + 1) + '\n' + srtTime(l.time) + ' --> ' + srtTime(end) + '\n' + l.text + '\n';
}).join('\n');
}
@@ -5201,7 +5201,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var m = Math.floor(l.time / 60);
var s = l.time - m * 60;
var ms = Math.round((s - Math.floor(s)) * 1000);
- // R5-2:双行模式换行改为" / "分隔(原实现直接删除,原文译文无缝粘连)
+ // 双行模式换行改为" / "分隔(原实现直接删除,原文译文无缝粘连)
return '[' + String(m).padStart(2, '0') + ':' + String(Math.floor(s)).padStart(2, '0') +
'.' + String(Math.min(ms, 999)).padStart(3, '0') + ']' + l.text.replace(/\n/g, ' / ');
}).join('\n');
@@ -5231,7 +5231,7 @@ window.SITE_COOKIE_GATE = cookieGate;
ta.value = text;
document.body.appendChild(ta);
ta.select();
- // F100:execCommand 返回 false 也是失败(原实现只在抛异常时报错,静默失败仍提示成功)
+ // execCommand 返回 false 也是失败(原实现只在抛异常时报错,静默失败仍提示成功)
try {
var ok = document.execCommand('copy');
if (ok) toast('已复制到剪贴板', 'success');
@@ -5242,7 +5242,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
/* ================= 歌单解析 ================= */
- // B5:合集渲染代次守卫——快速切换解析目标时,迟到的旧响应不再覆盖新页面
+ // 合集渲染代次守卫——快速切换解析目标时,迟到的旧响应不再覆盖新页面
function _collGen(type) {
state.collGen = state.collGen || {};
state.collGen[type] = (state.collGen[type] || 0) + 1;
@@ -5378,7 +5378,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var tracks = info.tracks || info.songs || [];
var isPlaylist = prefix === 'playlist';
- // B4:残缺歌单显式提示(后端标记 trackIds 与实际拿到的曲目数不一致)
+ // 残缺歌单显式提示(后端标记 trackIds 与实际拿到的曲目数不一致)
if (info.incomplete && info.missing) {
toast('注意:该歌单不完整,缺少 ' + info.missing + ' 首(可能版权受限或接口失败)', 'warning');
}
@@ -5480,13 +5480,13 @@ window.SITE_COOKIE_GATE = cookieGate;
.then(function (data) {
if (data.status !== 200) throw new Error(data.message || '获取榜单失败');
var list = data.data || [];
- // F58:空结果也标记为已加载(原文案"暂无榜单"可见即可),
+ // 空结果也标记为已加载(原文案"暂无榜单"可见即可),
// 但 loading/error 节点在完成后不再被 finally 一律隐藏
state.toplistLoaded = true;
renderToplists(list);
})
.catch(function (err) {
- // F58:失败态保持可见(错误信息 + 重试按钮),不被 finally 藏起来
+ // 失败态保持可见(错误信息 + 重试按钮),不被 finally 藏起来
$('#toplist-loading').hidden = false;
$('#toplist-loading').innerHTML = '
📊
' + escapeHtml(err.message || '获取榜单失败') + '
';
});
@@ -5500,7 +5500,7 @@ window.SITE_COOKIE_GATE = cookieGate;
$('#toplist-loading').innerHTML = '
📊
暂无榜单数据
';
return;
}
- $('#toplist-loading').hidden = true; // F58:有数据时才隐藏提示区
+ $('#toplist-loading').hidden = true; // 有数据时才隐藏提示区
grid.hidden = false;
grid.innerHTML = list.map(function (t) {
return '
' +
@@ -5524,7 +5524,7 @@ window.SITE_COOKIE_GATE = cookieGate;
});
document.addEventListener('click', function (e) {
if (e.target.closest('#toplist-retry')) {
- // F58:重试必须重置"已加载"标志(原实现标志仍真,重试是空操作)
+ // 重试必须重置"已加载"标志(原实现标志仍真,重试是空操作)
state.toplistLoaded = false;
$('#toplist-loading').innerHTML = '';
loadToplists();
@@ -5556,7 +5556,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var _podcastHotBusy = false;
function loadPodcastHot(cateId, append) {
var grid = $('#podcast-hot-grid');
- // S2:在途请求锁——双击"加载更多"不再用同一 offset 重复请求
+ // 在途请求锁——双击"加载更多"不再用同一 offset 重复请求
if (append && _podcastHotBusy) return;
var loadingHtml = '
';
if (!append) grid.innerHTML = loadingHtml;
@@ -5573,7 +5573,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var html = (d.radios || []).map(podcastRadioCardHtml).join('');
if (append) grid.insertAdjacentHTML('beforeend', html);
else grid.innerHTML = html || '
';
- // S2:按服务端 has_more 显隐(空页/短页没有更多)
+ // 按服务端 has_more 显隐(空页/短页没有更多)
$('#podcast-hot-more-wrap').hidden = !(d.radios || []).length || !d.has_more;
})
.catch(function (err) {
@@ -5603,11 +5603,11 @@ window.SITE_COOKIE_GATE = cookieGate;
var _podcastGen = 0;
function podcastParse() {
if (!requireLogin()) return;
- if (!cookieGate()) return; // 第 102 条(U15 拍板):播客入口补齐撤回闸(原音乐四入口有、播客三入口漏)
+ if (!cookieGate()) return; // 播客入口补齐撤回闸(原音乐四入口有、播客三入口漏)
var raw = $('#podcast-input').value.trim();
if (!raw) { toast('请输入播客电台/单集链接或ID', 'warning'); return; }
var btn = $('#podcast-btn');
- var gen = ++_podcastGen; // 第 97 条(P2):代次守卫——回车绕过按钮禁用,迟到响应不得覆盖新解析
+ var gen = ++_podcastGen; // 代次守卫——回车绕过按钮禁用,迟到响应不得覆盖新解析
setBtnLoading(btn, true, '解析中...');
podcastShowLoading(true);
$('#podcast-radio-result').hidden = true;
@@ -5625,7 +5625,7 @@ window.SITE_COOKIE_GATE = cookieGate;
toast(err.message || '解析失败', 'error');
})
.finally(function () {
- // 第 97 条(对抗复审):按钮无条件配对复位(重入计数器,见 doSearch);
+ // 按钮无条件配对复位(重入计数器,见 doSearch);
// 全局 spinner 保持代次门——旧请求不提前收起新请求的 loading
setBtnLoading(btn, false);
if (gen === _podcastGen) podcastShowLoading(false);
@@ -5691,7 +5691,7 @@ window.SITE_COOKIE_GATE = cookieGate;
var _podcastMoreBusy = false;
$('#podcast-more').addEventListener('click', function () {
- // F57:在途请求锁——双击"加载更多"不再用同一 offset 发两次请求
+ // 在途请求锁——双击"加载更多"不再用同一 offset 发两次请求
//(原实现两次 append 相同页后 offset 跳两页,漏掉一页)
if (_podcastMoreBusy) return;
_podcastMoreBusy = true;
@@ -5721,7 +5721,7 @@ window.SITE_COOKIE_GATE = cookieGate;
state.podcast.offset = (d.programs || []).length;
renderPodcastEpisodes(d.programs || [], false);
}).catch(function (err) {
- // F57:排序失败回滚状态与按钮文案(原实现失败后界面方向与数据不一致)
+ // 排序失败回滚状态与按钮文案(原实现失败后界面方向与数据不一致)
state.podcast.asc = prevAsc;
state.podcast.offset = prevOffset;
// 原实现的 [0] 取值恒为 undefined 导致短路,恢复文案从未生效
@@ -5733,15 +5733,15 @@ window.SITE_COOKIE_GATE = cookieGate;
/* ---- 整台打包 ---- */
$('#podcast-radio-zip').addEventListener('click', function () {
- if (!cookieGate()) return; // 第 102 条(U15 拍板):整台打包补齐撤回闸
+ if (!cookieGate()) return; // 整台打包补齐撤回闸
if (!state.podcast.radioId) return;
var name = (state.podcast.radio && state.podcast.radio.name) || state.podcast.radioId;
var credits = (window.getSITECredits && window.getSITECredits()) || 0;
- // F57:预检上限取 min(实际集数, 200)——原实现固定按 200 集比较,
+ // 预检上限取 min(实际集数, 200)——原实现固定按 200 集比较,
// 3 集的小电台也会误报"不足以打包全部单集"
- var limit = Math.min(500, state.podcast.count || 200); // 第 98 条:与后端 500 上限对齐(原 200 使 201-500 集电台永远打不满)
+ var limit = Math.min(500, state.podcast.count || 200); // 与后端 500 上限对齐(原 200 使 201-500 集电台永远打不满)
if (credits < 0) {
- // P3-24:未登录明确拦截(原实现游客 -1 穿过预检,提交后才吃 401)
+ // 未登录明确拦截(原实现游客 -1 穿过预检,提交后才吃 401)
if (window.handleAuthExpired) window.handleAuthExpired('请先登录后再使用打包下载', false);
return;
}
@@ -5802,14 +5802,14 @@ window.SITE_COOKIE_GATE = cookieGate;
: '
未获取到音频直链';
var player = $('#podcast-audio');
if (canPlay) {
- // B6:与音乐播放器互斥——开播播客先暂停音乐
+ // 与音乐播放器互斥——开播播客先暂停音乐
try {
var ma = $('#player audio');
if (ma && !ma.paused) ma.pause();
} catch (e) {}
player.src = audio.url;
player.hidden = false;
- // P3-38:试听音量随主播放器(原实现原生控件独立,主播放器调低音量后
+ // 试听音量随主播放器(原实现原生控件独立,主播放器调低音量后
// 切播客试听仍 100% 首响)
try {
if (typeof playerState.volume === 'number') {
@@ -5819,7 +5819,7 @@ window.SITE_COOKIE_GATE = cookieGate;
} catch (e) {}
openBtn.href = audio.url;
openBtn.hidden = false;
- // T5:试听链接 36 分钟过期(=35 分钟保留期+60s),失效时给出明确指引(重新解析即可刷新)
+ // 试听链接 36 分钟过期(=35 分钟保留期+60s),失效时给出明确指引(重新解析即可刷新)
player.onerror = function () {
toast('试听链接已过期或失效,请重新点击「试听」刷新', 'warning');
};
@@ -5870,7 +5870,7 @@ window.SITE_COOKIE_GATE = cookieGate;
/* ---- 单集下载 ---- */
function podcastDownloadEpisode(pid, name) {
if (!requireLogin()) return;
- if (!cookieGate()) return; // 第 102 条(U15 拍板):单集下载补齐撤回闸
+ if (!cookieGate()) return; // 单集下载补齐撤回闸
var taskId = genTaskId();
apiPost('/podcast/download', {
id: pid,
@@ -5977,7 +5977,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
/* ================= 管理员设置(二维码登录 / Cookie 管理) ================= */
- // F54:旧版前端写死口令已废除——本面板全部接口仅认站点管理员会话;
+ // 旧版前端写死口令已废除——本面板全部接口仅认站点管理员会话;
// 入口改为直接用会话探测(管理员已登录 site_uid 即放行,否则引导去管理后台)
function adminEnter() {
@@ -6055,7 +6055,7 @@ window.SITE_COOKIE_GATE = cookieGate;
$('#admin-qr-status').style.color = '';
toast('二维码已生成,请用手机扫码', 'info');
adminStopQrPoll();
- // 第 97 条(P2):换新码即作废旧 unikey 的在途 check 响应(代次令牌)
+ // 换新码即作废旧 unikey 的在途 check 响应(代次令牌)
state.adminQrGen = (state.adminQrGen || 0) + 1;
state.adminQrTimer = setInterval(adminQrPoll, 3000);
})
@@ -6067,7 +6067,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function adminQrPoll() {
if (!state.adminUnikey) { adminStopQrPoll(); return; }
- // 第 97 条(P2):代次令牌——删除 Cookie/重新生成二维码后,旧 unikey 的
+ // 代次令牌——删除 Cookie/重新生成二维码后,旧 unikey 的
// 在途 check 响应不得杀掉新一轮轮询或误报「登录成功,Cookie 已自动保存」
state.adminQrGen = (state.adminQrGen || 0) + 1;
var gen = state.adminQrGen;
@@ -6120,7 +6120,7 @@ window.SITE_COOKIE_GATE = cookieGate;
.then(function (data) {
if (data.status !== 200) throw new Error(data.message || '删除失败');
adminStopQrPoll();
- // 第 97 条(P2):删除后作废在途 check 响应(代次令牌)
+ // 删除后作废在途 check 响应(代次令牌)
state.adminQrGen = (state.adminQrGen || 0) + 1;
$('#admin-qr-area').hidden = true;
state.adminUnikey = '';
@@ -6323,7 +6323,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function personalOpenPlaylist(id, name) {
if (!id) return;
state.personal.current = { id: id, name: name };
- var gen = _collGen('personal'); // B5 同型:迟到响应不得覆盖新打开的歌单
+ var gen = _collGen('personal'); // 迟到响应不得覆盖新打开的歌单
$('#personal-tracks-wrap').hidden = false;
$('#personal-tracks-title').textContent = '歌单歌曲:' + (name || id);
$('#personal-tracks').innerHTML = '
';
@@ -6342,7 +6342,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function personalRenderTracks(info) {
var tracks = info.tracks || [];
- // F51:空列表同样走 renderPagedTracks——清空 tracks_personal 状态与分页条,
+ // 空列表同样走 renderPagedTracks——清空 tracks_personal 状态与分页条,
// 原实现提前 return,旧歌单的下载数据残留(批量下载会下到旧歌)
renderPagedTracks('personal', tracks, 1);
}
@@ -6368,7 +6368,7 @@ window.SITE_COOKIE_GATE = cookieGate;
$('#personal-qr-status').style.color = '';
toast('二维码已生成,请用手机扫码', 'info');
personalStopQrPoll();
- state.personal.qrGen = (state.personal.qrGen || 0) + 1; // F70:新轮询代次
+ state.personal.qrGen = (state.personal.qrGen || 0) + 1; // 新轮询代次
state.personal.qrTimer = setInterval(personalQrPoll, 3000);
})
.catch(function (err) {
@@ -6379,7 +6379,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function personalQrPoll() {
if (!state.personal.unikey) { personalStopQrPoll(); return; }
- var myGen = state.personal.qrGen; // F70:本次轮询所属代次,退出后作废
+ var myGen = state.personal.qrGen; // 本次轮询所属代次,退出后作废
apiPost('/user/qr/check', { unikey: state.personal.unikey })
.then(function (data) {
if (myGen !== state.personal.qrGen) return; // 期间已退出/重开,丢弃迟到响应
@@ -6399,7 +6399,7 @@ window.SITE_COOKIE_GATE = cookieGate;
} else if (st === 'success') {
personalStopQrPoll();
// 个人登录 Cookie 保存在本机浏览器;
- // 站点账号登录时后端会同步一份到站点库供管理后台查看(第 98 条口径对齐)
+ // 站点账号登录时后端会同步一份到站点库供管理后台查看(口径对齐)
if (data.data && data.data.cookie) {
state.personal.cookie = data.data.cookie;
try { localStorage.setItem('netease_user_cookie', state.personal.cookie); } catch (e) {}
@@ -6430,7 +6430,7 @@ window.SITE_COOKIE_GATE = cookieGate;
.then(function (data) {
if (data.status !== 200) throw new Error(data.message || '退出失败');
personalStopQrPoll();
- state.personal.qrGen = (state.personal.qrGen || 0) + 1; // F70:作废在途轮询响应
+ state.personal.qrGen = (state.personal.qrGen || 0) + 1; // 作废在途轮询响应
state.personal.authed = false;
state.personal.user = null;
state.personal.playlists = [];
@@ -6463,7 +6463,7 @@ window.SITE_COOKIE_GATE = cookieGate;
// 歌单列表点击查看
$('#personal-playlists').addEventListener('click', function (e) {
- // F89:取消收藏按钮的点击不触发"打开歌单"(两条无关操作不再同时发生)
+ // 取消收藏按钮的点击不触发"打开歌单"(两条无关操作不再同时发生)
if (e.target.closest('.act-uncollect')) return;
var btn = e.target.closest('.act-personal-open');
var row = e.target.closest('.personal-pl');
@@ -6574,7 +6574,7 @@ window.SITE_COOKIE_GATE = cookieGate;
function globalOpenPlaylist(id, name) {
if (!id) return;
state.global.current = { id: id, name: name };
- var gen = _collGen('global'); // B5 同型:迟到响应不得覆盖新打开的歌单
+ var gen = _collGen('global'); // 迟到响应不得覆盖新打开的歌单
$('#global-tracks-wrap').hidden = false;
$('#global-tracks-title').textContent = '歌单歌曲:' + (name || id);
$('#global-tracks').innerHTML = '
';
@@ -6596,18 +6596,18 @@ window.SITE_COOKIE_GATE = cookieGate;
function globalRenderTracks(info) {
var tracks = info.tracks || [];
- // F51:空列表同样走 renderPagedTracks,清空 tracks_global 状态(同 personal 侧)
+ // 空列表同样走 renderPagedTracks,清空 tracks_global 状态(同 personal 侧)
renderPagedTracks('global', tracks, 1);
}
/* 播放歌单:调用公开播放 API 获取可播放直链,加载第一首进播放器并支持自动连播。
- 第 93 条(P1-1 契约断裂修复):端点自第 78 条起强制校验 `key`——原实现不带
+ 端点强制校验 `key`——原实现不带
key,管理员点「播放」100% 被 401 拒绝。此处先取播放密钥再附带请求。 */
function globalPlayPlaylist(id, name, btn) {
if (!id) { toast('缺少歌单 ID', 'warning'); return; }
var lvl = $('#global-level').value;
if (lvl === 'auto') lvl = (state.levelsOk && state.levelsOk.length ? state.levelsOk[0] : 'lossless');
- // 第 97 条(P3):代次守卫——行内 ▶ 与底部播放按钮双入口可并发,
+ // 代次守卫——行内 ▶ 与底部播放按钮双入口可并发,
// 迟到旧队列不得覆盖 playQueue(否则连播接着播错歌单的歌)
state._globalPlayGen = (state._globalPlayGen || 0) + 1;
var gen = state._globalPlayGen;
@@ -6637,7 +6637,7 @@ window.SITE_COOKIE_GATE = cookieGate;
toast(err.message || '获取播放列表失败', 'error');
})
.finally(function () {
- // 第 97 条(对抗复审):双入口是不同按钮——每个调用无条件复位自己的
+ // 双入口是不同按钮——每个调用无条件复位自己的
// 按钮(重入计数器配对),否则被作废旧入口的按钮永久卡"解析中..."
if (btn) setBtnLoading(btn, false);
});
@@ -6648,11 +6648,11 @@ window.SITE_COOKIE_GATE = cookieGate;
var base = (location.origin + location.pathname).replace(/\/+$/, '');
var apiUrl = base + '/api/playlist/play';
var url = apiUrl + '?id=' + encodeURIComponent(playlistId || '');
- // 第 98 条:代次守卫——快速连开两个歌单的 API 弹窗时,迟到响应
+ // 代次守卫——快速连开两个歌单的 API 弹窗时,迟到响应
// 不得把复制地址写回旧歌单(复制出去即错链)
state._apiModalGen = (state._apiModalGen || 0) + 1;
var gen = state._apiModalGen;
- // 第 78 条:密钥实时取(管理端点仅站点管理员可达),弹窗里的地址总是可直接使用
+ // 密钥实时取(管理端点仅站点管理员可达),弹窗里的地址总是可直接使用
apiPost('/admin/site/play_key/get', {}).then(function (d) {
if (gen !== state._apiModalGen) return; // 迟到响应丢弃
var key = (d && d.data && d.data.key) || '';
@@ -6679,7 +6679,7 @@ window.SITE_COOKIE_GATE = cookieGate;
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
- // F100:execCommand 返回 false 也是失败,不再谎报已复制
+ // execCommand 返回 false 也是失败,不再谎报已复制
try {
var ok = document.execCommand('copy');
if (ok) toast(tip || '已复制', 'success');
@@ -6746,7 +6746,7 @@ window.SITE_COOKIE_GATE = cookieGate;
return String(p.id) === String(c.id);
});
if (c.type === 'album') {
- // F101:专辑收藏状态依据本会话收藏结果,不再无条件还原为未收藏
+ // 专辑收藏状态依据本会话收藏结果,不再无条件还原为未收藏
if (c.albumCollected) {
$('#collect-album-btn').textContent = '✓ 已收藏专辑';
$('#collect-album-btn').classList.add('btn-success');
@@ -6780,7 +6780,7 @@ window.SITE_COOKIE_GATE = cookieGate;
.then(function (data) {
if (data.status !== 200) throw new Error(data.message || tip + '失败');
toast(action === 1 ? '✅ 已收藏到你的个人歌单' : '已取消收藏', 'success');
- // F101:专辑收藏结果记入 currentCollection,供 updateCollectButtons 正确渲染
+ // 专辑收藏结果记入 currentCollection,供 updateCollectButtons 正确渲染
if (state.currentCollection && String(state.currentCollection.id) === String(id)) {
state.currentCollection.albumCollected = (type === 'album' && action === 1);
}
@@ -6844,7 +6844,7 @@ window.SITE_COOKIE_GATE = cookieGate;
/* ================= 批量下载 ================= */
/* 次数不足时的选择框:仅下载前 credits 首 / 取消 */
function confirmPartialDownload(need, credits, cb) {
- // P3-8:先移除残留实例——连点两次下载会叠两个弹窗,$('#cpd-ok')
+ // 先移除残留实例——连点两次下载会叠两个弹窗,$('#cpd-ok')
// 永远命中第一个,第二个弹窗按钮失效(遮罩还盖着页面)
var old = document.querySelector('.modal-mask[data-dynamic="1"].show');
if (old) old.remove();
@@ -6882,7 +6882,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
function batchDownload(btn, tracks, _prechecked) {
- // S10:入口按 ID 去重(服务端同样去重计费,预检与实际口径一致)
+ // 入口按 ID 去重(服务端同样去重计费,预检与实际口径一致)
var seen = {};
var list = (tracks || []).filter(function (t) {
if (!t || !t.id || seen[t.id]) return false;
@@ -6902,7 +6902,7 @@ window.SITE_COOKIE_GATE = cookieGate;
if (state.settings.batchZip) {
setBtnLoading(btn, true, '打包中...');
var cfg = downloadConfig();
- // F87:批量模式的元数据字段跟随"批量写入"开关,不受单曲开关影响
+ // 批量模式的元数据字段跟随"批量写入"开关,不受单曲开关影响
//(原实现单曲关闭时这里会提交 __none__,批量开启也写不进任何字段)
var batchFields = Object.keys(state.settings.metaFields).filter(function (k) {
return state.settings.metaFields[k];
@@ -6945,7 +6945,7 @@ window.SITE_COOKIE_GATE = cookieGate;
// 逐个下载
setBtnLoading(btn, true, '批量下载中...');
- var i = 0, okCount = 0, failCount = 0, unknownCount = 0; // F50/B7:逐首结果如实汇总
+ var i = 0, okCount = 0, failCount = 0, unknownCount = 0; // 逐首结果如实汇总
function next() {
if (i >= list.length) {
setBtnLoading(btn, false);
@@ -6976,7 +6976,7 @@ window.SITE_COOKIE_GATE = cookieGate;
apiResolveLevel(song.id)
.then(function (level) {
var cfg = downloadConfig();
- // B1:逐首分支的元数据字段同样跟随批量开关(与 ZIP 分支同口径)
+ // 逐首分支的元数据字段同样跟随批量开关(与 ZIP 分支同口径)
var oneFields = Object.keys(state.settings.metaFields).filter(function (k) {
return state.settings.metaFields[k];
});
@@ -7007,7 +7007,7 @@ window.SITE_COOKIE_GATE = cookieGate;
}
if (data.status !== 200) {
toast(data.message || ('下载失败:' + (song.name || song.id)), 'error');
- done(false); // F50:提交失败如实计为失败
+ done(false); // 提交失败如实计为失败
return;
}
// 任务加入悬浮窗;完成后自动下载该文件并继续下一首
@@ -7015,7 +7015,7 @@ window.SITE_COOKIE_GATE = cookieGate;
id: taskId, type: 'single',
label: '单曲 · ' + (song.name || song.id),
total: 1, done: 0, status: 'queued',
- doneCb: function (success) { done(success); } // B7:透传 true/false/'unknown'
+ doneCb: function (success) { done(success); } // 透传 true/false/'unknown'
});
if (window.refreshSITECredits) window.refreshSITECredits(); // 立即同步顶栏剩余次数
}).catch(function () {
@@ -7085,7 +7085,7 @@ window.SITE_COOKIE_GATE = cookieGate;
});
_scrollLockMO.observe(document.body, { attributes: true, attributeFilter: ['class'], subtree: true });
} catch (e) {}
- // 第 81 条:删除注册四框的 Enter→提交冗余绑定——site_auth.js:988 已对
+ // 删除注册四框的 Enter→提交冗余绑定——site_auth.js:988 已对
// 全部四框绑 Enter→doRegister,此处旧绑定只挂 #reg-code,两处叠加导致
// 一次回车 doRegister 执行两次(第二请求被验证码原子消费挡下但报错闪烁)
// 播放器缓冲预览:进度条底层显示已缓冲长度(CSS 变量 --buffered,纯读取 audio.buffered,不改播放逻辑)
@@ -7313,10 +7313,10 @@ window.SITE_COOKIE_GATE = cookieGate;
renderHistory();
// 重新打开网站:找回本设备未结束的下载任务(断线自动恢复,接着下载)
- // A1/S11:登录后才会调用(游客请求该接口会 401 并误弹登录框);
+ // 登录后才会调用(游客请求该接口会 401 并误弹登录框);
// 登录成功由 site_auth 的 afterAuth 回调再触发一次
window.SITE_TASKS_BOOTSTRAP = loadMyTasks;
- // 第 102 条(U15 拍板):站点退出时连带清除个人网易云登录——共享电脑场景下,
+ // 站点退出时连带清除个人网易云登录——共享电脑场景下,
// 下一位使用者不得沿用前一人的网易云身份(发评/收藏/看歌单)。
// 清理强度与 personalLogout 对齐(对抗复审):作废在途轮询(防 Cookie 写回)、
// 清个人面板数据与展示(防下一位用户闪现上一人的头像/歌单)
@@ -7383,7 +7383,7 @@ window.SITE_COOKIE_GATE = cookieGate;
/* ================= 下载任务悬浮窗(批量打包 + 单曲下载,可取消) ================= */
function getDeviceId() {
var key = 'netease_device_id';
- // F108:页面内存缓存兜底——localStorage 不可写时,同一页面内标识保持稳定,
+ // 页面内存缓存兜底——localStorage 不可写时,同一页面内标识保持稳定,
// 心跳/任务归属不再每次漂移
if (window.__deviceIdCache) return window.__deviceIdCache;
var id = null;
@@ -7439,7 +7439,7 @@ window.SITE_COOKIE_GATE = cookieGate;
? '
' : '';
var dismissBtn = t.claimable
? '
' : '';
- // F55:任务 ID 一律转义后再进属性(防属性注入)
+ // 任务 ID 一律转义后再进属性(防属性注入)
return '
' +
'
' +
'' + escapeHtml(t.label) + '' +
@@ -7462,7 +7462,7 @@ window.SITE_COOKIE_GATE = cookieGate;
});
});
}
- var _pollBusy = false; // P3-9:在途锁——15s 超时下轮询响应重叠时,同一终态会弹两次 toast
+ var _pollBusy = false; // 在途锁——15s 超时下轮询响应重叠时,同一终态会弹两次 toast
function pollTasks() {
if (_pollBusy) return;
var active = tasks.filter(function (t) {
@@ -7479,19 +7479,19 @@ window.SITE_COOKIE_GATE = cookieGate;
apiPost('/api/batch_progress', { task_id: t.id })
.then(function (data) {
if (data.status === 404) {
- // 任务已被服务端清理(过期):只收尾一次(F50:404 重叠不再重复推进队列)
+ // 任务已被服务端清理(过期):只收尾一次(404 重叠不再重复推进队列)
if (t._gone) return;
t._gone = true;
toast('任务已过期:' + t.label, 'info');
removeTaskLater(t.id, 500);
renderTaskPanel();
- // B7:过期=交付结果未知,明确以 'unknown' 传给逐首队列(不再虚报成功)
+ // 过期=交付结果未知,明确以 'unknown' 传给逐首队列(不再虚报成功)
if (typeof t.doneCb === 'function') { t.doneCb('unknown'); }
return;
}
if (data.status !== 200) return;
var d = data.data || {};
- // F51/F95:陈旧响应不得把终态任务拉回运行中(以 _done/_ended 为准);
+ // 陈旧响应不得把终态任务拉回运行中(以 _done/_ended 为准);
// total 同步服务端(播客总数解析后进度条口径一致)
if (t._done || t._ended) return;
t.done = d.done || 0;
@@ -7518,7 +7518,7 @@ window.SITE_COOKIE_GATE = cookieGate;
t._ended = true;
toast('下载失败:' + t.label + (d.msg ? '(' + d.msg + ')' : ''), 'error');
if (window.refreshSITECredits) window.refreshSITECredits(); // 同步顶栏次数(含退费)
- // F24/F50:失败但已有部分成果(has_result)→ 照常领取;
+ // 失败但已有部分成果(has_result)→ 照常领取;
// 逐首队列的回调也要推进并如实报失败(原实现 error/canceled 不调 doneCb,队列卡死)
if (d.has_result) { onTaskDone(t); }
else if (typeof t.doneCb === 'function') { t.doneCb(false); removeTaskLater(t.id, 8000); }
@@ -7575,7 +7575,7 @@ window.SITE_COOKIE_GATE = cookieGate;
// 完成条目保留「领取」入口(原实现 4 秒即移除——浏览器拦截自动下载时
// 用户毫无补救办法,文件在服务端留 35 分钟却取不到)
t.claimable = true;
- removeTaskLater(t.id, 35 * 60 * 1000); // P1-3/D-1:与服务端 35 分钟保留期完全一致(原 30 分钟末尾领取必 404)
+ removeTaskLater(t.id, 35 * 60 * 1000); // 与服务端 35 分钟保留期完全一致(原 30 分钟末尾领取必 404)
trimEndedTasks(20);
renderTaskPanel();
if (t.doneCb) t.doneCb(t);
@@ -7634,7 +7634,7 @@ window.SITE_COOKIE_GATE = cookieGate;
if (added) {
renderTaskPanel();
$('#task-panel').classList.add('show');
- // F49:恢复任务后启动持续轮询(原实现只查一次,
+ // 恢复任务后启动持续轮询(原实现只查一次,
// 刷新页面恢复的任务永远等不到 done)
if (!taskPollTimer) taskPollTimer = setInterval(pollTasks, 1500);
pollTasks();
diff --git a/tests/golden/responses.json b/tests/golden/responses.json
index 2f84aec..a3b54a2 100644
--- a/tests/golden/responses.json
+++ b/tests/golden/responses.json
@@ -534,7 +534,7 @@
"payload": null,
"status": 200,
"body": null,
- "html_len": 466261,
- "html_sha256": "5ba897c2f07b43b6c34e998505fea385cb6a6c8bfd5115d014f80044a38c6200"
+ "html_len": 464333,
+ "html_sha256": "0596d87ccbe8e7b5a19494c5d1ab1fd11bc95b82665ddc600ba90e4f2f790b06"
}
]
\ No newline at end of file
diff --git a/tests/test_golden.py b/tests/test_golden.py
index 72b89a9..3414712 100644
--- a/tests/test_golden.py
+++ b/tests/test_golden.py
@@ -8,6 +8,10 @@ tests/golden/responses.json 是**删除 Flask 之前**从 Flask 应用捕获的
说明:
- 会话 Cookie 在运行时用当前库的 login_seq 动态构造(快照不存 Cookie)。
- 请求为只读/确定性调用,不含实时上游数据(那些由 parity/单测覆盖)。
+- `index` 条目的 html_len/html_sha256 记的是**当前模板**渲染结果,不是 Flask 期数值
+ (该条目 body 为 null)。有意改模板后必须同步刷新这两项:
+ 服务端渲染 = templates/index.html 去掉末尾换行,故
+ html_len = 文件字节数 - 1,html_sha256 = sha256(文件去掉末尾换行)。
"""
import hashlib
import json