YouTube 评论增强:J/K 导航、大图展示评论、Z/X 点赞点踩、磨砂背景。
// ==UserScript==
// @name YouTube Comment Enhancer
// @namespace http://tampermonkey.net/
// @version 1.0
// @description YouTube 评论增强:J/K 导航、大图展示评论、Z/X 点赞点踩、磨砂背景。
// @author Antigravity
// @match *://www.youtube.com/watch*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/**
* [模块 0] 配置中心
*/
const SITE_CONFIG = {
name: "YouTube Comments",
selectors: {
item: 'ytd-comment-thread-renderer, ytd-comment-view-model',
author: '#author-text',
avatar: '#author-thumbnail img',
content: '#content-text',
replyContent: '#content-text',
likeBtn: '#like-button',
dislikeBtn: '#dislike-button',
translateBtn: '.translate-button, #translate-button, ytd-tri-state-button-view-model',
// 视频层级选择器
videoLikeBtn: 'segmented-like-dislike-button-view-model like-button-view-model button',
videoDislikeBtn: 'segmented-like-dislike-button-view-model dislike-button-view-model button',
subscribeBtn: 'ytd-subscribe-button-renderer button, #subscribe-button button'
},
theme: { accent: '#ff0000', overlayBg: 'rgba(0,0,0,0.77)', borderRadius: '24px' }
};
let policy = { createHTML: (s) => s };
if (window.trustedTypes?.createPolicy) {
try { policy = window.trustedTypes.createPolicy('enhancer-policy-watch', { createHTML: (s) => s }); } catch (e) { }
}
const setHTML = (el, html) => {
if (!el) return;
try { el.innerHTML = policy.createHTML(html); } catch (e) { el.textContent = html.replace(/<[^>]*>/g, ''); }
};
/**
* [模块 1] Core: 评论数据提取
*/
const App_Core = (() => {
return {
getCommentData: (card) => {
if (!card) return null;
const authorEl = card.querySelector(SITE_CONFIG.selectors.author);
const avatarEl = card.querySelector(SITE_CONFIG.selectors.avatar);
const contentEl = card.querySelector(SITE_CONFIG.selectors.content);
return {
row: card,
author: authorEl?.innerText.trim() || "User",
avatarUrl: avatarEl?.src || avatarEl?.getAttribute('src'),
content: contentEl?.innerText.trim() || ""
};
},
findComments: () => {
const all = Array.from(document.querySelectorAll(SITE_CONFIG.selectors.item));
return all.filter(c => {
// 核心过滤:排除嵌套关系,确保每个评论只抓取一次最外层
let p = c.parentElement;
while (p) { if (p.matches?.(SITE_CONFIG.selectors.item)) return false; p = p.parentElement; }
return c.offsetHeight > 20;
});
}
};
})();
/**
* [模块 2] UI: 评论大图预览
*/
const App_UI = (() => {
let overlay, vCard, vText, vAvatar, vAuthor, counter;
let isEnabled = true;
const injectCSS = () => {
if (document.getElementById('enhancer-watch-css')) return;
const s = document.createElement('style'); s.id = 'enhancer-watch-css';
s.textContent = `
.eh-comment-active { outline: 3px solid ${SITE_CONFIG.theme.accent} !important; border-radius: 8px !important; z-index: 10 !important; position: relative !important; }
.eh-comment-preview-card {
background: rgba(255,255,255,0.05);
padding: 40px;
border-radius: ${SITE_CONFIG.theme.borderRadius};
max-width: 800px;
width: 90%;
box-shadow: 0 40px 100px rgba(0,0,0,0.8);
border: 1px solid rgba(255,255,255,0.1);
display: flex;
flex-direction: column;
gap: 20px;
}
.eh-comment-avatar { width: 80px; height: 80px; border-radius: 50%; border: 3px solid rgba(255,255,255,0.2); }
.eh-comment-author { font-size: 24px; font-weight: 900; color: #fff; }
.eh-comment-text { font-size: 28px; line-height: 1.5; color: rgba(255,255,255,0.9); white-space: pre-wrap; word-break: break-all; }
`;
document.head.appendChild(s);
};
const update = (data, idx, total) => {
if (!data) return;
setHTML(vAvatar, `<img src="${data.avatarUrl || ''}" class="eh-comment-avatar">`);
setHTML(vAuthor, data.author);
setHTML(vText, data.content);
counter.innerText = `${idx + 1} / ${total}`;
};
return {
show: (data, idx, total) => {
if (!isEnabled) return;
if (!overlay) {
overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed', inset: 0, backgroundColor: SITE_CONFIG.theme.overlayBg,
zIndex: 999999, display: 'flex', alignItems: 'center', justifyContent: 'center',
backdropFilter: 'blur(20px) saturate(160%)', webkitBackdropFilter: 'blur(20px) saturate(160%)'
});
vCard = document.createElement('div'); vCard.className = 'eh-comment-preview-card';
const head = document.createElement('div');
Object.assign(head.style, { display: 'flex', alignItems: 'center', gap: '20px' });
vAvatar = document.createElement('div');
vAuthor = document.createElement('div'); vAuthor.className = 'eh-comment-author';
head.append(vAvatar, vAuthor);
vText = document.createElement('div'); vText.className = 'eh-comment-text';
const info = document.createElement('div');
Object.assign(info.style, { marginTop: '20px', color: 'rgba(255,255,255,0.3)', fontSize: '14px', fontWeight: '700' });
info.innerText = '[Z] 点赞 [X] 不喜欢 [C] 翻译 [D] 开关预览 [ESC] 关闭';
counter = document.createElement('div');
Object.assign(counter.style, { position: 'absolute', top: '30px', left: '30px', color: '#fff', fontSize: '18px', fontWeight: '900', background: 'rgba(255,255,255,0.1)', padding: '8px 16px', borderRadius: '12px' });
vCard.append(head, vText, info); overlay.append(vCard, counter); document.body.appendChild(overlay);
overlay.onclick = (e) => { if (e.target === overlay) overlay.style.display = 'none'; };
}
overlay.style.display = 'flex';
update(data, idx, total);
},
hide: () => { if (overlay) overlay.style.display = 'none'; },
toggle: () => {
isEnabled = !isEnabled; if (!isEnabled) App_UI.hide();
App_UI.showToast(isEnabled ? '评论预览:开启' : '评论预览:关闭');
return isEnabled;
},
updateData: update,
isVisible: () => overlay && overlay.style.display === 'flex' && isEnabled,
injectCSS,
showToast: (msg) => {
const old = document.getElementById('enhancer-toast'); if (old) old.remove();
const t = document.createElement('div'); t.id = 'enhancer-toast';
Object.assign(t.style, { position: 'fixed', bottom: '50px', left: '50%', transform: 'translateX(-50%)', padding: '12px 30px', background: '#fff', color: '#000', borderRadius: '30px', zIndex: 1000005, fontWeight: '900', boxShadow: '0 15px 30px rgba(0,0,0,0.4)', transition: '0.3s' });
t.innerText = msg; document.body.appendChild(t);
setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 300); }, 1500);
}
};
})();
/**
* [模块 3] Nav: 评论导航
*/
const App_Nav = (() => {
let curIdx = -1, comments = [];
return {
move: (step) => {
comments = App_Core.findComments();
if (!comments.length) {
// 核心优化:如果没有评论(YouTube 没加载),自动滚一下触发加载
window.scrollBy({ top: 600, behavior: 'smooth' });
App_UI.showToast('📄 正在加载评论区...');
return null;
}
if (curIdx === -1) {
const center = comments.sort((a, b) => Math.abs(a.getBoundingClientRect().top - window.innerHeight / 2) - Math.abs(b.getBoundingClientRect().top - window.innerHeight / 2))[0];
curIdx = comments.indexOf(center);
} else curIdx = Math.max(0, Math.min(comments.length - 1, curIdx + step));
return { data: App_Core.getCommentData(comments[curIdx]), index: curIdx, total: comments.length };
},
scroll: () => {
document.querySelectorAll('.eh-comment-active').forEach(e => e.classList.remove('eh-comment-active'));
const c = comments[curIdx];
if (c) { c.classList.add('eh-comment-active'); c.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
},
getCard: () => comments[curIdx],
getIndex: () => curIdx,
getTotal: () => comments.length,
reset: () => { curIdx = -1; document.querySelectorAll('.eh-comment-active').forEach(e => e.classList.remove('eh-comment-active')); }
};
})();
/**
* [模块 4] Init
*/
const init = () => {
window.addEventListener('keydown', (e) => {
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) || document.activeElement.hasAttribute('contenteditable')) return;
const k = e.key.toLowerCase();
if (k === 'd') {
const enabled = App_UI.toggle();
if (enabled) { const r = App_Nav.move(0); if (r) App_UI.show(r.data, r.index, r.total); }
return;
}
if (k === 'j' || k === 'arrowdown') {
e.preventDefault(); const r = App_Nav.move(1);
if (r) { App_UI.show(r.data, r.index, r.total); App_Nav.scroll(); }
} else if (k === 'k' || k === 'arrowup') {
e.preventDefault(); const r = App_Nav.move(-1);
if (r) { App_UI.show(r.data, r.index, r.total); App_Nav.scroll(); }
} else if (k === 'z' || k === 'x') {
const card = App_Nav.getCard();
if (card) {
const btn = card.querySelector(k === 'z' ? SITE_CONFIG.selectors.likeBtn : SITE_CONFIG.selectors.dislikeBtn);
const innerBtn = btn?.querySelector('button') || btn;
if (innerBtn) {
innerBtn.click();
App_UI.showToast(k === 'z' ? '👍 已点赞评论' : '👎 已踩评论');
}
} else {
// 视频层级操作
const btn = document.querySelector(k === 'z' ? SITE_CONFIG.selectors.videoLikeBtn : SITE_CONFIG.selectors.videoDislikeBtn);
if (btn) {
btn.click();
App_UI.showToast(k === 'z' ? '👍 已点赞视频' : '👎 已踩视频');
}
}
} else if (k === 's') {
const btn = document.querySelector(SITE_CONFIG.selectors.subscribeBtn);
if (btn) {
btn.click();
const isSubbed = btn.innerText.includes('已') || btn.getAttribute('aria-label')?.includes('取消');
App_UI.showToast(isSubbed ? '✅ 已订阅' : '🔔 订阅状态变更');
}
} else if (k === 'c') {
const card = App_Nav.getCard();
if (card) {
// 更加暴力地寻找翻译按钮(包括内部的点击目标)
const translateArea = card.querySelector('.translate-button, ytd-tri-state-button-view-model');
const clickTarget = translateArea?.querySelector('[role="button"], tp-yt-paper-button') || translateArea;
if (clickTarget) {
clickTarget.click();
App_UI.showToast('🌐 正在翻译...');
// 翻译是异步的,需要循环检查文字变化来更新大屏预览
let retry = 0;
const originalText = App_Core.getCommentData(card).content;
const checkTimer = setInterval(() => {
const newData = App_Core.getCommentData(card);
// 如果文字变了,或者是尝试了5次还没变(可能已经翻译完了或翻译失败)
if (newData.content !== originalText || retry > 5) {
if (App_UI.isVisible()) {
App_UI.updateData(newData, App_Nav.getIndex(), App_Nav.getTotal());
}
clearInterval(checkTimer);
}
retry++;
}, 500);
} else {
App_UI.showToast('ℹ️ 该评论无法翻译');
}
}
} else if (k === 'escape' && App_UI.isVisible()) {
App_UI.hide();
}
}, true);
App_UI.injectCSS();
// 监控路径变化
let lp = window.location.href; setInterval(() => { if (window.location.href !== lp) { lp = window.location.href; App_Nav.reset(); App_UI.hide(); } }, 800);
};
setTimeout(init, 2000);
})();