// ==UserScript==
// @name LZT Local Uniq LTV
// @version 1.1.3
// @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 Toquio
// @license MIT
// @namespace lztlocaluniqLTV
// @match https://lolz.live/*
// @match https://zelenka.guru/*
// @match https://lolz.guru/*
// @match https://lzt.market/*
// @connect greasyfork.org/ru/scripts/550788-lzt-local-uniq-ltv
// @connect greasyfork.org/
// @grant GM_getResourceText
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// ====================КОНФИГУРАЦИЯ ====================
const MEGA_CONFIG = {
VERSION: '1.1',
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 = GM_getValue('megaUniq-settings');
if (settings) {
MegaLogger.log('Настройки загружены из GM:', settings);
return settings;
}
// Если нет настроек в GM, пробуем загрузить из localStorage (для обратной совместимости)
return this.getSettingsFromLocalStorage();
} catch (e) {
MegaLogger.error('Ошибка загрузки настроек:', e);
return this.getDefaultSettings();
}
}
static saveSettings(settings) {
try {
// Сохраняем в GM (кросс-доменно)
GM_setValue('megaUniq-settings', settings);
// Также сохраняем в localStorage для обратной совместимости
this.saveSettingsToLocalStorage(settings);
MegaLogger.success('Настройки сохранены в GM хранилище');
return true;
} catch (e) {
MegaLogger.error('Ошибка сохранения настроек:', e);
return false;
}
}
// Методы для обратной совместимости с localStorage
static getSettingsFromLocalStorage() {
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') || '',
svgIcon: localStorage.getItem('megaUniq-svg-icon') || '',
theme: localStorage.getItem('megaUniq-theme') || 'DARK',
autoDetect: localStorage.getItem('megaUniq-autoDetect') !== 'false',
lastUpdate: localStorage.getItem('megaUniq-lastUpdate') || '',
usageCount: parseInt(localStorage.getItem('megaUniq-usageCount') || '0')
};
// Если есть настройки в localStorage, переносим их в GM
if (settings.myId || settings.myName) {
this.saveSettings(settings);
MegaLogger.log('Настройки мигрированы из localStorage в GM');
}
return settings;
} catch (e) {
return this.getDefaultSettings();
}
}
static saveSettingsToLocalStorage(settings) {
try {
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-svg-icon', settings.svgIcon || '');
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', (settings.usageCount || 0).toString());
} catch (e) {
MegaLogger.error('Ошибка сохранения в localStorage:', e);
}
}
static getDefaultSettings() {
return {
svgIcon: '',
myId: '',
myName: '',
username: '',
banner: '',
usernameCss: '',
bannerCss: '',
theme: 'DARK',
autoDetect: true,
lastUpdate: '',
usageCount: 0
};
}
static clearCache() {
try {
// Очищаем GM хранилище
const values = GM_listValues();
values.forEach(key => {
if (key.includes('megaUniq')) {
GM_deleteValue(key);
}
});
// Очищаем localStorage
const keys = Object.keys(localStorage).filter(key => key.startsWith('megaUniq-'));
keys.forEach(key => localStorage.removeItem(key));
MegaLogger.log('Кэш очищен в GM и localStorage');
return keys.length;
} catch (e) {
MegaLogger.error('Ошибка очистки кэша:', e);
return 0;
}
}
static getStorageInfo() {
try {
const settings = GM_getValue('megaUniq-settings');
const size = settings ? JSON.stringify(settings).length : 0;
return { keys: 1, size: size };
} catch (e) {
return { keys: 0, size: 0 };
}
}
}
// ==================== СИСТЕМА ОБРАБОТКИ DOM ====================
class MegaDOMProcessor {
constructor(settings) {
this.settings = settings;
this.processedElements = new WeakSet();
this.retryCount = 0;
this.lastProcessed = 0;
}
// ПРОЦЕССИНГ:
applyMegaUniq() {
// УСИЛЕННАЯ ПРОВЕРКА: требуем только myId и myName, остальное опционально
if (!this.settings.myId || !this.settings.myName) {
MegaLogger.warn('Не заданы обязательные настройки: ID и текущий ник');
return false;
}
const startTime = Date.now();
MegaLogger.log(`Запуск мега-обработки для: ${this.settings.myName} (ID: ${this.settings.myId})`);
try {
// Очищаем предыдущие обработки
this.processedElements = new WeakSet();
// Последовательная обработка ВСЕХ элементов
const processors = [
() => this.processUserLinks(),
() => this.processBanners(),
() => this.processBadgeIcons(),
// () => 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(), 100);
}
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"]`,
`.marketSidebarUserHeadInformation .username span`, // Ник в боковой панели
`.marketItemView--sidebarUser--Username .username span`, // Ник в боковой панели (альтернативный)
`.notranslate.username span`, // Любые никнеймы с классом notranslate
`a.notranslate.username span` // Ссылки-никнеймы с notranslate
];
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.usernameIcon ?
`${this.settings.usernameIcon} ${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) {
if ((!this.settings.username || String(this.settings.username).trim()==='') &&
(!this.settings.usernameIcon || String(this.settings.usernameIcon).trim()==='') &&
(!this.settings.usernameCss || String(this.settings.usernameCss).trim()==='')) {
return;
}
const usernameText = this.settings.username || this.settings.myName;
const finalText = this.settings.usernameIcon ?
`${this.settings.usernameIcon} ${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 && String(this.settings.usernameCss).trim() !== '') {
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',
'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)) {
MegaLogger.log('Найдена МОЯ лычка:', banner);
this.processBannerElement(banner);
this.processedElements.add(banner);
}
});
} catch (e) {
MegaLogger.error(`Ошибка обработки баннеров ${selector}:`, e);
}
});
}
// Обработка SVG-иконки в бейдже аватара
processBadgeIcons() {
try {
const myId = String(this.settings.myId || '').trim();
const svg = String(this.settings.svgIcon || '').trim();
if (!myId || !svg) return;
const safeSvg = this.sanitizeSvg ? this.sanitizeSvg(svg) : svg;
document.querySelectorAll(`
.avatarUserBadge .customUniqIcon,
.avatarUserBadges .avatarUserBadge .customUniqIcon,
.messageUserInfo .avatarUserBadge .customUniqIcon
`).forEach(iconBox => {
const badge = iconBox.closest('.avatarUserBadge');
if (!badge) return;
// If already applied to different owner, skip
const appliedTo = iconBox.getAttribute('data-owner-id');
if (appliedTo && appliedTo !== myId) return;
// Determine ownership within a reasonable scope around the badge
const scope = badge.closest('.avatarHolder, .message, .comment, .profilePage, .memberCard, .messageUserInfo') || badge;
const isMine =
// Common: link to member profile contains ID
!!(scope.querySelector(`a[href*="/members/${myId}/"]`)) ||
// Some templates store numeric id in data-user
!!(scope.querySelector(`a.username[data-user="${myId}"], .username[data-user="${myId}"]`)) ||
// Avatar link sometimes carries a class marker like Av{ID}
!!(scope.querySelector(`a.avatar[class*="Av${myId}"]`));
if (!isMine) return;
// Create missing box if needed
let box = iconBox;
if (!box) box = badge.querySelector('.customUniqIcon');
if (!box) {
box = document.createElement('div');
box.className = 'customUniqIcon';
badge.prepend(box);
}
// Apply only to my badge
box.innerHTML = safeSvg;
box.dataset.megaApplied = '1';
box.setAttribute('data-owner-id', myId);
});
} catch (e) {
try { MegaLogger.error('Ошибка применения SVG-иконки:', e); } catch(_) {}
}
}
isMyBanner(banner) {
const myId = String(this.settings.myId || '').trim().toLowerCase();
const myName = String(this.settings.myName || '').trim();
if (!myId && !myName) {
return false;
}
// 1. Проверяем URL страницы - новый формат lolz.live/username/
const currentUrl = window.location.href.toLowerCase();
if (myId && currentUrl.includes(`/${myId}/`)) {
return true;
}
// 2. Для страниц профиля - ищем h1 с именем пользователя
if (currentUrl.match(/lolz\.live\/[^\/]+\/$/)) {
const profileHeader = document.querySelector('h1.username, .mainProfileHeader .username');
if (profileHeader) {
const profileName = profileHeader.textContent?.trim();
if (profileName === myName) {
return true;
}
}
// Дополнительная проверка для страницы профиля
const profileTabs = document.querySelector('.profileNavigation');
if (profileTabs) {
return true; // Если есть навигация профиля - это страница профиля
}
}
// 3. Для постов/сообщений - ищем контейнер и проверяем автора
const container = banner.closest('.message, .messageUserInfo, .memberListItem, .userItem');
if (container) {
// Ищем username элемент и проверяем data-user-id или текст
const usernameEl = container.querySelector('.username, a.username');
if (usernameEl) {
// Проверяем data-user-id
const userId = usernameEl.getAttribute('data-user-id');
if (userId && userId === myId) {
return true;
}
// Проверяем по тексту
const usernameText = usernameEl.textContent?.trim();
if (usernameText === myName) {
return true;
}
// Проверяем ссылку
const href = usernameEl.getAttribute('href');
if (href && href.includes(`/${myId}/`)) {
return true;
}
}
}
return false;
}
processBannerElement(banner) {
const bannerText = this.settings.banner || '';
const finalText = this.settings.bannerIcon ?
`${this.settings.bannerIcon} ${bannerText}` : bannerText;
MegaLogger.log('Обработка лычки:', banner, 'Текст:', finalText);
let textElement = banner.querySelector('strong') ||
banner.querySelector('span') ||
banner;
// Сохраняем оригинальные классы и структуру
const originalClasses = textElement.className;
const originalHTML = textElement.innerHTML;
const hasBanner = !!(this.settings.banner && String(this.settings.banner).trim() !== '');
const hasBannerIcon = !!(this.settings.bannerIcon && String(this.settings.bannerIcon).trim() !== '');
if (hasBanner || hasBannerIcon) {
if (!hasBanner && hasBannerIcon) {
const origText = (textElement.textContent || '').trim();
const icon = this.settings.bannerIcon ? (this.settings.bannerIcon + ' ') : '';
textElement.textContent = icon + origText;
} else {
textElement.textContent = finalText;
}
} else {
textElement.innerHTML = originalHTML;
}
textElement.className = originalClasses; // Восстанавливаем классы
if (this.settings.bannerCss && String(this.settings.bannerCss).trim() !== '') {
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);
}
}
// ОБРАБОТКА ИКОНОК
processIcons() {
if (!this.settings.avatarIcon && !this.settings.bannerIcon && !this.settings.usernameIcon) return;
document.querySelectorAll(`a[href*="/members/${this.settings.myId}/"]`).forEach(link => {
if (this.settings.usernameIcon && !link.textContent.includes(this.settings.usernameIcon)) {
link.innerHTML = this.settings.usernameIcon + ' ' + link.innerHTML;
}
});
}
// ОБРАБОТКА ТЕКСТОВЫХ УЗЛОВ
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)) {
// Используем новый ник если задан, иначе оставляем текущий
const replacementText = this.settings.username || this.settings.myName;
document.title = document.title.replace(
new RegExp(this.escapeRegExp(this.settings.myName), 'g'),
replacementText
);
}
}
processRichContent() {
document.querySelectorAll('.messageText, .content, .postbody, .userContent').forEach(element => {
if (element.textContent.includes(this.settings.myName) && !element.isContentEditable) {
// Используем новый ник если задан, иначе оставляем текущий
const replacementText = this.settings.username || this.settings.myName;
element.innerHTML = element.innerHTML.replace(
new RegExp(this.escapeRegExp(this.settings.myName), 'g'),
replacementText
);
}
});
}
processMetaTags() {
document.querySelectorAll('meta[name="description"], meta[property="og:title"]').forEach(meta => {
if (meta.content.includes(this.settings.myName)) {
// Используем новый ник если задан, иначе оставляем текущий
const replacementText = this.settings.username || this.settings.myName;
meta.content = meta.content.replace(
new RegExp(this.escapeRegExp(this.settings.myName), 'g'),
replacementText
);
}
});
}
processNavigation() {
document.querySelectorAll('.breadcrumb, .navTabs, .pagination').forEach(nav => {
if (nav.textContent.includes(this.settings.myName)) {
// Используем новый ник если задан, иначе оставляем текущий
const replacementText = this.settings.username || this.settings.myName;
nav.innerHTML = nav.innerHTML.replace(
new RegExp(this.escapeRegExp(this.settings.myName), 'g'),
replacementText
);
}
});
}
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 replacementText = this.settings.username || this.settings.myName;
const newTitle = title.replace(
new RegExp(this.escapeRegExp(this.settings.myName), 'g'),
replacementText
);
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 {
// Не красим элементы внутри форм/настроек/chosen
if (element.closest('.ctrlUnit, .xenForm, .chosen-container, .Lzt-PrettySelect, .group_hide_banner')) {
return;
}
// Очищаем старые стили перед применением новых
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">
<div class="SvgIcon" style="display: inline-block; vertical-align: middle; margin-right: 5px;">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M9.39504 19.3711L9.97949 20.6856C10.1532 21.0768 10.4368 21.4093 10.7957 21.6426C11.1547 21.8759 11.5736 22.0001 12.0017 22C12.4298 22.0001 12.8488 21.8759 13.2077 21.6426C13.5667 21.4093 13.8502 21.0768 14.0239 20.6856L14.6084 19.3711C14.8164 18.9047 15.1664 18.5159 15.6084 18.26C16.0532 18.0034 16.5677 17.8941 17.0784 17.9478L18.5084 18.1C18.934 18.145 19.3636 18.0656 19.7451 17.8713C20.1265 17.6771 20.4434 17.3763 20.6573 17.0056C20.8714 16.635 20.9735 16.2103 20.951 15.7829C20.9285 15.3555 20.7825 14.9438 20.5306 14.5978L19.6839 13.4344C19.3825 13.0171 19.2214 12.5148 19.2239 12C19.2238 11.4866 19.3864 10.9864 19.6884 10.5711L20.535 9.40778C20.7869 9.06175 20.933 8.65007 20.9554 8.22267C20.9779 7.79528 20.8759 7.37054 20.6617 7C20.4478 6.62923 20.1309 6.32849 19.7495 6.13423C19.3681 5.93997 18.9385 5.86053 18.5128 5.90556L17.0828 6.05778C16.5722 6.11141 16.0576 6.00212 15.6128 5.74556C15.1699 5.48825 14.8199 5.09736 14.6128 4.62889L14.0239 3.31444C13.8502 2.92317 13.5667 2.59072 13.2077 2.3574C12.8488 2.12408 12.4298 1.99993 12.0017 2C11.5736 1.99993 11.1547 2.12408 10.7957 2.3574C10.4368 2.59072 10.1532 2.92317 9.97949 3.31444L9.39504 4.62889C9.18797 5.09736 8.83792 5.48825 8.39504 5.74556C7.95026 6.00212 7.43571 6.11141 6.92504 6.05778L5.4906 5.90556C5.06493 5.86053 4.63534 5.93997 4.25391 6.13423C3.87249 6.32849 3.55561 6.62923 3.34171 7C3.12753 7.37054 3.02549 7.79528 3.04798 8.22267C3.07046 8.65007 3.2165 9.06175 3.46838 9.40778L4.31504 10.5711C4.61698 10.9864 4.77958 11.4866 4.77949 12C4.77958 12.5134 4.61698 13.0137 4.31504 13.4289L3.46838 14.5922C3.2165 14.9382 3.07046 15.3499 3.04798 15.7773C3.02549 16.2047 3.12753 16.6295 3.34171 17C3.55582 17.3706 3.87274 17.6712 4.25411 17.8654C4.63548 18.0596 5.06496 18.1392 5.4906 18.0944L6.9206 17.9422C7.43127 17.8886 7.94581 17.9979 8.3906 18.2544C8.83513 18.511 9.18681 18.902 9.39504 19.3711Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M11.9999 15C13.6568 15 14.9999 13.6569 14.9999 12C14.9999 10.3431 13.6568 9 11.9999 9C10.3431 9 8.99992 10.3431 8.99992 12C8.99992 13.6569 10.3431 15 11.9999 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</div>
Основные
</button>
<button class="mega-tab" data-tab="theme">
<img src="data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath opacity='0.12' d='M2 12C2 17.5228 6.47715 22 12 22C13.6569 22 15 20.6569 15 19V18.5C15 18.0356 15 17.8034 15.0257 17.6084C15.2029 16.2622 16.2622 15.2029 17.6084 15.0257C17.8034 15 18.0356 15 18.5 15H19C20.6569 15 22 13.6569 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12Z' fill='%23505050'/%3E%3Cpath d='M2 12C2 17.5228 6.47715 22 12 22C13.6569 22 15 20.6569 15 19V18.5C15 18.0356 15 17.8034 15.0257 17.6084C15.2029 16.2622 16.2622 15.2029 17.6084 15.0257C17.8034 15 18.0356 15 18.5 15H19C20.6569 15 22 13.6569 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12Z' stroke='%23505050' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M7 13C7.55228 13 8 12.5523 8 12C8 11.4477 7.55228 11 7 11C6.44772 11 6 11.4477 6 12C6 12.5523 6.44772 13 7 13Z' stroke='%23505050' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M16 9C16.5523 9 17 8.55228 17 8C17 7.44772 16.5523 7 16 7C15.4477 7 15 7.44772 15 8C15 8.55228 15.4477 9 16 9Z' stroke='%23505050' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M10 8C10.5523 8 11 7.55228 11 7C11 6.44772 10.5523 6 10 6C9.44772 6 9 6.44772 9 7C9 7.55228 9.44772 8 10 8Z' stroke='%23505050' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"
alt="Тема"
style="width: 16px; height: 16px; margin-right: 5px; vertical-align: middle;">
Тема
</button>
<button class="mega-tab" data-tab="stats">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 5px; vertical-align: middle;">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Статистика
</button>
</div>
<div class="mega-content">
<!-- ВКЛАДКА ОСНОВНЫЕ -->
<div class="mega-tab-content active" data-tab="basic">
<div class="mega-section">
<label>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512" style="width: 16px; height: 16px; margin-right: 5px; vertical-align: middle;"><path fill="currentColor" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z"/></svg>
Ваш ID - (Адрес профиля):
<label>
<input type="text" id="mega-myId" value="${settings.myId}" placeholder="tokyo / 7883978 / ">
Рекомендуется вписывать цифровой ID
</div>
<div class="mega-section">
<label>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 5px; vertical-align: middle;">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
Ваш ник:
</label>
<input type="text" id="mega-myName" value="${settings.myName}" placeholder="Введите ваш текущий ник">
</div>
<div class="mega-section">
<label>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 5px; vertical-align: middle;">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="16"></line>
<line x1="8" y1="12" x2="16" y2="12"></line>
</svg>
Новый ник:
</label>
<input type="text" id="mega-username" value="${settings.username}"
placeholder="Оставьте пустым, если не нужно менять ник">
</div>
<div class="mega-section">
<label>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 5px; vertical-align: middle;">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
Лычка:
</label>
<input type="text" id="mega-banner" value="${settings.banner}" placeholder="Текст лычки">
</div>
<div class="mega-section">
<label>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width: 16px; height: 16px; margin-right: 5px; vertical-align: middle;">
<path fill="currentColor" d="M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"/>
</svg>
CSS для ника:
</label>
<textarea id="mega-username-css" placeholder="CSS стили для ника">${settings.usernameCss}</textarea>
</div>
<div class="mega-section">
<label>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width: 16px; height: 16px; margin-right: 5px; vertical-align: middle;">
<path fill="currentColor" d="M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"/>
</svg>
CSS для лычки:
</label>
<textarea id="mega-banner-css" placeholder="CSS стили для лычки">${settings.bannerCss}</textarea>
<div class="mega-section">
<label>
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23505050' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E<path d='M12 2L15 8L22 9L17 14L18 21L12 18L6 21L7 14L2 9L9 8L12 2Z'/%3E</svg>"
alt="SVG"
style="width: 16px; height: 16px; margin-right: 5px; vertical-align: middle;">
SVG иконка:
</label>
<textarea id="mega-svg-icon" value="${settings.svgIcon || ``}" placeholder="Вставьте SVG код иконки">${settings.svgIcon || ''}</textarea>
Для того чтобы ваша иконка корректно отображалась, нужно ввести постоянную ссылку на профиль (7883978)
</div>
<div class="mega-section">
<label>
<input type="checkbox" id="mega-autoDetect" ${settings.autoDetect ? 'checked' : ''}><div style="display: inline-block; margin-right: 5px; vertical-align: middle; position: relative;"><i class="QuickSearchIcon far fa-search" style="color: currentColor; font-size: 14px;"></i></div>
Авто-определение
</label>
</div>
</div>
</div>
<!-- ВКЛАДКА ТЕМА -->
<div class="mega-tab-content" data-tab="theme">
<div class="mega-section">
<label>
<i class="fal fa-photo-video" aria-hidden="true" style="margin-right: 5px;"></i>
Тема оформления:
</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"><i class="iconKey far fa-trash-alt"></i> Очистить кэш</button>
<button class="mega-btn mega-btn-export" id="mega-export"><i class="fa fa-cloud-upload" aria-hidden="true" style="margin-right: 5px;"></i>Экспорт</button>
<button class="mega-btn mega-btn-import" id="mega-import"><i class="fa fa-cloud-download" aria-hidden="true" style="margin-right: 5px;"></i>Импорт</button>
</div>
</div>
</div>
<div class="mega-footer">
<button class="mega-btn mega-btn-primary" id="mega-apply"> <i class="fa fa-check" aria-hidden="true" style="margin-right: 5px;"></i>Применить</button>
<button class="mega-btn mega-btn-secondary" id="mega-save"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 5px; vertical-align: middle;"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>Сохранить</button>
<button class="mega-btn mega-btn-secondary" id="mega-reset"><span class="svgIconLink" style="display: inline-block; margin-right: 5px; vertical-align: middle;"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M22 10C22 10 19.995 7.26822 18.3662 5.63824C16.7373 4.00827 14.4864 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.1031 21 19.5649 18.2543 20.6482 14.5M22 10V4M22 10H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg></span>Сбросить</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;
}
});
}
async applySettings() {
try {
const settings = this.getCurrentSettings();
MegaLogger.log('Попытка применения настроек:', settings);
if (MegaStorage.saveSettings(settings)) {
// Пересоздаем процессор с новыми настройками
const processor = new MegaDOMProcessor(settings);
const success = processor.applyMegaUniq();
try {
if (processor.ensureMyId) {
const __beforeId2 = String(settings.myId || '');
const __resolved2 = await processor.ensureMyId();
if (__resolved2 && __resolved2 !== __beforeId2) {
settings.myId = __resolved2;
MegaStorage && MegaStorage.saveSettings && MegaStorage.saveSettings(settings);
}
}
} catch(e) {}
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'),
svgIcon: this.getElementValue('mega-svg-icon'),
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);
// Ensure numeric myId even if user typed slug/nick
if (this.processor.ensureMyId) {
try {
const __beforeId = String(this.settings.myId || '');
const __resolved = await this.processor.ensureMyId();
if (__resolved && __resolved !== __beforeId) {
this.settings.myId = __resolved;
MegaStorage && MegaStorage.saveSettings && MegaStorage.saveSettings(this.settings);
}
} catch(e) {}
}
// Инициализируем интерфейс
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(), 1000);
}
}
}
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() - 1000) {
this.processor.applyMegaUniq();
}
}, 1000);
}
setupObserver() {
// Наблюдатель за изменениями DOM
this.observer = new MutationObserver((mutations) => {
let shouldProcess = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node;
if (element.querySelector && (
element.querySelector(`a[href*="/members/${this.settings.myId}/"]`) ||
element.querySelector('.userBanner') ||
element.querySelector('.avatar') || element.querySelector('.avatarUserBadge') || element.querySelector('.customUniqIcon')
|| element.querySelector('.customUniqIcon')
)) {
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} загружен и готов к работе!`);
})();
(function(){
if (typeof MegaDOMProcessor === 'function') {
// Resolve numeric myId from slug/name present on page
MegaDOMProcessor.prototype.ensureMyId = async function() {
try {
let id = String(this.settings.myId || '').trim();
if (/^\d+$/.test(id)) return id;
const slug = String(this.settings.mySlug || this.settings.myLink || '').trim().replace(/\/+$/, '');
const myName = String(this.settings.myName || '').trim();
const avatars = document.querySelectorAll('a.avatar[class*="Av"]');
for (const a of avatars) {
const m = a.className && a.className.match(/Av(\d+)/);
if (m) {
if (slug && a.getAttribute('href') && a.getAttribute('href').includes(`${slug}/`)) {
this.settings.myId = m[1]; return m[1];
}
if (myName) {
const scope = a.closest('.avatarHolder, .message, .comment, .profilePage, .memberCard') || document;
const nameEl = scope.querySelector('a.username.poster, a.username, .username, [data-user-name]');
const txt = (nameEl?.getAttribute?.('data-user-name') || nameEl?.textContent || '').trim();
if (txt === myName) { this.settings.myId = m[1]; return m[1]; }
}
}
}
const dataUserEl = document.querySelector('a.username.poster[data-user], a.username[data-user]');
if (dataUserEl) {
const du = String(dataUserEl.getAttribute('data-user') || '');
const m = du.match(/^(\d+)/);
if (m) { this.settings.myId = m[1]; return m[1]; }
}
} catch(e) {}
return null;
};
MegaDOMProcessor.prototype.processCommentBadges = function() {
try {
const myId = String(this.settings.myId || '').trim();
const svgRaw = String(this.settings.svgIcon || '').trim();
if (!myId || !svgRaw) return;
const svg = this.sanitizeSvg ? this.sanitizeSvg(svgRaw) : svgRaw;
document.querySelectorAll('.comment .avatarHolder').forEach(holder => {
const myAvatar = holder.querySelector(`a.avatar[class*="Av${myId}"]`);
if (!myAvatar) return;
const box = holder.querySelector('.avatarUserBadge .customUniqIcon');
if (!box) return;
if (box.dataset.megaApplied === '1' && box.getAttribute('data-owner-id') === myId) return;
box.innerHTML = svg;
box.dataset.megaApplied = '1';
box.setAttribute('data-owner-id', myId);
});
} catch(e) { try{ console.error('LTV processCommentBadges error:', e); }catch(_){ } }
};
const __orig = MegaDOMProcessor.prototype.applyMegaUniq;
if (typeof __orig === 'function') {
MegaDOMProcessor.prototype.applyMegaUniq = function() {
const r = __orig.apply(this, arguments);
try { this.processCommentBadges && this.processCommentBadges(); } catch(e){}
return r;
};
}
}
setInterval(() => {
try {
if (window.megaSystem && window.megaSystem.processor && window.megaSystem.processor.processCommentBadges) {
window.megaSystem.processor.processCommentBadges();
}
} catch(e){}
}, 1000);
})();