Greasy Fork

Greasy Fork is available in English.

百度贴吧增强套件Pro

临时版本:某些情况下帖子会陷入无限加载,加入强制刷新网页的临时措施。

当前为 2025-12-26 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         百度贴吧增强套件Pro
// @namespace    http://tampermonkey.net/
// @version      7.76
// @description  临时版本:某些情况下帖子会陷入无限加载,加入强制刷新网页的临时措施。
// @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
// @require      https://cdn.jsdelivr.net/npm/[email protected]/libs/lz-string.min.js
// @connect      tieba.baidu.com
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 一、关键修复点说明:
    // 1. 增强AJAX拦截,覆盖XMLHttpRequest
    // 2. 改进MutationObserver监听动态内容
    // 3. 持久化屏蔽词配置到localStorage
    // 4. 监听翻页按钮点击触发过滤
    // 5. 使用标记优化过滤性能
    // 6. 修复控制面板拖动不跟手问题

    // 二、性能优化常量
    const DEBOUNCE_LEVEL = { QUICK: 100, COMPLEX: 500 };
    const LOG_LEVEL = GM_getValue('logLevel', 'verbose');
    const MAX_LOG_ENTRIES = 100;
    const DATA_VERSION = 2;
    const CACHE_TTL = 60000;

    // 三、网络优化预加载资源列表
    const preloadList = [
        '/static/emoji.png',
        '/static/theme-dark.css'
    ];

    // 四、增强的日志管理
    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) {
        const debugMode = GM_getValue('debugMode', true);
        const levelMap = { ERROR: 1, WARN: 2, LOG: 3 };
        const currentLevel = levelMap[level.toUpperCase()];
        const minLevel = levelMap[LOG_LEVEL.toUpperCase()] || 3;

        if (!debugMode || (currentLevel && minLevel < currentLevel)) return;

        const timestamp = new Date().toISOString();
        const formattedArgs = args.map(arg => {
            if (typeof arg === 'object') {
                try { return JSON.stringify(arg); }
                catch { return String(arg); }
            }
            return String(arg);
        }).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 DomUtils {
        static safeQuery(selector, parent = document) {
            try {
                return parent.querySelector(selector) || null;
            } catch (e) {
                customConsole.error(`无效选择器: ${selector}`, e);
                return null;
            }
        }

        static safeQueryAll(selector, parent = document) {
            try {
                return parent.querySelectorAll(selector);
            } catch (e) {
                customConsole.error(`无效选择器: ${selector}`, e);
                return [];
            }
        }
    }

    // 六、配置管理类
    class ConfigManager {
        static get defaultFilterSettings() {
            return {
                hideInvalid: true,
                hideSpam: true,
                spamKeywords: ["顶", "沙发", "签到"],
                whitelist: [],
                blockedElements: [],
                tempBlockedElements: [],
                autoExpandImages: true,
                blockType: 'perm',
                blockAds: true,
                enhanceImages: true,
                linkifyVideos: true,
                darkMode: false,
                showHiddenFloors: false
            };
        }

        static get defaultPanelSettings() {
            return {
                width: 320,
                minHeight: 100,
                maxHeight: '90vh',
                position: { x: 20, y: 20 },
                scale: 1.0,
                minimized: true
            };
        }

        static getFilterSettings() {
            const raw = GM_getValue('settings');
            const settings = raw ? decompressSettings(raw) : this.defaultFilterSettings;
            const savedKeywords = loadConfig();
            if (savedKeywords.length > 0) settings.spamKeywords = savedKeywords;
            return settings;
        }

        static getPanelSettings() {
            return GM_getValue('panelSettings', this.defaultPanelSettings);
        }

        static updateFilterSettings(newSettings) {
            // 验证并清理无效选择器
            const validateSelector = (selector) => {
                if (!selector || typeof selector !== 'string' || selector.trim() === '') {
                    return false;
                }
                try {
                    document.querySelector(selector);
                    return true;
                } catch (error) {
                    return false;
                }
            };
            
            // 清理永久屏蔽元素列表
            if (newSettings.blockedElements) {
                newSettings.blockedElements = newSettings.blockedElements.filter(validateSelector);
            }
            
            // 清理临时屏蔽元素列表
            if (newSettings.tempBlockedElements) {
                newSettings.tempBlockedElements = newSettings.tempBlockedElements.filter(validateSelector);
            }
            
            GM_setValue('settings', compressSettings({ ...this.defaultFilterSettings, ...newSettings }));
            saveConfig(newSettings.spamKeywords);
        }

        static updatePanelSettings(newSettings) {
            GM_setValue('panelSettings', { ...this.defaultPanelSettings, ...newSettings });
        }
    }

    // 七、本地存储压缩与迁移
    function compressSettings(settings) {
        return LZString.compressToUTF16(JSON.stringify(settings));
    }

    function decompressSettings(data) {
        try {
            return JSON.parse(LZString.decompressFromUTF16(data));
        } catch {
            return ConfigManager.defaultFilterSettings;
        }
    }

    function saveConfig(keywords) {
        localStorage.setItem('filterConfig', JSON.stringify(keywords));
        customConsole.log('保存屏蔽词配置到 localStorage:', keywords);
    }

    function loadConfig() {
        return JSON.parse(localStorage.getItem('filterConfig')) || [];
    }

    function migrateSettings() {
        const storedVer = GM_getValue('dataVersion', 1);
        if (storedVer < DATA_VERSION) {
            const old = GM_getValue('settings');
            if (storedVer === 1 && old) {
                const decompressed = decompressSettings(old);
                const newSettings = { ...ConfigManager.defaultFilterSettings, ...decompressed };
                GM_setValue('settings', compressSettings(newSettings));
            }
            GM_setValue('dataVersion', DATA_VERSION);
        }
    }

    // 八、错误边界
    class ErrorBoundary {
        static wrap(fn, context) {
            return function (...args) {
                try {
                    return fn.apply(context, args);
                } catch (e) {
                    customConsole.error(`Error in ${fn.name}:`, e);
                    context.showErrorToast?.(`${fn.name}出错`, e);
                    return null;
                }
            };
        }
    }

    // 九、事件管理
    const listenerMap = new WeakMap();
    function addSafeListener(element, type, listener) {
        const wrapped = e => { try { listener(e); } catch (err) { customConsole.error('Listener error:', err); } };
        element.addEventListener(type, wrapped);
        listenerMap.set(listener, wrapped);
    }

    function removeSafeListener(element, type, listener) {
        const wrapped = listenerMap.get(listener);
        if (wrapped) {
            element.removeEventListener(type, wrapped);
            listenerMap.delete(listener);
        }
    }

    // 十、性能监控类
    class PerformanceMonitor {
        static instance;
        constructor() {
            this.metrics = { memoryUsage: [], processSpeed: [], networkRequests: [] };
            this.maxMetrics = 100;
        }

        static getInstance() {
            if (!PerformanceMonitor.instance) PerformanceMonitor.instance = new PerformanceMonitor();
            return PerformanceMonitor.instance;
        }

        recordMemory() {
            if ('memory' in performance) {
                const used = performance.memory.usedJSHeapSize;
                this.metrics.memoryUsage.push(used);
                if (this.metrics.memoryUsage.length > this.maxMetrics) this.metrics.memoryUsage.shift();
                logWrapper('pageState', 'LOG', `Memory usage: ${Math.round(used / 1024 / 1024)} MB`);
            }
        }

        recordProcessSpeed(time) {
            this.metrics.processSpeed.push(time);
            if (this.metrics.processSpeed.length > this.maxMetrics) this.metrics.processSpeed.shift();
            logWrapper('pageState', 'LOG', `Process speed: ${time.toFixed(2)} ms`);
        }
    }

    // 十一、智能空闲任务调度
    const idleQueue = [];
    let idleCallback = null;

    function scheduleIdleTask(task) {
        idleQueue.push(task);
        if (!idleCallback) {
            idleCallback = requestIdleCallback(processIdleTasks, { timeout: 1000 });
        }
    }

    function processIdleTasks(deadline) {
        while (deadline.timeRemaining() > 0 && idleQueue.length) {
            idleQueue.shift()();
        }
        if (idleQueue.length) {
            idleCallback = requestIdleCallback(processIdleTasks, { timeout: 1000 });
        } else {
            idleCallback = null;
        }
    }

    // 十二、网络优化策略
    function prefetchResources() {
        preloadList.forEach(url => {
            const link = document.createElement('link');
            link.rel = 'prefetch';
            link.href = url;
            document.head.appendChild(link);
        });
        customConsole.log('预加载资源完成:', preloadList);
    }

    const cacheStore = new Map();

    function smartFetch(url) {
        const cached = cacheStore.get(url);
        if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
            customConsole.log('从缓存读取:', url);
            return Promise.resolve(cached.data);
        }
        return fetch(url)
            .then(res => res.json())
            .then(data => {
                cacheStore.set(url, { data, timestamp: Date.now() });
                customConsole.log('缓存新数据:', url);
                return data;
            })
            .catch(err => {
                customConsole.error('请求失败:', url, err);
                throw err;
            });
    }

    // 十三、帖子过滤类
    class PostFilter {
        constructor() {
            this.settings = ConfigManager.getFilterSettings();
            this.postContainer = DomUtils.safeQuery('.l_post_list') || DomUtils.safeQuery('.pb_list') || document.body;
            this.postsCache = new Map();
            this.spamPosts = new Set();
            this.originalOrder = new Map();
            this.isPageLoaded = false;
            this.isLoading = false;
            
            this.firstFloorPid = null;
            this.firstFloorIdentified = false;
            this.protectedPids = new Set();
            
            this.applyStyles();
            this.saveOriginalOrder();
            this.applyFilters = ErrorBoundary.wrap(this.applyFilters, this);
            
            this.updateFirstFloorCache();
            
            if (document.readyState === 'complete') {
                this.isPageLoaded = true;
                this.applyFilters();
            } else {
                const loadHandler = () => {
                    this.isPageLoaded = true;
                    this.applyFilters();
                    window.removeEventListener('load', loadHandler);
                };
                window.addEventListener('load', loadHandler);
            }
            
            this.autoExpandImages();
            this.observeDOMChanges();
            this.handlePagination();
            this.startSpamEnforcer();
            this.blockAds();
            this.interceptAjax();
            if (this.settings.linkifyVideos) this.linkifyVideos();
            this.cleanupMemory();
            
            setTimeout(() => {
                this.runFilterTests();
            }, 1000);
            
            customConsole.log('PostFilter 初始化完成');
        }

        applyStyles() {
            GM_addStyle(`
                .l_post {
                    transition: opacity 0.3s ease;
                    overflow: hidden;
                }
                .spam-hidden {
                    display: none !important;
                    opacity: 0;
                }
                .invalid-hidden { display: none !important; }
                .pb_list.filtering, .l_post_list.filtering {
                    position: relative;
                    opacity: 0.8;
                }
                .pb_list.filtering::after, .l_post_list.filtering::after {
                    content: "过滤中...";
                    position: absolute;
                    top: 20px;
                    left: 50%;
                    transform: translateX(-50%);
                    color: #666;
                }
            `);
        }

        saveOriginalOrder() {
            if (this.originalOrder.size > 0) {
                customConsole.log('跳过保存帖子原始顺序,已保存过');
                return;
            }
            const posts = DomUtils.safeQueryAll('.l_post', this.postContainer);
            customConsole.log('保存帖子原始顺序,检测到帖子数量:', posts.length);
            posts.forEach(post => {
                const data = this.safeGetPostData(post);
                if (!this.originalOrder.has(data.pid)) {
                    this.originalOrder.set(data.pid, { pid: data.pid, floor: data.floor });
                }
            });
            customConsole.log('保存帖子原始顺序完成,数量:', this.originalOrder.size);
            logWrapper('pageBehavior', 'LOG', `Saved original order, posts: ${this.originalOrder.size}`);
        }

        updateFirstFloorCache() {
            if (this.firstFloorIdentified) return;
            
            const allPosts = DomUtils.safeQueryAll('.l_post', this.postContainer);
            if (allPosts.length === 0) return;
            
            for (const post of allPosts) {
                const floor = parseInt(post?.dataset?.floor || post?.getAttribute?.('data-floor')) || 0;
                if (floor === 1) {
                    const pid = post?.dataset?.pid || post?.getAttribute?.('data-pid') || `temp_${Date.now()}`;
                    this.firstFloorPid = pid;
                    this.firstFloorIdentified = true;
                    this.protectedPids.add(pid);
                    customConsole.log('通过 data-floor 属性识别一楼帖子:', pid);
                    return;
                }
            }
            
            const firstPost = allPosts[0];
            const pid = firstPost?.dataset?.pid || firstPost?.getAttribute?.('data-pid') || `temp_${Date.now()}`;
            this.firstFloorPid = pid;
            this.firstFloorIdentified = true;
            this.protectedPids.add(pid);
            customConsole.log('通过DOM位置识别一楼帖子:', pid);
        }

        applyFilters(nodes = DomUtils.safeQueryAll('.l_post:not(.filtered)', this.postContainer)) {
            this.updateFirstFloorCache();
            
            logWrapper('script', 'LOG', 'Starting filter process', `Keywords: ${this.settings.spamKeywords}`);
            customConsole.log('开始应用过滤器,帖子数量:', nodes.length);

            const startTime = performance.now();
            this.postContainer.classList.add('filtering');
            let hiddenCount = 0;

            const keywords = this.settings.spamKeywords
                .map(k => k.trim())
                .filter(k => k.length > 0);
            const regex = keywords.length > 0
                ? new RegExp(`(${keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})`, 'i')
                : null;

            nodes.forEach(post => {
                const data = this.safeGetPostData(post);
                const pid = data.pid;

                if (!post || post.classList.contains('filtered')) return;

                post.style.display = '';
                post.classList.remove('spam-hidden', 'invalid-hidden');

                if (data.isFirstFloor || pid === this.firstFloorPid) {
                    post.classList.add('filtered');
                    this.postsCache.set(pid, true);
                    this.spamPosts.delete(pid);
                    this.protectedPids.add(pid);
                    return;
                }

                if (this.settings.hideInvalid && !data.content) {
                    post.classList.add('invalid-hidden');
                    hiddenCount++;
                    logWrapper('pageBehavior', 'LOG', `Hid invalid post: ${pid}`);
                    if (this.settings.showHiddenFloors) {
                        post.classList.remove('invalid-hidden');
                        hiddenCount--;
                        logWrapper('pageBehavior', 'LOG', `Restored invalid post: ${pid}`);
                    }
                } else if (this.settings.hideSpam && data.content && regex && regex.test(data.content)) {
                    if (this.protectedPids.has(pid)) {
                        logWrapper('pageBehavior', 'WARN', `Skipped filtering protected post: ${pid}`);
                        return;
                    }
                    
                    post.classList.add('spam-hidden');
                    post.style.display = 'none';
                    this.spamPosts.add(pid);
                    hiddenCount++;
                    const matchedKeyword = keywords.find(k => data.content.toLowerCase().includes(k.toLowerCase())) || 'unknown';
                    logWrapper('pageBehavior', 'LOG', `Hid spam post: ${pid}, Keyword: ${matchedKeyword}, Content: ${data.content.slice(0, 50)}...`);
                }

                post.classList.add('filtered');
                this.postsCache.set(pid, true);
            });

            // 只有在页面完全加载后才应用屏蔽规则
            if (this.isPageLoaded) {
                // 安全检查:避免匹配帖子核心内容区域
                const corePostClasses = ['l_post', 'd_post_content', 'p_author', 'j_p_postlist', 'pb_content', 'l_post_list', 'pb_list', 'd_post_content_main', 'p_content', 'core_reply_tail', 'core_reply_content', 'j_lzl_container', 'lzl_content_main'];
                const blockedElements = [...(this.settings.blockedElements || []), ...(this.settings.tempBlockedElements || [])];
                blockedElements.forEach(selector => {
                    try {
                        // 跳过无效选择器
                        if (!selector || typeof selector !== 'string' || selector.trim() === '') {
                            return;
                        }
                        
                        // 跳过可能影响页面加载的选择器
                        const problematicSelectors = ['ul.tbui_aside_float_bar', '.tbui_aside_float_bar', 'aside', 'sidebar', '.aside', '.sidebar'];
                        if (problematicSelectors.includes(selector)) {
                            customConsole.log(`跳过可能影响页面加载的选择器: ${selector}`);
                            return;
                        }
                        
                        // 优化:限制选择器范围,只在非核心内容区域应用
                        const elements = DomUtils.safeQueryAll(selector + ':not(.filtered)', this.postContainer);
                        elements.forEach(el => {
                            // 关键安全检查:如果元素是帖子的子元素,跳过屏蔽
                            if (el.closest('.l_post')) {
                                // 元素在帖子内部,跳过屏蔽
                                return;
                            }
                            
                            // 检查元素是否为核心内容区域
                            let isCoreContent = false;
                            if (el.classList) {
                                for (const cls of corePostClasses) {
                                    if (el.classList.contains(cls)) {
                                        isCoreContent = true;
                                        break;
                                    }
                                }
                            }
                            
                            // 检查父元素是否是核心内容区域
                            let parent = el.parentNode;
                            for (let i = 0; i < 4; i++) {
                                if (parent && parent.nodeType === 1) {
                                    if (parent.classList && parent.classList.contains('l_post')) {
                                        isCoreContent = true;
                                        break;
                                    }
                                    parent = parent.parentNode;
                                } else {
                                    break;
                                }
                            }
                            
                            // 只屏蔽非核心内容区域,并且避免影响页面布局
                            if (!isCoreContent) {
                                // 检查元素是否在视口中,避免屏蔽不可见元素
                                const rect = el.getBoundingClientRect();
                                if (rect.width > 0 && rect.height > 0) {
                                    const pid = el.dataset.pid || `temp_${Date.now()}`;
                                    if (!this.postsCache.has(pid)) {
                                        el.classList.add('spam-hidden');
                                        el.style.display = 'none';
                                        hiddenCount++;
                                        logWrapper('pageBehavior', 'LOG', `Hid blocked element: ${selector}`);
                                        el.classList.add('filtered');
                                        this.postsCache.set(pid, true);
                                    }
                                }
                            }
                        });
                    } catch (error) {
                        customConsole.error(`Invalid selector: ${selector}`, error);
                        // 移除无效选择器
                        const blockList = this.settings.blockType === 'temp' ? this.settings.tempBlockedElements : this.settings.blockedElements;
                        const index = blockList.indexOf(selector);
                        if (index > -1) {
                            blockList.splice(index, 1);
                            ConfigManager.updateFilterSettings(this.settings);
                        }
                    }
                });
            }

            setTimeout(() => {
                this.postContainer.classList.remove('filtering');
                if (hiddenCount > 0) this.showToast(`已屏蔽 ${hiddenCount} 条水贴`, 'success');
                customConsole.log(`[LOG] 翻页后过滤完成,处理帖子数:${nodes.length}`);
                logWrapper('pageBehavior', 'LOG', `Filter completed, processed posts: ${nodes.length}, hidden: ${hiddenCount}`);
            }, 500);

            const endTime = performance.now();
            PerformanceMonitor.getInstance().recordProcessSpeed(endTime - startTime);
            return hiddenCount;
        }

        startSpamEnforcer() {
            const enforce = () => {
                if (this.spamPosts.size === 0) {
                    setTimeout(enforce, 1000);
                    return;
                }

                const allPosts = DomUtils.safeQueryAll('.l_post', this.postContainer);
                let enforcedCount = 0;

                allPosts.forEach(post => {
                    const data = this.safeGetPostData(post);
                    const pid = data.pid;
                    
                    if (data.isFirstFloor || pid === this.firstFloorPid || this.protectedPids.has(pid)) {
                        this.spamPosts.delete(pid);
                        post.style.display = '';
                        post.classList.remove('spam-hidden');
                        return;
                    }
                    
                    if (this.spamPosts.has(pid) && document.body.contains(post) && post.style.display !== 'none') {
                        post.style.display = 'none';
                        post.classList.add('spam-hidden');
                        enforcedCount++;
                        logWrapper('pageBehavior', 'LOG', `Re-enforced spam hiding for post: ${pid}`);
                    }
                });

                if (enforcedCount > 0) {
                    customConsole.log(`Spam enforcer 执行完毕,处理了 ${enforcedCount} 个帖子`);
                }

                setTimeout(enforce, 1000);
            };
            enforce();
            customConsole.log('Spam enforcer 已启动');
        }

        autoExpandImages(nodes = DomUtils.safeQueryAll('.replace_tip:not(.expanded)', this.postContainer)) {
            if (!this.settings.autoExpandImages) return;
            customConsole.log('开始自动展开图片,图片数量:', nodes.length);
            logWrapper('pageBehavior', 'LOG', 'Auto expanding images');
            nodes.forEach(tip => {
                if (tip.style.display !== 'none' && !tip.classList.contains('expanded')) {
                    const rect = tip.getBoundingClientRect();
                    tip.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.top }));
                    tip.classList.add('expanded');
                    const img = DomUtils.safeQuery('img', tip.closest('.replace_div'));
                    if (this.settings.enhanceImages && img) this.enhanceImage(img);
                    this.postsCache.set(tip.dataset.pid || `img_${Date.now()}`, true);
                    logWrapper('pageBehavior', 'LOG', `Expanded image for post: ${tip.dataset.pid || 'unknown'}`);
                }
            });
        }

        updateFilters() {
            this.settings = ConfigManager.getFilterSettings();
            customConsole.log('更新过滤器配置,屏蔽词:', this.settings.spamKeywords);
            logWrapper('script', 'LOG', 'Updating filters with new settings');
            this.postsCache.clear();
            this.spamPosts.clear();
            this.originalOrder.clear();
            this.saveOriginalOrder();
            this.applyFilters();
            this.autoExpandImages();
            this.blockAds();
            if (this.settings.linkifyVideos) this.linkifyVideos();
        }

        observeDOMChanges() {
            let isFiltering = false;
            let lastFilterTime = 0;
            const MIN_FILTER_INTERVAL = 1000;

            const debouncedFilter = _.debounce(() => {
                const now = Date.now();
                if (isFiltering || now - lastFilterTime < MIN_FILTER_INTERVAL) {
                    customConsole.log('跳过过滤:正在过滤或间隔时间不足');
                    return;
                }
                
                if (this.isPageLoaded) {
                    isFiltering = true;
                    lastFilterTime = now;
                    try {
                        this.applyFilters();
                        this.autoExpandImages();
                        logWrapper('pageBehavior', 'LOG', 'DOM change detected, filters reapplied');
                    } finally {
                        isFiltering = false;
                    }
                }
            }, 800);

            const observer = new MutationObserver(mutations => {
                let shouldApplyFilter = false;
                let hasNewContent = false;
                
                mutations.forEach(mut => {
                    if (mut.addedNodes.length) {
                        for (const node of mut.addedNodes) {
                            if (node.nodeType === 1) {
                                if (node.classList?.contains('l_post') || node.querySelector('.l_post')) {
                                    hasNewContent = true;
                                    shouldApplyFilter = true;
                                    break;
                                }
                                if (node.classList?.contains('ad_item') || node.classList?.contains('mediago')) {
                                    hasNewContent = true;
                                }
                            }
                        }
                    }
                });
                
                if (shouldApplyFilter && hasNewContent && this.isPageLoaded) {
                    debouncedFilter();
                }
            });

            const postContainers = ['.l_post_list', '.pb_list', '.j_p_postlist'];
            for (const selector of postContainers) {
                const container = DomUtils.safeQuery(selector);
                if (container) {
                    observer.observe(container, { childList: true, subtree: false });
                    customConsole.log('MutationObserver 已初始化,监听容器:', selector);
                    return;
                }
            }
            
            const bodyObserver = new MutationObserver(mutations => {
                let shouldApplyFilter = false;
                mutations.forEach(mut => {
                    if (mut.addedNodes.length) {
                        for (const node of mut.addedNodes) {
                            if (node.nodeType === 1 && (node.classList?.contains('l_post') || node.querySelector('.l_post'))) {
                                shouldApplyFilter = true;
                                break;
                            }
                        }
                    }
                });
                if (shouldApplyFilter && this.isPageLoaded) {
                    debouncedFilter();
                }
            });
            
            bodyObserver.observe(document.body, { childList: true, subtree: false });
            customConsole.log('MutationObserver 已初始化,监听document.body');
        }

        handlePagination() {
            addSafeListener(document, 'click', e => {
                if (e.target.closest('.pagination a, .pager a')) {
                    customConsole.log('检测到翻页按钮点击');
                    logWrapper('userActions', 'LOG', 'Pagination button clicked');
                    this.isPageLoaded = false;
                    this.postsCache.clear();
                    this.spamPosts.clear();
                    this.originalOrder.clear();
                    setTimeout(() => {
                        this.saveOriginalOrder();
                        this.applyFilters();
                        this.autoExpandImages();
                        this.isPageLoaded = true;
                    }, 500);
                }
            });
            customConsole.log('翻页按钮监听已初始化');
        }

        interceptAjax() {
            const originalFetch = window.fetch;
            window.fetch = async (url, options) => {
                const response = await originalFetch(url, options);
                if (typeof url === 'string' && /tieba\.baidu\.com\/p\/\d+(\?pn=\d+)?$/.test(url)) {
                    customConsole.log('检测到翻页 fetch 请求:', url);
                    logWrapper('pageBehavior', 'LOG', `Page change detected via fetch: ${url}`);
                    this.postsCache.clear();
                    this.spamPosts.clear();
                    this.originalOrder.clear();
                    this.isPageLoaded = false;
                    requestAnimationFrame(() => {
                        setTimeout(() => {
                            this.saveOriginalOrder();
                            this.applyFilters();
                            this.autoExpandImages();
                            this.blockAds();
                            this.isPageLoaded = true;
                        }, 300);
                    });
                }
                return response;
            };

            const originalOpen = XMLHttpRequest.prototype.open;
            const self = this;
            XMLHttpRequest.prototype.open = function(method, url, ...args) {
                this.addEventListener('load', () => {
                    try {
                        if (typeof url === 'string' && url.includes('tieba.baidu.com/p/')) {
                            customConsole.log('检测到翻页 XMLHttpRequest 请求:', this.responseURL);
                            logWrapper('pageBehavior', 'LOG', `Page change detected via XMLHttpRequest: ${this.responseURL}`);
                            self.postsCache.clear();
                            self.spamPosts.clear();
                            self.originalOrder.clear();
                            self.isPageLoaded = false;
                            requestAnimationFrame(() => {
                                setTimeout(() => {
                                    self.saveOriginalOrder();
                                    self.applyFilters();
                                    self.autoExpandImages();
                                    self.blockAds();
                                    self.isPageLoaded = true;
                                }, 200);
                            });
                        }
                    } catch (error) {
                        customConsole.error('XMLHttpRequest load 事件处理错误:', error);
                    }
                });
                return originalOpen.call(this, method, url, ...args);
            };

            customConsole.log('AJAX 拦截器已初始化(fetch 和 XMLHttpRequest)');
        }

        blockAds() {
            if (!this.settings.blockAds) return;
            customConsole.log('开始屏蔽广告');
            logWrapper('pageBehavior', 'LOG', 'Blocking advertisements');
            const adSelectors = ['.ad_item', '.mediago', '[class*="ad_"]:not([class*="content"])', '.app_download_box', '.right_section .region_bright:not(.content)'];
            adSelectors.forEach(selector => {
                DomUtils.safeQueryAll(selector + ':not(.filtered)', this.postContainer).forEach(el => {
                    if (!el.closest('.d_post_content') && !el.closest('.l_container')) {
                        el.classList.add('spam-hidden');
                        const pid = el.dataset.pid || `ad_${Date.now()}`;
                        this.postsCache.set(pid, true);
                        el.classList.add('filtered');
                        logWrapper('pageBehavior', 'LOG', `Hid ad element: ${selector}`);
                    }
                });
            });
        }

        restoreOriginalOrder(isPageChange = false) {
            if (!this.postContainer) return;

            customConsole.log('开始恢复帖子顺序,原始顺序数量:', this.originalOrder.size);
            logWrapper('pageBehavior', 'LOG', `Restoring original order, isPageChange: ${isPageChange}`);

            const currentPosts = new Map();
            DomUtils.safeQueryAll('.l_post', this.postContainer).forEach(post => {
                const data = this.safeGetPostData(post);
                currentPosts.set(data.pid, post);
            });

            if (currentPosts.size === 0 || currentPosts.size !== this.originalOrder.size) {
                customConsole.log('跳过恢复帖子顺序,当前帖子数量:', currentPosts.size, '原始顺序数量:', this.originalOrder.size);
                return;
            }

            const sortedOrder = Array.from(this.originalOrder.values())
                .sort((a, b) => Number(a.floor) - Number(b.floor));

            let hasChanged = false;
            const currentPostsArray = Array.from(DomUtils.safeQueryAll('.l_post', this.postContainer));
            for (let i = 0; i < currentPostsArray.length; i++) {
                const currentPost = currentPostsArray[i];
                const data = this.safeGetPostData(currentPost);
                const expectedPost = sortedOrder[i];
                if (expectedPost && data.pid !== expectedPost.pid) {
                    hasChanged = true;
                    break;
                }
            }

            if (!hasChanged) {
                customConsole.log('帖子顺序未改变,无需恢复');
                return;
            }

            sortedOrder.forEach((item, index) => {
                const existingPost = currentPosts.get(item.pid);
                if (existingPost) {
                    const data = this.safeGetPostData(existingPost);
                    if (data.isFirstFloor || data.pid === this.firstFloorPid) {
                        existingPost.style.display = '';
                        existingPost.classList.remove('spam-hidden', 'invalid-hidden');
                        this.spamPosts.delete(data.pid);
                        this.protectedPids.add(data.pid);
                    }
                    this.postContainer.appendChild(existingPost);
                }
            });

            customConsole.log('帖子顺序恢复完成,数量:', sortedOrder.length);
            logWrapper('pageBehavior', 'LOG', `Restored order, posts: ${sortedOrder.length}`);
            if (!isPageChange) this.applyFilters();
        }

        linkifyVideos() {
            customConsole.log('开始链接化视频');
            logWrapper('pageBehavior', 'LOG', 'Linking videos');
            const videoRegex = /(?:av\d+|BV\w+)|(?:https?:\/\/(?:www\.)?(youtube\.com|youtu\.be)\/[^\s]+)/gi;
            DomUtils.safeQueryAll('.d_post_content:not(.filtered)', this.postContainer).forEach(post => {
                const pid = this.safeGetPostData(post).pid;
                if (!this.postsCache.has(pid)) {
                    post.innerHTML = post.innerHTML.replace(videoRegex, match => {
                        return match.startsWith('http') ? `<a href="${match}" target="_blank">${match}</a>` : `<a href="https://bilibili.com/video/${match}" target="_blank">${match}</a>`;
                    });
                    post.classList.add('filtered');
                    this.postsCache.set(pid, true);
                    logWrapper('pageBehavior', 'LOG', `Linked videos in post: ${pid}`);
                }
            });
        }

        enhanceImage(img) {
            if (!img) return;
            img.style.cursor = 'pointer';
            removeSafeListener(img, 'click', this.handleImageClick);
            addSafeListener(img, 'click', this.handleImageClick.bind(this));
            logWrapper('pageBehavior', 'LOG', `Enhanced image: ${img.src}`);
        }

        handleImageClick(event) {
            const overlay = document.createElement('div');
            overlay.className = 'image-overlay';
            const largeImg = document.createElement('img');
            largeImg.src = event.target.src;
            largeImg.className = 'large-image';

            // 添加拖动相关变量
            let isDragging = false;
            let startX = 0;
            let startY = 0;
            let translateX = 0;
            let translateY = 0;
            let scale = 1;

            overlay.appendChild(largeImg);
            document.body.appendChild(overlay);

            // 添加关闭按钮
            const closeBtn = document.createElement('button');
            closeBtn.textContent = '✕';
            closeBtn.style.cssText = `
                position: absolute;
                top: 20px;
                right: 20px;
                background: rgba(0, 0, 0, 0.5);
                color: white;
                border: none;
                border-radius: 50%;
                width: 30px;
                height: 30px;
                font-size: 20px;
                cursor: pointer;
                z-index: 10002;
            `;
            overlay.appendChild(closeBtn);

            // 关闭图片放大视图
            const closeOverlay = () => {
                overlay.remove();
            };

            addSafeListener(overlay, 'click', closeOverlay);
            addSafeListener(closeBtn, 'click', e => {
                e.stopPropagation();
                closeOverlay();
            });

            // 图片缩放功能
            addSafeListener(overlay, 'wheel', e => {
                e.preventDefault();
                const delta = e.deltaY > 0 ? 0.9 : 1.1;
                const rect = largeImg.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;

                // 计算缩放中心相对于图片的位置
                const relX = x / rect.width;
                const relY = y / rect.height;

                // 计算缩放前后的偏移量变化
                const prevWidth = rect.width;
                const prevHeight = rect.height;
                const newScale = scale * delta;
                const newWidth = prevWidth * delta;
                const newHeight = prevHeight * delta;

                // 调整平移量,使缩放中心保持不变
                translateX -= (newWidth - prevWidth) * relX;
                translateY -= (newHeight - prevHeight) * relY;

                scale = newScale;

                // 应用变换
                largeImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
            });

            // 图片拖动功能
            addSafeListener(largeImg, 'mousedown', e => {
                e.stopPropagation();
                isDragging = true;
                startX = e.clientX - translateX;
                startY = e.clientY - translateY;
                largeImg.style.cursor = 'grabbing';
            });

            addSafeListener(document, 'mousemove', e => {
                if (!isDragging) return;
                translateX = e.clientX - startX;
                translateY = e.clientY - startY;
                largeImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
            });

            addSafeListener(document, 'mouseup', () => {
                isDragging = false;
                largeImg.style.cursor = 'grab';
            });

            // 双击重置缩放和平移
            addSafeListener(largeImg, 'dblclick', e => {
                e.stopPropagation();
                scale = 1;
                translateX = 0;
                translateY = 0;
                largeImg.style.transform = `translate(0, 0) scale(1)`;
            });

            // 设置初始样式
            largeImg.style.transform = `translate(0, 0) scale(1)`;
            largeImg.style.cursor = 'grab';
            largeImg.style.transition = 'transform 0.1s ease';

            logWrapper('userActions', 'LOG', `Clicked to enlarge image: ${event.target.src}`);
        }

        safeGetPostData(post) {
            if (!post || post.nodeType !== 1) {
                return { pid: `temp_${Date.now()}`, floor: 0, content: '', author: '匿名', isFirstFloor: false };
            }
            
            const pid = post?.dataset?.pid || post?.getAttribute?.('data-pid') || `temp_${Date.now()}`;
            let floor = parseInt(post?.dataset?.floor || post?.getAttribute?.('data-floor')) || 0;
            
            let isFirstFloor = false;
            if (floor === 1) {
                isFirstFloor = true;
            } else if (this.firstFloorPid === pid) {
                isFirstFloor = true;
            } else if (!this.firstFloorIdentified) {
                const allPosts = DomUtils.safeQueryAll('.l_post', this.postContainer);
                if (allPosts.length > 0 && allPosts[0] === post) {
                    isFirstFloor = true;
                    this.firstFloorPid = pid;
                    this.firstFloorIdentified = true;
                }
            }
            
            const contentEle = DomUtils.safeQuery('.d_post_content', post);
            const content = contentEle ? contentEle.textContent.trim() : '';
            const author = DomUtils.safeQuery('.p_author_name', post)?.textContent?.trim() || '匿名';
            
            return { pid, floor, content, author, isFirstFloor };
        }

        runFilterTests() {
            const testResults = [];
            
            const test1 = this.testFirstFloorProtection();
            testResults.push({ name: '一楼帖子保护', result: test1 });
            
            const test2 = this.testNormalContent();
            testResults.push({ name: '正常内容保护', result: test2 });
            
            const test3 = this.testSpamFiltering();
            testResults.push({ name: '水贴内容屏蔽', result: test3 });
            
            customConsole.log('过滤测试结果:', testResults);
            return testResults;
        }

        testFirstFloorProtection() {
            const mockPost = {
                dataset: { pid: 'test_pid_1', floor: '1' },
                classList: { contains: () => false }
            };
            
            const data = this.safeGetPostData(mockPost);
            const isProtected = data.isFirstFloor || this.protectedPids.has(data.pid);
            
            customConsole.log(`测试一楼保护: floor=${data.floor}, isFirstFloor=${data.isFirstFloor}, protected=${isProtected}`);
            return isProtected;
        }

        testNormalContent() {
            const mockPost = {
                dataset: { pid: 'test_pid_2', floor: '2' },
                classList: { contains: () => false },
                textContent: '这是正常的帖子内容'
            };
            
            const data = this.safeGetPostData(mockPost);
            const shouldHide = this.settings.hideSpam && 
                              data.content && 
                              this.settings.spamKeywords.some(k => data.content.includes(k));
            
            customConsole.log(`测试正常内容: shouldHide=${shouldHide}`);
            return !shouldHide;
        }

        testSpamFiltering() {
            const mockPost = {
                dataset: { pid: 'test_pid_3', floor: '3' },
                classList: { contains: () => false },
                textContent: '顶顶顶'
            };
            
            const data = this.safeGetPostData(mockPost);
            const shouldHide = this.settings.hideSpam && 
                              data.content && 
                              this.settings.spamKeywords.some(k => data.content.includes(k));
            
            customConsole.log(`测试水贴屏蔽: shouldHide=${shouldHide}`);
            return shouldHide;
        }

        cleanupMemory() {
            // 初始化缓存时为每个帖子添加时间戳
            this.postsCache = new Map([...this.postsCache.entries()].map(([pid, value]) => {
                if (typeof value === 'boolean') {
                    return [pid, { used: value, timestamp: Date.now() }];
                }
                return [pid, value];
            }));

            const cleanupInterval = setInterval(() => {
                const now = Date.now();
                const CACHE_EXPIRY = 300000; // 5分钟
                let removedCount = 0;

                // 遍历缓存,移除过期的缓存项
                for (const [pid, data] of this.postsCache.entries()) {
                    if (typeof data === 'object' && data.timestamp && now - data.timestamp > CACHE_EXPIRY) {
                        this.postsCache.delete(pid);
                        removedCount++;
                    }
                }

                // 只有当有缓存项被移除时才输出日志
                if (removedCount > 0) {
                    customConsole.log(`清理内存,移除了 ${removedCount} 个过期缓存项,剩余缓存条目:`, this.postsCache.size);
                }

                // 当缓存大小为0时,清除定时器
                if (this.postsCache.size === 0) {
                    clearInterval(cleanupInterval);
                    customConsole.log('缓存已清空,停止清理定时器');
                }
            }, 60000); // 每分钟清理一次
        }

        showToast(message, type = 'info') {
            const toast = document.createElement('div');
            toast.textContent = message;
            toast.style.cssText = `
                position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
                background: ${type === 'success' ? '#34c759' : '#ff4444'}; color: white;
                padding: 10px 20px; border-radius: 5px; z-index: 10001; transition: opacity 0.3s;
            `;
            document.body.appendChild(toast);
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => toast.remove(), 300);
            }, 2000);
            logWrapper('script', 'LOG', `Showed toast: ${message}`);
        }
    }

    // 十四、动态面板类
    class DynamicPanel {
        constructor() {
            this.panel = null;
            this.minimizedIcon = null;
            this.isDragging = false;
            this.dragOccurred = false;
            this.lastClickTime = 0;
            this.isResizing = false;
            this.settings = ConfigManager.getFilterSettings();
            this.panelSettings = ConfigManager.getPanelSettings();
            this.postFilter = new PostFilter();
            this.debugPanel = { show: () => {}, hide: () => {}, update: () => {}, remove: () => {} }; // 简化处理
            this.init();
            this.applyDarkMode(this.settings.darkMode);
            migrateSettings();
            if (!GM_getValue('debugMode', true)) this.debugPanel.hide();
            customConsole.log('DynamicPanel 初始化完成');
        }

        init() {
            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'));
            this.setupUserActionListeners();
            this.setupCleanup();
            scheduleIdleTask(() => this.startPerformanceMonitoring());
            prefetchResources();
        }

        ensureVisibility() {
            this.panel.style.opacity = '1';
            this.panel.style.visibility = 'visible';
            this.minimizedIcon.style.opacity = '1';
            this.minimizedIcon.style.visibility = 'visible';
            customConsole.log('Panel visibility ensured at:', this.panelSettings.position);
        }

        createPanel() {
            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: opacity 0.3s ease; will-change: left, top; 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; transform: translateZ(0); backface-visibility: hidden; }
                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; transition: background 0.3s, color 0.3s;
                }
                body.dark-mode #enhanced-panel { background: rgba(50,50,50,0.98) !important; color: #ddd !important; }
                body.dark-mode a { color: #66b3ff !important; }
                @media (max-width: 768px) {
                    #enhanced-panel { width: 90vw !important; left: 5vw !important; }
                    .tool-grid { grid-template-columns: 1fr; }
                }
                .quick-actions { margin-top: 10px; }
                .quick-actions button { margin: 5px; padding: 5px 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
            `);
            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>
            `;
        }

        createMinimizedIcon() {
            this.minimizedIcon = document.createElement('div');
            this.minimizedIcon.id = 'minimized-icon';
            this.minimizedIcon.textContent = '⚙️';
            addSafeListener(this.minimizedIcon, 'click', e => {
                const now = Date.now();
                if (now - this.lastClickTime > 300 && !this.dragOccurred) {
                    this.toggleMinimize();
                    this.lastClickTime = now;
                }
                this.dragOccurred = false;
                e.stopPropagation();
            });
        }

        loadContent() {
            const content = DomUtils.safeQuery('.panel-content', this.panel);
            content.innerHTML = `
                <div class="filter-controls">
                    <h3>📊 智能过滤设置</h3>
                    <div class="setting-group">
                        <label class="toggle-switch">
                            <input type="checkbox" data-setting="debugMode" ${GM_getValue('debugMode', true) ? '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="quick-actions">
                    <button data-action="toggleAllImages">一键展开/收起图片</button>
                </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);
        }

        adjustPanelHeight() {
            if (this.panelSettings.minimized) return;
            const content = DomUtils.safeQuery('.panel-content', this.panel);
            const headerHeight = DomUtils.safeQuery('.panel-header', this.panel)?.offsetHeight || 0;
            const maxHeight = Math.min(content.scrollHeight + headerHeight + 32, window.innerHeight * 0.9);
            this.panel.style.height = `${maxHeight}px`;
        }

        bindEvents() {
            DomUtils.safeQueryAll('input[type="checkbox"]', this.panel).forEach(checkbox => {
                addSafeListener(checkbox, 'change', () => {
                    if (checkbox.dataset.setting === 'debugMode') {
                        GM_setValue('debugMode', checkbox.checked);
                        if (checkbox.checked) {
                            this.debugPanel.show();
                        } else {
                            this.debugPanel.hide();
                        }
                    } else if (checkbox.dataset.setting === 'darkMode') {
                        this.settings.darkMode = checkbox.checked;
                        ConfigManager.updateFilterSettings(this.settings);
                        this.applyDarkMode(checkbox.checked);
                    } else {
                        this.settings[checkbox.dataset.setting] = checkbox.checked;
                        ConfigManager.updateFilterSettings(this.settings);
                        this.postFilter.updateFilters();
                    }
                    logWrapper('userActions', 'LOG', `Toggled ${checkbox.dataset.setting} to ${checkbox.checked}`);
                    this.adjustPanelHeight();
                });
            });

            const blockTypeSelect = DomUtils.safeQuery('[data-setting="blockType"]', this.panel);
            if (blockTypeSelect) {
                addSafeListener(blockTypeSelect, 'change', e => {
                    this.settings.blockType = e.target.value;
                    ConfigManager.updateFilterSettings(this.settings);
                    logWrapper('userActions', 'LOG', `Changed blockType to ${e.target.value}`);
                });
            }

            const actions = {
                editKeywords: () => this.showKeywordEditor(),
                toggleBlockMode: () => this.toggleBlockMode(),
                showUndoList: () => this.showUndoList(),
                exportSettings: () => this.exportConfig(),
                importSettings: () => this.importConfig(),
                performanceChart: () => {
                    const perfInfo = DomUtils.safeQuery('.performance-info', this.panel);
                    perfInfo.style.display = perfInfo.style.display === 'block' ? 'none' : 'block';
                    this.adjustPanelHeight();
                    logWrapper('userActions', 'LOG', `Toggled performance chart: ${perfInfo.style.display}`);
                },
                quickSearch: () => this.toggleSearch(),
                saveLogs: () => this.showLogSaveDialog(),
                toggleAllImages: () => {
                    const tips = DomUtils.safeQueryAll('.replace_tip', this.postFilter.postContainer);
                    const allExpanded = Array.from(tips).every(tip => tip.classList.contains('expanded'));
                    tips.forEach(tip => {
                        if (allExpanded && tip.classList.contains('expanded')) {
                            tip.click();
                            tip.classList.remove('expanded');
                        } else if (!tip.classList.contains('expanded')) {
                            tip.click();
                            tip.classList.add('expanded');
                        }
                    });
                    this.showToast(allExpanded ? '已收起所有图片' : '已展开所有图片', 'success');
                    logWrapper('userActions', 'LOG', `Toggled all images: ${allExpanded ? 'collapsed' : 'expanded'}`);
                }
            };

            DomUtils.safeQueryAll('[data-action]', this.panel).forEach(btn => {
                addSafeListener(btn, 'click', () => actions[btn.dataset.action]?.());
            });

            const minimizeBtn = DomUtils.safeQuery('.minimize-btn', this.panel);
            if (minimizeBtn) addSafeListener(minimizeBtn, 'click', e => { this.toggleMinimize(); e.stopPropagation(); });
        }

        setupPanelInteractions() {
            const header = DomUtils.safeQuery('.panel-header', this.panel);
            const resizeHandle = DomUtils.safeQuery('.resize-handle', this.panel);
            let startX, startY, startWidth, startHeight, rafId;

    const onDragStart = (e, isIcon = false) => {
        if (e.button !== 0) return; // 只响应左键
        this.isDragging = true;
        this.dragOccurred = false;
        const target = isIcon ? this.minimizedIcon : this.panel;
        startX = e.clientX - this.panelSettings.position.x;
        startY = e.clientY - this.panelSettings.position.y;
        e.preventDefault();
        customConsole.log(`Drag started at:`, { x: e.clientX, y: e.clientY, isIcon });
    };

    const updatePosition = (x, y) => {
        const target = this.panelSettings.minimized ? this.minimizedIcon : this.panel;
        const panelWidth = target.offsetWidth;
        const panelHeight = target.offsetHeight;
        this.panelSettings.position.x = Math.max(10, Math.min(x - startX, window.innerWidth - panelWidth - 10));
        this.panelSettings.position.y = Math.max(10, Math.min(y - startY, window.innerHeight - panelHeight - 10));
        target.style.left = `${this.panelSettings.position.x}px`;
        target.style.top = `${this.panelSettings.position.y}px`;
        customConsole.log('Dragging to:', { x: this.panelSettings.position.x, y: this.panelSettings.position.y, minimized: this.panelSettings.minimized });
    };

    const onDragMove = (e) => {
        if (!this.isDragging) return;
        this.dragOccurred = true;
        cancelAnimationFrame(rafId);
        rafId = requestAnimationFrame(() => updatePosition(e.clientX, e.clientY));
    };

    const onDragEnd = (e) => {
        if (!this.isDragging) return;
        this.isDragging = false;
        cancelAnimationFrame(rafId);
        ConfigManager.updatePanelSettings(this.panelSettings);
        this.adjustPanelHeight();
        customConsole.log('Drag ended, position:', this.panelSettings.position);
        setTimeout(() => { this.dragOccurred = false; }, 100);
    };

    if (header) {
        addSafeListener(header, 'mousedown', (e) => onDragStart(e, false));
    }
    addSafeListener(this.minimizedIcon, 'mousedown', (e) => onDragStart(e, true));
    addSafeListener(document, 'mousemove', onDragMove);
    addSafeListener(document, 'mouseup', onDragEnd);

    if (resizeHandle) {
        addSafeListener(resizeHandle, 'mousedown', (e) => {
            this.isResizing = true;
            startX = e.clientX;
            startY = e.clientY;
            startWidth = this.panelSettings.width;
            startHeight = parseInt(this.panel.style.height) || this.panel.offsetHeight;

            // 添加调整大小时的视觉反馈
            this.panel.style.boxShadow = '0 8px 32px rgba(0,0,0,0.3)';

            e.preventDefault();
        });
    }

    const onResizeMove = (e) => {
        if (this.isResizing) {
            // 使用 requestAnimationFrame 优化 resize 过程,使动画更加流畅
            cancelAnimationFrame(rafId);
            rafId = requestAnimationFrame(() => {
                // 定义面板的最小和最大尺寸
                const MIN_WIDTH = 200;
                const MIN_HEIGHT = 200;
                const MAX_WIDTH = window.innerWidth * 0.8;
                const MAX_HEIGHT = window.innerHeight * 0.9;

                // 计算新的宽度和高度
                let newWidth = startWidth + (e.clientX - startX);
                let newHeight = startHeight + (e.clientY - startY);

                // 应用边界限制
                newWidth = Math.max(MIN_WIDTH, Math.min(newWidth, MAX_WIDTH));
                newHeight = Math.max(MIN_HEIGHT, Math.min(newHeight, MAX_HEIGHT));

                // 更新面板设置
                this.panelSettings.width = newWidth;
                this.panelSettings.height = newHeight;

                // 应用样式
                this.panel.style.width = `${newWidth}px`;
                this.panel.style.height = `${newHeight}px`;
            });
        }
    };

    const onResizeEnd = () => {
        if (this.isResizing) {
            this.isResizing = false;
            cancelAnimationFrame(rafId);

            // 恢复原始样式
            this.panel.style.boxShadow = '';

            ConfigManager.updatePanelSettings(this.panelSettings);
            this.adjustPanelHeight();
            customConsole.log('Resize ended, size:', { width: this.panelSettings.width, height: this.panelSettings.height });
        }
    };

    addSafeListener(document, 'mousemove', onResizeMove);
    addSafeListener(document, 'mouseup', onResizeEnd);
    addSafeListener(document, 'mouseleave', onResizeEnd); // 当鼠标离开窗口时结束 resize

    // 支持键盘调整窗口大小
    addSafeListener(document, 'keydown', (e) => {
        if (!this.panel || this.panelSettings.minimized) return;

        const STEP = 10;
        let changed = false;

        // 只有当面板获得焦点或用户正在与面板交互时才响应键盘事件
        if (e.target.closest('#enhanced-panel') || e.ctrlKey) {
            switch (e.key) {
                case 'ArrowUp':
                    e.preventDefault();
                    this.panelSettings.height = Math.max(200, this.panelSettings.height - STEP);
                    this.panel.style.height = `${this.panelSettings.height}px`;
                    changed = true;
                    break;
                case 'ArrowDown':
                    e.preventDefault();
                    this.panelSettings.height = Math.min(window.innerHeight * 0.9, this.panelSettings.height + STEP);
                    this.panel.style.height = `${this.panelSettings.height}px`;
                    changed = true;
                    break;
                case 'ArrowLeft':
                    e.preventDefault();
                    this.panelSettings.width = Math.max(200, this.panelSettings.width - STEP);
                    this.panel.style.width = `${this.panelSettings.width}px`;
                    changed = true;
                    break;
                case 'ArrowRight':
                    e.preventDefault();
                    this.panelSettings.width = Math.min(window.innerWidth * 0.8, this.panelSettings.width + STEP);
                    this.panel.style.width = `${this.panelSettings.width}px`;
                    changed = true;
                    break;
            }

            if (changed) {
                ConfigManager.updatePanelSettings(this.panelSettings);
                this.adjustPanelHeight();
            }
        }
    });

    DomUtils.safeQueryAll('.scale-btn', this.panel).forEach(btn => {
        addSafeListener(btn, 'click', e => {
            this.panelSettings.scale = parseFloat(btn.dataset.scale);
            this.panel.style.transform = `scale(${this.panelSettings.scale})`;
            ConfigManager.updatePanelSettings(this.panelSettings);
            this.ensureVisibility();
            this.adjustPanelHeight();
            e.stopPropagation();
        });
    });

    addSafeListener(window, 'resize', () => {
        const target = this.panelSettings.minimized ? this.minimizedIcon : this.panel;
        const panelWidth = target.offsetWidth * (this.panelSettings.scale || 1);
        const panelHeight = target.offsetHeight * (this.panelSettings.scale || 1);
        this.panelSettings.position.x = Math.min(this.panelSettings.position.x, window.innerWidth - panelWidth - 10);
        this.panelSettings.position.y = Math.min(this.panelSettings.position.y, window.innerHeight - panelHeight - 10);
        target.style.left = `${this.panelSettings.position.x}px`;
        target.style.top = `${this.panelSettings.position.y}px`;
        ConfigManager.updatePanelSettings(this.panelSettings);
        customConsole.log('Adjusted position on resize:', this.panelSettings.position);
    });
}
        setupUserActionListeners() {
            addSafeListener(document, '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 || ''}`);
                }
            });
            addSafeListener(document, 'scroll', _.debounce(() => {
                logWrapper('userActions', 'LOG', `Scrolled to (${window.scrollX}, ${window.scrollY})`);
            }, DEBOUNCE_LEVEL.QUICK));
        }

        startPerformanceMonitoring() {
            const perfMonitor = PerformanceMonitor.getInstance();
            const update = () => {
                perfMonitor.recordMemory();
                const memUsage = perfMonitor.metrics.memoryUsage.length ? Math.round(_.mean(perfMonitor.metrics.memoryUsage) / 1024 / 1024) : 0;
                const memElement = DomUtils.safeQuery('#mem-usage', this.panel);
                const progElement = DomUtils.safeQuery('#mem-progress', this.panel);
                if (memElement && progElement) {
                    memElement.textContent = `${memUsage} MB`;
                    progElement.style.width = `${Math.min(memUsage / 100 * 100, 100)}%`;
                }
                this.debugPanel.update({
                    posts: DomUtils.safeQueryAll('.l_post', this.postFilter.postContainer).length,
                    hidden: DomUtils.safeQueryAll('.spam-hidden', this.postFilter.postContainer).length,
                    memory: memUsage,
                    cacheSize: this.postFilter.postsCache.size
                });
                requestAnimationFrame(update);
            };
            requestAnimationFrame(update);
        }

        toggleMinimize() {
            if (this.panelSettings.minimized) this.restorePanel();
            else this.minimizePanel();
            ConfigManager.updatePanelSettings(this.panelSettings);
            this.ensureVisibility();
            logWrapper('userActions', 'LOG', `Panel minimized: ${this.panelSettings.minimized}`);
        }

        minimizePanel() {
            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;
            this.ensureVisibility();
            customConsole.log('Minimized panel, icon position:', this.panelSettings.position);
}

        restorePanel() {
            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.panelSettings.minimized = false;
            this.adjustPanelHeight();
            this.ensureVisibility();
            customConsole.log('Restored panel, position:', this.panelSettings.position);
}

        toggleBlockMode(event) {
            this.isBlockingMode = !this.isBlockingMode;
            const blockBtn = DomUtils.safeQuery('.btn-block', this.panel);
            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)
                };
                addSafeListener(document, 'mousemove', this.listeners.move);
                addSafeListener(document, 'click', this.listeners.click);
            } else {
                document.body.classList.remove('blocking-mode');
                this.removeCursorCircle();
                if (this.listeners) {
                    removeSafeListener(document, 'mousemove', this.listeners.move);
                    removeSafeListener(document, 'click', this.listeners.click);
                    this.listeners = null;
                }
                this.removeHighlight();
                this.selectedTarget = null;
            }
            if (event) event.stopPropagation();
            this.adjustPanelHeight();
            logWrapper('userActions', 'LOG', `Toggled block mode: ${this.isBlockingMode}`);
        }

        createCursorCircle() {
            this.cursorCircle = document.createElement('div');
            this.cursorCircle.className = 'cursor-circle';
            document.body.appendChild(this.cursorCircle);
        }

        moveCursorCircle(event) {
            if (!this.isBlockingMode || !this.cursorCircle) return;
            this.cursorCircle.style.left = `${event.clientX - 10}px`;
            this.cursorCircle.style.top = `${event.clientY - 10}px`;
            this.highlightElement(event);
        }

        removeCursorCircle() {
            if (this.cursorCircle && document.body.contains(this.cursorCircle)) document.body.removeChild(this.cursorCircle);
            this.cursorCircle = null;
        }

        highlightElement(event) {
            if (!this.isBlockingMode) return;
            this.removeHighlight();
            const target = event.target;
            if (!target || target.nodeType !== 1) return;
            if (target === this.panel || this.panel.contains(target) || target.classList.contains('block-modal') || target.closest('.block-modal')) return;
            target.classList.add('hover-highlight');
        }

        removeHighlight() {
            const highlighted = DomUtils.safeQuery('.hover-highlight');
            if (highlighted) highlighted.classList.remove('hover-highlight');
        }

        handleBlockClick(event) {
            if (!this.isBlockingMode) return;
            event.preventDefault();
            event.stopPropagation();
            const target = event.target;
            if (!target || target.nodeType !== 1) return;
            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);
        }

        showConfirmDialog(x, y) {
            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>
            `;
            document.body.appendChild(modal);
            const confirmBtn = DomUtils.safeQuery('.btn-confirm', modal);
            const cancelBtn = DomUtils.safeQuery('.btn-cancel', modal);
            addSafeListener(confirmBtn, 'click', e => {
                e.stopPropagation();
                if (this.selectedTarget) this.blockElement(this.selectedTarget, x, y);
                document.body.removeChild(modal);
            }, { once: true });
            addSafeListener(cancelBtn, 'click', e => {
                e.stopPropagation();
                document.body.removeChild(modal);
                this.toggleBlockMode();
            }, { once: true });
        }

        blockElement(target, x, y) {
            if (!target || !document.body.contains(target)) {
                this.showToast('无效的元素', 'error');
                return;
            }
            if (target.nodeType !== 1) {
                this.showToast('只能屏蔽元素节点', 'error');
                return;
            }
            const selector = this.getUniqueSelector(target);
            if (!selector) {
                this.toggleBlockMode();
                return;
            }
            const blockList = this.settings.blockType === 'temp' ? this.settings.tempBlockedElements : this.settings.blockedElements;
            if (!blockList.includes(selector)) {
                blockList.push(selector);
                ConfigManager.updateFilterSettings(this.settings);
                logWrapper('userActions', 'LOG', `Blocked element: ${selector}`, `Type: ${this.settings.blockType}`);
            }
            target.classList.add('spam-hidden');
            
            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();
                    this.showToast('已屏蔽该元素,页面将自动刷新', 'success');
                    setTimeout(() => {
                        location.reload();
                    }, 1000);
                }, 300);
            } else {
                this.toggleBlockMode();
                this.showToast('已屏蔽该元素,页面将自动刷新', 'success');
                setTimeout(() => {
                    location.reload();
                }, 1000);
            }
            this.adjustPanelHeight();
        }

        getUniqueSelector(element) {
            if (!element || element.nodeType !== 1) {
                return null;
            }

            if (element.id && element.id !== '') {
                return `#${element.id}`;
            }

            const corePostClasses = ['l_post', 'd_post_content', 'p_author', 'j_p_postlist', 'pb_content', 'l_post_list', 'pb_list', 'd_post_content_main', 'p_content', 'core_reply_tail', 'core_reply_content', 'j_lzl_container', 'lzl_content_main'];
            
            const floorInfo = element.dataset?.floor || element.getAttribute?.('data-floor');
            if (floorInfo === '1') {
                this.showToast('不能屏蔽一楼帖子', 'error');
                return null;
            }
            
            if (element.classList) {
                for (const cls of corePostClasses) {
                    if (element.classList.contains(cls)) {
                        this.showToast('不能屏蔽帖子核心内容区域', 'error');
                        return null;
                    }
                }
            }
            
            if (element.closest('.l_post')) {
                this.showToast('不能屏蔽帖子内部元素', 'error');
                return null;
            }
            
            let parent = element.parentNode;
            for (let i = 0; i < 5; i++) {
                if (parent && parent.nodeType === 1) {
                    if (parent.classList && parent.classList.contains('l_post')) {
                        this.showToast('不能屏蔽帖子内部元素', 'error');
                        return null;
                    }
                    parent = parent.parentNode;
                } else {
                    break;
                }
            }
            
            const criticalElements = ['aside', 'sidebar', '.aside', '.sidebar', '.tbui_aside_float_bar', 'ul.tbui_aside_float_bar'];
            for (const sel of criticalElements) {
                if (element.matches(sel)) {
                    this.showToast('不能屏蔽可能影响页面加载的元素', 'error');
                    return null;
                }
                let parent = element.parentNode;
                for (let i = 0; i < 2; i++) {
                    if (parent && parent.matches && parent.matches(sel)) {
                        this.showToast('不能屏蔽可能影响页面加载的元素', 'error');
                        return null;
                    }
                    parent = parent.parentNode;
                }
            }

            const path = [];
            let current = element;
            const MAX_PATH_LENGTH = 7;
            let pathLength = 0;

            while (current && current.nodeType === 1 && current !== document.body && pathLength < MAX_PATH_LENGTH) {
                let selector = current.tagName.toLowerCase();

                if (current.className) {
                    const classes = current.className.trim().split(/\s+/);
                    const meaningfulClasses = classes.filter(cls => {
                        return !corePostClasses.includes(cls) &&
                               !cls.match(/^(spam-hidden|filtered|expanded|hover-highlight)$/) &&
                               cls.length > 4;
                    });

                    if (meaningfulClasses.length > 0) {
                        selector += meaningfulClasses.map(cls => `.${cls}`).join('');
                    }
                }

                const siblings = Array.from(current.parentNode?.children || []).filter(child => child.tagName === current.tagName);
                if (siblings.length > 1) {
                    const index = siblings.indexOf(current) + 1;
                    selector += `:nth-child(${index})`;
                }

                path.unshift(selector);
                current = current.parentNode;
                pathLength++;
            }

            let finalSelector = path.join(' > ');

            if (pathLength < 2) {
                const parent = element.parentNode;
                if (parent && parent.nodeType === 1 && parent !== document.body) {
                    let parentSelector = parent.tagName.toLowerCase();
                    if (parent.className) {
                        const parentClasses = parent.className.trim().split(/\s+/);
                        if (parentClasses.length > 0) {
                            parentSelector += `.${parentClasses[0]}`;
                        }
                    }
                    finalSelector = `${parentSelector} > ${finalSelector}`;
                }
            }
            
            finalSelector = `.pb_content ${finalSelector}`;

            try {
                const matches = document.querySelectorAll(finalSelector);
                if (matches.length !== 1) {
                    if (element.dataset && Object.keys(element.dataset).length > 0) {
                        const dataAttr = Object.keys(element.dataset)[0];
                        finalSelector += `[data-${dataAttr}="${element.dataset[dataAttr]}"]`;
                    } else {
                        this.showToast('无法生成唯一的选择器,请尝试选择其他元素', 'error');
                        return null;
                    }
                }
            } catch (error) {
                customConsole.error(`选择器测试失败: ${finalSelector}`, error);
                this.showToast('无法生成有效的选择器,请尝试选择其他元素', 'error');
                return null;
            }

            return finalSelector;
        }

        showKeywordEditor() {
            const modal = document.createElement('div');
            modal.className = 'keyword-modal';
            modal.innerHTML = `
                <div class="modal-content">
                    <h3>关键词管理</h3>
                    <textarea id="keywordInput">${this.settings.spamKeywords.join('\n')}</textarea>
                    <div class="modal-actions">
                        <button class="btn-cancel">取消</button>
                        <button class="btn-save">保存</button>
                    </div>
                </div>
            `;
            document.body.appendChild(modal);
            const textarea = DomUtils.safeQuery('#keywordInput', modal);
            const saveBtn = DomUtils.safeQuery('.btn-save', modal);
            const cancelBtn = DomUtils.safeQuery('.btn-cancel', modal);

            addSafeListener(saveBtn, 'click', () => {
                const keywords = textarea.value.split('\n').map(k => k.trim()).filter(k => k.length > 0);
                if (keywords.length > 0) {
                    this.settings.spamKeywords = keywords;
                    ConfigManager.updateFilterSettings(this.settings);
                    customConsole.log('保存自定义屏蔽词:', keywords);
                    logWrapper('userActions', 'LOG', `Updated spam keywords: ${keywords.join(', ')}`);
                    this.postFilter.updateFilters();
                    document.body.removeChild(modal);
                    this.showToast('关键词已更新', 'success');
                } else {
                    this.showToast('请至少输入一个关键词', 'error');
                }
            });
            addSafeListener(cancelBtn, 'click', () => document.body.removeChild(modal));
        }

        showUndoList() {
            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>
            `;
            document.body.appendChild(modal);
            DomUtils.safeQueryAll('.btn-undo', modal).forEach(btn => {
                addSafeListener(btn, 'click', () => {
                    const index = parseInt(btn.dataset.index);
                    const type = btn.dataset.type;
                    this.undoBlockElement(index, type);
                    document.body.removeChild(modal);
                    this.showUndoList();
                });
            });
            addSafeListener(DomUtils.safeQuery('.btn-cancel', modal), 'click', () => document.body.removeChild(modal));
        }

        undoBlockElement(index, type) {
            const blockList = type === 'temp' ? this.settings.tempBlockedElements : this.settings.blockedElements;
            if (index >= 0 && index < blockList.length) {
                const selector = blockList[index];
                blockList.splice(index, 1);
                ConfigManager.updateFilterSettings(this.settings);
                this.postFilter.updateFilters();
                DomUtils.safeQueryAll(selector).forEach(el => el.classList.remove('spam-hidden'));
                this.showToast(`已撤销屏蔽: ${selector}`, 'success');
                logWrapper('userActions', 'LOG', `Undid block (${type}): ${selector}`);
            }
            this.adjustPanelHeight();
        }

        exportConfig() {
            const config = { filter: this.settings, panel: this.panelSettings };
            const blob = new Blob([JSON.stringify(config, null, 2)], { 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);
            this.showToast('配置已导出', 'success');
            logWrapper('userActions', 'LOG', 'Exported configuration');
        }

        importConfig() {
            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>
            `;
            document.body.appendChild(modal);
            const fileInput = DomUtils.safeQuery('#configFileInput', modal);
            addSafeListener(DomUtils.safeQuery('.btn-save', modal), '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 = { ...ConfigManager.defaultFilterSettings, ...importedConfig.filter };
                            this.panelSettings = { ...ConfigManager.defaultPanelSettings, ...importedConfig.panel };
                            ConfigManager.updateFilterSettings(this.settings);
                            ConfigManager.updatePanelSettings(this.panelSettings);
                            this.postFilter.updateFilters();
                            this.loadContent();
                            if (this.panelSettings.minimized) this.minimizePanel(); else this.restorePanel();
                            this.applyDarkMode(this.settings.darkMode);
                            this.showToast('配置已导入', 'success');
                            logWrapper('userActions', 'LOG', 'Imported configuration');
                        } catch (err) {
                            this.showToast('配置文件无效', 'error');
                        }
                        document.body.removeChild(modal);
                    };
                    reader.readAsText(file);
                } else {
                    this.showToast('请选择一个配置文件', 'error');
                }
            });
            addSafeListener(DomUtils.safeQuery('.btn-cancel', modal), 'click', () => document.body.removeChild(modal));
        }

        toggleSearch() {
            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>
            `;
            document.body.appendChild(modal);
            const searchInput = DomUtils.safeQuery('#searchInput', modal);
            addSafeListener(DomUtils.safeQuery('.btn-search', modal), 'click', () => {
                const keyword = searchInput.value.trim();
                if (keyword) this.performSearch(keyword);
            });
            addSafeListener(DomUtils.safeQuery('.btn-cancel', modal), 'click', () => document.body.removeChild(modal));
            searchInput.focus();
        }

        performSearch(keyword) {
            DomUtils.safeQueryAll('.highlight-match', this.postFilter.postContainer).forEach(el => el.replaceWith(el.textContent));
            const posts = DomUtils.safeQueryAll('.d_post_content', this.postFilter.postContainer);
            let regex;
            try {
                regex = new RegExp(keyword, 'gi');
            } catch {
                regex = new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
            }
            let found = false;
            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', inline: 'nearest' });
                    found = true;
                }
            });
            this.showToast(found ? '搜索完成' : '未找到匹配内容', found ? 'success' : 'error');
            logWrapper('userActions', 'LOG', `Searched for keyword: ${keyword}, found: ${found}`);
        }

        showLogSaveDialog() {
            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>
            `;
            document.body.appendChild(modal);
            addSafeListener(DomUtils.safeQuery('.btn-save', modal), 'click', () => {
                const fullLog = [
                    '=== 脚本运行日志 ===', ...logBuffer.script,
                    '\n=== 网页运行状态 ===', ...logBuffer.pageState,
                    '\n=== 网页行为 ===', ...logBuffer.pageBehavior,
                    '\n=== 用户操作 ===', ...logBuffer.userActions
                ].join('\n');
                const blob = new Blob([fullLog], { type: 'text/plain;charset=utf-8' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `tieba_enhance_log_${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
                document.body.removeChild(modal);
                this.showToast('日志已保存', 'success');
                logWrapper('userActions', 'LOG', 'Saved logs to file');
            });
            addSafeListener(DomUtils.safeQuery('.btn-cancel', modal), 'click', () => document.body.removeChild(modal));
        }

        setupCleanup() {
            addSafeListener(window, 'beforeunload', () => {
                this.panel.remove();
                this.minimizedIcon.remove();
                this.debugPanel.remove();
                this.observer?.disconnect();
                if (this.listeners) {
                    removeSafeListener(document, 'mousemove', this.listeners.move);
                    removeSafeListener(document, 'click', this.listeners.click);
                }
            });
        }

        applyDarkMode(enable) {
            if (enable) document.body.classList.add('dark-mode');
            else document.body.classList.remove('dark-mode');
            logWrapper('pageBehavior', 'LOG', `Applied dark mode: ${enable}`);
        }

        showToast(message, type = 'info') {
            const toast = document.createElement('div');
            toast.textContent = message;
            toast.style.cssText = `
                position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
                background: ${type === 'success' ? '#34c759' : '#ff4444'};
                color: white;
                padding: 10px 20px;
                border-radius: 5px;
                z-index: 10001;
                transition: opacity 0.3s;
            `;
            document.body.appendChild(toast);
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => toast.remove(), 300);
            }, 2000);
            logWrapper('script', 'LOG', `Showed toast: ${message}`);
        }

        showErrorToast(methodName, error) {
            this.showToast(`${methodName}出错: ${error.message}`, 'error');
        }
    }

    // 十五、初始化
    const REFRESH_PREFIX = 'tieba_refresh_';
    let isRefreshScheduled = false;
    let hasRefreshedThisSession = false;

    function getPageRefreshKey() {
        return REFRESH_PREFIX + window.location.pathname;
    }

    function checkAndAutoRefresh() {
        if (isRefreshScheduled) {
            customConsole.log('当前页面已调度刷新,跳过重复调度');
            return;
        }
        
        const pageKey = getPageRefreshKey();
        const hasRefreshed = sessionStorage.getItem(pageKey);
        
        if (hasRefreshed) {
            customConsole.log('当前页面本次会话已刷新过,跳过自动刷新');
            return;
        }
        
        isRefreshScheduled = true;
        customConsole.log('首次打开此帖子页面,将执行强刷新');
    }

    function executeForceRefresh() {
        if (!isRefreshScheduled || hasRefreshedThisSession) return;
        
        hasRefreshedThisSession = true;
        const pageKey = getPageRefreshKey();
        sessionStorage.setItem(pageKey, 'true');
        
        customConsole.log('执行强刷新,绕过浏览器缓存');
        try {
            location.reload(true);
        } catch (error) {
            customConsole.error('强刷新失败:', error);
        }
    }

    addSafeListener(document, 'DOMContentLoaded', () => {
        checkAndAutoRefresh();
        new DynamicPanel();
        customConsole.log('DOM content loaded, script initialized');
        
        if (document.readyState === 'complete') {
            setTimeout(() => {
                executeForceRefresh();
            }, 500);
        } else {
            addSafeListener(window, 'load', () => {
                setTimeout(() => {
                    executeForceRefresh();
                }, 500);
            });
        }
    });

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        setTimeout(() => {
            if (!DomUtils.safeQuery('#enhanced-panel') && !DomUtils.safeQuery('#minimized-icon')) {
                checkAndAutoRefresh();
                new DynamicPanel();
                customConsole.log('Fallback initialization triggered');
                
                if (document.readyState === 'complete') {
                    setTimeout(() => {
                        executeForceRefresh();
                    }, 500);
                } else {
                    addSafeListener(window, 'load', () => {
                        setTimeout(() => {
                            executeForceRefresh();
                        }, 500);
                    });
                }
            }
        }, 50);
    }
})();