抖音评论区监控脚本
直接上脚本



// ==UserScript==
// @name 抖音评论监控 · 弹幕版 (历史记录+导出)
// @namespace https://www.douyin.com/
// @version 2.7.0
// @description 监控评论关键词,右侧悬浮窗始终显示最新两条,历史记录包含评论内容
// @author You
// @match *://*.douyin.com/*
// @grant GM_setClipboard
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// ==================== 配置存储 ====================
const STORAGE_KEY = 'dy_monitor_config_v7';
const HISTORY_KEY = 'dy_monitor_history_v7'; // 新增版本号避免冲突
const DEFAULT_CONFIG = {
keywords: ['福利', '加我', '微信', 'qq', '进群', '兼职', '赚钱', '秒杀'],
regions: ['广西', '全部'],
enableRegionFilter: true,
alertMode: 'right', // 默认改为右侧
autoOpenComment: true,
autoScrollComments: true,
scrollInterval: 2000,
maxMarquees: 5,
showVideoLink: true,
showVideoInfo: true,
maxHistoryItems: 100,
maxSideAlerts: 2 // 新增:右侧最大显示数量
};
// 从存储加载配置
let config = GM_getValue(STORAGE_KEY, DEFAULT_CONFIG);
// 转换为运行时变量
let keywords = new Set(config.keywords);
let monitorRegions = new Set(config.regions);
let enableRegionFilter = config.enableRegionFilter;
let alertMode = config.alertMode;
let autoOpenComment = config.autoOpenComment;
let autoScrollComments = config.autoScrollComments;
let scrollInterval = config.scrollInterval;
let maxMarquees = config.maxMarquees;
let showVideoLink = config.showVideoLink;
let showVideoInfo = config.showVideoInfo;
let maxHistoryItems = config.maxHistoryItems;
let maxSideAlerts = config.maxSideAlerts || 2; // 默认2条
const triggeredComments = new Set();
// 历史记录
let historyRecords = GM_getValue(HISTORY_KEY, []);
// 状态变量
let isCommentPanelOpen = false;
let scrollTimer = null;
let commentContainer = null;
// 弹幕容器
let marqueeContainer = null;
// 侧边弹窗数组 - 用于管理显示数量
let sideAlerts = [];
// ==================== 保存配置 ====================
function saveConfig() {
config = {
keywords: Array.from(keywords),
regions: Array.from(monitorRegions),
enableRegionFilter: enableRegionFilter,
alertMode: alertMode,
autoOpenComment: autoOpenComment,
autoScrollComments: autoScrollComments,
scrollInterval: scrollInterval,
maxMarquees: maxMarquees,
showVideoLink: showVideoLink,
showVideoInfo: showVideoInfo,
maxHistoryItems: maxHistoryItems,
maxSideAlerts: maxSideAlerts
};
GM_setValue(STORAGE_KEY, config);
showToast('💾 配置已保存', 1000);
}
// ==================== 历史记录管理 ====================
function addHistoryRecord(commentData, videoInfo) {
const record = {
id: Date.now() + Math.random().toString(36).substr(2, 5),
timestamp: Date.now(),
date: new Date().toLocaleString(),
comment: {
text: commentData.text,
author: commentData.author,
time: commentData.time,
timeStr: formatTime(commentData.time),
ipLocation: commentData.ipLocation,
matchedKeywords: commentData.matchedKeywords
},
video: {
author: videoInfo.author,
publishTime: videoInfo.publishTime,
title: videoInfo.fullTitle,
url: videoInfo.videoUrl
}
};
historyRecords.unshift(record); // 新记录插入开头
// 限制数量
if (historyRecords.length > maxHistoryItems) {
historyRecords = historyRecords.slice(0, maxHistoryItems);
}
GM_setValue(HISTORY_KEY, historyRecords);
}
function clearHistory() {
if (confirm('确定要清空所有历史记录吗?')) {
historyRecords = [];
GM_setValue(HISTORY_KEY, []);
showToast('🗑️ 历史记录已清空', 1500);
}
}
// ==================== 导出功能 ====================
function exportHistoryToCSV() {
if (historyRecords.length === 0) {
showToast('📭 没有历史记录可导出', 1500);
return;
}
// CSV 表头 - 新增评论内容列
const headers = [
'序号',
'视频作者',
'视频标题',
'视频发布时间',
'评论人',
'评论时间',
'评论地区',
'评论内容', // 新增
'命中关键词',
'视频链接',
'记录时间'
];
// 转换数据 - 新增评论内容
const rows = historyRecords.map((record, index) => {
return [
index + 1,
record.video.author,
record.video.title.replace(/,/g, ','),
record.video.publishTime,
record.comment.author,
record.comment.timeStr,
record.comment.ipLocation,
record.comment.text.replace(/,/g, ','), // 评论内容,替换逗号
record.comment.matchedKeywords.join('、'),
record.video.url,
record.date
];
});
// 构建CSV内容
const csvContent = [
headers.join(','),
...rows.map(row => row.map(cell => `"${cell}"`).join(','))
].join('\n');
// 创建下载链接
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `抖音评论监控_${new Date().toLocaleDateString()}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showToast('📥 历史记录已导出', 1500);
}
// ==================== 工具函数 ====================
function showToast(msg, duration = 2000, isWarning = false) {
const toast = document.createElement('div');
Object.assign(toast.style, {
position: 'fixed',
left: '50%',
top: '30%',
transform: 'translateX(-50%)',
background: isWarning ? 'rgba(255, 80, 80, 0.95)' : 'rgba(0,0,0,0.8)',
color: '#fff',
padding: '10px 20px',
borderRadius: '40px',
fontSize: '15px',
fontWeight: '500',
zIndex: 99999999,
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
transition: 'opacity 0.2s',
pointerEvents: 'none'
});
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, duration);
}
function formatTime(timestamp) {
if (!timestamp) return '未知时间';
let date = new Date(timestamp * 1000);
if (isNaN(date.getTime())) {
date = new Date(timestamp);
if (isNaN(date.getTime())) return '未知时间';
}
const pad = (n) => n.toString().padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
function generateCommentId(comment) {
return `${comment.text || comment.content}_${comment.user?.nickname || ''}_${comment.create_time || comment.time || ''}`;
}
function isRegionMatched(ipLocation) {
if (!enableRegionFilter) return true;
if (!ipLocation || ipLocation === '未知') return false;
if (monitorRegions.has('全部')) return true;
for (let region of monitorRegions) {
if (!region || region === '全部') continue;
if (ipLocation.includes(region)) {
return true;
}
}
return false;
}
// ==================== 提取视频信息 ====================
function getVideoInfo() {
const info = {
author: '未知作者',
publishTime: '未知时间',
title: '未知标题',
fullTitle: '未知标题',
videoUrl: window.location.href
};
try {
const authorElement = document.querySelector('[data-e2e="feed-video-nickname"] span, .account-name-text span');
if (authorElement) {
info.author = authorElement.textContent.trim().replace('@', '');
}
const timeElement = document.querySelector('.video-create-time .time, [class*="time"]');
if (timeElement) {
info.publishTime = timeElement.textContent.trim();
}
const titleContainer = document.querySelector('.title .FJhgcCvF, [data-e2e="video-desc"]');
if (titleContainer) {
const titleSpans = titleContainer.querySelectorAll('span');
let fullTitle = '';
titleSpans.forEach(span => {
const text = span.textContent.trim();
if (text && !text.includes('展开') && !text.includes('收起')) {
fullTitle += text + ' ';
}
});
info.fullTitle = fullTitle.trim() || titleContainer.textContent.trim();
info.title = info.fullTitle.length > 80 ? info.fullTitle.substring(0, 80) + '...' : info.fullTitle;
}
info.videoUrl = window.location.href;
} catch (e) {
console.log('提取视频信息出错:', e);
}
return info;
}
function formatVideoInfo(info) {
return {
author: info.author,
publishTime: info.publishTime,
title: info.title,
fullTitle: info.fullTitle,
videoUrl: info.videoUrl
};
}
function copyFullComment(commentData, videoInfo) {
const { text, author, time, ipLocation, matchedKeywords } = commentData;
const timeStr = formatTime(time);
const keywordStr = matchedKeywords.length > 0 ? `[关键词: ${matchedKeywords.join('、')}]` : '';
const fullInfo = `👤 评论用户:${author || '匿名'}
📍 评论地区:${ipLocation || '未知'}
🕒 评论时间:${timeStr}
🔑 ${keywordStr}
📹 视频信息:
👤 发布者:${videoInfo.author}
🕒 发布时间:${videoInfo.publishTime}
📝 标题:${videoInfo.fullTitle}
🔗 链接:${videoInfo.videoUrl}
💬 评论内容:${text}
-----
来自抖音评论监控插件`;
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(fullInfo, 'text');
showToast('✅ 已复制完整信息', 1500);
} else {
const textarea = document.createElement('textarea');
textarea.value = fullInfo;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
showToast('📋 已复制完整信息', 1500);
}
}
// ==================== 弹幕模式 ====================
function showMarquee(commentData) {
const { text, author, time, ipLocation, matchedKeywords } = commentData;
const videoInfo = formatVideoInfo(getVideoInfo());
// 添加到历史记录
addHistoryRecord(commentData, videoInfo);
if (!marqueeContainer) {
marqueeContainer = document.createElement('div');
marqueeContainer.id = 'marquee-container';
Object.assign(marqueeContainer.style, {
position: 'fixed',
top: '70px',
left: '0',
right: '0',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '8px',
zIndex: 999999999,
pointerEvents: 'none'
});
document.body.appendChild(marqueeContainer);
}
const marquee = document.createElement('div');
marquee.className = 'dy-comment-marquee';
Object.assign(marquee.style, {
width: '700px',
maxWidth: '90%',
background: '#ffffff',
borderRadius: '8px',
padding: '16px 20px',
color: '#333',
fontSize: '14px',
boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
border: '1px solid #eaeaea',
pointerEvents: 'auto',
marginBottom: '4px',
transition: 'opacity 0.2s',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
lineHeight: '1.5'
});
if (!document.getElementById('marquee-styles')) {
const style = document.createElement('style');
style.id = 'marquee-styles';
style.textContent = `
@keyframes marqueeFadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.video-link {
color: #2196F3;
text-decoration: none;
font-size: 12px;
word-break: break-all;
}
.video-link:hover {
text-decoration: underline;
color: #4CAF50;
}
.video-title {
font-weight: 500;
color: #1a1a1a;
line-height: 1.4;
}
.video-meta {
color: #666;
font-size: 12px;
}
.keyword-tag {
background: #ff4d4d;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
`;
document.head.appendChild(style);
}
// 视频信息区域
if (showVideoInfo) {
const videoInfoDiv = document.createElement('div');
videoInfoDiv.style.marginBottom = '12px';
videoInfoDiv.style.padding = '10px 12px';
videoInfoDiv.style.backgroundColor = '#f5f9ff';
videoInfoDiv.style.borderRadius = '6px';
videoInfoDiv.style.borderLeft = '3px solid #4CAF50';
const authorLine = document.createElement('div');
authorLine.style.display = 'flex';
authorLine.style.alignItems = 'center';
authorLine.style.gap = '12px';
authorLine.style.marginBottom = '6px';
authorLine.style.flexWrap = 'wrap';
authorLine.innerHTML = `
<span style="font-weight:600; color:#1a1a1a;">👤 ${videoInfo.author}</span>
<span style="color:#999; font-size:12px;">🕒 ${videoInfo.publishTime}</span>
`;
const titleLine = document.createElement('div');
titleLine.className = 'video-title';
titleLine.style.marginBottom = '4px';
titleLine.style.fontSize = '13px';
titleLine.style.color = '#333';
titleLine.innerHTML = `📝 ${videoInfo.title}`;
videoInfoDiv.appendChild(authorLine);
videoInfoDiv.appendChild(titleLine);
if (showVideoLink) {
const linkLine = document.createElement('div');
linkLine.style.marginTop = '6px';
linkLine.style.fontSize = '12px';
linkLine.style.display = 'flex';
linkLine.style.alignItems = 'center';
linkLine.style.gap = '6px';
linkLine.innerHTML = `
<span style="color:#666;">🔗</span>
<a href="${videoInfo.videoUrl}" target="_blank" class="video-link">打开视频</a>
`;
videoInfoDiv.appendChild(linkLine);
}
marquee.appendChild(videoInfoDiv);
}
// 评论信息区域
const commentInfoDiv = document.createElement('div');
commentInfoDiv.style.marginBottom = '12px';
commentInfoDiv.style.padding = '10px 12px';
commentInfoDiv.style.backgroundColor = '#f8f8f8';
commentInfoDiv.style.borderRadius = '6px';
commentInfoDiv.style.borderLeft = '3px solid #ff4d4d';
const metaLine = document.createElement('div');
metaLine.style.display = 'flex';
metaLine.style.alignItems = 'center';
metaLine.style.gap = '12px';
metaLine.style.marginBottom = '8px';
metaLine.style.flexWrap = 'wrap';
metaLine.innerHTML = `
<span style="font-weight:600; color:#1a1a1a;">👤 ${author || '匿名'}</span>
<span style="color:#666;">📍 ${ipLocation || '未知'}</span>
<span style="color:#999; font-size:12px;">🕒 ${formatTime(time)}</span>
`;
const keywordsBar = document.createElement('div');
keywordsBar.style.display = 'flex';
keywordsBar.style.alignItems = 'center';
keywordsBar.style.gap = '6px';
keywordsBar.style.marginBottom = '8px';
keywordsBar.style.flexWrap = 'wrap';
matchedKeywords.forEach(kw => {
const tag = document.createElement('span');
tag.className = 'keyword-tag';
tag.textContent = `# ${kw}`;
keywordsBar.appendChild(tag);
});
const contentDiv = document.createElement('div');
contentDiv.style.fontSize = '14px';
contentDiv.style.lineHeight = '1.6';
contentDiv.style.wordBreak = 'break-all';
contentDiv.style.padding = '4px 0';
contentDiv.style.color = '#444';
contentDiv.textContent = text;
commentInfoDiv.appendChild(metaLine);
commentInfoDiv.appendChild(keywordsBar);
commentInfoDiv.appendChild(contentDiv);
marquee.appendChild(commentInfoDiv);
const actionsBar = document.createElement('div');
actionsBar.style.display = 'flex';
actionsBar.style.justifyContent = 'flex-end';
actionsBar.style.gap = '10px';
actionsBar.style.marginTop = '8px';
const copyBtn = document.createElement('button');
copyBtn.textContent = '📋 复制完整信息';
Object.assign(copyBtn.style, {
background: '#4CAF50',
border: 'none',
color: '#fff',
padding: '6px 14px',
borderRadius: '4px',
fontSize: '12px',
cursor: 'pointer',
transition: 'all 0.2s',
fontWeight: '500'
});
copyBtn.onmouseenter = () => copyBtn.style.background = '#66BB6A';
copyBtn.onmouseleave = () => copyBtn.style.background = '#4CAF50';
copyBtn.onclick = (e) => {
e.stopPropagation();
copyFullComment(commentData, videoInfo);
};
const closeBtn = document.createElement('button');
closeBtn.textContent = '✕ 关闭';
Object.assign(closeBtn.style, {
background: '#ff4d4d',
border: 'none',
color: '#fff',
padding: '6px 14px',
borderRadius: '4px',
fontSize: '12px',
cursor: 'pointer',
transition: 'all 0.2s',
fontWeight: '500'
});
closeBtn.onmouseenter = () => closeBtn.style.background = '#ff6666';
closeBtn.onmouseleave = () => closeBtn.style.background = '#ff4d4d';
closeBtn.onclick = (e) => {
e.stopPropagation();
marquee.remove();
if (marqueeContainer.children.length === 0) {
marqueeContainer.remove();
marqueeContainer = null;
}
};
actionsBar.appendChild(copyBtn);
actionsBar.appendChild(closeBtn);
marquee.appendChild(actionsBar);
marquee.addEventListener('click', (e) => {
if (e.target === copyBtn || e.target === closeBtn ||
copyBtn.contains(e.target) || closeBtn.contains(e.target) ||
e.target.tagName === 'A') {
return;
}
copyFullComment(commentData, videoInfo);
});
marqueeContainer.appendChild(marquee);
while (marqueeContainer.children.length > maxMarquees) {
marqueeContainer.removeChild(marqueeContainer.firstChild);
}
}
// ==================== 侧边弹窗(修改为始终显示最新两条)====================
function showSideAlert(commentData, side = 'right') {
const { text, author, time, ipLocation, matchedKeywords } = commentData;
const videoInfo = formatVideoInfo(getVideoInfo());
// 添加到历史记录
addHistoryRecord(commentData, videoInfo);
// 创建新弹窗
const overlay = document.createElement('div');
overlay.className = 'dy-comment-alert';
overlay.dataset.timestamp = Date.now(); // 用于排序
Object.assign(overlay.style, {
position: 'fixed',
top: '20px',
[side]: '20px',
width: '380px',
background: '#1e1e1e',
borderRadius: '12px',
boxShadow: '0 8px 20px rgba(0,0,0,0.5)',
color: '#fff',
zIndex: 999999999,
border: '1px solid #ff4d4d',
animation: `sideSlideIn-${side} 0.2s ease-out`,
marginBottom: '10px',
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
});
if (!document.getElementById('side-styles')) {
const style = document.createElement('style');
style.id = 'side-styles';
style.textContent = `
@keyframes sideSlideIn-right {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes sideSlideIn-left {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.video-link-dark {
color: #64B5F6;
text-decoration: none;
}
.video-link-dark:hover {
text-decoration: underline;
color: #90CAF9;
}
`;
document.head.appendChild(style);
}
const card = document.createElement('div');
card.style.padding = '16px';
const header = document.createElement('div');
header.style.display = 'flex';
header.style.justifyContent = 'space-between';
header.style.alignItems = 'center';
header.style.marginBottom = '16px';
const title = document.createElement('div');
title.style.display = 'flex';
title.style.alignItems = 'center';
title.style.gap = '8px';
title.innerHTML = `
<span style="background:#ff4d4d; width:10px; height:10px; border-radius:50%;"></span>
<span style="font-weight:600; font-size:15px;">🔔 触发 ${matchedKeywords.length} 个关键词</span>
`;
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
Object.assign(closeBtn.style, {
background: 'none',
border: 'none',
color: '#aaa',
fontSize: '24px',
cursor: 'pointer',
padding: '0',
lineHeight: '1'
});
closeBtn.onclick = () => {
overlay.remove();
// 从数组中移除
sideAlerts = sideAlerts.filter(el => el !== overlay);
};
header.appendChild(title);
header.appendChild(closeBtn);
// 视频信息区域
if (showVideoInfo) {
const videoInfoDiv = document.createElement('div');
videoInfoDiv.style.marginBottom = '16px';
videoInfoDiv.style.padding = '12px';
videoInfoDiv.style.background = '#2a2a2a';
videoInfoDiv.style.borderRadius = '8px';
videoInfoDiv.style.borderLeft = '3px solid #4CAF50';
const authorLine = document.createElement('div');
authorLine.style.display = 'flex';
authorLine.style.alignItems = 'center';
authorLine.style.gap = '10px';
authorLine.style.marginBottom = '6px';
authorLine.style.flexWrap = 'wrap';
authorLine.innerHTML = `
<span style="font-weight:600; color:#4CAF50;">👤 ${videoInfo.author}</span>
<span style="color:#aaa; font-size:12px;">🕒 ${videoInfo.publishTime}</span>
`;
const titleLine = document.createElement('div');
titleLine.style.fontSize = '13px';
titleLine.style.color = '#ddd';
titleLine.style.marginBottom = '6px';
titleLine.style.lineHeight = '1.5';
titleLine.textContent = videoInfo.title;
videoInfoDiv.appendChild(authorLine);
videoInfoDiv.appendChild(titleLine);
if (showVideoLink) {
const linkLine = document.createElement('div');
linkLine.style.marginTop = '8px';
linkLine.style.fontSize = '12px';
linkLine.innerHTML = `
<span style="color:#aaa;">🔗</span>
<a href="${videoInfo.videoUrl}" target="_blank" class="video-link-dark" style="margin-left:4px;">打开视频</a>
`;
videoInfoDiv.appendChild(linkLine);
}
card.appendChild(videoInfoDiv);
}
// 评论信息区域
const commentDiv = document.createElement('div');
commentDiv.style.marginBottom = '16px';
commentDiv.style.padding = '12px';
commentDiv.style.background = '#2a2a2a';
commentDiv.style.borderRadius = '8px';
commentDiv.style.borderLeft = '3px solid #ff4d4d';
const metaLine = document.createElement('div');
metaLine.style.display = 'flex';
metaLine.style.flexWrap = 'wrap';
metaLine.style.gap = '10px';
metaLine.style.marginBottom = '8px';
metaLine.style.color = '#aaa';
metaLine.style.fontSize = '12px';
const isMatchedRegion = isRegionMatched(ipLocation);
const ipStyle = isMatchedRegion ? 'color: #4CAF50; font-weight: bold;' : '';
metaLine.innerHTML = `
<span>👤 ${author || '匿名'}</span>
<span>🕒 ${formatTime(time)}</span>
<span style="${ipStyle}">📍 ${ipLocation || '未知'}</span>
`;
const tagsContainer = document.createElement('div');
tagsContainer.style.display = 'flex';
tagsContainer.style.flexWrap = 'wrap';
tagsContainer.style.gap = '6px';
tagsContainer.style.marginBottom = '8px';
matchedKeywords.forEach(kw => {
const tag = document.createElement('span');
tag.style.background = '#ff4d4d';
tag.style.padding = '4px 10px';
tag.style.borderRadius = '16px';
tag.style.fontSize = '11px';
tag.textContent = `# ${kw}`;
tagsContainer.appendChild(tag);
});
const contentDiv = document.createElement('div');
contentDiv.style.fontSize = '13px';
contentDiv.style.lineHeight = '1.6';
contentDiv.style.wordBreak = 'break-all';
contentDiv.style.maxHeight = '100px';
contentDiv.style.overflowY = 'auto';
contentDiv.style.color = '#eee';
contentDiv.textContent = text;
commentDiv.appendChild(metaLine);
commentDiv.appendChild(tagsContainer);
commentDiv.appendChild(contentDiv);
card.appendChild(commentDiv);
const actions = document.createElement('div');
actions.style.display = 'flex';
actions.style.gap = '8px';
actions.style.justifyContent = 'flex-end';
const copyBtn = document.createElement('button');
copyBtn.textContent = '📋 复制完整信息';
Object.assign(copyBtn.style, {
background: '#4CAF50',
border: 'none',
color: '#fff',
padding: '6px 14px',
borderRadius: '6px',
fontSize: '12px',
cursor: 'pointer',
transition: 'background 0.2s'
});
copyBtn.onmouseenter = () => copyBtn.style.background = '#66BB6A';
copyBtn.onmouseleave = () => copyBtn.style.background = '#4CAF50';
copyBtn.onclick = () => copyFullComment(commentData, videoInfo);
const closeCardBtn = document.createElement('button');
closeCardBtn.textContent = '关闭';
Object.assign(closeCardBtn.style, {
background: '#ff4d4d',
border: 'none',
color: '#fff',
padding: '6px 14px',
borderRadius: '6px',
fontSize: '12px',
cursor: 'pointer',
transition: 'background 0.2s'
});
closeCardBtn.onmouseenter = () => closeCardBtn.style.background = '#ff6666';
closeCardBtn.onmouseleave = () => closeCardBtn.style.background = '#ff4d4d';
closeCardBtn.onclick = () => {
overlay.remove();
sideAlerts = sideAlerts.filter(el => el !== overlay);
};
actions.appendChild(copyBtn);
actions.appendChild(closeCardBtn);
card.appendChild(header);
card.appendChild(actions);
overlay.appendChild(card);
// 添加到文档
document.body.appendChild(overlay);
// 添加到管理数组
sideAlerts.push(overlay);
// 按时间戳排序(最新的在最后)
sideAlerts.sort((a, b) => (a.dataset.timestamp || 0) - (b.dataset.timestamp || 0));
// 如果超过最大显示数量,移除最早的
while (sideAlerts.length > maxSideAlerts) {
const oldest = sideAlerts.shift();
if (oldest && oldest.parentNode) {
oldest.remove();
}
}
// 重新计算所有弹窗的位置(从下往上排列,最新的在底部)
updateSideAlertPositions(side);
}
// 更新侧边弹窗位置(从下往上排列,最新的在底部)
function updateSideAlertPositions(side) {
const alerts = document.querySelectorAll('.dy-comment-alert');
const baseTop = 20;
const offset = 10;
let currentTop = baseTop;
// 转为数组并排序(老的在上,新的在下)
const sorted = Array.from(alerts).sort((a, b) =>
(a.dataset.timestamp || 0) - (b.dataset.timestamp || 0)
);
sorted.forEach((el, index) => {
el.style.top = currentTop + 'px';
currentTop += el.offsetHeight + offset;
});
}
// ==================== 根据模式显示弹窗 ====================
function showAlert(commentData) {
switch(alertMode) {
case 'left':
showSideAlert(commentData, 'left');
break;
case 'right':
showSideAlert(commentData, 'right');
break;
case 'marquee':
showMarquee(commentData);
break;
default:
showSideAlert(commentData, 'right');
}
}
// ==================== 历史记录表格弹窗(新增评论内容列)====================
function showHistoryWindow() {
// 创建遮罩层
const overlay = document.createElement('div');
overlay.id = 'history-window-overlay';
Object.assign(overlay.style, {
position: 'fixed',
top: '0',
left: '0',
right: '0',
bottom: '0',
background: 'rgba(0,0,0,0.7)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 999999999,
backdropFilter: 'blur(4px)'
});
// 创建窗口
const windowDiv = document.createElement('div');
Object.assign(windowDiv.style, {
width: '90%',
maxWidth: '1300px', // 加宽以适应新列
height: '80%',
background: '#1e1e1e',
borderRadius: '12px',
boxShadow: '0 20px 40px rgba(0,0,0,0.5)',
color: '#fff',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
border: '1px solid #333'
});
// 标题栏
const titleBar = document.createElement('div');
Object.assign(titleBar.style, {
padding: '16px 20px',
borderBottom: '1px solid #333',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
background: '#252525'
});
const title = document.createElement('h3');
title.style.margin = '0';
title.style.fontSize = '16px';
title.style.fontWeight = '500';
title.innerHTML = `📋 历史记录 (${historyRecords.length})`;
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
Object.assign(closeBtn.style, {
background: 'none',
border: 'none',
color: '#aaa',
fontSize: '24px',
cursor: 'pointer',
padding: '0 8px'
});
closeBtn.onmouseenter = () => closeBtn.style.color = '#fff';
closeBtn.onmouseleave = () => closeBtn.style.color = '#aaa';
closeBtn.onclick = () => overlay.remove();
titleBar.appendChild(title);
titleBar.appendChild(closeBtn);
// 工具栏
const toolbar = document.createElement('div');
Object.assign(toolbar.style, {
padding: '12px 20px',
borderBottom: '1px solid #333',
display: 'flex',
gap: '10px',
background: '#1a1a1a'
});
const exportBtn = document.createElement('button');
exportBtn.textContent = '📥 导出CSV';
Object.assign(exportBtn.style, {
background: '#4CAF50',
border: 'none',
borderRadius: '4px',
padding: '6px 16px',
color: '#fff',
fontSize: '13px',
cursor: 'pointer'
});
exportBtn.onclick = exportHistoryToCSV;
const clearBtn = document.createElement('button');
clearBtn.textContent = '🗑️ 清空记录';
Object.assign(clearBtn.style, {
background: '#ff4d4d',
border: 'none',
borderRadius: '4px',
padding: '6px 16px',
color: '#fff',
fontSize: '13px',
cursor: 'pointer'
});
clearBtn.onclick = () => {
clearHistory();
overlay.remove();
};
toolbar.appendChild(exportBtn);
toolbar.appendChild(clearBtn);
// 表格容器
const tableContainer = document.createElement('div');
Object.assign(tableContainer.style, {
flex: '1',
overflow: 'auto',
padding: '20px'
});
if (historyRecords.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.style.textAlign = 'center';
emptyMsg.style.padding = '50px';
emptyMsg.style.color = '#666';
emptyMsg.textContent = '暂无历史记录';
tableContainer.appendChild(emptyMsg);
} else {
const table = document.createElement('table');
Object.assign(table.style, {
width: '100%',
borderCollapse: 'collapse',
fontSize: '13px',
minWidth: '1200px' // 加宽
});
// 表头 - 新增评论内容列
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
headerRow.style.background = '#2a2a2a';
const headers = [
'序号', '视频作者', '视频标题', '视频发布时间',
'评论人', '评论时间', '评论地区', '评论内容', // 新增
'命中关键词', '视频链接'
];
headers.forEach(text => {
const th = document.createElement('th');
th.textContent = text;
Object.assign(th.style, {
padding: '12px 8px',
textAlign: 'left',
borderBottom: '2px solid #444',
color: '#aaa',
fontWeight: '500'
});
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// 表体 - 新增评论内容
const tbody = document.createElement('tbody');
historyRecords.forEach((record, index) => {
const row = document.createElement('tr');
row.style.borderBottom = '1px solid #333';
row.onmouseenter = () => row.style.background = '#2a2a2a';
row.onmouseleave = () => row.style.background = 'transparent';
const cells = [
index + 1,
record.video.author,
record.video.title,
record.video.publishTime,
record.comment.author,
record.comment.timeStr,
record.comment.ipLocation,
record.comment.text, // 新增:评论内容
record.comment.matchedKeywords.join('、'),
record.video.url
];
cells.forEach((cell, i) => {
const td = document.createElement('td');
Object.assign(td.style, {
padding: '10px 8px',
color: i === 8 ? '#ffb3b3' : '#e0e0e0', // 关键词列索引变为8
maxWidth: i === 2 ? '200px' : (i === 7 ? '250px' : 'none'), // 标题和评论内容列宽限制
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
});
if (i === 9) { // 视频链接列索引变为9
const link = document.createElement('a');
link.href = cell;
link.target = '_blank';
link.textContent = '打开';
link.style.color = '#4CAF50';
link.style.textDecoration = 'none';
link.onmouseenter = () => link.style.textDecoration = 'underline';
link.onmouseleave = () => link.style.textDecoration = 'none';
td.appendChild(link);
} else {
td.textContent = cell;
if (i === 8 && cell) { // 关键词列
td.style.fontWeight = '500';
}
}
row.appendChild(td);
});
tbody.appendChild(row);
});
table.appendChild(tbody);
tableContainer.appendChild(table);
}
windowDiv.appendChild(titleBar);
windowDiv.appendChild(toolbar);
windowDiv.appendChild(tableContainer);
overlay.appendChild(windowDiv);
document.body.appendChild(overlay);
}
// ==================== 模拟X键打开评论区 ====================
function simulateKeyX() {
const event = new KeyboardEvent('keydown', {
key: 'x',
keyCode: 88,
which: 88,
code: 'KeyX',
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
const eventUp = new KeyboardEvent('keyup', {
key: 'x',
keyCode: 88,
which: 88,
code: 'KeyX',
bubbles: true,
cancelable: true
});
document.dispatchEvent(eventUp);
}
function openCommentPanel() {
if (isCommentPanelOpen) return;
simulateKeyX();
isCommentPanelOpen = true;
showToast('💬 尝试打开评论区 (模拟X键)', 1500);
setTimeout(() => {
const commentEl = findCommentContainer();
if (commentEl) {
showToast('✅ 评论区已打开', 1000);
if (autoScrollComments) {
startCommentScroll();
}
} else {
isCommentPanelOpen = false;
}
}, 2000);
}
// ==================== 查找评论区容器 ====================
function findCommentContainer() {
const selectors = [
'[class*="comment-list"]',
'[class*="CommentList"]',
'[class*="commentContainer"]',
'[class*="comments-container"]',
'[class*="comment_list"]',
'.LWSPvSJk',
'#comment-container',
'[data-e2e="comment-list"]'
];
for (let selector of selectors) {
const el = document.querySelector(selector);
if (el && el.children.length > 0) return el;
}
return null;
}
// ==================== 开始滚动评论区 ====================
function startCommentScroll() {
if (scrollTimer) {
clearInterval(scrollTimer);
scrollTimer = null;
}
commentContainer = findCommentContainer();
if (!commentContainer) {
showToast('⚠️ 未找到评论区', 1500);
return;
}
showToast('🔄 开始滚动加载评论', 1500);
scrollTimer = setInterval(() => {
if (commentContainer) {
commentContainer.scrollTop = commentContainer.scrollHeight;
commentContainer.scrollBy(0, 200);
}
}, scrollInterval);
}
function stopCommentScroll() {
if (scrollTimer) {
clearInterval(scrollTimer);
scrollTimer = null;
showToast('⏸️ 停止滚动', 1000);
}
}
// ==================== 劫持评论接口 ====================
function inspectResponseBody(url, bodyText) {
if (!bodyText || typeof bodyText !== 'string') return;
const commentAPIs = [
'/comment/',
'/v2/comment/',
'/aweme/v1/comment/',
'/aweme/v1/web/comment/list/'
];
if (!commentAPIs.some(api => url.includes(api))) return;
try {
const data = JSON.parse(bodyText);
let comments = [];
if (data.comments) comments = data.comments;
else if (data.data && Array.isArray(data.data)) comments = data.data;
else if (data.data && data.data.comments) comments = data.data.comments;
else if (data.comment_list) comments = data.comment_list;
else return;
comments.forEach(comment => {
const commentId = generateCommentId(comment);
if (triggeredComments.has(commentId)) return;
let text = comment.text || comment.content || '';
let user = comment.user?.nickname || comment.user?.unique_id || comment.author || '匿名';
let createTime = comment.create_time || comment.time || null;
let ipLabel = comment.ip_label || comment.ip_location || comment.region || '未知';
if (!ipLabel || ipLabel === '未知') {
ipLabel = comment.user?.ip_location || comment.ip || '未知';
}
if (text) {
const matchedKeywords = [];
for (let kw of keywords) {
if (text.includes(kw)) {
matchedKeywords.push(kw);
}
}
if (matchedKeywords.length > 0 && isRegionMatched(ipLabel)) {
triggeredComments.add(commentId);
showAlert({
text: text,
author: user,
time: createTime,
ipLocation: ipLabel,
matchedKeywords: matchedKeywords
});
}
}
});
} catch (e) {}
}
// 劫持 fetch
const originalFetch = window.fetch;
window.fetch = function (...args) {
const url = args[0] instanceof Request ? args[0].url : args[0];
return originalFetch.apply(this, args).then(response => {
const cloned = response.clone();
cloned.text().then(body => inspectResponseBody(url, body)).catch(() => {});
return response;
});
};
// 劫持 XHR
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this._monitorUrl = url;
return originalXHROpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (...args) {
if (this._monitorUrl) {
const url = this._monitorUrl;
const originalOnLoad = this.onload;
this.onload = function (e) {
if (this.readyState === 4 && this.status === 200) {
inspectResponseBody(url, this.responseText);
}
if (originalOnLoad) originalOnLoad.call(this, e);
};
}
return originalXHRSend.apply(this, args);
};
// ==================== 悬浮配置面板 ====================
let panel = null;
let isDragging = false;
let offsetX = 0, offsetY = 0;
let isMinimized = false;
function createPanel() {
if (panel) panel.remove();
panel = document.createElement('div');
panel.id = 'comment-monitor-panel';
Object.assign(panel.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
width: isMinimized ? '160px' : '420px',
background: '#1f1f1f',
borderRadius: '12px',
boxShadow: '0 8px 20px rgba(0,0,0,0.5)',
color: '#e0e0e0',
fontSize: '13px',
zIndex: 9999999,
border: '1px solid #333',
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
cursor: 'default',
userSelect: 'none',
transition: 'width 0.2s ease'
});
const header = document.createElement('div');
header.id = 'panel-header';
Object.assign(header.style, {
padding: '10px 14px',
borderBottom: isMinimized ? 'none' : '1px solid #333',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
background: '#252525',
borderRadius: '12px 12px 0 0',
cursor: 'move',
userSelect: 'none'
});
const titleArea = document.createElement('div');
titleArea.style.display = 'flex';
titleArea.style.alignItems = 'center';
titleArea.style.gap = '6px';
const minimizeBtn = document.createElement('span');
minimizeBtn.textContent = isMinimized ? '□' : '—';
minimizeBtn.title = isMinimized ? '展开' : '最小化';
Object.assign(minimizeBtn.style, {
cursor: 'pointer',
color: '#888',
fontSize: '14px',
padding: '2px 6px',
borderRadius: '4px',
transition: 'all 0.2s'
});
minimizeBtn.onmouseenter = () => minimizeBtn.style.color = '#fff';
minimizeBtn.onmouseleave = () => minimizeBtn.style.color = '#888';
minimizeBtn.onclick = (e) => {
e.stopPropagation();
isMinimized = !isMinimized;
createPanel();
};
const title = document.createElement('div');
title.style.fontWeight = '500';
title.style.fontSize = isMinimized ? '13px' : '14px';
title.innerHTML = isMinimized ? '🎯 监控' : '弹幕监控 v2.7';
titleArea.appendChild(minimizeBtn);
titleArea.appendChild(title);
const historyBtn = document.createElement('span');
historyBtn.textContent = '📋';
historyBtn.title = '查看历史记录';
Object.assign(historyBtn.style, {
cursor: 'pointer',
color: '#4CAF50',
fontSize: '14px',
padding: '2px 6px',
borderRadius: '4px',
marginLeft: '4px',
transition: 'all 0.2s'
});
historyBtn.onmouseenter = () => { historyBtn.style.background = '#333'; };
historyBtn.onmouseleave = () => { historyBtn.style.background = 'transparent'; };
historyBtn.onclick = (e) => {
e.stopPropagation();
showHistoryWindow();
};
titleArea.appendChild(historyBtn);
const saveBtn = document.createElement('span');
saveBtn.textContent = '💾';
saveBtn.title = '保存配置';
Object.assign(saveBtn.style, {
cursor: 'pointer',
color: '#4CAF50',
fontSize: '14px',
padding: '2px 6px',
borderRadius: '4px',
marginLeft: '4px',
transition: 'all 0.2s'
});
saveBtn.onmouseenter = () => { saveBtn.style.background = '#333'; };
saveBtn.onmouseleave = () => { saveBtn.style.background = 'transparent'; };
saveBtn.onclick = (e) => {
e.stopPropagation();
saveConfig();
};
titleArea.appendChild(saveBtn);
const closeBtn = document.createElement('span');
closeBtn.textContent = '×';
Object.assign(closeBtn.style, {
cursor: 'pointer',
color: '#888',
fontSize: '18px',
fontWeight: '400',
padding: '0 4px',
borderRadius: '4px',
transition: 'all 0.2s'
});
closeBtn.onmouseenter = () => { closeBtn.style.background = '#333'; closeBtn.style.color = '#fff'; };
closeBtn.onmouseleave = () => { closeBtn.style.background = 'transparent'; closeBtn.style.color = '#888'; };
closeBtn.onclick = () => panel.remove();
header.appendChild(titleArea);
header.appendChild(closeBtn);
// 拖拽功能
header.addEventListener('mousedown', (e) => {
if (e.button !== 0 || e.target === closeBtn || e.target === minimizeBtn || e.target === saveBtn || e.target === historyBtn) return;
e.preventDefault();
const rect = panel.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
isDragging = true;
panel.style.transition = 'none';
panel.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
e.preventDefault();
const newLeft = e.clientX - offsetX;
const newTop = e.clientY - offsetY;
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
panel.style.left = Math.min(Math.max(0, newLeft), maxX) + 'px';
panel.style.top = Math.min(Math.max(0, newTop), maxY) + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
panel.style.cursor = 'default';
panel.style.transition = 'width 0.2s ease';
}
});
header.addEventListener('dragstart', (e) => e.preventDefault());
panel.appendChild(header);
if (!isMinimized) {
const body = document.createElement('div');
body.style.padding = '14px';
body.style.maxHeight = '500px';
body.style.overflowY = 'auto';
// ========== 状态显示 ==========
const statusSection = document.createElement('div');
statusSection.style.marginBottom = '14px';
statusSection.style.padding = '10px';
statusSection.style.background = '#1a1a1a';
statusSection.style.borderRadius = '8px';
const recordCount = document.createElement('div');
recordCount.style.marginBottom = '8px';
recordCount.style.fontSize = '12px';
recordCount.style.color = '#aaa';
recordCount.innerHTML = `📋 历史记录: ${historyRecords.length} 条`;
const controlBtns = document.createElement('div');
controlBtns.style.display = 'flex';
controlBtns.style.gap = '8px';
controlBtns.style.marginTop = '4px';
const openBtn = document.createElement('button');
openBtn.textContent = '打开评论区 (X)';
Object.assign(openBtn.style, {
flex: '2',
background: '#4CAF50',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '12px',
cursor: 'pointer'
});
openBtn.onclick = openCommentPanel;
const startScrollBtn = document.createElement('button');
startScrollBtn.textContent = '开始滚动';
Object.assign(startScrollBtn.style, {
flex: '1',
background: '#2196F3',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '12px',
cursor: 'pointer'
});
startScrollBtn.onclick = startCommentScroll;
const stopScrollBtn = document.createElement('button');
stopScrollBtn.textContent = '停止滚动';
Object.assign(stopScrollBtn.style, {
flex: '1',
background: '#ff4d4d',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '12px',
cursor: 'pointer'
});
stopScrollBtn.onclick = stopCommentScroll;
controlBtns.appendChild(openBtn);
controlBtns.appendChild(startScrollBtn);
controlBtns.appendChild(stopScrollBtn);
statusSection.appendChild(recordCount);
statusSection.appendChild(controlBtns);
// ========== 弹窗模式切换 ==========
const modeSection = document.createElement('div');
modeSection.style.marginBottom = '14px';
modeSection.style.padding = '10px';
modeSection.style.background = '#1a1a1a';
modeSection.style.borderRadius = '8px';
const modeTitle = document.createElement('div');
modeTitle.style.marginBottom = '8px';
modeTitle.style.color = '#aaa';
modeTitle.style.fontSize = '12px';
modeTitle.innerHTML = '🎨 弹窗模式';
const modeButtons = document.createElement('div');
modeButtons.style.display = 'flex';
modeButtons.style.gap = '6px';
const leftBtn = document.createElement('button');
leftBtn.textContent = '左侧';
leftBtn.style.background = alertMode === 'left' ? '#4CAF50' : '#3a3a3a';
Object.assign(leftBtn.style, {
flex: '1',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '12px',
cursor: 'pointer'
});
leftBtn.onclick = () => {
alertMode = 'left';
leftBtn.style.background = '#4CAF50';
rightBtn.style.background = '#3a3a3a';
marqueeBtn.style.background = '#3a3a3a';
saveConfig();
showToast('✅ 已切换到左侧弹窗', 1000);
};
const rightBtn = document.createElement('button');
rightBtn.textContent = '右侧';
rightBtn.style.background = alertMode === 'right' ? '#2196F3' : '#3a3a3a';
Object.assign(rightBtn.style, {
flex: '1',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '12px',
cursor: 'pointer'
});
rightBtn.onclick = () => {
alertMode = 'right';
rightBtn.style.background = '#2196F3';
leftBtn.style.background = '#3a3a3a';
marqueeBtn.style.background = '#3a3a3a';
saveConfig();
showToast('✅ 已切换到右侧弹窗', 1000);
};
const marqueeBtn = document.createElement('button');
marqueeBtn.textContent = '弹幕';
marqueeBtn.style.background = alertMode === 'marquee' ? '#FF9800' : '#3a3a3a';
Object.assign(marqueeBtn.style, {
flex: '1',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '12px',
cursor: 'pointer'
});
marqueeBtn.onclick = () => {
alertMode = 'marquee';
marqueeBtn.style.background = '#FF9800';
leftBtn.style.background = '#3a3a3a';
rightBtn.style.background = '#3a3a3a';
saveConfig();
showToast('✅ 已切换到弹幕模式', 1000);
};
modeButtons.appendChild(leftBtn);
modeButtons.appendChild(rightBtn);
modeButtons.appendChild(marqueeBtn);
modeSection.appendChild(modeTitle);
modeSection.appendChild(modeButtons);
// ========== 右侧显示数量设置 ==========
const countSection = document.createElement('div');
countSection.style.marginBottom = '14px';
countSection.style.padding = '10px';
countSection.style.background = '#1a1a1a';
countSection.style.borderRadius = '8px';
const countTitle = document.createElement('div');
countTitle.style.marginBottom = '8px';
countTitle.style.color = '#aaa';
countTitle.style.fontSize = '12px';
countTitle.innerHTML = '📊 右侧弹窗数量';
const countControl = document.createElement('div');
countControl.style.display = 'flex';
countControl.style.alignItems = 'center';
countControl.style.gap = '8px';
const countInput = document.createElement('input');
countInput.type = 'number';
countInput.min = '1';
countInput.max = '5';
countInput.value = maxSideAlerts;
Object.assign(countInput.style, {
width: '60px',
background: '#2a2a2a',
border: '1px solid #3a3a3a',
borderRadius: '4px',
padding: '4px',
color: '#fff',
textAlign: 'center'
});
const countLabel = document.createElement('span');
countLabel.style.color = '#ccc';
countLabel.style.fontSize = '11px';
countLabel.textContent = '条 (1-5)';
const countApply = document.createElement('button');
countApply.textContent = '应用';
Object.assign(countApply.style, {
background: '#4CAF50',
border: 'none',
borderRadius: '4px',
padding: '4px 12px',
color: '#fff',
fontSize: '11px',
cursor: 'pointer',
marginLeft: 'auto'
});
countApply.onclick = () => {
const val = parseInt(countInput.value);
if (val >= 1 && val <= 5) {
maxSideAlerts = val;
saveConfig();
showToast(`✅ 右侧弹窗数量已设为 ${val}`);
} else {
showToast('❌ 请输入1-5之间的数字', 1500, true);
}
};
countControl.appendChild(countInput);
countControl.appendChild(countLabel);
countControl.appendChild(countApply);
countSection.appendChild(countTitle);
countSection.appendChild(countControl);
// ========== 视频信息开关 ==========
const infoSection = document.createElement('div');
infoSection.style.marginBottom = '14px';
infoSection.style.padding = '10px';
infoSection.style.background = '#1a1a1a';
infoSection.style.borderRadius = '8px';
const infoToggle = document.createElement('div');
infoToggle.style.display = 'flex';
infoToggle.style.justifyContent = 'space-between';
infoToggle.style.alignItems = 'center';
infoToggle.style.marginBottom = '8px';
const infoLabel = document.createElement('span');
infoLabel.style.color = '#aaa';
infoLabel.style.fontSize = '12px';
infoLabel.innerHTML = '📹 显示视频信息';
const infoCheckbox = document.createElement('input');
infoCheckbox.type = 'checkbox';
infoCheckbox.checked = showVideoInfo;
infoCheckbox.addEventListener('change', (e) => {
showVideoInfo = e.target.checked;
saveConfig();
showToast(`📹 视频信息: ${e.target.checked ? '显示' : '隐藏'}`);
});
infoToggle.appendChild(infoLabel);
infoToggle.appendChild(infoCheckbox);
const linkToggle = document.createElement('div');
linkToggle.style.display = 'flex';
linkToggle.style.justifyContent = 'space-between';
linkToggle.style.alignItems = 'center';
const linkLabel = document.createElement('span');
linkLabel.style.color = '#aaa';
linkLabel.style.fontSize = '12px';
linkLabel.innerHTML = '🔗 显示视频链接';
const linkCheckbox = document.createElement('input');
linkCheckbox.type = 'checkbox';
linkCheckbox.checked = showVideoLink;
linkCheckbox.addEventListener('change', (e) => {
showVideoLink = e.target.checked;
saveConfig();
showToast(`🔗 视频链接: ${e.target.checked ? '显示' : '隐藏'}`);
});
linkToggle.appendChild(linkLabel);
linkToggle.appendChild(linkCheckbox);
infoSection.appendChild(infoToggle);
infoSection.appendChild(linkToggle);
// ========== 地区管理 ==========
const regionSection = document.createElement('div');
regionSection.style.marginBottom = '14px';
regionSection.style.padding = '10px';
regionSection.style.background = '#1a1a1a';
regionSection.style.borderRadius = '8px';
const regionHeader = document.createElement('div');
regionHeader.style.display = 'flex';
regionHeader.style.justifyContent = 'space-between';
regionHeader.style.marginBottom = '8px';
const regionTitle = document.createElement('div');
regionTitle.style.color = '#aaa';
regionTitle.style.fontSize = '12px';
regionTitle.innerHTML = `📍 地区 (${monitorRegions.size})`;
const regionToggle = document.createElement('label');
regionToggle.style.display = 'flex';
regionToggle.style.alignItems = 'center';
regionToggle.style.gap = '4px';
regionToggle.style.color = '#4CAF50';
regionToggle.style.fontSize = '11px';
const regionCheckbox = document.createElement('input');
regionCheckbox.type = 'checkbox';
regionCheckbox.checked = enableRegionFilter;
regionCheckbox.addEventListener('change', (e) => {
enableRegionFilter = e.target.checked;
saveConfig();
showToast(`📍 筛选: ${enableRegionFilter ? '开启' : '关闭'}`);
});
regionToggle.appendChild(regionCheckbox);
regionToggle.appendChild(document.createTextNode('开启'));
regionHeader.appendChild(regionTitle);
regionHeader.appendChild(regionToggle);
const regionList = document.createElement('div');
regionList.style.marginBottom = '8px';
regionList.style.maxHeight = '70px';
regionList.style.overflowY = 'auto';
regionList.style.background = '#252525';
regionList.style.borderRadius = '4px';
regionList.style.padding = '4px';
function renderRegions() {
regionList.innerHTML = '';
if (monitorRegions.size === 0) {
regionList.innerHTML = '<div style="padding:6px; text-align:center; color:#666;">暂无</div>';
return;
}
monitorRegions.forEach(region => {
const item = document.createElement('div');
item.style.display = 'flex';
item.style.justifyContent = 'space-between';
item.style.padding = '4px 6px';
item.style.borderBottom = '1px solid #333';
item.style.fontSize = '11px';
item.innerHTML = `<span>${region}</span>`;
const delBtn = document.createElement('span');
delBtn.textContent = '✕';
delBtn.style.cursor = 'pointer';
delBtn.style.color = '#ff4d4d';
delBtn.style.opacity = '0.6';
delBtn.style.padding = '2px 6px';
delBtn.onmouseenter = () => delBtn.style.opacity = '1';
delBtn.onmouseleave = () => delBtn.style.opacity = '0.6';
delBtn.onclick = (e) => {
e.stopPropagation();
monitorRegions.delete(region);
saveConfig();
renderRegions();
regionTitle.innerHTML = `📍 地区 (${monitorRegions.size})`;
showToast(`❌ 已移除: ${region}`);
};
item.appendChild(delBtn);
regionList.appendChild(item);
});
}
renderRegions();
const regionAddArea = document.createElement('div');
regionAddArea.style.display = 'flex';
regionAddArea.style.gap = '4px';
const regionInput = document.createElement('input');
regionInput.type = 'text';
regionInput.placeholder = '地区';
Object.assign(regionInput.style, {
flex: '1',
background: '#2a2a2a',
border: '1px solid #3a3a3a',
borderRadius: '4px',
padding: '4px 8px',
color: '#fff',
outline: 'none',
fontSize: '11px'
});
const regionAddBtn = document.createElement('button');
regionAddBtn.textContent = '+';
Object.assign(regionAddBtn.style, {
background: '#4CAF50',
border: 'none',
borderRadius: '4px',
width: '24px',
color: '#fff',
cursor: 'pointer',
fontSize: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
});
regionAddBtn.onclick = () => {
const val = regionInput.value.trim();
if (val) {
monitorRegions.add(val);
saveConfig();
renderRegions();
regionTitle.innerHTML = `📍 地区 (${monitorRegions.size})`;
regionInput.value = '';
showToast(`✅ 已添加: ${val}`);
}
};
regionAddArea.appendChild(regionInput);
regionAddArea.appendChild(regionAddBtn);
regionSection.appendChild(regionHeader);
regionSection.appendChild(regionList);
regionSection.appendChild(regionAddArea);
// ========== 关键词管理 ==========
const keywordSection = document.createElement('div');
keywordSection.style.marginBottom = '8px';
keywordSection.style.padding = '10px';
keywordSection.style.background = '#1a1a1a';
keywordSection.style.borderRadius = '8px';
const keywordHeader = document.createElement('div');
keywordHeader.style.display = 'flex';
keywordHeader.style.justifyContent = 'space-between';
keywordHeader.style.marginBottom = '8px';
keywordHeader.innerHTML = `
<span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
<span style="color:#4CAF50; font-size:11px;">批量添加</span>
`;
const keywordList = document.createElement('div');
keywordList.style.marginBottom = '8px';
keywordList.style.maxHeight = '80px';
keywordList.style.overflowY = 'auto';
keywordList.style.background = '#252525';
keywordList.style.borderRadius = '4px';
keywordList.style.padding = '4px';
function renderKeywords() {
keywordList.innerHTML = '';
if (keywords.size === 0) {
keywordList.innerHTML = '<div style="padding:6px; text-align:center; color:#666;">暂无</div>';
return;
}
keywords.forEach(kw => {
const item = document.createElement('div');
item.style.display = 'flex';
item.style.justifyContent = 'space-between';
item.style.padding = '4px 6px';
item.style.borderBottom = '1px solid #333';
item.style.fontSize = '11px';
item.innerHTML = `<span>${kw}</span>`;
const delBtn = document.createElement('span');
delBtn.textContent = '✕';
delBtn.style.cursor = 'pointer';
delBtn.style.color = '#ff4d4d';
delBtn.style.opacity = '0.6';
delBtn.style.padding = '2px 6px';
delBtn.onmouseenter = () => delBtn.style.opacity = '1';
delBtn.onmouseleave = () => delBtn.style.opacity = '0.6';
delBtn.onclick = (e) => {
e.stopPropagation();
keywords.delete(kw);
saveConfig();
renderKeywords();
keywordHeader.innerHTML = `
<span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
<span style="color:#4CAF50; font-size:11px;">批量添加</span>
`;
showToast(`❌ 已删除: ${kw}`);
};
item.appendChild(delBtn);
keywordList.appendChild(item);
});
}
renderKeywords();
const keywordAddArea = document.createElement('div');
keywordAddArea.style.display = 'flex';
keywordAddArea.style.gap = '4px';
keywordAddArea.style.marginBottom = '8px';
const keywordInput = document.createElement('input');
keywordInput.type = 'text';
keywordInput.placeholder = '添加关键词';
Object.assign(keywordInput.style, {
flex: '1',
background: '#2a2a2a',
border: '1px solid #3a3a3a',
borderRadius: '4px',
padding: '4px 8px',
color: '#fff',
outline: 'none',
fontSize: '11px'
});
const keywordAddBtn = document.createElement('button');
keywordAddBtn.textContent = '+';
Object.assign(keywordAddBtn.style, {
background: '#4CAF50',
border: 'none',
borderRadius: '4px',
width: '24px',
color: '#fff',
cursor: 'pointer',
fontSize: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
});
keywordAddBtn.onclick = () => {
const val = keywordInput.value.trim();
if (val) {
keywords.add(val);
saveConfig();
renderKeywords();
keywordHeader.innerHTML = `
<span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
<span style="color:#4CAF50; font-size:11px;">批量添加</span>
`;
keywordInput.value = '';
showToast(`✅ 已添加: ${val}`);
}
};
keywordAddArea.appendChild(keywordInput);
keywordAddArea.appendChild(keywordAddBtn);
const batchTextarea = document.createElement('textarea');
batchTextarea.placeholder = '批量添加(逗号/空格分隔)';
Object.assign(batchTextarea.style, {
width: '100%',
background: '#2a2a2a',
border: '1px solid #3a3a3a',
borderRadius: '4px',
padding: '6px',
color: '#fff',
fontSize: '11px',
marginTop: '4px',
resize: 'vertical',
minHeight: '40px'
});
const batchBtn = document.createElement('button');
batchBtn.textContent = '批量添加';
Object.assign(batchBtn.style, {
width: '100%',
background: '#3a3a3a',
border: 'none',
borderRadius: '4px',
padding: '6px 0',
color: '#fff',
fontSize: '11px',
cursor: 'pointer',
marginTop: '4px'
});
batchBtn.onclick = () => {
const raw = batchTextarea.value.trim();
if (!raw) return;
const parts = raw.split(/[,\s]+/).filter(p => p.length > 0);
let added = 0;
parts.forEach(p => {
if (!keywords.has(p)) {
keywords.add(p);
added++;
}
});
if (added > 0) {
saveConfig();
renderKeywords();
keywordHeader.innerHTML = `
<span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
<span style="color:#4CAF50; font-size:11px;">批量添加</span>
`;
showToast(`✅ 批量添加 ${added} 个`);
}
batchTextarea.value = '';
};
keywordSection.appendChild(keywordHeader);
keywordSection.appendChild(keywordList);
keywordSection.appendChild(keywordAddArea);
keywordSection.appendChild(batchTextarea);
keywordSection.appendChild(batchBtn);
body.appendChild(statusSection);
body.appendChild(modeSection);
body.appendChild(countSection);
body.appendChild(infoSection);
body.appendChild(regionSection);
body.appendChild(keywordSection);
panel.appendChild(body);
}
document.body.appendChild(panel);
}
// ==================== 初始化 ====================
function init() {
console.log('抖音评论监控插件 v2.7 已启动 (右侧最新两条)');
createPanel();
if (autoOpenComment) {
setTimeout(openCommentPanel, 2000);
}
setInterval(() => {
if (!commentContainer) {
commentContainer = findCommentContainer();
}
}, 3000);
}
// 注册菜单
if (typeof GM_registerMenuCommand !== 'undefined') {
GM_registerMenuCommand('📋 打开监控面板', createPanel);
GM_registerMenuCommand('📋 查看历史记录', showHistoryWindow);
GM_registerMenuCommand('📥 导出历史记录', exportHistoryToCSV);
GM_registerMenuCommand('💬 打开评论区 (X)', openCommentPanel);
GM_registerMenuCommand('▶️ 开始滚动', startCommentScroll);
GM_registerMenuCommand('⏸️ 停止滚动', stopCommentScroll);
GM_registerMenuCommand('💾 保存配置', saveConfig);
}
// 启动
if (document.body) {
setTimeout(init, 1500);
} else {
document.addEventListener('DOMContentLoaded', () => setTimeout(init, 1500));
}
})();
发布时间: