// ==UserScript==
// @name LZT Local Uniq LTV
// @version 1.0.0
// @description Some useful utilities for Lolzteam
// @description:ru Полезные улучшения для Lolzteam
// @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 greasyfork.org
// @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: '#4ecdc4',
border: '#444'
}
},
ICONS: {
DEFAULT: ''
}
};
// ==================== СИСТЕМА ЛОГГИРОВАНИЯ ====================
class MegaLogger {
static log(...args) {
if (MEGA_CONFIG.DEBUG) {
console.log(`%c🔹 LZT MEGA v${MEGA_CONFIG.VERSION}:`, 'color: #4ecdc4; 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: #ff6b6b; font-weight: bold;">
MEGA UNIQ 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 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: 50%;
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: #2ed573;
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 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 успешно инициализирован');
} 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 остановлен');
}
}
// ==================== ЗАПУСК СИСТЕМЫ ====================
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 v${MEGA_CONFIG.VERSION} загружен и готов к работе!`);
})();