DMHY Torrent Block

Enhanced version of DMHY Block script with more features: title display management, user interface management, regex filtering, context menu, ad blocking, and GitHub sync

安裝腳本?
作者推薦腳本

您可能也會喜歡 动漫花园树状显示

安裝腳本
// ==UserScript==
// @name:zh-CN   动漫花园种子屏蔽助手
// @name         DMHY Torrent Block
// @namespace    https://github.com/xkbkx5904
// @version      1.3.10
// @author       xkbkx5904
// @description  Enhanced version of DMHY Block script with more features: title display management, user interface management, regex filtering, context menu, ad blocking, and GitHub sync
// @description:zh-CN  增强版的动漫花园资源屏蔽工具,支持标题显示管理(简繁体切换)、用户界面管理、正则表达式过滤、右键菜单、广告屏蔽和GitHub同步等功能。提供标题过滤、云端数据同步、公共统计池和用户排行榜等特性。
// @homepage     https://github.com/xkbkx5904/dmhy-torrent-block
// @supportURL   https://github.com/xkbkx5904/dmhy-torrent-block/issues
// @match        *://share.dmhy.org/*
// @license      MIT
// @run-at       document-end
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @noframes
// @copyright    2025, xkbkx5904
// @originalAuthor tautcony
// @originalURL  https://greasyfork.org/zh-CN/scripts/36871-dmhy-block
// @icon         https://share.dmhy.org/favicon.ico
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/full.js
// ==/UserScript==

/*
更新日志:
v1.3.10
- 优化标题转换功能,智能识别中英文内容
- 改进标题显示逻辑,只转换中文部分
- 保留英文、数字和特殊字符的原始格式
- 提升标题转换的准确性和性能
- 优化代码结构,提高可维护性

v1.3.9
- 修复标题简繁体转换时链接丢失的问题
- 优化标题转换逻辑,保留HTML结构
- 改进文本节点处理方式

v1.3.8
- 重构 TitleManager 和 UIManager 类,优化代码结构
- 移除重复的标题显示管理代码
- 改进类之间的依赖关系
- 优化事件监听器的绑定逻辑
- 提升代码可维护性和性能

v1.3.7
- 重构标题管理功能,创建独立的 TitleManager 类
- 优化简繁体切换功能,支持实时切换
- 改进代码结构,提高可维护性
- 统一缓存管理,创建 CacheManager 类

v1.3.6
- 添加详细的日志输出系统
- 优化错误处理和提示信息
- 改进代码结构和可维护性
- 统一配置管理

v1.3.5
- 优化 GitHub 同步功能
- 改进 Gist 查找和创建逻辑
- 修复同步时 Gist 不存在的问题
- 增强错误处理和自动恢复机制

v1.3.4
- 优化公共统计池的JSON数据格式
- 改进数据可读性,添加缩进和换行
- 优化数据存储结构

v1.3.1
- 优化公共统计池显示效果
- 添加用户名自动获取和缓存功能
- 改进统计列表显示格式,支持显示用户名
- 优化批量获取用户名的性能

v1.3.0
- 添加 GitHub Gist 同步功能
- 支持黑名单数据的云端备份和恢复
- 添加 GitHub 登录和验证功能
- 支持将黑名单数据贡献到公共统计池
- 添加黑名单用户排行榜功能

v1.2.4
- 优化管理界面加载速度
- 移除打开管理界面时的加载提示
- 使用缓存的用户名信息,提升显示速度
- 异步更新缺失的用户名信息

v1.2.3
- 添加行重排序功能,修复斑马纹样式 (感谢 ishadows)
- 优化过滤后的显示效果

v1.2.2
- 优化简繁体转换功能,增加香港繁体支持
- 添加文字转换缓存机制,提升性能
- 扩大缓存容量至100条
- 改进转换准确度

v1.2.1
- 修复opencc依赖问题

v1.2.0
- 添加简繁体标题过滤功能
- 集成OpenCC实现准确的简繁体转换
- 优化关键词过滤逻辑,支持不区分简繁体

v1.1.6
- 修复关键词输入单个斜杠时的验证问题
- 优化关键词处理逻辑,将单个斜杠视为普通字符串过滤
- 改进管理界面,已有内容时自动在末尾添加分号,方便添加新内容

v1.1.5
- 移除右键添加黑名单时的通知提示
- 优化代码结构,删除未使用的通知管理类
- 改进性能,减少不必要的DOM操作

v1.1.4
- 修复管理界面关闭时错误的未保存更改提示

v1.1.3
- 优化用户名显示和管理功能
- 改进用户ID输入规则提示
- 优化未完整删除的用户数据处理逻辑

v1.1.2
- 优化用户名显示和管理功能
- 改进用户ID输入规则提示
- 优化未完整删除的用户数据处理逻辑

v1.1.1
- 修复数字ID选择器的兼容性问题
- 优化广告屏蔽性能和时机
- 改进广告选择器的精确度
- 统一广告和PikPak按钮的处理逻辑

v1.1.0
- 初始版本发布
- 支持用户界面管理
- 支持正则表达式过滤
- 支持右键菜单
- 支持广告屏蔽
*/

/**
 * 配置对象
 */
const CONFIG = {
    // 存储相关配置
    storage: {
        blockListKey: 'dmhy_blocklist',
        usernameMapKey: 'dmhy_username_map',
        githubTokenKey: 'github_token',
        githubGistIdKey: 'github_gist_id',
        isContributingKey: 'is_contributing',
        githubUserKey: 'github_user'
    },

    // DOM选择器配置
    selectors: {
        torrentList: "table#topic_list tbody tr",
        userLink: "td:last-child a[href*='/user_id/']",
        titleCell: "td.title",
        adSelectors: [
            '[id="1280_adv"]',
            '[id="pkpk"]',
            '.kiwi-ad-wrapper-1280x120',
            'a[onclick*="_trackEvent"][onclick*="ad"]',
            'a[href*="mypikpak.com/drive/url-checker"]',
            'div[align="center"] > a[href*="sng.link"] > img',
            'div[align="center"] > a[href*="weidian.com"] > img[src*="/1280pik.png"]',
            'img[src*="/VA"][src*=".gif"]',
            '.download-pp'
        ]
    },

    // GitHub 相关配置
    github: {
        publicStatsGistId: 'c2df1ecfe5d04f3f2cfb92fd206d4884',
        gistDescription: 'DMHY Block List Sync'
    },

    // 缓存配置
    cache: {
        textConverterSize: 200
    }
};

/**
 * 样式配置
 */
const STYLES = {
    notification: `
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        background: rgba(0, 0, 0, 0.8);
        color: white;
        padding: 10px 20px;
        border-radius: 4px;
        z-index: 10001;
        font-size: 14px;
        transition: opacity 0.3s;
    `,
    blocklistUI: `
        position: fixed;
        left: 10px;
        top: 10px;
        z-index: 9999;
    `,
    manager: `
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-50%);
        background: white;
        padding: 20px;
        border: 1px solid #ccc;
        border-radius: 5px;
        z-index: 10000;
        width: 500px;
        max-height: 80vh;
        overflow-y: auto;
    `
};

/**
 * 缓存管理类
 */
class CacheManager {
    constructor() {
        this.caches = new Map();
        this.maxSize = 200;
    }

    get(cacheName, key) {
        const cache = this.caches.get(cacheName);
        return cache?.get(key);
    }

    set(cacheName, key, value) {
        if (!this.caches.has(cacheName)) {
            this.caches.set(cacheName, new Map());
        }
        const cache = this.caches.get(cacheName);
        
        if (cache.size >= this.maxSize) {
            const firstKey = cache.keys().next().value;
            cache.delete(firstKey);
        }
        cache.set(key, value);
    }
}

