Greasy Fork

Greasy Fork is available in English.

贴吧广告过滤登录去除

贴吧广告过滤,自动关闭登录弹窗,控制面板可拖动(支持折叠/展开+位置同步)

当前为 2025-09-04 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         贴吧广告过滤登录去除
// @namespace    noting
// @version      0.8.5
// @description  贴吧广告过滤,自动关闭登录弹窗,控制面板可拖动(支持折叠/展开+位置同步)
// @author       Time (优化: 面板增强+分页功能+反馈功能+位置同步+折叠功能)
// @match        https://tieba.baidu.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const options = {
        expensionName: "贴吧广告过滤登录去除",
        interval: 500,
        development: false,
        isDomRemove: false,
        showAds: false,
        isCollapsed: false, // 默认是否折叠
        matchingHost: "tieba.baidu.com",
        loginDomId: "tiebaCustomPassLogin",
        adTextClass: "label_text",
        adText: "广告",
        feedbackEmail: "[email protected]",
        storageKey: "tiebaAdFilterPanelPosition",
        collapseStorageKey: "tiebaAdFilterPanelCollapsed", // 用于存储折叠状态
        adIds: [
            "pagelet_frs-aside/pagelet/fengchao_ad",
            "banner_pb_customize",
            "plat_recom_carousel"
        ],
        adClasses: [
            "fengchao-wrap-feed",
            "head_banner",
            "head_ad_pop",
            "l_banner",
            "j_couplet",
            "card_banner",
            "bus-top-activity-wrap"
        ],
        page:{
            container:"pb_list_pager",
            down:"下一页",
            up:"上一页"
        },
        panelStyle: {
            width: "240px",
            minHeight: "320px",
            collapsedHeight: "45px", // 折叠状态高度
            top: "120px",
            left: "15px",
            zIndex: 9999,
            border: "1px solid #e5e7eb",
            borderColor: "#e5e7eb",
            bgColor: "#ffffff",
            shadow: "0 4px 12px rgba(0,0,0,0.08)",
            headerBg: "#2563eb",
            headerColor: "#ffffff",
            textColor: "#374151",
            subTextColor: "#6b7280",
            checkboxSize: "16px",
            btnBg: "#f3f4f6",
            btnHoverBg: "#e5e7eb",
            btnRadius: "4px",
            marginBottom: "12px",
            padding: "12px"
        }
    };

    // 工具函数 - 日志输出
    const logger = {
        log: (...args) => {
            if (options.development) {
                console.log(`[${options.expensionName}]`, ...args);
            }
        },
        error: (...args) => {
            console.error(`[${options.expensionName}]`, ...args);
        }
    };

    // 存储工具 - 处理面板位置和状态的本地存储
    const StorageUtil = {
        // 保存面板位置到localStorage
        savePosition: (position) => {
            try {
                const data = {
                    top: position.top,
                    left: position.left,
                    timestamp: Date.now()
                };
                localStorage.setItem(options.storageKey, JSON.stringify(data));
            } catch (ex) {
                logger.error("保存面板位置失败:", ex);
            }
        },

        // 从localStorage获取面板位置
        getPosition: () => {
            try {
                const data = localStorage.getItem(options.storageKey);
                if (data) {
                    return JSON.parse(data);
                }
                return null;
            } catch (ex) {
                logger.error("获取面板位置失败:", ex);
                return null;
            }
        },

        // 保存折叠状态
        saveCollapsedState: (isCollapsed) => {
            try {
                localStorage.setItem(options.collapseStorageKey, JSON.stringify({
                    isCollapsed,
                    timestamp: Date.now()
                }));
            } catch (ex) {
                logger.error("保存折叠状态失败:", ex);
            }
        },

        // 获取保存的折叠状态
        getCollapsedState: () => {
            try {
                const data = localStorage.getItem(options.collapseStorageKey);
                if (data) {
                    return JSON.parse(data);
                }
                return { isCollapsed: options.isCollapsed };
            } catch (ex) {
                logger.error("获取折叠状态失败:", ex);
                return { isCollapsed: options.isCollapsed };
            }
        }
    };

    // 广告检测与处理模块
    const AdManager = {
        detectedAds: new Set(),

        isAdDetected: (element) => {
            return AdManager.detectedAds.has(element);
        },

        addAdElement: (element) => {
            if (!element || AdManager.isAdDetected(element)) return;

            AdManager.detectedAds.add(element);
            AdManager.handleAdElement(element);

            const observer = new MutationObserver((mutations) => {
                if (!document.body.contains(element)) {
                    AdManager.detectedAds.delete(element);
                    observer.disconnect();
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        },

        handleAdElement: (element) => {
            if (!element || !element.style) return;

            try {
                if (options.showAds) {
                    element.style.display = "";
                } else {
                    if (options.isDomRemove) {
                        element.remove();
                        AdManager.detectedAds.delete(element);
                    } else if (element.style.display !== "none") {
                        element.style.display = "none";
                    }
                }
            } catch (ex) {
                logger.error("处理广告元素出错:", ex);
            }
        },

        handleAllAds: () => {
            AdManager.detectedAds.forEach(element => {
                AdManager.handleAdElement(element);
            });
        }
    };

    // 检测器模块
    const Detector = {
        detectLogin: () => {
            try {
                const loginElement = document.getElementById(options.loginDomId);
                if (loginElement) {
                    AdManager.addAdElement(loginElement);
                }
            } catch (ex) {
                logger.error("检测登录弹窗出错:", ex);
            }
        },

        detectBySelector: (type, selectors) => {
            if (!selectors || !Array.isArray(selectors) || selectors.length === 0) return;

            selectors.forEach(selector => {
                try {
                    let elements;
                    if (type === 'id') {
                        const el = document.getElementById(selector);
                        if (el) elements = [el];
                    } else if (type === 'class') {
                        elements = document.getElementsByClassName(selector);
                    }

                    if (elements && elements.length > 0) {
                        Array.from(elements).forEach(el => AdManager.addAdElement(el));
                    }
                } catch (ex) {
                    logger.error(`检测广告元素 ${selector} 出错:`, ex);
                }
            });
        },

        detectByAdText: () => {
            try {
                const adTextElements = document.getElementsByClassName(options.adTextClass);
                Array.from(adTextElements).forEach(element => {
                    if (element.innerText.trim() === options.adText) {
                        let parent = element.closest('div, section, article');
                        if (parent) {
                            AdManager.addAdElement(parent);
                        }
                    }
                });
            } catch (ex) {
                logger.error("检测文字标记广告出错:", ex);
            }
        },

        detectAll: () => {
            Detector.detectLogin();
            Detector.detectBySelector('class', options.adClasses);
            Detector.detectBySelector('id', options.adIds);
            Detector.detectByAdText();
        }
    };

    // 分页工具模块
    const PaginationTool = {
        findPageLink: (targetText) => {
            try {
                const pagerContainer = document.getElementsByClassName(options.page.container)[0];
                if (!pagerContainer) {
                    logger.log("未找到分页容器(class: pb_list_pager)");
                    return null;
                }

                const linkElements = pagerContainer.getElementsByTagName('a');
                for (let link of linkElements) {
                    if (link.textContent.trim() === targetText) {
                        return link;
                    }
                }

                logger.log(`未找到文本为"${targetText}"的链接`);
                return null;
            } catch (ex) {
                logger.error("查找分页链接出错:", ex);
                return null;
            }
        },

        triggerPageClick: (targetText) => {
            const targetLink = PaginationTool.findPageLink(targetText);
            if (targetLink) {
                targetLink.click();
                logger.log(`已触发"${targetText}"点击`);
                return true;
            }
            return false;
        },

        updateButtonStates: (prevBtn, nextBtn) => {
            const hasPrev = PaginationTool.findPageLink(options.page.up) !== null;
            prevBtn.disabled = !hasPrev;
            prevBtn.style.opacity = hasPrev ? "1" : "0.6";
            prevBtn.style.cursor = hasPrev ? "pointer" : "not-allowed";

            const hasNext = PaginationTool.findPageLink(options.page.down) !== null;
            nextBtn.disabled = !hasNext;
            nextBtn.style.opacity = hasNext ? "1" : "0.6";
            nextBtn.style.cursor = hasNext ? "pointer" : "not-allowed";
        }
    };

    // 控制面板模块(新增折叠/展开功能)
    const ControlPanel = {
        panelElement: null,
        contentElement: null,
        collapseButton: null,
        isProgrammaticMove: false,

        createPanel: () => {
            // 获取保存的折叠状态
            const savedCollapseState = StorageUtil.getCollapsedState();
            options.isCollapsed = savedCollapseState.isCollapsed;

            // 1. 创建面板容器
            const panel = document.createElement("div");
            panel.id = "ad-filter-panel";

            // 检查是否有保存的位置
            const savedPos = StorageUtil.getPosition();
            const topPos = savedPos ? `${savedPos.top}px` : options.panelStyle.top;
            const leftPos = savedPos ? `${savedPos.left}px` : options.panelStyle.left;

            panel.style.cssText = `
                position: fixed;
                width: ${options.panelStyle.width};
                min-height: ${options.isCollapsed ? options.panelStyle.collapsedHeight : options.panelStyle.minHeight};
                height: auto;
                top: ${topPos};
                left: ${leftPos};
                z-index: ${options.panelStyle.zIndex};
                border: ${options.panelStyle.border};
                border-radius: 6px;
                background-color: ${options.panelStyle.bgColor};
                box-shadow: ${options.panelStyle.shadow};
                overflow: hidden;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                transition: all 0.2s ease;
            `;

            // 2. 面板头部(拖拽区+折叠按钮)
            const header = document.createElement("div");
            header.className = "panel-header";
            header.style.cssText = `
                padding: ${options.panelStyle.padding};
                background-color: ${options.panelStyle.headerBg};
                color: ${options.panelStyle.headerColor};
                font-size: 15px;
                font-weight: 500;
                cursor: move;
                display: flex;
                justify-content: space-between;
                align-items: center;
            `;

            // 2.1 折叠/展开按钮
            const collapseBtn = document.createElement("button");
            collapseBtn.id = "collapse-btn";
            collapseBtn.innerHTML = options.isCollapsed ? "▷" : "▽"; // 使用三角形符号表示状态
            collapseBtn.style.cssText = `
                background: none;
                border: none;
                color: ${options.panelStyle.headerColor};
                font-size: 16px;
                cursor: pointer;
                width: 24px;
                height: 24px;
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 0;
                opacity: 0.9;
                transition: transform 0.2s ease;
            `;
            collapseBtn.title = options.isCollapsed ? "展开面板" : "折叠面板";
            ControlPanel.collapseButton = collapseBtn;

            header.innerHTML = `
                <span>${options.expensionName}</span>
                <span class="panel-version" style="font-size: 12px; opacity: 0.9;">v${GM_info?.script?.version || '0.8.5'}</span>
            `;
            header.appendChild(collapseBtn);

            // 3. 面板内容区
            const content = document.createElement("div");
            content.className = "panel-content";
            content.style.cssText = `
                padding: ${options.panelStyle.padding};
                color: ${options.panelStyle.textColor};
                font-size: 14px;
                display: ${options.isCollapsed ? "none" : "block"};
                transition: all 0.2s ease;
            `;
            ControlPanel.contentElement = content;

            // 3.1 广告显示/隐藏开关
            const adToggleGroup = document.createElement("div");
            adToggleGroup.style.cssText = `
                display: flex;
                align-items: center;
                margin-bottom: ${options.panelStyle.marginBottom};
                padding-bottom: ${options.panelStyle.marginBottom};
                border-bottom: 1px solid #f3f4f6;
            `;
            const adToggle = document.createElement("input");
            adToggle.type = "checkbox";
            adToggle.id = "ad-toggle";
            adToggle.checked = options.showAds;
            adToggle.style.cssText = `
                width: ${options.panelStyle.checkboxSize};
                height: ${options.panelStyle.checkboxSize};
                margin-right: 8px;
                cursor: pointer;
            `;
            const adToggleLabel = document.createElement("label");
            adToggleLabel.htmlFor = "ad-toggle";
            adToggleLabel.textContent = "显示广告(默认隐藏)";
            adToggleLabel.style.cursor = "pointer";
            adToggleGroup.append(adToggle, adToggleLabel);

            // 3.2 广告删除方式
            const deleteModeGroup = document.createElement("div");
            deleteModeGroup.style.cssText = `
                display: flex;
                align-items: center;
                margin-bottom: ${options.panelStyle.marginBottom};
                padding-bottom: ${options.panelStyle.marginBottom};
                border-bottom: 1px solid #f3f4f6;
            `;
            const deleteModeToggle = document.createElement("input");
            deleteModeToggle.type = "checkbox";
            deleteModeToggle.id = "delete-mode-toggle";
            deleteModeToggle.checked = options.isDomRemove;
            deleteModeToggle.style.cssText = `
                width: ${options.panelStyle.checkboxSize};
                height: ${options.panelStyle.checkboxSize};
                margin-right: 8px;
                cursor: pointer;
            `;
            const deleteModeLabel = document.createElement("label");
            deleteModeLabel.htmlFor = "delete-mode-toggle";
            deleteModeLabel.innerHTML = `
                彻底删除广告(默认隐藏)<br>
                <span style="font-size: 12px; color: ${options.panelStyle.subTextColor}; margin-top: 2px; display: block;">
                注:彻底删除可减少页面占用,但可能影响部分页面布局
                </span>
            `;
            deleteModeLabel.style.cursor = "pointer";
            deleteModeGroup.append(deleteModeToggle, deleteModeLabel);

            // 3.3 分页快捷操作区
            const paginationGroup = document.createElement("div");
            paginationGroup.style.cssText = `
                margin-bottom: ${options.panelStyle.marginBottom};
                padding-bottom: ${options.panelStyle.marginBottom};
                border-bottom: 1px solid #f3f4f6;
            `;
            const paginationTitle = document.createElement("div");
            paginationTitle.style.cssText = `
                font-size: 13px;
                margin-bottom: 8px;
                color: ${options.panelStyle.subTextColor};
            `;
            paginationTitle.textContent = "分页快捷操作";
            const pageBtnContainer = document.createElement("div");
            pageBtnContainer.style.cssText = "display: flex; gap: 8px;";
            const prevPageBtn = document.createElement("button");
            prevPageBtn.id = "prev-page-btn";
            prevPageBtn.textContent = options.page.up;
            prevPageBtn.style.cssText = `
                flex: 1;
                padding: 8px 0;
                background-color: ${options.panelStyle.btnBg};
                border: none;
                border-radius: ${options.panelStyle.btnRadius};
                color: ${options.panelStyle.textColor};
                font-size: 13px;
                cursor: pointer;
                transition: background-color 0.2s ease;
            `;
            const nextPageBtn = document.createElement("button");
            nextPageBtn.id = "next-page-btn";
            nextPageBtn.textContent = options.page.down;
            nextPageBtn.style.cssText = `
                flex: 1;
                padding: 8px 0;
                background-color: ${options.panelStyle.btnBg};
                border: none;
                border-radius: ${options.panelStyle.btnRadius};
                color: ${options.panelStyle.textColor};
                font-size: 13px;
                cursor: pointer;
                transition: background-color 0.2s ease;
            `;
            prevPageBtn.onmouseover = () => {
                if (!prevPageBtn.disabled) {
                    prevPageBtn.style.backgroundColor = options.panelStyle.btnHoverBg;
                }
            };
            prevPageBtn.onmouseout = () => {
                if (!prevPageBtn.disabled) {
                    prevPageBtn.style.backgroundColor = options.panelStyle.btnBg;
                }
            };
            nextPageBtn.onmouseover = () => {
                if (!nextPageBtn.disabled) {
                    nextPageBtn.style.backgroundColor = options.panelStyle.btnHoverBg;
                }
            };
            nextPageBtn.onmouseout = () => {
                if (!nextPageBtn.disabled) {
                    nextPageBtn.style.backgroundColor = options.panelStyle.btnBg;
                }
            };
            pageBtnContainer.append(prevPageBtn, nextPageBtn);
            paginationGroup.append(paginationTitle, pageBtnContainer);

            // 3.4 反馈邮件区域
            const feedbackGroup = document.createElement("div");
            feedbackGroup.style.cssText = `
                margin-bottom: ${options.panelStyle.marginBottom};
                padding-bottom: ${options.panelStyle.marginBottom};
                border-bottom: 1px solid #f3f4f6;
            `;
            const feedbackTitle = document.createElement("div");
            feedbackTitle.style.cssText = `
                font-size: 13px;
                margin-bottom: 8px;
                color: ${options.panelStyle.subTextColor};
            `;
            feedbackTitle.textContent = "反馈与建议";
            const feedbackBtn = document.createElement("a");
            feedbackBtn.href = `mailto:${options.feedbackEmail}?subject=贴吧广告过滤插件反馈&body=请描述您遇到的问题或建议...`;
            feedbackBtn.textContent = "发送邮件反馈";
            feedbackBtn.style.cssText = `
                display: block;
                width: 100%;
                padding: 8px 0;
                background-color: ${options.panelStyle.btnBg};
                border: none;
                border-radius: ${options.panelStyle.btnRadius};
                color: #2563eb;
                font-size: 13px;
                cursor: pointer;
                text-align: center;
                text-decoration: none;
                transition: background-color 0.2s ease;
            `;
            feedbackBtn.onmouseover = () => {
                feedbackBtn.style.backgroundColor = options.panelStyle.btnHoverBg;
            };
            feedbackBtn.onmouseout = () => {
                feedbackBtn.style.backgroundColor = options.panelStyle.btnBg;
            };
            feedbackGroup.append(feedbackTitle, feedbackBtn);

            // 3.5 手动刷新广告检测
            const refreshBtn = document.createElement("button");
            refreshBtn.id = "refresh-ad-detect";
            refreshBtn.textContent = "手动刷新广告检测";
            refreshBtn.style.cssText = `
                width: 100%;
                padding: 8px 0;
                background-color: ${options.panelStyle.btnBg};
                border: none;
                border-radius: ${options.panelStyle.btnRadius};
                color: ${options.panelStyle.textColor};
                font-size: 13px;
                cursor: pointer;
                transition: background-color 0.2s ease;
                margin-bottom: ${options.panelStyle.marginBottom};
            `;
            refreshBtn.onmouseover = () => {
                refreshBtn.style.backgroundColor = options.panelStyle.btnHoverBg;
            };
            refreshBtn.onmouseout = () => {
                refreshBtn.style.backgroundColor = options.panelStyle.btnBg;
            };

            // 3.6 广告统计显示
            const adCount = document.createElement("div");
            adCount.id = "ad-count";
            adCount.style.cssText = `
                font-size: 12px;
                color: ${options.panelStyle.subTextColor};
                text-align: right;
                padding-top: 8px;
                border-top: 1px dashed #f3f4f6;
            `;
            adCount.textContent = `已过滤广告:${AdManager.detectedAds.size} 个`;

            // 4. 组装内容区
            content.append(adToggleGroup, deleteModeGroup, paginationGroup, feedbackGroup, refreshBtn, adCount);
            panel.append(header, content);
            document.body.appendChild(panel);
            ControlPanel.panelElement = panel;

            // 5. 绑定事件(包括折叠按钮事件)
            ControlPanel.bindEvents(adToggle, deleteModeToggle, refreshBtn, adCount, prevPageBtn, nextPageBtn);

            // 6. 初始化拖拽
            ControlPanel.initDrag();

            // 7. 初始化存储同步
            ControlPanel.initStorageSync();

            // 8. 面板hover效果
            panel.onmouseover = () => {
                panel.style.boxShadow = "0 6px 16px rgba(0,0,0,0.12)";
            };
            panel.onmouseout = () => {
                panel.style.boxShadow = options.panelStyle.shadow;
            };

            return panel;
        },

        /**
         * 切换面板折叠/展开状态
         */
        toggleCollapse: () => {
            const panel = ControlPanel.panelElement;
            const content = ControlPanel.contentElement;
            const collapseBtn = ControlPanel.collapseButton;

            // 切换状态
            options.isCollapsed = !options.isCollapsed;

            // 更新UI
            if (options.isCollapsed) {
                // 折叠状态
                panel.style.minHeight = `${options.panelStyle.collapsedHeight}`;
                content.style.display = "none";
                collapseBtn.innerHTML = "▷";
                collapseBtn.title = "展开面板";
            } else {
                // 展开状态
                panel.style.minHeight = `${options.panelStyle.minHeight}`;
                content.style.display = "block";
                collapseBtn.innerHTML = "▽";
                collapseBtn.title = "折叠面板";
            }

            // 保存状态到本地存储
            StorageUtil.saveCollapsedState(options.isCollapsed);
        },

        /**
         * 从存储同步折叠状态
         */
        syncCollapseState: (newState) => {
            if (options.isCollapsed === newState.isCollapsed) return;

            options.isCollapsed = newState.isCollapsed;
            const panel = ControlPanel.panelElement;
            const content = ControlPanel.contentElement;
            const collapseBtn = ControlPanel.collapseButton;

            // 更新UI
            if (options.isCollapsed) {
                panel.style.minHeight = `${options.panelStyle.collapsedHeight}`;
                content.style.display = "none";
                collapseBtn.innerHTML = "▷";
                collapseBtn.title = "展开面板";
            } else {
                panel.style.minHeight = `${options.panelStyle.minHeight}`;
                content.style.display = "block";
                collapseBtn.innerHTML = "▽";
                collapseBtn.title = "折叠面板";
            }
        },

        /**
         * 绑定面板事件
         */
        bindEvents: (adToggle, deleteModeToggle, refreshBtn, adCount, prevPageBtn, nextPageBtn) => {
            // 绑定折叠按钮事件
            ControlPanel.collapseButton.addEventListener('click', (e) => {
                e.stopPropagation(); // 防止触发拖拽
                ControlPanel.toggleCollapse();
            });

            // 广告显示/隐藏切换
            adToggle.addEventListener('change', (e) => {
                options.showAds = e.target.checked;
                AdManager.handleAllAds();
            });

            // 广告删除模式切换
            deleteModeToggle.addEventListener('change', (e) => {
                options.isDomRemove = e.target.checked;
                AdManager.handleAllAds();
                if (options.isDomRemove) {
                    AdManager.detectedAds.forEach(el => {
                        if (document.body.contains(el)) el.remove();
                    });
                    AdManager.detectedAds.clear();
                }
            });

            // 手动刷新广告检测
            refreshBtn.addEventListener('click', () => {
                refreshBtn.textContent = "检测中...";
                refreshBtn.disabled = true;
                Detector.detectAll();
                AdManager.handleAllAds();
                setTimeout(() => {
                    refreshBtn.textContent = "手动刷新广告检测";
                    refreshBtn.disabled = false;
                    adCount.textContent = `已过滤广告:${AdManager.detectedAds.size} 个`;
                    PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn);
                }, 1000);
            });

            // 定时更新广告统计和分页按钮状态
            setInterval(() => {
                adCount.textContent = `已过滤广告:${AdManager.detectedAds.size} 个`;
                PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn);
            }, 3000);

            // 上一页按钮点击事件
            prevPageBtn.addEventListener('click', () => {
                if (!prevPageBtn.disabled) {
                    PaginationTool.triggerPageClick(options.page.up);
                    prevPageBtn.disabled = true;
                    setTimeout(() => {
                        PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn);
                    }, 1000);
                }
            });

            // 下一页按钮点击事件
            nextPageBtn.addEventListener('click', () => {
                if (!nextPageBtn.disabled) {
                    PaginationTool.triggerPageClick(options.page.down);
                    nextPageBtn.disabled = true;
                    setTimeout(() => {
                        PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn);
                    }, 1000);
                }
            });

            // 初始更新分页按钮状态
            PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn);
        },

        /**
         * 初始化拖拽功能
         */
        initDrag: () => {
            const panel = ControlPanel.panelElement;
            const dragHandle = panel.querySelector(".panel-header");
            let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
            let isDragging = false;

            dragHandle.addEventListener('mousedown', (e) => {
                e.preventDefault();
                isDragging = true;

                panel.style.boxShadow = "0 8px 24px rgba(0,0,0,0.15)";
                panel.style.transition = "box-shadow 0.1s ease";

                pos3 = e.clientX;
                pos4 = e.clientY;

                document.addEventListener('mousemove', dragMove);
                document.addEventListener('mouseup', dragEnd);
            });

            const dragMove = (e) => {
                if (!isDragging) return;
                e.preventDefault();

                pos1 = pos3 - e.clientX;
                pos2 = pos4 - e.clientY;
                pos3 = e.clientX;
                pos4 = e.clientY;

                const newTop = panel.offsetTop - pos2;
                const newLeft = panel.offsetLeft - pos1;
                const viewportWidth = window.innerWidth;
                const viewportHeight = window.innerHeight;

                // 限制在视口内
                const constrainedTop = Math.max(0, Math.min(newTop, viewportHeight - panel.offsetHeight));
                const constrainedLeft = Math.max(0, Math.min(newLeft, viewportWidth - panel.offsetWidth));

                panel.style.top = `${constrainedTop}px`;
                panel.style.left = `${constrainedLeft}px`;

                // 只有用户主动拖拽才保存位置
                if (!ControlPanel.isProgrammaticMove) {
                    StorageUtil.savePosition({
                        top: constrainedTop,
                        left: constrainedLeft
                    });
                }
            };

            const dragEnd = () => {
                isDragging = false;
                panel.style.boxShadow = options.panelStyle.shadow;
                panel.style.transition = "box-shadow 0.2s ease";

                document.removeEventListener('mousemove', dragMove);
                document.removeEventListener('mouseup', dragEnd);
            };
        },

        /**
         * 初始化存储同步,监听其他标签页的位置和折叠状态变化
         */
        initStorageSync: () => {
            const panel = ControlPanel.panelElement;

            window.addEventListener('storage', (e) => {
                // 处理位置同步
                if (e.key === options.storageKey) {
                    try {
                        const newPos = JSON.parse(e.newValue);
                        if (!newPos) return;

                        ControlPanel.isProgrammaticMove = true;
                        panel.style.top = `${newPos.top}px`;
                        panel.style.left = `${newPos.left}px`;

                        setTimeout(() => {
                            ControlPanel.isProgrammaticMove = false;
                        }, 100);

                        logger.log("从其他标签页同步了面板位置");
                    } catch (ex) {
                        logger.error("同步面板位置失败:", ex);
                    }
                }
                // 处理折叠状态同步
                else if (e.key === options.collapseStorageKey) {
                    try {
                        const newState = JSON.parse(e.newValue);
                        if (newState) {
                            ControlPanel.syncCollapseState(newState);
                            logger.log("从其他标签页同步了折叠状态");
                        }
                    } catch (ex) {
                        logger.error("同步折叠状态失败:", ex);
                    }
                }
            });
        }
    };

    // 初始化函数
    const init = () => {
        if (window.location.host.indexOf(options.matchingHost) === -1) {
            logger.log("不匹配目标网站,脚本不执行");
            return;
        }

        ControlPanel.createPanel();

        const checkInterval = setInterval(() => {
            if (document.readyState === 'unloaded') {
                clearInterval(checkInterval);
                return;
            }

            Detector.detectAll();
            AdManager.handleAllAds();
        }, options.interval);

        window.addEventListener('beforeunload', () => {
            clearInterval(checkInterval);
        });

        logger.log("脚本初始化完成");
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();