// ==UserScript== // @name UNIT3D Bot Hider // @namespace https://github.com/flowerey/unit3d-scripts // @version 1.3.3 // @description Toggles the visibility of bot messages on UNIT3D chatbox. // @author blueberry // @match https://*/chatbox* // @match https://*/chat/* // @downloadURL https://raw.githubusercontent.com/flowerey/unit3d-scripts/main/bot-hider.user.js // @updateURL https://raw.githubusercontent.com/flowerey/unit3d-scripts/main/bot-hider.user.js.meta.js // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY_NAMES = 'bh_bot_names'; const STORAGE_KEY_PATTERNS = 'bh_filter_patterns'; const STORAGE_KEY_ENABLED = 'bh_enabled'; const STORAGE_KEY_MODE = 'bh_filter_mode'; const DEFAULT_BOT_NAMES = ['SystemBot']; const DEFAULT_PATTERNS = []; function loadBotNames() { try { const stored = JSON.parse(localStorage.getItem(STORAGE_KEY_NAMES)); if (Array.isArray(stored) && stored.length > 0) return stored; } catch (e) { /* ignore */ } return [...DEFAULT_BOT_NAMES]; } function saveBotNames(names) { try { localStorage.setItem(STORAGE_KEY_NAMES, JSON.stringify(names)); } catch (e) { /* ignore */ } } function loadPatterns() { try { const stored = JSON.parse(localStorage.getItem(STORAGE_KEY_PATTERNS)); if (Array.isArray(stored)) return stored; } catch (e) { /* ignore */ } return [...DEFAULT_PATTERNS]; } function savePatterns(patterns) { try { localStorage.setItem(STORAGE_KEY_PATTERNS, JSON.stringify(patterns)); } catch (e) { /* ignore */ } } const SELECTORS = { MESSAGES: 'li, article.chatbox-message', CHAT_CONTAINER: '.chatroom__messages, #chatbox__messages', UI_PANEL: '#chatbox_header .panel__actions', WRAPPER_ID: 'bothider-wrap' }; class BotHider { constructor() { this.isEnabled = localStorage.getItem(STORAGE_KEY_ENABLED) !== 'false'; this.filterMode = localStorage.getItem(STORAGE_KEY_MODE) || 'names'; this.botNames = loadBotNames(); this.patterns = loadPatterns(); this.compiledPatterns = []; this.observer = null; this.hiddenCount = 0; this.compilePatterns(); this.init(); } compilePatterns() { this.compiledPatterns = []; for (const p of this.patterns) { try { this.compiledPatterns.push(new RegExp(p, 'i')); } catch (e) { /* skip invalid regex */ } } } init() { this.injectStyles(); this.injectUI(); this.processMessages(); this.setupObserver(); } injectStyles() { if (document.getElementById('bothider-custom-css')) return; const style = document.createElement('style'); style.id = 'bothider-custom-css'; style.textContent = ` .bh-inactive { display: none !important; } #bothider-wrap { display: flex; align-items: center; gap: 8px; padding: 0 10px; border-right: 1px solid rgba(255,255,255,0.1); height: 100%; } .bh-toggle { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; transition: opacity 0.2s; } .bh-toggle:hover { opacity: 0.8; } .bh-toggle input { cursor: pointer; margin: 0; } .bh-toggle span { font-size: 11px; font-weight: 600; text-transform: uppercase; color: rgba(255,255,255,0.9); letter-spacing: 0.5px; } .bh-settings-btn { background: none; border: 1px solid rgba(255,255,255,0.2); color: rgba(255,255,255,0.7); border-radius: 4px; padding: 2px 6px; font-size: 11px; cursor: pointer; transition: all 0.2s; } .bh-settings-btn:hover { background: rgba(255,255,255,0.1); color: #fff; } .bh-counter { font-size: 10px; color: #888; min-width: 20px; text-align: center; } .bh-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 29999; } .bh-settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a1a; border: 1px solid #444; border-radius: 8px; padding: 20px; z-index: 30000; color: #ddd; font-family: system-ui, sans-serif; min-width: 360px; max-width: 90vw; max-height: 80vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.5); } .bh-settings-panel h3 { margin: 0 0 12px 0; color: #fff; border-bottom: 1px solid #333; padding-bottom: 8px; font-size: 14px; } .bh-settings-panel label { display: block; font-size: 12px; color: #aaa; margin-bottom: 4px; } .bh-settings-panel input[type="text"] { width: 100%; padding: 6px 10px; border: 1px solid #444; border-radius: 4px; background: #111; color: #ddd; font-size: 12px; box-sizing: border-box; margin-bottom: 8px; } .bh-settings-panel input[type="text"]:focus { outline: none; border-color: #2ecc71; } .bh-settings-panel .bh-tag-list { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; } .bh-settings-panel .bh-tag { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; background: #333; border-radius: 4px; font-size: 11px; color: #ddd; } .bh-settings-panel .bh-tag-remove { cursor: pointer; color: #e74c3c; font-weight: bold; font-size: 13px; } .bh-settings-panel .bh-tag-remove:hover { color: #ff6b6b; } .bh-settings-panel .bh-row { display: flex; gap: 6px; margin-bottom: 12px; } .bh-settings-panel .bh-row input[type="text"] { flex: 1; margin-bottom: 0; } .bh-settings-panel .bh-row button { padding: 6px 12px; background: #2ecc71; color: #fff; border: none; border-radius: 4px; font-size: 12px; cursor: pointer; white-space: nowrap; } .bh-settings-panel .bh-row button:hover { background: #27ae60; } .bh-settings-panel .bh-save-btn { width: 100%; padding: 8px; background: #2ecc71; color: #fff; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; margin-top: 8px; } .bh-settings-panel .bh-save-btn:hover { background: #27ae60; } .bh-settings-panel .bh-hint { font-size: 10px; color: #666; margin-top: -6px; margin-bottom: 8px; } .bh-settings-panel .bh-mode-row { display: flex; gap: 12px; margin-bottom: 12px; } .bh-settings-panel .bh-mode-row label { display: flex; align-items: center; gap: 4px; cursor: pointer; color: #ddd; font-size: 12px; } `; document.head.appendChild(style); } shouldHide(textContent) { if (!this.isEnabled) return false; const lower = textContent.toLowerCase(); for (const name of this.botNames) { if (lower.includes(name.toLowerCase())) return true; } for (const re of this.compiledPatterns) { if (re.test(textContent)) return true; } return false; } processMessages() { const targets = document.querySelectorAll(SELECTORS.MESSAGES); this.hiddenCount = 0; targets.forEach(el => { const content = el.textContent || ''; if (this.shouldHide(content)) { el.classList.add('bh-inactive'); this.hiddenCount++; } else { el.classList.remove('bh-inactive'); } }); this.updateCounter(); } updateCounter() { const counter = document.querySelector('.bh-counter'); if (counter) { counter.textContent = this.hiddenCount > 0 ? `(${this.hiddenCount})` : ''; counter.title = this.hiddenCount > 0 ? `${this.hiddenCount} messages hidden` : ''; } } injectUI() { if (document.getElementById(SELECTORS.WRAPPER_ID)) return; const panel = document.querySelector(SELECTORS.UI_PANEL); if (!panel) return; const controls = document.createElement('div'); controls.id = SELECTORS.WRAPPER_ID; const labelWrap = document.createElement('label'); labelWrap.className = 'bh-toggle'; labelWrap.innerHTML = ` Hide Bots `; labelWrap.querySelector('input').addEventListener('change', (e) => { this.isEnabled = e.target.checked; localStorage.setItem(STORAGE_KEY_ENABLED, this.isEnabled); this.processMessages(); }); const counter = document.createElement('span'); counter.className = 'bh-counter'; const settingsBtn = document.createElement('button'); settingsBtn.className = 'bh-settings-btn'; settingsBtn.textContent = '\u2699'; settingsBtn.title = 'Bot Hider Settings'; settingsBtn.addEventListener('click', () => this.openSettings()); controls.append(labelWrap, counter, settingsBtn); panel.prepend(controls); } openSettings() { if (document.querySelector('.bh-settings-overlay')) return; const overlay = document.createElement('div'); overlay.className = 'bh-settings-overlay'; overlay.onclick = () => overlay.remove(); const panel = document.createElement('div'); panel.className = 'bh-settings-panel'; panel.onclick = (e) => e.stopPropagation(); const renderTagList = (container, items, onRemove) => { container.innerHTML = ''; items.forEach((item, i) => { const tag = document.createElement('span'); tag.className = 'bh-tag'; const textNode = document.createTextNode(item); tag.appendChild(textNode); tag.appendChild(document.createTextNode(' ')); const removeBtn = document.createElement('span'); removeBtn.className = 'bh-tag-remove'; removeBtn.dataset.idx = i; removeBtn.textContent = '\u00D7'; removeBtn.onclick = (e) => { e.stopPropagation(); onRemove(i); renderTagList(container, items, onRemove); }; tag.appendChild(removeBtn); container.appendChild(tag); }); }; const names = [...this.botNames]; const patterns = [...this.patterns]; panel.innerHTML = `
has gifted hides gift messages.