/**
 * 工具类
 */
class Utils {
    static cacheManager = new CacheManager();
    static opencc = {
        s2t: null,
        s2hk: null,
        t2s: null
    };

    static async init() {
        try {
            this.opencc = {
                s2t: await OpenCC.Converter({ from: 'cn', to: 'tw' }),
                s2hk: await OpenCC.Converter({ from: 'cn', to: 'hk' }),
                t2s: await OpenCC.Converter({ from: 'tw', to: 'cn' })
            };
        } catch (error) {
            this.handleError(error, 'Utils.init');
        }
    }

    static handleError(error, context) {
        console.warn(`[DMHY Block] Error in ${context}:`, error);
    }

    static log(message, type = 'info') {
        const prefix = '[DMHY Block]';
        switch (type) {
            case 'info':
                console.log(`${prefix} ${message}`);
                break;
            case 'warn':
                console.warn(`${prefix} ${message}`);
                break;
            case 'error':
                console.error(`${prefix} ${message}`);
                break;
            case 'debug':
                console.debug(`${prefix} ${message}`);
                break;
        }
    }

    static convertText(text) {
        if (!text) return {
            original: '',
            simplified: '',
            traditionalTW: '',
            traditionalHK: ''
        };

        const cached = this.cacheManager.get('textConverter', text);
        if (cached) return cached;

        try {
            const result = {
                original: text,
                simplified: this.opencc.t2s?.(text) || text,
                traditionalTW: this.opencc.s2t?.(text) || text,
                traditionalHK: this.opencc.s2hk?.(text) || text
            };

            this.cacheManager.set('textConverter', text, result);
            return result;
        } catch (error) {
            this.handleError(error, 'Utils.convertText');
            return {
                original: text,
                simplified: text,
                traditionalTW: text,
                traditionalHK: text
            };
        }
    }

    static parseKeyword(keyword) {
        if (typeof keyword === 'string' && keyword.startsWith('/') && keyword.endsWith('/')) {
            try {
                return new RegExp(keyword.slice(1, -1));
            } catch (e) {
                return keyword;
            }
        }
        return keyword;
    }

    static formatKeyword(keyword) {
        if (keyword instanceof RegExp) {
            return `/${keyword.source}/`;
        }
        return keyword;
    }
}

/**
 * 黑名单管理类
 */
class BlockListManager {
    constructor() {
        Utils.log('初始化黑名单管理器');
        this.blockList = [];
        this.userNameMap = new Map();
    }

    async init() {
        Utils.log('加载黑名单数据');
        await this.loadBlockList();
        const savedUserNames = GM_getValue(CONFIG.storage.usernameMapKey, {});
        this.userNameMap = new Map(Object.entries(savedUserNames));
        Utils.log(`已加载 ${this.userNameMap.size} 个用户名映射`);
    }

    async loadBlockList() {
        try {
            const saved = GM_getValue(CONFIG.storage.blockListKey, []);
            this.blockList = Array.isArray(saved) ? saved.map(item => {
                if (item.type === 'keywords') {
                    return {
                        type: 'keywords',
                        values: item.values.map(Utils.parseKeyword)
                    };
                }
                return item;
            }) : [];
            Utils.log(`已加载 ${this.blockList.length} 条黑名单规则`);
        } catch (error) {
            Utils.handleError(error, 'BlockListManager.loadBlockList');
            this.blockList = [];
        }
    }

    saveBlockList() {
        try {
            const listToSave = this.blockList.map(item => ({
                ...item,
                values: item.type === 'keywords'
                    ? item.values.map(k => k instanceof RegExp ? `/${k.source}/` : k)
                    : item.values
            }));
            GM_setValue(CONFIG.storage.blockListKey, listToSave);
            Utils.log('黑名单数据已保存');
        } catch (error) {
            Utils.handleError(error, 'BlockListManager.saveBlockList');
        }
    }

    addUser(userId, userName) {
        if (!userId || isNaN(userId)) {
            Utils.log(`无效的用户ID: ${userId}`, 'warn');
            return false;
        }

        const userIdList = this.getUserIds();
        if (!userIdList.includes(userId)) {
            this.updateBlockList('userId', [...userIdList, userId]);
            if (userName) {
                this.userNameMap.set(userId.toString(), userName);
                this.saveUserNameMap();
                Utils.log(`已添加用户: ${userName}(${userId})`);
            }
            return true;
        }
        Utils.log(`用户 ${userId} 已在黑名单中`, 'debug');
        return false;
    }

    getUserIds() {
        return this.blockList.find(item => item.type === 'userId')?.values || [];
    }

    getKeywords() {
        return this.blockList.find(item => item.type === 'keywords')?.values || [];
    }

    updateBlockList(type, values) {
        const index = this.blockList.findIndex(item => item.type === type);
        if (index >= 0) {
            this.blockList[index].values = values;
        } else {
            this.blockList.push({ type, values });
        }
        this.saveBlockList();
    }

    saveUserNameMap() {
        GM_setValue(CONFIG.storage.usernameMapKey, Object.fromEntries(this.userNameMap));
    }

    async getUserName(userId, forceUpdate = false) {
        if (!userId) return null;

        const userIdStr = userId.toString();
        const cachedName = this.userNameMap.get(userIdStr);
        
        if (cachedName && !forceUpdate) return cachedName;

        const userLink = document.querySelector(`a[href="/topics/list/user_id/${userId}"]`);
        if (userLink) {
            const userName = userLink.textContent;
            if (userName) {
                this.userNameMap.set(userIdStr, userName);
                this.saveUserNameMap();
                return userName;
            }
        }

        return new Promise(resolve => {
            const callback = async () => {
                try {
                    const response = await fetch(`https://share.dmhy.org/topics/list/user_id/${userId}`);
                    const text = await response.text();
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(text, 'text/html');
                    const userName = doc.querySelector(`a[href="/topics/list/user_id/${userId}"]`)?.textContent;

                    if (userName) {
                        this.userNameMap.set(userIdStr, userName);
                        this.saveUserNameMap();
                        resolve(userName);
                    } else {
                        resolve(userIdStr);
                    }
                } catch (error) {
                    Utils.handleError(error, 'BlockListManager.getUserName');
                    resolve(userIdStr);
                }
            };

            if (window.requestIdleCallback) {
                requestIdleCallback(() => callback(), { timeout: 5000 });
            } else {
                setTimeout(callback, 0);
            }
        });
    }
}

/**
 * 标题管理类
 */
class TitleManager {
    constructor() {
        this.displayMode = GM_getValue('dmhy_title_display_mode', 'original');
    }

    init() {
        this.saveOriginalHTML();
        this.updateAllTitles();
    }

    saveOriginalHTML() {
        document.querySelectorAll(CONFIG.selectors.titleCell).forEach(cell => {
            if (!cell.hasAttribute('data-original-html')) {
                cell.setAttribute('data-original-html', cell.innerHTML);
            }
        });
    }

    getDisplayModeText() {
        switch (this.displayMode) {
            case 'simplified':
                return '简体';
            case 'traditional':
                return '繁体';
            default:
                return '原文';
        }
    }

    toggleTitleDisplay() {
        const modes = ['original', 'simplified', 'traditional'];
        const currentIndex = modes.indexOf(this.displayMode);
        this.displayMode = modes[(currentIndex + 1) % modes.length];
        GM_setValue('dmhy_title_display_mode', this.displayMode);
        this.updateAllTitles();
    }

