电报网页版-屏蔽群组用户发言 Telegram Web - Block Specific Users

【经典UI回归】完整功能版:用户屏蔽+关键词屏蔽+优雅界面

// ==UserScript==
// @name         电报网页版-屏蔽群组用户发言 Telegram Web - Block Specific Users
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  【经典UI回归】完整功能版:用户屏蔽+关键词屏蔽+优雅界面
// @author       南竹 & grok AI & deepseek AI
// @match        https://web.telegram.org/k/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置项
    const config = {
        debug: false,
        blockKeywords: []
    };

    // 初始化数据
    let blockedUsers = JSON.parse(GM_getValue('blockedUsers', '[]'));
    if (GM_getValue('blockKeywords')) {
        config.blockKeywords = JSON.parse(GM_getValue('blockKeywords'));
    }

    // 调试输出
    function log(...args) {
        if (config.debug) console.log('[TG Block]', ...args);
    }

    // ========================
    // 经典UI样式 (v1.0风格)
    // ========================
    const style = document.createElement('style');
    style.textContent = `
        .block-users-window {
            position: fixed;
            top: 80px;
            left: 20px;
            width: 350px;
            background: white;
            border: 1px solid #d1d1d1;
            box-shadow: 0 2px 15px rgba(0,0,0,0.1);
            z-index: 10000;
            padding: 0;
            font-family: Arial, sans-serif;
            border-radius: 6px;
            overflow: hidden;
        }
        .block-users-window .header {
            background: #f5f5f5;
            padding: 10px 15px;
            cursor: move;
            border-bottom: 1px solid #e0e0e0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: bold;
            color: #333;
        }
        .block-users-window .close-btn {
            cursor: pointer;
            font-size: 18px;
            color: #777;
            background: none;
            border: none;
            padding: 0 5px;
        }
        .block-users-window .close-btn:hover {
            color: #333;
        }
        .block-users-window .tabs {
            display: flex;
            background: #f0f0f0;
            border-bottom: 1px solid #ddd;
        }
        .block-users-window .tab {
            padding: 8px 0;
            cursor: pointer;
            flex: 1;
            text-align: center;
            font-size: 13px;
            color: #555;
            transition: all 0.2s;
        }
        .block-users-window .tab:hover {
            background: #e8e8e8;
        }
        .block-users-window .tab.active {
            background: white;
            color: #0088cc;
            font-weight: bold;
            border-bottom: 2px solid #0088cc;
        }
        .block-users-window .tab-content {
            padding: 12px 15px;
            max-height: 300px;
            overflow-y: auto;
        }
        .block-users-window .list-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 8px 0;
            border-bottom: 1px solid #f0f0f0;
            font-size: 13px;
        }
        .block-users-window .list-item:last-child {
            border-bottom: none;
        }
        .block-users-window .list-item button {
            background: #ff6b6b;
            color: white;
            border: none;
            padding: 3px 8px;
            cursor: pointer;
            border-radius: 3px;
            font-size: 12px;
            transition: background 0.2s;
        }
        .block-users-window .list-item button:hover {
            background: #ff5252;
        }
        .block-users-window .add-section {
            display: flex;
            gap: 8px;
            margin-top: 15px;
            padding-top: 10px;
            border-top: 1px solid #eee;
        }
        .block-users-window select {
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 3px;
            background: white;
            flex: 0.8;
        }
        .block-users-window input {
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 3px;
            flex: 2;
        }
        .block-users-window .add-btn {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 6px 12px;
            cursor: pointer;
            border-radius: 3px;
            flex: 0.7;
            transition: background 0.2s;
        }
        .block-users-window .add-btn:hover {
            background: #45a049;
        }
        .block-users-window .empty-tip {
            color: #999;
            text-align: center;
            padding: 20px 0;
            font-size: 13px;
        }
    `;
    document.head.appendChild(style);

    // ========================
    // 核心功能函数
    // ========================
    function getUserInfo(message) {
        const usernameElement = message.querySelector('.peer-title');
        return usernameElement ? {
            id: usernameElement.getAttribute('data-peer-id'),
            nickname: usernameElement.textContent.trim()
        } : null;
    }

    function getMessageText(message) {
        const selectors = ['.text-content', '.message-text', '.translatable-message'];
        for (const selector of selectors) {
            const el = message.querySelector(selector);
            if (el && el.textContent.trim()) return el.textContent.trim();
        }
        return '';
    }

    function checkKeywords(text) {
        return config.blockKeywords.some(keyword =>
            new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i').test(text)
        );
    }

    // ========================
    // 经典浮动控制面板
    // ========================
    function createBlockUsersWindow() {
        // 移除现有窗口
        const existingWindow = document.querySelector('.block-users-window');
        if (existingWindow) existingWindow.remove();

        // 创建窗口DOM
        const windowDiv = document.createElement('div');
        windowDiv.className = 'block-users-window';
        windowDiv.innerHTML = `
            <div class="header">
                <span>屏蔽管理 v1.4</span>
                <button class="close-btn">×</button>
            </div>
            <div class="tabs">
                <div class="tab active" data-tab="users">用户屏蔽</div>
                <div class="tab" data-tab="keywords">关键词屏蔽</div>
            </div>
            <div class="tab-content active" data-tab="users">
                <div class="content"></div>
                <div class="add-section">
                    <select id="block-type">
                        <option value="nickname">按昵称</option>
                        <option value="id">按ID</option>
                    </select>
                    <input id="block-value" placeholder="输入内容" />
                    <button class="add-btn" id="add-btn">添加</button>
                </div>
            </div>
            <div class="tab-content" data-tab="keywords">
                <div class="content"></div>
                <div class="add-section">
                    <input id="keyword-value" placeholder="输入关键词" style="flex:2.8" />
                    <button class="add-btn" id="add-keyword-btn">添加</button>
                </div>
            </div>
        `;
        document.body.appendChild(windowDiv);

        // 窗口拖动功能
        const header = windowDiv.querySelector('.header');
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        header.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            windowDiv.style.top = (windowDiv.offsetTop - pos2) + "px";
            windowDiv.style.left = (windowDiv.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }

        // 标签页切换
        windowDiv.querySelectorAll('.tab').forEach(tab => {
            tab.addEventListener('click', function() {
                windowDiv.querySelectorAll('.tab, .tab-content').forEach(el => {
                    el.classList.remove('active');
                });
                this.classList.add('active');
                const tabContent = windowDiv.querySelector(`.tab-content[data-tab="${this.dataset.tab}"]`);
                if (tabContent) tabContent.classList.add('active');
            });
        });

        // 关闭按钮
        windowDiv.querySelector('.close-btn').addEventListener('click', () => {
            windowDiv.remove();
        });

        // 更新列表显示
        function updateLists() {
            updateUserList();
            updateKeywordList();
        }

        function updateUserList() {
            const content = windowDiv.querySelector('.tab-content[data-tab="users"] .content');
            content.innerHTML = blockedUsers.length ? '' : '<div class="empty-tip">暂无屏蔽用户</div>';

            blockedUsers.forEach((user, index) => {
                const item = document.createElement('div');
                item.className = 'list-item';
                item.innerHTML = `
                    <span>${user.type === 'nickname' ? '昵称' : 'ID'}: <strong>${user.value}</strong></span>
                    <button data-index="${index}">删除</button>
                `;
                content.appendChild(item);
            });

            // 绑定删除事件
            content.querySelectorAll('button').forEach(btn => {
                btn.addEventListener('click', function() {
                    blockedUsers.splice(parseInt(this.dataset.index), 1);
                    GM_setValue('blockedUsers', JSON.stringify(blockedUsers));
                    updateLists();
                    hideMessages();
                });
            });
        }

        function updateKeywordList() {
            const content = windowDiv.querySelector('.tab-content[data-tab="keywords"] .content');
            content.innerHTML = config.blockKeywords.length ? '' : '<div class="empty-tip">暂无屏蔽关键词</div>';

            config.blockKeywords.forEach((keyword, index) => {
                const item = document.createElement('div');
                item.className = 'list-item';
                item.innerHTML = `
                    <span>关键词: <strong>${keyword}</strong></span>
                    <button data-index="${index}">删除</button>
                `;
                content.appendChild(item);
            });

            // 绑定删除事件
            content.querySelectorAll('button').forEach(btn => {
                btn.addEventListener('click', function() {
                    config.blockKeywords.splice(parseInt(this.dataset.index), 1);
                    GM_setValue('blockKeywords', JSON.stringify(config.blockKeywords));
                    updateLists();
                    hideMessages();
                });
            });
        }

        // 添加按钮事件
        windowDiv.querySelector('#add-btn').addEventListener('click', addBlockUser);
        windowDiv.querySelector('#add-keyword-btn').addEventListener('click', addKeyword);

        function addBlockUser() {
            const type = windowDiv.querySelector('#block-type').value;
            const value = windowDiv.querySelector('#block-value').value.trim();

            if (!value) {
                alert('请输入有效内容!');
                return;
            }

            if (blockedUsers.some(u => u.type === type && u.value === value)) {
                alert('该条目已存在!');
                return;
            }

            blockedUsers.push({ type, value });
            GM_setValue('blockedUsers', JSON.stringify(blockedUsers));
            windowDiv.querySelector('#block-value').value = '';
            updateLists();
            hideMessages();
        }

        function addKeyword() {
            const keyword = windowDiv.querySelector('#keyword-value').value.trim();

            if (!keyword) {
                alert('请输入有效关键词!');
                return;
            }

            if (config.blockKeywords.includes(keyword)) {
                alert('该关键词已存在!');
                return;
            }

            config.blockKeywords.push(keyword);
            GM_setValue('blockKeywords', JSON.stringify(config.blockKeywords));
            windowDiv.querySelector('#keyword-value').value = '';
            updateLists();
            hideMessages();
        }

        // 初始化列表
        updateLists();
    }

    // ========================
    // 消息屏蔽核心逻辑
    // ========================
    function hideMessages() {
        document.querySelectorAll('.bubble:not(.block-processed)').forEach(message => {
            message.classList.add('block-processed');
            const userInfo = getUserInfo(message);
            const text = getMessageText(message);

            const isUserBlocked = userInfo && blockedUsers.some(user =>
                (user.type === 'nickname' && userInfo.nickname &&
                 user.value.toLowerCase() === userInfo.nickname.toLowerCase()) ||
                (user.type === 'id' && userInfo.id && user.value === userInfo.id)
            );

            const isKeywordBlocked = text && checkKeywords(text);

            if (isUserBlocked || isKeywordBlocked) {
                message.style.display = 'none';
                if (config.debug) {
                    console.log('屏蔽消息:',
                        isUserBlocked ? `用户匹配 (${userInfo.nickname || userInfo.id})` : `关键词匹配: "${text.substring(0, 20)}..."`,
                        message
                    );
                }
            }
        });
    }

    // ========================
    // 初始化脚本
    // ========================
    GM_registerMenuCommand('打开屏蔽面板', createBlockUsersWindow);
    GM_registerMenuCommand('清空屏蔽列表', () => {
        if (confirm('确定要清空所有屏蔽规则吗?')) {
            blockedUsers = [];
            config.blockKeywords = [];
            GM_setValue('blockedUsers', '[]');
            GM_setValue('blockKeywords', '[]');
            document.querySelectorAll('.bubble').forEach(m => m.style.display = '');
            alert('已清空所有屏蔽规则!');
        }
    });

    // 监听DOM变化
    const observer = new MutationObserver(hideMessages);
    observer.observe(document.querySelector('#column-center') || document.body, {
        childList: true,
        subtree: true
    });

    // 页面加载后初始化
    window.addEventListener('load', () => {
        setTimeout(() => {
            hideMessages();
            if (config.debug) console.log('屏蔽脚本已加载', {
                version: '1.4',
                users: blockedUsers,
                keywords: config.blockKeywords
            });
        }, 3000);
    });
})();