// ==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();
})();