JAV Blade

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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 可查看所有功能');

})();