    // 判断文本是否需要转换
    shouldConvertText(text) {
        // 如果文本只包含英文、数字、特殊字符,不需要转换
        if (!/[\u4e00-\u9fa5]/.test(text)) {
            return false;
        }
        return true;
    }

    // 智能分割文本,保留英文和特殊字符
    splitText(text) {
        const parts = [];
        let currentPart = '';
        let isChinese = false;

        for (let i = 0; i < text.length; i++) {
            const char = text[i];
            const isCharChinese = /[\u4e00-\u9fa5]/.test(char);
            
            if (isCharChinese !== isChinese) {
                if (currentPart) {
                    parts.push({
                        text: currentPart,
                        needConvert: isChinese
                    });
                }
                currentPart = char;
                isChinese = isCharChinese;
            } else {
                currentPart += char;
            }
        }

        if (currentPart) {
            parts.push({
                text: currentPart,
                needConvert: isChinese
            });
        }

        return parts;
    }

    updateAllTitles() {
        document.querySelectorAll(CONFIG.selectors.titleCell).forEach(cell => {
            if (this.displayMode === 'original') {
                const originalHTML = cell.getAttribute('data-original-html');
                if (originalHTML) {
                    cell.innerHTML = originalHTML;
                }
                return;
            }

            if (!cell.hasAttribute('data-original-html')) {
                cell.setAttribute('data-original-html', cell.innerHTML);
            }

            const walker = document.createTreeWalker(cell, NodeFilter.SHOW_TEXT, null, false);
            const textNodes = [];
            let node;
            while (node = walker.nextNode()) {
                textNodes.push(node);
            }

            textNodes.forEach(textNode => {
                const originalText = textNode.textContent;
                
                // 如果文本不需要转换,直接跳过
                if (!this.shouldConvertText(originalText)) {
                    return;
                }

                // 智能分割文本
                const parts = this.splitText(originalText);
                let newText = '';

                parts.forEach(part => {
                    if (part.needConvert) {
                        const { simplified, traditionalTW } = Utils.convertText(part.text);
                        newText += this.displayMode === 'simplified' ? simplified : traditionalTW;
                    } else {
                        newText += part.text;
                    }
                });

                textNode.textContent = newText;
            });
        });
    }

    shouldHideByTitle(title, blockedKeywords) {
        const { original, simplified, traditionalTW, traditionalHK } = Utils.convertText(title);

        return blockedKeywords.some(keyword => {
            if (typeof keyword === 'string') {
                const keywordVariants = Utils.convertText(keyword);
                const lowerKeyword = keyword.toLowerCase();

                return [original, simplified, traditionalTW, traditionalHK].some(variant =>
                    variant.toLowerCase().includes(lowerKeyword) ||
                    variant.toLowerCase().includes(keywordVariants.simplified.toLowerCase()) ||
                    variant.toLowerCase().includes(keywordVariants.traditionalTW.toLowerCase()) ||
                    variant.toLowerCase().includes(keywordVariants.traditionalHK.toLowerCase())
                );
            }
            return keyword instanceof RegExp && (
                original.match(keyword) ||
                simplified.match(keyword) ||
                traditionalTW.match(keyword) ||
                traditionalHK.match(keyword)
            );
        });
    }
}

/**
 * 过滤管理类
 */
class FilterManager {
    constructor(blockListManager, titleManager) {
        Utils.log('初始化过滤管理器');
        this.blockListManager = blockListManager;
        this.titleManager = titleManager;
    }

    init() {
        Utils.log('应用过滤规则');
        this.applyFilters();
        // 初始化时应用标题显示模式
        this.uiManager?.updateAllTitles();
    }

    setUIManager(uiManager) {
        this.uiManager = uiManager;
    }

    applyFilters() {
        try {
            document.querySelectorAll(`${CONFIG.selectors.torrentList}[style*='display: none']`)
                .forEach(elem => elem.style.display = '');

            if (!this.blockListManager.blockList.length) {
                Utils.log('没有黑名单规则,跳过过滤');
                return;
            }

            const blockedUserIds = this.blockListManager.getUserIds();
            const blockedKeywords = this.blockListManager.getKeywords();

            if (!blockedUserIds.length && !blockedKeywords.length) {
                Utils.log('黑名单为空,跳过过滤');
                return;
            }

            Utils.log(`开始过滤: ${blockedUserIds.length} 个用户ID, ${blockedKeywords.length} 个关键词`);
            this.filterTorrentList(blockedUserIds, blockedKeywords);
        } catch (error) {
            Utils.handleError(error, 'FilterManager.applyFilters');
        }
    }

    filterTorrentList(blockedUserIds, blockedKeywords) {
        let n = 0; // 用于设置行的奇偶样式

        document.querySelectorAll(CONFIG.selectors.torrentList).forEach(elem => {
            try {
                const { title, userId } = this.extractItemInfo(elem);
                if (!title || !userId) return;

                if (this.shouldHideItem(userId, title, blockedUserIds, blockedKeywords)) {
                    elem.style.display = 'none'; // 隐藏元素而不是删除
                } else {
                    elem.style.display = ''; // 确保元素可见
                    // 设置奇偶行样式
                    elem.className = n % 2 === 0 ? 'even' : 'odd';
                    n++;
                }
            } catch (error) {
                Utils.handleError(error, 'FilterManager.filterTorrentList.item');
            }
        });

        // 过滤后更新标题显示
        this.uiManager?.updateAllTitles();
    }

    extractItemInfo(elem) {
        const titleCell = elem.querySelector(CONFIG.selectors.titleCell);
        const title = titleCell ? Array.from(titleCell.childNodes)
            .map(node => node.textContent?.trim())
            .filter(text => text)
            .join(' ') : '';

        const idMatch = elem.querySelector(CONFIG.selectors.userLink)?.href?.match(/user_id\/(\d+)/);
        const userId = idMatch ? parseInt(idMatch[1]) : null;

        return { title, userId };
    }

    shouldHideItem(userId, title, blockedUserIds, blockedKeywords) {
        if (blockedUserIds.includes(userId)) return true;
        return this.titleManager.shouldHideByTitle(title, blockedKeywords);
    }
}

/**
 * UI管理类
 */
class UIManager {
    constructor(blockListManager, filterManager, githubSyncManager, titleManager) {
        this.blockListManager = blockListManager;
        this.filterManager = filterManager;
        this.githubSyncManager = githubSyncManager;
        this.titleManager = titleManager;
        this.uiTexts = {
            manageButton: '管理种子黑名单',
            toggleButton: '切换标题显示:',
            simplified: '简体',
            traditional: '繁体',
            original: '原文',
            blockedUsers: '已屏蔽用户:',
            titleKeywords: '标题关键词(用分号分隔):',
            keywordTips: [
                '提示:支持普通关键词和正则表达式',
                '- 普通关键词直接输入,用分号分隔',
                '- 正则表达式用 / 包裹,例如:/\\d+话/',
                '- 示例:关键词1;/\\d+话/;关键词2'
            ],
            userIdTips: [
                '提示:用户ID输入规则:',
                '- 支持纯数字ID,如:123456',
                '- 支持用户名(ID)格式,如:用户名(123456)',
                '- 多个ID之间用分号分隔'
            ],
            save: '保存',
            close: '关闭',
            githubSync: 'GitHub 同步',
            login: '登录',
            getTokenGuide: '获取 Token 指南',
            howToGetToken: '如何获取 GitHub Token:',
            tokenSteps: [
                '点击下方按钮打开 GitHub Token 设置页面',
                '点击 "Generate new token (classic)"',
                '在 Note 中输入描述(如:DMHY Block)',
                '在 Select scopes 中勾选 "gist"',
                '点击底部的 "Generate token" 按钮',
                '复制生成的 token 并粘贴到上方输入框'
            ],
            openTokenPage: '打开 Token 设置页面 →',
            loggedInAs: '已登录为:',
            logout: '退出',
            syncToGithub: '同步到 GitHub',
            syncFromGithub: '从 GitHub 同步',
            contributeStats: '贡献到公共统计池(用于生成黑名单用户排行榜)',
            userRanking: '黑名单用户排行榜',
            lastUpdate: '最后更新:'
        };
    }

