// ==UserScript==
// @name 百度贴吧终极增强套件Pro
// @namespace http://tampermonkey.net/
// @version 7.37
// @description 优化版:解决强制屏蔽水贴失效问题,解决逻辑冲突,优化动态DOM处理,移除排序功能,部分来自(Grok AI)。未经许可,禁止修改或分发。
// @author YourName
// @match *://tieba.baidu.com/p/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
// @connect tieba.baidu.com
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 防伪校验
const SCRIPT_AUTH_TOKEN = 'xAI-Grok3-EnhancedTieba-20250225'; // 更新为新日期
const checkAuthenticity = () => {
const meta = GM_info.script;
if (meta.version !== '7.37' || meta.name !== '百度贴吧终极增强套件Pro' || !meta.description.includes('xAI')) {
alert('脚本可能被篡改或非正版,请从官方渠道下载!');
return false;
}
GM_setValue('authToken', SCRIPT_AUTH_TOKEN);
return true;
};
const LOG_LEVEL = GM_getValue('logLevel', 'verbose');
const MAX_LOG_ENTRIES = 100;
const CONFIG = {
debugMode: GM_getValue('debugMode', true),
defaultSettings: {
filter: {
hideInvalid: true,
hideSpam: true,
spamKeywords: ["顶", "沙发", "签到"],
whitelist: [],
blockedElements: [],
tempBlockedElements: [],
autoExpandImages: true,
blockType: 'perm',
blockAds: true,
enhanceImages: true,
linkifyVideos: true,
darkMode: false,
showHiddenFloors: false
},
panel: {
width: 320,
minHeight: 100,
maxHeight: '90vh',
position: { x: 20, y: 20 },
scale: 1.0,
minimized: true
},
logPath: `tieba_enhance_log_${new Date().toISOString().replace(/[:.]/g, '-')}.txt`
}
};
const logBuffer = {
script: [],
pageState: [],
pageBehavior: [],
userActions: []
};
const originalConsole = {
log: console.log.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console)
};
function logWrapper(category, level, ...args) {
if (!CONFIG.debugMode || (LOG_LEVEL === 'error' && level !== 'ERROR')) return;
const timestamp = new Date().toISOString();
const formattedArgs = args.map(arg => {
try {
return typeof arg === 'object' ? JSON.stringify(arg) : arg.toString();
} catch (e) {
return '[Unserializable Object]';
}
}).join(' ');
const message = `[${timestamp}] [${level}] ${formattedArgs}`;
logBuffer[category].push(message);
if (logBuffer[category].length > MAX_LOG_ENTRIES) logBuffer[category].shift();
originalConsole[level.toLowerCase()](message);
}
const customConsole = {
log: (...args) => logWrapper('script', 'LOG', ...args),
warn: (...args) => logWrapper('script', 'WARN', ...args),
error: (...args) => logWrapper('script', 'ERROR', ...args)
};
class PerformanceMonitor {
static instance;
constructor() {
this.metrics = {
memoryUsage: [],
processSpeed: [],
networkRequests: []
};
}
static getInstance() {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
recordMemory() {
try {
if ('memory' in performance) {
const used = performance.memory.usedJSHeapSize;
this.metrics.memoryUsage.push(used);
if (this.metrics.memoryUsage.length > 100) this.metrics.memoryUsage.shift();
logWrapper('pageState', 'LOG', `Memory recorded: ${Math.round(used / 1024 / 1024)} MB`);
}
} catch (e) {
customConsole.error('Error in recordMemory:', e);
}
}
recordProcessSpeed(time) {
try {
this.metrics.processSpeed.push(time);
if (this.metrics.processSpeed.length > 100) this.metrics.processSpeed.shift();
logWrapper('pageState', 'LOG', `Process speed recorded: ${time.toFixed(2)} ms`);
} catch (e) {
customConsole.error('Error in recordProcessSpeed:', e);
}
}
recordNetwork() {
try {
if (performance.getEntriesByType) {
const requests = performance.getEntriesByType('resource');
requests.forEach(req => {
if (!this.metrics.networkRequests.some(r => r.name === req.name)) {
this.metrics.networkRequests.push(req);
if (this.metrics.networkRequests.length > 100) this.metrics.networkRequests.shift();
logWrapper('pageState', 'LOG', `Network request: ${req.name}, Duration: ${req.duration}ms`);
}
});
}
} catch (e) {
customConsole.error('Error in recordNetwork:', e);
}
}
recordPageTiming() {
try {
const timing = performance.timing;
if (timing.loadEventEnd && timing.navigationStart) {
const loadTime = timing.loadEventEnd - timing.navigationStart;
logWrapper('pageState', 'LOG', `Page load time: ${loadTime}ms`);
}
} catch (e) {
customConsole.error('Error in recordPageTiming:', e);
}
}
}
class PostFilter {
constructor() {
try {
this.settings = GM_getValue('settings', CONFIG.defaultSettings.filter);
customConsole.log('PostFilter settings:', this.settings);
this.postsCache = new Map();
this.spamPosts = new Set();
this.originalOrder = [];
this.applyStyles();
this.saveOriginalOrder();
this.applyFilters();
this.autoExpandImages();
this.observeDOMChanges();
this.startSpamEnforcer();
this.blockAds();
this.interceptAjax();
if (this.settings.linkifyVideos) this.linkifyVideos();
} catch (e) {
customConsole.error('Error initializing PostFilter:', e);
}
}
applyStyles() {
GM_addStyle(`
.spam-hidden { display: none !important; }
.invalid-hidden { display: none !important; }
`);
}
saveOriginalOrder() {
const posts = document.querySelectorAll('.l_post');
this.originalOrder = Array.from(posts).map(post => {
const pid = post.dataset.pid || 'unknown';
const floor = post.dataset.floor || post.querySelector('.tail-info')?.textContent.match(/(\d+)楼/)?.[1] || '0';
return { pid, floor, element: post.cloneNode(true) };
});
customConsole.log('Saved original post order:', this.originalOrder.length, 'Posts:', this.originalOrder.map(p => ({ pid: p.pid, floor: p.floor })));
}
applyFilters(nodes = document.querySelectorAll('.l_post')) {
customConsole.log('Applying filters');
logWrapper('pageBehavior', 'LOG', 'Applying content filters');
const startTime = performance.now();
try {
nodes.forEach(post => {
if (!post || this.postsCache.has(post)) return;
const contentEle = post.querySelector('.d_post_content');
if (!contentEle) return;
const content = contentEle.textContent.trim();
const pid = post.dataset.pid || 'unknown';
post.style.display = '';
post.classList.remove('spam-hidden', 'invalid-hidden');
if (this.settings.hideInvalid && !content) {
post.classList.add('invalid-hidden');
logWrapper('pageBehavior', 'LOG', `Hid invalid post: ${pid}`);
if (this.settings.showHiddenFloors) {
post.classList.remove('invalid-hidden');
logWrapper('pageBehavior', 'LOG', `Restored invalid post: ${pid}`);
}
} else if (this.settings.hideSpam) {
const keywords = this.settings.spamKeywords.map(k => k.trim());
const regex = new RegExp(keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'i');
if (regex.test(content)) {
post.classList.add('spam-hidden');
post.style.display = 'none';
this.spamPosts.add(post);
const matchedKeyword = keywords.find(k => content.toLowerCase().includes(k.toLowerCase())) || 'unknown';
logWrapper('pageBehavior', 'LOG', `Hid spam post: ${pid}, Keyword: ${matchedKeyword}, Content: ${content.slice(0, 50)}...`);
}
}
this.postsCache.set(post, true);
});
const blockedElements = [...(this.settings.blockedElements || []), ...(this.settings.tempBlockedElements || [])];
blockedElements.forEach(selector => {
try {
document.querySelectorAll(selector).forEach(el => {
if (!this.postsCache.has(el)) {
el.classList.add('spam-hidden');
el.style.display = 'none';
logWrapper('pageBehavior', 'LOG', `Hid blocked element: ${selector}`);
this.postsCache.set(el, true);
}
});
} catch (e) {
customConsole.warn(`Invalid selector: ${selector}`, e);
}
});
setTimeout(() => this.enforceSpamHiding(), 100);
} catch (e) {
customConsole.error('Error in applyFilters:', e);
}
const endTime = performance.now();
PerformanceMonitor.getInstance().recordProcessSpeed(endTime - startTime);
}
startSpamEnforcer() {
const observer = new MutationObserver(() => {
if (this.settings.hideSpam) {
this.enforceSpamHiding();
}
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
customConsole.log('Spam enforcer started');
}
enforceSpamHiding() {
const allPosts = document.querySelectorAll('.l_post');
allPosts.forEach(post => {
if (!post || !this.spamPosts.has(post) || !document.body.contains(post)) return;
if (post.style.display !== 'none') {
post.style.display = 'none';
post.classList.add('spam-hidden');
const pid = post.dataset.pid || 'unknown';
logWrapper('pageBehavior', 'LOG', `Re-enforced spam hiding for post: ${pid}`);
}
});
setTimeout(() => this.enforceSpamHiding(), 500);
}
autoExpandImages(nodes = document.querySelectorAll('.replace_tip')) {
if (!this.settings.autoExpandImages) return;
customConsole.log('Auto expanding images');
logWrapper('pageBehavior', 'LOG', 'Starting auto expand images');
const startTime = performance.now();
try {
nodes.forEach(tip => {
if (tip.style.display !== 'none' && !tip.dataset.expanded) {
const rect = tip.getBoundingClientRect();
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.top });
tip.dispatchEvent(clickEvent);
tip.dataset.expanded = 'true';
const img = tip.closest('.replace_div')?.querySelector('img');
logWrapper('pageBehavior', 'LOG', `Expanded image: ${img?.src || 'unknown'}`);
logWrapper('userActions', 'LOG', `Simulated click on replace_tip at (${Math.round(rect.left)}, ${Math.round(rect.top)})`);
this.postsCache.set(tip, true);
if (this.settings.enhanceImages && img) this.enhanceImage(img);
}
});
} catch (e) {
customConsole.error('Error in autoExpandImages:', e);
}
const endTime = performance.now();
PerformanceMonitor.getInstance().recordProcessSpeed(endTime - startTime);
}
updateFilters() {
try {
this.settings = GM_getValue('settings', CONFIG.defaultSettings.filter);
customConsole.log('Updated settings:', this.settings);
this.postsCache.clear();
this.spamPosts.clear();
this.saveOriginalOrder();
this.applyFilters();
this.autoExpandImages();
this.blockAds();
this.restoreOriginalOrder(); // 仅恢复原始顺序
if (this.settings.linkifyVideos) this.linkifyVideos();
} catch (e) {
customConsole.error('Error in updateFilters:', e);
}
}
observeDOMChanges() {
try {
const observer = new MutationObserver(_.debounce(mutations => {
let shouldProcess = false;
const newPosts = new Set();
mutations.forEach(mutation => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
shouldProcess = true;
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches('.l_post')) newPosts.add(node);
node.querySelectorAll('.l_post').forEach(post => newPosts.add(post));
}
});
}
});
if (shouldProcess) {
customConsole.log('Detected DOM change, reapplying filters');
logWrapper('pageBehavior', 'LOG', 'Page content updated, reapplying filters');
this.postsCache.clear();
this.spamPosts.clear();
this.saveOriginalOrder();
this.applyFilters(document.querySelectorAll('.l_post'));
this.autoExpandImages();
this.blockAds();
this.restoreOriginalOrder(); // 仅恢复原始顺序
if (this.settings.linkifyVideos) this.linkifyVideos();
} else if (newPosts.size > 0) {
customConsole.log('New posts detected:', newPosts.size);
this.applyFilters([...newPosts]);
this.autoExpandImages();
}
}, 300));
observer.observe(document.body, { childList: true, subtree: true });
customConsole.log('DOM observer initialized');
} catch (e) {
customConsole.error('Error in observeDOMChanges:', e);
}
}
interceptAjax() {
const originalFetch = window.fetch;
window.fetch = async (url, options) => {
const response = await originalFetch(url, options);
if (url.includes('pn=') && url.includes('ajax=1')) {
customConsole.log('Detected AJAX page load:', url);
setTimeout(() => {
this.postsCache.clear();
this.spamPosts.clear();
this.saveOriginalOrder();
this.applyFilters(document.querySelectorAll('.l_post'));
this.autoExpandImages();
this.blockAds();
this.restoreOriginalOrder(); // 仅恢复原始顺序
}, 500);
}
return response;
};
customConsole.log('AJAX interceptor initialized');
}
blockAds() {
if (!this.settings.blockAds) return;
customConsole.log('Blocking ads');
logWrapper('pageBehavior', 'LOG', 'Blocking advertisements');
try {
const adSelectors = [
'.ad_item',
'.mediago',
'[class*="ad_"]:not([class*="content"])',
'.app_download_box',
'.right_section .region_bright:not(.content)'
];
adSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
if (!el.closest('.d_post_content') && !el.closest('.l_container')) {
el.classList.add('spam-hidden');
el.style.display = 'none';
logWrapper('pageBehavior', 'LOG', `Hid ad element: ${selector}`);
this.postsCache.set(el, true);
}
});
});
} catch (e) {
customConsole.error('Error in blockAds:', e);
}
}
restoreOriginalOrder() {
customConsole.log('Restoring original post order');
logWrapper('pageBehavior', 'LOG', 'Restoring original post order');
try {
const container = document.querySelector('.pb_list');
if (!container || !this.originalOrder.length) return;
const currentPosts = new Map(Array.from(document.querySelectorAll('.l_post')).map(p => [p.dataset.pid, p]));
customConsole.log('Current posts:', Array.from(currentPosts.keys()));
customConsole.log('Original order:', this.originalOrder.map(p => p.pid));
while (container.firstChild) container.removeChild(container.firstChild);
this.originalOrder
.sort((a, b) => Number(a.floor) - Number(b.floor))
.forEach(item => {
const existingPost = currentPosts.get(item.pid);
if (existingPost) {
container.appendChild(existingPost);
} else {
container.appendChild(item.element.cloneNode(true));
}
});
this.applyFilters();
} catch (e) {
customConsole.error('Error in restoreOriginalOrder:', e);
}
}
linkifyVideos() {
customConsole.log('Linking videos');
logWrapper('pageBehavior', 'LOG', 'Converting video links');
try {
const videoRegex = /(?:av\d+|BV\w+)|(?:https?:\/\/(?:www\.)?(youtube\.com|youtu\.be)\/[^\s]+)/gi;
document.querySelectorAll('.d_post_content').forEach(post => {
if (!this.postsCache.has(post)) {
post.innerHTML = post.innerHTML.replace(videoRegex, match => {
if (match.startsWith('http')) {
return `<a href="${match}" target="_blank">${match}</a>`;
} else {
return `<a href="https://bilibili.com/video/${match}" target="_blank">${match}</a>`;
}
});
this.postsCache.set(post, true);
}
});
} catch (e) {
customConsole.error('Error in linkifyVideos:', e);
}
}
enhanceImage(img) {
if (!img) return;
customConsole.log('Enhancing image:', img.src);
try {
img.style.cursor = 'pointer';
img.removeEventListener('click', this.handleImageClick);
img.addEventListener('click', this.handleImageClick.bind(this));
} catch (e) {
customConsole.error('Error in enhanceImage:', e);
}
}
handleImageClick() {
const overlay = document.createElement('div');
overlay.className = 'image-overlay';
const largeImg = document.createElement('img');
largeImg.src = this.src;
largeImg.className = 'large-image';
overlay.appendChild(largeImg);
document.body.appendChild(overlay);
overlay.addEventListener('click', () => overlay.remove());
overlay.addEventListener('wheel', (e) => {
e.preventDefault();
const scale = e.deltaY > 0 ? 0.9 : 1.1;
largeImg.style.transform = `scale(${(parseFloat(largeImg.style.transform?.match(/scale\((.*?)\)/)?.[1]) || 1) * scale})`;
});
}
}
class DynamicPanel {
constructor() {
try {
this.panel = null;
this.minimizedIcon = null;
this.isDragging = false;
this.dragOccurred = false;
this.lastClickTime = 0;
this.isResizing = false;
this.settings = GM_getValue('settings', CONFIG.defaultSettings.filter) || CONFIG.defaultSettings.filter;
this.panelSettings = GM_getValue('panelSettings', CONFIG.defaultSettings.panel) || CONFIG.defaultSettings.panel;
if (!this.settings.blockedElements) this.settings.blockedElements = [];
if (!this.settings.tempBlockedElements) this.settings.tempBlockedElements = [];
customConsole.log('DynamicPanel settings:', this.panelSettings);
this.postFilter = new PostFilter();
this.init();
this.applyDarkMode(this.settings.darkMode);
} catch (e) {
customConsole.error('Error initializing DynamicPanel:', e);
}
}
init() {
customConsole.log('Initializing DynamicPanel');
try {
this.createPanel();
this.createMinimizedIcon();
document.body.appendChild(this.panel);
document.body.appendChild(this.minimizedIcon);
this.loadContent();
this.setupPanelInteractions();
this.minimizePanel();
if (!this.panelSettings.minimized) {
this.restorePanel();
}
this.ensureVisibility();
this.observer = new ResizeObserver(() => this.adjustPanelHeight());
this.observer.observe(this.panel.querySelector('.panel-content'));
customConsole.log('Panel initialized, visible:', this.panel.style.display !== 'none');
this.setupUserActionListeners();
this.setupCleanup();
setTimeout(() => this.startPerformanceMonitoring(), 100);
} catch (e) {
customConsole.error('Error in init:', e);
}
}
ensureVisibility() {
try {
this.panel.style.opacity = '1';
this.panel.style.visibility = 'visible';
this.minimizedIcon.style.opacity = '1';
this.minimizedIcon.style.visibility = 'visible';
customConsole.log('Ensuring visibility:', {
panel: { display: this.panel.style.display },
icon: { display: this.minimizedIcon.style.display }
});
} catch (e) {
customConsole.error('Error in ensureVisibility:', e);
}
}
createPanel() {
customConsole.log('Creating panel');
try {
this.panel = document.createElement('div');
this.panel.id = 'enhanced-panel';
GM_addStyle(`
#enhanced-panel {
position: fixed;
z-index: 9999;
top: ${this.panelSettings.position.y}px;
left: ${this.panelSettings.position.x}px;
width: ${this.panelSettings.width}px;
min-height: ${this.panelSettings.minHeight}px;
max-height: ${this.panelSettings.maxHeight};
background: rgba(255,255,255,0.98);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
transition: all 0.3s ease;
transform: scale(${this.panelSettings.scale});
contain: strict;
display: none;
opacity: 1;
visibility: visible;
height: auto;
}
#minimized-icon {
position: fixed;
z-index: 9999;
top: ${this.panelSettings.position.y}px;
left: ${this.panelSettings.position.x}px;
width: 32px;
height: 32px;
background: #ffffff;
border-radius: 50%;
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
display: block;
cursor: pointer;
text-align: center;
line-height: 32px;
font-size: 16px;
color: #007bff;
overflow: hidden;
}
.panel-header {
padding: 16px;
border-bottom: 1px solid #eee;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
}
.panel-content {
padding: 16px;
overflow-y: auto;
overscroll-behavior: contain;
height: auto;
max-height: calc(90vh - 80px);
}
.resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 20px;
height: 20px;
cursor: nwse-resize;
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTUgNUwxNSAxNSIgc3Ryb2tlPSIjOTk5OTk5IiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNNSAxNUwxNSAxNSIgc3Ryb2tlPSIjOTk5OTk5IiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=') no-repeat center;
}
.minimize-btn, .scale-btn {
cursor: pointer;
padding: 0 8px;
}
.minimize-btn:hover, .scale-btn:hover {
color: #007bff;
}
.setting-group {
display: flex;
align-items: center;
padding: 10px 0;
gap: 10px;
}
.toggle-switch {
position: relative;
width: 40px;
height: 20px;
flex-shrink: 0;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #ccc;
border-radius: 10px;
cursor: pointer;
transition: background 0.3s;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
top: 2px;
background: white;
border-radius: 50%;
transition: transform 0.3s;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.toggle-switch input:checked + .toggle-slider {
background: #34c759;
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(20px);
}
.setting-label {
flex: 1;
font-size: 14px;
color: #333;
}
body.dark-mode .setting-label {
color: #ddd !important;
}
select {
padding: 4px;
border: 1px solid #ddd;
border-radius: 4px;
}
.divider {
height: 1px;
background: #eee;
margin: 16px 0;
}
.tool-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.tool-card {
padding: 12px;
background: #f8f9fa;
border: 1px solid #eee;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.tool-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.metric-grid {
display: grid;
gap: 12px;
}
.progress-bar {
height: 4px;
background: #e9ecef;
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #28a745;
width: 0%;
transition: width 0.3s ease;
}
.block-modal, .keyword-modal, .log-modal, .search-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
pointer-events: auto;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
width: 400px;
max-height: 80vh;
overflow-y: auto;
pointer-events: auto;
}
.modal-content p {
color: #666;
margin: 5px 0 10px;
font-size: 12px;
}
textarea, input[type="text"] {
width: 100%;
margin: 10px 0;
padding: 8px;
border: 1px solid #ddd;
resize: vertical;
}
.modal-actions {
text-align: right;
}
.btn-cancel, .btn-save, .btn-block, .btn-undo, .btn-confirm, .btn-search {
padding: 6px 12px;
margin: 0 5px;
border: none;
border-radius: 4px;
cursor: pointer;
pointer-events: auto;
}
.btn-cancel {
background: #eee;
}
.btn-save, .btn-block, .btn-undo, .btn-confirm, .btn-search {
background: #34c759;
color: white;
}
.btn-block.active {
background: #ff4444;
}
.btn-undo {
background: #ff9800;
}
.hover-highlight {
outline: 2px solid #ff4444;
outline-offset: 2px;
}
.blocked-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0;
border-bottom: 1px solid #eee;
}
.blocked-item button {
padding: 4px 8px;
font-size: 12px;
}
.cursor-circle {
position: fixed;
width: 20px;
height: 20px;
background: rgba(128, 128, 128, 0.5);
border-radius: 50%;
pointer-events: none;
z-index: 10001;
transition: transform 0.2s ease;
}
.cursor-circle.confirm {
background: rgba(52, 199, 89, 0.8);
transform: scale(1.5);
transition: transform 0.3s ease, background 0.3s ease;
}
body.blocking-mode * {
cursor: none !important;
}
.performance-info {
display: none;
}
.highlight-match {
background-color: yellow;
}
.image-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
}
.large-image {
max-width: 90%;
max-height: 90%;
cursor: move;
}
body.dark-mode,
body.dark-mode .wrap1,
body.dark-mode .l_container,
body.dark-mode .pb_content,
body.dark-mode .d_post_content,
body.dark-mode .left_section,
body.dark-mode .right_section {
background: #222 !important;
color: #ddd !important;
}
body.dark-mode #enhanced-panel {
background: rgba(50,50,50,0.98) !important;
color: #ddd !important;
}
body.dark-mode a {
color: #66b3ff !important;
}
`);
this.panel.innerHTML = `
<div class="panel-header"><span>贴吧增强控制台</span><div class="panel-controls"><span class="minimize-btn">—</span><span class="scale-btn" data-scale="0.8">缩小</span><span class="scale-btn" data-scale="1.0">还原</span></div></div>
<div class="panel-content"></div>
<div class="resize-handle"></div>
`;
} catch (e) {
customConsole.error('Error in createPanel:', e);
}
}
createMinimizedIcon() {
customConsole.log('Creating minimized icon');
try {
this.minimizedIcon = document.createElement('div');
this.minimizedIcon.id = 'minimized-icon';
this.minimizedIcon.textContent = '⚙️';
this.minimizedIcon.addEventListener('click', e => {
const now = Date.now();
if (now - this.lastClickTime > 300 && !this.dragOccurred) {
customConsole.log('Minimized icon clicked, toggling panel');
this.toggleMinimize();
this.lastClickTime = now;
}
this.dragOccurred = false;
e.stopPropagation();
});
} catch (e) {
customConsole.error('Error in createMinimizedIcon:', e);
}
}
loadContent() {
customConsole.log('Loading panel content');
try {
this.panel.querySelector('.panel-content').innerHTML = `
<div class="filter-controls">
<h3>📊 智能过滤设置</h3>
<div class="setting-group">
<label class="toggle-switch">
<input type="checkbox" data-setting="debugMode" ${CONFIG.debugMode ? 'checked' : ''}>
<span class="toggle-slider"></span>
</label>
<span class="setting-label">启用调试模式</span>
</div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="hideInvalid" ${this.settings.hideInvalid ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">隐藏无效楼层</span></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="hideSpam" ${this.settings.hideSpam ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">屏蔽水贴内容</span><button class="btn-config" data-action="editKeywords">✏️ 编辑关键词</button></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="autoExpandImages" ${this.settings.autoExpandImages ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">自动展开图片</span></div>
<div class="setting-group">
<button class="btn-block" data-action="toggleBlockMode">🛡️ ${this.isBlockingMode ? '停止选择屏蔽' : '开始选择屏蔽元素'}</button>
<select data-setting="blockType">
<option value="perm" ${this.settings.blockType === 'perm' ? 'selected' : ''}>永久屏蔽</option>
<option value="temp" ${this.settings.blockType === 'temp' ? 'selected' : ''}>临时屏蔽</option>
</select>
</div>
<div class="setting-group"><button class="btn-undo" data-action="showUndoList">🔄 查看并撤回屏蔽</button></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="blockAds" ${this.settings.blockAds ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">自动屏蔽广告</span></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="enhanceImages" ${this.settings.enhanceImages ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">图片交互优化</span></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="linkifyVideos" ${this.settings.linkifyVideos ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">视频链接跳转</span></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="darkMode" ${this.settings.darkMode ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">黑夜模式</span></div>
<div class="setting-group"><label class="toggle-switch"><input type="checkbox" data-setting="showHiddenFloors" ${this.settings.showHiddenFloors ? 'checked' : ''}><span class="toggle-slider"></span></label><span class="setting-label">显示隐藏楼层</span></div>
</div>
<div class="divider"></div>
<div class="advanced-tools">
<h3>⚙️ 高级工具</h3>
<div class="tool-grid">
<button class="tool-card" data-action="exportSettings"><div class="icon">📤</div><span>导出配置</span></button>
<button class="tool-card" data-action="importSettings"><div class="icon">📥</div><span>导入配置</span></button>
<button class="tool-card" data-action="performanceChart"><div class="icon">📈</div><span>性能图表</span></button>
<button class="tool-card" data-action="quickSearch"><div class="icon">🔍</div><span>快速检索</span></button>
<button class="tool-card" data-action="saveLogs"><div class="icon">💾</div><span>保存日志</span></button>
</div>
</div>
<div class="divider"></div>
<div class="performance-info">
<h3>💻 系统监控</h3>
<div class="metric-grid">
<div class="metric-item"><span class="metric-label">内存占用</span><span class="metric-value" id="mem-usage">0 MB</span><div class="progress-bar"><div class="progress-fill" id="mem-progress"></div></div></div>
<div class="metric-item"><span class="metric-label">处理速度</span><span class="metric-value" id="process-speed">0 ms</span><div class="sparkline" id="speed-chart"></div></div>
</div>
</div>
`;
this.bindEvents();
setTimeout(() => this.adjustPanelHeight(), 50);
} catch (e) {
customConsole.error('Error in loadContent:', e);
}
}
adjustPanelHeight() {
try {
if (this.panelSettings.minimized) return;
const content = this.panel.querySelector('.panel-content');
const headerHeight = this.panel.querySelector('.panel-header').offsetHeight;
const maxHeight = Math.min(content.scrollHeight + headerHeight + 32, window.innerHeight * 0.9);
this.panel.style.height = `${maxHeight}px`;
customConsole.log('Adjusted panel height:', maxHeight);
} catch (e) {
customConsole.error('Error in adjustPanelHeight:', e);
}
}
bindEvents() {
customConsole.log('Binding events');
try {
this.panel.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', () => {
if (checkbox.dataset.setting === 'debugMode') {
CONFIG.debugMode = checkbox.checked;
GM_setValue('debugMode', CONFIG.debugMode);
} else if (checkbox.dataset.setting === 'darkMode') {
this.settings.darkMode = checkbox.checked;
GM_setValue('settings', this.settings);
this.applyDarkMode(checkbox.checked);
} else {
this.settings[checkbox.dataset.setting] = checkbox.checked;
GM_setValue('settings', this.settings);
this.postFilter.updateFilters();
}
logWrapper('userActions', 'LOG', `Toggled ${checkbox.dataset.setting} to ${checkbox.checked}`);
this.adjustPanelHeight();
});
});
this.panel.querySelector('[data-setting="blockType"]').addEventListener('change', (e) => {
this.settings.blockType = e.target.value;
GM_setValue('settings', this.settings);
logWrapper('userActions', 'LOG', `Set block type to ${this.settings.blockType}`);
});
const actions = {
editKeywords: () => this.showKeywordEditor(),
toggleBlockMode: () => this.toggleBlockMode(),
showUndoList: () => this.showUndoList(),
exportSettings: () => this.exportConfig(),
importSettings: () => this.importConfig(),
performanceChart: () => {
const perfInfo = this.panel.querySelector('.performance-info');
perfInfo.style.display = perfInfo.style.display === 'block' ? 'none' : 'block';
logWrapper('userActions', 'LOG', `Toggled performance chart: ${perfInfo.style.display}`);
this.adjustPanelHeight();
customConsole.log('Toggling performance chart');
},
quickSearch: () => this.toggleSearch(),
saveLogs: () => this.showLogSaveDialog()
};
this.panel.querySelectorAll('[data-action]').forEach(btn => {
btn.addEventListener('click', () => {
actions[btn.dataset.action]();
logWrapper('userActions', 'LOG', `Clicked button: ${btn.dataset.action}`);
});
});
this.panel.querySelector('.minimize-btn').addEventListener('click', e => {
this.toggleMinimize();
logWrapper('userActions', 'LOG', 'Clicked minimize button');
e.stopPropagation();
});
} catch (e) {
customConsole.error('Error in bindEvents:', e);
}
}
setupPanelInteractions() {
customConsole.log('Setting up panel interactions');
try {
const header = this.panel.querySelector('.panel-header');
const resizeHandle = this.panel.querySelector('.resize-handle');
let startX, startY, startWidth, startHeight;
const onDragStart = (e, target) => {
this.isDragging = true;
this.dragOccurred = false;
startX = e.clientX - this.panelSettings.position.x;
startY = e.clientY - this.panelSettings.position.y;
e.preventDefault();
};
const onDragMove = (e) => {
if (this.isDragging) {
this.dragOccurred = true;
const panelWidth = this.panel.offsetWidth;
const panelHeight = this.panel.offsetHeight;
this.panelSettings.position.x = Math.max(0, Math.min(e.clientX - startX, window.innerWidth - panelWidth));
this.panelSettings.position.y = Math.max(0, Math.min(e.clientY - startY, window.innerHeight - panelHeight));
const target = this.panelSettings.minimized ? this.minimizedIcon : this.panel;
target.style.left = `${this.panelSettings.position.x}px`;
target.style.top = `${this.panelSettings.position.y}px`;
logWrapper('userActions', 'LOG', `Dragged ${this.panelSettings.minimized ? 'minimized icon' : 'panel'} to (${this.panelSettings.position.x}, ${this.panelSettings.position.y})`);
}
if (this.isResizing) {
const newWidth = startWidth + (e.clientX - startX);
const newHeight = startHeight + (e.clientY - startY);
this.panelSettings.width = Math.max(200, newWidth);
this.panel.style.width = `${this.panelSettings.width}px`;
this.panel.style.height = `${Math.max(200, newHeight)}px`;
logWrapper('userActions', 'LOG', `Resized panel to ${this.panelSettings.width}x${Math.max(200, newHeight)}`);
}
};
const onDragEnd = () => {
if (this.isDragging || this.isResizing) {
GM_setValue('panelSettings', this.panelSettings);
this.isDragging = false;
this.isResizing = false;
this.adjustPanelHeight();
setTimeout(() => { this.dragOccurred = false; }, 100);
customConsole.log('Drag or resize ended');
}
};
header.addEventListener('mousedown', e => onDragStart(e, this.panel));
this.minimizedIcon.addEventListener('mousedown', e => onDragStart(e, this.minimizedIcon));
document.addEventListener('mousemove', onDragMove);
document.addEventListener('mouseup', onDragEnd);
resizeHandle.addEventListener('mousedown', e => {
this.isResizing = true;
startX = e.clientX;
startY = e.clientY;
startWidth = this.panelSettings.width;
startHeight = parseInt(this.panel.style.height) || this.panel.offsetHeight;
e.preventDefault();
});
this.panel.querySelectorAll('.scale-btn').forEach(btn => {
btn.addEventListener('click', e => {
this.panelSettings.scale = parseFloat(btn.dataset.scale);
this.panel.style.transform = `scale(${this.panelSettings.scale})`;
GM_setValue('panelSettings', this.panelSettings);
this.ensureVisibility();
this.adjustPanelHeight();
logWrapper('userActions', 'LOG', `Scaled panel to ${this.panelSettings.scale}`);
e.stopPropagation();
});
});
} catch (e) {
customConsole.error('Error in setupPanelInteractions:', e);
}
}
setupUserActionListeners() {
try {
document.addEventListener('click', e => {
if (!this.panel.contains(e.target) && !this.minimizedIcon.contains(e.target)) {
logWrapper('userActions', 'LOG', `Clicked on page at (${e.clientX}, ${e.clientY}), Target: ${e.target.tagName}.${e.target.className || ''}`);
}
});
document.addEventListener('input', e => {
logWrapper('userActions', 'LOG', `Input in ${e.target.tagName}, Value: ${e.target.value}`);
});
document.addEventListener('scroll', _.debounce(() => {
logWrapper('userActions', 'LOG', `Scrolled to (${window.scrollX}, ${window.scrollY})`);
}, 200));
} catch (e) {
customConsole.error('Error in setupUserActionListeners:', e);
}
}
startPerformanceMonitoring() {
const perfMonitor = PerformanceMonitor.getInstance();
const updatePerformance = () => {
perfMonitor.recordMemory();
perfMonitor.recordNetwork();
const memUsage = perfMonitor.metrics.memoryUsage.length > 0 ? Math.round(_.mean(perfMonitor.metrics.memoryUsage) / 1024 / 1024) : 0;
const processSpeed = perfMonitor.metrics.processSpeed.length > 0 ? _.mean(perfMonitor.metrics.processSpeed).toFixed(2) : 0;
const memElement = document.getElementById('mem-usage');
const progElement = document.getElementById('mem-progress');
const speedElement = document.getElementById('process-speed');
if (memElement && progElement) {
memElement.textContent = `${memUsage} MB`;
progElement.style.width = `${Math.min(memUsage / 100 * 100, 100)}%`;
}
if (speedElement) {
speedElement.textContent = `${processSpeed} ms`;
}
requestAnimationFrame(updatePerformance);
};
requestAnimationFrame(updatePerformance);
}
toggleMinimize() {
try {
customConsole.log('Toggling minimize, current state:', this.panelSettings.minimized);
if (this.panelSettings.minimized) {
this.restorePanel();
} else {
this.minimizePanel();
}
GM_setValue('panelSettings', this.panelSettings);
this.ensureVisibility();
customConsole.log('Toggle minimize completed, minimized:', this.panelSettings.minimized);
} catch (e) {
customConsole.error('Error in toggleMinimize:', e);
}
}
minimizePanel() {
try {
this.panel.style.display = 'none';
this.minimizedIcon.style.display = 'block';
this.minimizedIcon.style.left = `${this.panelSettings.position.x}px`;
this.minimizedIcon.style.top = `${this.panelSettings.position.y}px`;
this.panelSettings.minimized = true;
customConsole.log('Minimized panel');
} catch (e) {
customConsole.error('Error in minimizePanel:', e);
}
}
restorePanel() {
try {
this.panel.style.display = 'block';
this.minimizedIcon.style.display = 'none';
this.panel.style.left = `${this.panelSettings.position.x}px`;
this.panel.style.top = `${this.panelSettings.position.y}px`;
this.panel.style.transform = `scale(${this.panelSettings.scale})`;
this.panelSettings.minimized = false;
this.adjustPanelHeight();
customConsole.log('Restored panel');
} catch (e) {
customConsole.error('Error in restorePanel:', e);
}
}
toggleBlockMode(event) {
try {
this.isBlockingMode = !this.isBlockingMode;
const blockBtn = this.panel.querySelector('.btn-block');
blockBtn.textContent = `🛡️ ${this.isBlockingMode ? '停止选择屏蔽' : '开始选择屏蔽元素'}`;
blockBtn.classList.toggle('active', this.isBlockingMode);
if (this.isBlockingMode) {
document.body.classList.add('blocking-mode');
this.createCursorCircle();
this.listeners = {
move: this.moveCursorCircle.bind(this),
click: this.handleBlockClick.bind(this)
};
document.addEventListener('mousemove', this.listeners.move);
document.addEventListener('click', this.listeners.click);
} else {
document.body.classList.remove('blocking-mode');
this.removeCursorCircle();
if (this.listeners) {
document.removeEventListener('mousemove', this.listeners.move);
document.removeEventListener('click', this.listeners.click);
this.listeners = null;
}
this.removeHighlight();
this.selectedTarget = null;
}
customConsole.log('Block mode:', this.isBlockingMode);
if (event) event.stopPropagation();
this.adjustPanelHeight();
} catch (e) {
customConsole.error('Error in toggleBlockMode:', e);
}
}
createCursorCircle() {
try {
this.cursorCircle = document.createElement('div');
this.cursorCircle.className = 'cursor-circle';
document.body.appendChild(this.cursorCircle);
} catch (e) {
customConsole.error('Error in createCursorCircle:', e);
}
}
moveCursorCircle(event) {
if (!this.isBlockingMode || !this.cursorCircle) return;
try {
this.cursorCircle.style.left = `${event.clientX - 10}px`;
this.cursorCircle.style.top = `${event.clientY - 10}px`;
this.highlightElement(event);
} catch (e) {
customConsole.error('Error in moveCursorCircle:', e);
}
}
removeCursorCircle() {
try {
if (this.cursorCircle && document.body.contains(this.cursorCircle)) {
document.body.removeChild(this.cursorCircle);
}
this.cursorCircle = null;
} catch (e) {
customConsole.error('Error in removeCursorCircle:', e);
}
}
highlightElement(event) {
if (!this.isBlockingMode) return;
try {
this.removeHighlight();
const target = event.target;
if (target === this.panel || this.panel.contains(target) ||
target.classList.contains('block-modal') || target.closest('.block-modal')) return;
target.classList.add('hover-highlight');
customConsole.log('Highlighting:', target.tagName + '.' + (target.className || ''));
} catch (e) {
customConsole.error('Error in highlightElement:', e);
}
}
removeHighlight() {
try {
const highlighted = document.querySelector('.hover-highlight');
if (highlighted) highlighted.classList.remove('hover-highlight');
} catch (e) {
customConsole.error('Error in removeHighlight:', e);
}
}
handleBlockClick(event) {
if (!this.isBlockingMode) return;
try {
event.preventDefault();
event.stopPropagation();
const target = event.target;
if (target === this.panel || this.panel.contains(target) ||
target.classList.contains('block-modal') || target.closest('.block-modal')) return;
this.selectedTarget = target;
this.showConfirmDialog(event.clientX, event.clientY);
} catch (e) {
customConsole.error('Error in handleBlockClick:', e);
}
}
showConfirmDialog(x, y) {
try {
const modal = document.createElement('div');
modal.className = 'block-modal';
modal.innerHTML = `
<div class="modal-content">
<h3>确认屏蔽</h3>
<p>确定要屏蔽此元素吗?当前模式:${this.settings.blockType === 'temp' ? '临时' : '永久'}</p>
<div class="modal-actions">
<button class="btn-cancel">取消</button>
<button class="btn-confirm">确定</button>
</div>
</div>
`;
const confirmBtn = modal.querySelector('.btn-confirm');
const cancelBtn = modal.querySelector('.btn-cancel');
confirmBtn.addEventListener('click', e => {
e.stopPropagation();
if (this.selectedTarget) this.blockElement(this.selectedTarget, x, y);
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Confirmed block element');
}, { once: true });
cancelBtn.addEventListener('click', e => {
e.stopPropagation();
document.body.removeChild(modal);
this.toggleBlockMode();
logWrapper('userActions', 'LOG', 'Canceled block element');
}, { once: true });
document.body.appendChild(modal);
} catch (e) {
customConsole.error('Error in showConfirmDialog:', e);
}
}
blockElement(target, x, y) {
try {
const selector = this.getUniqueSelector(target);
const blockList = this.settings.blockType === 'temp' ? this.settings.tempBlockedElements : this.settings.blockedElements;
if (!blockList.includes(selector)) {
blockList.push(selector);
GM_setValue('settings', this.settings);
this.postFilter.updateFilters();
customConsole.log(`Blocked element (${this.settings.blockType}) with selector:`, selector);
logWrapper('userActions', 'LOG', `Blocked element (${this.settings.blockType}): ${selector}`);
}
target.classList.add('spam-hidden');
target.style.display = 'none';
if (this.cursorCircle) {
this.cursorCircle.style.left = `${x - 10}px`;
this.cursorCircle.style.top = `${y - 10}px`;
this.cursorCircle.classList.add('confirm');
setTimeout(() => {
this.cursorCircle.classList.remove('confirm');
this.toggleBlockMode();
}, 300);
} else {
this.toggleBlockMode();
}
this.adjustPanelHeight();
} catch (e) {
customConsole.error('Error in blockElement:', e);
}
}
getUniqueSelector(element) {
try {
if (element.id) return `#${element.id}`;
const path = [];
let current = element;
while (current && current.nodeType === Node.ELEMENT_NODE && current !== document.body) {
let selector = current.tagName.toLowerCase();
if (current.className) selector += `.${current.className.trim().split(/\s+/).join('.')}`;
const siblings = Array.from(current.parentNode.children).filter(child => child.tagName === current.tagName);
if (siblings.length > 1) selector += `:nth-child(${siblings.indexOf(current) + 1})`;
path.unshift(selector);
current = current.parentNode;
}
return path.join(' > ');
} catch (e) {
customConsole.error('Error in getUniqueSelector:', e);
return '';
}
}
showKeywordEditor() {
customConsole.log('Showing keyword editor');
try {
const modal = document.createElement('div');
modal.className = 'keyword-modal';
modal.innerHTML = `
<div class="modal-content">
<h3>关键词管理</h3>
<textarea>${this.settings.spamKeywords.join('\n')}</textarea>
<div class="modal-actions">
<button class="btn-cancel">取消</button>
<button class="btn-save">保存</button>
</div>
</div>
`;
modal.querySelector('.btn-save').addEventListener('click', () => {
this.settings.spamKeywords = modal.querySelector('textarea').value.split('\n').map(k => k.trim()).filter(k => k.length > 0);
GM_setValue('settings', this.settings);
this.postFilter.updateFilters();
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', `Updated spam keywords: ${this.settings.spamKeywords.join(', ')}`);
this.adjustPanelHeight();
});
modal.querySelector('.btn-cancel').addEventListener('click', () => {
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Canceled keyword editor');
});
document.body.appendChild(modal);
} catch (e) {
customConsole.error('Error in showKeywordEditor:', e);
}
}
showUndoList() {
customConsole.log('Showing undo list');
try {
const modal = document.createElement('div');
modal.className = 'block-modal';
const permItems = this.settings.blockedElements.length > 0 ?
this.settings.blockedElements.map((sel, i) => `
<div class="blocked-item">
<span>[永久] ${sel}</span>
<button class="btn-undo" data-index="${i}" data-type="perm">撤销</button>
</div>
`).join('') : '';
const tempItems = this.settings.tempBlockedElements.length > 0 ?
this.settings.tempBlockedElements.map((sel, i) => `
<div class="blocked-item">
<span>[临时] ${sel}</span>
<button class="btn-undo" data-index="${i}" data-type="temp">撤销</button>
</div>
`).join('') : '';
const listItems = permItems + tempItems || '<p>暂无屏蔽元素</p>';
modal.innerHTML = `
<div class="modal-content">
<h3>屏蔽元素列表</h3>
<p>点击“撤销”恢复显示对应元素</p>
${listItems}
<div class="modal-actions">
<button class="btn-cancel">关闭</button>
</div>
</div>
`;
modal.querySelectorAll('.btn-undo').forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.dataset.index);
const type = btn.dataset.type;
this.undoBlockElement(index, type);
document.body.removeChild(modal);
this.showUndoList();
logWrapper('userActions', 'LOG', `Undid block (${type}) at index ${index}`);
});
});
modal.querySelector('.btn-cancel').addEventListener('click', () => {
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Closed undo list');
});
document.body.appendChild(modal);
} catch (e) {
customConsole.error('Error in showUndoList:', e);
}
}
undoBlockElement(index, type) {
try {
const blockList = type === 'temp' ? this.settings.tempBlockedElements : this.settings.blockedElements;
if (index < 0 || index >= blockList.length) {
customConsole.warn('Invalid undo index:', index);
return;
}
const selector = blockList[index];
blockList.splice(index, 1);
GM_setValue('settings', this.settings);
this.postFilter.updateFilters();
document.querySelectorAll(selector).forEach(el => el.classList.remove('spam-hidden'));
customConsole.log(`Undid block (${type}) for selector:`, selector);
this.adjustPanelHeight();
} catch (e) {
customConsole.error('Error in undoBlockElement:', e);
}
}
exportConfig() {
customConsole.log('Exporting config');
try {
const config = {
filter: this.settings,
panel: this.panelSettings
};
const configJson = JSON.stringify(config, null, 2);
const blob = new Blob([configJson], { type: 'application/json;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `tieba_enhance_config_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
logWrapper('userActions', 'LOG', 'Exported configuration');
} catch (e) {
customConsole.error('Error in exportConfig:', e);
}
}
importConfig() {
customConsole.log('Importing config');
try {
const modal = document.createElement('div');
modal.className = 'keyword-modal';
modal.innerHTML = `
<div class="modal-content">
<h3>导入配置</h3>
<p>请选择配置文件(JSON格式)</p>
<input type="file" accept=".json" id="configFileInput" />
<div class="modal-actions">
<button class="btn-cancel">取消</button>
<button class="btn-save">导入</button>
</div>
</div>
`;
const fileInput = modal.querySelector('#configFileInput');
modal.querySelector('.btn-save').addEventListener('click', () => {
const file = fileInput.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const importedConfig = JSON.parse(e.target.result);
this.settings = { ...CONFIG.defaultSettings.filter, ...importedConfig.filter };
this.panelSettings = { ...CONFIG.defaultSettings.panel, ...importedConfig.panel };
GM_setValue('settings', this.settings);
GM_setValue('panelSettings', this.panelSettings);
this.postFilter.updateFilters();
this.loadContent();
if (this.panelSettings.minimized) {
this.minimizePanel();
} else {
this.restorePanel();
}
this.applyDarkMode(this.settings.darkMode);
logWrapper('userActions', 'LOG', 'Imported configuration');
} catch (err) {
customConsole.error('Invalid config file:', err);
alert('配置文件无效,请检查格式');
}
document.body.removeChild(modal);
};
reader.readAsText(file);
} else {
alert('请选择一个配置文件');
}
});
modal.querySelector('.btn-cancel').addEventListener('click', () => {
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Canceled config import');
});
document.body.appendChild(modal);
} catch (e) {
customConsole.error('Error in importConfig:', e);
}
}
toggleSearch() {
customConsole.log('Toggling quick search');
try {
const modal = document.createElement('div');
modal.className = 'search-modal';
modal.innerHTML = `
<div class="modal-content">
<h3>快速检索</h3>
<p>输入关键词搜索帖子内容(支持正则表达式)</p>
<input type="text" id="searchInput" placeholder="请输入关键词" />
<div class="modal-actions">
<button class="btn-cancel">关闭</button>
<button class="btn-search">搜索</button>
</div>
</div>
`;
const searchInput = modal.querySelector('#searchInput');
modal.querySelector('.btn-search').addEventListener('click', () => {
const keyword = searchInput.value.trim();
if (keyword) {
this.performSearch(keyword);
logWrapper('userActions', 'LOG', `Searched for: ${keyword}`);
}
});
modal.querySelector('.btn-cancel').addEventListener('click', () => {
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Closed quick search');
});
document.body.appendChild(modal);
searchInput.focus();
} catch (e) {
customConsole.error('Error in toggleSearch:', e);
}
}
performSearch(keyword) {
try {
document.querySelectorAll('.highlight-match').forEach(el => {
el.classList.remove('highlight-match');
el.replaceWith(el.textContent);
});
const posts = document.querySelectorAll('.d_post_content');
let regex;
try {
regex = new RegExp(keyword, 'gi');
} catch (e) {
regex = new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
}
posts.forEach(post => {
if (regex.test(post.textContent)) {
post.innerHTML = post.innerHTML.replace(regex, match => `<span class="highlight-match">${match}</span>`);
post.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
} catch (e) {
customConsole.error('Error in performSearch:', e);
}
}
showLogSaveDialog() {
customConsole.log('Showing log save dialog');
try {
const modal = document.createElement('div');
modal.className = 'log-modal';
modal.innerHTML = `
<div class="modal-content">
<h3>保存日志</h3>
<p>点击“保存”将日志导出为文件(当前最多 ${MAX_LOG_ENTRIES} 条/分类)</p>
<div class="modal-actions">
<button class="btn-cancel">取消</button>
<button class="btn-save">保存</button>
</div>
</div>
`;
modal.querySelector('.btn-save').addEventListener('click', () => {
try {
customConsole.log('Preparing log content');
const fullLog = [
'=== 脚本运行日志 ===',
...logBuffer.script,
'\n=== 网页运行状态 ===',
...logBuffer.pageState,
'\n=== 网页行为 ===',
...logBuffer.pageBehavior,
'\n=== 用户操作 ===',
...logBuffer.userActions
].join('\n');
customConsole.log('Creating Blob');
const blob = new Blob([fullLog], { type: 'text/plain;charset=utf-8' });
customConsole.log('Generating download URL');
const url = URL.createObjectURL(blob);
customConsole.log('Initiating download');
const a = document.createElement('a');
a.href = url;
a.download = CONFIG.defaultSettings.logPath;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
customConsole.log('Log file download completed');
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Saved logs to file');
} catch (e) {
customConsole.error('Error saving logs:', e);
alert('保存日志失败,请检查控制台错误信息');
}
});
modal.querySelector('.btn-cancel').addEventListener('click', () => {
document.body.removeChild(modal);
logWrapper('userActions', 'LOG', 'Canceled log save');
});
document.body.appendChild(modal);
this.adjustPanelHeight();
} catch (e) {
customConsole.error('Error in showLogSaveDialog:', e);
alert('打开日志保存对话框失败');
}
}
setupCleanup() {
try {
window.addEventListener('beforeunload', () => {
this.panel.remove();
this.minimizedIcon.remove();
this.observer?.disconnect();
if (this.listeners) {
document.removeEventListener('mousemove', this.listeners.move);
document.removeEventListener('click', this.listeners.click);
}
customConsole.log('Cleaned up resources');
});
} catch (e) {
customConsole.error('Error in setupCleanup:', e);
}
}
applyDarkMode(enable) {
customConsole.log('Applying dark mode:', enable);
try {
if (enable) {
document.body.classList.add('dark-mode');
} else {
document.body.classList.remove('dark-mode');
}
logWrapper('pageBehavior', 'LOG', `Applying dark mode: ${enable}`);
} catch (e) {
customConsole.error('Error in applyDarkMode:', e);
}
}
}
document.addEventListener('DOMContentLoaded', () => {
if (!checkAuthenticity()) return; // 防伪校验
if (GM_getValue('authToken') !== SCRIPT_AUTH_TOKEN) {
alert('脚本验证失败,请重新安装正版脚本!');
return;
}
customConsole.log('DOM content loaded, initializing');
const perfMonitor = PerformanceMonitor.getInstance();
new DynamicPanel();
perfMonitor.recordPageTiming();
});
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(() => {
if (!checkAuthenticity()) return; // 防伪校验
if (!document.getElementById('enhanced-panel') && !document.getElementById('minimized-icon')) {
customConsole.log('Fallback initialization');
const perfMonitor = PerformanceMonitor.getInstance();
new DynamicPanel();
}
}, 50);
}
})();