DMHY Comment Block

Block users and keywords in dmhy comment section

安装此脚本?
作者推荐脚本

您可能也喜欢动漫花园种子屏蔽助手

安装此脚本
// ==UserScript==
// @name:zh-CN   动漫花园评论区屏蔽助手
// @name         DMHY Comment Block
// @namespace    https://github.com/xkbkx5904/dmhy-comment-block
// @version      1.0.9
// @description:zh-CN  屏蔽动漫花园评论区的用户和关键词
// @description  Block users and keywords in dmhy comment section
// @author       xkbkx5904
// @license      MIT
// @language     zh-CN
// @match        *://share.dmhy.org/topics/view/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// @noframes
// @supportURL   https://github.com/xkbkx5904/dmhy-comment-block/issues
// @homepageURL  https://github.com/xkbkx5904/dmhy-comment-block
// @icon         https://share.dmhy.org/favicon.ico
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// ==/UserScript==

// 用户黑名单列表
let UserBlockList = [];

// 缓存常用的 DOM 选择器结果
const SELECTORS = {
    COMMENT_TABLE: '#comment_recent',
    COMMENT_ROW: 'tr[id^="comment"]',
    USERNAME: '.username',
    CONTENT: '.comment_con span:last-child'
};

// 正则表达式工具类
const RegexUtils = {
    isValid(pattern) {
        if (!pattern.startsWith('/') || !pattern.endsWith('/')) return true;
        try {
            new RegExp(pattern.slice(1, -1));
            return true;
        } catch (e) {
            return false;
        }
    },

    toRegex(pattern) {
        if (pattern.startsWith('/') && pattern.endsWith('/')) {
            return new RegExp(pattern.slice(1, -1));
        }
        return pattern;
    },

    test(pattern, text) {
        if (pattern.startsWith('/') && pattern.endsWith('/')) {
            try {
                const regex = new RegExp(pattern.slice(1, -1));
                return regex.test(text);
            } catch {
                return false;
            }
        }
        return false;
    }
};

// 黑名单管理类
const BlockListManager = {
    addUser(username, commentId = null) {
        let userList = UserBlockList.find(item => item.type === 'users');
        if (!userList) {
            userList = { type: 'users', values: [] };
            UserBlockList.push(userList);
        }

        const user = {
            username,
            userId: username.startsWith('/') ? null : commentId
        };

        userList.values.push(user);
        saveBlockList();
        handleComments();
    },

    updateUsers(usernames) {
        let userList = UserBlockList.find(item => item.type === 'users');
        if (!userList) {
            userList = { type: 'users', values: [] };
            UserBlockList.push(userList);
        }

        userList.values = usernames.map(username => ({
            username,
            userId: username.startsWith('/') ? null : 
                userList.values.find(u => u.username === username)?.userId || null
        }));
    },

    updateKeywords(keywords) {
        let keywordItem = UserBlockList.find(item => item.type === 'keywords');
        if (!keywordItem) {
            keywordItem = { type: 'keywords', values: [] };
            UserBlockList.push(keywordItem);
        }
        keywordItem.values = keywords.map(RegexUtils.toRegex);
    }
};

// 从本地存储加载黑名单
function loadBlockList() {
    try {
        const saved = GM_getValue('dmhy_comment_blocklist', []);
        UserBlockList = Array.isArray(saved) ? saved.map(item => {
            if (item.type === 'keywords') {
                return {
                    type: 'keywords',
                    values: item.values.map(k => {
                        if (typeof k === 'string' && k.startsWith('/') && k.endsWith('/')) {
                            try {
                                return new RegExp(k.slice(1, -1));
                            } catch (e) {
                                return k;
                            }
                        }
                        return k;
                    })
                };
            }
            return item;
        }) : [];
    } catch (err) {
        UserBlockList = [];
    }
}