    init() {
        this.addBlocklistUI();
        this.addContextMenu();
    }

    getDisplayModeText() {
        return this.titleManager.getDisplayModeText();
    }

    convertText(text) {
        if (this.titleManager.displayMode === 'original') {
            return text;
        }
        const { simplified, traditionalTW } = Utils.convertText(text);
        return this.titleManager.displayMode === 'simplified' ? simplified : traditionalTW;
    }

    convertTextArray(texts) {
        return texts.map(text => this.convertText(text));
    }

    updateUITexts() {
        const showBlocklistBtn = document.getElementById('show-blocklist');
        const toggleTitleBtn = document.getElementById('toggle-title-display');
        
        if (showBlocklistBtn) {
            showBlocklistBtn.textContent = this.convertText(this.uiTexts.manageButton);
        }
        if (toggleTitleBtn) {
            toggleTitleBtn.textContent = this.convertText(this.uiTexts.toggleButton) + this.getDisplayModeText();
        }
    }

    toggleTitleDisplay() {
        this.titleManager.toggleTitleDisplay();
        this.updateUITexts();
    }

    addBlocklistUI() {
        // 如果已经存在UI,先移除
        const existingUI = document.getElementById('dmhy-blocklist-ui');
        if (existingUI) {
            existingUI.remove();
        }

        const uiHtml = `
            <div id="dmhy-blocklist-ui" style="${STYLES.blocklistUI}">
                <button id="show-blocklist">${this.convertText(this.uiTexts.manageButton)}</button>
                <button id="toggle-title-display" style="margin-left:10px;">${this.convertText(this.uiTexts.toggleButton)}${this.getDisplayModeText()}</button>
            </div>
        `;
        document.body.insertAdjacentHTML('beforeend', uiHtml);

        // 确保在DOM加载完成后再绑定事件
        setTimeout(() => {
            const showBlocklistBtn = document.getElementById('show-blocklist');
            const toggleTitleBtn = document.getElementById('toggle-title-display');
            
            showBlocklistBtn?.addEventListener('click', () => this.showBlocklistManager());
            toggleTitleBtn?.addEventListener('click', () => this.toggleTitleDisplay());
        }, 0);
    }

    async showBlocklistManager() {
        // 如果已经存在管理界面,先移除
        const existingManager = document.getElementById('blocklist-manager');
        const existingOverlay = document.getElementById('blocklist-overlay');
        if (existingManager) existingManager.remove();
        if (existingOverlay) existingOverlay.remove();

        const managerHtml = `
            <div id="blocklist-manager" style="${STYLES.manager}">
                <h3 style="margin-top:0;">${this.convertText(this.uiTexts.manageButton)}</h3>
                <div style="margin-bottom:10px;">
                    <label>${this.convertText(this.uiTexts.blockedUsers)}</label><br>
                    <textarea id="user-ids" style="width:100%;height:100px;margin-top:5px;resize:none;border:1px solid #ccc;"></textarea>
                    <div id="user-ids-error" style="color:red;font-size:12px;margin-top:3px;display:none;"></div>
                </div>
                <div style="margin-bottom:10px;">
                    <label>${this.convertText(this.uiTexts.titleKeywords)}</label><br>
                    <textarea id="keywords" style="width:100%;height:100px;margin-top:5px;resize:none;border:1px solid #ccc;"></textarea>
                    <div id="keywords-error" style="color:red;font-size:12px;margin-top:3px;display:none;"></div>
                </div>
                <div style="display:flex;justify-content:space-between;color:#666;font-size:12px;margin-top:5px;">
                    <div style="flex:1;margin-right:10px;">
                        ${this.convertTextArray(this.uiTexts.keywordTips).join('<br>')}
                    </div>
                    <div style="flex:1;margin-left:10px;">
                        ${this.convertTextArray(this.uiTexts.userIdTips).join('<br>')}
                    </div>
                </div>
                <div style="margin-top:10px;text-align:right;">
                    <button id="save-blocklist" style="padding:5px 15px;">${this.convertText(this.uiTexts.save)}</button>
                    <button id="close-manager" style="padding:5px 15px;margin-left:10px;">${this.convertText(this.uiTexts.close)}</button>
                </div>
                <div style="margin-top:20px;border-top:1px solid #ccc;padding-top:10px;">
                    <h4 style="margin:0 0 10px 0;">GitHub 同步</h4>
                    <div id="github-login-section" style="display:none;">
                        <div style="margin-bottom:10px;">
                            <input type="text" id="github-token" placeholder="GitHub Personal Access Token" style="width:100%;margin-bottom:10px;padding:5px;">
                            <button id="github-login" style="padding:5px 15px;">${this.convertText(this.uiTexts.login)}</button>
                            <button id="get-token-guide" style="padding:5px 15px;margin-left:10px;background-color:#2ea44f;color:white;border:none;cursor:pointer;">
                                ${this.convertText(this.uiTexts.getTokenGuide)}
                            </button>
                        </div>
                        <div id="token-guide" style="display:none;background-color:#f6f8fa;padding:10px;border-radius:4px;margin-top:10px;font-size:12px;line-height:1.5;">
                            <h5 style="margin:0 0 10px 0;">${this.convertText(this.uiTexts.howToGetToken)}</h5>
                            <ol style="margin:0;padding-left:20px;">
                                ${this.convertTextArray(this.uiTexts.tokenSteps).map(step => `<li>${step}</li>`).join('')}
                            </ol>
                            <div style="margin-top:10px;text-align:right;">
                                <a href="https://github.com/settings/tokens" target="_blank" style="color:#0366d6;text-decoration:none;">
                                    ${this.convertText(this.uiTexts.openTokenPage)}
                                </a>
                            </div>
                        </div>
                    </div>
                    <div id="github-sync-section" style="display:none;">
                        <div style="margin-bottom:10px;">
                            ${this.convertText(this.uiTexts.loggedInAs)}<span id="github-username"></span>
                            <button id="github-logout" style="margin-left:10px;padding:2px 8px;">${this.convertText(this.uiTexts.logout)}</button>
                        </div>
                        <div style="margin-bottom:10px;">
                            <button id="sync-to-github" style="padding:5px 15px;margin-right:10px;">${this.convertText(this.uiTexts.syncToGithub)}</button>
                            <button id="sync-from-github" style="padding:5px 15px;">${this.convertText(this.uiTexts.syncFromGithub)}</button>
                        </div>
                        <div style="margin-bottom:10px;">
                            <label>
                                <input type="checkbox" id="contribute-stats">
                                ${this.convertText(this.uiTexts.contributeStats)}
                            </label>
                        </div>
                        <div id="stats-section" style="display:none;">
                            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                                <h5 style="margin:0;">${this.convertText(this.uiTexts.userRanking)}</h5>
                                <div style="font-size:12px;color:#666;">
                                    ${this.convertText(this.uiTexts.lastUpdate)}<span id="stats-last-update">-</span>
                                </div>
                            </div>
                            <div id="stats-list" style="max-height:200px;overflow-y:auto;"></div>
                        </div>
                    </div>
                </div>
            </div>
            <div id="blocklist-overlay" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;"></div>
        `;
        document.body.insertAdjacentHTML('beforeend', managerHtml);

        this.initManagerEvents();
        this.fillManagerData();
        this.initGitHubEvents();
        
        // 如果已开启贡献,显示统计信息并更新
        if (this.githubSyncManager.isContributing) {
            const statsSection = document.getElementById('stats-section');
            statsSection.style.display = 'block';
            await this.updateStatsList();
        }
    }

