DBD-RawsBanHelper

过滤动漫花园、末日动漫、Nyaa和蜜柑计划中的DBD-Raws内容,并修复行颜色问题

// ==UserScript==
// @name         DBD-RawsBanHelper
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  过滤动漫花园、末日动漫、Nyaa和蜜柑计划中的DBD-Raws内容,并修复行颜色问题
// @description  2.3更新内容:发现蜜柑计划番剧详情页面(https://mikanani.kas.pub/Home/Bangumi/XXXX)中也有残留
// @author       Fuck DBD-Raws
// @license      MIT
// @match        *://share.dmhy.org/*
// @match        *://share.acgnx.se/*
// @match        *://nyaa.land/*
// @match        *://nyaa.si/*
// @match        *://mikanani.me/*
// @match        *://mikanani.kas.pub/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log('DBD-RawsBanHelper: 脚本开始执行');

    // 配置参数
    const config = {
        targetKeywords: ['DBD-Raws', 'DBD制作组', 'DBD製作組','DBD转发','DBD轉發','DBD-SUB','DBD字幕组','DBD字幕組','DBD代发','DBD代發','DBD代传','DBD代傳','DBD转载','DBD轉載','DBD自购','DBD自購','DBD&','&DBD','[DBD]'], // 要过滤的关键词列表
        filterClass: 'dmhy-filtered', // 用于标记已过滤元素的类名
        showNotification: true, // 是否显示过滤通知
        notificationDuration: 3000 // 通知显示时间(毫秒)
    };

    // 添加通知相关的CSS样式
    const style = document.createElement('style');
    style.textContent = `
        #dmhy-filter-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #ff4757;
            color: white;
            padding: 12px 18px;
            border-radius: 4px;
            z-index: 10000;
            font-family: Arial, sans-serif;
            font-size: 14px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            transition: opacity 0.5s ease;
            cursor: pointer;
        }

        #dmhy-filter-notification:hover {
            opacity: 1 !important;
        }

        /* 蜜柑计划专用样式 */
        .mikanani-notification {
            top: 70px !important;
            z-index: 9999;
        }
    `;
    document.head.appendChild(style);

    // 全局变量用于存储通知的定时器
    let notificationTimer = null;
    let hasExecutedStaticFilter = false; // 标记静态页面是否已执行过滤
    let frameObservers = new Map(); // 存储各个frame的Observer实例

    // 检查文本是否包含任何目标关键词,并返回匹配到的关键词
    function containsTargetText(text) {
        const matchedKeywords = [];
        config.targetKeywords.forEach(keyword => {
            if (text.includes(keyword)) {
                matchedKeywords.push(keyword);
            }
        });
        return matchedKeywords.length > 0 ? matchedKeywords : false;
    }

    // 检查是否为蜜柑计划网站
    function isMikananiSite() {
        return window.location.hostname.includes('mikanani') ||
               window.location.hostname.includes('mikanime');
    }

    // 检查是否为蜜柑计划列表模式
    function isMikananiClassicMode() {
        return window.location.href.includes('/Home/Classic');
    }

    // 检查是否为动态加载页面(仅mikanani.kas.pub)
    function isDynamicPage() {
        return window.location.hostname === 'mikanani.kas.pub' && !window.location.href.includes('/Home');
    }

    // 等待页面加载完成
    window.addEventListener('load', function() {
        console.log('DBD-RawsBanHelper: 页面加载完成,开始初始化');

        // 延迟执行以确保所有内容都已加载
        setTimeout(() => {
            if (isDynamicPage()) {
                console.log('DBD-RawsBanHelper: 检测到动态加载页面,设置观察器');
                setupDynamicPageObserver();
            } else {
                console.log('DBD-RawsBanHelper: 检测到静态页面,执行一次过滤');
                filterContent();
                hasExecutedStaticFilter = true;
            }
        }, 1000);
    });

    // 设置动态页面观察器
    function setupDynamicPageObserver() {
        // 先执行一次初始过滤
        filterContent();

        // 设置点击事件监听器来监听新的frame加载
        setupClickListeners();

        // 观察已存在的frame
        observeExistingFrames();

        // 设置全局观察器来监听新frame的添加
        setupGlobalObserver();
    }

    // 设置点击事件监听器
    function setupClickListeners() {
        // 监听an-box animated中的li标签点击
        document.addEventListener('click', function(e) {
            const liElement = e.target.closest('.an-box.animated li');
            if (liElement) {
                console.log('DBD-RawsBanHelper: 检测到an-box animated中的li标签点击');

                // 设置一个延迟来等待可能的frame加载,然后开始观察
                setTimeout(() => {
                    observeExistingFrames();
                }, 100);
            }
        });
    }

    // 观察已存在的frame
    function observeExistingFrames() {
        const frames = document.querySelectorAll('.row.an-res-row-frame');
        console.log(`DBD-RawsBanHelper: 找到 ${frames.length} 个frame元素`);

        frames.forEach((frame, index) => {
            if (!frameObservers.has(frame)) {
                observeFrameContent(frame);
            }
        });
    }

    // 观察单个frame的内容变化
    function observeFrameContent(frame) {
        // 如果已经在这个frame上设置了观察器,则跳过
        if (frameObservers.has(frame)) {
            return;
        }

        // 先立即执行一次过滤
        filterFrameContent(frame);

        // 创建MutationObserver来监听frame内部的内容变化
        const observer = new MutationObserver(function(mutations) {
            let shouldFilter = false;

            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList') {
                    // 检查是否有新的子元素添加
                    if (mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach(function(node) {
                            if (node.nodeType === 1) { // Element node
                                // 检查是否添加了包含资源信息的元素
                                if (node.classList && (
                                    node.classList.contains('sk-col') ||
                                    node.classList.contains('tag-res-name') ||
                                    node.classList.contains('anime-res-block') ||
                                    node.tagName.toLowerCase() === 'li' ||
                                    node.tagName.toLowerCase() === 'div'
                                )) {
                                    shouldFilter = true;
                                } else if (node.querySelectorAll) {
                                    // 检查子元素中是否包含资源相关元素
                                    const resourceElements = node.querySelectorAll('.sk-col, .tag-res-name, .anime-res-block, li, div');
                                    if (resourceElements.length > 0) {
                                        shouldFilter = true;
                                    }
                                }
                            }
                        });
                    }
                }
            });

            if (shouldFilter) {
                console.log('DBD-RawsBanHelper: 检测到frame内容变化,执行过滤');
                // 使用防抖机制,避免频繁过滤
                clearTimeout(frame.filterTimeout);
                frame.filterTimeout = setTimeout(() => {
                    filterFrameContent(frame);
                }, 100);
            }
        });

        // 开始观察frame内部的变化
        observer.observe(frame, {
            childList: true,
            subtree: true,
            characterData: true
        });

        // 存储观察器实例
        frameObservers.set(frame, observer);
    }

    // 设置全局观察器来监听新frame的添加
    function setupGlobalObserver() {
        const globalObserver = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    mutation.addedNodes.forEach(function(node) {
                        if (node.nodeType === 1) {
                            // 检查是否添加了新的frame
                            if (node.classList && node.classList.contains('row') && node.classList.contains('an-res-row-frame')) {
                                console.log('DBD-RawsBanHelper: 检测到新frame添加');
                                // 给新frame一点时间加载内容
                                setTimeout(() => {
                                    observeFrameContent(node);
                                }, 200);
                            }
                            // 检查子元素中是否包含新的frame
                            const newFrames = node.querySelectorAll ? node.querySelectorAll('.row.an-res-row-frame') : [];
                            newFrames.forEach(newFrame => {
                                console.log('DBD-RawsBanHelper: 检测到子元素中的新frame');
                                setTimeout(() => {
                                    observeFrameContent(newFrame);
                                }, 200);
                            });
                        }
                    });
                }
            });
        });

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

        console.log('DBD-RawsBanHelper: 全局观察器已启动');
    }

    // 过滤单个frame的内容
    function filterFrameContent(frame) {
        let removedCount = 0;
        let matchedKeywords = new Set();

        // 查找frame中的所有资源元素
        const resourceElements = frame.querySelectorAll('div.sk-col.tag-res-name, li, a, span, .anime-res-block, .resource-item');

        resourceElements.forEach(element => {
            // 检查元素是否已经被过滤
            if (element.classList.contains(config.filterClass)) {
                return;
            }

            const keywords = containsTargetText(element.textContent);
            if (keywords) {
                keywords.forEach(keyword => matchedKeywords.add(keyword));

                // 尝试找到最外层的li元素进行移除
                const liElement = element.closest('li');
                if (liElement && !liElement.classList.contains(config.filterClass)) {
                    liElement.classList.add(config.filterClass);
                    liElement.style.display = 'none';
                    removedCount++;
                    console.log('DBD-RawsBanHelper: 过滤了一个资源元素', liElement);
                } else if (!liElement) {
                    // 如果没有找到li元素,直接隐藏当前元素
                    element.classList.add(config.filterClass);
                    element.style.display = 'none';
                    removedCount++;
                    console.log('DBD-RawsBanHelper: 过滤了一个资源元素', element);
                }
            }
        });

        if (removedCount > 0) {
            console.log(`DBD-RawsBanHelper: 在当前frame中过滤了 ${removedCount} 个资源`);
            if (config.showNotification) {
                const keywordsText = Array.from(matchedKeywords).join('、');
                showNotification(`动态过滤: 移除 ${removedCount} 个DBD资源 (${keywordsText})`);
            }
        }

        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 主过滤函数
    function filterContent() {
        // 对于静态页面,确保只执行一次
        if (!isDynamicPage() && hasExecutedStaticFilter) {
            console.log('DBD-RawsBanHelper: 静态页面已执行过过滤,跳过本次执行');
            return { removedCount: 0, matchedKeywords: [] };
        }

        console.log('DBD-RawsBanHelper: 开始执行内容过滤');
        let rowsRemoved = 0;
        let matchedKeywords = new Set();

        // 根据网站结构选择不同的处理方式
        if (window.location.hostname.includes('dmhy.org')) {
            console.log('DBD-RawsBanHelper: 处理动漫花园网站');
            const result = filterDmhyContent();
            rowsRemoved = result.removedCount;
            result.matchedKeywords.forEach(keyword => matchedKeywords.add(keyword));
        } else if (window.location.hostname.includes('acgnx.se')) {
            console.log('DBD-RawsBanHelper: 处理末日动漫网站');
            const result = filterAcgnxContent();
            rowsRemoved = result.removedCount;
            result.matchedKeywords.forEach(keyword => matchedKeywords.add(keyword));
        } else if (window.location.hostname.includes('nyaa')) {
            console.log('DBD-RawsBanHelper: 处理Nyaa网站');
            const result = filterNyaaContent();
            rowsRemoved = result.removedCount;
            result.matchedKeywords.forEach(keyword => matchedKeywords.add(keyword));
        } else if (isMikananiSite()) {
            console.log('DBD-RawsBanHelper: 处理蜜柑计划网站');
            const result = filterMikananiContent();
            rowsRemoved = result.removedCount;
            result.matchedKeywords.forEach(keyword => matchedKeywords.add(keyword));
        }

        // 显示过滤通知
        if (rowsRemoved > 0 && config.showNotification) {
            const keywordsText = Array.from(matchedKeywords).join('、');
            console.log(`DBD-RawsBanHelper: 过滤完成,共移除 ${rowsRemoved} 个资源,匹配关键词: ${keywordsText}`);
            showNotification(`已过滤 ${rowsRemoved} 个DBD-Raws资源,匹配过滤词条:${keywordsText}`);
        } else if (rowsRemoved === 0) {
            console.log('DBD-RawsBanHelper: 未找到需要过滤的内容');
        }

        // 标记静态页面已执行
        if (!isDynamicPage()) {
            hasExecutedStaticFilter = true;
        }

        return { removedCount: rowsRemoved, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 过滤动漫花园内容
    function filterDmhyContent() {
        let removedCount = 0;
        let matchedKeywords = new Set();

        const rows = document.querySelectorAll('tr');
        if (rows.length > 5) {
            rows.forEach((row, index) => {
                const keywords = containsTargetText(row.textContent);
                if (keywords && !row.classList.contains(config.filterClass)) {
                    keywords.forEach(keyword => matchedKeywords.add(keyword));
                    row.parentNode.removeChild(row);
                    removedCount++;
                }
            });
        }

        console.log(`DBD-RawsBanHelper: 动漫花园 - 移除了 ${removedCount} 行`);
        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 过滤末日动漫内容
    function filterAcgnxContent() {
        let removedCount = 0;
        let matchedKeywords = new Set();

        const rowSelectors = ['tr', '.row', '.item'];
        let rowsFound = false;

        for (let selector of rowSelectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 5) {
                rowsFound = true;
                elements.forEach((element, index) => {
                    const keywords = containsTargetText(element.textContent);
                    if (keywords && !element.classList.contains(config.filterClass)) {
                        keywords.forEach(keyword => matchedKeywords.add(keyword));
                        element.classList.add(config.filterClass);
                        removedCount++;
                        element.style.display = 'none';
                    }
                });
                break;
            }
        }

        console.log(`DBD-RawsBanHelper: 末日动漫 - 移除了 ${removedCount} 行`);
        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 过滤Nyaa内容
    function filterNyaaContent() {
        let removedCount = 0;
        let matchedKeywords = new Set();

        const rows = document.querySelectorAll('tr');
        if (rows.length > 0) {
            rows.forEach((row, index) => {
                const keywords = containsTargetText(row.textContent);
                if (keywords && !row.classList.contains(config.filterClass)) {
                    keywords.forEach(keyword => matchedKeywords.add(keyword));
                    row.parentNode.removeChild(row);
                    removedCount++;
                }
            });
        }

        console.log(`DBD-RawsBanHelper: Nyaa - 移除了 ${removedCount} 行`);
        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 过滤蜜柑计划内容
    function filterMikananiContent() {
        let removedCount = 0;
        let matchedKeywords = new Set();

        // 处理列表模式
        if (isMikananiClassicMode()) {
            console.log('DBD-RawsBanHelper: 检测到蜜柑计划列表模式');
            const result = filterMikananiClassicMode();
            removedCount = result.removedCount;
            result.matchedKeywords.forEach(keyword => matchedKeywords.add(keyword));
        } else {
            // 处理普通模式
            const result = filterMikananiNormalMode();
            removedCount = result.removedCount;
            result.matchedKeywords.forEach(keyword => matchedKeywords.add(keyword));
        }

        console.log(`DBD-RawsBanHelper: 蜜柑计划 - 移除了 ${removedCount} 行`);
        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 过滤蜜柑计划列表模式内容
    function filterMikananiClassicMode() {
        let removedCount = 0;
        let matchedKeywords = new Set();

        // 查找所有包含magnet-link-wrap的tr行
        const rows = document.querySelectorAll('tr');

        rows.forEach(row => {
            const magnetLink = row.querySelector('a.magnet-link-wrap');
            if (magnetLink) {
                const keywords = containsTargetText(magnetLink.textContent);
                if (keywords) {
                    keywords.forEach(keyword => matchedKeywords.add(keyword));
                    row.parentNode.removeChild(row);
                    removedCount++;
                    console.log('DBD-RawsBanHelper: 列表模式 - 移除了一行包含DBD内容的tr');
                }
            }
        });

        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 过滤蜜柑计划普通模式内容
    function filterMikananiNormalMode() {
        let removedCount = 0;
        let matchedKeywords = new Set();

        // 1. 过滤主内容表格行
        const allRows = document.querySelectorAll('tr[data-itemindex]');
        const rowsToRemove = [];

        allRows.forEach(row => {
            const keywords = containsTargetText(row.textContent);
            if (keywords && !row.classList.contains(config.filterClass)) {
                keywords.forEach(keyword => matchedKeywords.add(keyword));
                rowsToRemove.push(row);
            }
        });

        rowsToRemove.forEach(row => {
            row.parentNode.removeChild(row);
            removedCount++;
        });

        // 重新排序剩余的行的data-itemindex
        if (removedCount > 0) {
            reindexMikananiTable();
        }

        // 2. 过滤"相关字幕组"列表(搜索页面)
        const subtitleGroups = document.querySelectorAll('.leftbar-nav');
        subtitleGroups.forEach(container => {
            const items = container.querySelectorAll('a, span, li, div');
            items.forEach(item => {
                const keywords = containsTargetText(item.textContent);
                if (keywords && !item.classList.contains(config.filterClass)) {
                    keywords.forEach(keyword => matchedKeywords.add(keyword));
                    item.parentNode.removeChild(item);
                    removedCount++;
                }
            });
        });

        const targetDiv = document.getElementById('575');
        if (targetDiv) {
            const nextTable = targetDiv.nextElementSibling;
            if (nextTable && nextTable.tagName === 'TABLE') {
                nextTable.remove();
            }
            targetDiv.remove();
        }

        return { removedCount, matchedKeywords: Array.from(matchedKeywords) };
    }

    // 重新索引蜜柑计划表格的data-itemindex
    function reindexMikananiTable() {
        const allRows = document.querySelectorAll('tr[data-itemindex]');
        const sortedRows = Array.from(allRows).sort((a, b) => {
            const indexA = parseInt(a.getAttribute('data-itemindex'));
            const indexB = parseInt(b.getAttribute('data-itemindex'));
            return indexA - indexB;
        });

        sortedRows.forEach((row, index) => {
            row.setAttribute('data-itemindex', index + 1);
        });
    }

    // 显示过滤通知
    function showNotification(message) {
        // 清除现有的定时器
        if (notificationTimer) {
            clearTimeout(notificationTimer);
            notificationTimer = null;
        }

        // 移除现有的通知
        const existingNotification = document.getElementById('dmhy-filter-notification');
        if (existingNotification) {
            existingNotification.remove();
        }

        // 创建新通知
        const notification = document.createElement('div');
        notification.id = 'dmhy-filter-notification';
        notification.textContent = message;

        // 为蜜柑计划添加特殊样式
        if (isMikananiSite()) {
            notification.classList.add('mikanani-notification');
        }

        // 添加鼠标事件监听器
        notification.addEventListener('mouseenter', function() {
            if (notificationTimer) {
                clearTimeout(notificationTimer);
                notificationTimer = null;
            }
        });

        notification.addEventListener('mouseleave', function() {
            startNotificationTimer(notification);
        });

        // 添加点击事件,点击也可以立即关闭
        notification.addEventListener('click', function() {
            hideNotification(notification);
        });

        document.body.appendChild(notification);

        // 启动定时器
        startNotificationTimer(notification);
    }

    // 启动通知定时器
    function startNotificationTimer(notification) {
        if (notificationTimer) {
            clearTimeout(notificationTimer);
        }

        notificationTimer = setTimeout(() => {
            hideNotification(notification);
        }, config.notificationDuration);
    }

    // 隐藏通知
    function hideNotification(notification) {
        if (notification && notification.parentNode) {
            notification.style.opacity = '0';
            setTimeout(() => {
                if (notification && notification.parentNode) {
                    notification.remove();
                }
            }, 500);
        }
    }

    console.log('DBD-RawsBanHelper: 脚本初始化完成');
})();