Fortnite Image Replacer

Manual image replacement for Fortnite.gg with enhanced error handling

// ==UserScript==
// @name         Fortnite Image Replacer
// @version      1.11
// @description  Manual image replacement for Fortnite.gg with enhanced error handling
// @match        https://fortnite.gg/*
// @grant        none
// @namespace https://greasyfork.org/users/789838
// ==/UserScript==

(function() {
    'use strict'; // Использование строгого режима JavaScript

    // Прозрачный пиксель в формате base64, используется как fallback
    const TRANSPARENT_PIXEL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';

    // Функция для проверки доступности изображения по URL
    function checkImage(url, callback) {
        const img = new Image();
        img.onload = () => callback(true); // Изображение загружено успешно
        img.onerror = () => callback(false); // Ошибка загрузки изображения
        img.src = url + '?t=' + Date.now(); // Добавление timestamp для избежания кеширования
    }

    // Функция для захвата кадра из видео
    function captureVideoFrame(videoUrl, callback) {
        const video = document.createElement('video');
        video.crossOrigin = 'anonymous'; // Разрешение кросс-доменных запросов
        let attempts = 0; // Счетчик попыток захвата кадра

        // Функция для очистки ресурсов
        const cleanup = () => {
            video.removeEventListener('loadeddata', onLoaded);
            video.removeEventListener('seeked', onSeeked);
            video.removeEventListener('error', onError);
            video.remove();
        };

        // Обработчик события загрузки метаданных видео
        const onLoaded = () => {
            if (video.duration === Infinity || video.duration < 2) {
                video.currentTime = Math.min(0.01, video.duration || 0.01);
                return;
            }
            video.currentTime = 2; // Перемотка на 2 секунду для захвата кадра
        };

        // Обработчик события завершения перемотки видео
        const onSeeked = () => {
            try {
                if (video.readyState < 2) return;

                // Создание canvas для рендеринга кадра видео
                const canvas = document.createElement('canvas');
                canvas.width = video.videoWidth || 800;
                canvas.height = video.videoHeight || 450;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                callback(canvas.toDataURL()); // Возврат кадра в формате data URL
                cleanup();
            } catch (e) {
                handleVideoError(e);
            }
        };

        // Обработчик ошибок при работе с видео
        const handleVideoError = (e) => {
            if (attempts++ < 2) {
                video.currentTime = 0.01; // Повторная попытка с другим временем
                return;
            }
            console.error('Video capture failed:', e);
            callback(null); // Возврат null при неудаче
            cleanup();
        };

        const onError = (e) => {
            handleVideoError(e);
        };

        // Подписка на события видео
        video.addEventListener('loadeddata', onLoaded);
        video.addEventListener('seeked', onSeeked);
        video.addEventListener('error', onError);
        video.src = videoUrl;
    }

    // Основная функция замены изображений
    function replaceImages() {
        // Поиск всех изображений с классами или атрибутами, содержащими путь к изображениям предметов
        const images = document.querySelectorAll('.img, img[src*="/img/items/"]');

        images.forEach((image) => {
            const originalSrc = image.src || image.getAttribute('data-src');
            if (!originalSrc) return;

            // Обработка изображений из раздела survey
            const surveyMatch = originalSrc.match(/https:\/\/fortnite\.gg\/img\/items-survey\/(\d+)\.jpg(\?.+)?/);
            if (surveyMatch) {
                // Замена на изображение большего размера
                image.src = `https://fortnite.gg/img/items-survey/big/${surveyMatch[1]}.jpg${surveyMatch[2] || ''}`;
                return;
            }

            // Обработка иконок предметов
            const iconMatch = originalSrc.match(/\/img\/items\/(\d+)\/icon\.(png|jpg)(\?.+)?/);
            if (iconMatch) {
                const itemId = iconMatch[1];
                const featuredUrl = `https://fortnite.gg/img/items/${itemId}/featured.png${iconMatch[3] || ''}`;

                // Проверка существования featured-изображения
                checkImage(featuredUrl, (exists) => {
                    if (!exists) {
                        // Если featured-изображения нет, пытаемся получить кадр из видео
                        const videoUrl = `https://fnggcdn.com/items/${itemId}/video.mp4?t=${Date.now()}`;
                        captureVideoFrame(videoUrl, (frameUrl) => {
                            image.src = frameUrl || TRANSPARENT_PIXEL; // Fallback на прозрачный пиксель
                            image.style.display = 'block';
                            if (!frameUrl) image.style.opacity = '0'; // Скрытие при неудаче
                        });
                        return;
                    }

                    // Использование featured-изображения, если оно доступно
                    image.onerror = null;
                    image.src = featuredUrl;
                });
            }
        });
    }

    // Функция добавления панели управления на страницу
    function addControlPanel() {
        // Стили для панели управления
        const panelStyle = [
            'position: fixed',
            'top: 20px',
            'right: 20px',
            'background: rgba(0,0,0,0.8)',
            'padding: 15px',
            'border-radius: 8px',
            'z-index: 9999',
            'box-shadow: 0 2px 10px rgba(0,0,0,0.5)'
        ].join(';');

        // Стили для кнопок
        const buttonStyle = [
            'display: block',
            'width: 100%',
            'margin: 5px 0',
            'padding: 10px',
            'background: #5865F2',
            'color: white',
            'border: none',
            'border-radius: 4px',
            'cursor: pointer',
            'font-family: inherit',
            'transition: background 0.2s'
        ].join(';');

        // Создание элементов панели управления
        const panel = document.createElement('div');
        panel.style.cssText = panelStyle;

        // Кнопка замены изображений
        const replaceBtn = document.createElement('button');
        replaceBtn.textContent = '🖼️ Replace Images';
        replaceBtn.style.cssText = buttonStyle;
        replaceBtn.onclick = replaceImages;
        replaceBtn.onmouseover = () => replaceBtn.style.background = '#4752C4';
        replaceBtn.onmouseout = () => replaceBtn.style.background = '#5865F2';

        // Кнопка очистки кеша
        const clearBtn = document.createElement('button');
        clearBtn.textContent = '🧹 Clear Cache';
        clearBtn.style.cssText = buttonStyle;
        clearBtn.onclick = () => {
            if (confirm('Clear all cached data and reload?')) {
                localStorage.clear();
                sessionStorage.clear();
                window.location.reload();
            }
        };
        clearBtn.onmouseover = () => clearBtn.style.background = '#DA3636';
        clearBtn.onmouseout = () => clearBtn.style.background = '#5865F2';

        // Добавление кнопок на панель и панели на страницу
        panel.appendChild(replaceBtn);
        document.body.appendChild(panel);
    }

    // Добавление панели управления после полной загрузки страницы
    window.addEventListener('load', addControlPanel);
})();