    initManagerEvents() {
        const closeManager = () => {
            if (this.hasUnsavedChanges()) {
                if (confirm('有未保存的更改,确定要关闭吗?')) {
                    document.getElementById('blocklist-manager')?.remove();
                    document.getElementById('blocklist-overlay')?.remove();
                }
            } else {
                document.getElementById('blocklist-manager')?.remove();
                document.getElementById('blocklist-overlay')?.remove();
            }
        };

        document.getElementById('close-manager')?.addEventListener('click', closeManager);

        document.getElementById('blocklist-overlay')?.addEventListener('click', e => {
            if (e.target === e.currentTarget) {
                closeManager();
            }
        });

        document.getElementById('save-blocklist')?.addEventListener('click', async () => {
            const saveResult = await this.saveManagerData();
            if (saveResult) {
                closeManager();
                this.filterManager.applyFilters();
            }
        });

        document.getElementById('user-ids')?.addEventListener('input', () => {
            this.validateManagerData();
        });

        document.getElementById('keywords')?.addEventListener('input', () => {
            this.validateManagerData();
        });
    }

    fillManagerData() {
        const keywords = this.blockListManager.getKeywords();
        const keywordsText = keywords.map(k => {
            if (k instanceof RegExp) {
                return `/${k.source}/`;
            }
            return k;
        }).join(';');

        // 如果有关键词,在末尾添加分号
        document.getElementById('keywords').value = keywordsText ? keywordsText + ';' : '';

        // 获取用户ID列表并在末尾添加分号
        const userIds = this.blockListManager.getUserIds()
            .map(id => {
                const name = this.blockListManager.userNameMap.get(id.toString());
                return name ? `${name}(${id})` : id;
            })
            .join(';');

        document.getElementById('user-ids').value = userIds ? userIds + ';' : '';

        // 异步更新缺失的用户名
        this.updateMissingUserNames();
    }

    async updateMissingUserNames() {
        const userIds = this.blockListManager.getUserIds();
        const missingIds = userIds.filter(id => !this.blockListManager.userNameMap.has(id.toString()));

        if (missingIds.length > 0) {
            for (const id of missingIds) {
            try {
                const userName = await this.blockListManager.getUserName(id, true);
                if (userName) {
                        // 更新输入框中的用户名显示
                        const currentValue = document.getElementById('user-ids').value;
                        const newValue = currentValue.replace(
                            new RegExp(`\\b${id}\\b`),
                            `${userName}(${id})`
                        );
                        document.getElementById('user-ids').value = newValue;
                }
            } catch (error) {
                    this.handleError(error, 'UIManager.updateMissingUserNames');
            }
            await new Promise(resolve => setTimeout(resolve, 500));
        }
        }
    }

    hasUnsavedChanges() {
        const currentUserIds = document.getElementById('user-ids')?.value.trim() || '';
        const currentKeywords = document.getElementById('keywords')?.value.trim() || '';

        const originalUserIds = this.blockListManager.getUserIds()
            .map(id => {
                const name = this.blockListManager.userNameMap.get(id.toString());
                return name ? `${name}(${id})` : id;
            })
            .join(';');

        const originalKeywords = this.blockListManager.getKeywords()
            .map(k => k instanceof RegExp ? `/${k.source}/` : k)
            .join(';');

        const normalizeString = (str) => str.split(/[;;]/)
            .map(s => s.trim())
            .filter(s => s)
            .sort()
            .join(';');

        return normalizeString(currentUserIds) !== normalizeString(originalUserIds) ||
               normalizeString(currentKeywords) !== normalizeString(originalKeywords);
    }

    validateManagerData() {
        const userIdsInput = document.getElementById('user-ids');
        const keywordsInput = document.getElementById('keywords');
        const userIdsError = document.getElementById('user-ids-error');
        const keywordsError = document.getElementById('keywords-error');
        const saveButton = document.getElementById('save-blocklist');

        let isValid = true;

        userIdsError.style.display = 'none';
        keywordsError.style.display = 'none';
        userIdsInput.style.borderColor = '#ccc';
        keywordsInput.style.borderColor = '#ccc';
        saveButton.style.borderColor = '';

        if (userIdsInput.value.trim()) {
            const items = userIdsInput.value.trim().split(/[;;]/).map(item => item.trim()).filter(item => item);
            const invalidItems = items.filter(item => {
                return !(/^\d+$/.test(item) || /^.+\(\d+\)$/.test(item));
            });

            if (invalidItems.length > 0) {
                userIdsError.textContent = `以下用户ID格式无效:${invalidItems.join('、')}`;
                userIdsError.style.display = 'block';
                userIdsInput.style.borderColor = 'red';
                isValid = false;
            }
        }

        if (keywordsInput.value.trim()) {
            const keywords = keywordsInput.value.trim().split(/[;;]/).map(k => k.trim()).filter(k => k);
            const invalidKeywords = keywords.filter(k => {
                if (k === '/') return false;

                if (k.startsWith('/') && k.endsWith('/')) {
                    try {
                        new RegExp(k.slice(1, -1));
                        return false;
                    } catch (e) {
                        return true;
                    }
                }
                return false;
            });

            if (invalidKeywords.length > 0) {
                keywordsError.textContent = `以下正则表达式格式无效:${invalidKeywords.join('、')}`;
                keywordsError.style.display = 'block';
                keywordsInput.style.borderColor = 'red';
                isValid = false;
            }
        }

        if (!isValid) {
            saveButton.style.borderColor = 'red';
        }

        return { isValid };
    }