// 保存黑名单到本地存储
function saveBlockList() {
    try {
        const listToSave = UserBlockList.map(item => {
            if (item.type === 'keywords') {
                return {
                    type: 'keywords',
                    values: item.values.map(k => {
                        if (k instanceof RegExp) {
                            return `/${k.source}/`;
                        }
                        return k;
                    })
                };
            }
            return item;
        });
        GM_setValue('dmhy_comment_blocklist', listToSave);
    } catch (err) {
        console.error('保存黑名单失败:', err);
    }
}

// 处理评论显示
function handleComments() {
    const comments = document.querySelectorAll(SELECTORS.COMMENT_ROW);
    if (!comments.length) return;

    // 预先获取黑名单数据
    const userList = UserBlockList.find(item => item.type === 'users')?.values || [];
    const blockedKeywords = UserBlockList.find(item => item.type === 'keywords')?.values || [];

    comments.forEach(comment => {
        try {
            const commentId = comment.id.replace('comment', '');
            const usernameEl = comment.querySelector(SELECTORS.USERNAME);
            if (!usernameEl) return;

            const username = usernameEl.textContent.trim();
            const content = comment.querySelector(SELECTORS.CONTENT)?.textContent?.trim() || '';

            // 处理用户名链接
            if (!usernameEl.querySelector('a')) {
                const userLink = document.createElement('a');
                userLink.className = 'user-link';
                userLink.style.cssText = 'color:blue;text-decoration:underline;cursor:pointer;';
                userLink.textContent = username;
                
                userLink.oncontextmenu = (e) => {
                    e.preventDefault();
                    showContextMenu(e, commentId);
                    return false;
                };

                usernameEl.innerHTML = '';
                usernameEl.appendChild(userLink);
            }

            // 重置显示状态并检查是否需要屏蔽
            comment.style.removeProperty('display');
            if (shouldBlockComment(username, content, commentId, userList, blockedKeywords)) {
                comment.style.display = 'none';
            }
        } catch (err) {
            console.error('Error processing comment:', err);
        }
    });
}

// 判断是否需要屏蔽评论
function shouldBlockComment(username, content, commentId, userList, blockedKeywords) {
    if (!username) return false;

    // 检查用户名
    const isUserBlocked = userList.some(user => {
        // 正则匹配
        if (user.username.startsWith('/')) {
            return RegexUtils.test(user.username, username);
        }
        
        // 普通用户名匹配
        const isMatch = user.username === username;
        if (isMatch && !user.userId && commentId) {
            user.userId = parseInt(commentId);
            saveBlockList();
        }
        return isMatch || (user.userId && user.userId === parseInt(commentId));
    });

    if (isUserBlocked) return true;

    // 检查关键词
    return Boolean(content) && blockedKeywords.some(keyword => 
        typeof keyword === 'string' 
            ? content.toLowerCase().includes(keyword.toLowerCase())
            : keyword.test(content)
    );
}

// 显示右键菜单
function showContextMenu(event, commentId) {
    const menu = document.getElementById('dmhy-comment-context-menu');
    if (!menu) return;

    menu.style.position = 'fixed';
    menu.style.left = event.clientX + 'px';
    menu.style.top = event.clientY + 'px';
    menu.style.display = 'block';
    
    const username = event.target.textContent;
    
    const blockUserOption = document.getElementById('block-comment-user');
    if (blockUserOption) {
        blockUserOption.onclick = function(e) {
            e.stopPropagation();
            BlockListManager.addUser(username, commentId);
            menu.style.display = 'none';
        };
    }

    // 改进关闭菜单的逻辑
    const closeMenu = (e) => {
        if (!menu.contains(e.target)) {
            menu.style.display = 'none';
            document.removeEventListener('click', closeMenu);
            document.removeEventListener('scroll', closeMenu);
        }
    };

    window.addEventListener('scroll', closeMenu, { once: true });
    setTimeout(() => {
        document.addEventListener('click', closeMenu);
    }, 0);
}

