您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Режим стримера для Lolzteam. Улучшенная и модифицированная версия!
// ==UserScript== // @name LztStreamerModeRework // @namespace https://github.com/nellimonix // @version 1.2.4 // @description Режим стримера для Lolzteam. Улучшенная и модифицированная версия! // @author llimonix // @match https://zelenka.guru/* // @match https://lolz.live/* // @match https://lzt.market/* // @icon https://cdn-icons-png.flaticon.com/512/18429/18429788.png // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @run-at document-start // @license MIT // ==/UserScript== // Проверяем наши настройки let StreamerModeSettings = GM_getValue('lzt_streamer_mode_settings', null); const default_settings = { 'main_page': { 'title': 'Основное', 'description': 'LZT Streamer Mode', 'enabled': true }, 'banwords_page': { 'banwords': [], 'title': 'Запрещенные слова', 'description': 'Блокировать запрещенные слова (укажите их через запятую)', 'enabled': true }, 'selector_page': { 'title': 'Настройка режима стримера', 'general': { 'title': 'Общие настройки', 'balance': { 'selectors': [ 'div#AccountMenu span.left', '#MarketMoneyTransferForm .marketRefillBalance--Row:nth-of-type(2) .bigTextHeading', '#MarketMoneyTransferForm div.MarketPopularTransfers', 'div.marketUserPanel div.balance-value', 'div.marketUserPanel a.holdPayment', 'div.marketUserPanel span.balanceNumber' ], 'disabled_selectors': ['span#NavigationAccountBalance'], 'ignored_selectors': [], 'description': 'Скрывать все элементы, отображающие баланс пользователя (меню, переводы и т.д.)', 'enabled': true }, 'messages': { 'selectors': [ '#ConversationListItems', 'div.ng-notification div.body', ], 'disabled_selectors': [], 'ignored_selectors': [], 'description': 'Скрывать личные сообщения, список диалогов и уведомления о новых сообщениях', 'enabled': true }, 'alerts': { 'selectors': [ 'div#AlertPanels', 'div.account_alerts .alertGroup ol', 'div.liveAlert', 'div.imDialog', ], 'disabled_selectors': [], 'ignored_selectors': [], 'description': 'Скрывать все уведомления, включая всплывающие', 'enabled': true }, 'personal_details': { 'selectors': [ 'div.account_security input[name="email"]' ], 'disabled_selectors': [], 'ignored_selectors': [], 'description': 'Скрывать элементы в настройках профиля', 'enabled': true } }, 'forum': {}, 'market': { 'title': 'Настройки маркета', 'viewed': { 'selectors': [ 'div.marketViewedItems' ], 'disabled_selectors': [], 'ignored_selectors': ['.marketMyPayments'], 'description': 'Скрывать блок недавно просмотренных товаров и аккаунтов', 'enabled': true }, 'payments': { 'selectors': [ 'div.marketMyPayments div.wrapper' ], 'disabled_selectors': [], 'ignored_selectors': ['.marketViewedItems'], 'description': 'Скрывать блок последних действий и историю операций', 'enabled': true }, 'orders': { 'selectors': [], 'disabled_selectors': [], 'ignored_selectors': [], 'description': 'Скрывать список купленных товаров и аккаунтов', 'enabled': true }, 'login_data': { 'selectors': [ 'div.marketItemView--loginData', 'div.marketItemView--loginData--box', ], 'disabled_selectors': [], 'ignored_selectors': [ '.login_details_for_arbitrators', '.make_claim_seller_block', '.recommendation_change_password' ], 'description': 'Скрывать данные для входа в аккаунт: логин, пароль, email и т.п.', 'enabled': true }, 'payment_stats': { 'selectors': [ 'div.paymentStats div.stats' ], 'disabled_selectors': [], 'ignored_selectors': [], 'description': 'Скрывать сумму платежей за все время / указанный срок', 'enabled': true }, 'need_payment': { 'selectors': [ 'div.need-payment div.need-payment-item' ], 'disabled_selectors': [], 'ignored_selectors': [], 'description': 'Скрывать платежи ожидающие оплаты', 'enabled': true } }, 'staff': { 'title': 'Настройки команды форума', 'warning': 'Может затронуть некоторые обычные функции, если они используют схожие элементы', 'balance_users': { 'selectors': [], 'disabled_selectors': [ 'div.userContentLinks a[href$="/payments"]', 'div.userContentLinks a[href$="/donate-to-forum"]', ], 'ignored_selectors': [], 'description': 'Скрывать все элементы, отображающие баланс пользователей', 'enabled': false }, 'account_menu': { 'selectors': [], 'disabled_selectors': [ 'a[href="account/email-blacklist"]', 'a[href="account/spam-words"]', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки в меню аккаунта', 'enabled': false }, 'account_links': { 'selectors': [], 'disabled_selectors': [ 'div.account-links .modLink', ], 'ignored_selectors': [], 'description': 'Скрывать иконки премодерации, репортов и службы поддержки', 'enabled': false }, 'profile_panel': { 'selectors': [], 'disabled_selectors': [ 'div.banLogs div#logs-buttons-f', 'ul.mainTabs a[href$="#staff-notes"]', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки в панели профиля (записи команды форума, журнал изменений)', 'enabled': false }, 'profile_info': { 'selectors': [], 'disabled_selectors': [ 'div.profilePage a[href$="/telegram-info"]', 'div.profilePage a[href$="/eog-info"]', 'div.profilePage div.profile_info_row:nth-of-type(3)' ], 'ignored_selectors': [], 'description': 'Скрывать дополнительные функции, связанные с персональной информацией', 'enabled': false }, 'profile_id': { 'selectors': [], 'disabled_selectors': [ 'div.profilePage div.profile_info_row:nth-of-type(2)' ], 'ignored_selectors': [], 'description': 'Скрывать ID пользователя в профиле', 'enabled': false }, 'personal_details': { 'selectors': [], 'disabled_selectors': [ 'div.navigationSideBar a[href="account/moderator-settings"]', 'form.personalDetailsForm dl.customFieldEditscamURL', 'form.personalDetailsForm dl.customFieldEditlztUnbanAmount', 'form.personalDetailsForm dl.customFieldEditlztLikesZeroing', 'form.personalDetailsForm dl.customFieldEditlztAwardUserTrophy', 'form.personalDetailsForm dl.customFieldEditlztLikesIncreasing', 'form.personalDetailsForm dl.customFieldEditlztSympathyZeroing', 'form.personalDetailsForm dl.customFieldEditlztSympathyZeroing', 'form.personalDetailsForm dl.customFieldEditlztSympathyIncreasing', ], 'ignored_selectors': [], 'description': 'Скрывать элементы в настройках профиля', 'enabled': false }, 'thread_checkbox': { 'selectors': [], 'disabled_selectors': [ 'body label[for*="inlineModCheck-thread"]', '.InlineModCheck', ], 'ignored_selectors': [], 'description': 'Скрывать чекбоксы для выделения тем', 'enabled': false }, 'thread_visibility': { 'selectors': [], 'disabled_selectors': [ 'a[href*="set-deleted-content-visibility"]', '.messageList .message.deleted', '.messageSimpleList .messageSimple.deleted', 'div.discussionListItem.moderated', 'div.discussionListItem.deleted' ], 'ignored_selectors': [], 'description': 'Скрывать удаленные и премодерациооные темы', 'enabled': false }, 'shared_ips': { 'selectors': [], 'disabled_selectors': [ 'div.ipMatches a[href$="/#gauid"]', 'div.ipMatches a[href$="/#evercookie"]', 'div.ipMatches a[href$="/#fingerprints"]', 'div.ipMatches a[href$="/#multiple"]', 'div.logInfo li.ipLog span.info-separattor', 'div.logInfo li.ipLog span.muted', ], 'ignored_selectors': [], 'description': 'Скрывать дополнительную информацию в общих IP', 'enabled': false }, 'profile_dottes': { 'selectors': [], 'disabled_selectors': [ //'div.MenuContainer a[href$="/edit"]', 'div.MenuContainer a[href^="spam-cleaner"]', 'div.MenuContainer a[href^="support-tickets/list"]', 'div.MenuContainer a[href^="members/quick-ban"]', 'div.MenuContainer a[href^="members/quick-unban"]', 'div.MenuContainer a[href$="/change-logs"]', ], 'ignored_selectors': [], 'description': 'Скрывать функции управления над пользователем', 'enabled': false }, 'comments_control': { 'selectors': [], 'disabled_selectors': [ 'div.MenuContainer a[href*="/warn"]', 'div.MenuContainer a[href*="/unapprove"]', 'div.MenuContainer a[href$="/history"]', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки для управления комментариями', 'enabled': false }, 'thread_control': { 'selectors': [], 'disabled_selectors': [ 'div.MenuContainer a[href$="/reply-bans"]', 'div.MenuContainer a[href$="/user-actions"]', 'div.MenuContainer a[href$="/show-posts"]', 'div.MenuContainer a[href$="/change-starter"]', 'div.MenuContainer a[href$="/reply-bans"]', 'div.MenuContainer a[href$="/undelete"]', 'div.MenuContainer form[action$="/quick-update"] li:nth-child(2)', 'div.MenuContainer #threadViewThreadCheck', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки для управления темами', 'enabled': false }, 'arbitor_control': { 'selectors': [], 'disabled_selectors': [ 'div.decideMenuWrapper', 'div.quickReply span.QuickUsernameInsert', 'div.pageContent div.staffClaimsStats', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки управления арбитражем', 'enabled': false }, 'moderator_control': { 'selectors': [], 'disabled_selectors': [ 'div.decideMenuWrapper', 'div.quickReply span.QuickThreadMove', 'div.pageContent div.staffClaimsStats', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки управления жалобами, недочетами и предложениями', 'enabled': false }, 'quick_ban': { 'selectors': [], 'disabled_selectors': [ 'div.profilePage a.siropuManageBan', ], 'ignored_selectors': [], 'description': 'Скрывать кнопки для заблокированного пользователя', 'enabled': false }, 'node_hide': { 'selectors': [], 'disabled_selectors': [ '.nodeList .list.node911', '.nodeList .list.node993', '.nodeList .list.node434', ], 'ignored_selectors': [], 'description': 'Скрывать разделы для команды форума', 'enabled': false }, 'market_control': { 'selectors': [], 'disabled_selectors': [ '.marketExtraSidebarMenu:has(a[href="games"])', 'div.marketItemView--sidebarUser a[href$="/orders"]', 'div.marketItemView--sidebarUser a[href$="/payments"]', 'div.marketItemView--sidebarUser span.Tooltip', ], 'ignored_selectors': [], 'description': 'Скрывать баланс и историю операций пользователя на маркете', 'enabled': false }, 'search_popup': { 'selectors': [], 'disabled_selectors': [ 'div.search_results div.Popup', ], 'ignored_selectors': [], 'description': 'Скрывать инструменты модератора при поиске', 'enabled': false } } } } if (!StreamerModeSettings) { GM_setValue('lzt_streamer_mode_settings', default_settings); StreamerModeSettings = default_settings; } function UpdateConfig() { function mergeConfigsSmart(defaultConfig, userConfig, preserveFields = ['banwords', 'enabled']) { const merged = structuredClone(defaultConfig); function deepMerge(target, source) { for (const key in source) { if (source.hasOwnProperty(key)) { const sourceValue = source[key]; if (sourceValue === undefined) continue; if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) { if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) { target[key] = {}; } deepMerge(target[key], sourceValue); } else { // Сохраняем только указанные поля из пользовательского конфига if (preserveFields.includes(key)) { target[key] = sourceValue; } } } } } deepMerge(merged, userConfig); return merged; } const updated = mergeConfigsSmart(default_settings, StreamerModeSettings); GM_setValue('lzt_streamer_mode_settings', updated); StreamerModeSettings = updated; } function collectSelectors(settings, mode = 'enabled') { const collect = (obj) => { if (!obj || typeof obj !== 'object' || !StreamerModeSettings.main_page.enabled) return []; return Object.values(obj).flatMap(value => { if (value.enabled && Array.isArray(value[mode === 'enabled' ? 'selectors' : 'disabled_selectors'])) { return value[mode === 'enabled' ? 'selectors' : 'disabled_selectors']; } return collect(value); }); }; return collect(settings); } // 1. Сразу добавляем CSS-блюр с плавными переходами const selectors = collectSelectors(StreamerModeSettings, 'enabled'); const disabled_selectors = collectSelectors(StreamerModeSettings, 'disabled'); const path = window.location.pathname; if (path === "/user/orders" || path === "/viewed") { selectors.push('div.MarketItems') } else if (path === "/streamer-mode") { disabled_selectors.push( 'div.pageContent div.error-container', 'div.pageContent div.errorOverlay', 'div.pageContent div.titleBar', 'div.pageContent div.market-selectCategory-block' ); } const style = document.createElement('style'); style.textContent = ` ${selectors.map(selector => `${selector}`).join(', ')} {filter: blur(6px);} ${disabled_selectors.map(selector => `${selector}`).join(', ')} {display: none !important;} div.bbCodeHide blockquote.hideContainer {filter: ${StreamerModeSettings.main_page?.enabled ? 'blur(6px)' : 'none'}} .member_tabs li a[href*="#change-logs"] { font-size:0px } .member_tabs li a[href*="#change-logs"]:before { content: "История блокировок"; font-size: 13px } .CensorStreamer { filter: blur(6px) !important; transition: filter 0.3s ease !important; position: relative !important; pointer-events: none !important; } .CensorStreamer:hover { filter: blur(4px) !important; } .CensorStreamerClicked { filter: none !important; pointer-events: auto !important; } .CensorStreamer::before { content: '' !important; position: absolute !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; z-index: 9999 !important; cursor: pointer !important; pointer-events: auto !important; background: transparent !important; } .CensorStreamerClicked::before { display: none !important; } `; document.head.appendChild(style); // Обновляем конфиг если надо UpdateConfig() // 2. Ждём, пока появится XenForo const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; Object.defineProperty(win, 'XenForo', { configurable: true, set(value) { delete win.XenForo; win.XenForo = value; if (typeof win.XenForo.register === 'function') { try { initBlurFeature(); } catch (error) { console.error('Ошибка при вызове initBlurFeature:', error); } } } }); // 3. Инициализация блюра через XenForo function initBlurFeature() { // Основная функция XenForo.applyBlurCustom = function(element, ignoredSelectors = [], index = null) { let el = element instanceof jQuery ? element[0] : element; if (!el || typeof el.classList === 'undefined') return; const isIgnored = (target) => ignoredSelectors.some((selector) => target.matches(selector)); if (isIgnored(el)) { el.style.filter = "none" return; // Не применяем блюр к игнорируемым элементам } // Проверяем, не обработан ли уже элемент if (el.classList.contains("CensorStreamer") || el.classList.contains("CensorStreamerClicked")) return; // Добавляем класс для блюра el.classList.add("CensorStreamer"); // Обработчик клика для снятия блюра const handleClick = (event) => { // Проверяем, что клик был по overlay (::before элементу) if (event.target === el) { event.preventDefault(); event.stopPropagation(); // Снимаем блюр и делаем элемент кликабельным el.classList.remove("CensorStreamer"); el.classList.add("CensorStreamerClicked"); // Удаляем обработчики, так как блюр снят el.removeEventListener("click", handleClick); } }; // Добавляем обработчик клика el.addEventListener("click", handleClick); }; function registerSelectors(section) { if (!section.enabled) return; const { selectors = [], ignored_selectors = [] } = section; selectors.forEach(sel => { XenForo.register(sel, function() { XenForo.applyBlurCustom(this, ignored_selectors); }); }); } function registerBlurForSelectorPage(settings) { const { selector_page } = settings; if (!selector_page || !settings.main_page?.enabled) return; Object.values(selector_page).forEach(section => { Object.values(section).forEach(category => { registerSelectors(category); }); }); // Специфическая логика для сообщений if (selector_page.general?.messages?.enabled) { XenForo.register('li.conversationItem', function() { const container = this.closest('ol'); XenForo.applyBlurCustom(container); }); } // Логика для MarketItems const marketEnabled = selector_page.market?.viewed?.enabled || selector_page.market?.orders?.enabled; if (marketEnabled) { XenForo.register('div.MarketItems', function() { const path = window.location.pathname; if ((path === "/user/orders" && selector_page.market?.orders?.enabled) || (path === "/viewed" && selector_page.market?.viewed?.enabled)) { XenForo.applyBlurCustom(this); } }); } // Секретный вопрос XenForo.register('input[name="secret_answer"]', function() { let el = this instanceof jQuery ? this[0] : this; el.setAttribute('type', 'password'); }); // Хайды XenForo.register('div.bbCodeHide', function() { let el = this instanceof jQuery ? this[0] : this; const quote = el.querySelector('div.quote'); const quoteContainer = el.querySelector('blockquote.hideContainer'); const attribution = el.querySelector('aside div.attribution.type'); if (quote && quoteContainer && attribution) { XenForo.applyBlurCustom(quoteContainer); } else { quoteContainer.style.filter = "none" } }); } const banWordsList = StreamerModeSettings?.banwords_page?.banwords XenForo.applyCensorCustom = function(element, banwords = []) { let el = element instanceof jQuery ? element[0] : element; if (!el || typeof el.classList === 'undefined') return; const regex = new RegExp(banWordsList.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'gi'); function censorText(text) { return text.replace(regex, match => '*'.repeat(match.length)); } function censorNode(node) { if (node.nodeType === Node.TEXT_NODE) { const censored = censorText(node.nodeValue); if (node.nodeValue !== censored) { node.nodeValue = censored; } } else if (node.nodeType === Node.ELEMENT_NODE) { for (let child of node.childNodes) { censorNode(child); } } } censorNode(el); }; function pochinit_kostili() { const api_use_info = document.querySelector('div.profilePage div.profile_info_row:nth-of-type(3)'); if (api_use_info) { const api_text = api_use_info.querySelector('div.labeled').textContent.trim(); if (!api_text.includes('API')) { api_use_info.style.display = 'revert !important'; } } const id_info = document.querySelector('div.profilePage div.profile_info_row:nth-of-type(2)'); if (id_info) { const id_user = id_info.querySelector('div.label').textContent.trim(); if (!id_user.includes('ID')) { id_info.style.display = 'revert !important'; } } } registerBlurForSelectorPage(StreamerModeSettings); pochinit_kostili(); if (StreamerModeSettings?.banwords_page?.enabled && StreamerModeSettings?.main_page?.enabled) { const selectors = [ 'div.messageContent', 'div.commentContent', 'div.pageContent div.titleBar', '.threadLastPost', '.discussionListItem', 'li.Alert', ' div.liveAlert', 'ul li.conversationItem', '#page_current_info', '.userStatus', 'div.ng-notification div.body' ] selectors.forEach(sel => { XenForo.register(sel, function() { XenForo.applyCensorCustom(this, banWordsList); }); }); XenForo.register('ol li.conversationItem', function() { const container = this.querySelector('.Content'); XenForo.applyCensorCustom(container); }); XenForo.register('ul li.conversationItem', function() { const container = this.querySelector('.Content'); XenForo.applyCensorCustom(container); }); } const publicTabsCount = document.querySelectorAll('ul.account-links .navTab').length; const searchBar = document.querySelector('div#searchBar .formPopup'); if (publicTabsCount >= 7 && searchBar) { searchBar.style.width = '110px'; } const publicTabs = document.querySelector('ul.account-links'); const newItem = document.createElement('li'); newItem.classList.add('navTab', 'PopupClosed'); newItem.innerHTML = ` <a href="/streamer-mode" class="navLink"> <div class="counter-container"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"> <path d="M23 7l-7 5 7 5V7z"/> <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/> </svg> </div> </a> `; publicTabs.prepend(newItem); if (path === "/streamer-mode") { document.title = 'LZT Streamer Mode [made ❤︎ llimonix]'; const settingsRoot = document.querySelector('div#headerMover div.pageContent'); const htmlTree = buildStreamerModeSettings(StreamerModeSettings); settingsRoot.appendChild(htmlTree); buildBanwordsInputStyle(); document.getElementById('lzt_streamer_mode_save')?.addEventListener('click', lzt_streamer_mode_save); document.getElementById('lzt_streamer_mode_reset')?.addEventListener('click', function () { GM_deleteValue('lzt_streamer_mode_settings'); location.reload(); }); document.getElementById('lzt_sm_banwords_show')?.addEventListener('click', toggleBanwordVisibility); document.getElementById('lzt_sm_banwords_import')?.addEventListener('click', buttonBanwordImport); document.getElementById('lzt_sm_banwords_export')?.addEventListener('click', buttonBanwordExport); } } function toggleBanwordVisibility() { const btnBanwords = document.getElementById('lzt_sm_banwords_show'); const isCurrentlyShown = btnBanwords.classList.contains('showBanwords'); const show = !isCurrentlyShown; if (show) { btnBanwords.classList.remove('unshowBanwords'); btnBanwords.classList.add('showBanwords'); btnBanwords.textContent = 'Показать запрещенные слова' } else { btnBanwords.classList.remove('showBanwords'); btnBanwords.classList.add('unshowBanwords'); btnBanwords.textContent = 'Скрыть запрещенные слова' } document.querySelectorAll('.word[data-real]').forEach(span => { const real = span.dataset.real; span.textContent = isCurrentlyShown ? real : '•'.repeat(real.length); }); } function buttonBanwordExport() { // Получение банвордов const banwordElements = document.querySelectorAll('.lzt_sm_input_banword .word'); const banwords = Array.from(banwordElements) .map(el => el.dataset.real?.trim()) // <-- Берём из data-real .filter(word => word && word.length > 0); console.log(banwords) // Создание текстового файла const text = banwords.join(', '); const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); // Создание и клик по ссылке const a = document.createElement('a'); a.href = url; a.download = 'banwords.txt'; a.style.display = 'none'; document.body.appendChild(a); a.click(); // Очистка document.body.removeChild(a); URL.revokeObjectURL(url); } function buttonBanwordImport() { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.txt'; fileInput.style.display = 'none'; fileInput.addEventListener('change', () => { const file = fileInput.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function (e) { const content = e.target.result; if (!content) return; // Получаем контейнер и input const inputContainer = document.querySelector('.lzt_sm_input'); const textInput = document.getElementById('lzt_sm_banwords_input'); if (!inputContainer || !textInput) return; // Список уже добавленных слов const existingWords = new Set(); inputContainer.querySelectorAll('.word[data-real]').forEach(span => { const real = span.dataset.real?.trim(); if (real) existingWords.add(real); }); // Обработка слов из файла const importedWords = content .split(/[\n,]+/) .map(w => w.trim()) .filter(w => w.length > 0); const added = new Set(); for (const word of importedWords) { if (existingWords.has(word) || added.has(word)) continue; // Создание тега слова const wordTag = document.createElement('div'); wordTag.className = 'lzt_sm_input_banword'; const span = document.createElement('span'); span.className = 'word'; span.dataset.real = word; span.textContent = '•'.repeat(word.length); // скрытое отображение const removeBtn = document.createElement('div'); removeBtn.className = 'remove_word'; removeBtn.textContent = '×'; removeBtn.onclick = () => { wordTag.remove(); existingWords.delete(word); }; wordTag.appendChild(span); wordTag.appendChild(removeBtn); inputContainer.insertBefore(wordTag, textInput); added.add(word); } XenForo.stackAlert(`Импортировано слов: ${added.size}`, 5000) }; reader.readAsText(file); }); document.body.appendChild(fileInput); fileInput.click(); document.body.removeChild(fileInput); } function lzt_streamer_mode_save() { // Клонируем текущие настройки const updatedSettings = structuredClone(StreamerModeSettings); document.querySelectorAll('div.lztStreamerMode input[type="checkbox"]').forEach(checkbox => { const path = checkbox.id.replace('lzt_sm_', '') if (!path) return; const keys = path.split('.'); // Разбиваем путь по точкам let current = updatedSettings; // Идем по иерархии ключей for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current)) { console.warn(`Путь не найден: ${key} в ${path}`); return; } current = current[key]; } const lastKey = keys[keys.length - 1]; if (lastKey in current) { current[lastKey].enabled = checkbox.checked; } else { console.warn(`Ключ ${lastKey} не найден в ${path}`); } }); // Получение банвордов const banwordElements = document.querySelectorAll('.lzt_sm_input_banword .word'); const banwords = Array.from(banwordElements) .map(el => el.dataset.real?.trim()) // <-- Берём из data-real .filter(word => word && word.length > 0); if (updatedSettings.banwords_page) { updatedSettings.banwords_page.banwords = banwords; } else { console.warn('banwords_page отсутствует в настройках'); } // Сохраняем GM_setValue('lzt_streamer_mode_settings', updatedSettings); XenForo.alert('[LZT Streamer Mode] Настройки сохранены', '', 5000) console.log('[LZT Streamer Mode] Настройки сохранены'); } function buildBanwordsInputStyle() { const container = document.querySelector('.lzt_sm_input'); const input = document.getElementById('lzt_sm_banwords_input'); const existingWords = new Set(); // Создание тега function addWordTag(word) { const btnBanwords = document.getElementById('lzt_sm_banwords_show'); const isCurrentlyShown = !btnBanwords.classList.contains('showBanwords'); const trimmed = word.trim(); if (!trimmed || existingWords.has(trimmed.toLowerCase())) return; const tag = document.createElement('div'); tag.className = 'lzt_sm_input_banword'; const span = document.createElement('span'); span.className = 'word'; span.textContent = isCurrentlyShown ? trimmed : '•'.repeat(trimmed.length); span.dataset.real = trimmed; // сохраняем оригинальное значение const remove = document.createElement('div'); remove.className = 'remove_word'; remove.textContent = '×'; remove.addEventListener('click', () => { tag.remove(); existingWords.delete(trimmed.toLowerCase()); }); tag.appendChild(span); tag.appendChild(remove); container.insertBefore(tag, input); existingWords.add(trimmed.toLowerCase()); } // Обработка Enter и запятой input.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ',' || e.code === "Space") { e.preventDefault(); const parts = input.value.split(','); parts.forEach(word => addWordTag(word)); input.value = ''; } else if (e.key === 'Backspace' && input.value === '') { const tags = container.querySelectorAll('.lzt_sm_input_banword'); if (tags.length > 0) { const lastTag = tags[tags.length - 1]; const word = lastTag.querySelector('.word').textContent; existingWords.delete(word.toLowerCase()); lastTag.remove(); } } }); // Обработка вставки текста input.addEventListener('paste', function (e) { e.preventDefault(); const paste = (e.clipboardData || window.clipboardData).getData('text'); const parts = paste.split(','); parts.forEach(word => addWordTag(word)); input.value = ''; }); } function buildStreamerModeSettings(settings) { const container = document.createElement('div'); container.className = 'container'; const mainContentBlock = document.createElement('div'); mainContentBlock.className = 'mainContentBlock sectionMain'; container.appendChild(mainContentBlock); const lztStreamerMode = document.createElement('div'); lztStreamerMode.className = 'lztStreamerMode'; mainContentBlock.appendChild(lztStreamerMode); const title = document.createElement('div'); title.className = 'lzt_sm_title'; title.style.borderRadius = '10px 10px 0 0'; title.style.margin = '0'; title.textContent = 'LZT Streamer Mode [made ❤︎ llimonix]'; lztStreamerMode.appendChild(title); for (const [pageKey, page] of Object.entries(settings)) { const dl = document.createElement('dl'); dl.className = 'ctrlUnit_blocks'; const heading = document.createElement('div'); heading.className = 'textHeading'; heading.textContent = page.title || 'Без названия'; dl.appendChild(heading); if (pageKey === 'main_page') { const div = document.createElement('div'); const label = document.createElement('label'); const input = document.createElement('input'); input.type = 'checkbox'; input.id = `lzt_sm_${pageKey}`; input.checked = page.enabled; label.appendChild(input); label.appendChild(document.createTextNode(' ' + (page.description || key))); div.appendChild(label); dl.appendChild(div); lztStreamerMode.appendChild(dl); continue; } if (pageKey === 'banwords_page') { const div = document.createElement('div'); const label = document.createElement('label'); const input = document.createElement('input'); input.type = 'checkbox'; input.id = `lzt_sm_${pageKey}`; input.checked = page.enabled; label.appendChild(input); label.appendChild(document.createTextNode(' ' + (page.description || key))); div.appendChild(label); dl.appendChild(div); const dd = document.createElement('dd'); dd.style.margin = '15px 0 0'; // Контейнер для ввода и тегов const inputContainer = document.createElement('div'); inputContainer.className = 'lzt_sm_input'; const textInput = document.createElement('input'); textInput.type = 'text'; textInput.id = 'lzt_sm_banwords_input'; textInput.className = 'lzt_sm_input'; inputContainer.appendChild(textInput); dd.appendChild(inputContainer); // Контейнер с кнопками const buttonsContainer = document.createElement('div'); buttonsContainer.className = 'banwordsOptionsBtn'; buttonsContainer.style.marginTop = '10px'; // Кнопка: Показать запрещенные слова const showButton = document.createElement('button'); showButton.id = 'lzt_sm_banwords_show' showButton.className = 'button smallButton showBanwords'; showButton.style.marginRight = '5px'; showButton.textContent = 'Показать запрещенные слова'; // Кнопка: Импортировать слова const importButton = document.createElement('button'); importButton.id = 'lzt_sm_banwords_import' importButton.className = 'button smallButton'; importButton.style.marginRight = '5px'; importButton.textContent = 'Импортировать слова'; // Кнопка: Экспортировать слова const exportButton = document.createElement('button'); exportButton.id = 'lzt_sm_banwords_export' exportButton.className = 'button smallButton'; exportButton.textContent = 'Экспортировать слова'; // Добавляем кнопки в контейнер buttonsContainer.appendChild(showButton); buttonsContainer.appendChild(importButton); buttonsContainer.appendChild(exportButton); dl.appendChild(dd); dl.appendChild(buttonsContainer); lztStreamerMode.appendChild(dl); // Загрузить слова из page.banwords (массив слов) const existingWords = new Set(); // для исключения дублей const renderBanwordTag = (word) => { if (!word || existingWords.has(word)) return; existingWords.add(word); const wordTag = document.createElement('div'); wordTag.className = 'lzt_sm_input_banword'; const span = document.createElement('span'); span.className = 'word'; span.textContent = word; const removeBtn = document.createElement('div'); removeBtn.className = 'remove_word'; removeBtn.textContent = '×'; span.textContent = '•'.repeat(word.length); // маскировка span.dataset.real = word; // сохраняем оригинальное значение removeBtn.onclick = () => { wordTag.remove(); existingWords.delete(word); }; wordTag.appendChild(span); wordTag.appendChild(removeBtn); inputContainer.insertBefore(wordTag, textInput); }; // подгрузка из конфига if (Array.isArray(page.banwords)) { page.banwords.forEach(renderBanwordTag); } continue; } const dd = document.createElement('dd'); const ul = document.createElement('ul'); for (const [sectionKey, section] of Object.entries(page)) { if (['title', 'description', 'enabled'].includes(sectionKey)) continue; if (!section || Object.keys(section).length === 0) continue; const li = document.createElement('li'); const desc = document.createElement('div'); desc.className = 'lzt_sm_description'; desc.textContent = section.title || sectionKey; li.appendChild(desc); const checkboxDiv = document.createElement('div'); checkboxDiv.className = 'checkbox_streamer'; if (section.warning) { const label = document.createElement('label'); label.className = 'warningText'; label.appendChild(document.createTextNode(' ' + (section.warning || sectionKey))); checkboxDiv.appendChild(label); } for (const [key, item] of Object.entries(section)) { if (['title', 'description', 'enabled'].includes(key)) continue; if (!item.selectors) continue; const label = document.createElement('label'); const input = document.createElement('input'); input.type = 'checkbox'; input.id = `lzt_sm_${pageKey}.${sectionKey}.${key}`; input.checked = item.enabled; label.appendChild(input); label.appendChild(document.createTextNode(' ' + (item.description || key))); checkboxDiv.appendChild(label); } li.appendChild(checkboxDiv); ul.appendChild(li); } dd.appendChild(ul); dl.appendChild(dd); lztStreamerMode.appendChild(dl); } // Кнопки const btnDl = document.createElement('dl'); btnDl.className = 'ctrlUnit_blocks_button_save'; const btnDd = document.createElement('dd'); btnDd.innerHTML = ` <div style="display: flex; flex-direction: row; gap: 8px; flex-wrap: wrap;"> <button class="button primary" id="lzt_streamer_mode_save">Сохранить изменения</button> <button class="button" id="lzt_streamer_mode_reset">Сбросить настройки</button> </div>`; btnDl.appendChild(btnDd); mainContentBlock.appendChild(btnDl); const style = document.createElement('style'); style.textContent = ` .lztStreamerMode .lzt_sm_description { margin: 0 0 10px; color: #949494; } input.lzt_sm_input { height: 38px; line-height: 38px; padding-left: 4px; border: 0; color: rgb(214, 214, 214); flex-grow: 1; background: none; width: 100px; } div.lzt_sm_input { font-size: 13px; color: #d6d6d6; border: 0 none #000; box-sizing: border-box; padding: 4px 8px; cursor: text; line-height: 34px; border-radius: 8px; outline: none; display: flex; flex-wrap: wrap; height: unset !important; background: none; box-shadow: 0 0 0 1px #323232; } .lztStreamerMode .textHeading { margin: 0 0 10px; } .lzt_sm_title { display: flex; align-items: center; gap: 8px; padding: 20px; font-size: 16px; font-weight: 600; border-bottom: 1px solid #242424; margin: -10px -10px 16px -10px; width: 100%; box-sizing: border-box; } .mainContentBlock { border-radius: 10px; vertical-align: top; margin: 10px auto; zoom: 1; } .ctrlUnit_blocks { border-bottom: 1px solid rgb(36, 36, 36); padding: 20px 20px 16px 20px; } .ctrlUnit_blocks_button { border-bottom: 1px solid rgb(36, 36, 36); padding: 20px 0px 20px 20px; } .lztStreamerMode .ctrlUnit_blocks label { margin: 0 20px 0 0; white-space: nowrap; } .ctrlUnit_blocks>dd>*>li { margin: 5px 0 14px; padding-left: 1px; } .ctrlUnit_blocks_button_save { padding: 20px 0px 20px 20px; } .checkbox_streamer { display: flex; gap: 8px; flex-direction: column; flex-wrap: wrap; } .lzt_sm_input_banword { border-radius: 6px; display: inline-flex; gap: 5px; padding: 4px 8px; align-items: center; margin: 5px 5px 5px 0; height: 20px; line-height: 20px; background-color: #2d2d2d; } .lzt_sm_input_banword span.word { height: 100%; width: 100%; display: flex; align-items: center; font-weight: 600; } .lzt_sm_input_banword div.remove_word { font-size: 19px; color: #999; cursor: pointer; margin-left: 2px; }`; mainContentBlock.appendChild(style); return container; }