JAV Blade

单部屏蔽 · 番头屏蔽 · 关键字屏蔽 · 已下载过滤 · 批量导入导出 · 右下角导航

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

Advertisement:

// ==UserScript==
// @name         JAV Blade
// @namespace    https://github.com/yourusername/jav-blade
// @version      3.2
// @author       JAV Blade
// @description  单部屏蔽 · 番头屏蔽 · 关键字屏蔽 · 已下载过滤 · 批量导入导出 · 右下角导航
// @match        https://javdb.com/*
// @match        https://www.javbus.com/*
// @match        https://javdb*.com/*
// @match        https://*javbus*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ============================================================
    // 1. 存储工具
    // ============================================================
    const DY_WATCHED_KEY = 'jav_blade_watched_videos';
    const DY_BLOCKED_KEY = 'jav_blade_blocked_videos';
    const DY_BLOCKED_SERIES_KEY = 'jav_blade_blocked_series';
    const DY_BLOCKED_KEYWORDS_KEY = 'jav_blade_blocked_keywords';
    const DY_FILTER_KEY = 'jav_blade_filter_enabled';
    const DY_BLOCK_SWITCH_KEY = 'jav_blade_block_switch_enabled';

    function getDYWatched() {
        try { return JSON.parse(localStorage.getItem(DY_WATCHED_KEY) || '[]'); } catch { return []; }
    }
    function getDYBlocked() {
        try { return JSON.parse(localStorage.getItem(DY_BLOCKED_KEY) || '[]'); } catch { return []; }
    }
    function getDYBlockedSeries() {
        try { return JSON.parse(localStorage.getItem(DY_BLOCKED_SERIES_KEY) || '[]'); } catch { return []; }
    }
    function getDYBlockedKeywords() {
        try { return JSON.parse(localStorage.getItem(DY_BLOCKED_KEYWORDS_KEY) || '[]'); } catch { return []; }
    }
    function getDYFilter() {
        return localStorage.getItem(DY_FILTER_KEY) === 'true';
    }
    function getDYBlockSwitch() {
        return localStorage.getItem(DY_BLOCK_SWITCH_KEY) !== 'false';
    }
    function setDYFilter(enabled) {
        localStorage.setItem(DY_FILTER_KEY, String(enabled));
    }
    function setDYBlockSwitch(enabled) {
        localStorage.setItem(DY_BLOCK_SWITCH_KEY, String(enabled));
    }
    function setDYWatched(list) {
        localStorage.setItem(DY_WATCHED_KEY, JSON.stringify(list));
    }
    function setDYBlocked(list) {
        localStorage.setItem(DY_BLOCKED_KEY, JSON.stringify(list));
    }
    function setDYBlockedSeries(list) {
        localStorage.setItem(DY_BLOCKED_SERIES_KEY, JSON.stringify(list));
    }
    function setDYBlockedKeywords(list) {
        localStorage.setItem(DY_BLOCKED_KEYWORDS_KEY, JSON.stringify(list));
    }
    function toggleDYWatched(videoId) {
        let list = getDYWatched();
        const idx = list.indexOf(videoId);
        if (idx > -1) list.splice(idx, 1);
        else list.push(videoId);
        setDYWatched(list);
        return list;
    }
    function toggleDYBlocked(videoId) {
        let list = getDYBlocked();
        const idx = list.indexOf(videoId);
        if (idx > -1) list.splice(idx, 1);
        else list.push(videoId);
        setDYBlocked(list);
        return list;
    }
    function toggleDYBlockedSeries(series) {
        const lower = series.toLowerCase();
        let list = getDYBlockedSeries();
        const idx = list.indexOf(lower);
        if (idx > -1) list.splice(idx, 1);
        else list.push(lower);
        setDYBlockedSeries(list);
        return list;
    }
    function toggleDYBlockedKeyword(keyword) {
        const lower = keyword.toLowerCase().trim();
        let list = getDYBlockedKeywords();
        const idx = list.indexOf(lower);
        if (idx > -1) list.splice(idx, 1);
        else list.push(lower);
        setDYBlockedKeywords(list);
        return list;
    }

    function extractDYSeries(carNum) {
        if (!carNum) return null;
        const match = carNum.match(/^([A-Z]{2,})/i);
        if (match) return match[1].toLowerCase();
        const vrMatch = carNum.match(/^(VR)/i);
        if (vrMatch) return vrMatch[1].toLowerCase();
        return null;
    }

    // ============================================================
    // 2. 获取影片信息
    // ============================================================
    function getCarNumFromItem(item) {
        const strong = item.querySelector('.video-title strong');
        if (strong) return strong.textContent.trim();

        const link = item.querySelector('a');
        if (link) {
            const href = link.href || link.getAttribute('href') || '';
            let match = href.match(/\/(?:v\/)?([A-Za-z]{2,}[-_]?\d+)/i);
            if (match) return match[1].toUpperCase();
            match = href.match(/\/(\d{6,}[-_]\d{3,})/);
            if (match) return match[1];
            match = href.match(/\/(\d{8,})/);
            if (match) return match[1];
            const parts = href.split('/');
            const last = parts[parts.length - 1].split(/[?#]/)[0];
            if (/[A-Z]{2,}[-_]?\d+/i.test(last)) return last.toUpperCase();
            if (/\d{6,}[-_]\d{3,}/.test(last)) return last;
            if (/\d{8,}/.test(last)) return last;
        }

        const img = item.querySelector('img');
        if (img) {
            const text = (img.title || img.alt || '');
            let match = text.match(/[A-Z]{2,}[-_]?\d+/i);
            if (match) return match[0].toUpperCase();
            match = text.match(/\d{6,}[-_]\d{3,}/);
            if (match) return match[0];
            match = text.match(/\d{8,}/);
            if (match) return match[0];
        }

        const text = item.textContent;
        let match = text.match(/[A-Z]{2,}[-_]?\d+/i);
        if (match) return match[0].toUpperCase();
        match = text.match(/\d{6,}[-_]\d{3,}/);
        if (match) return match[0];
        match = text.match(/\d{8,}/);
        if (match) return match[0];

        return '';
    }

    function getMovieTitle(item) {
        const titleEl = item.querySelector('.video-title');
        if (titleEl) return titleEl.textContent.trim();
        const link = item.querySelector('a');
        if (link) return link.textContent.trim() || link.title || '';
        const img = item.querySelector('img');
        if (img) return img.title || img.alt || '';
        return '';
    }

    function getMovieItems() {
        const selectors = [
            '.movie-list .item',
            '.masonry .item',
            '.waterfall .item',
            '#waterfall .item',
            '[class*="movie"] [class*="item"]',
            '[class*="video"] [class*="item"]'
        ];
        for (const selector of selectors) {
            const items = document.querySelectorAll(selector);
            if (items.length > 0) return items;
        }
        return [];
    }

    // ============================================================
    // 3. 核心过滤逻辑
    // ============================================================
    function applyDYFilter() {
        const filterEnabled = getDYFilter();
        const blockEnabled = getDYBlockSwitch();
        const watchedList = getDYWatched();
        const blockedVideos = getDYBlocked();
        const blockedSeries = getDYBlockedSeries();
        const blockedKeywords = getDYBlockedKeywords();

        const items = getMovieItems();
        if (items.length === 0) return;

        let total = 0, hidden = 0;
        const toHide = [];

        items.forEach(item => {
            total++;
            const carNum = getCarNumFromItem(item);
            const title = getMovieTitle(item);
            const series = extractDYSeries(carNum);

            let shouldHide = false;

            // 1. 番头屏蔽(系列屏蔽)
            if (blockEnabled && series && blockedSeries.includes(series)) {
                shouldHide = true;
            }

            // 2. 单部屏蔽
            if (!shouldHide && blockEnabled && carNum && blockedVideos.includes(carNum)) {
                shouldHide = true;
            }

            // 3. 关键字屏蔽
            if (!shouldHide && blockEnabled && blockedKeywords.length > 0) {
                const checkText = (carNum + ' ' + title).toLowerCase();
                for (const keyword of blockedKeywords) {
                    if (checkText.includes(keyword)) {
                        shouldHide = true;
                        break;
                    }
                }
            }

            // 4. 已下载过滤
            if (!shouldHide && filterEnabled && carNum && watchedList.includes(carNum)) {
                shouldHide = true;
            }

            if (shouldHide) {
                toHide.push(item);
                hidden++;
            }
        });

        if (toHide.length > 0) {
            const hideClass = 'dy-filter-hidden';
            if (!document.getElementById('dy-filter-style')) {
                const style = document.createElement('style');
                style.id = 'dy-filter-style';
                style.textContent = `
                    .dy-filter-hidden {
                        display: none !important;
                        visibility: hidden !important;
                        height: 0 !important;
                        min-height: 0 !important;
                        padding: 0 !important;
                        margin: 0 !important;
                        border: none !important;
                        overflow: hidden !important;
                        position: absolute !important;
                        pointer-events: none !important;
                        opacity: 0 !important;
                    }
                `;
                document.head.appendChild(style);
            }

            toHide.forEach(item => {
                item.classList.add('dy-filter-hidden');
                item.style.display = '';
                item.style.visibility = '';
                item.style.height = '';
                item.style.minHeight = '';
                item.style.padding = '';
                item.style.margin = '';
                item.style.border = '';
                item.style.overflow = '';
                item.style.position = '';
                item.style.pointerEvents = '';
                item.style.opacity = '';
            });

            items.forEach(item => {
                if (!toHide.includes(item)) {
                    item.classList.remove('dy-filter-hidden');
                    item.style.display = '';
                    item.style.visibility = '';
                }
            });
        } else {
            items.forEach(item => {
                item.classList.remove('dy-filter-hidden');
                item.style.display = '';
                item.style.visibility = '';
            });
        }

        fixWaterfallLayout();

        const statsEl = document.getElementById('dy-stats');
        if (statsEl) {
            const visible = total - hidden;
            const blockStatus = blockEnabled ? '🟢' : '🔴';
            statsEl.textContent = `📊 显示: ${visible}/${total} | ${blockStatus}屏蔽: ${blockedVideos.length}部 ${blockedSeries.length}番头 ${blockedKeywords.length}关键字 | 已下载: ${watchedList.length}`;
        }
    }

    function fixWaterfallLayout() {
        if (typeof $.fn.masonry === 'function') {
            $('.masonry').masonry('layout');
        }
        if (typeof $.fn.isotope === 'function') {
            $('.masonry').isotope('layout');
        }
        window.dispatchEvent(new Event('resize'));

        const containers = document.querySelectorAll('.masonry, .waterfall, #waterfall, .movie-list');
        containers.forEach(container => {
            const currentHeight = container.style.height;
            container.style.height = 'auto';
            void container.offsetHeight;
            if (currentHeight) {
                container.style.height = currentHeight;
            }
        });
    }

    // ============================================================
    // 4. 批量导入解析函数
    // ============================================================
    function parseTXTFile(file) {
        return new Promise((resolve, reject) => {
            if (!file) { reject('未选择文件'); return; }
            if (!file.name.endsWith('.txt')) { reject('请上传 .txt 文件'); return; }

            const reader = new FileReader();
            reader.onload = function(e) {
                const text = e.target.result;
                const lines = text.split('\n')
                    .map(line => line.trim())
                    .filter(line => line.length > 0);

                const results = lines.map(line => {
                    let carNum = null;
                    let url = line;

                    let match = line.match(/\/(?:v\/)?([A-Za-z]{2,}[-_]?\d+)/i);
                    if (match) { carNum = match[1].toUpperCase(); }

                    if (!carNum) {
                        match = line.match(/(\d{6,}[-_]\d{3,})/);
                        if (match) carNum = match[1];
                    }

                    if (!carNum) {
                        match = line.match(/(\d{8,})/);
                        if (match) carNum = match[1];
                    }

                    if (!carNum) {
                        match = line.match(/[A-Z]{2,}[-_]?\d+/i);
                        if (match) carNum = match[0].toUpperCase();
                    }

                    if (!carNum) {
                        match = line.match(/FC2[-_]?(?:PPV-)?(\d+)/i);
                        if (match) carNum = 'FC2-' + match[1];
                    }

                    return {
                        original: line,
                        carNum: carNum,
                        url: line.includes('http') ? line : null,
                        isKeyword: !carNum
                    };
                });

                const validItems = results.filter(item => item.carNum);
                const keywordItems = results.filter(item => !item.carNum);
                const invalidItems = results.filter(item => !item.carNum && item.original.length < 3);

                resolve({
                    valid: validItems,
                    keywordItems: keywordItems.filter(item => item.original.length >= 2),
                    invalid: invalidItems,
                    total: results.length
                });
            };
            reader.onerror = function() {
                reject('读取文件失败');
            };
            reader.readAsText(file);
        });
    }

    // ============================================================
    // 5. 导入屏蔽目录
    // ============================================================
    function openBlockImportDialog() {
        const overlay = document.createElement('div');
        overlay.id = 'dy-block-import-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 9999999;
            display: flex;
            justify-content: center;
            align-items: center;
        `;

        const dialog = document.createElement('div');
        dialog.style.cssText = `
            background: #1a1a2e;
            border-radius: 12px;
            padding: 30px;
            max-width: 700px;
            width: 90%;
            max-height: 85vh;
            overflow-y: auto;
            border: 1px solid #e74c3c;
            color: #ecf0f1;
            font-family: 'Segoe UI', Arial, sans-serif;
        `;

        dialog.innerHTML = `
            <h2 style="color:#e74c3c;margin-bottom:10px;">🚫 导入屏蔽目录</h2>
            <p style="color:#aaa;font-size:13px;margin-bottom:15px;">
                上传TXT文件,每行一个链接、番号或关键字。<br>
                <span style="color:#f39c12;">📌 番号/链接 → 单部屏蔽(只屏蔽当前这一部)</span><br>
                <span style="color:#e67e22;">📌 番号/链接 + 标记【番头】→ 屏蔽整个系列(如 SSIS-001 → 屏蔽所有 SSIS)</span><br>
                <span style="color:#3498db;">📌 关键字 → 屏蔽所有包含该关键字的影片</span>
            </p>
            
            <div style="margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;">
                <div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
                    <input type="file" id="dy-block-file-input" accept=".txt" style="color:#aaa;font-size:13px;">
                    <button id="dy-block-import-btn" style="padding:6px 20px;border:none;border-radius:4px;background:#e74c3c;color:#fff;cursor:pointer;font-size:13px;font-weight:bold;">🚫 导入屏蔽</button>
                </div>
                <div style="margin-top:8px;font-size:11px;color:#888;">
                    💡 提示:在番号后面加上 <span style="color:#e67e22;font-weight:bold;">【番头】</span> 标记可屏蔽整个系列,例如:<span style="color:#aaa;">SSIS-001 【番头】</span>
                </div>
            </div>
            
            <div id="dy-block-preview" style="display:none;margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;max-height:300px;overflow-y:auto;">
                <div style="display:flex;justify-content:space-between;margin-bottom:10px;">
                    <span id="dy-block-count" style="color:#e74c3c;font-weight:bold;">找到 0 个</span>
                    <span id="dy-block-result" style="color:#f39c12;"></span>
                </div>
                <div id="dy-block-list" style="font-size:12px;color:#aaa;word-break:break-all;"></div>
            </div>
            
            <div id="dy-block-result-box" style="display:none;margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;">
                <div id="dy-block-result-text" style="color:#e74c3c;font-weight:bold;"></div>
                <div id="dy-block-error-list" style="font-size:12px;color:#f39c12;margin-top:5px;"></div>
            </div>
            
            <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:10px;">
                <button id="dy-close-block-import" style="padding:8px 24px;border:none;border-radius:6px;background:#34495e;color:#fff;cursor:pointer;font-size:14px;">关闭</button>
            </div>
        `;

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

        const fileInput = document.getElementById('dy-block-file-input');
        const importBtn = document.getElementById('dy-block-import-btn');
        const preview = document.getElementById('dy-block-preview');
        const countEl = document.getElementById('dy-block-count');
        const listEl = document.getElementById('dy-block-list');
        const resultBox = document.getElementById('dy-block-result-box');
        const resultText = document.getElementById('dy-block-result-text');
        const errorList = document.getElementById('dy-block-error-list');

        let parsedValid = [];
        let parsedSeries = [];
        let parsedKeywords = [];
        let parsedInvalid = [];

        fileInput.addEventListener('change', async function(e) {
            const file = this.files[0];
            if (!file) return;

            try {
                const result = await parseTXTFile(file);
                parsedValid = [];
                parsedSeries = [];
                parsedKeywords = [];
                parsedInvalid = [];

                result.valid.forEach(item => {
                    if (item.original.includes('【番头】') || item.original.includes('[番头]')) {
                        parsedSeries.push(item);
                    } else {
                        parsedValid.push(item);
                    }
                });

                parsedKeywords = result.keywordItems;
                parsedInvalid = result.invalid;

                preview.style.display = 'block';
                let countMsg = `✅ 识别到 ${parsedValid.length} 个单部屏蔽`;
                if (parsedSeries.length > 0) {
                    countMsg += `,📦 ${parsedSeries.length} 个番头屏蔽`;
                }
                if (parsedKeywords.length > 0) {
                    countMsg += `,🔑 ${parsedKeywords.length} 个关键字`;
                }
                if (parsedInvalid.length > 0) {
                    countMsg += `,❌ ${parsedInvalid.length} 个无效`;
                }
                countEl.textContent = countMsg;

                if (parsedValid.length > 0 || parsedSeries.length > 0 || parsedKeywords.length > 0) {
                    let listHtml = '';

                    if (parsedValid.length > 0) {
                        const videoList = parsedValid.map(item => item.carNum);
                        const uniqueVideos = [...new Set(videoList)];
                        listHtml += `<div style="color:#f39c12;font-weight:bold;margin-bottom:4px;">🚫 单部屏蔽 (${uniqueVideos.length}部):</div>`;
                        listHtml += uniqueVideos.slice(0, 15).map(v =>
                            `<div style="padding-left:12px;">• ${v}</div>`
                        ).join('');
                        if (uniqueVideos.length > 15) {
                            listHtml += `<div style="padding-left:12px;color:#666;">... 还有 ${uniqueVideos.length - 15} 部</div>`;
                        }
                    }

                    if (parsedSeries.length > 0) {
                        const seriesList = parsedSeries.map(item => {
                            const s = extractDYSeries(item.carNum);
                            return s ? s.toUpperCase() : item.carNum;
                        });
                        const uniqueSeries = [...new Set(seriesList)];
                        listHtml += `<div style="color:#e67e22;font-weight:bold;margin-top:8px;margin-bottom:4px;">📦 番头屏蔽 (${uniqueSeries.length}个系列):</div>`;
                        listHtml += uniqueSeries.slice(0, 15).map(s =>
                            `<div style="padding-left:12px;">• ${s}*</div>`
                        ).join('');
                        if (uniqueSeries.length > 15) {
                            listHtml += `<div style="padding-left:12px;color:#666;">... 还有 ${uniqueSeries.length - 15} 个</div>`;
                        }
                    }

                    if (parsedKeywords.length > 0) {
                        const keywordList = parsedKeywords.map(item => item.original);
                        const uniqueKeywords = [...new Set(keywordList)];
                        listHtml += `<div style="color:#3498db;font-weight:bold;margin-top:8px;margin-bottom:4px;">🔑 屏蔽关键字 (${uniqueKeywords.length}个):</div>`;
                        listHtml += uniqueKeywords.slice(0, 15).map(k =>
                            `<div style="padding-left:12px;">• "${k}"</div>`
                        ).join('');
                        if (uniqueKeywords.length > 15) {
                            listHtml += `<div style="padding-left:12px;color:#666;">... 还有 ${uniqueKeywords.length - 15} 个</div>`;
                        }
                    }

                    listEl.innerHTML = listHtml;
                }

                if (parsedInvalid.length > 0) {
                    listEl.innerHTML += `<div style="color:#f39c12;margin-top:5px;">⚠️ 无效: ${parsedInvalid.slice(0,5).map(i => i.original).join(', ')}${parsedInvalid.length > 5 ? ` ... 等 ${parsedInvalid.length} 个` : ''}</div>`;
                }

                resultBox.style.display = 'none';
            } catch (err) {
                showToast('⚠️ ' + err);
                this.value = '';
            }
        });

        importBtn.addEventListener('click', function() {
            if (parsedValid.length === 0 && parsedSeries.length === 0 && parsedKeywords.length === 0) {
                showToast('⚠️ 没有可导入的内容,请先选择文件');
                return;
            }

            const currentBlocked = getDYBlocked();
            const currentSeries = getDYBlockedSeries();
            const currentKeywords = getDYBlockedKeywords();

            let videoSuccess = 0, videoFail = 0;
            let seriesSuccess = 0, seriesFail = 0;
            let keywordSuccess = 0, keywordFail = 0;
            const failed = [];

            // 单部屏蔽
            const videoList = parsedValid.map(item => item.carNum);
            const uniqueVideos = [...new Set(videoList)];
            uniqueVideos.forEach(video => {
                if (!currentBlocked.includes(video)) {
                    currentBlocked.push(video);
                    videoSuccess++;
                } else {
                    videoFail++;
                    failed.push(video + ' (已存在)');
                }
            });

            // 番头屏蔽
            const seriesList = parsedSeries.map(item => {
                const s = extractDYSeries(item.carNum);
                return s || item.carNum;
            });
            const uniqueSeries = [...new Set(seriesList)];
            uniqueSeries.forEach(series => {
                const lower = series.toLowerCase();
                if (!currentSeries.includes(lower)) {
                    currentSeries.push(lower);
                    seriesSuccess++;
                } else {
                    seriesFail++;
                    failed.push(series.toUpperCase() + '* (已存在)');
                }
            });

            // 关键字屏蔽
            const keywordList = parsedKeywords.map(item => item.original.toLowerCase().trim());
            const uniqueKeywords = [...new Set(keywordList)];
            uniqueKeywords.forEach(keyword => {
                if (!currentKeywords.includes(keyword)) {
                    currentKeywords.push(keyword);
                    keywordSuccess++;
                } else {
                    keywordFail++;
                    failed.push('"' + keyword + '" (已存在)');
                }
            });

            setDYBlocked(currentBlocked);
            setDYBlockedSeries(currentSeries);
            setDYBlockedKeywords(currentKeywords);

            resultBox.style.display = 'block';
            let resultMsg = `✅ 单部: ${videoSuccess} 部`;
            if (videoFail > 0) resultMsg += `,❌ ${videoFail} 部已存在`;
            resultMsg += ` | 📦 番头: ${seriesSuccess} 个`;
            if (seriesFail > 0) resultMsg += `,❌ ${seriesFail} 个已存在`;
            resultMsg += ` | 🔑 关键字: ${keywordSuccess} 个`;
            if (keywordFail > 0) resultMsg += `,❌ ${keywordFail} 个已存在`;
            resultText.textContent = resultMsg;

            if (failed.length > 0 && failed.length <= 10) {
                errorList.textContent = '失败: ' + failed.join('; ');
            } else if (failed.length > 10) {
                errorList.textContent = '失败: ' + failed.slice(0, 10).join('; ') + ` ... 等 ${failed.length} 个`;
            } else {
                errorList.textContent = '';
            }

            setDYBlockSwitch(true);
            showToast(`✅ 导入完成!单部 ${videoSuccess} 部,番头 ${seriesSuccess} 个,关键字 ${keywordSuccess} 个`);
            applyDYFilter();

            fileInput.value = '';
            parsedValid = [];
            parsedSeries = [];
            parsedKeywords = [];
            parsedInvalid = [];
        });

        document.getElementById('dy-close-block-import').onclick = () => overlay.remove();
        overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
    }

    // ============================================================
    // 6. 导入已下载目录
    // ============================================================
    function openWatchedImportDialog() {
        const overlay = document.createElement('div');
        overlay.id = 'dy-watched-import-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 9999999;
            display: flex;
            justify-content: center;
            align-items: center;
        `;

        const dialog = document.createElement('div');
        dialog.style.cssText = `
            background: #1a1a2e;
            border-radius: 12px;
            padding: 30px;
            max-width: 700px;
            width: 90%;
            max-height: 85vh;
            overflow-y: auto;
            border: 1px solid #3498db;
            color: #ecf0f1;
            font-family: 'Segoe UI', Arial, sans-serif;
        `;

        dialog.innerHTML = `
            <h2 style="color:#3498db;margin-bottom:10px;">📥️ 导入已下载目录</h2>
            <p style="color:#aaa;font-size:13px;margin-bottom:15px;">
                上传TXT文件,每行一个链接或番号。<br>
                系统会自动识别番号并标记为"已下载"
            </p>
            
            <div style="margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;">
                <div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
                    <input type="file" id="dy-watched-file-input" accept=".txt" style="color:#aaa;font-size:13px;">
                    <button id="dy-watched-import-btn" style="padding:6px 20px;border:none;border-radius:4px;background:#3498db;color:#fff;cursor:pointer;font-size:13px;font-weight:bold;">📥️ 导入已下载</button>
                </div>
            </div>
            
            <div id="dy-watched-preview" style="display:none;margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;max-height:300px;overflow-y:auto;">
                <div style="display:flex;justify-content:space-between;margin-bottom:10px;">
                    <span id="dy-watched-count" style="color:#3498db;font-weight:bold;">找到 0 个</span>
                    <span id="dy-watched-result" style="color:#f39c12;"></span>
                </div>
                <div id="dy-watched-list" style="font-size:12px;color:#aaa;word-break:break-all;"></div>
            </div>
            
            <div id="dy-watched-result-box" style="display:none;margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;">
                <div id="dy-watched-result-text" style="color:#3498db;font-weight:bold;"></div>
                <div id="dy-watched-error-list" style="font-size:12px;color:#f39c12;margin-top:5px;"></div>
            </div>
            
            <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:10px;">
                <button id="dy-close-watched-import" style="padding:8px 24px;border:none;border-radius:6px;background:#34495e;color:#fff;cursor:pointer;font-size:14px;">关闭</button>
            </div>
        `;

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

        const fileInput = document.getElementById('dy-watched-file-input');
        const importBtn = document.getElementById('dy-watched-import-btn');
        const preview = document.getElementById('dy-watched-preview');
        const countEl = document.getElementById('dy-watched-count');
        const listEl = document.getElementById('dy-watched-list');
        const resultBox = document.getElementById('dy-watched-result-box');
        const resultText = document.getElementById('dy-watched-result-text');
        const errorList = document.getElementById('dy-watched-error-list');

        let parsedValid = [];
        let parsedInvalid = [];

        fileInput.addEventListener('change', async function(e) {
            const file = this.files[0];
            if (!file) return;

            try {
                const result = await parseTXTFile(file);
                parsedValid = result.valid;
                parsedInvalid = result.invalid;

                preview.style.display = 'block';
                countEl.textContent = `✅ 识别到 ${parsedValid.length} 个番号${parsedInvalid.length > 0 ? `,❌ ${parsedInvalid.length} 个无效` : ''}`;

                if (parsedValid.length > 0) {
                    const listHtml = parsedValid.slice(0, 20).map(item =>
                        `<div>• ${item.carNum} ${item.url ? '(链接)' : ''}</div>`
                    ).join('');
                    listEl.innerHTML = listHtml + (parsedValid.length > 20 ? `<div style="color:#666;">... 还有 ${parsedValid.length - 20} 个</div>` : '');
                }

                if (parsedInvalid.length > 0) {
                    listEl.innerHTML += `<div style="color:#f39c12;margin-top:5px;">⚠️ 无效: ${parsedInvalid.slice(0,5).map(i => i.original).join(', ')}${parsedInvalid.length > 5 ? ` ... 等 ${parsedInvalid.length} 个` : ''}</div>`;
                }

                resultBox.style.display = 'none';
            } catch (err) {
                showToast('⚠️ ' + err);
                this.value = '';
            }
        });

        importBtn.addEventListener('click', function() {
            if (parsedValid.length === 0) {
                showToast('⚠️ 没有可导入的番号,请先选择文件');
                return;
            }

            const current = getDYWatched();
            let successCount = 0;
            let failCount = 0;
            const failed = [];

            parsedValid.forEach(item => {
                if (!current.includes(item.carNum)) {
                    current.push(item.carNum);
                    successCount++;
                } else {
                    failCount++;
                    failed.push(item.carNum + ' (已存在)');
                }
            });

            setDYWatched(current);

            resultBox.style.display = 'block';
            resultText.textContent = `✅ 成功导入 ${successCount} 个已下载${failCount > 0 ? `,❌ ${failCount} 个已存在` : ''}`;
            if (failed.length > 0) {
                errorList.textContent = '已存在: ' + failed.slice(0, 10).join('; ') + (failed.length > 10 ? ` ... 等 ${failed.length} 个` : '');
            } else {
                errorList.textContent = '';
            }

            showToast(`✅ 已下载导入完成,成功 ${successCount} 个`);
            applyDYFilter();

            fileInput.value = '';
            parsedValid = [];
            parsedInvalid = [];
        });

        document.getElementById('dy-close-watched-import').onclick = () => overlay.remove();
        overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
    }

    // ============================================================
    // 7. 导出数据对话框
    // ============================================================
    function openExportDialog() {
        const watched = getDYWatched();
        const blocked = getDYBlocked();
        const blockedSeries = getDYBlockedSeries();
        const keywords = getDYBlockedKeywords();

        const overlay = document.createElement('div');
        overlay.id = 'dy-export-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 9999999;
            display: flex;
            justify-content: center;
            align-items: center;
        `;

        const dialog = document.createElement('div');
        dialog.style.cssText = `
            background: #1a1a2e;
            border-radius: 12px;
            padding: 30px;
            max-width: 500px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
            border: 1px solid #2ecc71;
            color: #ecf0f1;
            font-family: 'Segoe UI', Arial, sans-serif;
        `;

        dialog.innerHTML = `
            <h2 style="color:#2ecc71;margin-bottom:15px;">📤 导出数据</h2>
            <p style="color:#aaa;font-size:13px;margin-bottom:15px;">
                选择要导出的数据类型,导出为 TXT 文件,每行一条记录。
            </p>

            <div style="margin-bottom:15px;background:#2c3e50;padding:15px;border-radius:8px;">
                <div style="margin-bottom:10px;">
                    <label style="cursor:pointer;display:flex;align-items:center;gap:10px;">
                        <input type="checkbox" id="dy-export-watched" checked>
                        <span>📥️ 已下载列表 (<span id="dy-export-watched-count">${watched.length}</span>)</span>
                    </label>
                </div>
                <div style="margin-bottom:10px;">
                    <label style="cursor:pointer;display:flex;align-items:center;gap:10px;">
                        <input type="checkbox" id="dy-export-blocked" checked>
                        <span>🚫 单部屏蔽列表 (<span id="dy-export-blocked-count">${blocked.length}</span>)</span>
                    </label>
                </div>
                <div style="margin-bottom:10px;">
                    <label style="cursor:pointer;display:flex;align-items:center;gap:10px;">
                        <input type="checkbox" id="dy-export-series" checked>
                        <span>📦 番头屏蔽列表 (<span id="dy-export-series-count">${blockedSeries.length}</span>)</span>
                    </label>
                </div>
                <div>
                    <label style="cursor:pointer;display:flex;align-items:center;gap:10px;">
                        <input type="checkbox" id="dy-export-keywords" checked>
                        <span>🔑 屏蔽关键字列表 (<span id="dy-export-keywords-count">${keywords.length}</span>)</span>
                    </label>
                </div>
            </div>

            <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:10px;">
                <button id="dy-export-do-btn" style="padding:8px 24px;border:none;border-radius:6px;background:#2ecc71;color:#fff;cursor:pointer;font-size:14px;font-weight:bold;">📤 导出</button>
                <button id="dy-close-export" style="padding:8px 24px;border:none;border-radius:6px;background:#34495e;color:#fff;cursor:pointer;font-size:14px;">关闭</button>
            </div>
        `;

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

        document.getElementById('dy-close-export').onclick = () => overlay.remove();
        overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };

        document.getElementById('dy-export-do-btn').onclick = function() {
            const exportWatched = document.getElementById('dy-export-watched').checked;
            const exportBlocked = document.getElementById('dy-export-blocked').checked;
            const exportSeries = document.getElementById('dy-export-series').checked;
            const exportKeywords = document.getElementById('dy-export-keywords').checked;

            if (!exportWatched && !exportBlocked && !exportSeries && !exportKeywords) {
                showToast('⚠️ 请至少选择一项要导出的数据');
                return;
            }

            let content = '';
            const timestamp = new Date().toLocaleString('zh-CN');

            content += `# JAV Blade 导出数据\n`;
            content += `# 导出时间: ${timestamp}\n`;
            content += `# 注: 每行一条记录,可直接用于导入\n`;
            content += `# 番头屏蔽导入时请在番号后加 【番头】 标记\n\n`;

            if (exportWatched && watched.length > 0) {
                content += `# ========== 已下载列表 (${watched.length}条) ==========\n`;
                content += watched.join('\n');
                content += '\n\n';
            } else if (exportWatched) {
                content += `# 已下载列表: 无数据\n\n`;
            }

            if (exportBlocked && blocked.length > 0) {
                content += `# ========== 单部屏蔽列表 (${blocked.length}条) ==========\n`;
                content += blocked.join('\n');
                content += '\n\n';
            } else if (exportBlocked) {
                content += `# 单部屏蔽列表: 无数据\n\n`;
            }

            if (exportSeries && blockedSeries.length > 0) {
                content += `# ========== 番头屏蔽列表 (${blockedSeries.length}条) ==========\n`;
                content += blockedSeries.map(s => s.toUpperCase() + ' 【番头】').join('\n');
                content += '\n\n';
            } else if (exportSeries) {
                content += `# 番头屏蔽列表: 无数据\n\n`;
            }

            if (exportKeywords && keywords.length > 0) {
                content += `# ========== 屏蔽关键字列表 (${keywords.length}条) ==========\n`;
                content += keywords.join('\n');
                content += '\n\n';
            } else if (exportKeywords) {
                content += `# 屏蔽关键字列表: 无数据\n\n`;
            }

            const dateStr = new Date().toISOString().slice(0, 10);
            const fileName = `JAV_Blade_导出数据_${dateStr}.txt`;

            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            showToast(`✅ 导出成功!共导出 ${(exportWatched ? watched.length : 0) + (exportBlocked ? blocked.length : 0) + (exportSeries ? blockedSeries.length : 0) + (exportKeywords ? keywords.length : 0)} 条数据`);
            overlay.remove();
        };
    }

    // ============================================================
    // 8. 添加按钮到每个影片
    // ============================================================
    function addDYButtons() {
        const items = getMovieItems();
        items.forEach(item => {
            if (item.querySelector('.dy-btn-group')) return;

            const carNum = getCarNumFromItem(item);
            if (!carNum) return;

            const title = getMovieTitle(item);
            const blockEnabled = getDYBlockSwitch();
            const series = extractDYSeries(carNum);
            const isSeriesBlocked = series ? getDYBlockedSeries().includes(series) : false;
            const isBlocked = getDYBlocked().includes(carNum);
            const isWatched = getDYWatched().includes(carNum);
            const blockedKeywords = getDYBlockedKeywords();
            let isKeywordBlocked = false;
            if (blockedKeywords.length > 0) {
                const checkText = (carNum + ' ' + title).toLowerCase();
                for (const kw of blockedKeywords) {
                    if (checkText.includes(kw)) {
                        isKeywordBlocked = true;
                        break;
                    }
                }
            }
            const isBlockedDisplay = isBlocked || isSeriesBlocked || isKeywordBlocked;

            const btnGroup = document.createElement('div');
            btnGroup.className = 'dy-btn-group';
            btnGroup.style.cssText = 'display:flex;gap:6px;margin-top:6px;flex-wrap:wrap;padding:4px 0;';

            // 屏蔽番头按钮
            const seriesBtn = document.createElement('button');
            seriesBtn.textContent = isSeriesBlocked ? '✅ 番头已屏蔽' : '📦 屏蔽番头';
            seriesBtn.style.cssText = `padding:2px 10px;font-size:11px;border:none;border-radius:4px;background:${isSeriesBlocked ? '#95a5a6' : '#e67e22'};color:#fff;cursor:pointer;font-weight:bold;`;
            seriesBtn.title = blockEnabled ? '屏蔽整个系列(所有同番头影片)' : '⚠️ 屏蔽总开关已关闭';
            seriesBtn.style.opacity = blockEnabled ? '1' : '0.5';
            seriesBtn.onclick = (e) => {
                e.stopPropagation();
                e.preventDefault();
                if (!blockEnabled) {
                    showToast('⚠️ 屏蔽总开关已关闭,请先开启');
                    return;
                }
                if (!carNum || !series) {
                    showToast('无法识别番头');
                    return;
                }
                const nowBlocked = getDYBlockedSeries().includes(series);
                toggleDYBlockedSeries(series);
                showToast(nowBlocked ? `已解除番头屏蔽: ${series.toUpperCase()}*` : `已屏蔽番头: ${series.toUpperCase()}*`);
                applyDYFilter();
                const isNowBlocked = getDYBlockedSeries().includes(series);
                seriesBtn.textContent = isNowBlocked ? '✅ 番头已屏蔽' : '📦 屏蔽番头';
                seriesBtn.style.background = isNowBlocked ? '#95a5a6' : '#e67e22';
            };
            btnGroup.appendChild(seriesBtn);

            // 屏蔽单部按钮
            const blockBtn = document.createElement('button');
            blockBtn.textContent = isBlocked ? '✅ 已屏蔽' : '🚫 屏蔽这部';
            blockBtn.style.cssText = `padding:2px 10px;font-size:11px;border:none;border-radius:4px;background:${isBlocked ? '#95a5a6' : '#e74c3c'};color:#fff;cursor:pointer;font-weight:bold;`;
            blockBtn.title = blockEnabled ? '点击屏蔽/解除屏蔽当前这部影片' : '⚠️ 屏蔽总开关已关闭';
            blockBtn.style.opacity = blockEnabled ? '1' : '0.5';
            blockBtn.onclick = (e) => {
                e.stopPropagation();
                e.preventDefault();
                if (!blockEnabled) {
                    showToast('⚠️ 屏蔽总开关已关闭,请先开启');
                    return;
                }
                if (!carNum) {
                    showToast('无法识别番号');
                    return;
                }
                const nowBlocked = getDYBlocked().includes(carNum);
                toggleDYBlocked(carNum);
                showToast(nowBlocked ? `已解除屏蔽: ${carNum}` : `已屏蔽: ${carNum}`);
                applyDYFilter();
                const isNowBlocked = getDYBlocked().includes(carNum);
                blockBtn.textContent = isNowBlocked ? '✅ 已屏蔽' : '🚫 屏蔽这部';
                blockBtn.style.background = isNowBlocked ? '#95a5a6' : '#e74c3c';
            };
            btnGroup.appendChild(blockBtn);

            // 标记已下载按钮
            const watchedBtn = document.createElement('button');
            watchedBtn.textContent = isWatched ? '📥️ 已下载' : '📥️ 标记下载';
            watchedBtn.style.cssText = `padding:2px 10px;font-size:11px;border:none;border-radius:4px;background:${isWatched ? '#2ecc71' : '#3498db'};color:#fff;cursor:pointer;font-weight:bold;`;
            watchedBtn.onclick = (e) => {
                e.stopPropagation();
                e.preventDefault();
                if (!carNum) { showToast('无法识别番号'); return; }
                toggleDYWatched(carNum);
                const nowWatched = getDYWatched().includes(carNum);
                showToast(nowWatched ? `✅ 已标记下载: ${carNum}` : `❌ 已取消标记: ${carNum}`);
                watchedBtn.textContent = nowWatched ? '📥️ 已下载' : '📥️ 标记下载';
                watchedBtn.style.background = nowWatched ? '#2ecc71' : '#3498db';
                if (getDYFilter()) applyDYFilter();
            };
            btnGroup.appendChild(watchedBtn);

            // 显示番号和标记
            const idLabel = document.createElement('span');
            let labelText = `📌 ${carNum}`;
            if (isSeriesBlocked && blockEnabled) labelText += ' 📦';
            if (isKeywordBlocked && blockEnabled) labelText += ' 🔑';
            idLabel.textContent = labelText;
            idLabel.style.cssText = `font-size:10px;color:${(isKeywordBlocked || isSeriesBlocked) && blockEnabled ? '#f39c12' : '#888'};margin-left:4px;`;
            btnGroup.appendChild(idLabel);

            const targets = [
                item.querySelector('.video-title'),
                item.querySelector('.title'),
                item.querySelector('.photo-info'),
                item.querySelector('.meta'),
                item.querySelector('.tags')
            ];
            let inserted = false;
            for (const target of targets) {
                if (target) {
                    target.after(btnGroup);
                    inserted = true;
                    break;
                }
            }
            if (!inserted) {
                item.appendChild(btnGroup);
            }
        });
    }

    // ============================================================
    // 9. 顶部工具栏
    // ============================================================
    function addDYTopBar() {
        if (document.getElementById('dy-top-bar')) return;

        const topBar = document.createElement('div');
        topBar.id = 'dy-top-bar';
        topBar.style.cssText = `
            position: sticky;
            top: 0;
            z-index: 999998;
            background: #1a1a2e;
            padding: 8px 20px;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
            border-bottom: 2px solid #e94560;
            font-family: 'Segoe UI', Arial, sans-serif;
        `;

        const logo = document.createElement('span');
        logo.textContent = '🔪 JAV Blade';
        logo.style.cssText = 'color:#e94560;font-weight:bold;font-size:14px;user-select:none;';
        topBar.appendChild(logo);

        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'dy-toggle-btn';
        const isEnabled = getDYFilter();
        toggleBtn.textContent = isEnabled ? '🟢 已下载过滤(开)' : '🔴 已下载过滤(关)';
        toggleBtn.style.cssText = `padding:4px 14px;border:none;border-radius:4px;color:#fff;font-size:12px;font-weight:bold;cursor:pointer;background:${isEnabled ? '#27ae60' : '#e74c3c'};`;
        toggleBtn.title = '开启后隐藏已下载的影片';
        toggleBtn.onclick = () => {
            const current = getDYFilter();
            setDYFilter(!current);
            const nowEnabled = getDYFilter();
            toggleBtn.textContent = nowEnabled ? '🟢 已下载过滤(开)' : '🔴 已下载过滤(关)';
            toggleBtn.style.background = nowEnabled ? '#27ae60' : '#e74c3c';
            applyDYFilter();
            showToast(nowEnabled ? '✅ 已开启过滤,隐藏已下载的影片' : '❌ 已关闭过滤,显示所有影片');
        };
        topBar.appendChild(toggleBtn);

        const blockSwitchBtn = document.createElement('button');
        blockSwitchBtn.id = 'dy-block-switch-btn';
        const blockEnabled = getDYBlockSwitch();
        blockSwitchBtn.textContent = blockEnabled ? '🟢 屏蔽总开关(开)' : '🔴 屏蔽总开关(关)';
        blockSwitchBtn.style.cssText = `padding:4px 14px;border:none;border-radius:4px;color:#fff;font-size:12px;font-weight:bold;cursor:pointer;background:${blockEnabled ? '#27ae60' : '#e74c3c'};`;
        blockSwitchBtn.title = '开启后屏蔽所有匹配的影片(单部屏蔽 + 番头屏蔽 + 关键字屏蔽)';
        blockSwitchBtn.onclick = () => {
            const current = getDYBlockSwitch();
            setDYBlockSwitch(!current);
            const nowEnabled = getDYBlockSwitch();
            blockSwitchBtn.textContent = nowEnabled ? '🟢 屏蔽总开关(开)' : '🔴 屏蔽总开关(关)';
            blockSwitchBtn.style.background = nowEnabled ? '#27ae60' : '#e74c3c';
            applyDYFilter();
            showToast(nowEnabled ? '✅ 已开启屏蔽总开关' : '❌ 已关闭屏蔽总开关');
        };
        topBar.appendChild(blockSwitchBtn);

        const stats = document.createElement('span');
        stats.id = 'dy-stats';
        stats.style.cssText = 'color:#aaa;font-size:11px;flex:1;min-width:80px;';
        topBar.appendChild(stats);

        const sep1 = document.createElement('span');
        sep1.textContent = '|';
        sep1.style.cssText = 'color:#555;font-size:12px;';
        topBar.appendChild(sep1);

        const blockImportBtn = document.createElement('button');
        blockImportBtn.textContent = '🚫 导入屏蔽目录';
        blockImportBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;background:#e74c3c;color:#fff;font-size:11px;cursor:pointer;font-weight:bold;';
        blockImportBtn.title = '导入TXT:番号→单部屏蔽 | 番号+【番头】→屏蔽整个系列 | 关键字→屏蔽所有包含该关键字的影片';
        blockImportBtn.onclick = openBlockImportDialog;
        topBar.appendChild(blockImportBtn);

        const watchedImportBtn = document.createElement('button');
        watchedImportBtn.textContent = '📥️ 导入已下载目录';
        watchedImportBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;background:#3498db;color:#fff;font-size:11px;cursor:pointer;font-weight:bold;';
        watchedImportBtn.onclick = openWatchedImportDialog;
        topBar.appendChild(watchedImportBtn);

        const exportBtn = document.createElement('button');
        exportBtn.textContent = '📤 导出数据';
        exportBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;background:#2ecc71;color:#fff;font-size:11px;cursor:pointer;font-weight:bold;';
        exportBtn.title = '导出已下载、屏蔽列表到TXT文件';
        exportBtn.onclick = openExportDialog;
        topBar.appendChild(exportBtn);

        const sep2 = document.createElement('span');
        sep2.textContent = '|';
        sep2.style.cssText = 'color:#555;font-size:12px;';
        topBar.appendChild(sep2);

        const manageBtn = document.createElement('button');
        manageBtn.textContent = '⚙️ 管理列表';
        manageBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;background:#34495e;color:#ecf0f1;font-size:11px;cursor:pointer;';
        manageBtn.onclick = openDYManageDialog;
        topBar.appendChild(manageBtn);

        const clearBtn = document.createElement('button');
        clearBtn.textContent = '🗑️ 清空';
        clearBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;background:#c0392b;color:#fff;font-size:11px;cursor:pointer;';
        clearBtn.onclick = () => {
            if (confirm('确定要清空所有数据吗?(已下载列表 + 单部屏蔽列表 + 番头屏蔽列表 + 屏蔽关键字列表)')) {
                localStorage.removeItem(DY_WATCHED_KEY);
                localStorage.removeItem(DY_BLOCKED_KEY);
                localStorage.removeItem(DY_BLOCKED_SERIES_KEY);
                localStorage.removeItem(DY_BLOCKED_KEYWORDS_KEY);
                localStorage.removeItem(DY_FILTER_KEY);
                localStorage.removeItem(DY_BLOCK_SWITCH_KEY);
                showToast('已清空所有数据,页面即将刷新');
                setTimeout(() => location.reload(), 500);
            }
        };
        topBar.appendChild(clearBtn);

        const fixBtn = document.createElement('button');
        fixBtn.textContent = '🔄 修复布局';
        fixBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;background:#6c5ce7;color:#fff;font-size:11px;cursor:pointer;';
        fixBtn.title = '如果出现黑屏或布局错乱,点击修复';
        fixBtn.onclick = () => {
            fixWaterfallLayout();
            applyDYFilter();
            showToast('✅ 已修复布局');
        };
        topBar.appendChild(fixBtn);

        const targets = [
            document.querySelector('.navbar'),
            document.querySelector('.top-bar'),
            document.querySelector('.header'),
            document.querySelector('.main-nav'),
            document.querySelector('.masonry')
        ];
        let inserted = false;
        for (const target of targets) {
            if (target) {
                target.parentNode.insertBefore(topBar, target);
                inserted = true;
                break;
            }
        }
        if (!inserted) {
            document.body.prepend(topBar);
        }

        setTimeout(applyDYFilter, 300);
    }

    // ============================================================
    // 10. 右下角导航按钮
    // ============================================================
    function addScrollButtons() {
        if (document.getElementById('dy-scroll-buttons')) return;

        const container = document.createElement('div');
        container.id = 'dy-scroll-buttons';
        container.style.cssText = `
            position: fixed;
            bottom: 30px;
            right: 20px;
            z-index: 999997;
            display: flex;
            flex-direction: column;
            gap: 8px;
        `;

        // 回到顶部
        const topBtn = document.createElement('button');
        topBtn.id = 'dy-scroll-top';
        topBtn.innerHTML = '⬆';
        topBtn.style.cssText = `
            width: 44px;
            height: 44px;
            border: none;
            border-radius: 50%;
            background: rgba(233, 69, 96, 0.85);
            color: #fff;
            font-size: 22px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            user-select: none;
            backdrop-filter: blur(4px);
        `;
        topBtn.title = '回到顶部';
        topBtn.onmouseenter = () => {
            topBtn.style.background = 'rgba(233, 69, 96, 1)';
            topBtn.style.transform = 'scale(1.1)';
        };
        topBtn.onmouseleave = () => {
            topBtn.style.background = 'rgba(233, 69, 96, 0.85)';
            topBtn.style.transform = 'scale(1)';
        };
        topBtn.onclick = () => {
            window.scrollTo({ top: 0, behavior: 'smooth' });
        };

        // 回到底部
        const bottomBtn = document.createElement('button');
        bottomBtn.id = 'dy-scroll-bottom';
        bottomBtn.innerHTML = '⬇';
        bottomBtn.style.cssText = `
            width: 44px;
            height: 44px;
            border: none;
            border-radius: 50%;
            background: rgba(52, 152, 219, 0.85);
            color: #fff;
            font-size: 22px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            user-select: none;
            backdrop-filter: blur(4px);
        `;
        bottomBtn.title = '回到底部';
        bottomBtn.onmouseenter = () => {
            bottomBtn.style.background = 'rgba(52, 152, 219, 1)';
            bottomBtn.style.transform = 'scale(1.1)';
        };
        bottomBtn.onmouseleave = () => {
            bottomBtn.style.background = 'rgba(52, 152, 219, 0.85)';
            bottomBtn.style.transform = 'scale(1)';
        };
        bottomBtn.onclick = () => {
            window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
        };

        container.appendChild(topBtn);
        container.appendChild(bottomBtn);
        document.body.appendChild(container);

        let hideTimeout;
        window.addEventListener('scroll', function() {
            const scrollY = window.scrollY;
            const maxScroll = document.body.scrollHeight - window.innerHeight;

            if (scrollY > 100) {
                topBtn.style.opacity = '1';
                topBtn.style.pointerEvents = 'auto';
            } else {
                topBtn.style.opacity = '0.3';
                topBtn.style.pointerEvents = 'none';
            }

            if (maxScroll - scrollY > 100) {
                bottomBtn.style.opacity = '1';
                bottomBtn.style.pointerEvents = 'auto';
            } else {
                bottomBtn.style.opacity = '0.3';
                bottomBtn.style.pointerEvents = 'none';
            }

            clearTimeout(hideTimeout);
            container.style.opacity = '1';
            hideTimeout = setTimeout(() => {
                container.style.opacity = '0.6';
            }, 3000);
        });

        container.onmouseenter = () => {
            container.style.opacity = '1';
            clearTimeout(hideTimeout);
        };
        container.onmouseleave = () => {
            container.style.opacity = '0.6';
        };

        setTimeout(() => {
            const scrollY = window.scrollY;
            const maxScroll = document.body.scrollHeight - window.innerHeight;
            if (scrollY <= 100) {
                topBtn.style.opacity = '0.3';
                topBtn.style.pointerEvents = 'none';
            }
            if (maxScroll - scrollY <= 100) {
                bottomBtn.style.opacity = '0.3';
                bottomBtn.style.pointerEvents = 'none';
            }
            container.style.opacity = '0.6';
        }, 500);
    }

    // ============================================================
    // 11. 管理对话框
    // ============================================================
    function openDYManageDialog() {
        const blocked = getDYBlocked();
        const blockedSeries = getDYBlockedSeries();
        const keywords = getDYBlockedKeywords();
        const watched = getDYWatched();

        const overlay = document.createElement('div');
        overlay.id = 'dy-manage-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 9999999;
            display: flex;
            justify-content: center;
            align-items: center;
        `;

        const dialog = document.createElement('div');
        dialog.style.cssText = `
            background: #1a1a2e;
            border-radius: 12px;
            padding: 30px;
            max-width: 700px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
            border: 1px solid #e94560;
            color: #ecf0f1;
            font-family: 'Segoe UI', Arial, sans-serif;
        `;

        const blockedHtml = blocked.length === 0
            ? '<span style="color:#999;">暂无屏蔽的影片</span>'
            : blocked.map(v =>
                `<span style="display:inline-block;background:#e74c3c;color:#fff;padding:4px 12px;border-radius:4px;margin:4px;font-size:13px;">${v} <span style="cursor:pointer;margin-left:6px;font-weight:bold;" onclick="document.dispatchEvent(new CustomEvent('dy-unblock', {detail:{video:'${v}'}}))">✕</span></span>`
            ).join('');

        const seriesHtml = blockedSeries.length === 0
            ? '<span style="color:#999;">暂无屏蔽的番头</span>'
            : blockedSeries.map(s =>
                `<span style="display:inline-block;background:#e67e22;color:#fff;padding:4px 12px;border-radius:4px;margin:4px;font-size:13px;">📦 ${s.toUpperCase()}* <span style="cursor:pointer;margin-left:6px;font-weight:bold;" onclick="document.dispatchEvent(new CustomEvent('dy-unblock-series', {detail:{series:'${s}'}}))">✕</span></span>`
            ).join('');

        const keywordsHtml = keywords.length === 0
            ? '<span style="color:#999;">暂无屏蔽的关键字</span>'
            : keywords.map(k =>
                `<span style="display:inline-block;background:#f39c12;color:#fff;padding:4px 12px;border-radius:4px;margin:4px;font-size:13px;">🔑 "${k}" <span style="cursor:pointer;margin-left:6px;font-weight:bold;" onclick="document.dispatchEvent(new CustomEvent('dy-unblock-keyword', {detail:{keyword:'${k}'}}))">✕</span></span>`
            ).join('');

        const watchedHtml = watched.length === 0
            ? '<span style="color:#999;">暂无已下载的影片</span>'
            : watched.map(id =>
                `<span style="display:inline-block;background:#3498db;color:#fff;padding:4px 12px;border-radius:4px;margin:4px;font-size:13px;">${id} <span style="cursor:pointer;margin-left:6px;font-weight:bold;" onclick="document.dispatchEvent(new CustomEvent('dy-unwatch', {detail:{videoId:'${id}'}}))">✕</span></span>`
            ).join('');

        dialog.innerHTML = `
            <h2 style="color:#e94560;margin-bottom:20px;">⚙️ 管理屏蔽与下载</h2>
            
            <div style="margin-bottom:20px;">
                <h3 style="color:#e74c3c;font-size:14px;margin-bottom:10px;">🚫 单部屏蔽 (${blocked.length})</h3>
                <div style="background:#2c3e50;padding:12px;border-radius:6px;min-height:40px;">${blockedHtml}</div>
            </div>
            
            <div style="margin-bottom:20px;">
                <h3 style="color:#e67e22;font-size:14px;margin-bottom:10px;">📦 番头屏蔽 (${blockedSeries.length})</h3>
                <div style="background:#2c3e50;padding:12px;border-radius:6px;min-height:40px;">${seriesHtml}</div>
            </div>
            
            <div style="margin-bottom:20px;">
                <h3 style="color:#f39c12;font-size:14px;margin-bottom:10px;">🔑 屏蔽关键字 (${keywords.length})</h3>
                <div style="background:#2c3e50;padding:12px;border-radius:6px;min-height:40px;">${keywordsHtml}</div>
            </div>
            
            <div style="margin-bottom:20px;">
                <h3 style="color:#3498db;font-size:14px;margin-bottom:10px;">📥️ 已下载影片 (${watched.length})</h3>
                <div style="background:#2c3e50;padding:12px;border-radius:6px;min-height:40px;">${watchedHtml}</div>
            </div>
            
            <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:20px;">
                <button id="dy-close-manage" style="padding:8px 24px;border:none;border-radius:6px;background:#34495e;color:#fff;cursor:pointer;font-size:14px;">关闭</button>
            </div>
        `;

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

        document.getElementById('dy-close-manage').onclick = () => overlay.remove();
        overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };

        document.addEventListener('dy-unblock', (e) => {
            const video = e.detail.video;
            let list = getDYBlocked();
            list = list.filter(v => v !== video);
            setDYBlocked(list);
            overlay.remove();
            applyDYFilter();
            showToast(`已解除屏蔽: ${video}`);
            openDYManageDialog();
        }, { once: true });

        document.addEventListener('dy-unblock-series', (e) => {
            const series = e.detail.series;
            let list = getDYBlockedSeries();
            list = list.filter(s => s !== series);
            setDYBlockedSeries(list);
            overlay.remove();
            applyDYFilter();
            showToast(`已解除番头屏蔽: ${series.toUpperCase()}*`);
            openDYManageDialog();
        }, { once: true });

        document.addEventListener('dy-unblock-keyword', (e) => {
            const keyword = e.detail.keyword;
            let list = getDYBlockedKeywords();
            list = list.filter(k => k !== keyword);
            setDYBlockedKeywords(list);
            overlay.remove();
            applyDYFilter();
            showToast(`已解除屏蔽关键字: "${keyword}"`);
            openDYManageDialog();
        }, { once: true });

        document.addEventListener('dy-unwatch', (e) => {
            const videoId = e.detail.videoId;
            let list = getDYWatched();
            list = list.filter(id => id !== videoId);
            setDYWatched(list);
            overlay.remove();
            applyDYFilter();
            showToast(`已取消标记: ${videoId}`);
            openDYManageDialog();
        }, { once: true });
    }

    // ============================================================
    // 12. Toast 提示
    // ============================================================
    function showToast(msg, duration = 2000) {
        const old = document.getElementById('dy-toast');
        if (old) old.remove();

        const toast = document.createElement('div');
        toast.id = 'dy-toast';
        toast.textContent = msg;
        toast.style.cssText = `
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.85);
            color: #fff;
            padding: 12px 28px;
            border-radius: 8px;
            font-size: 14px;
            z-index: 99999999;
            box-shadow: 0 4px 20px rgba(0,0,0,0.4);
            font-family: 'Segoe UI', Arial, sans-serif;
            max-width: 90%;
            text-align: center;
            animation: fadeIn 0.3s ease;
        `;

        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateX(-50%) translateY(20px); }
                to { opacity: 1; transform: translateX(-50%) translateY(0); }
            }
        `;
        document.head.appendChild(style);

        document.body.appendChild(toast);
        setTimeout(() => {
            toast.style.opacity = '0';
            toast.style.transition = 'opacity 0.3s';
            setTimeout(() => toast.remove(), 300);
        }, duration);
    }

    // ============================================================
    // 13. 油猴菜单命令
    // ============================================================
    function registerMenuCommand() {
        GM_registerMenuCommand('💾 快速导出备份 (JAV Blade)', function() {
            const watched = getDYWatched();
            const blocked = getDYBlocked();
            const blockedSeries = getDYBlockedSeries();
            const keywords = getDYBlockedKeywords();

            let content = `# JAV Blade 数据备份\n`;
            content += `# 导出时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
            content += `# 已下载列表 (${watched.length}条)\n${watched.join('\n')}\n\n`;
            content += `# 单部屏蔽列表 (${blocked.length}条)\n${blocked.join('\n')}\n\n`;
            content += `# 番头屏蔽列表 (${blockedSeries.length}条)\n${blockedSeries.map(s => s.toUpperCase()).join('\n')}\n\n`;
            content += `# 屏蔽关键字列表 (${keywords.length}条)\n${keywords.join('\n')}`;

            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `JAV_Blade_备份_${new Date().toISOString().slice(0,10)}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            showToast('✅ 数据已导出,放心删除脚本吧!');
        });
    }

    // ============================================================
    // 14. 监听DOM变化
    // ============================================================
    let observer = null;

    function observeChanges() {
        if (observer) observer.disconnect();

        observer = new MutationObserver(() => {
            const items = getMovieItems();
            let hasNew = false;
            items.forEach(item => {
                if (!item.querySelector('.dy-btn-group')) {
                    hasNew = true;
                }
            });
            if (hasNew) {
                addDYButtons();
                applyDYFilter();
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // ============================================================
    // 15. 初始化
    // ============================================================
    function init() {
        console.log('🔪 JAV Blade v3.2 加载中...');

        try {
            registerMenuCommand();
        } catch (e) {}

        let attempts = 0;
        const maxAttempts = 30;
        const check = setInterval(() => {
            attempts++;
            const hasItems = getMovieItems().length > 0;
            if (hasItems || attempts >= maxAttempts) {
                clearInterval(check);
                console.log('✅ JAV Blade v3.2 已完全加载!');
                addDYTopBar();
                addScrollButtons();
                setTimeout(() => {
                    addDYButtons();
                    applyDYFilter();
                    observeChanges();
                    console.log('📖 功能列表:');
                    console.log('  📦 屏蔽番头 - 屏蔽整个系列');
                    console.log('  🚫 屏蔽这部 - 只屏蔽当前一部影片');
                    console.log('  🔑 关键字屏蔽 - 包含即屏蔽');
                    console.log('  📥️ 标记下载 - 标记为已下载');
                    console.log('  📤 导出数据 - 导出所有列表到TXT');
                    console.log('  ⬆⬇ 右下角导航 - 快速回到顶部/底部');
                    console.log('  💾 油猴菜单 - 点击油猴图标 → JAV Blade → 快速导出备份');
                }, 500);
            }
        }, 300);
    }

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

    // ============================================================
    // 16. 暴露到全局
    // ============================================================
    unsafeWindow.JAV_BLADE = {
        getWatched: getDYWatched,
        getBlocked: getDYBlocked,
        getBlockedSeries: getDYBlockedSeries,
        getBlockedKeywords: getDYBlockedKeywords,
        getFilter: getDYFilter,
        getBlockSwitch: getDYBlockSwitch,
        setFilter: setDYFilter,
        setBlockSwitch: setDYBlockSwitch,
        toggleWatched: toggleDYWatched,
        toggleBlocked: toggleDYBlocked,
        toggleBlockedSeries: toggleDYBlockedSeries,
        toggleBlockedKeyword: toggleDYBlockedKeyword,
        applyFilter: applyDYFilter,
        fixLayout: fixWaterfallLayout,
        showToast: showToast,
        openBlockImport: openBlockImportDialog,
        openWatchedImport: openWatchedImportDialog,
        openExport: openExportDialog
    };

    console.log('📖 在控制台输入 JAV_BLADE 可查看所有功能');

})();