// 添加管理界面
function addBlocklistUI() {
    const maxAttempts = 5;
    let attempts = 0;

    function checkAndAddUI() {
        const mainBlocklistUI = document.getElementById('dmhy-blocklist-ui');
        
        if (mainBlocklistUI) {
            const mainButton = mainBlocklistUI.querySelector('button');
            if (mainButton) {
                mainButton.textContent = '管理种子黑名单';
            }
            
            if (!document.getElementById('show-comment-blocklist')) {
                const button = document.createElement('button');
                button.id = 'show-comment-blocklist';
                button.textContent = '管理评论黑名单';
                button.style.marginTop = '5px';
                button.style.display = 'block';
                mainBlocklistUI.appendChild(button);
                button.addEventListener('click', showBlocklistManager);
            }
        } else {
            attempts++;
            if (attempts < maxAttempts) {
                setTimeout(checkAndAddUI, 200);
            } else {
                const uiHtml = `
                    <div id="dmhy-comment-blocklist-ui" style="position:fixed;left:10px;top:10px;z-index:9999;">
                        <button id="show-comment-blocklist">管理评论黑名单</button>
                    </div>
                `;
                document.body.insertAdjacentHTML('beforeend', uiHtml);
                document.getElementById('show-comment-blocklist')?.addEventListener('click', showBlocklistManager);
            }
        }
    }

    checkAndAddUI();
}

// 显示黑名单管理界面
function showBlocklistManager() {
    const existingManager = document.getElementById('comment-blocklist-manager');
    const existingOverlay = document.getElementById('comment-blocklist-overlay');
    if (existingManager) existingManager.remove();
    if (existingOverlay) existingOverlay.remove();

    const managerHtml = `
        <div id="comment-blocklist-manager" style="position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);
            background:white;padding:20px;border:1px solid #ccc;border-radius:5px;z-index:10000;
            width:500px;max-height:80vh;overflow-y:auto;">
            <h3 style="margin-top:0;">评论区黑名单管理</h3>
            <div style="margin-bottom:10px;">
                <label>用户黑名单(注意是用户名,用分号分隔):</label><br>
                <textarea id="blocked-usernames" style="width:100%;height:60px;margin-top:5px;resize:none;"></textarea>
            </div>
            <div style="margin-bottom:10px;">
                <label>关键词屏蔽(用分号分隔):</label><br>
                <textarea id="comment-keywords" style="width:100%;height:60px;margin-top:5px;resize:none;"></textarea>
                <div style="color:#666;font-size:12px;margin-top:5px;">
                    提示:支持普通文本和正则表达式<br>
                    - 普通文本直接输入,用分号分隔<br>
                    - 正则表达式用 / 包裹,例如:/\\d+/<br>
                    - 示例:文本1;/user\\d+/;文本2
                </div>
            </div>
            <div style="margin-top:10px;text-align:right;">
                <button id="save-comment-blocklist">保存</button>
                <button id="close-comment-manager">关闭</button>
            </div>
        </div>
        <div id="comment-blocklist-overlay" style="position:fixed;top:0;left:0;right:0;bottom:0;
            background:rgba(0,0,0,0.5);z-index:9999;"></div>
    `;

    document.body.insertAdjacentHTML('beforeend', managerHtml);

    // 填充现有数据
    const userList = UserBlockList.find(item => item.type === 'users')?.values || [];
    const keywords = UserBlockList.find(item => item.type === 'keywords')?.values || [];

    document.getElementById('blocked-usernames').value = userList
        .map(user => user.username)
        .filter(username => username)
        .join(';');

    document.getElementById('comment-keywords').value = keywords.map(k => {
        if (k instanceof RegExp) {
            return `/${k.source}/`;
        }
        return k;
    }).join(';');

    // 绑定事件
    document.getElementById('close-comment-manager').addEventListener('click', function() {
        document.getElementById('comment-blocklist-manager')?.remove();
        document.getElementById('comment-blocklist-overlay')?.remove();
    });

    document.getElementById('comment-blocklist-overlay').addEventListener('click', function(e) {
        if (e.target === this) {
            document.getElementById('comment-blocklist-manager')?.remove();
            document.getElementById('comment-blocklist-overlay')?.remove();
        }
    });

    // 保存按钮事件
    document.getElementById('save-comment-blocklist').addEventListener('click', function() {
        const usernames = document.getElementById('blocked-usernames').value
            .split(/[;;]/)
            .map(name => name.trim())
            .filter(Boolean);

        const keywords = document.getElementById('comment-keywords').value
            .split(/[;;]/)
            .map(k => k.trim())
            .filter(Boolean);

        // 验证正则表达式
        const invalidUsername = usernames.find(name => !RegexUtils.isValid(name));
        if (invalidUsername) {
            showNotification(`用户名正则表达式错误: ${invalidUsername}`);
            return;
        }

        const invalidKeyword = keywords.find(k => !RegexUtils.isValid(k));
        if (invalidKeyword) {
            showNotification(`关键词正则表达式错误: ${invalidKeyword}`);
            return;
        }

        // 更新数据
        BlockListManager.updateUsers(usernames);
        BlockListManager.updateKeywords(keywords);

        saveBlockList();
        handleComments();
        
        document.getElementById('comment-blocklist-manager')?.remove();
        document.getElementById('comment-blocklist-overlay')?.remove();
        showNotification('黑名单已更新');
    });
}

