Enhanced_Media_Helper

Enhanced media downloader with multiple site support

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name           Enhanced_Media_Helper
// @version        2.7.6
// @description    Enhanced media downloader with multiple site support
// @author         cores
// @match          https://jable.tv/videos/*
// @match          https://tokyolib.com/*
// @match          https://javgg.net/tag/to-be-release/*
// @match          https://javgg.net/featured/*
// @match          https://javgg.net/
// @match          https://javgg.net/new-post/*
// @match          https://javgg.net/jav/*
// @match          https://javgg.net/star/*
// @match          https://javgg.net/trending/*
// @match          https://www.javbus.com/*
// @include        /.*javtxt.[a-z]+\/v/.*$/
// @include        /.*javtxt.[a-z]+\/.*$/
// @include        /.*javtext.[a-z]+\/v/.*$/
// @match          https://cableav.tv/?p=*
// @require        https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @icon           data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant          GM_xmlhttpRequest
// @grant          GM_setValue
// @grant          GM_getValue
// @connect        api-shoulei-ssl.xunlei.com
// @connect        subtitle.v.geilijiasu.com
// @license        MPL
// @namespace      cdn.bootcss.com
// ==/UserScript==

(function () {
    'use strict';

    let EMH_currentVideoCode = null;

    function updateGlobalVideoCode(code) {
        if (code) {
            EMH_currentVideoCode = code;
            console.log("EMH: Global video code updated:", EMH_currentVideoCode);
            // The draggable button no longer relies on this for being enabled,
            // but EMH_currentVideoCode is used as a default in its prompt.
        }
    }

    const CONFIG = {
        serverMode: 2,
        serverPort: 9909,
        alternateUrl: {
			 av123:'https://123av.com/zh/v/',
			 jable:'https://jable.tv/videos/',
			 cili1: 'https://1cili.com/search?q=',
        },
        subtitleApiUrl: 'https://api-shoulei-ssl.xunlei.com/oracle/subtitle',
        elementCheckInterval: 200,
        elementCheckTimeout: 7000,
        searchHistoryKey: 'emh_subtitle_search_history',
        maxHistoryItems: 10,
        animationDuration: 300,
        toastDuration: 3000,
        // 字幕文件名处理选项
        subtitleFilenameOptions: {
            useOriginalName: true,      // 使用API返回的原始name属性,不做修改
            addCodePrefix: false,       // 已弃用:是否添加影片编码作为前缀(当useOriginalName=false时使用)
            removeIllegalChars: true,   // 是否移除非法字符
            maxLength: 100              // 文件名最大长度
        },
        // 番号管理相关配置
        codeManager: {
            storageKey: 'emh_code_library',
            trashStorageKey: 'emh_code_trash',
            trashRetentionDays: 7,      // 回收站保留天数
            autoAddDetected: true,      // 自动添加检测到的番号
            defaultPage: 'all',         // 默认页面: all, favorite, watched, trash
            statusColors: {
                unmarked: '#909090',    // 未标记 - 灰色
                favorite: '#ff4757',    // 关注 - 红色
                watched: '#2ed573'      // 已看 - 绿色
            }
        }
    };

    // 番号管理库
    const CODE_LIBRARY = {
        // 数据结构
        data: null,
        trash: null,
        initialized: false,

        // 初始化库
        init: function() {
            if (this.initialized) return true;

            try {
                // 主库
                const savedData = GM_getValue(CONFIG.codeManager.storageKey);
                this.data = savedData ? JSON.parse(savedData) : {
                    items: [],
                    lastUpdated: new Date().toISOString()
                };

                // 回收站
                const savedTrash = GM_getValue(CONFIG.codeManager.trashStorageKey);
                this.trash = savedTrash ? JSON.parse(savedTrash) : {
                    items: [],
                    lastUpdated: new Date().toISOString()
                };

                // 清理过期回收站条目
                this.cleanupTrash();

                this.initialized = true;
                return true;
            } catch (e) {
                console.error('番号库初始化失败:', e);
                this.data = { items: [], lastUpdated: new Date().toISOString() };
                this.trash = { items: [], lastUpdated: new Date().toISOString() };
                this.initialized = true;
                return false;
            }
        },

        // 保存数据
        save: function() {
            try {
                // 更新时间戳
                this.data.lastUpdated = new Date().toISOString();
                const dataString = JSON.stringify(this.data);
                GM_setValue(CONFIG.codeManager.storageKey, dataString);

                this.trash.lastUpdated = new Date().toISOString();
                GM_setValue(CONFIG.codeManager.trashStorageKey, JSON.stringify(this.trash));

                // 触发自定义事件
                const event = new CustomEvent('emh_library_updated', {
                    detail: {
                        type: 'library_update',
                        data: this.data
                    }
                });
                window.dispatchEvent(event);

                // 同步更新所有打开的标签页
                if (typeof GM_setValue !== 'undefined') {
                    // 使用时间戳作为更新标记
                    GM_setValue('emh_sync_timestamp', Date.now().toString());
                }

                return true;
            } catch (e) {
                console.error('保存番号库失败:', e);
                UTILS.showToast('保存番号库失败', 'error');
                return false;
            }
        },

        // 获取所有番号
        getAll: function() {
            if (!this.initialized) this.init();
            return [...this.data.items];
        },

        // 获取关注列表
        getFavorites: function() {
            if (!this.initialized) this.init();
            return this.data.items.filter(item => item.status === 'favorite');
        },

        // 获取已看记录
        getWatched: function() {
            if (!this.initialized) this.init();
            return this.data.items.filter(item => item.status === 'watched');
        },

        // 获取回收站内容
        getTrash: function() {
            if (!this.initialized) this.init();
            return [...this.trash.items];
        },

        // 添加新番号
        add: function(code, title = '', remarks = '') {
            if (!this.initialized) this.init();
            if (!code) return false;

            // 标准化番号格式(大写)
            const normalizedCode = code.toUpperCase();

            // 检查是否已存在
            if (this.getItem(normalizedCode)) {
                UTILS.showToast(`番号 ${normalizedCode} 已存在于番号库中`, 'warning');
                return false;
            }

            // 创建新条目
            const newItem = {
                code: normalizedCode,
                title: title || normalizedCode,
                status: 'unmarked',
                remarks: remarks || '',
                tags: [],
                createdDate: new Date().toISOString(),
                modifiedDate: new Date().toISOString()
            };

            this.data.items.unshift(newItem);
            this.save();
            return true;
        },

        // 删除番号(移至回收站)
        delete: function(code) {
            if (!this.initialized) this.init();
            if (!code) return false;

            // 标准化番号格式
            const normalizedCode = code.toUpperCase();

            // 查找条目
            const itemIndex = this.data.items.findIndex(item => item.code.toUpperCase() === normalizedCode);
            if (itemIndex === -1) return false; // 不存在

            // 添加删除日期并移至回收站
            const item = this.data.items[itemIndex];
            item.deleteDate = new Date().toISOString();

            // 从主库中删除
            this.data.items.splice(itemIndex, 1);

            // 添加到回收站
            this.trash.items.unshift(item);

            return this.save();
        },

        // 清理回收站中过期的条目
        cleanupTrash: function() {
            if (!this.trash || !this.trash.items || !this.trash.items.length) return;

            const now = new Date();
            const retentionPeriod = CONFIG.codeManager.trashRetentionDays * 24 * 60 * 60 * 1000; // 转换为毫秒

            this.trash.items = this.trash.items.filter(item => {
                const deleteDate = new Date(item.deleteDate);
                return (now - deleteDate) < retentionPeriod;
            });

            this.save();
        },

        // 获取单个番号的信息
        getItem: function(code) {
            if (!this.initialized) this.init();
            if (!code) return null;

            // 标准化番号格式(大写)
            const normalizedCode = code.toUpperCase();
            return this.data.items.find(item => item.code.toUpperCase() === normalizedCode);
        },

        // 获取番号状态
        getStatus: function(code) {
            const item = this.getItem(code);
            return item ? item.status : 'unmarked';
        },

        // 标记番号
        markItem: function(code, status, title = '', remark = '') {
            if (!this.initialized) this.init();
            if (!code) return false;

            // 标准化番号格式(大写)
            const normalizedCode = code.toUpperCase();

            // 检查状态是否有效
            if (!['unmarked', 'favorite', 'watched'].includes(status)) {
                status = 'unmarked';
            }

            // 检查是否已存在
            const existingIndex = this.data.items.findIndex(item => item.code.toUpperCase() === normalizedCode);

            if (existingIndex >= 0) {
                // 更新现有条目
                this.data.items[existingIndex].status = status;

                // 只在提供了新值时更新这些字段
                if (title) this.data.items[existingIndex].title = title;
                if (remark !== undefined) this.data.items[existingIndex].remark = remark;

                // 更新修改时间
                this.data.items[existingIndex].modifiedDate = new Date().toISOString();
            } else {
                // 创建新条目
                const newItem = {
                    code: normalizedCode,
                    title: title || normalizedCode,
                    status: status,
                    remark: remark || '',
                    tags: [],
                    createdDate: new Date().toISOString(),
                    modifiedDate: new Date().toISOString()
                };

                this.data.items.unshift(newItem); // 添加到数组开头
            }

            return this.save();
        },

        // 导出数据
        exportData: function(filter = 'all') {
            if (!this.initialized) this.init();

            let exportData = {
                version: "1.0",
                exportDate: new Date().toISOString(),
                filter: filter,
                items: []
            };

            // 确定导出的数据
            if (filter === 'trash') {
                exportData.items = [...this.trash.items];
            } else if (filter === 'all') {
                exportData.items = [...this.data.items];
            } else {
                exportData.items = this.data.items.filter(item => item.status === filter);
            }

            return exportData;
        },

        // 导入数据
        importData: function(data, mode = 'merge') {
            if (!this.initialized) this.init();

            try {
                // 验证数据格式
                if (!data.items || !Array.isArray(data.items)) {
                    throw new Error('导入的数据格式不正确');
                }

                if (mode === 'replace') {
                    // 替换模式:完全覆盖现有数据
                    this.data.items = data.items;
                } else if (mode === 'merge') {
                    // 合并模式:更新已有条目,添加新条目
                    for (const importedItem of data.items) {
                        if (!importedItem.code) continue;

                        const normalizedCode = importedItem.code.toUpperCase();
                        const existingIndex = this.data.items.findIndex(item =>
                            item.code.toUpperCase() === normalizedCode
                        );

                        if (existingIndex >= 0) {
                            // 更新现有条目
                            this.data.items[existingIndex] = {
                                ...this.data.items[existingIndex],
                                ...importedItem,
                                code: normalizedCode,
                                modifiedDate: new Date().toISOString()
                            };
                        } else {
                            // 添加新条目
                            const newItem = {
                                ...importedItem,
                                code: normalizedCode,
                                createdDate: importedItem.createdDate || new Date().toISOString(),
                                modifiedDate: new Date().toISOString()
                            };
                            this.data.items.unshift(newItem);
                        }
                    }
                }

                this.save();
                return {
                    success: true,
                    message: `成功导入 ${data.items.length} 个番号条目`
                };
            } catch (e) {
                console.error('导入番号数据失败:', e);
                return {
                    success: false,
                    message: '导入失败: ' + e.message
                };
            }
        }
    };

    // 获取搜索历史
    function getSearchHistory() {
        try {
            const history = localStorage.getItem(CONFIG.searchHistoryKey);
            return history ? JSON.parse(history) : [];
        } catch (e) {
            console.error('读取搜索历史失败:', e);
            return [];
        }
    }

    // 保存搜索历史
    function saveSearchHistory(term) {
        if (!term || term.trim() === '') return;

        try {
            let history = getSearchHistory();
            // 移除已存在的相同条目
            history = history.filter(item => item.toLowerCase() !== term.toLowerCase());
            // 添加到开头
            history.unshift(term);
            // 限制数量
            if (history.length > CONFIG.maxHistoryItems) {
                history = history.slice(0, CONFIG.maxHistoryItems);
            }
            localStorage.setItem(CONFIG.searchHistoryKey, JSON.stringify(history));
        } catch (e) {
            console.error('保存搜索历史失败:', e);
        }
    }

    // 清除搜索历史
    function clearSearchHistory() {
        try {
            localStorage.removeItem(CONFIG.searchHistoryKey);
            return true;
        } catch (e) {
            console.error('清除搜索历史失败:', e);
            return false;
        }
    }

    // 字幕管理模块
    const SUBTITLE_MANAGER = {
        // 获取字幕列表
        fetchSubtitles: (searchTerm) => {
            if (!searchTerm || searchTerm.trim() === "") {
                UTILS.showToast("请输入有效的字幕搜索关键字", "error");
                return;
            }
            const searchTermTrimmed = searchTerm.trim();

            UTILS.showToast(`正在为 "${searchTermTrimmed}" 获取字幕信息...`, "info");

            const buttonsToDisable = [
                document.getElementById('emh-getSubtitles'), // Main auto-detect button
                ...document.querySelectorAll(`.emh-subtitle-button-small[data-video-code]`), // All small per-item buttons
                document.getElementById('emh-draggable-custom-subtitle-btn') // Draggable custom search button
            ].filter(Boolean);

            buttonsToDisable.forEach(btn => {
                btn.disabled = true;
                if (btn.classList.contains('btn')) {
                    btn.classList.add('btn-disabled');
                }
            });

            const apiUrl = `${CONFIG.subtitleApiUrl}?name=${encodeURIComponent(searchTermTrimmed)}`;

            const reEnableButtons = () => {
                buttonsToDisable.forEach(btn => {
                    btn.disabled = false;
                    if (btn.classList.contains('btn')) {
                        btn.classList.remove('btn-disabled');
                    }
                });
            };

            const handleResponse = (responseText) => {
                reEnableButtons();
                try {
                    const data = JSON.parse(responseText);
                    SUBTITLE_MANAGER.createSubtitleModal(data, searchTermTrimmed); // Pass searchTerm to modal
                    if (data.data && data.data.length > 0) {
                        UTILS.showToast(`"${searchTermTrimmed}" 的字幕信息获取成功`, "success");
                    } else {
                        UTILS.showToast(`未找到 "${searchTermTrimmed}" 的字幕`, "info");
                    }
                } catch (e) {
                    console.error("解析字幕数据时出错:", e);
                    UTILS.showToast("解析字幕数据时出错", "error");
                    SUBTITLE_MANAGER.createSubtitleModal(null, searchTermTrimmed);
                }
            };

            const handleError = (error) => {
                reEnableButtons();
                console.error("获取字幕时出错:", error);
                UTILS.showToast("获取字幕时出错", "error");
                SUBTITLE_MANAGER.createSubtitleModal(null, searchTermTrimmed);
            };

            // 设置超时处理
            let timeoutId = setTimeout(() => {
                reEnableButtons();
                UTILS.showToast("获取字幕超时", "error");
                SUBTITLE_MANAGER.createSubtitleModal(null, searchTermTrimmed);

                // 清理可能的JSONP回调
                if (window.emhJsonpCallback) {
                    delete window.emhJsonpCallback;
                }

                // 清理可能添加的script标签
                const jsonpScript = document.getElementById('emh-jsonp-script');
                if (jsonpScript) {
                    jsonpScript.remove();
                }
            }, 15000);

            if (typeof GM_xmlhttpRequest !== 'undefined') {
                // 使用油猴API,它能自动绕过CORS限制
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: apiUrl,
                    timeout: 15000,
                    onload: (response) => {
                        clearTimeout(timeoutId);
                        handleResponse(response.responseText);
                    },
                    onerror: (error) => {
                        clearTimeout(timeoutId);
                        handleError(error);
                    },
                    ontimeout: () => {
                        clearTimeout(timeoutId);
                        reEnableButtons();
                        UTILS.showToast("获取字幕超时", "error");
                        SUBTITLE_MANAGER.createSubtitleModal(null, searchTermTrimmed);
                    }
                });
            } else {
                // 尝试使用CORS代理
                const corsProxies = [
                    `https://api.allorigins.win/raw?url=${encodeURIComponent(apiUrl)}`,
                    `https://corsproxy.io/?${encodeURIComponent(apiUrl)}`,
                    `https://cors-anywhere.herokuapp.com/${apiUrl}`
                ];

                // 创建一个Promise数组,对每个代理进行尝试
                const fetchRequests = corsProxies.map(proxyUrl => {
                    return fetch(proxyUrl, {
                        method: 'GET',
                        headers: {
                            'Accept': 'application/json',
                            'X-Requested-With': 'XMLHttpRequest'
                        }
                    })
                    .then(response => {
                        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                        return response.text();
                    });
                });

                // 使用Promise.any来获取第一个成功的结果
                Promise.any(fetchRequests)
                    .then(text => {
                        clearTimeout(timeoutId);
                        if (!text) {
                            handleResponse('{"data": []}');
                        } else {
                            handleResponse(text);
                        }
                    })
                    .catch(error => {
                        // 所有代理都失败时,尝试使用JSONP方法
                        console.warn("所有CORS代理失败,尝试JSONP方法");

                        // 清理之前可能存在的回调和脚本
                        if (window.emhJsonpCallback) {
                            delete window.emhJsonpCallback;
                        }
                        const oldScript = document.getElementById('emh-jsonp-script');
                        if (oldScript) {
                            oldScript.remove();
                        }

                        // 创建JSONP回调
                        window.emhJsonpCallback = function(data) {
                            clearTimeout(timeoutId);
                            handleResponse(JSON.stringify(data));
                            delete window.emhJsonpCallback;
                        };

                        // 尝试直接请求,某些服务器可能支持JSONP
                        const jsonpUrl = `${CONFIG.subtitleApiUrl}?name=${encodeURIComponent(searchTermTrimmed)}&callback=emhJsonpCallback`;
                        const script = document.createElement('script');
                        script.id = 'emh-jsonp-script';
                        script.src = jsonpUrl;
                        script.onerror = () => {
                            // JSONP失败时,创建一个空结果并处理
                            if (!document.getElementById('emh-subtitle-modal')) {
                                clearTimeout(timeoutId);
                                handleResponse('{"data": []}');
                                UTILS.showToast("无法连接到字幕API,请稍后重试", "error");
                            }
                        };
                        document.head.appendChild(script);
                    });
            }
        },

        // 创建字幕模态框
        createSubtitleModal: (subtitleContent = null, videoCode = null) => {
            const existingModal = document.getElementById('emh-subtitle-modal');
            if (existingModal) existingModal.remove();

            const modal = document.createElement('div');
            modal.id = 'emh-subtitle-modal';
            modal.className = 'emh-modal';

            const modalContent = document.createElement('div');
            modalContent.className = 'emh-modal-content';

            const modalHeader = document.createElement('div');
            modalHeader.className = 'emh-modal-header';
            modalHeader.innerHTML = `<h3>字幕列表 (搜索关键字: ${videoCode || '未知'})</h3><span class="emh-modal-close">&times;</span>`;
            modalContent.appendChild(modalHeader);

            const modalBody = document.createElement('div');
            modalBody.className = 'emh-modal-body';

            if (subtitleContent && subtitleContent.data && subtitleContent.data.length > 0) {
                const list = document.createElement('ul');
                list.className = 'emh-subtitle-list';

                // 调试用:输出字幕数据结构
                console.log("字幕数据:", subtitleContent.data);

                subtitleContent.data.forEach((subtitle) => {
                    SUBTITLE_MANAGER.createSubtitleItem(list, subtitle, videoCode);
                });
                modalBody.appendChild(list);
            } else {
                modalBody.innerHTML = `<p class="emh-no-subtitle-message">未找到 "${videoCode}" 的相关字幕</p>`;
            }

            modalContent.appendChild(modalBody);
            modal.appendChild(modalContent);
            document.body.appendChild(modal);

            modal.querySelector('.emh-modal-close').onclick = () => modal.remove();
            modal.onclick = (event) => {
                if (event.target === modal) {
                    modal.remove();
                }
            };
            setTimeout(() => modal.classList.add('show'), 10);
            return modal;
        },

        // 创建单个字幕项
        createSubtitleItem: (listElement, subtitle, videoCode) => {
            const item = document.createElement('li');
            item.className = 'emh-subtitle-item';

            // 获取原始文件名(直接从API返回)
            let originalFilename = subtitle.name || '';

            // 确保文件名有扩展名
            if (originalFilename && !originalFilename.toLowerCase().endsWith(`.${subtitle.ext}`)) {
                originalFilename = `${originalFilename}.${subtitle.ext || 'srt'}`;
            } else if (!originalFilename) {
                originalFilename = `subtitle.${subtitle.ext || 'srt'}`;
            }

            // 清理文件名中的非法字符
            if (CONFIG.subtitleFilenameOptions.removeIllegalChars) {
                originalFilename = UTILS.sanitizeFilename(originalFilename);
            }

            // 保存最终的下载文件名
            const downloadFilename = originalFilename;

            item.innerHTML = `
                <div class="emh-subtitle-info">
                    <h4>${subtitle.name || '未命名字幕'}</h4>
                    <p>格式: ${subtitle.ext || '未知'} | 语言: ${subtitle.languages?.length ? subtitle.languages.join(', ') : '未知'} ${subtitle.extra_name ? '| 来源: ' + subtitle.extra_name : ''}</p>
                </div>
                <div class="emh-subtitle-actions">
                    ${subtitle.url ? `
                        <button class="btn my-btn-primary emh-download-subtitle-btn" data-url="${subtitle.url}" data-filename="${downloadFilename}">缓存下载</button>
                        <a href="${subtitle.url}" target="_blank" class="btn btn-outline" download="${downloadFilename}">直接下载</a>
                    ` : ''}
                </div>
            `;
            listElement.appendChild(item);
            return item;
        },

        // 下载字幕文件
        downloadSubtitle: async (url, defaultFilename) => {
            try {
                UTILS.showToast('正在获取字幕文件...', 'info');

                // 处理可能的跨域问题
                if (typeof GM_xmlhttpRequest !== 'undefined') {
                    // 使用GM_xmlhttpRequest获取字幕内容(可绕过跨域限制)
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: url,
                        responseType: 'blob',
                        onload: function(response) {
                            if (response.status >= 200 && response.status < 300) {
                                const blob = response.response;
                                SUBTITLE_MANAGER.processSubtitleDownload(blob, defaultFilename);
                            } else {
                                UTILS.showToast(`获取字幕失败: ${response.status}`, 'error');
                            }
                        },
                        onerror: function(error) {
                            console.error('字幕下载失败:', error);
                            UTILS.showToast('字幕下载失败,请尝试直接下载', 'error');
                        }
                    });
                } else {
                    // 使用标准fetch API
                    try {
                        const corsProxies = [
                            url, // 先尝试直接访问
                            `https://corsproxy.io/?${encodeURIComponent(url)}`,
                            `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`
                        ];

                        // 尝试所有代理URL
                        let success = false;
                        for (const proxyUrl of corsProxies) {
                            try {
                                const response = await fetch(proxyUrl, {
                                    method: 'GET',
                                    headers: {
                                        'Accept': 'text/plain, application/octet-stream'
                                    }
                                });

                                if (response.ok) {
                                    const blob = await response.blob();
                                    SUBTITLE_MANAGER.processSubtitleDownload(blob, defaultFilename);
                                    success = true;
                                    break;
                                }
                            } catch (err) {
                                console.warn(`尝试使用代理 ${proxyUrl} 失败:`, err);
                                // 继续尝试下一个代理
                            }
                        }

                        if (!success) {
                            throw new Error('所有代理都失败');
                        }
                    } catch (error) {
                        console.error('字幕下载失败:', error);
                        UTILS.showToast('字幕下载失败,请尝试直接下载', 'error');

                        // 如果所有方法都失败,尝试打开新标签页直接下载
                        if (confirm('自动下载失败,是否尝试在新标签页中直接打开字幕链接?')) {
                            window.open(url, '_blank');
                        }
                    }
                }
            } catch (error) {
                console.error('字幕下载处理失败:', error);
                UTILS.showToast('字幕下载处理失败', 'error');
            }
        },

        // 处理字幕下载的通用流程
        processSubtitleDownload: (blob, defaultFilename) => {
            try {
                // 创建一个临时URL
                const objectUrl = URL.createObjectURL(blob);

                // 直接使用提供的文件名,无需用户确认
                const downloadLink = document.createElement('a');
                downloadLink.href = objectUrl;
                downloadLink.download = defaultFilename;
                downloadLink.style.display = 'none';

                // 添加到文档中并点击
                document.body.appendChild(downloadLink);
                downloadLink.click();

                // 清理
                setTimeout(() => {
                    document.body.removeChild(downloadLink);
                    URL.revokeObjectURL(objectUrl);
                }, 100);

                UTILS.showToast(`字幕文件 "${defaultFilename}" 下载已开始`, 'success');
            } catch (error) {
                console.error('字幕下载处理失败:', error);
                UTILS.showToast('字幕下载处理失败', 'error');
            }
        }
    };

    const UTILS = {
        getDomain: () => document.domain,

        getCodeFromUrl: (url) => {
            const match = url.match(/\/([a-z0-9-]+)\/?$/i);
            return match ? match[1] : null;
        },

        getPosterImage: () => {
            const videoContainer = document.querySelector('.video-player-container, .player-container, #player');
            if (videoContainer) {
                const posterElem = videoContainer.querySelector('.plyr__poster, [poster]');
                if (posterElem) {
                    if (posterElem.hasAttribute('poster')) {
                        return posterElem.getAttribute('poster');
                    }
                    const backgroundImageStyle = window.getComputedStyle(posterElem).getPropertyValue('background-image');
                    const matches = /url\("(.+)"\)/.exec(backgroundImageStyle);
                    return matches ? matches[1] : null;
                }
            }
            const metaPoster = document.querySelector('meta[property="og:image"], meta[name="twitter:image"]');
            return metaPoster ? metaPoster.content : null;
        },

        getActressNames: () => {
            const actressLinks = document.querySelectorAll('.video-info .info-item a[href*="/actress/"], .models-list .model a, .attributes a[href*="/star/"]');
            return Array.from(actressLinks)
                .map(link => link.getAttribute('title') || link.textContent.trim())
                .filter(name => name)
                .filter((value, index, self) => self.indexOf(value) === index)
                .join(',');
        },

        buildApiUrl: (domain, options) => {
            const queryParams = Object.keys(options.query || {})
                .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(options.query[key])}`)
                .join("&");
            const query = queryParams.length > 0 ? `?${queryParams}` : "";
            return `http://${domain}${options.path || ''}${query}`;
        },

        showToast: (message, type = 'info') => {
            let toastContainer = document.getElementById('custom-toast-container');
            if (!toastContainer) {
                toastContainer = document.createElement('div');
                toastContainer.id = 'custom-toast-container';
                document.body.appendChild(toastContainer);
            }
            const toast = document.createElement('div');
            toast.className = `custom-toast custom-toast-${type}`;
            toast.textContent = message;
            toastContainer.appendChild(toast);
            setTimeout(() => toast.classList.add('show'), 10);
            setTimeout(() => {
                toast.classList.remove('show');
                toast.addEventListener('transitionend', () => toast.remove());
            }, 3000);
        },

        copyToClipboard: async (text) => {
            if (!text) {
                UTILS.showToast("没有可复制的内容", "error");
                return false;
            }
            try {
                await navigator.clipboard.writeText(text);
                UTILS.showToast("内容已成功复制到剪贴板", "success");
                return true;
            } catch (error) {
                UTILS.showToast("复制失败,请检查浏览器权限", "error");
                console.error("Copy error:", error);
                try {
                    const textArea = document.createElement("textarea");
                    textArea.value = text;
                    textArea.style.position = "fixed";
                    textArea.style.top = "0";
                    textArea.style.left = "0";
                    textArea.style.opacity = "0";
                    document.body.appendChild(textArea);
                    textArea.focus();
                    textArea.select();
                    const successful = document.execCommand('copy');
                    document.body.removeChild(textArea);
                    if (successful) {
                        UTILS.showToast("内容已复制 (fallback)", "success");
                        return true;
                    } else {
                        throw new Error('execCommand failed');
                    }
                } catch (fallbackError) {
                    UTILS.showToast("复制到剪贴板时出错", "error");
                    console.error("Fallback copy error:", fallbackError);
                    return false;
                }
            }
        },

        addActionButtons: (container, videoUrl, videoCode) => {
            const buttonContainer = document.createElement("div");
            buttonContainer.className = "emh-action-buttons";

            // Add code status indicator if we have a valid code
            if (videoCode) {
                createCodeStatusIndicator(buttonContainer, videoCode);

                // Auto-add to library if enabled
                if (CONFIG.codeManager.autoAddDetected && CODE_LIBRARY.initialized) {
                    const existingItem = CODE_LIBRARY.getItem(videoCode);
                    if (!existingItem) {
                        // Get title from page if possible
                        let title = '';
                        const titleElement = document.querySelector("h4.title, h1.post-title, .video-info h4, meta[property='og:title']");
                        if (titleElement) {
                            title = titleElement.content || titleElement.innerText.trim();
                            if (title.includes(videoCode)) {
                                title = title.split(videoCode).pop().trim().replace(/^[-–—\s]+/, '');
                            }
                        }

                        // Add to library with "unmarked" status
                        CODE_LIBRARY.markItem(videoCode, 'unmarked', title);
                    }
                }
            }

            const copyButton = document.createElement("button");
            copyButton.id = "emh-copyLink";
            copyButton.className = "btn my-btn-primary";
            copyButton.innerHTML = "<span>📋 复制链接</span>";
            copyButton.title = videoUrl || "无有效视频链接";
            copyButton.dataset.videoUrl = videoUrl || '';
            buttonContainer.appendChild(copyButton);

            const sendButton = document.createElement("button");
            sendButton.id = "emh-sendData";
            sendButton.className = "btn my-btn-danger";
            sendButton.innerHTML = "<span>💾 发送到服务器</span>";
            sendButton.dataset.videoUrl = videoUrl || '';
            sendButton.dataset.videoCode = videoCode || '';
            buttonContainer.appendChild(sendButton);

            const subtitleButton = document.createElement("button");
            subtitleButton.id = "emh-getSubtitles"; // This is for auto-detected code
            subtitleButton.className = "btn my-btn-success";
            subtitleButton.innerHTML = "<span>📄 获取字幕</span>";
            subtitleButton.dataset.videoCode = videoCode || '';
            buttonContainer.appendChild(subtitleButton);

            // Add code manager button
            const codeManagerButton = document.createElement("button");
            codeManagerButton.id = "emh-code-manager-btn";
            codeManagerButton.className = "btn btn-info";
            codeManagerButton.innerHTML = "<span>📋 番号库</span>";
            codeManagerButton.title = "打开番号管理面板";
            codeManagerButton.addEventListener('click', () => {
                if (window.CodeManagerPanel) {
                    window.CodeManagerPanel.togglePanel();
                }
            });
            buttonContainer.appendChild(codeManagerButton);

            container.appendChild(buttonContainer);
            return buttonContainer;
        },

        // 注意:下面的字幕相关函数已移至SUBTITLE_MANAGER模块,保留API兼容性
        createSubtitleModal: (subtitleContent, videoCode) => {
            return SUBTITLE_MANAGER.createSubtitleModal(subtitleContent, videoCode);
        },

        fetchSubtitles: (searchTerm) => {
            return SUBTITLE_MANAGER.fetchSubtitles(searchTerm);
        },

        downloadSubtitle: (url, defaultFilename) => {
            return SUBTITLE_MANAGER.downloadSubtitle(url, defaultFilename);
        },

        createDraggableSubtitleButton: () => {
            const button = document.createElement('button');
            button.id = 'emh-draggable-custom-subtitle-btn'; // New ID
            button.className = 'btn btn-info emh-draggable-btn';
            button.innerHTML = '<span>🔍 高级搜索</span>'; // Updated text
            button.title = '拖动我 | 点击打开高级字幕搜索';

            let isDragging = false;
            let offsetX, offsetY;
            let hasDragged = false;
            let startX, startY;

            button.onmousedown = (e) => {
                if (e.button !== 0) return;
                e.preventDefault();

                isDragging = true;
                hasDragged = false;
                button.style.cursor = 'grabbing';

                startX = e.clientX;
                startY = e.clientY;

                const rect = button.getBoundingClientRect();
                offsetX = e.clientX - rect.left;
                offsetY = e.clientY - rect.top;
                button.style.position = 'fixed';

                document.onmousemove = (moveEvent) => {
                    if (!isDragging) return;
                    if (Math.abs(moveEvent.clientX - startX) > 3 || Math.abs(moveEvent.clientY - startY) > 3) {
                        hasDragged = true;
                    }
                    let newX = moveEvent.clientX - offsetX;
                    let newY = moveEvent.clientY - offsetY;
                    const viewportWidth = window.innerWidth;
                    const viewportHeight = window.innerHeight;
                    const buttonWidth = button.offsetWidth;
                    const buttonHeight = button.offsetHeight;

                    if (newX < 0) newX = 0;
                    if (newY < 0) newY = 0;
                    if (newX + buttonWidth > viewportWidth) newX = viewportWidth - buttonWidth;
                    if (newY + buttonHeight > viewportHeight) newY = viewportHeight - buttonHeight;

                    button.style.left = `${newX}px`;
                    button.style.top = `${newY}px`;
                    button.style.bottom = 'auto';
                    button.style.right = 'auto';
                };

                document.onmouseup = () => {
                    if (!isDragging) return;
                    isDragging = false;
                    button.style.cursor = 'grab';
                    document.onmousemove = null;
                    document.onmouseup = null;

                    if (!hasDragged) { // Click action
                        // 使用高级搜索模态框替代简单的 prompt
                        const defaultSearchTerm = EMH_currentVideoCode || "";
                        UTILS.createSearchModal(defaultSearchTerm);
                    }
                };
            };
            button.onclick = (e) => { // Prevent click if drag occurred
                if (hasDragged) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            };

            document.body.appendChild(button);
            if (!button.style.left && !button.style.top) {
                 button.style.position = 'fixed';
                 button.style.bottom = '70px';
                 button.style.right = '20px';
            }
            return button;
        },

        // 创建高级搜索模态框
        createSearchModal: (defaultSearchTerm = '') => {
            // 移除已存在的模态框
            const existingModal = document.getElementById('emh-search-modal');
            if (existingModal) existingModal.remove();

            // 创建模态框基本结构
            const modal = document.createElement('div');
            modal.id = 'emh-search-modal';
            modal.className = 'emh-modal';

            const modalContent = document.createElement('div');
            modalContent.className = 'emh-modal-content emh-search-modal-content';

            // 创建模态框头部
            const modalHeader = document.createElement('div');
            modalHeader.className = 'emh-modal-header';
            modalHeader.innerHTML = `
                <h3>高级字幕搜索</h3>
                <span class="emh-modal-close">&times;</span>
            `;

            // 创建模态框主体
            const modalBody = document.createElement('div');
            modalBody.className = 'emh-modal-body';

            // 搜索表单
            const searchForm = document.createElement('form');
            searchForm.className = 'emh-search-form';
            searchForm.addEventListener('submit', (e) => {
                e.preventDefault();
                const searchInput = document.getElementById('emh-subtitle-search-input');
                const searchTerm = searchInput.value.trim();
                if (searchTerm) {
                    saveSearchHistory(searchTerm);
                    modal.remove();
                    UTILS.fetchSubtitles(searchTerm);
                }
            });

            // 搜索输入区域
            const searchInputGroup = document.createElement('div');
            searchInputGroup.className = 'emh-search-input-group';
            searchInputGroup.innerHTML = `
                <div class="emh-input-wrapper">
                    <input type="text" id="emh-subtitle-search-input" class="emh-search-input"
                        placeholder="输入字幕关键词..." value="${defaultSearchTerm}" autofocus>
                    <button type="button" class="emh-search-clear-btn" title="清除输入">&times;</button>
                </div>
                <button type="submit" class="emh-search-btn">
                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <circle cx="11" cy="11" r="8"></circle>
                        <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                    </svg>
                    搜索
                </button>
            `;

            searchForm.appendChild(searchInputGroup);

            // 搜索历史
            const historySection = document.createElement('div');
            historySection.className = 'emh-search-history-section';

            const historyHeader = document.createElement('div');
            historyHeader.className = 'emh-search-history-header';
            historyHeader.innerHTML = `
                <h4>搜索历史</h4>
                <button type="button" class="emh-clear-history-btn">清除历史</button>
            `;

            const historyList = document.createElement('div');
            historyList.className = 'emh-search-history-list';
            UTILS.updateHistoryList(historyList);

            historySection.appendChild(historyHeader);
            historySection.appendChild(historyList);

            // 热门搜索(可选功能 - 如果有API支持)
            const trendingSection = document.createElement('div');
            trendingSection.className = 'emh-trending-section';
            trendingSection.innerHTML = `
                <h4>热门推荐</h4>
                <div class="emh-trending-tags">
                    <span class="emh-trending-tag">中文字幕</span>
                    <span class="emh-trending-tag">4K高清</span>
                    <span class="emh-trending-tag">双语字幕</span>
                    <span class="emh-trending-tag">特效字幕</span>
                    <span class="emh-trending-tag">日语字幕</span>
                </div>
            `;

            // 添加设置选项
            const settingsSection = document.createElement('div');
            settingsSection.className = 'emh-settings-section';
            settingsSection.innerHTML = `
                <h4>设置选项</h4>
                <div class="emh-setting-item">
                    <label for="emh-original-name-setting" class="emh-setting-label">
                        <span>使用原始文件名下载字幕</span>
                        <input type="checkbox" id="emh-original-name-setting" class="emh-toggle-checkbox" ${CONFIG.subtitleFilenameOptions.useOriginalName ? 'checked' : ''} disabled>
                        <span class="emh-toggle-switch"></span>
                    </label>
                </div>
            `;

            // 添加到主体
            modalBody.appendChild(searchForm);
            modalBody.appendChild(historySection);
            modalBody.appendChild(trendingSection);
            modalBody.appendChild(settingsSection);

            // 添加到模态框
            modalContent.appendChild(modalHeader);
            modalContent.appendChild(modalBody);
            modal.appendChild(modalContent);

            // 添加到文档
            document.body.appendChild(modal);

            // 绑定事件
            UTILS.setupSearchModalEvents(modal);

            // 显示模态框
            setTimeout(() => modal.classList.add('show'), 10);

            return modal;
        },

        // 更新历史列表
        updateHistoryList: (historyList) => {
            const history = getSearchHistory();

            if (history.length === 0) {
                historyList.innerHTML = '<div class="emh-empty-history">暂无搜索历史</div>';
                return;
            }

            historyList.innerHTML = '';
            history.forEach(term => {
                const historyItem = document.createElement('div');
                historyItem.className = 'emh-history-item';
                historyItem.innerHTML = `
                    <span class="emh-history-text">${term}</span>
                    <button class="emh-history-use-btn" data-term="${term}" title="使用该关键词">
                        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <polyline points="9 10 4 15 9 20"></polyline>
                            <path d="M20 4v7a4 4 0 0 1-4 4H4"></path>
                        </svg>
                    </button>
                `;
                historyList.appendChild(historyItem);
            });
        },

        // 设置搜索模态框事件
        setupSearchModalEvents: (modal) => {
            // 关闭按钮
            const closeBtn = modal.querySelector('.emh-modal-close');
            if (closeBtn) {
                closeBtn.addEventListener('click', () => {
                    modal.classList.remove('show');
                    setTimeout(() => modal.remove(), CONFIG.animationDuration);
                });
            }

            // 点击模态框背景关闭
            modal.addEventListener('click', (e) => {
                if (e.target === modal) {
                    modal.classList.remove('show');
                    setTimeout(() => modal.remove(), CONFIG.animationDuration);
                }
            });

            // 清除输入按钮
            const clearInputBtn = modal.querySelector('.emh-search-clear-btn');
            const searchInput = modal.querySelector('#emh-subtitle-search-input');

            if (clearInputBtn && searchInput) {
                clearInputBtn.addEventListener('click', () => {
                    searchInput.value = '';
                    searchInput.focus();
                });

                // 根据输入框内容显示/隐藏清除按钮
                searchInput.addEventListener('input', () => {
                    if (searchInput.value) {
                        clearInputBtn.style.visibility = 'visible';
                    } else {
                        clearInputBtn.style.visibility = 'hidden';
                    }
                });

                // 初始状态
                if (searchInput.value) {
                    clearInputBtn.style.visibility = 'visible';
                } else {
                    clearInputBtn.style.visibility = 'hidden';
                }
            }

            // 清除历史按钮
            const clearHistoryBtn = modal.querySelector('.emh-clear-history-btn');
            if (clearHistoryBtn) {
                clearHistoryBtn.addEventListener('click', () => {
                    if (confirm('确定要清除所有搜索历史吗?')) {
                        const success = clearSearchHistory();
                        if (success) {
                            const historyList = modal.querySelector('.emh-search-history-list');
                            if (historyList) {
                                UTILS.updateHistoryList(historyList);
                            }
                            UTILS.showToast('搜索历史已清除', 'success');
                        } else {
                            UTILS.showToast('清除历史失败', 'error');
                        }
                    }
                });
            }

            // 历史项使用按钮
            modal.querySelectorAll('.emh-history-use-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    const term = btn.getAttribute('data-term');
                    if (term && searchInput) {
                        searchInput.value = term;
                        searchInput.focus();
                    }
                });
            });

            // 热门标签点击
            modal.querySelectorAll('.emh-trending-tag').forEach(tag => {
                tag.addEventListener('click', () => {
                    if (searchInput) {
                        searchInput.value = tag.textContent;
                        searchInput.focus();
                    }
                });
            });
        },

        // 创建悬浮搜索按钮
        createFloatingSearchButton: () => {
            const button = document.createElement('button');
            button.id = 'emh-floating-search-btn';
            button.className = 'emh-floating-btn';
            button.title = '高级字幕搜索';
            button.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="11" cy="11" r="8"></circle>
                    <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                </svg>
            `;

            button.addEventListener('click', () => {
                // 使用当前视频代码作为默认搜索词
                let defaultSearchTerm = '';
                if (typeof EMH_currentVideoCode !== 'undefined' && EMH_currentVideoCode) {
                    defaultSearchTerm = EMH_currentVideoCode;
                }
                UTILS.createSearchModal(defaultSearchTerm);
            });

            document.body.appendChild(button);
            return button;
        },
        // 清理文件名,移除非法字符
        sanitizeFilename: (filename) => {
            if (!filename) return '字幕';

            // 移除Windows/通用文件系统中的非法字符
            let sanitized = filename.replace(/[<>:"\/\\|?*\x00-\x1F]/g, '');

            // 替换连续空格为单个空格
            sanitized = sanitized.replace(/\s+/g, ' ').trim();

            // 如果为空,返回默认名称
            return sanitized || '字幕';
        },

        // 下载字幕文件(先缓存再下载)
        downloadSubtitle: async (url, defaultFilename) => {
            try {
                UTILS.showToast('正在获取字幕文件...', 'info');

                // 处理可能的跨域问题
                if (typeof GM_xmlhttpRequest !== 'undefined') {
                    // 使用GM_xmlhttpRequest获取字幕内容(可绕过跨域限制)
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: url,
                        responseType: 'blob',
                        onload: function(response) {
                            if (response.status >= 200 && response.status < 300) {
                                const blob = response.response;
                                SUBTITLE_MANAGER.processSubtitleDownload(blob, defaultFilename);
                            } else {
                                UTILS.showToast(`获取字幕失败: ${response.status}`, 'error');
                            }
                        },
                        onerror: function(error) {
                            console.error('字幕下载失败:', error);
                            UTILS.showToast('字幕下载失败,请尝试直接下载', 'error');
                        }
                    });
                } else {
                    // 使用标准fetch API
                    try {
                        const corsProxies = [
                            url, // 先尝试直接访问
                            `https://corsproxy.io/?${encodeURIComponent(url)}`,
                            `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`
                        ];

                        // 尝试所有代理URL
                        let success = false;
                        for (const proxyUrl of corsProxies) {
                            try {
                                const response = await fetch(proxyUrl, {
                                    method: 'GET',
                                    headers: {
                                        'Accept': 'text/plain, application/octet-stream'
                                    }
                                });

                                if (response.ok) {
                                    const blob = await response.blob();
                                    SUBTITLE_MANAGER.processSubtitleDownload(blob, defaultFilename);
                                    success = true;
                                    break;
                                }
                            } catch (err) {
                                console.warn(`尝试使用代理 ${proxyUrl} 失败:`, err);
                                // 继续尝试下一个代理
                            }
                        }

                        if (!success) {
                            throw new Error('所有代理都失败');
                        }
                    } catch (error) {
                        console.error('字幕下载失败:', error);
                        UTILS.showToast('字幕下载失败,请尝试直接下载', 'error');

                        // 如果所有方法都失败,尝试打开新标签页直接下载
                        if (confirm('自动下载失败,是否尝试在新标签页中直接打开字幕链接?')) {
                            window.open(url, '_blank');
                        }
                    }
                }
            } catch (error) {
                console.error('字幕下载处理失败:', error);
                UTILS.showToast('字幕下载失败,请尝试直接下载', 'error');
            }
        },

        // 处理字幕下载的通用流程
        processSubtitleDownload: (blob, defaultFilename) => {
            try {
                // 创建一个临时URL
                const objectUrl = URL.createObjectURL(blob);

                // 直接使用提供的文件名,无需用户确认
                const downloadLink = document.createElement('a');
                downloadLink.href = objectUrl;
                downloadLink.download = defaultFilename;
                downloadLink.style.display = 'none';

                // 添加到文档中并点击
                document.body.appendChild(downloadLink);
                downloadLink.click();

                // 清理
                setTimeout(() => {
                    document.body.removeChild(downloadLink);
                    URL.revokeObjectURL(objectUrl);
                }, 100);

                UTILS.showToast(`字幕文件 "${defaultFilename}" 下载已开始`, 'success');
            } catch (error) {
                console.error('字幕下载处理失败:', error);
                UTILS.showToast('字幕下载处理失败', 'error');
            }
        },
    };

    const SITE_HANDLERS = {
        javtxt: {
            isMatch: () => UTILS.getDomain().includes('javtxt') || UTILS.getDomain().includes('tokyolib') || UTILS.getDomain().includes('javtext'),
            targetSelector: 'body > div.main > div.info > div.attributes > dl > dt:nth-child(2)',
            process: (targetElement) => {
                if (!targetElement) {
                    console.error("JavTXT: Target element not found.");
                    return;
                }
                const config = {
                    links: [
                        { urlTemplate: 'https://123av.com/zh/v/$code', target: '_blank', displayText: '123av' },
                        { urlTemplate: 'https://jable.tv/videos/$code/', target: '_blank', displayText: 'Jable' }
                    ]
                };
                const cleanedCode = extractCode(targetElement.innerText);
                if (!cleanedCode) {
                    console.error("JavTXT: Failed to extract code.");
                    return;
                }
                updateGlobalVideoCode(cleanedCode);

                // 创建状态指示器容器
                const statusContainer = document.createElement('div');
                statusContainer.className = 'emh-code-status-container';
                statusContainer.style.display = 'inline-block';
                statusContainer.style.marginLeft = '10px';
                createCodeStatusIndicator(statusContainer, cleanedCode);

                // 将状态指示器添加到番号文本后面
                targetElement.appendChild(statusContainer);

                const controlsContainer = document.createElement('div');
                controlsContainer.className = 'emh-controls-container';
                config.links.forEach(linkConfig => {
                    const link = document.createElement('a');
                    link.href = linkConfig.urlTemplate.replace('$code', cleanedCode);
                    link.target = linkConfig.target;
                    link.className = 'btn btn-outline';
                    link.innerText = linkConfig.displayText;
                    controlsContainer.appendChild(link);
                });
                const subtitleButton = document.createElement('button');
                subtitleButton.id = 'emh-getSubtitles';
                subtitleButton.className = 'btn my-btn-success';
                subtitleButton.innerHTML = '<span>📄 获取字幕</span>';
                subtitleButton.dataset.videoCode = cleanedCode;
                controlsContainer.appendChild(subtitleButton);
                targetElement.parentNode.insertBefore(controlsContainer, targetElement.nextSibling);
            }
        },
        javgg: {
            isMatch: () => UTILS.getDomain().includes('javgg'),
            targetSelector: 'article.item.movies .data, h1.post-title, .videoinfo .meta',
            process: (targetElement) => {

                if (document.querySelector("article.item.movies")) {
                    const sidebar = document.querySelector("#contenedor > div > div.sidebar.right.scrolling");
                    if (sidebar) sidebar.remove();

                    const linkProviders = [
                        { code: "njav", url: CONFIG.alternateUrl.av123 + "$p", target: "_blank" },
                        { code: "jable", url: CONFIG.alternateUrl.jable+"$p/", target: "_blank" },
                        { code: "1cili", url: CONFIG.alternateUrl.cili1+"$p", target: "_blank" }
                    ];
                    document.querySelectorAll("article.item.movies").forEach(entry => {
                        const dataElement = entry.querySelector(".data");
                        const anchorTag = dataElement ? dataElement.querySelector("h3 a") : null;
                        if (anchorTag) {
                            const videoCode = anchorTag.textContent.trim();
                            if (!videoCode) return;
                            if (dataElement.querySelector('.emh-javgg-controls')) return;

                            // 创建状态指示器容器
                            const statusContainer = document.createElement('div');
                            statusContainer.className = 'emh-code-status-container';
                            statusContainer.style.display = 'inline-block';
                            statusContainer.style.marginLeft = '10px';
                            createCodeStatusIndicator(statusContainer, videoCode);

                            // 将状态指示器添加到标题后面
                            anchorTag.parentNode.appendChild(statusContainer);

                            const controlsDiv = document.createElement('div');
                            controlsDiv.className = 'emh-javgg-controls';
                            linkProviders.forEach(provider => {
                                const newAnchorTag = document.createElement("a");
                                newAnchorTag.href = provider.url.replace("$p", videoCode);
                                newAnchorTag.target = provider.target;
                                newAnchorTag.className = 'btn btn-outline';
                                newAnchorTag.style.padding = '4px 8px';
                                newAnchorTag.style.fontSize = '12px';
                                newAnchorTag.textContent = provider.code;
                                controlsDiv.appendChild(newAnchorTag);
                            });
                            const subtitleButton = document.createElement('button');
                            subtitleButton.className = 'btn my-btn-success emh-subtitle-button-small';
                            subtitleButton.style.padding = '4px 8px';
                            subtitleButton.style.fontSize = '12px';
                            subtitleButton.innerHTML = '<span>字幕</span>';
                            subtitleButton.dataset.videoCode = videoCode;
                            controlsDiv.appendChild(subtitleButton);
                            dataElement.appendChild(controlsDiv);
                        }
                    });
                }
            }
        },
        jable: {
            isMatch: () => UTILS.getDomain().includes('jable') || UTILS.getDomain().includes('cableav') || UTILS.getDomain().includes('fs1.app'),
            targetSelector: '.video-toolbar, .video-info .level, .video-info .row, .text-center, #detail-container .pb-3, .container .mt-4, .player-container + div',
            process: (targetElement) => {
                if (!targetElement) {
                    console.error("Jable-like: Target container not found or page structure mismatch.");
                    return;
                }
                if (targetElement.querySelector('.emh-ui-container') || document.querySelector('.emh-ui-container')) {
                    return;
                }
                const isCableAv = UTILS.getDomain() === "cableav.tv";
                let videoUrl = '';
                let videoCode = UTILS.getCodeFromUrl(window.location.href);
                if (!videoCode) {
                    const titleCodeMatch = document.title.match(/^([A-Z0-9-]+)/i);
                    if (titleCodeMatch) videoCode = titleCodeMatch[1].toUpperCase();
                }
                 if (!videoCode) {
                    const ogTitle = document.querySelector("meta[property='og:title']");
                    if (ogTitle && ogTitle.content) {
                        const titleMatch = ogTitle.content.match(/^([A-Z0-9-]+)/i);
                        if (titleMatch) videoCode = titleMatch[1].toUpperCase();
                    }
                }
                if (!isCableAv) {
                    if (typeof hlsUrl !== 'undefined' && hlsUrl) {
                        videoUrl = hlsUrl;
                    } else {
                        const scripts = document.querySelectorAll('script');
                        for (let script of scripts) {
                            if (script.textContent.includes('player.src({')) {
                                const match = script.textContent.match(/src:\s*['"]([^'"]+\.m3u8[^'"]*)['"]/);
                                if (match && match[1]) {
                                    videoUrl = match[1];
                                    break;
                                }
                            }
                        }
                    }
                    if (videoUrl && videoCode) {
                        videoUrl += "#" + videoCode;
                    } else if (videoUrl && !videoCode) {
                         if (videoCode) videoUrl += "#" + videoCode;
                    }
                } else {
                    const metaTag = document.head.querySelector("meta[property~='og:video:url'][content]");
                    if (metaTag) videoUrl = metaTag.content;
                }
                if (videoCode) {
                    updateGlobalVideoCode(videoCode);
                } else {
                    console.warn("Jable-like: Video code could not be determined for this page.");
                }
                const uiContainer = document.createElement("div");
                uiContainer.className = "emh-ui-container";
                if (videoCode) {
                    const dataElement = document.createElement("span");
                    dataElement.id = "emh-dataElement";
                    dataElement.className = "btn btn-outline";
                    dataElement.style.cursor = 'pointer';
                    dataElement.innerHTML = `番号: ${videoCode}`;
                    dataElement.title = "点击搜索番号 (1cili)";
                    dataElement.dataset.videoCode = videoCode;

                    // 创建状态指示器容器
                    const statusContainer = document.createElement('div');
                    statusContainer.className = 'emh-code-status-container';
                    statusContainer.style.display = 'inline-block';
                    statusContainer.style.marginLeft = '10px';
                    createCodeStatusIndicator(statusContainer, videoCode);

                    // 将状态指示器添加到番号文本后面
                    dataElement.appendChild(statusContainer);

                    uiContainer.appendChild(dataElement);
                }
                UTILS.addActionButtons(uiContainer, videoUrl, videoCode);
                targetElement.appendChild(uiContainer);
                console.log("EMH: Added UI buttons to Jable-like page via target:", targetElement);
            }
        },
        javbus:{
            isMatch: () => UTILS.getDomain().includes('javbus.com'),
                targetSelector: '.item.masonry-brick',  // 目标元素选择器

                // 处理函数:对每个由targetSelector选中的元素执行的操作
                process: (targetElement) => {
                    // 遍历所有masonry布局的视频项
                   document.querySelectorAll('.item.masonry-brick').forEach(entry => {
    // 获取视频信息容器元素
    const photoInfoElement = entry.querySelector(".photo-info");
    if (!photoInfoElement) return;

    // 防止重复处理
    // 检查 photoInfoElement 是否已经处理过
    if (photoInfoElement.classList.contains('emh-indicator-processed')) return;

    // 从span元素中提取第一个date元素(视频代码)
    const dateElements = photoInfoElement.querySelectorAll("span > date");
    const videoCodeElement = dateElements.length > 0 ? dateElements[0] : null;

    if (videoCodeElement) {
        const videoCode = videoCodeElement.textContent.trim();
        if (!videoCode) return; // 跳过空代码
        // --- 创建状态指示器的容器 ---
        const statusContainer = document.createElement('div');

        // statusContainer.className = 'emh-indicator-wrapper';
        createCodeStatusIndicator(statusContainer, videoCode); // 填充 statusContainer

        // 检查 statusContainer 是否真的包含了子元素 (指示器)
        if (statusContainer.firstChild) {
             // --- 插入到 photoInfoElement 的最前面 ---
             photoInfoElement.insertBefore(statusContainer.firstChild, photoInfoElement.firstChild);
             // photoInfoElement.insertBefore(indicatorElement, photoInfoElement.firstChild);
             // 添加标记,表示这个 photoInfoElement 已经处理过
             photoInfoElement.classList.add('emh-indicator-processed');
        }


    }
});
                }
        },
    };

    const VIDEO_MANAGER = {
        sendVideoData: (button) => {
            const videoUrl = button.dataset.videoUrl || '';
            const videoCode = button.dataset.videoCode || EMH_currentVideoCode || UTILS.getCodeFromUrl(window.location.href);
            const titleElement = document.querySelector("h4.title, h1.post-title, .video-info h4, meta[property='og:title']");
            let title = titleElement ? (titleElement.content || titleElement.innerText.trim()) : document.title;
            if (videoCode && title.includes(videoCode)) {
                title = title.split(videoCode).pop().trim().replace(/^[-–—\s]+/, '');
            }
            const posterImage = UTILS.getPosterImage();
            const actress = UTILS.getActressNames();
            const videoData = {
                code: videoCode || 'UNKNOWN',
                name: title || 'Untitled',
                img: posterImage || '',
                url: window.location.href,
                actress: actress || '',
                video: videoUrl || ''
            };
            if (!videoData.code || videoData.code === 'UNKNOWN') {
                UTILS.showToast("无法获取视频代码,发送中止", "warning");
                console.warn("Send data aborted, missing video code.", videoData);
                return;
            }
            console.log("Data to send:", videoData);
            const serverDomain = (CONFIG.serverMode === 1) ? `localhost:${CONFIG.serverPort}` : `YOUR_SERVER_IP:${CONFIG.serverPort}`;
            if (CONFIG.serverMode === 2 && serverDomain.includes('YOUR_SERVER_IP')) {
                UTILS.showToast("请先在脚本中配置服务器IP地址", "error");
                console.error("Server IP not configured in script for serverMode 2.");
                return;
            }
            const apiUrl = UTILS.buildApiUrl(serverDomain, { path: '/add', query: videoData });
            if (typeof GM_xmlhttpRequest !== 'undefined') {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: apiUrl,
                    timeout: 10000,
                    onload: (response) => {
                        if (response.status >= 200 && response.status < 300) {
                            UTILS.showToast("数据已发送到服务器", "success");
                        } else {
                            UTILS.showToast(`服务器响应错误: ${response.status}`, "error");
                            console.error("Server response error:", response);
                        }
                    },
                    onerror: (error) => {
                        UTILS.showToast("发送数据时网络错误", "error");
                        console.error("Send data network error:", error);
                    },
                    ontimeout: () => {
                        UTILS.showToast("发送数据超时", "error");
                    }
                });
            } else {
                fetch(apiUrl, { mode: 'no-cors', signal: AbortSignal.timeout(10000) })
                    .then(response => {
                        UTILS.showToast("数据已尝试发送 (no-cors)", "success");
                    })
                    .catch(error => {
                        if (error.name === 'AbortError') {
                            UTILS.showToast("发送数据超时", "error");
                        } else {
                            UTILS.showToast("发送数据时出错 (fetch)", "error");
                        }
                        console.error("Send data error (fetch):", error);
                    });
            }
            return videoData;
        }
    };

    function extractCode(text) {
        if (!text) return null;
        const match = text.match(/([A-Za-z]{2,5}-?\d{2,5})/);
        return match ? match[1].toUpperCase() : text.replace(/\s*\(.*?\)/g, '').trim().toUpperCase();
    }

    function waitForElement(selector, callback, timeout = CONFIG.elementCheckTimeout) {
        const startTime = Date.now();
        const intervalId = setInterval(() => {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) {
                clearInterval(intervalId);
                callback(elements[0]);
            } else if (Date.now() - startTime > timeout) {
                clearInterval(intervalId);
                console.warn(`EMH: Element "${selector}" not found within ${timeout}ms.`);
                callback(null);
            }
        }, CONFIG.elementCheckInterval);
    }

    // 给可拖动按钮增加高级搜索功能
    function enhanceDraggableButton() {
        const draggableBtn = document.getElementById('emh-draggable-custom-subtitle-btn');
        if (draggableBtn) {
            draggableBtn.innerHTML = '<span>🔍 高级搜索</span>';
            draggableBtn.title = '拖动我 | 点击打开高级字幕搜索';

            // 保留原有的拖动功能,但改变点击行为
            const originalClickHandler = draggableBtn.onclick;
            draggableBtn.onclick = function(e) {
                // 检查是否进行了拖动
                if (this.hasDragged) {
                    if (originalClickHandler) {
                        originalClickHandler.call(this, e);
                    }
                    return;
                }

                // 没有拖动,则打开高级搜索
                e.preventDefault();
                e.stopPropagation();

                let defaultSearchTerm = '';
                if (typeof EMH_currentVideoCode !== 'undefined' && EMH_currentVideoCode) {
                    defaultSearchTerm = EMH_currentVideoCode;
                }
                UTILS.createSearchModal(defaultSearchTerm);
            };
        } else {
            // 如果找不到原有按钮,创建新的
            UTILS.createFloatingSearchButton();
        }
    }

    function main() {
        let handlerFound = false;
        for (const [name, handler] of Object.entries(SITE_HANDLERS)) {
            if (handler.isMatch()) {
                handlerFound = true;
                if (handler.targetSelector) {
                    waitForElement(handler.targetSelector, (targetElement) => {
                        if (targetElement || name === 'javgg') {
                            try {
                                setTimeout(() => { handler.process(targetElement); }, 50);
                            } catch (e) { console.error(`EMH: Error processing handler ${name} with target:`, e, targetElement); }
                        }
                    });
                } else {
                    try {
                        setTimeout(() => handler.process(null), 150);
                    } catch (e) { console.error(`EMH: Error processing handler ${name} immediately:`, e); }
                }
                break;
            }
        }
        if (!handlerFound) {
            console.log("EMH: No matching handler found for this site.");
        }
        setupEventListeners();

        // 延迟执行增强可拖动按钮功能,确保原按钮已创建
        setTimeout(() => {
            enhanceDraggableButton();
        }, 1500);
    }

    function setupEventListeners() {
        $(document).off('.emh');
        $(document).on('click.emh', '#emh-copyLink', function () {
            UTILS.copyToClipboard($(this).data('videoUrl'));
        });
        $(document).on('click.emh', '#emh-sendData', function () {
            VIDEO_MANAGER.sendVideoData(this);
        });
        $(document).on('click.emh', '#emh-getSubtitles, .emh-subtitle-button-small', function (e) {
            e.preventDefault();
            const videoCode = $(this).data('videoCode'); // This is for auto-detected codes
            if (videoCode) {
                SUBTITLE_MANAGER.fetchSubtitles(videoCode);
            } else {
                UTILS.showToast("无法从此按钮获取番号", "warning");
            }
        });
        $(document).on('click.emh', '#emh-dataElement', function () {
            const code = $(this).data('videoCode');
            if (code) {
                window.open(`https://1cili.com/search?q=${code}`, "_blank");
            }
        });
        $(document).on('click.emh', '#emh-floating-search-btn', function () {
            const defaultSearchTerm = EMH_currentVideoCode || '';
            UTILS.createSearchModal(defaultSearchTerm);
        });
        $(document).on('click.emh', '.emh-trending-tag', function () {
            const searchInput = document.getElementById('emh-subtitle-search-input');
            if (searchInput) {
                searchInput.value = $(this).text();
                searchInput.focus();
            }
        });

        // 字幕下载按钮点击事件
        $(document).on('click.emh', '.emh-download-subtitle-btn', function(e) {
            e.preventDefault();
            const url = $(this).data('url');
            const filename = $(this).data('filename');
            if (url && filename) {
                SUBTITLE_MANAGER.downloadSubtitle(url, filename);
            } else {
                UTILS.showToast("下载信息不完整", "error");
            }
        });

        // 番号管理按钮点击事件
        $(document).on('click.emh', '#emh-code-manager-btn', function() {
            if (window.CodeManagerPanel) {
                window.CodeManagerPanel.togglePanel();
            } else {
                UTILS.showToast("番号管理面板未能加载", "error");
            }
        });
    }

    function addCustomStyles() {
        const style = document.createElement('style');
        style.textContent = `
/* ==== CSS from Script ==== */
.navbar{z-index:12345679!important}.sub-header,#footer,.search-recent-keywords,.app-desktop-banner,div[data-controller=movie-tab] .tabs,h3.main-title,div.video-meta-panel>div>div:nth-child(2)>nav>div.review-buttons>div:nth-child(2),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(3),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(2),div.video-detail>div:nth-child(4)>div>div.tabs.no-bottom>ul>li:nth-child(1),.top-meta,.float-buttons{display:none!important}div.tabs.no-bottom,.tabs ul{border-bottom:none!important}.movie-list .item{position:relative!important}.fr-btn{float:right;margin-left:4px!important}.menu-box{position:fixed;right:10px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;z-index:1000;gap:6px}.menu-btn{display:inline-block!important;min-width:80px;padding:7px 12px;border-radius:4px;color:#fff!important;text-decoration:none;font-weight:700;font-size:12px;text-align:center;cursor:pointer;transition:all .3s ease;box-shadow:0 2px 5px #0000001a;text-shadow:0 1px 1px rgba(0,0,0,.2);border:none;line-height:1.3;margin:0}.menu-btn:hover{transform:translateY(-1px);box-shadow:0 3px 6px #00000026;opacity:.9}.menu-btn:active{transform:translateY(0);box-shadow:0 1px 2px #0000001a}.my-btn-primary,.my-btn-success,.my-btn-danger,.btn-warning,.btn-info,.btn-dark,.btn-outline,.btn-disabled{display:inline-flex;align-items:center;justify-content:center;padding:6px 14px;margin-left:10px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:500;transition:all .2s ease;cursor:pointer;border:1px solid rgba(0,0,0,.08);white-space:nowrap}.btn:hover{transform:translateY(-1px);box-shadow:0 2px 8px #0000000d}.my-btn-primary{background:#e0f2fe;color:#0369a1;border-color:#bae6fd}.my-btn-primary:hover{background:#bae6fd}.my-btn-success{background:#dcfce7;color:#166534;border-color:#bbf7d0}.my-btn-success:hover{background:#bbf7d0}.my-btn-danger{background:#fee2e2;color:#b91c1c;border-color:#fecaca}.my-btn-danger:hover{background:#fecaca}.btn-warning{background:#ffedd5;color:#9a3412;border-color:#fed7aa}.btn-warning:hover{background:#fed7aa}.btn-info{background:#ccfbf1;color:#0d9488;border-color:#99f6e4}.btn-info:hover{background:#99f6e4}.btn-dark{background:#e2e8f0;color:#334155;border-color:#cbd5e1}.btn-dark:hover{background:#cbd5e1}.btn-outline{background:transparent;color:#64748b;border-color:#cbd5e1}.btn-outline:hover{background:#f8fafc}.btn-disabled{background:#f1f5f9!important;color:#94a3b8!important;border-color:#e2e8f0!important;cursor:not-allowed!important}.btn-disabled:hover{transform:none!important;box-shadow:none!important;background:#f1f5f9!important;color:#94a3b8!important;}.data-table{width:100%;border-collapse:separate;border-spacing:0;font-family:Helvetica Neue,Arial,sans-serif;background:#fff;overflow:hidden;box-shadow:0 4px 20px #00000008;margin:0 auto}.data-table thead tr{background:#f8fafc}.data-table th{padding:16px 20px;text-align:center!important;color:#64748b;font-weight:500;font-size:14px;text-transform:uppercase;letter-spacing:.5px;border-bottom:1px solid #e2e8f0}.data-table td{padding:14px 20px;color:#334155;font-size:15px;border-bottom:1px solid #f1f5f9;text-align:center!important;vertical-align:middle}.data-table tbody tr:last-child td{border-bottom:none}.data-table tbody tr{transition:all .2s ease}.data-table tbody tr:hover{background:#f8fafc}.data-table .text-left{text-align:left}.data-table .text-right{text-align:right}.data-table.show-border,.data-table.show-border th,.data-table.show-border td{border:1px solid #e2e8f0}
.loading-container{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;background-color:rgba(0,0,0,.1);z-index:99999999}.loading-animation{position:relative;width:60px;height:12px;background:linear-gradient(90deg,#4facfe 0,#00f2fe 100%);border-radius:6px;animation:loading-animate 1.8s ease-in-out infinite;box-shadow:0 4px 12px rgba(0,0,0,.1)}.loading-animation:after,.loading-animation:before{position:absolute;display:block;content:"";animation:loading-animate 1.8s ease-in-out infinite;height:12px;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.1)}.loading-animation:before{top:-20px;left:10px;width:40px;background:linear-gradient(90deg,#ff758c 0,#ff7eb3 100%)}.loading-animation:after{bottom:-20px;width:35px;background:linear-gradient(90deg,#ff9a9e 0,#fad0c4 100%)}@keyframes loading-animate{0%{transform:translateX(40px)}50%{transform:translateX(-30px)}100%{transform:translateX(40px)}}

/* ==== Additional Styles needed by EMH ==== */
.emh-modal{display:flex;align-items:center;justify-content:center;position:fixed;z-index:9998;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgba(0,0,0,0.7);opacity:0;transition:opacity 0.3s ease;backdrop-filter:blur(3px);}.emh-modal.show{opacity:1;}.emh-modal-content{background-color:#fff;margin:auto;padding:0;border-radius:12px;width:90%;max-width:650px;box-shadow:0 8px 24px rgba(0,0,0,0.4);transform:scale(0.9);transition:transform 0.3s ease;overflow:hidden;}.emh-modal.show .emh-modal-content{transform:scale(1);}.emh-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;background-color:#f8f9fa;border-bottom:1px solid #dadce0;}.emh-modal-header h3{margin:0;color:#202124;font-size:18px;font-weight:500;}.emh-modal-close{color:#5f6368;font-size:28px;font-weight:bold;cursor:pointer;line-height:1;padding:0 5px;transition:color 0.2s ease;}.emh-modal-close:hover{color:#202124;}.emh-modal-body{padding:20px 24px;max-height:65vh;overflow-y:auto;background-color:#fff;}.emh-no-subtitle-message{text-align:center;color:#5f6368;font-size:16px;padding:36px 0;}.emh-subtitle-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:14px;}.emh-subtitle-item{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;padding:16px;background-color:#f8f9fa;border-radius:8px;border:1px solid #e8eaed;gap:12px;transition:all 0.2s ease;}.emh-subtitle-item:hover{transform:translateY(-2px);box-shadow:0 4px 8px rgba(0,0,0,0.1);background-color:#f1f5f9;}.emh-subtitle-info{flex:1 1 auto;min-width:200px;}.emh-subtitle-info h4{margin:0 0 8px 0;color:#202124;font-size:16px;font-weight:500;overflow:hidden;text-overflow:ellipsis;}.emh-subtitle-info p{margin:3px 0;color:#5f6368;font-size:14px;line-height:1.5;}.emh-subtitle-item .my-btn-primary{flex-shrink:0; margin-left: auto !important;}
.emh-ui-container{margin:12px 0 8px 0;text-align:center;display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:10px;border-top:1px solid #dadce0;padding:14px 8px 6px 8px;background-color:rgba(248,249,250,0.5);border-radius:8px;}
.emh-action-buttons{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;}
.emh-controls-container{margin-top:10px;padding:8px;display:flex;gap:10px;align-items:center;flex-wrap:wrap;background-color:rgba(248,249,250,0.5);border-radius:8px;}
.emh-javgg-controls{margin-top:6px;display:inline-flex;flex-wrap:wrap;gap:6px;align-items:center;margin-left:10px;vertical-align:middle;padding:4px 6px;background-color:rgba(248,249,250,0.5);border-radius:6px;}
#custom-toast-container{position:fixed;top:70px;right:20px;z-index:10000;display:flex;flex-direction:column;gap:10px;}
.custom-toast{padding:14px 20px;border-radius:8px;color:#fff;box-shadow:0 4px 12px rgba(0,0,0,0.25);transition:opacity 0.3s ease,transform 0.3s ease;opacity:0;transform:translateX(100%);font-size:14px;font-weight:500;display:flex;align-items:center;}
.custom-toast.show{opacity:1;transform:translateX(0);}
.custom-toast::before{margin-right:8px;font-weight:bold;}.custom-toast-success{background:linear-gradient(to right, #68D391, #9AE6B4);}.custom-toast-success::before{content:"✓";}
.custom-toast-error{background:linear-gradient(to right, #FC8181, #FEB2B2);}.custom-toast-error::before{content:"✕";}
.custom-toast-info{background:linear-gradient(to right, #A78BFA, #C4B5FD);}.custom-toast-info::before{content:"ℹ";}
.custom-toast-warning{background:linear-gradient(to right, #ff9a9e, #fad0c4); color:#202124;}.custom-toast-warning::before{content:"⚠";}
.emh-button {} .emh-subtitle-button-small {min-width: auto;} .emh-external-link, .emh-external-link-small {} .emh-data-element {cursor: pointer;} .emh-data-element::after {content: " 🔍";opacity: 0.7;margin-left: 4px;} .emh-action-buttons, .emh-controls-container, .emh-javgg-controls, .emh-ui-container {display: flex;flex-wrap: wrap;align-items: center;gap: 8px;} .emh-ui-container {justify-content: center;margin-top: 10px;padding-top: 10px;border-top: 1px solid #e2e8f0;} .emh-javgg-controls {margin-left: 5px;}

/* Styles for the Draggable Custom Subtitle Button */
.emh-draggable-btn {
    z-index: 10001 !important;
    cursor: grab;
    padding: 8px 12px !important;
    box-shadow: 0 4px 10px rgba(0,0,0,0.2);
    min-width: auto !important;
    margin: 0 !important; /* Reset margin from .btn if any */
}
.emh-draggable-btn:active {
    cursor: grabbing !important;
}

/* Status Indicator for Video Codes */
.emh-code-status-indicator {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    cursor: pointer;
    margin-right: 8px;
    transition: all 0.2s ease;
    position: relative;
    border: 1px solid rgba(0,0,0,0.1);
}

.emh-code-status-indicator:hover {
    transform: scale(1.2);
    box-shadow: 0 0 5px rgba(0,0,0,0.2);
}

.emh-code-status-indicator[data-status="favorite"] {
    background-color: #ff4757; /* Red */
}

.emh-code-status-indicator[data-status="watched"] {
    background-color: #2ed573; /* Green */
}

.emh-code-status-indicator[data-status="unmarked"] {
    background-color: #909090; /* Gray */
}

/* 搜索模态框样式 */
.emh-search-modal-content {
    max-width: 540px;
}

.emh-search-form {
    margin-bottom: 20px;
}

.emh-search-input-group {
    display: flex;
    gap: 8px;
    width: 100%;
}

.emh-input-wrapper {
    position: relative;
    flex-grow: 1;
}

.emh-search-input {
    width: 100%;
    padding: 12px 32px 12px 16px;
    border-radius: 8px;
    border: 1px solid #dadce0;
    font-size: 15px;
    outline: none;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.emh-search-input:focus {
    border-color: #a78bfa;
    box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.2);
}

.emh-search-clear-btn {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    color: #5f6368;
    font-size: 20px;
    line-height: 1;
    padding: 4px;
    cursor: pointer;
    border-radius: 50%;
    visibility: hidden;
}

.emh-search-clear-btn:hover {
    background-color: rgba(0,0,0,0.05);
}

.emh-search-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0 20px;
    border: none;
    border-radius: 8px;
    background: linear-gradient(45deg, #a78bfa, #c4b5fd);
    color: white;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
}

.emh-search-btn:hover {
    box-shadow: 0 4px 12px rgba(167, 139, 250, 0.3);
    transform: translateY(-1px);
}

.emh-search-btn:active {
    transform: translateY(0);
}

/* 搜索历史样式 */
.emh-search-history-section,
.emh-trending-section,
.emh-settings-section {
    margin-top: 20px;
    padding-top: 20px;
    border-top: 1px solid #f1f5f9;
}

.emh-search-history-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
}

.emh-search-history-header h4,
.emh-trending-section h4,
.emh-settings-section h4 {
    margin: 0;
    color: #202124;
    font-size: 15px;
    font-weight: 500;
}

.emh-clear-history-btn {
    background: none;
    border: none;
    color: #5f6368;
    font-size: 13px;
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 4px;
}

.emh-clear-history-btn:hover {
    background-color: rgba(0,0,0,0.05);
    color: #202124;
}

.emh-search-history-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 150px;
    overflow-y: auto;
}

.emh-history-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    background-color: #f8f9fa;
    border-radius: 8px;
    transition: background-color 0.2s ease;
}

.emh-history-item:hover {
    background-color: #f1f5f9;
}

.emh-history-text {
    color: #202124;
    font-size: 14px;
    flex-grow: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.emh-history-use-btn {
    background: none;
    border: none;
    color: #5f6368;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 4px;
    border-radius: 50%;
    cursor: pointer;
    opacity: 0.7;
    transition: all 0.2s ease;
}

.emh-history-use-btn:hover {
    background-color: rgba(0,0,0,0.05);
    color: #a78bfa;
    opacity: 1;
}

.emh-empty-history {
    text-align: center;
    color: #5f6368;
    font-size: 14px;
    padding: 12px 0;
}

/* 热门推荐样式 */
.emh-trending-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
}

.emh-trending-tag {
    background: linear-gradient(45deg, #e9d5ff, #f3e8ff);
    color: #7e22ce;
    border-radius: 16px;
    padding: 6px 14px;
    font-size: 13px;
    cursor: pointer;
    transition: all 0.2s ease;
}

.emh-trending-tag:hover {
    background: linear-gradient(45deg, #d8b4fe, #e9d5ff);
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgba(167, 139, 250, 0.2);
}

/* 悬浮搜索按钮 */
.emh-floating-btn {
    position: fixed;
    bottom: 120px;
    right: 20px;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background: linear-gradient(45deg, #a78bfa, #c4b5fd);
    color: white;
    border: none;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 4px 10px rgba(0,0,0,0.2);
    z-index: 10001;
    transition: all 0.2s ease;
}

.emh-floating-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 12px rgba(0,0,0,0.3);
}

/* 自定义滚动条 */
.emh-search-history-list::-webkit-scrollbar {
    width: 8px;
}

.emh-search-history-list::-webkit-scrollbar-track {
    background: #f1f5f9;
    border-radius: 8px;
}

.emh-search-history-list::-webkit-scrollbar-thumb {
    background: #cbd5e1;
    border-radius: 8px;
}

.emh-search-history-list::-webkit-scrollbar-thumb:hover {
    background: #94a3b8;
}

/* 设置项样式 */
.emh-setting-item {
    margin: 12px 0;
}

.emh-setting-disabled {
    opacity: 0.5;
    pointer-events: none;
}

.emh-setting-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
    padding: 8px 12px;
    background-color: #f8f9fa;
    border-radius: 8px;
    transition: background-color 0.2s ease;
}

.emh-setting-label:hover {
    background-color: #f1f5f9;
}

.emh-setting-label span {
    color: #202124;
    font-size: 14px;
}

/* 开关样式 */
.emh-toggle-checkbox {
    height: 0;
    width: 0;
    visibility: hidden;
    position: absolute;
}

.emh-toggle-switch {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 20px;
    background: #e2e8f0;
    border-radius: 20px;
    transition: 0.3s;
}

.emh-toggle-switch:after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    background: #fff;
    border-radius: 16px;
    transition: 0.3s;
}

.emh-toggle-checkbox:checked + .emh-toggle-switch {
    background: #a78bfa;
}

.emh-toggle-checkbox:checked + .emh-toggle-switch:after {
    left: calc(100% - 2px);
    transform: translateX(-100%);
}

/* 响应式调整 */
@media (max-width: 576px) {
    .emh-search-input-group {
        flex-direction: column;
    }

    .emh-search-btn {
        height: 44px;
    }
}

/* 美化字幕模态框和列表 */
#emh-subtitle-modal {
    backdrop-filter: blur(5px);
}

#emh-subtitle-modal .emh-modal-content {
    border: none;
    box-shadow: 0 10px 30px rgba(0,0,0,0.25);
}

#emh-subtitle-modal .emh-modal-header {
    background: linear-gradient(135deg, #f8f9fa, #e9ecef);
    padding: 18px 24px;
}

#emh-subtitle-modal .emh-modal-header h3 {
    font-size: 20px;
    background: linear-gradient(90deg, #4b6cb7, #182848);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    font-weight: 600;
}

#emh-subtitle-modal .emh-modal-close {
    font-size: 26px;
    transition: all 0.2s ease;
    border-radius: 50%;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
}

#emh-subtitle-modal .emh-modal-close:hover {
    background-color: rgba(0,0,0,0.05);
    transform: rotate(90deg);
}

#emh-subtitle-modal .emh-modal-body {
    background: linear-gradient(135deg, #ffffff, #f8f9fa);
    padding: 22px 24px;
}

/* 美化字幕列表样式 */
#emh-subtitle-modal .emh-subtitle-list {
    gap: 16px;
}

#emh-subtitle-modal .emh-subtitle-item {
    background: #ffffff;
    box-shadow: 0 2px 6px rgba(0,0,0,0.05);
    border-radius: 12px;
    border: 1px solid rgba(0,0,0,0.08);
    padding: 18px;
    transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
}

#emh-subtitle-modal .emh-subtitle-item:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 16px rgba(0,0,0,0.1);
    border-color: rgba(75, 108, 183, 0.3);
}

#emh-subtitle-modal .emh-subtitle-info h4 {
    font-size: 17px;
    font-weight: 600;
    margin-bottom: 10px;
    color: #2d3748;
}

#emh-subtitle-modal .emh-subtitle-info p {
    color: #718096;
    font-size: 14.5px;
    line-height: 1.5;
}

/* 美化字幕按钮样式 */
#emh-subtitle-modal .emh-subtitle-actions {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: flex-end;
    margin-left: auto;
}

#emh-subtitle-modal .emh-subtitle-actions .btn {
    padding: 8px 16px;
    border-radius: 8px;
    font-weight: 500;
    transition: all 0.2s ease;
    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}

#emh-subtitle-modal .emh-subtitle-actions .my-btn-primary {
    background: linear-gradient(90deg, #4b6cb7, #182848);
    border: none;
    color: white;
}

#emh-subtitle-modal .emh-subtitle-actions .my-btn-primary:hover {
    box-shadow: 0 4px 8px rgba(75, 108, 183, 0.3);
    transform: translateY(-2px);
}

#emh-subtitle-modal .emh-subtitle-actions .btn-outline {
    border: 1px solid #cbd5e0;
    color: #4a5568;
    background: white;
}

#emh-subtitle-modal .emh-subtitle-actions .btn-outline:hover {
    background: #f7fafc;
    border-color: #a0aec0;
}

/* 无字幕时的提示 */
#emh-subtitle-modal .emh-no-subtitle-message {
    text-align: center;
    color: #718096;
    font-size: 17px;
    padding: 40px 0;
    font-weight: 500;
    background: #f7fafc;
    border-radius: 8px;
    border: 1px dashed #cbd5e0;
}

/* 响应式调整 - 字幕模态框 */
@media (max-width: 576px) {
    #emh-subtitle-modal .emh-subtitle-item {
        flex-direction: column;
        align-items: stretch;
    }

    #emh-subtitle-modal .emh-subtitle-actions {
        margin-top: 16px;
        justify-content: center;
    }

    #emh-subtitle-modal .emh-modal-content {
        width: 95%;
        max-width: 95%;
    }

    #emh-subtitle-modal .emh-modal-header h3 {
        font-size: 18px;
    }
}

.emh-code-manager-toggle {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 10000;
    padding: 8px 16px;
    border-radius: 8px;
    background: #3b82f6;
    color: white;
    border: none;
    cursor: pointer;
    font-size: 14px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.2);
    transition: all 0.2s ease;
}

.emh-code-manager-toggle:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    background: #2563eb;
}

.emh-code-manager-panel {
    position: fixed;
    top: 0;
    right: -500px;
    width: 450px;
    height: 100vh;
    background: white;
    box-shadow: -5px 0 15px rgba(0,0,0,0.2);
    z-index: 22345679 !important;
    transition: right 0.3s ease;
    display: flex;
    flex-direction: column;
}

.emh-code-manager-panel.visible {
    right: 0;
}

        `;
        document.head.appendChild(style);
    }

    // 创建番号状态标记按钮
    function createCodeStatusIndicator(container, code) {
        if (!code || !container) return null;

        // 初始化 CODE_LIBRARY
        if (!CODE_LIBRARY.initialized) {
            CODE_LIBRARY.init();
        }

        // 获取当前番号状态
        const currentStatus = CODE_LIBRARY.getStatus(code);

        // 创建状态指示器
        const statusIndicator = document.createElement('div');
        statusIndicator.className = 'emh-code-status-indicator';
        statusIndicator.dataset.code = code;
        statusIndicator.dataset.status = currentStatus;

        // 设置状态图标和颜色
        const statusColors = CONFIG.codeManager.statusColors;
        statusIndicator.style.backgroundColor = statusColors[currentStatus] || statusColors.unmarked;

        // 状态提示文本
        let statusText = '未标记';
        if (currentStatus === 'favorite') statusText = '已关注';
        if (currentStatus === 'watched') statusText = '已看过';

        // 根据状态设置不同的提示文本
        if (currentStatus === 'watched') {
            statusIndicator.title = `状态: ${statusText} (请在番号库中修改状态)`;
            statusIndicator.style.cursor = 'default';  // 已看状态下不可点击
        } else {
            statusIndicator.title = `状态: ${statusText} (点击${currentStatus === 'favorite' ? '取消' : ''}关注)`;
            statusIndicator.style.cursor = 'pointer';  // 可点击状态
        }

        // 点击事件 - 只能切换关注状态
        statusIndicator.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();

            // 获取最新的当前状态
            const currentStatus = CODE_LIBRARY.getStatus(code);

            // 如果是已看状态,不允许修改
            if (currentStatus === 'watched') {
                UTILS.showToast('已看状态请在番号库中修改', 'warning');
                return;
            }

            // 在未标记和关注之间切换
            const newStatus = currentStatus === 'favorite' ? 'unmarked' : 'favorite';

            // 更新标记
            CODE_LIBRARY.markItem(code, newStatus);

            // 更新UI
            updateCodeStatusIndicators();

            // 显示提示
            const statusText = newStatus === 'favorite' ? '已关注' : '已取消关注';
            UTILS.showToast(`番号 ${code} ${statusText}`, 'success');
        });

        // 添加到容器
        container.appendChild(statusIndicator);
        return statusIndicator;
    }

    // 更新所有番号状态指示器
    function updateCodeStatusIndicators() {
        // 更新所有页面上的状态指示器
        document.querySelectorAll('.emh-code-status-indicator').forEach(indicator => {
            const code = indicator.dataset.code;
            if (!code) return;

            const currentStatus = CODE_LIBRARY.getStatus(code);
            indicator.dataset.status = currentStatus;

            // 更新颜色
            const statusColors = CONFIG.codeManager.statusColors;
            indicator.style.backgroundColor = statusColors[currentStatus] || statusColors.unmarked;

            // 更新提示和鼠标样式
            let statusText = '未标记';
            if (currentStatus === 'favorite') statusText = '已关注';
            if (currentStatus === 'watched') statusText = '已看过';

            if (currentStatus === 'watched') {
                indicator.title = `状态: ${statusText} (请在番号库中修改状态)`;
                indicator.style.cursor = 'default';
            } else {
                indicator.title = `状态: ${statusText} (点击${currentStatus === 'favorite' ? '取消' : ''}关注)`;
                indicator.style.cursor = 'pointer';
            }
        });
    }

    // Code Manager Panel Implementation
    const CodeManagerPanel = {
        initialized: false,
        panelElement: null,
        currentFilter: 'all',
        searchQuery: '',
        selectedItems: [],
        multiSelectMode: false,

        createToggleButton: function() {
            const existingButton = document.getElementById('emh-code-manager-toggle');
            if (existingButton) {
                existingButton.remove();
            }

            const btn = document.createElement('button');
            btn.id = 'emh-code-manager-toggle';
            btn.className = 'emh-code-manager-toggle';
            btn.innerHTML = '<span>📋 番号库</span>';
            btn.title = '管理番号库';

            btn.addEventListener('click', () => {
                this.togglePanel();
            });

            document.body.appendChild(btn);
        },

        init: function() {
            if (!this.initialized) {
                this.createStyles();
                this.createPanelElement();
                this.createToggleButton(); // 确保创建番号库按钮
                this.attachEventListeners();
                this.initialized = true;
                console.log('Code Manager Panel initialized');
            }
        },

        // Toggle panel visibility
        togglePanel: function() {
            if (this.isVisible) {
                this.hidePanel();
            } else {
                this.showPanel();
            }
        },

        // Show the panel
       showPanel: function() {
    if (!this.panelElement) { // 确保面板 DOM 元素已创建
        this.createPanelElement();
    }
    if (!CODE_LIBRARY.initialized) {
        CODE_LIBRARY.init();
    }

    this.isVisible = true;
    this.panelElement.classList.add('visible'); // 使面板滑入视图
    this.refreshPanelContent(); // 加载或刷新面板内容

    // ---- 新增:针对 javtxt 类网站调整面板位置 ----
    if (SITE_HANDLERS.javtxt && typeof SITE_HANDLERS.javtxt.isMatch === 'function' && SITE_HANDLERS.javtxt.isMatch()) {
        // !!! 重要: 下面的 'nav.site-navbar' 是一个示例选择器 !!!
        // !!! 你需要通过浏览器开发者工具检查 javtxt 网站,找到实际的导航栏元素的选择器 !!!
        // !!! 目标是找到那个 z-index 为 12345679 !important 的导航栏元素 !!!
        const navbarSelector = 'nav.navbar'; // <--- 【请修改为 javtxt 导航栏的实际 CSS 选择器】
                                           // 例如可能是: 'header#main-header', '.fixed-top-bar', 'div[class*="navbar-fixed"]' 等

        let navbarElement = document.querySelector(navbarSelector);

        // 如果特定选择器找不到,尝试更通用的、基于已知高 z-index 的启发式查找
        if (!navbarElement) {
            const potentialNavs = document.querySelectorAll('nav, header'); // 常见的导航栏标签
            for (const el of potentialNavs) {
                const style = window.getComputedStyle(el);
                // 检查是否固定定位在顶部且具有非常高的 z-index
                if ((style.position === 'fixed' || style.position === 'sticky') &&
                    parseInt(style.zIndex) >= 10000000 && // 查找具有类似超高 z-index 的元素
                    el.getBoundingClientRect().top < 10 && // 确保它在视口顶部
                    el.offsetHeight > 20) { // 确保它有一定高度
                    navbarElement = el;
                    console.log("EMH: Detected potential javtxt navbar via heuristics:", navbarElement);
                    break;
                }
            }
        }

        if (navbarElement) {
            const navbarHeight = navbarElement.offsetHeight;
            if (navbarHeight > 0) {
                this.panelElement.style.top = `${navbarHeight}px`;
                this.panelElement.style.height = `calc(100vh - ${navbarHeight}px)`;
                console.log(`EMH: Adjusted panel for javtxt navbar. Top: ${navbarHeight}px`);

                // (可选) 如果面板的 z-index 之前设置得非常高以覆盖导航栏,现在可以考虑降低它
                // this.panelElement.style.zIndex = '10050'; // 例如,一个仍然较高但低于导航栏的值
            } else {
                // 导航栏找到但高度为0,或不可见,则使用默认全屏
                this.panelElement.style.top = '0px';
                this.panelElement.style.height = '100vh';
            }
        } else {
            // 未找到导航栏,使用默认全屏
            this.panelElement.style.top = '0px';
            this.panelElement.style.height = '100vh';
            console.warn("EMH: javtxt navbar element not found with selector or heuristics. Panel might be obscured if navbar exists.");
        }
    } else {
        // 非 javtxt 类网站,使用默认全屏
        this.panelElement.style.top = '0px';
        this.panelElement.style.height = '100vh';
    }
    // ---- 调整结束 ----
},

        // Hide the panel
        hidePanel: function() {
            this.isVisible = false;
            if (this.panelElement) {
                this.panelElement.classList.remove('visible');
            }

            if (this.multiSelectMode) {
                this.toggleMultiSelectMode();
            }
        },

        // Create the panel element
        createPanelElement: function() {
            if (this.panelElement) return;

            const panel = document.createElement('div');
            panel.id = 'emh-code-manager-panel';
            panel.className = 'emh-code-manager-panel';

            panel.innerHTML = `
                <div class="emh-panel-header">
                    <h2>番号管理</h2>
                    <div class="emh-panel-controls">
                        <button class="emh-panel-close">&times;</button>
                    </div>
                </div>
                <div class="emh-panel-tabs">
                    <button data-filter="all" class="active">全部</button>
                    <button data-filter="favorite">关注列表</button>
                    <button data-filter="watched">已看记录</button>
                    <button data-filter="trash">回收站</button>
                </div>
                <div class="emh-panel-search">
                    <input type="text" placeholder="搜索番号或备注..." />
                    <button class="emh-search-btn">🔍</button>
                </div>
                <div class="emh-panel-content">
                    <!-- Content will be filled dynamically -->
                </div>
                <div class="emh-panel-actions">
                    <button id="emh-add-code" class="btn my-btn-primary">添加</button>
                    <button id="emh-multi-select" class="btn btn-outline">多选</button>
                    <button id="emh-export" class="btn btn-info">导出</button>
                    <button id="emh-import" class="btn btn-info">导入</button>
                    <button id="emh-clear-trash" class="btn my-btn-danger" style="display: none;">清空回收站</button>
                </div>
                <div class="emh-panel-multi-actions" style="display: none;">
                    <span class="emh-selected-count">已选择 0 项</span>
                    <button id="emh-mark-favorite" class="btn my-btn-danger">标为关注</button>
                    <button id="emh-mark-watched" class="btn my-btn-success">标为已看</button>
                    <button id="emh-delete-selected" class="btn btn-outline">删除</button>
                    <button id="emh-cancel-multi" class="btn btn-outline">取消</button>
                </div>
                <div class="emh-panel-modal" style="display: none;">
                    <div class="emh-panel-modal-content">
                        <h3></h3>
                        <div class="emh-panel-modal-buttons">
                            <button class="btn my-btn-danger emh-panel-modal-confirm">确定</button>
                            <button class="btn btn-outline emh-panel-modal-cancel">取消</button>
                        </div>
                    </div>
                </div>
            `;

            document.body.appendChild(panel);
            this.panelElement = panel;

            panel.querySelector('.emh-panel-close').addEventListener('click', () => {
                this.hidePanel();
            });
        },

        // Add CSS styles for the panel
        createStyles: function() {
            const styleElement = document.createElement('style');
            styleElement.textContent = `
                .emh-code-manager-toggle {
                    position: fixed;
                    bottom: 20px;
                    right: 20px;
                    z-index: 10000;
                    padding: 8px 16px;
                    border-radius: 8px;
                    background: #3b82f6;
                    color: white;
                    border: none;
                    cursor: pointer;
                    font-size: 14px;
                    box-shadow: 0 2px 8px rgba(0,0,0,0.2);
                    transition: all 0.2s ease;
                }

                .emh-code-manager-toggle:hover {
                    transform: translateY(-2px);
                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    background: #2563eb;
                }

                .emh-code-manager-panel {
                    position: fixed;
                    top: 0;
                    right: -500px;
                    width: 450px;
                    height: 100vh;
                    background: white;
                    box-shadow: -5px 0 15px rgba(0,0,0,0.2);
                    z-index: 10010;
                    transition: right 0.3s ease;
                    display: flex;
                    flex-direction: column;
                }

                .emh-code-manager-panel.visible {
                    right: 0;
                }

                .emh-panel-header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 15px 20px;
                    border-bottom: 1px solid #e2e8f0;
                }

                .emh-panel-header h2 {
                    margin: 0;
                    font-size: 20px;
                    font-weight: 500;
                    color: #334155;
                }

                .emh-panel-close {
                    background: none;
                    border: none;
                    font-size: 24px;
                    cursor: pointer;
                    color: #64748b;
                }

                .emh-panel-tabs {
                    display: flex;
                    border-bottom: 1px solid #e2e8f0;
                    padding: 0 15px;
                }

                .emh-panel-tabs button {
                    background: none;
                    border: none;
                    padding: 12px 15px;
                    font-size: 14px;
                    cursor: pointer;
                    color: #64748b;
                    position: relative;
                }

                .emh-panel-tabs button.active {
                    color: #3b82f6;
                    font-weight: 500;
                }

                .emh-panel-tabs button.active::after {
                    content: '';
                    position: absolute;
                    bottom: 0;
                    left: 0;
                    right: 0;
                    height: 2px;
                    background: #3b82f6;
                }

                .emh-panel-search {
                    padding: 15px;
                    display: flex;
                    border-bottom: 1px solid #e2e8f0;
                }

                .emh-panel-search input {
                    flex: 1;
                    padding: 8px 12px;
                    border: 1px solid #cbd5e1;
                    border-radius: 4px 0 0 4px;
                    outline: none;
                }

                .emh-search-btn {
                    background: #f1f5f9;
                    border: 1px solid #cbd5e1;
                    border-left: none;
                    border-radius: 0 4px 4px 0;
                    padding: 0 12px;
                    cursor: pointer;
                }

                .emh-panel-content {
                    flex: 1;
                    overflow-y: auto;
                    padding: 15px;
                    background: #f8fafc;
                }

                .emh-panel-actions,
                .emh-panel-multi-actions {
                    padding: 15px;
                    display: flex;
                    gap: 10px;
                    border-top: 1px solid #e2e8f0;
                    background: white;
                }

                .emh-item {
                    display: flex;
                    align-items: center;
                    padding: 12px;
                    border: 1px solid #e2e8f0;
                    border-radius: 6px;
                    margin-bottom: 8px;
                    background: white;
                }

                .emh-item.favorite {
                    border-color: #f56565;
                    background: #fff5f5;
                }

                .emh-item.watched {
                    border-color: #48bb78;
                    background: #f0fff4;
                }

                .emh-item.selected {
                    border-color: #4299e1;
                    background: #ebf8ff;
                }

                .emh-item-code {
                    font-weight: 500;
                    color: #2d3748;
                    margin-right: 12px;
                }

                .emh-item-remarks {
                    flex: 1;
                    color: #718096;
                    font-size: 14px;
                }

                .emh-item-actions {
                    display: flex;
                    gap: 8px;
                }

                .emh-item-actions button {
                    background: none;
                    border: none;
                    cursor: pointer;
                    padding: 4px;
                    opacity: 0.7;
                    transition: opacity 0.2s;
                }

                .emh-item-actions button:hover {
                    opacity: 1;
                }

                .emh-empty-state {
                    text-align: center;
                    color: #718096;
                    padding: 40px 0;
                }

                @media (max-width: 576px) {
                    .emh-code-manager-panel {
                        width: 100%;
                        right: -100%;
                    }
                }

                .emh-panel-modal {
                    position: absolute;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background: rgba(0, 0, 0, 0.5);
                    display: none;
                    justify-content: center;
                    align-items: center;
                    z-index: 10011;
                }

                .emh-panel-modal-content {
                    background: white;
                    padding: 20px;
                    border-radius: 8px;
                    width: 80%;
                    max-width: 300px;
                    text-align: center;
                }

                .emh-panel-modal-content h3 {
                    margin: 0 0 20px 0;
                    color: #334155;
                }

                .emh-panel-modal-buttons {
                    display: flex;
                    justify-content: center;
                    gap: 10px;
                    margin-top: 20px;
                }
            `;

            document.head.appendChild(styleElement);
        },

        // Attach event listeners
        attachEventListeners: function() {
            // Tab switching
            this.panelElement.querySelectorAll('.emh-panel-tabs button').forEach(tab => {
                tab.addEventListener('click', (e) => {
                    this.currentFilter = e.target.dataset.filter;
                    this.refreshPanelContent();

                    // Update active tab
                    this.panelElement.querySelectorAll('.emh-panel-tabs button').forEach(t => {
                        t.classList.remove('active');
                    });
                    e.target.classList.add('active');
                });
            });

            // Search functionality
            const searchInput = this.panelElement.querySelector('.emh-panel-search input');
            const searchBtn = this.panelElement.querySelector('.emh-search-btn');

            searchInput.addEventListener('input', (e) => {
                this.searchQuery = e.target.value;
                this.refreshPanelContent();
            });

            searchBtn.addEventListener('click', () => {
                this.refreshPanelContent();
            });

            // Multi-select mode
            const multiSelectBtn = this.panelElement.querySelector('#emh-multi-select');
            multiSelectBtn.addEventListener('click', () => {
                this.toggleMultiSelectMode();
            });

            // Multi-select actions
            this.panelElement.querySelector('#emh-mark-favorite').addEventListener('click', () => {
                this.batchMarkItems('favorite');
            });

            this.panelElement.querySelector('#emh-mark-watched').addEventListener('click', () => {
                this.batchMarkItems('watched');
            });

            this.panelElement.querySelector('#emh-delete-selected').addEventListener('click', () => {
                this.batchDeleteItems();
            });

            this.panelElement.querySelector('#emh-cancel-multi').addEventListener('click', () => {
                this.toggleMultiSelectMode();
            });

            // Add code button
            this.panelElement.querySelector('#emh-add-code').addEventListener('click', () => {
                const code = prompt('请输入要添加的番号:');
                if (code) {
                    CODE_LIBRARY.add(code);
                    this.refreshPanelContent();
                    UTILS.showToast(`番号 ${code} 已添加`, 'success');
                }
            });

            // Export button
            this.panelElement.querySelector('#emh-export').addEventListener('click', () => {
                const data = CODE_LIBRARY.exportData();
                const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'});
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'emh_code_library.json';
                a.click();
                URL.revokeObjectURL(url);
                UTILS.showToast('数据导出成功', 'success');
            });

            // Import button
            this.panelElement.querySelector('#emh-import').addEventListener('click', () => {
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.json';
                input.onchange = (e) => {
                    const file = e.target.files[0];
                    if (file) {
                        const reader = new FileReader();
                        reader.onload = (e) => {
                            try {
                                const data = JSON.parse(e.target.result);
                                CODE_LIBRARY.importData(data);
                                this.refreshPanelContent();
                                UTILS.showToast('数据导入成功', 'success');
                            } catch (err) {
                                console.error('Import failed:', err);
                                UTILS.showToast('数据导入失败', 'error');
                            }
                        };
                        reader.readAsText(file);
                    }
                };
                input.click();
            });

            // 修改删除按钮的事件处理
            const self = this;
            this.panelElement.addEventListener('click', function(e) {
                const deleteBtn = e.target.closest('.emh-delete');
                if (deleteBtn) {
                    e.stopPropagation();
                    const item = deleteBtn.closest('.emh-item');
                    const code = item.dataset.code;
                    if (code) {
                        self.showConfirmDialog(`确定要删除番号 ${code} 吗?`, function() {
                            CODE_LIBRARY.delete(code);
                            self.refreshPanelContent();
                            UTILS.showToast(`番号 ${code} 已删除`, 'success');
                        });
                    }
                }
            });

            // 添加清空回收站按钮事件
            const clearTrashBtn = this.panelElement.querySelector('#emh-clear-trash');
            if (clearTrashBtn) {
                clearTrashBtn.addEventListener('click', () => {
                    this.clearTrash();
                });
            }
        },

        // Toggle multi-select mode
        toggleMultiSelectMode: function() {
            this.multiSelectMode = !this.multiSelectMode;
            this.selectedItems = [];

            const actionsBar = this.panelElement.querySelector('.emh-panel-actions');
            const multiActionsBar = this.panelElement.querySelector('.emh-panel-multi-actions');

            if (this.multiSelectMode) {
                actionsBar.style.display = 'none';
                multiActionsBar.style.display = 'flex';
            } else {
                actionsBar.style.display = 'flex';
                multiActionsBar.style.display = 'none';
            }

            this.refreshPanelContent();
        },

        // Refresh panel content based on current filter and search term
        refreshPanelContent: function() {
            const contentArea = this.panelElement.querySelector('.emh-panel-content');
            let items = [];

            // Get items based on current filter
            switch(this.currentFilter) {
                case 'favorite':
                    items = CODE_LIBRARY.getFavorites();
                    break;
                case 'watched':
                    items = CODE_LIBRARY.getWatched();
                    break;
                case 'trash':
                    items = CODE_LIBRARY.getTrash();
                    // 在回收站视图中显示清空按钮
                    this.panelElement.querySelector('#emh-clear-trash').style.display = 'inline-flex';
                    // 隐藏不相关的按钮
                    this.panelElement.querySelector('#emh-add-code').style.display = 'none';
                    this.panelElement.querySelector('#emh-multi-select').style.display = 'none';
                    break;
                default:
                    items = CODE_LIBRARY.getAll();
                    // 在非回收站视图中隐藏清空按钮
                    this.panelElement.querySelector('#emh-clear-trash').style.display = 'none';
                    // 显示常规按钮
                    this.panelElement.querySelector('#emh-add-code').style.display = 'inline-flex';
                    this.panelElement.querySelector('#emh-multi-select').style.display = 'inline-flex';
            }

            // Apply search filter if needed
            if (this.searchQuery) {
                items = items.filter(item =>
                    item.code.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
                    (item.remarks && item.remarks.toLowerCase().includes(this.searchQuery.toLowerCase()))
                );
            }

            // Generate HTML for items
            const itemsHtml = items.map(item => this.generateItemHtml(item)).join('');
            contentArea.innerHTML = itemsHtml || '<div class="emh-empty-state">没有找到相关记录</div>';

            // Update selected count if in multi-select mode
            if (this.multiSelectMode) {
                this.panelElement.querySelector('.emh-selected-count').textContent =
                    `已选择 ${this.selectedItems.length} 项`;
            }

            // Add click handlers for items
            contentArea.querySelectorAll('.emh-item').forEach(item => {
                const code = item.dataset.code;

                if (this.multiSelectMode) {
                    // Multi-select mode click handler
                    item.addEventListener('click', () => {
                        const checkbox = item.querySelector('input[type="checkbox"]');
                        checkbox.checked = !checkbox.checked;

                        if (checkbox.checked) {
                            this.selectedItems.push(code);
                            item.classList.add('selected');
                        } else {
                            this.selectedItems = this.selectedItems.filter(c => c !== code);
                            item.classList.remove('selected');
                        }

                        this.panelElement.querySelector('.emh-selected-count').textContent =
                            `已选择 ${this.selectedItems.length} 项`;
                    });
                } else {
                    // Normal mode - individual action handlers
                    const actions = item.querySelector('.emh-item-actions');
                    if (actions) {
                        actions.querySelector('.emh-mark-favorite').addEventListener('click', (e) => {
                            e.stopPropagation();
                            CODE_LIBRARY.markItem(code, 'favorite');
                            this.refreshPanelContent();
                            UTILS.showToast(`番号 ${code} 已标记为关注`, 'success');
                        });

                        actions.querySelector('.emh-mark-watched').addEventListener('click', (e) => {
                            e.stopPropagation();
                            CODE_LIBRARY.markItem(code, 'watched');
                            this.refreshPanelContent();
                            UTILS.showToast(`番号 ${code} 已标记为已看`, 'success');
                        });

                        actions.querySelector('.emh-delete').addEventListener('click', (e) => {
                            e.stopPropagation();
                            this.showConfirmDialog(`确定要删除番号 ${code} 吗?`, () => {
                                CODE_LIBRARY.delete(code);
                                this.refreshPanelContent();
                                UTILS.showToast(`番号 ${code} 已删除`, 'success');
                            });
                        });
                    }
                }
            });
        },

        // Generate HTML for a single item
        generateItemHtml: function(item) {
            const isSelected = this.selectedItems.includes(item.code);
            const statusClass = item.status === 'favorite' ? 'favorite' :
                              item.status === 'watched' ? 'watched' : '';

            return `
                <div class="emh-item ${statusClass} ${isSelected ? 'selected' : ''}"
                     data-code="${item.code}">
                    ${this.multiSelectMode ? `
                        <input type="checkbox" ${isSelected ? 'checked' : ''} />
                    ` : ''}
                    <div class="emh-item-code">${item.code}</div>
                    <div class="emh-item-remarks">${item.remarks || ''}</div>
                    <div class="emh-item-actions">
                        ${!this.multiSelectMode ? `
                            <button class="emh-mark-favorite">❤️</button>
                            <button class="emh-mark-watched">✓</button>
                            <button class="emh-delete">🗑️</button>
                        ` : ''}
                    </div>
                </div>
            `;
        },

        // Batch operations
        batchMarkItems: function(status) {
            this.selectedItems.forEach(code => {
                CODE_LIBRARY.markItem(code, status);
            });
            this.toggleMultiSelectMode();
            this.refreshPanelContent();
            UTILS.showToast(`已批量标记 ${this.selectedItems.length} 个番号`, 'success');
        },

        batchDeleteItems: function() {
            const self = this;
            this.showConfirmDialog(`确定要删除选中的 ${this.selectedItems.length} 项吗?`, function() {
                self.selectedItems.forEach(code => {
                    CODE_LIBRARY.delete(code);
                });
                self.toggleMultiSelectMode();
                self.refreshPanelContent();
                UTILS.showToast(`已删除 ${self.selectedItems.length} 个番号`, 'success');
            });
        },

        // Show confirmation dialog
        showConfirmDialog: function(message, onConfirm) {
            const modal = this.panelElement.querySelector('.emh-panel-modal');
            const modalTitle = modal.querySelector('h3');
            const confirmBtn = modal.querySelector('.emh-panel-modal-confirm');
            const cancelBtn = modal.querySelector('.emh-panel-modal-cancel');

            modalTitle.textContent = message;
            modal.style.display = 'flex';

            const handleConfirm = () => {
                modal.style.display = 'none';
                onConfirm();
                cleanup();
            };

            const handleCancel = () => {
                modal.style.display = 'none';
                cleanup();
            };

            const cleanup = () => {
                confirmBtn.removeEventListener('click', handleConfirm);
                cancelBtn.removeEventListener('click', handleCancel);
            };

            confirmBtn.addEventListener('click', handleConfirm);
            cancelBtn.addEventListener('click', handleCancel);
        },

        // 添加清空回收站方法
        clearTrash: function() {
            if (!CODE_LIBRARY.trash.items.length) {
                UTILS.showToast('回收站已经是空的', 'info');
                return;
            }

            this.showConfirmDialog('确定要清空回收站吗?此操作不可撤销!', () => {
                CODE_LIBRARY.trash.items = [];
                CODE_LIBRARY.save();
                this.refreshPanelContent();
                UTILS.showToast('回收站已清空', 'success');
            });
        },
    };

    function initialize() {
        addCustomStyles();

        // 加载用户设置
        try {
            const savedSubtitleOptions = localStorage.getItem('emh_subtitle_filename_options');
            if (savedSubtitleOptions) {
                const parsedOptions = JSON.parse(savedSubtitleOptions);

                // 合并保存的设置到CONFIG
                if (parsedOptions) {
                    CONFIG.subtitleFilenameOptions = {
                        ...CONFIG.subtitleFilenameOptions,
                        ...parsedOptions
                    };
                }
            }
        } catch (err) {
            console.error('加载字幕设置失败:', err);
        }

        // 初始化番号库
        CODE_LIBRARY.init();

        UTILS.createDraggableSubtitleButton(); // Create the draggable button

        // 初始化番号管理面板
        if (typeof CodeManagerPanel !== 'undefined') {
            window.CodeManagerPanel = CodeManagerPanel;
            CodeManagerPanel.init();
        }

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', main);
        } else {
            main();
        }
        console.log("EMH Initialized with Enhanced Subtitle Search and Code Manager");

        // 添加自定义事件监听器
        window.addEventListener('emh_library_updated', function(e) {
            if (e.detail.type === 'library_update') {
                // 更新所有状态指示器
                updateCodeStatusIndicators();
                // 如果面板是打开的,刷新面板内容
                if (CodeManagerPanel.isVisible) {
                    CodeManagerPanel.refreshPanelContent();
                }
            }
        });

        // 添加 GM 存储变化监听
        if (typeof GM_addValueChangeListener !== 'undefined') {
            GM_addValueChangeListener('emh_sync_timestamp', function(name, old_value, new_value, remote) {
                if (remote) {
                    CODE_LIBRARY.init();
                    updateCodeStatusIndicators();
                    if (CodeManagerPanel.isVisible) {
                        CodeManagerPanel.refreshPanelContent();
                    }
                }
            });
        }

        // 定期检查更新(作为备用同步机制)
        setInterval(function() {
            if (typeof GM_getValue !== 'undefined') {
                const lastUpdate = GM_getValue('emh_sync_timestamp');
                if (lastUpdate && lastUpdate !== CodeManagerPanel.lastSyncTimestamp) {
                    CodeManagerPanel.lastSyncTimestamp = lastUpdate;
                    CODE_LIBRARY.init();
                    updateCodeStatusIndicators();
                    if (CodeManagerPanel.isVisible) {
                        CodeManagerPanel.refreshPanelContent();
                    }
                }
            }
        }, 2000); // 每2秒检查一次
    }

    initialize();
})();