您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically load multiple pages of battle log, transfers, clan log, and warehouse log
当前为
// ==UserScript== // @name HeroesWM Logs Loader // @namespace http://tampermonkey.net/ // @version 2.1 // @description Automatically load multiple pages of battle log, transfers, clan log, and warehouse log // @author Any Key Fake Leader // @license MIT // @match https://www.heroeswm.ru/pl_warlog.php* // @match https://www.heroeswm.ru/pl_transfers.php* // @match https://www.heroeswm.ru/clan_log.php* // @match https://www.heroeswm.ru/sklad_log.php* // @match https://mirror.heroeswm.ru/pl_warlog.php* // @match https://mirror.heroeswm.ru/pl_transfers.php* // @match https://mirror.heroeswm.ru/clan_log.php* // @match https://mirror.heroeswm.ru/sklad_log.php* // @match https://my.lordswm.com/pl_warlog.php* // @match https://my.lordswm.com/pl_transfers.php* // @match https://my.lordswm.com/clan_log.php* // @match https://my.lordswm.com/sklad_log.php* // @match https://www.lordswm.com/pl_warlog.php* // @match https://www.lordswm.com/pl_transfers.php* // @match https://www.lordswm.com/clan_log.php* // @match https://www.lordswm.com/sklad_log.php* // @grant GM_setClipboard // ==/UserScript== (function() { 'use strict'; // Словарь категорий боёв по подкатегориям const BATTLE_CATEGORIES = { 'Гильдии': { '0': 'Охотников', '40': 'Тактиков', '66': 'Воров (мобы)', '26': 'Воров (игроки)', '61': 'Рейнджеров', '95': 'Стражей', '127': 'Лидеров', '135': 'Опасные бандиты', '145': 'КСЗС', '110': 'Искателей' }, 'Наёмники': { '8': 'Набеги', '5': 'Захватчики', '12': 'Армии', '7': 'Монстры', '28': 'Заговорщики', '29': 'Разбойники', '10': 'Отряды' }, 'Ивенты': { '94': 'Портал (парный и старый одиночный)', '96': 'Пиратская блокада', '99': 'Поиск сокровищ', '115': 'Охота на пиратов (парная)', '119': 'Защита деревень', '120': 'Подземные пещеры', '123': 'Пиратские рейды', '133': 'Рисковая авантюра', '138': 'Экспедиция', '139': 'Гильдия Лидеров', '140': 'Сезон охоты', '142': 'Новый исход орды', '143': 'Контрабандисты (старое поле)', '144': 'Единство', '146': 'Распутье тайн', '147': 'Новогоднее дело', '148': 'Одиночный портал', '117': 'Поиск существ (портал)', '150': 'Контрабандисты (новое поле)', '151': 'Счёт наёмника', '152': 'Гробница' }, 'Сурвилурги': { '80': 'Защиты', '81': 'Захваты', '88': 'Перехваты' }, 'Турниры': { '14': 'Малый турнир', '134': 'Парный турнир', '113': 'Великое состязание', '137': 'Гильдии Лидеров', '141': 'На выживание', '68': 'Я не знаю что это за бои' }, 'Групповые бои': { '1': 'КСЗС (на 4)', '24': 'КСЗС (на 6)', '37': 'Командные', '21': 'Я не знаю что это за бои' }, 'Другое': { '104': 'Налоги', '89': 'КБО ПвП', '67': 'Нарушители границы', '126': 'Группа разбойников', '111': 'Армия холода', '44': 'Ночные кошмары' }, 'Неизвестное': { 'unknown': 'Неизвестные типы боёв' } }; // Хранилище для всех загруженных боёв let allBattlesData = []; // Определение типа страницы function getPageType() { const path = window.location.pathname; if (path.includes('pl_warlog.php')) return 'warlog'; if (path.includes('pl_transfers.php')) return 'transfers'; if (path.includes('clan_log.php')) return 'clan_log'; if (path.includes('sklad_log.php')) return 'sklad_log'; return 'unknown'; } // Получение ID из URL (универсальная функция) function getEntityId() { const url = new URL(window.location.href); return url.searchParams.get('id'); } // Получение номера текущей страницы function getCurrentPage() { const url = new URL(window.location.href); const pageParam = url.searchParams.get('page'); return pageParam ? parseInt(pageParam) : 0; } // Декодирование windows-1251 контента function decodeWindows1251(buffer) { const decoder = new TextDecoder('windows-1251'); return decoder.decode(buffer); } // Извлечение записей протокола с учетом HTML комментариев function extractLogLines(html) { const lines = []; // Обновленный regex учитывает опциональные HTML комментарии перед const regex = /(<!--\d+-->)? .*?<BR>/gi; const matches = html.match(regex); if (matches) { lines.push(...matches); } return lines; } // Построение URL для загрузки страницы с сохранением всех параметров function buildPageUrl(pageType, entityId, pageNum) { // Получаем текущий URL const currentUrl = new URL(window.location.href); // Создаем новый URL на основе текущего const newUrl = new URL(currentUrl); // Обновляем только параметр page newUrl.searchParams.set('page', pageNum.toString()); // Убеждаемся что id установлен правильно (на случай если его нет в текущем URL) if (entityId) { newUrl.searchParams.set('id', entityId); } console.log(`Построен URL для страницы ${pageNum}:`, newUrl.toString()); return newUrl.toString(); } // Получение названия типа протокола для отображения function getLogTypeName(pageType) { switch (pageType) { case 'warlog': return 'протокола боёв'; case 'transfers': return 'протокола передач'; case 'clan_log': return 'протокола клана'; case 'sklad_log': return 'протокола склада'; default: return 'протокола'; } } // Обновленная функция создания интерфейса function createStatusDisplay() { const container = document.querySelector('.global_container_block_header'); if (!container) return; const pageCount = localStorage.getItem('hwm_page_count') || '25'; const pageType = getPageType(); const logTypeName = getLogTypeName(pageType); const statusContainer = document.createElement('div'); statusContainer.style.cssText = 'margin: 10px 0; text-align: center; background: #f0f0f0; padding: 8px; border-radius: 4px; display: flex; align-items: center; justify-content: center; gap: 10px;'; const textContainer = document.createElement('div'); textContainer.style.cssText = 'display: flex; align-items: center;'; const beforeText = document.createElement('span'); beforeText.textContent = 'Скрипт отображает '; beforeText.style.cssText = 'font-weight: bold; color: #333; margin-right: 5px;'; const input = document.createElement('input'); input.type = 'number'; input.id = 'pageCount'; input.min = '1'; input.max = '100'; input.value = pageCount; input.style.cssText = 'width: 60px; padding: 2px; text-align: center; margin-left: 5px; margin-right: 5px;'; input.onchange = function() { localStorage.setItem('hwm_page_count', this.value); }; const afterText = document.createElement('span'); afterText.textContent = ' страниц'; afterText.style.cssText = 'font-weight: bold; color: #333; margin-left: 5px;'; const copyButton = document.createElement('button'); copyButton.textContent = `Скопировать HTML код ${logTypeName}`; copyButton.style.cssText = 'padding: 4px 12px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 12px;'; copyButton.onclick = function() { copyLogHTML(copyButton, logTypeName); }; // Кнопка "Запуск" для перезагрузки страниц const reloadButton = document.createElement('button'); reloadButton.textContent = 'Запуск'; reloadButton.style.cssText = 'padding: 4px 12px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; margin-left: 10px;'; reloadButton.onclick = function() { // Очищаем данные и запускаем загрузку заново allBattlesData = []; // Очищаем контейнер перед загрузкой (решает проблему №3) const logContainer = getLogContainer(); if (logContainer) { logContainer.innerHTML = ''; } loadPagesAutomatically(true); }; textContainer.appendChild(beforeText); textContainer.appendChild(input); textContainer.appendChild(afterText); textContainer.appendChild(reloadButton); statusContainer.appendChild(textContainer); statusContainer.appendChild(copyButton); container.parentNode.insertBefore(statusContainer, container.nextSibling); } // Функция для сворачивания/разворачивания всех категорий function toggleAllCategories(expand) { const toggles = document.querySelectorAll('.category-toggle'); const contents = document.querySelectorAll('.subcategory-content'); const newCollapseStates = {}; // Устанавливаем состояние для всех категорий toggles.forEach(toggle => { if (expand) { toggle.classList.add('expanded'); } else { toggle.classList.remove('expanded'); } }); contents.forEach(content => { const subcategoryBlock = content.closest('[data-category]'); const subcategory = subcategoryBlock ? subcategoryBlock.dataset.category : null; if (expand) { content.classList.add('expanded'); if (subcategory) { newCollapseStates[subcategory] = true; } } else { content.classList.remove('expanded'); if (subcategory) { newCollapseStates[subcategory] = false; } } }); // Сохраняем состояния localStorage.setItem('hwm_battle_filter_collapse', JSON.stringify(newCollapseStates)); } // Функция создания интерфейса фильтрации боёв с сворачиваемыми подкатегориями function createBattleFilterUI() { if (getPageType() !== 'warlog') return; // Получаем сохранённые настройки const savedPreferences = JSON.parse(localStorage.getItem('hwm_battle_filter') || '{}'); // Получаем сохранённые состояния свёрнутости подкатегорий const savedCollapseStates = JSON.parse(localStorage.getItem('hwm_battle_filter_collapse') || '{}'); // Создаём контейнер const filterContainer = document.createElement('div'); filterContainer.id = 'battle-filter-container'; filterContainer.style.cssText = 'margin: 10px 0; background: #f0f0f0; padding: 12px; border-radius: 4px;'; // Добавляем CSS для треугольников свёрнутости const style = document.createElement('style'); style.textContent = ` .category-toggle { display: inline-block; width: 0; height: 0; margin-right: 8px; border-left: 6px solid #666; border-top: 6px solid transparent; border-bottom: 6px solid transparent; transition: transform 0.3s; cursor: pointer; } .category-toggle.expanded { transform: rotate(90deg); } .subcategory-header { display: flex; align-items: center; cursor: pointer; user-select: none; } .subcategory-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } .subcategory-content.expanded { max-height: 1000px; /* Достаточно большое значение для содержимого */ } `; document.head.appendChild(style); // Заголовок и кнопки управления const headerContainer = document.createElement('div'); headerContainer.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;'; const header = document.createElement('div'); header.textContent = 'Фильтр боёв:'; header.style.cssText = 'font-weight: bold; font-size: 14px;'; const buttonsContainer = document.createElement('div'); // Кнопки управления фильтром const expandAllBtn = document.createElement('button'); expandAllBtn.textContent = 'Развернуть все'; expandAllBtn.style.cssText = 'margin-right: 8px; padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;'; expandAllBtn.onclick = () => toggleAllCategories(true); const collapseAllBtn = document.createElement('button'); collapseAllBtn.textContent = 'Свернуть все'; collapseAllBtn.style.cssText = 'margin-right: 8px; padding: 4px 8px; background: #607D8B; color: white; border: none; border-radius: 3px; cursor: pointer;'; collapseAllBtn.onclick = () => toggleAllCategories(false); const selectAllBtn = document.createElement('button'); selectAllBtn.textContent = 'Выбрать все'; selectAllBtn.style.cssText = 'margin-right: 8px; padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer;'; selectAllBtn.onclick = selectAllCategories; const deselectAllBtn = document.createElement('button'); deselectAllBtn.textContent = 'Отменить все'; deselectAllBtn.style.cssText = 'padding: 4px 8px; background: #F44336; color: white; border: none; border-radius: 3px; cursor: pointer;'; deselectAllBtn.onclick = deselectAllCategories; buttonsContainer.appendChild(expandAllBtn); buttonsContainer.appendChild(collapseAllBtn); buttonsContainer.appendChild(selectAllBtn); buttonsContainer.appendChild(deselectAllBtn); headerContainer.appendChild(header); headerContainer.appendChild(buttonsContainer); filterContainer.appendChild(headerContainer); // Контейнер для подкатегорий const categoriesContainer = document.createElement('div'); categoriesContainer.style.cssText = 'display: flex; flex-wrap: wrap; gap: 15px;'; filterContainer.appendChild(categoriesContainer); // Создаём блоки для каждой подкатегории for (const [subcategory, categories] of Object.entries(BATTLE_CATEGORIES)) { const subcategoryBlock = document.createElement('div'); subcategoryBlock.style.cssText = 'background: white; padding: 10px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); flex: 1; min-width: 200px;'; subcategoryBlock.dataset.category = subcategory; // Заголовок подкатегории с треугольником const subcategoryHeader = document.createElement('div'); subcategoryHeader.className = 'subcategory-header'; // Добавляем треугольник для индикации свёрнутости const toggle = document.createElement('span'); toggle.className = 'category-toggle'; if (savedCollapseStates[subcategory]) { toggle.classList.add('expanded'); } // Создаем чекбокс для выбора/снятия всех подкатегорий const toggleAllCheckbox = document.createElement('input'); toggleAllCheckbox.type = 'checkbox'; toggleAllCheckbox.className = 'toggle-all-subcategories'; toggleAllCheckbox.dataset.category = subcategory; toggleAllCheckbox.style.marginRight = '5px'; // Загружаем сохраненное состояние const savedToggleAllStates = JSON.parse(localStorage.getItem('hwm_battle_filter_toggle_all') || '{}'); toggleAllCheckbox.checked = savedToggleAllStates[subcategory] !== false; // Добавляем обработчик события toggleAllCheckbox.addEventListener('change', function() { const isChecked = this.checked; const subcategory = this.dataset.category; const preferences = JSON.parse(localStorage.getItem('hwm_battle_filter') || '{}'); // Получаем все чекбоксы этой категории const checkboxes = document.querySelectorAll(`div[data-category="${subcategory}"] .subcategory-content input[type="checkbox"]`); // Обновляем все чекбоксы checkboxes.forEach(checkbox => { checkbox.checked = isChecked; preferences[checkbox.dataset.category] = isChecked; }); // Сохраняем состояние выбора всех const toggleAllStates = JSON.parse(localStorage.getItem('hwm_battle_filter_toggle_all') || '{}'); toggleAllStates[subcategory] = isChecked; localStorage.setItem('hwm_battle_filter_toggle_all', JSON.stringify(toggleAllStates)); // Сохраняем состояния фильтров localStorage.setItem('hwm_battle_filter', JSON.stringify(preferences)); // Применяем фильтры filterBattles(); }); const titleText = document.createElement('span'); titleText.textContent = subcategory; titleText.style.cssText = 'font-weight: bold; color: #444;'; subcategoryHeader.appendChild(toggle); subcategoryHeader.appendChild(toggleAllCheckbox); subcategoryHeader.appendChild(titleText); subcategoryHeader.style.cssText = 'margin-bottom: 8px; border-bottom: 1px solid #eee; padding-bottom: 5px;'; // Контейнер для содержимого подкатегории const contentContainer = document.createElement('div'); contentContainer.className = 'subcategory-content'; if (savedCollapseStates[subcategory]) { contentContainer.classList.add('expanded'); } // Обработчик клика для сворачивания/разворачивания subcategoryHeader.addEventListener('click', function() { toggle.classList.toggle('expanded'); contentContainer.classList.toggle('expanded'); // Сохраняем состояние свёрнутости const newCollapseStates = JSON.parse(localStorage.getItem('hwm_battle_filter_collapse') || '{}'); newCollapseStates[subcategory] = contentContainer.classList.contains('expanded'); localStorage.setItem('hwm_battle_filter_collapse', JSON.stringify(newCollapseStates)); }); subcategoryBlock.appendChild(subcategoryHeader); subcategoryBlock.appendChild(contentContainer); // Чекбоксы для категорий for (const [categoryId, categoryName] of Object.entries(categories)) { const checkboxWrapper = document.createElement('label'); checkboxWrapper.style.cssText = 'display: flex; align-items: center; margin: 5px 0;'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = categoryId; checkbox.dataset.category = categoryId; // Для категории "Неизвестное" проверяем значение по ключу 'unknown' if (subcategory === 'Неизвестное') { checkbox.checked = savedPreferences['unknown'] !== false; } else { checkbox.checked = savedPreferences[categoryId] !== false; } checkbox.addEventListener('change', updateCategoryPreference); const label = document.createElement('span'); label.textContent = ' ' + categoryName; label.style.cssText = 'margin-left: 5px;'; checkboxWrapper.appendChild(checkbox); checkboxWrapper.appendChild(label); contentContainer.appendChild(checkboxWrapper); } categoriesContainer.appendChild(subcategoryBlock); } // Счетчик отображаемых боёв const counterElem = document.createElement('div'); counterElem.id = 'battleFilterCounter'; counterElem.style.cssText = 'margin-top: 12px; font-style: italic; text-align: center;'; counterElem.textContent = 'Загрузка...'; filterContainer.appendChild(counterElem); // Добавляем фильтр на страницу const container = document.querySelector('.global_container_block_header'); if (container) { container.parentNode.insertBefore(filterContainer, container.nextSibling); } } // Выбор всех категорий function selectAllCategories() { const preferences = {}; const checkboxes = document.querySelectorAll('#battle-filter-container input[type="checkbox"]'); checkboxes.forEach(cb => { cb.checked = true; preferences[cb.dataset.category] = true; }); localStorage.setItem('hwm_battle_filter', JSON.stringify(preferences)); filterBattles(); } // Отмена выбора всех категорий function deselectAllCategories() { const preferences = {}; const checkboxes = document.querySelectorAll('#battle-filter-container input[type="checkbox"]'); checkboxes.forEach(cb => { cb.checked = false; preferences[cb.dataset.category] = false; }); localStorage.setItem('hwm_battle_filter', JSON.stringify(preferences)); filterBattles(); } // Обновление предпочтения для категории function updateCategoryPreference() { const preferences = JSON.parse(localStorage.getItem('hwm_battle_filter') || '{}'); preferences[this.dataset.category] = this.checked; localStorage.setItem('hwm_battle_filter', JSON.stringify(preferences)); filterBattles(); } // Кешированная ссылка на контейнер лога let cachedLogContainer = null; // Улучшенная функция получения контейнера с протоколом function getLogContainer() { // Если у нас есть кешированный контейнер и он всё ещё в DOM if (cachedLogContainer && document.contains(cachedLogContainer)) { return cachedLogContainer; } // Ищем контейнер заново const allContainers = document.querySelectorAll('.global_a_hover'); // Перебираем контейнеры, ища тот, который содержит лог или был ранее использован for (const container of allContainers) { // Если контейнер содержит лог или просто имеет подходящего родителя if (container.innerHTML.includes(' ') || container.innerHTML === "") { cachedLogContainer = container; return container; } } // Если ничего не нашли, берём первый контейнер if (allContainers.length > 0) { cachedLogContainer = allContainers[0]; return cachedLogContainer; } return null; } // Обновленная функция автоматической загрузки async function loadPagesAutomatically(includeCurrentPage = false) { const pageCount = parseInt(localStorage.getItem('hwm_page_count') || '25'); const pageType = getPageType(); const currentPage = getCurrentPage(); const entityId = getEntityId(); if (!entityId) { console.error('Не удается определить ID из URL'); return; } console.log(`Загружаем ${pageType}, ID: ${entityId}, текущая страница: ${currentPage}`); const promises = []; // Начинаем с текущей страницы, если указан параметр includeCurrentPage const startIndex = includeCurrentPage ? 0 : 1; // Создаем промисы для загрузки каждой страницы for (let i = startIndex; i < pageCount; i++) { const pageNum = currentPage + i; const url = buildPageUrl(pageType, entityId, pageNum); const promise = fetch(url) .then(response => { if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.arrayBuffer(); }) .then(buffer => decodeWindows1251(buffer)) .then(html => extractLogLines(html)) .catch(error => { console.error(`Ошибка загрузки страницы ${pageNum}:`, error); return []; }); promises.push(promise); } try { // Ждем загрузки всех страниц const results = await Promise.all(promises); // Находим правильный контейнер с записями const allContainers = document.querySelectorAll('.global_a_hover'); const logContainer = getLogContainer(); if (!logContainer) { console.error('Не удается найти контейнер с записями'); return; } // Получаем существующие записи для проверки дубликатов const existingEntries = extractLogLines(logContainer.innerHTML); // Объединяем все новые записи const allNewEntries = results.flat(); if (allNewEntries.length > 0) { // Фильтруем дубликаты const uniqueNewEntries = filterNewEntries(existingEntries, allNewEntries); if (uniqueNewEntries.length > 0) { // Если это протокол боёв, сохраняем все записи для фильтрации if (pageType === 'warlog') { // Объединяем существующие и новые записи allBattlesData = [...existingEntries, ...uniqueNewEntries]; // Применяем фильтр к полному набору данных filterBattles(); console.log(`Добавлено ${uniqueNewEntries.length} новых уникальных записей`); console.log(`Всего записей загружено: ${allBattlesData.length}`); } else { // Для других типов протоколов, просто добавляем новые записи uniqueNewEntries.forEach(entry => { logContainer.insertAdjacentHTML('beforeend', entry + '\n'); }); console.log(`Добавлено ${uniqueNewEntries.length} новых уникальных записей`); console.log(`Всего записей загружено: ${allNewEntries.length}`); console.log(`Отфильтровано дубликатов: ${allNewEntries.length - uniqueNewEntries.length}`); } } else { console.log('Новых уникальных записей не найдено'); } } else { console.log('Не загружено записей с дополнительных страниц'); } } catch (error) { console.error('Ошибка при загрузке страниц:', error); } } // Обновленная функция копирования (переименована для универсальности) function copyLogHTML(button, logTypeName) { // Находим контейнер с протоколом const allContainers = document.querySelectorAll('.global_a_hover'); const logContainer = getLogContainer(); if (!logContainer) { alert(`Не удается найти контейнер с ${logTypeName}`); return; } // Получаем HTML и декодируем сущности const rawHtmlContent = logContainer.innerHTML; const htmlContent = replaceAmpEntities(rawHtmlContent); console.log(`Размер HTML контента ${logTypeName}:`, htmlContent.length); console.log('Первые 200 символов HTML:', htmlContent.substring(0, 200)); console.log('Замен & на &:', (rawHtmlContent.match(/&/g) || []).length); // Пробуем GM_setClipboard let copySuccess = false; let method = ''; try { GM_setClipboard(htmlContent); method = 'text'; copySuccess = true; console.log('GM_setClipboard (text) выполнен'); } catch (error) { console.error('Ошибка GM_setClipboard (text):', error); } if (!copySuccess) { try { GM_setClipboard(htmlContent, 'text'); method = 'text explicit'; copySuccess = true; console.log('GM_setClipboard (text explicit) выполнен'); } catch (error) { console.error('Ошибка GM_setClipboard (text explicit):', error); } } // Проверяем результат через небольшую задержку setTimeout(() => { checkClipboardContent(copySuccess, method, htmlContent, button, logTypeName); }, 100); } function checkClipboardContent(copySuccess, method, originalContent, button, logTypeName) { if (navigator.clipboard && navigator.clipboard.readText) { navigator.clipboard.readText().then(clipboardText => { console.log('Содержимое буфера обмена:', clipboardText.length, 'символов'); if (clipboardText.length > 0) { if (clipboardText === originalContent || clipboardText.length > 1000) { console.log('✅ HTML код корректно скопирован'); button.style.background = '#4CAF50'; button.textContent = 'HTML скопирован!'; } else { console.log('⚠️ В буфере другой контент'); showHTMLModal(originalContent, button, logTypeName); return; } } else { console.log('❌ Буфер обмена пустой'); showHTMLModal(originalContent, button, logTypeName); return; } setTimeout(() => { button.style.background = '#2196F3'; button.textContent = `Скопировать HTML код ${logTypeName}`; }, 3000); }).catch(error => { console.log('Не удалось прочитать буфер обмена:', error); if (copySuccess) { button.style.background = '#4CAF50'; button.textContent = 'HTML скопирован!'; setTimeout(() => { button.style.background = '#2196F3'; button.textContent = `Скопировать HTML код ${logTypeName}`; }, 3000); } else { showHTMLModal(originalContent, button, logTypeName); } }); } else { if (copySuccess) { console.log('Clipboard API недоступен, но GM_setClipboard выполнен'); button.style.background = '#4CAF50'; button.textContent = 'HTML скопирован!'; setTimeout(() => { button.style.background = '#2196F3'; button.textContent = `Скопировать HTML код ${logTypeName}`; }, 3000); } else { showHTMLModal(originalContent, button, logTypeName); } } } // Простая функция замены & на & function replaceAmpEntities(text) { return text.replace(/&/g, '&'); } // Проверка идентичности записей (точное совпадение) function areEntriesIdentical(entry1, entry2) { return entry1.trim() === entry2.trim(); } // Фильтрация новых записей, исключение дубликатов function filterNewEntries(existingEntries, newEntries) { const uniqueNewEntries = []; for (const newEntry of newEntries) { let isDuplicate = false; for (const existingEntry of existingEntries) { if (areEntriesIdentical(newEntry, existingEntry)) { isDuplicate = true; break; } } if (!isDuplicate) { // Также проверяем против уже добавленных новых записей let isAlreadyAdded = false; for (const addedEntry of uniqueNewEntries) { if (areEntriesIdentical(newEntry, addedEntry)) { isAlreadyAdded = true; break; } } if (!isAlreadyAdded) { uniqueNewEntries.push(newEntry); } } } return uniqueNewEntries; } // Полная функция модального окна function showHTMLModal(htmlContent, button, logTypeName) { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 10000; display: flex; align-items: center; justify-content: center; `; const content = document.createElement('div'); content.style.cssText = ` background: white; padding: 20px; border-radius: 8px; max-width: 90%; max-height: 90%; overflow: auto; `; const title = document.createElement('h3'); title.textContent = `HTML код ${logTypeName}`; title.style.cssText = 'margin-top: 0; color: #333;'; const instruction = document.createElement('p'); instruction.textContent = 'Выделите весь текст (Ctrl+A) и скопируйте (Ctrl+C):'; instruction.style.cssText = 'color: #666; margin-bottom: 10px;'; const textarea = document.createElement('textarea'); textarea.value = htmlContent; textarea.style.cssText = ` width: 700px; height: 400px; font-family: monospace; font-size: 12px; border: 1px solid #ccc; padding: 10px; resize: both; `; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'text-align: center; margin-top: 15px;'; const selectAllBtn = document.createElement('button'); selectAllBtn.textContent = 'Выделить всё'; selectAllBtn.style.cssText = 'margin-right: 10px; padding: 8px 16px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;'; selectAllBtn.onclick = () => { textarea.select(); textarea.setSelectionRange(0, textarea.value.length); }; const closeBtn = document.createElement('button'); closeBtn.textContent = 'Закрыть'; closeBtn.style.cssText = 'padding: 8px 16px; background: #666; color: white; border: none; border-radius: 4px; cursor: pointer;'; closeBtn.onclick = () => document.body.removeChild(modal); // Собираем модальное окно buttonContainer.appendChild(selectAllBtn); buttonContainer.appendChild(closeBtn); content.appendChild(title); content.appendChild(instruction); content.appendChild(textarea); content.appendChild(buttonContainer); modal.appendChild(content); document.body.appendChild(modal); // Автоматически выделяем весь текст setTimeout(() => { textarea.focus(); textarea.select(); }, 100); // Обновляем кнопку button.style.background = '#FFA500'; button.textContent = 'Окно открыто'; setTimeout(() => { button.style.background = '#2196F3'; button.textContent = `Скопировать HTML код ${logTypeName}`; }, 3000); } // Функция фильтрации боёв function filterBattles() { if (getPageType() !== 'warlog' || !allBattlesData.length) return; // Получаем предпочтения и проверяем, есть ли хотя бы одна активная категория const preferences = JSON.parse(localStorage.getItem('hwm_battle_filter') || '{}'); let hasActiveCategories = false; // Проверяем все категории во всех подкатегориях for (const subcategory of Object.values(BATTLE_CATEGORIES)) { for (const categoryId of Object.keys(subcategory)) { if (preferences[categoryId] !== false) { hasActiveCategories = true; break; } } if (hasActiveCategories) break; } // Результирующий HTML для отображения let filteredHTML = ''; let totalBattles = 0; let shownBattles = 0; // Если все фильтры отключены, показываем сообщение if (!hasActiveCategories) { const logContainer = getLogContainer(); if (logContainer) { logContainer.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">Все фильтры отключены. Выберите хотя бы одну категорию боёв.</div>'; // Обновляем счетчик const counterElem = document.getElementById('battleFilterCounter'); if (counterElem) { counterElem.textContent = `Показано 0 из ${allBattlesData.length} боёв`; } } return; } // Проходим по всем записям боёв for (const battleEntry of allBattlesData) { totalBattles++; // Извлекаем категорию боя const categoryMatch = battleEntry.match(/<!--(\d+)-->/); const category = categoryMatch ? categoryMatch[1] : 'unknown'; // Находим, к какой категории относится этот бой let foundInCategory = false; for (const [subcategoryName, subcategories] of Object.entries(BATTLE_CATEGORIES)) { if (subcategoryName !== 'Неизвестное' && subcategories[category]) { foundInCategory = true; break; } } // Определяем, нужно ли показывать бой let shouldShowBattle; if (foundInCategory) { shouldShowBattle = preferences[category] !== false; } else { shouldShowBattle = preferences['unknown'] !== false; } // Добавляем бой, если он проходит фильтр if (shouldShowBattle) { filteredHTML += battleEntry + '\n'; shownBattles++; } } // Обновляем отображение const logContainer = getLogContainer(); if (logContainer) { logContainer.innerHTML = filteredHTML || '<div style="padding: 20px; text-align: center; color: #666;">Нет боёв, соответствующих выбранным фильтрам.</div>'; // Обновляем счетчик const counterElem = document.getElementById('battleFilterCounter'); if (counterElem) { counterElem.textContent = `Показано ${shownBattles} из ${totalBattles} боёв`; } } } // Обновление отображения протокола боёв function updateBattleLogDisplay(battles) { // Находим контейнер с записями const allContainers = document.querySelectorAll('.global_a_hover'); const logContainer = getLogContainer(); if (!logContainer) return; // Обновляем HTML контейнера logContainer.innerHTML = battles.join('\n'); } // Initialize when page loads function init() { // Wait for page to fully load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { createStatusDisplay(); createBattleFilterUI(); // Добавляем создание фильтра loadPagesAutomatically(); }); } else { createStatusDisplay(); createBattleFilterUI(); // Добавляем создание фильтра loadPagesAutomatically(); } } init(); })();