    async saveManagerData() {
        const { isValid } = this.validateManagerData();

        if (!isValid) {
            alert('请修正输入错误后再保存');
            return false;
        }

        const oldUserIds = this.blockListManager.getUserIds();

        const userIdsInput = document.getElementById('user-ids').value
            .split(/[;;]/)
            .map(item => item.trim())
            .filter(item => item);

        const validIds = [];
        const invalidItems = [];
        const retainedIds = [];

        userIdsInput.forEach(item => {
            if (/^\d+$/.test(item)) {
                validIds.push(parseInt(item));
                return;
            }

            const idMatch = item.match(/^.+\((\d+)\)$/);
            if (idMatch && /^\d+$/.test(idMatch[1])) {
                validIds.push(parseInt(idMatch[1]));
                return;
            }

            const partialMatch = item.match(/\((\d+)/);
            if (partialMatch) {
                const partialId = parseInt(partialMatch[1]);
                if (oldUserIds.includes(partialId)) {
                    retainedIds.push(partialId);
                    invalidItems.push(`${item} (已保留原数据)`);
                    return;
                }
            }

            invalidItems.push(item);
        });

        const finalIds = [...new Set([...validIds, ...retainedIds])];

        if (invalidItems.length > 0) {
            alert(`以下内容格式无效:${invalidItems.join('、')}`);
        }

        const newKeywords = document.getElementById('keywords').value
            .split(/[;;]/)
            .map(k => k.trim())
            .filter(k => k)
            .map(k => {
                if (k.startsWith('/') && k.endsWith('/')) {
                    try {
                        return new RegExp(k.slice(1, -1));
                    } catch (e) {
                        return k;
                    }
                }
                return k;
            });

        this.blockListManager.updateBlockList('userId', finalIds);
        this.blockListManager.updateBlockList('keywords', newKeywords);

        const addedUserIds = finalIds.filter(id => !oldUserIds.includes(id));

        if (addedUserIds.length > 0) {
            this.processNewUserIds(addedUserIds);
        }

        // 如果开启了贡献到公共池,自动同步
        if (this.githubSyncManager.isContributing) {
            await this.githubSyncManager.contributeToPublicStats();
            await this.updateStatsList();
        }

        return true;
    }

    processNewUserIds(userIds) {
        if (window.requestIdleCallback) {
            requestIdleCallback(() => {
                this.processUserNameQueue(userIds);
            }, { timeout: 1000 });
        } else {
            setTimeout(() => {
                this.processUserNameQueue(userIds);
            }, 0);
        }
    }

    async processUserNameQueue(userIds) {
        for (const userId of userIds) {
            try {
                const userName = await this.blockListManager.getUserName(userId, true);
                if (userName) {
                    console.log(`[DMHY Block] 成功获取用户名: ${userName}(${userId})`);
                }
            } catch (error) {
                this.handleError(error, 'UIManager.processUserNameQueue');
            }
            await new Promise(resolve => setTimeout(resolve, 500));
        }
    }

    addContextMenu() {
        const menuHtml = `
            <div id="dmhy-context-menu" style="display:none;position:fixed;background:white;
                border:1px solid #ccc;border-radius:3px;padding:5px;box-shadow:2px 2px 5px rgba(0,0,0,0.2);z-index:10000;">
                <div id="block-user" style="padding:5px 10px;cursor:pointer;hover:background-color:#f0f0f0;">
                    添加用户到黑名单
                </div>
            </div>
        `;
        document.body.insertAdjacentHTML('beforeend', menuHtml);
        this.initContextMenuEvents();
    }

    initContextMenuEvents() {
        const menu = document.getElementById('dmhy-context-menu');

        document.addEventListener('contextmenu', e => {
            const userLink = e.target.closest(CONFIG.selectors.userLink);
            if (userLink) {
                e.preventDefault();
                const userId = userLink.href.match(/user_id\/(\d+)/)?.[1];
                const userName = userLink.textContent;
                if (userId) {
                    menu.style.display = 'block';
                    menu.style.left = e.clientX + 'px';
                    menu.style.top = e.clientY + 'px';

                    document.getElementById('block-user').onclick = e => {
                        e.stopPropagation();
                        if (this.blockListManager.addUser(parseInt(userId), userName)) {
                            this.filterManager.applyFilters();
                        }
                        menu.style.display = 'none';
                    };
                }
            }
        });

        document.addEventListener('click', e => {
            if (!menu.contains(e.target)) {
                menu.style.display = 'none';
            }
        });

        window.addEventListener('scroll', () => {
            menu.style.display = 'none';
        });
    }

    initGitHubEvents() {
        const githubLoginSection = document.getElementById('github-login-section');
        const githubSyncSection = document.getElementById('github-sync-section');
        const githubUsername = document.getElementById('github-username');
        const contributeStats = document.getElementById('contribute-stats');
        const statsSection = document.getElementById('stats-section');
        const tokenGuide = document.getElementById('token-guide');

        // 显示登录状态
        if (this.githubSyncManager.githubUser) {
            githubLoginSection.style.display = 'none';
            githubSyncSection.style.display = 'block';
            githubUsername.textContent = this.githubSyncManager.githubUser;
            contributeStats.checked = this.githubSyncManager.isContributing;
        } else {
            githubLoginSection.style.display = 'block';
            githubSyncSection.style.display = 'none';
        }

        // 获取 Token 指南按钮
        document.getElementById('get-token-guide')?.addEventListener('click', () => {
            tokenGuide.style.display = tokenGuide.style.display === 'none' ? 'block' : 'none';
        });

        // 登录按钮
        document.getElementById('github-login')?.addEventListener('click', async () => {
            const token = document.getElementById('github-token').value.trim();
            if (!token) {
                alert('请输入 GitHub Personal Access Token');
                return;
            }

            this.githubSyncManager.setToken(token);
            if (await this.githubSyncManager.validateToken()) {
                githubLoginSection.style.display = 'none';
                githubSyncSection.style.display = 'block';
                githubUsername.textContent = this.githubSyncManager.githubUser;
            } else {
                alert('Token 无效,请检查后重试');
            }
        });

        // 退出按钮
        document.getElementById('github-logout')?.addEventListener('click', () => {
            this.githubSyncManager.setToken('');
            this.githubSyncManager.setContributing(false);
            githubLoginSection.style.display = 'block';
            githubSyncSection.style.display = 'none';
            statsSection.style.display = 'none';
        });

        // 同步到 GitHub
        document.getElementById('sync-to-github')?.addEventListener('click', async () => {
            if (await this.githubSyncManager.updateGist()) {
                alert('同步成功');
                if (this.githubSyncManager.isContributing) {
                    await this.githubSyncManager.contributeToPublicStats();
                }
            } else {
                alert('同步失败,请检查网络连接或 Token 权限');
            }
        });

        // 从 GitHub 同步
        document.getElementById('sync-from-github')?.addEventListener('click', async () => {
            if (await this.githubSyncManager.syncFromGist()) {
                this.fillManagerData();
                this.filterManager.applyFilters();
                alert('同步成功');
            } else {
                alert('同步失败,请检查网络连接或 Token 权限');
            }
        });

        // 贡献到公共统计池
        contributeStats?.addEventListener('change', async (e) => {
            this.githubSyncManager.setContributing(e.target.checked);
            if (e.target.checked) {
                await this.githubSyncManager.contributeToPublicStats();
                await this.updateStatsList();
                statsSection.style.display = 'block';
            } else {
                // 取消贡献时,从公共池中移除数据
                await this.githubSyncManager.removeFromPublicStats();
                statsSection.style.display = 'none';
            }
        });
    }

    async updateStatsList() {
        const statsList = document.getElementById('stats-list');
        const statsLastUpdate = document.getElementById('stats-last-update');
        const stats = await this.githubSyncManager.getPublicStats();
        
        if (stats && stats.length > 0) {
            const html = stats.slice(0, 10).map((stat, index) => `
                <div style="padding:5px;border-bottom:1px solid #eee;">
                    ${index + 1}. ${stat.userName} (ID: ${stat.userId}) - 被 ${stat.count} 人屏蔽
                </div>
            `).join('');
            statsList.innerHTML = html;
            statsLastUpdate.textContent = new Date().toLocaleString();
        } else {
            statsList.innerHTML = '<div style="padding:5px;color:#666;">暂无统计数据</div>';
            statsLastUpdate.textContent = '-';
        }
    }

    updateAllTitles() {
        document.querySelectorAll(CONFIG.selectors.titleCell).forEach(cell => {
            if (this.displayMode === 'original') {
                const originalHTML = cell.getAttribute('data-original-html');
                if (originalHTML) {
                    cell.innerHTML = originalHTML;
                }
                return;
            }

            if (!cell.hasAttribute('data-original-html')) {
                cell.setAttribute('data-original-html', cell.innerHTML);
            }

            const walker = document.createTreeWalker(cell, NodeFilter.SHOW_TEXT, null, false);
            const textNodes = [];
            let node;
            while (node = walker.nextNode()) {
                textNodes.push(node);
            }

            textNodes.forEach(textNode => {
                const originalText = textNode.textContent;
                
                // 如果文本不需要转换,直接跳过
                if (!this.shouldConvertText(originalText)) {
                    return;
                }

                // 智能分割文本
                const parts = this.splitText(originalText);
                let newText = '';

                parts.forEach(part => {
                    if (part.needConvert) {
                        const { simplified, traditionalTW } = Utils.convertText(part.text);
                        newText += this.displayMode === 'simplified' ? simplified : traditionalTW;
                    } else {
                        newText += part.text;
                    }
                });

                textNode.textContent = newText;
            });
        });
    }
}

/**
 * 广告拦截类
 */
class AdBlocker {
    static init() {
        this.hideAds();

        document.addEventListener('DOMContentLoaded', () => {
            this.hideAds();
        });

        this.initDOMObserver();

        window.addEventListener('load', () => {
            this.hideAds();
        });
    }

