TorrentBD Shoutbox Cleaner (Enhanced)

Modern, highly configurable cleaner for the TorrentBD shoutbox. Hide system messages, all user messages, and specific User IDs.

// ==UserScript==
// @name         TorrentBD Shoutbox Cleaner (Enhanced)
// @namespace    N/A
// @version      1.1
// @description  Modern, highly configurable cleaner for the TorrentBD shoutbox. Hide system messages, all user messages, and specific User IDs.
// @author       Sumbul⚡(UI by Gojuuroku & original script by LittleFox)
// @license      MIT
// @icon         https://s14.gifyu.com/images/bTqsQ.png
// @match        https://*.torrentbd.com/*
// @match        https://*.torrentbd.net/*
// @match        https://*.torrentbd.org/*
// @match        https://*.torrentbd.me/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';
    const systemFilters = {
        torrent: { phrase: "New Torrent :", label: "New Torrents" },
        forumPost: { phrase: "New Forum Post", label: "New Forum Posts" },
        forumTopic: { phrase: "New Forum Topic", label: "New Forum Topics" },
        request: { phrase: "New Request :", label: "New Requests" }
    };

    function checkShoutbox() {
        const shoutItems = document.querySelectorAll(".shout-item");
        const isEnabled = GM_getValue('sbc_enabled', true);
        const systemFilterStates = {};
        for (const key in systemFilters) {
            systemFilterStates[key] = GM_getValue(`sbc_filter_${key}`, true);
        }
        const hideAllUsers = GM_getValue('sbc_hide_all_users', false);
        const blockUsersEnabled = GM_getValue('sbc_block_users_enabled', true);
        const blockedUserIDs = GM_getValue('sbc_blocked_user_ids', []).filter(Boolean);

        shoutItems.forEach((item) => {
            item.style.display = "";
            if (!isEnabled) return;

            const textField = item.querySelector(".shout-text");
            if (!textField) return;

            const lowerCaseText = textField.textContent.toLowerCase();
            let shouldHide = false;

            let isSystemMessage = false;
            let systemMessageType = null;
            for (const key in systemFilters) {
                if (lowerCaseText.includes(systemFilters[key].phrase.toLowerCase())) {
                    isSystemMessage = true;
                    systemMessageType = key;
                    break;
                }
            }

            if (isSystemMessage) {
                if (systemFilterStates[systemMessageType]) {
                    shouldHide = true;
                }
            } else {
                if (hideAllUsers) {
                    shouldHide = true;
                }
                else if (blockUsersEnabled && blockedUserIDs.length > 0) {
                    const idElement = item.querySelector('.shout-user [data-tid]');
                    const userID = idElement ? idElement.getAttribute('data-tid') : null;
                    if (userID && blockedUserIDs.includes(userID)) {
                        shouldHide = true;
                    }
                }
            }

            if (shouldHide) {
                item.style.display = "none";
            }
        });
    }

    function createSettingsUI() {
        if (document.querySelector("#sbc-modal-wrapper")) return;
        const uiWrapper = document.createElement("div");
        uiWrapper.id = "sbc-modal-wrapper";
        uiWrapper.style.display = "none";
        uiWrapper.innerHTML = `
            <div id="sbc-container">
                <div id="sbc-header">
                    <h1>🧹 Shoutbox Cleaner</h1>
                    <p>Hide unwanted messages from the shoutbox</p>
                    <button id="sbc-close-btn" title="Close">×</button>
                </div>
                <div id="sbc-content">
                    <div class="sbc-setting-row sbc-master-switch">
                        <label for="sbc-enabled-switch">Enable Cleaner</label>
                        <label class="sbc-switch"><input type="checkbox" id="sbc-enabled-switch"><span class="sbc-slider"></span></label>
                    </div>
                    <hr class="sbc-divider">
                    <div class="sbc-section">
                        <h2 class="sbc-section-header">Hide system messages</h2>
                        <div class="sbc-setting-row">
                           <label for="sbc-hide-all-users-switch">New Messages</label>
                           <label class="sbc-switch"><input type="checkbox" id="sbc-hide-all-users-switch"><span class="sbc-slider"></span></label>
                        </div>
                        ${Object.keys(systemFilters).map(key => `<div class="sbc-setting-row"><label for="sbc_filter_${key}">${systemFilters[key].label}</label><label class="sbc-switch"><input type="checkbox" id="sbc_filter_${key}"><span class="sbc-slider"></span></label></div>`).join("")}
                    </div>
                    <hr class="sbc-divider">
                    <div class="sbc-section">
                        <h2 class="sbc-section-header">Custom Filters</h2>
                        <div class="sbc-form-group">
                             <div class="sbc-setting-row">
                                <label for="sbc-block-users-switch">Block by User ID</label>
                                <label class="sbc-switch"><input type="checkbox" id="sbc-block-users-switch"><span class="sbc-slider"></span></label>
                            </div>
                            <textarea id="sbc-blocked-user-ids" placeholder=""></textarea>
                        </div>
                    </div>
                </div>
            </div>`;
        document.body.appendChild(uiWrapper);
        const elements = {
            enabledSwitch: document.getElementById('sbc-enabled-switch'),
            hideAllUsersSwitch: document.getElementById('sbc-hide-all-users-switch'),
            blockUsersSwitch: document.getElementById('sbc-block-users-switch'),
            blockedUserIDsArea: document.getElementById('sbc-blocked-user-ids'),
            contentContainer: document.getElementById('sbc-content'),
            closeBtn: document.getElementById('sbc-close-btn')
        };
        const loadSettings = () => {
            const isEnabled = GM_getValue('sbc_enabled', true);
            elements.enabledSwitch.checked = isEnabled;
            // The content container itself is no longer grayed out, only its child sections
            const sections = elements.contentContainer.querySelectorAll('.sbc-section');
            sections.forEach(section => {
                section.style.opacity = isEnabled ? '1' : '0.5';
                section.style.pointerEvents = isEnabled ? 'auto' : 'none';
            });

            for (const key in systemFilters) {
                document.getElementById(`sbc_filter_${key}`).checked = GM_getValue(`sbc_filter_${key}`, true);
            }
            elements.hideAllUsersSwitch.checked = GM_getValue('sbc_hide_all_users', false);
            const blockUsersEnabled = GM_getValue('sbc_block_users_enabled', true);
            elements.blockUsersSwitch.checked = blockUsersEnabled;
            elements.blockedUserIDsArea.disabled = !blockUsersEnabled;
            elements.blockedUserIDsArea.value = GM_getValue('sbc_blocked_user_ids', []).join('\n');
        };
        elements.enabledSwitch.addEventListener('change', () => {
            const isEnabled = elements.enabledSwitch.checked;
            GM_setValue('sbc_enabled', isEnabled);
            const sections = elements.contentContainer.querySelectorAll('.sbc-section');
            sections.forEach(section => {
                section.style.opacity = isEnabled ? '1' : '0.5';
                section.style.pointerEvents = isEnabled ? 'auto' : 'none';
            });
            checkShoutbox();
        });
        for (const key in systemFilters) {
            document.getElementById(`sbc_filter_${key}`).addEventListener('change', (e) => {
                GM_setValue(`sbc_filter_${key}`, e.target.checked);
                checkShoutbox();
            });
        }
        elements.hideAllUsersSwitch.addEventListener('change', (e) => {
            GM_setValue('sbc_hide_all_users', e.target.checked);
            checkShoutbox();
        });
        elements.blockUsersSwitch.addEventListener('change', (e) => {
            const isChecked = e.target.checked;
            GM_setValue('sbc_block_users_enabled', isChecked);
            elements.blockedUserIDsArea.disabled = !isChecked;
            checkShoutbox();
        });
        elements.blockedUserIDsArea.addEventListener('input', () => {
            GM_setValue('sbc_blocked_user_ids', elements.blockedUserIDsArea.value.split('\n').map(u => u.trim()));
            checkShoutbox();
        });
        elements.closeBtn.addEventListener('click', () => { uiWrapper.style.display = 'none'; });
        uiWrapper.addEventListener('click', (e) => {
            if (e.target.id === 'sbc-modal-wrapper') {
                uiWrapper.style.display = 'none';
            }
        });
        loadSettings();
        return { uiWrapper, loadSettings };
    }

    function addSettingsButton() {
        const { uiWrapper, loadSettings } = createSettingsUI();
        const titleElement = document.querySelector('#shoutbox-container .content-title h6.left');
        if (titleElement && !document.querySelector("#sbc-settings-btn")) {
            const btn = document.createElement("div");
            btn.id = "sbc-settings-btn";
            btn.title = "Shoutbox Cleaner Settings";
            btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path fill-rule="evenodd" d="M14.457 1.314a1.5 1.5 0 0 1 2.112 1.056l.106.425a1.5 1.5 0 0 0 1.341 1.033l.443.044a1.5 1.5 0 0 1 1.579 1.95l-.17.426a1.5 1.5 0 0 0 .618 1.51l.36.27a1.5 1.5 0 0 1 .18 2.23l-.282.376a1.5 1.5 0 0 0 0 1.732l.282.376a1.5 1.5 0 0 1-.18 2.23l-.36.27a1.5 1.5 0 0 0-.618 1.51l.17.426a1.5 1.5 0 0 1-1.58 1.95l-.443.044a1.5 1.5 0 0 0-1.34 1.033l-.107.425a1.5 1.5 0 0 1-2.112 1.056L9.6 22.32a1.5 1.5 0 0 0-1.2 0L3.957 23.686a1.5 1.5 0 0 1-2.112-1.056l-.106-.425a1.5 1.5 0 0 0-1.341-1.033l-.443-.044a1.5 1.5 0 0 1-1.579-1.95l.17-.426a1.5 1.5 0 0 0-.618-1.51l-.36-.27a1.5 1.5 0 0 1-.18-2.23l.282-.376a1.5 1.5 0 0 0 0-1.732l-.282-.376a1.5 1.5 0 0 1 .18-2.23l.36-.27a1.5 1.5 0 0 0 .618-1.51l-.17-.426A1.5 1.5 0 0 1 .71 3.91l.443-.044A1.5 1.5 0 0 0 2.494 2.83l.107-.425a1.5 1.5 0 0 1 2.112-1.056L9.6 1.68a1.5 1.5 0 0 0 1.2 0l3.657-1.424ZM9.25 12.5a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5H9.25Z" clip-rule="evenodd" /></svg>`;
            btn.addEventListener("click", (e) => {
                e.stopPropagation();
                loadSettings();
                uiWrapper.style.display = "flex";
            });
            titleElement.style.display = 'flex';
            titleElement.style.alignItems = 'center';
            titleElement.appendChild(btn);
        }
    }

    GM_addStyle(`
        #sbc-modal-wrapper { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; position: fixed; z-index: 9998; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); display: none; justify-content: center; align-items: center; backdrop-filter: blur(4px); }
        #sbc-container { background-color: #2c2c2c; color: #e0e0e0; border: 1px solid #4a4a4a; border-radius: 16px; width: 90%; max-width: 420px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); display: flex; flex-direction: column; max-height: 90vh; }
        #sbc-header { padding: 24px; text-align: center; position: relative; flex-shrink: 0; }
        #sbc-header h1 { font-size: 24px; font-weight: 700; color: #fff; margin: 0 0 8px 0; }
        #sbc-header p { font-size: 14px; color: #a0a0a0; margin: 0; }
        #sbc-close-btn { position: absolute; top: 8px; right: 12px; border: none; background: none; color: #888; cursor: pointer; font-size: 28px; font-weight: bold; transition: color .2s; padding: 4px; line-height: 1; }
        #sbc-close-btn:hover { color: #fff; }
        #sbc-content { padding: 24px; overflow-y: auto; }
        .sbc-section { transition: opacity 0.3s ease; }
        .sbc-form-group { margin-bottom: 20px; }
        #sbc-content label { font-weight: 500; color: #CFCFCF; font-size: 14px; }
        .sbc-setting-row > label:first-child { flex-grow: 1; }
        textarea { width: 100%; height: 100px; resize: vertical; background-color: #202020; border: 1px solid #555; border-radius: 8px; padding: 10px; font-size: 14px; box-sizing: border-box; margin-top: 8px; transition: opacity 0.3s; }
        textarea:disabled { opacity: 0.5; cursor: not-allowed; }
        textarea:focus { outline: none; border-color: #888; }
        .sbc-section-header { font-size: 12px; text-transform: uppercase; color: #888; margin: 0 0 16px 0; letter-spacing: 0.5px; }
        .sbc-divider { border: none; border-top: 1px solid #444; margin: 20px 0; }
        .sbc-setting-row { display: flex; justify-content: space-between; align-items: center; padding: 4px 0; }
        .sbc-master-switch { padding-bottom: 0; }
        .sbc-master-switch label:first-child { font-size: 16px; font-weight: 600; color: #fff; }
        .sbc-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; cursor: pointer; }
        .sbc-switch input { opacity: 0; width: 0; height: 0; }
        .sbc-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #555; transition: .4s; border-radius: 24px; }
        .sbc-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
        input:checked + .sbc-slider { background-color: #14a76c; }
        input:checked + .sbc-slider:before { transform: translateX(20px); }
        #sbc-settings-btn { cursor: pointer; margin-left: 10px; display: inline-flex; align-items: center; color: #9e9e9e; transition: color .2s ease; }
        #sbc-settings-btn:hover { color: #fff; }
    `);

    window.addEventListener('load', () => {
        addSettingsButton();
        const shoutContainer = document.querySelector("#shouts-container");
        if (shoutContainer) {
            const observer = new MutationObserver(checkShoutbox);
            observer.observe(shoutContainer, { childList: true, subtree: true });
            checkShoutbox();
        } else {
            let retries = 0;
            const fallbackInterval = setInterval(() => {
                const dynamicShoutContainer = document.querySelector("#shouts-container");
                if (dynamicShoutContainer) {
                    clearInterval(fallbackInterval);
                    const observer = new MutationObserver(checkShoutbox);
                    observer.observe(dynamicShoutContainer, { childList: true, subtree: true });
                    checkShoutbox();
                }
                retries++;
                if (retries > 20) {
                    clearInterval(fallbackInterval);
                }
            }, 500);
        }
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址