// 添加右键菜单
function addContextMenu() {
    const menuHtml = `
        <div id="dmhy-comment-context-menu" style="
            display: none;
            position: fixed;
            background: white;
            border: 1px solid #ccc;
            border-radius: 3px;
            padding: 5px;
            box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
            z-index: 10000;
            min-width: 150px;
        ">
            <div id="block-comment-user" style="
                padding: 8px 12px;
                cursor: pointer;
                white-space: nowrap;
            ">
                添加评论用户到黑名单
            </div>
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', menuHtml);

    const blockUserOption = document.getElementById('block-comment-user');
    if (blockUserOption) {
        blockUserOption.addEventListener('mouseover', () => {
            blockUserOption.style.backgroundColor = '#f0f0f0';
        });
        blockUserOption.addEventListener('mouseout', () => {
            blockUserOption.style.backgroundColor = '';
        });
    }
}

// 显示通知
function showNotification(message) {
    const notification = document.createElement('div');
    notification.style.cssText = `
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        background: rgba(0, 0, 0, 0.8);
        color: white;
        padding: 10px 20px;
        border-radius: 4px;
        z-index: 10001;
        font-size: 14px;
        transition: opacity 0.3s;
    `;
    notification.textContent = message;
    document.body.appendChild(notification);

    setTimeout(() => {
        notification.style.opacity = '0';
        setTimeout(() => notification.remove(), 300);
    }, 2000);
}

// 等待评论区加载
function waitForComments() {
    return new Promise((resolve) => {
        const commentTable = document.querySelector(SELECTORS.COMMENT_TABLE);
        if (commentTable?.querySelector(SELECTORS.USERNAME)) {
            resolve();
            return;
        }

        let attempts = 0;
        const maxAttempts = 20;
        const interval = setInterval(() => {
            const commentTable = document.querySelector(SELECTORS.COMMENT_TABLE);
            if (commentTable?.querySelector(SELECTORS.USERNAME)) {
                clearInterval(interval);
                resolve();
                return;
            }

            attempts++;
            if (attempts >= maxAttempts) {
                clearInterval(interval);
                resolve();
            }
        }, 500);
    });
}

// 初始化
(function() {
    'use strict';
    
    loadBlockList();
    addBlocklistUI();
    addContextMenu();

    waitForComments().then(() => {
        handleComments();

        const commentList = document.querySelector('#comment_list');
        if (commentList) {
            const observer = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if (mutation.addedNodes.length > 0) {
                        handleComments();
                    }
                }
            });

            observer.observe(commentList, {
                childList: true,
                subtree: true
            });
        }
    });
})();