您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Some useful utilities for Lolzteam
当前为
// ==UserScript== // @name LZT Local Uniq LTV // @version 1.0.2 // @description Some useful utilities for Lolzteam // @description:ru Локальный уник LTV // @icon https://cdn.jsdelivr.net/gh/ilyhalight/[email protected]/public/static/img/lzt-upgrade-mini.png // @author Kain // @license MIT // @namespace lztlocaluniqLTV // @match https://lolz.live/* // @match https://zelenka.guru/* // @match https://lolz.guru/* // @match https://lzt.market/* // @connect https://greasyfork.org/ru/scripts/550788-lzt-local-uniq-ltv // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_info // @run-at document-start // ==/UserScript== (function() { 'use strict'; // ====================КОНФИГУРАЦИЯ ==================== const MEGA_CONFIG = { VERSION: '1.0', UPDATE_INTERVAL: 100, OBSERVER_DEBOUNCE: 100, MAX_RETRIES: 10, DEBUG: true, THEMES: { DARK: { name: 'Тёмная', bg: '#1a1a1a', card: '#2a2a2a', text: '#ffffff', accent: '#ff6b6b', secondary: '#228e5d', border: '#444' } }, ICONS: { DEFAULT: '' } }; // ==================== СИСТЕМА ЛОГГИРОВАНИЯ ==================== class MegaLogger { static log(...args) { if (MEGA_CONFIG.DEBUG) { console.log(`%c🔹 LZT MEGA v${MEGA_CONFIG.VERSION}:`, 'color: #183b14; font-weight: bold', ...args); } } static error(...args) { console.error(`%c❌ LZT MEGA ERROR:`, 'color: #ff6b6b; font-weight: bold', ...args); } static warn(...args) { console.warn(`%c⚠️ LZT MEGA WARN:`, 'color: #ff9800; font-weight: bold', ...args); } static success(...args) { console.log(`%c✅ LZT MEGA SUCCESS:`, 'color: #4caf50; font-weight: bold', ...args); } } // ==================== СИСТЕМА ХРАНЕНИЯ ==================== class MegaStorage { static getSettings() { try { const settings = { // Основные настройки myId: localStorage.getItem('megaUniq-myId') || '', myName: localStorage.getItem('megaUniq-myName') || '', username: localStorage.getItem('megaUniq-username') || '', banner: localStorage.getItem('megaUniq-banner') || '', usernameCss: localStorage.getItem('megaUniq-username-css') || '', bannerCss: localStorage.getItem('megaUniq-banner-css') || '', // Тема theme: localStorage.getItem('megaUniq-theme') || 'DARK', // Дополнительные настройки autoDetect: localStorage.getItem('megaUniq-autoDetect') !== 'false', // Статистика lastUpdate: localStorage.getItem('megaUniq-lastUpdate') || '', usageCount: parseInt(localStorage.getItem('megaUniq-usageCount') || '0') }; MegaLogger.log('Настройки загружены:', settings); return settings; } catch (e) { MegaLogger.error('Ошибка загрузки настроек:', e); return this.getDefaultSettings(); } } static saveSettings(settings) { try { // ВАЖНО: Добавляем проверку на валидность данных if (!settings || typeof settings !== 'object') { MegaLogger.error('Некорректные настройки для сохранения'); return false; } // Основные настройки localStorage.setItem('megaUniq-myId', settings.myId || ''); localStorage.setItem('megaUniq-myName', settings.myName || ''); localStorage.setItem('megaUniq-username', settings.username || ''); localStorage.setItem('megaUniq-banner', settings.banner || ''); localStorage.setItem('megaUniq-username-css', settings.usernameCss || ''); localStorage.setItem('megaUniq-banner-css', settings.bannerCss || ''); // Тема localStorage.setItem('megaUniq-theme', settings.theme || 'DARK'); // Дополнительные localStorage.setItem('megaUniq-autoDetect', settings.autoDetect ? 'true' : 'false'); // Статистика localStorage.setItem('megaUniq-lastUpdate', new Date().toISOString()); localStorage.setItem('megaUniq-usageCount', (parseInt(settings.usageCount) || 0).toString()); // СИНХРОНИЗАЦИЯ: Принудительно сохраняем изменения localStorage._forceSave = Date.now().toString(); MegaLogger.success('Настройки сохранены в localStorage. Ключи:', Object.keys(settings)); // Дополнительная проверка this.verifySave(settings); return true; } catch (e) { MegaLogger.error('Ошибка сохранения настроек:', e); // Пробуем альтернативный метод return this.alternativeSave(settings); } } // ДОБАВЬ этот метод для проверки сохранения static verifySave(settings) { try { const saved = this.getSettings(); let success = true; for (const key in settings) { if (settings[key] !== saved[key]) { MegaLogger.error(`Ошибка сохранения ключа ${key}:`, { expected: settings[key], actual: saved[key] }); success = false; } } if (success) { MegaLogger.success('✓ Все настройки успешно сохранены и проверены'); } return success; } catch (e) { MegaLogger.error('Ошибка проверки сохранения:', e); return false; } } // ДОБАВЬ альтернативный метод сохранения static alternativeSave(settings) { try { // Пробуем сохранить как единый JSON объект const backupKey = 'megaUniq-backup-settings'; localStorage.setItem(backupKey, JSON.stringify(settings)); MegaLogger.log('Настройки сохранены через альтернативный метод'); return true; } catch (e) { MegaLogger.error('Альтернативное сохранение тоже не удалось:', e); return false; } } static getDefaultSettings() { return { myId: '', myName: '', username: '', banner: 'LOLZTEAM', usernameCss: '', bannerCss: '', theme: 'DARK', autoDetect: true, lastUpdate: '', usageCount: 0 }; } static clearCache() { try { const keys = Object.keys(localStorage).filter(key => key.startsWith('megaUniq-')); keys.forEach(key => localStorage.removeItem(key)); MegaLogger.log('Кэш очищен. Удалено ключей:', keys.length); return keys.length; } catch (e) { MegaLogger.error('Ошибка очистки кэша:', e); return 0; } } static exportSettings() { return JSON.stringify(this.getSettings(), null, 2); } static importSettings(json) { try { const settings = JSON.parse(json); return this.saveSettings(settings); } catch (e) { MegaLogger.error('Ошибка импорта настроек:', e); return false; } } static getStorageInfo() { const keys = Object.keys(localStorage).filter(key => key.startsWith('megaUniq-')); let totalSize = 0; keys.forEach(key => { totalSize += localStorage.getItem(key).length; }); return { keys: keys.length, size: totalSize }; } } // ==================== СИСТЕМА ОБРАБОТКИ DOM ==================== class MegaDOMProcessor { constructor(settings) { this.settings = settings; this.processedElements = new WeakSet(); this.retryCount = 0; this.lastProcessed = 0; } // ПРОЦЕССИНГ: applyMegaUniq() { if (!this.settings.myId || !this.settings.myName) { MegaLogger.warn('Настройки не заданы'); return false; } const startTime = Date.now(); MegaLogger.log(`Запуск мега-обработки для: ${this.settings.myName}`); try { // Очищаем предыдущие обработки this.processedElements = new WeakSet(); // Последовательная обработка ВСЕХ элементов const processors = [ () => this.processUserLinks(), () => this.processBanners(), () => this.processProfileBanners(), () => this.processAvatars(), () => this.processIcons(), () => this.processTextNodes(), () => this.processPageTitle(), () => this.processRichContent(), () => this.processMetaTags(), () => this.processNavigation(), () => this.processTooltips() ]; processors.forEach(processor => processor()); this.retryCount = 0; const duration = Date.now() - startTime; MegaLogger.success(`Мега-обработка завершена за ${duration}ms`); this.lastProcessed = Date.now(); return true; } catch (error) { MegaLogger.error('Критическая ошибка обработки:', error); this.retryCount++; if (this.retryCount <= MEGA_CONFIG.MAX_RETRIES) { setTimeout(() => this.applyMegaUniq(), 1000); } return false; } } // 1. ОБРАБОТКА ССЫЛОК И ЭЛЕМЕНТОВ ИМЕНИ processUserLinks() { const selectors = [ `a[href*="/members/${this.settings.myId}/"]`, `a[href*="/members/${this.settings.myId}?"]`, `a[href*="/members/${this.settings.myId}"]`, `a[href*="user_id=${this.settings.myId}"]`, `[href*="/members/${this.settings.myId}/"]`, `h1.username span`, `#NavigationAccountUsername span`, `.username span`, `.accountUsername span`, `[itemprop="name"] span`, `h1.username`, `#NavigationAccountUsername`, `.accountUsername`, `[itemprop="name"]` ]; selectors.forEach(selector => { try { document.querySelectorAll(selector).forEach(element => { if (this.processedElements.has(element)) return; if (element.tagName === 'A' || element.href) { this.processLinkElement(element); } else { this.processUsernameElement(element); } this.processedElements.add(element); }); } catch (e) { MegaLogger.error(`Ошибка селектора ${selector}:`, e); } }); document.querySelectorAll('a').forEach(link => { if (link.textContent.includes(this.settings.myName) && !this.processedElements.has(link)) { this.processLinkElement(link); this.processedElements.add(link); } }); this.processSpecialUsernameElements(); } processLinkElement(link) { const usernameText = this.settings.username || this.settings.myName; const finalText = this.settings.avatarIcon ? `${this.settings.avatarIcon} ${usernameText}` : usernameText; // Сохраняем оригинальную структуру const originalHTML = link.innerHTML; let newHTML = originalHTML; // Умная замена: ищем текст содержащий имя пользователя if (originalHTML.includes(this.settings.myName)) { // Заменяем имя пользователя, сохраняя окружающий текст newHTML = originalHTML.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), finalText ); } // Применяем изменения if (newHTML !== originalHTML) { link.innerHTML = newHTML; } // Применяем CSS - но теперь более аккуратно if (this.settings.usernameCss) { this.safeApplyStylesToUsernameInLink(link, this.settings.myName, finalText); } } // НОВЫЙ МЕТОД: Умное применение стилей только к имени в ссылке safeApplyStylesToUsernameInLink(link, oldName, newName) { // Ищем текстовые узлы, содержащие новое имя const walker = document.createTreeWalker( link, NodeFilter.SHOW_TEXT, null, false ); let textNode; const nodesToProcess = []; while (textNode = walker.nextNode()) { if (textNode.textContent.includes(newName)) { nodesToProcess.push(textNode); } } // Обрабатываем найденные узлы nodesToProcess.forEach(textNode => { const parent = textNode.parentNode; // Если родитель уже спан с нашими стилями - пропускаем if (parent.classList && parent.classList.contains('mega-username-styled')) { return; } // Разделяем текст на части до и после имени const text = textNode.textContent; const nameIndex = text.indexOf(newName); if (nameIndex !== -1) { const beforeText = text.substring(0, nameIndex); const afterText = text.substring(nameIndex + newName.length); // Создаем новые узлы const beforeNode = document.createTextNode(beforeText); const nameSpan = document.createElement('span'); const afterNode = document.createTextNode(afterText); // Настраиваем спан с именем nameSpan.className = 'mega-username-styled'; nameSpan.style.cssText = this.settings.usernameCss; nameSpan.textContent = newName; // Заменяем оригинальный текстовый узел const fragment = document.createDocumentFragment(); if (beforeText) fragment.appendChild(beforeNode); fragment.appendChild(nameSpan); if (afterText) fragment.appendChild(afterNode); parent.replaceChild(fragment, textNode); } }); } processUsernameElement(element) { const usernameText = this.settings.username || this.settings.myName; const finalText = this.settings.avatarIcon ? `${this.settings.avatarIcon} ${usernameText}` : usernameText; if (element.textContent.includes(this.settings.myName)) { if (element.tagName === 'SPAN') { const originalHTML = element.innerHTML; const newHTML = originalHTML.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), finalText ); if (newHTML !== originalHTML) { element.innerHTML = newHTML; } } else { element.textContent = element.textContent.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), finalText ); } if (this.settings.usernameCss) { this.safeApplyStyles(element, this.settings.usernameCss); } } } processSpecialUsernameElements() { const specialCases = [ { selector: 'h1.username', innerSelector: 'span' }, { selector: '#NavigationAccountUsername', innerSelector: 'span' }, { selector: '.accountUsername', innerSelector: 'span' } ]; specialCases.forEach(specialCase => { try { document.querySelectorAll(specialCase.selector).forEach(container => { if (this.processedElements.has(container)) return; const innerElement = container.querySelector(specialCase.innerSelector); if (innerElement && innerElement.textContent.includes(this.settings.myName)) { this.processUsernameElement(innerElement); this.processedElements.add(container); } }); } catch (e) { MegaLogger.error(`Ошибка обработки специального случая ${specialCase.selector}:`, e); } }); } // 2. ОБРАБОТКА ЛЫЧЕК processBanners() { const bannerSelectors = [ 'em.userBanner', '.userBanner', '.userBannerWrapper', '[class*="banner"]', '[class*="Banner"]', 'span.banner', 'div.banner' ]; bannerSelectors.forEach(selector => { try { document.querySelectorAll(selector).forEach(banner => { if (this.processedElements.has(banner)) return; // ⚠️ Пропускаем бейджи у аватарок if ( banner.classList.contains('avatarUserBadge') || banner.closest('.avatarUserBadge') ) return; if (this.isMyBanner(banner)) { this.processBannerElement(banner); this.processedElements.add(banner); } }); } catch (e) { MegaLogger.error(`Ошибка обработки баннеров ${selector}:`, e); } }); } // НОВЫЙ МЕТОД: Обработка лычек на странице профиля processProfileBanners() { if (!window.location.href.includes('/members/')) return; const profileSelectors = [ '.mainProfileHeader .userBanner', '.profilePage .userBanner', '.userDetails .userBanner', '[class*="profile"] .userBanner', '.userBanner' ]; profileSelectors.forEach(selector => { try { document.querySelectorAll(selector).forEach(banner => { if (this.processedElements.has(banner)) return; // ⚠️ Пропускаем бейджи у аватарок в профиле if ( banner.classList.contains('avatarUserBadge') || banner.closest('.avatarUserBadge') ) return; if (window.location.href.includes(`/members/${this.settings.myId}/`)) { MegaLogger.log('Найдена лычка в профиле:', banner); this.processBannerElement(banner); this.processedElements.add(banner); } }); } catch (e) { MegaLogger.error(`Ошибка обработки профильных баннеров ${selector}:`, e); } }); } isMyBanner(banner) { const myId = String((this.settings && this.settings.myId) || '').trim(); const myName = (this.settings && this.settings.myName) ? String(this.settings.myName).trim() : ''; if (!myId) { // если myId не задан — не показываем лычку console.warn('isMyBanner: myId not set in settings', this.settings); return false; } // 1) если мы на странице профиля вида /members/<id>/ или ..<id> const url = window.location.href || ''; if (url.includes(`/members/${myId}/`) || url.match(new RegExp(`[\\./]${myId}(?:/|$)`))) { return true; } // вспомогательная функция: проверяет элемент на наличие ссылки/атрибута нашего id или имени function elementHasMyLink(el) { if (!el) return false; // 1) элементы с data-user-id const dataEls = el.querySelectorAll('[data-user-id]'); for (const e of dataEls) { if (String(e.getAttribute('data-user-id')) === myId) return true; } // 2) перебираем ссылки/якоря в контейнере const anchors = el.querySelectorAll('a[href], a'); for (const a of anchors) { const href = a.getAttribute('href') || ''; const dataUid = a.getAttribute('data-user-id') || (a.dataset && a.dataset.userId); if (dataUid && String(dataUid) === myId) return true; if (href.indexOf(`/members/${myId}/`) !== -1) return true; // матч на формат username.12345 (часто у XenForo в URL профиля) if (href.match(new RegExp(`\\.${myId}(?:/|$)`))) return true; if (myName && a.textContent && a.textContent.trim() === myName) return true; const dname = a.getAttribute('data-user-name') || (a.dataset && a.dataset.userName); if (myName && dname && dname === myName) return true; } return false; } // 2) типичные контейнеры рядом с баннером const container = banner.closest('.message, .messageUserInfo, .posterAvatar, .memberListItem, .userItem, .userList, .profilePage, .mainProfileHeader, .messageList'); if (elementHasMyLink(container)) return true; // 3) поднимаемся вверх по DOM до N уровней и проверяем let parent = banner.parentElement; for (let i = 0; i < 6 && parent; i++, parent = parent.parentElement) { if (elementHasMyLink(parent)) return true; } // не нашёл — чужая лычка return false; } processBannerElement(banner) { const bannerText = this.settings.banner || ''; const finalText = bannerText; MegaLogger.log('Обработка лычки:', banner, 'Текст:', finalText); // Ищем текстовый элемент внутри лычки let textElement = banner.querySelector('strong') || banner.querySelector('span') || banner; // Сохраняем оригинальные классы и структуру const originalClasses = textElement.className; const originalHTML = textElement.innerHTML; // Применяем текст textElement.textContent = finalText; textElement.className = originalClasses; // Восстанавливаем классы // Применяем CSS - очищаем старые стили и применяем новые if (this.settings.bannerCss) { banner.style.cssText = ''; this.safeApplyStyles(banner, this.settings.bannerCss); // Также применяем к внутренним элементам banner.querySelectorAll('strong, span').forEach(el => { el.style.cssText = ''; this.safeApplyStyles(el, this.settings.bannerCss); }); } MegaLogger.log('Лычка обработана:', banner); } // 3. ОБРАБОТКА АВАТАРОК processAvatars() { const avatarSelectors = [ '.avatar', '[class*="avatar"]', '.avatarScaler', '.userImg', '.profilePhoto', '[class*="userImg"]', '[class*="profilePhoto"]' ]; avatarSelectors.forEach(selector => { try { document.querySelectorAll(selector).forEach(avatar => { if (this.processedElements.has(avatar)) return; if (this.isMyAvatar(avatar)) { this.processAvatarElement(avatar); this.processedElements.add(avatar); } }); } catch (e) { MegaLogger.error(`Ошибка обработки аватарок ${selector}:`, e); } }); } isMyAvatar(avatar) { return (avatar.href && avatar.href.includes(`/members/${this.settings.myId}/`)) || (avatar.closest && avatar.closest(`a[href*="/members/${this.settings.myId}/"]`)) || (avatar.querySelector && avatar.querySelector(`a[href*="/members/${this.settings.myId}/"]`)); } processAvatarElement(avatar) { const usernameText = this.settings.username || this.settings.myName; const finalText = this.settings.avatarIcon ? `${this.settings.avatarIcon} ${usernameText}` : usernameText; if (this.settings.avatarIcon && !avatar.previousSibling?.textContent?.includes(this.settings.avatarIcon)) { const iconSpan = document.createElement('span'); iconSpan.textContent = this.settings.avatarIcon + ' '; iconSpan.style.marginRight = '5px'; avatar.parentNode.insertBefore(iconSpan, avatar); } if (this.settings.usernameCss) { this.safeApplyStyles(avatar, this.settings.usernameCss); } } // 4. ОБРАБОТКА ИКОНОК processIcons() { if (!this.settings.avatarIcon) return; document.querySelectorAll(`a[href*="/members/${this.settings.myId}/"]`).forEach(link => { if (this.settings.avatarIcon && !link.textContent.includes(this.settings.avatarIcon)) { link.innerHTML = this.settings.avatarIcon + ' ' + link.innerHTML; } }); } // 5. ОБРАБОТКА ТЕКСТОВЫХ УЗЛОВ processTextNodes() { try { const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { if (this.processedElements.has(node.parentElement) || node.parentElement.closest('a, .userBanner, .avatar, [class*="user"]')) { return NodeFilter.FILTER_REJECT; } return node.textContent.includes(this.settings.myName) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } }, false ); let node; while ((node = walker.nextNode())) { if (node.textContent.trim() === this.settings.myName) { const finalText = this.settings.avatarIcon ? `${this.settings.avatarIcon} ${this.settings.username || this.settings.myName}` : this.settings.username || this.settings.myName; node.textContent = finalText; } } } catch (e) { MegaLogger.error('Ошибка обработки текстовых узлов:', e); } } // 6. ДОПОЛНИТЕЛЬНЫЕ ОБРАБОТКИ processPageTitle() { if (document.title.includes(this.settings.myName)) { document.title = document.title.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), this.settings.username || this.settings.myName ); } } processRichContent() { document.querySelectorAll('.messageText, .content, .postbody, .userContent').forEach(element => { if (element.textContent.includes(this.settings.myName) && !element.isContentEditable) { element.innerHTML = element.innerHTML.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), this.settings.username || this.settings.myName ); } }); } processMetaTags() { document.querySelectorAll('meta[name="description"], meta[property="og:title"]').forEach(meta => { if (meta.content.includes(this.settings.myName)) { meta.content = meta.content.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), this.settings.username || this.settings.myName ); } }); } processNavigation() { document.querySelectorAll('.breadcrumb, .navTabs, .pagination').forEach(nav => { if (nav.textContent.includes(this.settings.myName)) { nav.innerHTML = nav.innerHTML.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), this.settings.username || this.settings.myName ); } }); } processTooltips() { document.querySelectorAll('[title], [data-tip], [data-original-title]').forEach(el => { const title = el.getAttribute('title') || el.getAttribute('data-tip') || el.getAttribute('data-original-title'); if (title && title.includes(this.settings.myName)) { const newTitle = title.replace( new RegExp(this.escapeRegExp(this.settings.myName), 'g'), this.settings.username || this.settings.myName ); el.setAttribute('title', newTitle); if (el.getAttribute('data-tip')) el.setAttribute('data-tip', newTitle); if (el.getAttribute('data-original-title')) el.setAttribute('data-original-title', newTitle); } }); } // ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ safeApplyStyles(element, css) { try { // Очищаем старые стили перед применением новых element.style.cssText = ''; // Применяем новые стили построчно const styles = css.split(';'); styles.forEach(style => { const [property, value] = style.split(':').map(s => s.trim()); if (property && value) { element.style[property] = value; } }); } catch (e) { MegaLogger.error('Ошибка применения стилей:', e); } } escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } } // ==================== СИСТЕМА ИНТЕРФЕЙСА ==================== class MegaUIManager { constructor() { this.editor = null; this.currentTheme = MEGA_CONFIG.THEMES.DARK; } createMegaMenuButton() { const menuContainers = [ 'ul[data-toggle-class="menuVisible"]', '.navTabs', '.navigation', 'nav ul', '.menu' ]; let menuContainer = null; for (const selector of menuContainers) { menuContainer = document.querySelector(selector); if (menuContainer) break; } if (!menuContainer) { setTimeout(() => this.createMegaMenuButton(), 1000); return; } if (document.getElementById('mega-uniq-btn')) return; const menuItem = document.createElement('li'); menuItem.innerHTML = ` <a href="#" id="mega-uniq-btn" style="color: #fff; text-shadow: 0px 0px 5px #e3609a; font-weight: bold;"> MEGA UNIQ LTV v${MEGA_CONFIG.VERSION} </a> `; menuContainer.appendChild(menuItem); menuItem.querySelector('a').addEventListener('click', (e) => { e.preventDefault(); this.showMegaEditor(); }); MegaLogger.success('Мега-кнопка создана'); } showMegaEditor() { if (this.editor) { this.editor.remove(); this.editor = null; } const settings = MegaStorage.getSettings(); this.currentTheme = MEGA_CONFIG.THEMES[settings.theme] || MEGA_CONFIG.THEMES.DARK; this.editor = document.createElement('div'); this.editor.id = 'mega-uniq-editor'; this.editor.innerHTML = this.getMegaEditorHTML(settings); this.applyMegaEditorStyles(); document.body.appendChild(this.editor); this.bindMegaEditorEvents(); MegaLogger.log('редактор открыт'); } getMegaEditorHTML(settings) { const themeOptions = Object.entries(MEGA_CONFIG.THEMES).map(([key, theme]) => `<option value="${key}" ${settings.theme === key ? 'selected' : ''}>${theme.name}</option>` ).join(''); const iconOptions = Object.entries(MEGA_CONFIG.ICONS).map(([key, icon]) => `<option value="${icon}" ${settings.avatarIcon === icon ? 'selected' : ''}>${icon} ${key}</option>` ).join(''); return ` <div class="mega-header"> <div class="mega-title"> MEGA UNIQ LTV v${MEGA_CONFIG.VERSION} </div> <div class="mega-controls"> <button class="mega-btn mega-btn-close" title="Закрыть">×</button> </div> </div> <div class="mega-tabs"> <button class="mega-tab active" data-tab="basic">⚙️ Основные</button> <button class="mega-tab" data-tab="theme">🎨 Тема</button> <button class="mega-tab" data-tab="stats">📊 Статистика</button> </div> <div class="mega-content"> <!-- ВКЛАДКА ОСНОВНЫЕ --> <div class="mega-tab-content active" data-tab="basic"> <div class="mega-section"> <label>👤 Ваш ID - (Адрес профиля):</label> <input type="text" id="mega-myId" value="${settings.myId}" placeholder="tokyo / 7883978 / "> </div> <div class="mega-section"> <label>👤 Ваш ник:</label> <input type="text" id="mega-myName" value="${settings.myName}" placeholder="Введите ваш текущий ник"> </div> <div class="mega-section"> <label>🆕 Новый ник:</label> <input type="text" id="mega-username" value="${settings.username}" placeholder="Введите новый ник"> </div> <div class="mega-section"> <label>🎭 Лычка:</label> <input type="text" id="mega-banner" value="${settings.banner}" placeholder="Текст лычки"> </div> <div class="mega-section"> <label>🎨 CSS для ника:</label> <textarea id="mega-username-css" placeholder="CSS стили для ника">${settings.usernameCss}</textarea> </div> <div class="mega-section"> <label>🎨 CSS для лычки:</label> <textarea id="mega-banner-css" placeholder="CSS стили для лычки">${settings.bannerCss}</textarea> </div> <div class="mega-section"> <label> <input type="checkbox" id="mega-autoDetect" ${settings.autoDetect ? 'checked' : ''}> 🔍 Авто-определение </label> </div> </div> <!-- ВКЛАДКА ТЕМА --> <div class="mega-tab-content" data-tab="theme"> <div class="mega-section"> <label>🎭 Тема оформления:</label> <select id="mega-theme">${themeOptions}</select> </div> </div> <!-- ВКЛАДКА СТАТИСТИКА --> <div class="mega-tab-content" data-tab="stats"> <div class="mega-stats"> <div class="mega-stat-item"> <span class="mega-stat-label">Версия:</span> <span class="mega-stat-value">v${MEGA_CONFIG.VERSION}</span> </div> <div class="mega-stat-item"> <span class="mega-stat-label">Использований:</span> <span class="mega-stat-value">${settings.usageCount}</span> </div> <div class="mega-stat-item"> <span class="mega-stat-label">Последнее обновление:</span> <span class="mega-stat-value">${settings.lastUpdate ? new Date(settings.lastUpdate).toLocaleString() : 'Никогда'}</span> </div> <div class="mega-stat-item"> <span class="mega-stat-label">Размер хранилища:</span> <span class="mega-stat-value">${MegaStorage.getStorageInfo().size} байт</span> </div> </div> <div class="mega-section"> <button class="mega-btn mega-btn-danger" id="mega-clear-cache">🗑️ Очистить кэш</button> <button class="mega-btn mega-btn-export" id="mega-export">📤 Экспорт</button> <button class="mega-btn mega-btn-import" id="mega-import">📥 Импорт</button> </div> </div> </div> <div class="mega-footer"> <button class="mega-btn mega-btn-primary" id="mega-apply">🚀 Применить</button> <button class="mega-btn mega-btn-secondary" id="mega-save">💾 Сохранить</button> <button class="mega-btn mega-btn-secondary" id="mega-reset">🔄 Сбросить</button> </div> `; } applyMegaEditorStyles() { const theme = this.currentTheme; const styles = ` #mega-uniq-editor { position: fixed; top: 50%; left: 18%; transform: translate(-50%, -50%); width: 90vw; max-width: 600px; max-height: 80vh; background: ${theme.card}; border: 2px solid ${theme.border}; border-radius: 12px; z-index: 10000; color: ${theme.text}; font-family: 'Segoe UI', system-ui, sans-serif; box-shadow: 0 10px 30px rgba(0,0,0,0.3); display: flex; flex-direction: column; } .mega-header { background: ${theme.accent}; color: white; padding: 15px 20px; border-radius: 10px 10px 0 0; display: flex; justify-content: space-between; align-items: center; } .mega-title { font-size: 18px; font-weight: bold; } .mega-controls .mega-btn-close { background: transparent; border: none; color: white; font-size: 24px; cursor: pointer; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; } .mega-tabs { display: flex; background: ${theme.bg}; border-bottom: 1px solid ${theme.border}; } .mega-tab { flex: 1; padding: 12px; background: transparent; border: none; color: ${theme.text}; cursor: pointer; transition: all 0.3s ease; } .mega-tab:hover { background: ${theme.accent}20; } .mega-tab.active { background: ${theme.accent}; color: white; } .mega-content { flex: 1; overflow-y: auto; padding: 20px; } .mega-tab-content { display: none; } .mega-tab-content.active { display: block; } .mega-section { margin-bottom: 15px; } .mega-section label { display: block; margin-bottom: 5px; font-weight: 600; color: ${theme.text}; } .mega-section input, .mega-section textarea, .mega-section select { width: 100%; padding: 10px; border: 1px solid ${theme.border}; border-radius: 6px; background: ${theme.bg}; color: ${theme.text}; font-size: 14px; box-sizing: border-box; } .mega-section textarea { min-height: 80px; resize: vertical; } .mega-stats { background: ${theme.bg}; padding: 15px; border-radius: 6px; margin-bottom: 15px; } .mega-stat-item { display: flex; justify-content: space-between; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid ${theme.border}; } .mega-stat-item:last-child { margin-bottom: 0; border-bottom: none; } .mega-footer { padding: 15px 20px; background: ${theme.bg}; border-top: 1px solid ${theme.border}; border-radius: 0 0 10px 10px; display: flex; gap: 10px; justify-content: flex-end; } .mega-btn { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; transition: all 0.3s ease; font-size: 14px; } .mega-btn-primary { background: ${theme.accent}; color: white; } .mega-btn-primary:hover { background: ${theme.accent}dd; } .mega-btn-secondary { background: ${theme.secondary}; color: white; } .mega-btn-secondary:hover { background: ${theme.secondary}dd; } .mega-btn-danger { background: #ff4757; color: white; } .mega-btn-danger:hover { background: #ff3742; } .mega-btn-export { background: #1f8456; color: white; } .mega-btn-import { background: #3742fa; color: white; } #mega-uniq-editor::backdrop { background: rgba(0,0,0,0.5); } @media (max-width: 768px) { #mega-uniq-editor { width: 95vw; max-height: 90vh; } .mega-footer { flex-direction: column; } .mega-btn { width: 100%; } } `; const styleElement = document.createElement('style'); styleElement.textContent = styles; this.editor.appendChild(styleElement); } bindMegaEditorEvents() { // Закрытие редактора this.editor.querySelector('.mega-btn-close').addEventListener('click', () => { this.editor.remove(); this.editor = null; }); // Переключение вкладок this.editor.querySelectorAll('.mega-tab').forEach(tab => { tab.addEventListener('click', () => { this.editor.querySelectorAll('.mega-tab').forEach(t => t.classList.remove('active')); this.editor.querySelectorAll('.mega-tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); const tabName = tab.getAttribute('data-tab'); this.editor.querySelector(`.mega-tab-content[data-tab="${tabName}"]`).classList.add('active'); }); }); // Кнопки действий this.editor.querySelector('#mega-apply').addEventListener('click', () => this.applySettings()); this.editor.querySelector('#mega-save').addEventListener('click', () => this.saveSettings()); this.editor.querySelector('#mega-reset').addEventListener('click', () => this.resetSettings()); this.editor.querySelector('#mega-clear-cache').addEventListener('click', () => this.clearCache()); this.editor.querySelector('#mega-export').addEventListener('click', () => this.exportSettings()); this.editor.querySelector('#mega-import').addEventListener('click', () => this.importSettings()); // Закрытие по клику вне редактора this.editor.addEventListener('click', (e) => { if (e.target === this.editor) { this.editor.remove(); this.editor = null; } }); // Закрытие по Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.editor) { this.editor.remove(); this.editor = null; } }); } applySettings() { try { const settings = this.getCurrentSettings(); MegaLogger.log('Попытка применения настроек:', settings); if (MegaStorage.saveSettings(settings)) { // Пересоздаем процессор с новыми настройками const processor = new MegaDOMProcessor(settings); const success = processor.applyMegaUniq(); if (success) { this.showNotification('✅ Настройки применены и сохранены!', 'success'); // Обновляем текущие настройки системы if (window.megaSystem) { window.megaSystem.settings = settings; window.megaSystem.processor = processor; } } else { this.showNotification('⚠️ Настройки сохранены, но применение не удалось', 'warning'); } } else { this.showNotification('❌ Ошибка сохранения настроек!', 'error'); } } catch (e) { MegaLogger.error('Критическая ошибка в applySettings:', e); this.showNotification('💥 Критическая ошибка применения настроек', 'error'); } } saveSettings() { const settings = this.getCurrentSettings(); if (MegaStorage.saveSettings(settings)) { this.showNotification('💾 Настройки сохранены!', 'success'); } else { this.showNotification('❌ Ошибка сохранения настроек', 'error'); } } resetSettings() { if (confirm('Вы уверены, что хотите сбросить все настройки?')) { const defaultSettings = MegaStorage.getDefaultSettings(); if (MegaStorage.saveSettings(defaultSettings)) { this.showNotification('🔄 Настройки сброшены!', 'success'); this.editor.remove(); this.editor = null; this.showMegaEditor(); } } } clearCache() { if (confirm('Вы уверены, что хотите очистить кэш?')) { const cleared = MegaStorage.clearCache(); this.showNotification(`🗑️ Очищено ${cleared} элементов кэша`, 'success'); } } exportSettings() { const settings = MegaStorage.exportSettings(); const blob = new Blob([settings], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `mega-uniq-settings-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); this.showNotification('📤 Настройки экспортированы!', 'success'); } importSettings() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { if (MegaStorage.importSettings(event.target.result)) { this.showNotification('📥 Настройки импортированы!', 'success'); this.editor.remove(); this.editor = null; this.showMegaEditor(); } else { this.showNotification('❌ Ошибка импорта настроек', 'error'); } }; reader.readAsText(file); } }; input.click(); } getCurrentSettings() { try { const editor = this.editor; if (!editor) { MegaLogger.error('Редактор не найден'); return MegaStorage.getSettings(); } const settings = { myId: this.getElementValue('mega-myId'), myName: this.getElementValue('mega-myName'), username: this.getElementValue('mega-username'), banner: this.getElementValue('mega-banner'), usernameCss: this.getElementValue('mega-username-css'), bannerCss: this.getElementValue('mega-banner-css'), avatarIcon: this.getElementValue('mega-avatarIcon'), theme: this.getElementValue('mega-theme'), autoDetect: this.getElementChecked('mega-autoDetect'), lastUpdate: new Date().toISOString(), usageCount: parseInt(localStorage.getItem('megaUniq-usageCount') || '0') + 1 }; MegaLogger.log('Текущие настройки из формы:', settings); return settings; } catch (e) { MegaLogger.error('Ошибка получения текущих настроек:', e); return MegaStorage.getSettings(); } } // вспомогательные методы getElementValue(id) { const element = document.getElementById(id); return element ? element.value : ''; } getElementChecked(id) { const element = document.getElementById(id); return element ? element.checked : false; } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; border-radius: 6px; color: white; font-weight: bold; z-index: 10001; transition: all 0.3s ease; ${type === 'success' ? 'background: #4caf50;' : type === 'error' ? 'background: #ff4757;' : 'background: #3498db;'} `; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }, 2000); } } // ==================== СИСТЕМА АВТОМАТИЧЕСКОГО ОБНАРУЖЕНИЯ ==================== class MegaAutoDetector { static detectUserInfo() { try { // Проверяем наличие элемента с именем пользователя const usernameElements = [ document.querySelector('#NavigationAccountUsername span'), document.querySelector('h1.username span'), document.querySelector('.accountUsername span'), document.querySelector('[itemprop="name"] span'), document.querySelector('.username') ].filter(el => el); if (usernameElements.length === 0) { MegaLogger.warn('Элементы имени пользователя не найдены'); return null; } const usernameElement = usernameElements[0]; const username = usernameElement.textContent.trim(); // Ищем ID пользователя из различных источников let userId = this.extractUserId(); if (!userId) { MegaLogger.warn('ID пользователя не найден'); return null; } MegaLogger.success(`Авто-определение: ${username} (ID: ${userId})`); return { username, userId }; } catch (error) { MegaLogger.error('Ошибка авто-определения:', error); return null; } } static extractUserId() { // 1. Из URL const urlMatch = window.location.href.match(/\/members\/(\d+)/); if (urlMatch) return urlMatch[1]; // 2. Из data-атрибутов const dataUserId = document.querySelector('[data-user-id]'); if (dataUserId) return dataUserId.getAttribute('data-user-id'); // 3. Из ссылок профиля const profileLink = document.querySelector('a[href*="/members/"]'); if (profileLink) { const hrefMatch = profileLink.href.match(/\/members\/(\d+)/); if (hrefMatch) return hrefMatch[1]; } // 4. Из мета-тегов const metaUserId = document.querySelector('meta[property="profile:username"]'); if (metaUserId) { const contentMatch = metaUserId.content.match(/\d+/); if (contentMatch) return contentMatch[0]; } return null; } } // ==================== ГЛАВНАЯ СИСТЕМА ==================== class MegaUniqSystem { constructor() { this.settings = null; this.processor = null; this.uiManager = new MegaUIManager(); this.observer = null; this.isInitialized = false; this.retryCount = 0; } async initialize() { try { MegaLogger.log(`🚀 Инициализация MEGA UNIQ LTV v${MEGA_CONFIG.VERSION}`); // Загружаем настройки this.settings = MegaStorage.getSettings(); MegaLogger.log('Настройки загружены:', this.settings); // Авто-определение если включено if (this.settings.autoDetect && (!this.settings.myId || !this.settings.myName)) { const userInfo = MegaAutoDetector.detectUserInfo(); if (userInfo) { this.settings.myId = userInfo.userId; this.settings.myName = userInfo.username; MegaStorage.saveSettings(this.settings); MegaLogger.success('Авто-определение завершено'); } } // Создаем процессор this.processor = new MegaDOMProcessor(this.settings); // Инициализируем интерфейс this.initializeUI(); // Запускаем обработку this.startProcessing(); // Настраиваем наблюдение this.setupObserver(); this.isInitialized = true; MegaLogger.success('MEGA UNIQ LTV успешно инициализирован'); } catch (error) { MegaLogger.error('Ошибка инициализации:', error); this.retryCount++; if (this.retryCount <= MEGA_CONFIG.MAX_RETRIES) { setTimeout(() => this.initialize(), 2000); } } } initializeUI() { // Создаем кнопку в меню this.uiManager.createMegaMenuButton(); // Добавляем глобальные обработчики document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'u') { e.preventDefault(); this.uiManager.showMegaEditor(); } }); MegaLogger.log('Интерфейс инициализирован'); } startProcessing() { // Первоначальная обработка setTimeout(() => { this.processor.applyMegaUniq(); }, MEGA_CONFIG.UPDATE_INTERVAL); // Периодическая обработка для динамического контента setInterval(() => { if (this.processor.lastProcessed < Date.now() - 2000) { this.processor.applyMegaUniq(); } }, 2000); } setupObserver() { // Наблюдатель за изменениями DOM this.observer = new MutationObserver((mutations) => { let shouldProcess = false; mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // Element node if (this.containsUserContent(node)) { shouldProcess = true; } } }); } }); if (shouldProcess) { clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(() => { this.processor.applyMegaUniq(); }, MEGA_CONFIG.OBSERVER_DEBOUNCE); } }); this.observer.observe(document.body, { childList: true, subtree: true }); MegaLogger.log('Наблюдатель DOM активирован'); } containsUserContent(node) { const userSelectors = [ '.message', '.userBanner', '.username', '.accountUsername', '[class*="user"]', '[class*="member"]', '[href*="/members/"]' ]; return userSelectors.some(selector => { return node.matches && node.matches(selector) || node.querySelector && node.querySelector(selector); }); } destroy() { if (this.observer) { this.observer.disconnect(); } MegaLogger.log('MEGA UNIQ LTV остановлен'); } } // ==================== ЗАПУСК СИСТЕМЫ ==================== let megaSystem = null; function initializeMegaSystem() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { megaSystem = new MegaUniqSystem(); megaSystem.initialize(); }); } else { megaSystem = new MegaUniqSystem(); megaSystem.initialize(); } } // Запускаем систему initializeMegaSystem(); // Экспортируем глобально для отладки window.MegaUniqSystem = { instance: megaSystem, storage: MegaStorage, logger: MegaLogger, config: MEGA_CONFIG }; MegaLogger.log(`MEGA UNIQ LTV v${MEGA_CONFIG.VERSION} загружен и готов к работе!`); })();