    static initDOMObserver() {
        const config = {
            childList: true,
            subtree: true,
            attributes: true,
        };

        const observer = new MutationObserver((mutations) => {
            window.requestAnimationFrame(() => {
                this.hideAds();
            });
        });

        observer.observe(document.documentElement, config);
    }

    static hideAds() {
        if (!document.getElementById('dmhy-ad-styles')) {
            const style = document.createElement('style');
            style.id = 'dmhy-ad-styles';
            style.textContent = CONFIG.selectors.adSelectors
                .map(selector => `${selector} { display: none !important; }`)
                .join('\n');
            document.head.appendChild(style);
        }

        CONFIG.selectors.adSelectors.forEach(selector => {
            try {
                document.querySelectorAll(selector).forEach(element => {
                    if (element) {
                        element.style.setProperty('display', 'none', 'important');
                    }
                });
            } catch (error) {
                Utils.handleError(error, 'AdBlocker.hideAds');
            }
        });
    }
}

/**
 * 事件管理类
 */
class EventManager {
    constructor(filterManager) {
        this.filterManager = filterManager;
    }

    init() {
        this.initSortingEvents();
    }

    initSortingEvents() {
        document.querySelectorAll("th.header").forEach(header => {
            header.addEventListener('click', () => {
                setTimeout(() => this.filterManager.applyFilters(), 100);
            });
        });
    }
}

/**
 * GitHub 同步管理类
 */
class GitHubSyncManager {
    constructor(blockListManager) {
        Utils.log('初始化 GitHub 同步管理器');
        this.blockListManager = blockListManager;
        this.token = GM_getValue(CONFIG.storage.githubTokenKey, '');
        this.gistId = GM_getValue(CONFIG.storage.githubGistIdKey, '');
        this.isContributing = GM_getValue(CONFIG.storage.isContributingKey, false);
        this.githubUser = GM_getValue(CONFIG.storage.githubUserKey, '');
        this.publicStatsGistId = CONFIG.github.publicStatsGistId;
    }

    async init() {
        if (this.token) {
            await this.validateToken();
        }
    }

    async validateToken() {
        try {
            Utils.log('验证 GitHub Token');
            const response = await fetch('https://api.github.com/user', {
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                }
            });

            if (response.ok) {
                const userData = await response.json();
                this.githubUser = userData.login;
                GM_setValue(CONFIG.storage.githubUserKey, this.githubUser);
                Utils.log(`GitHub 用户验证成功: ${this.githubUser}`);
                return true;
            } else {
                Utils.log('GitHub Token 无效', 'warn');
                this.token = '';
                this.githubUser = '';
                GM_setValue(CONFIG.storage.githubTokenKey, '');
                GM_setValue(CONFIG.storage.githubUserKey, '');
                return false;
            }
        } catch (error) {
            Utils.log('GitHub Token 验证失败', 'error');
            return false;
        }
    }

    async findExistingGist() {
        try {
            const response = await fetch('https://api.github.com/gists', {
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                }
            });

            if (response.ok) {
                const gists = await response.json();
                const existingGist = gists.find(gist => gist.description === CONFIG.github.gistDescription);
                if (existingGist) {
                    this.gistId = existingGist.id;
                    GM_setValue(CONFIG.storage.githubGistIdKey, this.gistId);
                    return true;
                }
            }
            return false;
        } catch (error) {
            console.error('[DMHY Block] 查找 Gist 失败:', error);
            return false;
        }
    }

    getBlockListData() {
        return {
            userIds: this.blockListManager.getUserIds(),
            keywords: this.blockListManager.getKeywords().map(k => 
                k instanceof RegExp ? `/${k.source}/` : k
            ),
            lastUpdate: new Date().toISOString()
        };
    }

    async createGist() {
        try {
            // 先检查是否已存在 Gist
            if (await this.findExistingGist()) {
                return true;
            }

            // 如果没有找到现有 Gist,创建新的
            const createResponse = await fetch('https://api.github.com/gists', {
                method: 'POST',
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                },
                body: JSON.stringify({
                    description: CONFIG.github.gistDescription,
                    public: false,
                    files: {
                        'blocklist.json': {
                            content: JSON.stringify(this.getBlockListData())
                        }
                    }
                })
            });

            if (createResponse.ok) {
                const gist = await createResponse.json();
                this.gistId = gist.id;
                GM_setValue(CONFIG.storage.githubGistIdKey, this.gistId);
                return true;
            }
            return false;
        } catch (error) {
            console.error('[DMHY Block] Create gist error:', error);
            return false;
        }
    }

    async updateGist() {
        if (!this.gistId) {
            return await this.createGist();
        }

        try {
            const response = await fetch(`https://api.github.com/gists/${this.gistId}`, {
                method: 'PATCH',
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                },
                body: JSON.stringify({
                    files: {
                        'blocklist.json': {
                            content: JSON.stringify(this.getBlockListData())
                        }
                    }
                })
            });

            if (!response.ok) {
                console.error('[DMHY Block] 更新 Gist 失败:', response.status);
                if (response.status === 404) {
                    this.gistId = '';
                    GM_setValue(CONFIG.storage.githubGistIdKey, '');
                    return await this.createGist();
                }
                return false;
            }

            return true;
        } catch (error) {
            console.error('[DMHY Block] Update gist error:', error);
            return false;
        }
    }

    async syncFromGist() {
        if (!this.gistId) {
            Utils.log('未找到 Gist ID,尝试查找现有 Gist');
            if (!await this.findExistingGist()) {
                Utils.log('未找到现有 Gist', 'warn');
                return false;
            }
        }

        try {
            Utils.log(`从 Gist ${this.gistId} 同步数据`);
            const response = await fetch(`https://api.github.com/gists/${this.gistId}`, {
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                }
            });

            if (!response.ok) {
                Utils.log(`从 Gist 同步失败: ${response.status}`, 'error');
                if (response.status === 404) {
                    this.gistId = '';
                    GM_setValue(CONFIG.storage.githubGistIdKey, '');
                    return await this.syncFromGist();
                }
                return false;
            }

            const gist = await response.json();
            const content = JSON.parse(gist.files['blocklist.json'].content);
            
            this.blockListManager.updateBlockList('userId', content.userIds);
            this.blockListManager.updateBlockList('keywords', content.keywords.map(k => {
                if (k.startsWith('/') && k.endsWith('/')) {
                    try {
                        return new RegExp(k.slice(1, -1));
                    } catch (e) {
                        return k;
                    }
                }
                return k;
            }));

            Utils.log('Gist 同步成功');
            return true;
        } catch (error) {
            Utils.log('Gist 同步失败', 'error');
            return false;
        }
    }

    async getPublicStats() {
        try {
            const response = await fetch(`https://api.github.com/gists/${this.publicStatsGistId}`, {
                headers: {
                    'Accept': 'application/vnd.github.v3+json'
                }
            });

            if (!response.ok) {
                console.error('[DMHY Block] 获取公共池数据失败:', response.status);
                return null;
            }

            const gist = await response.json();
            if (!gist.files || !gist.files['stats.json']) {
                console.error('[DMHY Block] 公共池文件不存在');
                return null;
            }

            const content = gist.files['stats.json'].content;
            if (!content) {
                console.error('[DMHY Block] 公共池内容为空');
                return null;
            }

            try {
                const parsedContent = JSON.parse(content);
                if (!parsedContent.contributors) {
                    parsedContent.contributors = [];
                }
                
                // 计算每个用户ID被屏蔽的次数
                const stats = {};
                parsedContent.contributors.forEach(contributor => {
                    contributor.userIds.forEach(userId => {
                        stats[userId] = (stats[userId] || 0) + 1;
                    });
                });

                // 转换为数组并排序
                const sortedStats = Object.entries(stats)
                    .map(([userId, count]) => ({ userId: parseInt(userId), count }))
                    .sort((a, b) => b.count - a.count);

                // 获取所有用户ID的用户名
                const userIds = sortedStats.map(stat => stat.userId);
                const userNames = await this.getUserNames(userIds);

                // 将用户名添加到统计结果中
                return sortedStats.map(stat => ({
                    ...stat,
                    userName: userNames[stat.userId] || `用户${stat.userId}`
                }));
            } catch (parseError) {
                console.error('[DMHY Block] 解析公共池数据失败:', parseError);
                return null;
            }
        } catch (error) {
            console.error('[DMHY Block] 获取公共统计失败:', error);
            return null;
        }
    }

    async removeFromPublicStats() {
        try {
            // 获取现有统计数据
            const response = await fetch(`https://api.github.com/gists/${this.publicStatsGistId}`, {
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                }
            });

            if (!response.ok) {
                console.error('[DMHY Block] 获取公共池数据失败:', response.status);
                return;
            }

            const gist = await response.json();
            if (!gist.files || !gist.files['stats.json']) {
                console.error('[DMHY Block] 公共池文件不存在');
                return;
            }

            const content = gist.files['stats.json'].content;
            if (!content) {
                console.error('[DMHY Block] 公共池内容为空');
                return;
            }

            try {
                const parsedContent = JSON.parse(content);
                if (!parsedContent.contributors) {
                    parsedContent.contributors = [];
                }
                
                // 移除当前用户的贡献
                parsedContent.contributors = parsedContent.contributors.filter(c => c.githubUser !== this.githubUser);

                // 更新 Gist
                const updateResponse = await fetch(`https://api.github.com/gists/${this.publicStatsGistId}`, {
                    method: 'PATCH',
                    headers: {
                        'Authorization': `token ${this.token}`,
                        'Accept': 'application/vnd.github.v3+json'
                    },
                    body: JSON.stringify({
                        files: {
                            'stats.json': {
                                content: JSON.stringify(parsedContent, null, 2)
                            }
                        }
                    })
                });

                if (!updateResponse.ok) {
                    console.error('[DMHY Block] 更新公共池失败:', updateResponse.status);
                }
            } catch (parseError) {
                console.error('[DMHY Block] 解析公共池数据失败:', parseError);
            }
        } catch (error) {
            console.error('[DMHY Block] 从公共池移除数据失败:', error);
        }
    }

    async contributeToPublicStats() {
        if (!this.isContributing) return;

        try {
            const userIds = this.blockListManager.getUserIds();

            // 获取现有统计数据
            const response = await fetch(`https://api.github.com/gists/${this.publicStatsGistId}`, {
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                }
            });

            if (!response.ok) {
                console.error('[DMHY Block] 获取公共池数据失败:', response.status);
                return;
            }

            const gist = await response.json();
            if (!gist.files || !gist.files['stats.json']) {
                console.error('[DMHY Block] 公共池文件不存在');
                return;
            }

            const content = gist.files['stats.json'].content;
            let parsedContent;
            
            try {
                parsedContent = content ? JSON.parse(content) : { contributors: [] };
            } catch (parseError) {
                console.error('[DMHY Block] 解析公共池数据失败:', parseError);
                parsedContent = { contributors: [] };
            }

            if (!parsedContent.contributors) {
                parsedContent.contributors = [];
            }
            
            // 更新或添加当前用户的贡献
            const contributorIndex = parsedContent.contributors.findIndex(c => c.githubUser === this.githubUser);
            if (contributorIndex >= 0) {
                parsedContent.contributors[contributorIndex] = {
                    githubUser: this.githubUser,
                    userIds: userIds,
                    lastUpdate: new Date().toISOString()
                };
            } else {
                parsedContent.contributors.push({
                    githubUser: this.githubUser,
                    userIds: userIds,
                    lastUpdate: new Date().toISOString()
                });
            }

            // 更新 Gist
            const updateResponse = await fetch(`https://api.github.com/gists/${this.publicStatsGistId}`, {
                method: 'PATCH',
                headers: {
                    'Authorization': `token ${this.token}`,
                    'Accept': 'application/vnd.github.v3+json'
                },
                body: JSON.stringify({
                    files: {
                        'stats.json': {
                            content: JSON.stringify(parsedContent, null, 2)
                        }
                    }
                })
            });

            if (!updateResponse.ok) {
                console.error('[DMHY Block] 更新公共池失败:', updateResponse.status);
            }
        } catch (error) {
            console.error('[DMHY Block] 贡献到公共池失败:', error);
        }
    }

    async getUserNames(userIds) {
        const userNames = {};
        const unknownIds = userIds.filter(id => !this.blockListManager.userNameMap.has(id.toString()));

        // 批量获取未知用户名的用户信息
        for (const id of unknownIds) {
            try {
                const userName = await this.blockListManager.getUserName(id, true);
                if (userName) {
                    userNames[id] = userName;
                }
            } catch (error) {
                console.error(`[DMHY Block] Error getting username for user ${id}:`, error);
            }
            // 添加延迟以避免请求过快
            await new Promise(resolve => setTimeout(resolve, 500));
        }

        // 合并已知的用户名
        userIds.forEach(id => {
            const cachedName = this.blockListManager.userNameMap.get(id.toString());
            if (cachedName) {
                userNames[id] = cachedName;
            }
        });

        return userNames;
    }

    setToken(token) {
        this.token = token;
        GM_setValue(CONFIG.storage.githubTokenKey, token);
    }

    setContributing(isContributing) {
        this.isContributing = isContributing;
        GM_setValue(CONFIG.storage.isContributingKey, isContributing);
    }
}

/**
 * 应用主类
 */
class App {
    static async init() {
        try {
            Utils.log('初始化应用');
            await Utils.init();
            Utils.log('文字转换器初始化完成');

            AdBlocker.init();
            Utils.log('广告拦截器初始化完成');

            const blockListManager = new BlockListManager();
            await blockListManager.init();
            Utils.log('黑名单管理器初始化完成');

            const titleManager = new TitleManager();
            titleManager.init();
            Utils.log('标题管理器初始化完成');

            const filterManager = new FilterManager(blockListManager, titleManager);
            const githubSyncManager = new GitHubSyncManager(blockListManager);
            await githubSyncManager.init();
            Utils.log('GitHub 同步管理器初始化完成');

            const uiManager = new UIManager(blockListManager, filterManager, githubSyncManager, titleManager);
            const eventManager = new EventManager(filterManager);

            uiManager.init();
            filterManager.init();
            eventManager.init();
            Utils.log('应用初始化完成');
        } catch (error) {
            Utils.log('应用初始化失败', 'error');
            Utils.handleError(error, 'App.init');
        }
    }
}

// 启动应用
(function() {
    'use strict';
    App.init();
})();