Boosty Image URL Cleaner

Удаление параметров mw и mh из ссылок на изображения на Boosty, если не указано "&croped", позволяет загружать изображения-превью в оригинальном разрешении

// ==UserScript==
// @name         Boosty Image URL Cleaner
// @version      1.11
// @description  Удаление параметров mw и mh из ссылок на изображения на Boosty, если не указано "&croped",  позволяет загружать изображения-превью в оригинальном разрешении
// @match        *://*.boosty.to/*
// @grant        none
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function() {
    'use strict';
    // --- (Этот скрипт оптимизирован для эффективности) ---
    // --- (Использует MutationObserver для обработки только НОВЫХ img) ---
    // --- (Использует Map для кэширования уже обработанных URL) ---

    // --- КАТЕГОРИЯ: КЭШИРОВАНИЕ И СОСТОЯНИЕ ---
    // Этот раздел содержит переменные, используемые для оптимизации и отслеживания состояния.
    // - urlCleanCache: Map-объект для кэширования.
    //   - Ключ: Оригинальный URL изображения.
    //   - Значение: Уже очищенный URL.
    //   - Это предотвращает повторную обработку одного и того же URL (например,
    //     одинаковых аватарок в комментариях) и значительно ускоряет скрипт.
    const urlCleanCache = new Map();

    // --- КАТЕГОРИЯ: ОСНОВНАЯ ЛОГИКА ---
    // Функции, выполняющие основную работу скрипта.

    /**
     * Принимает URL, очищает его от параметров 'mw' и 'mh',
     * если в нем нет параметра "&croped".
     * Использует кэширование для быстрого ответа.
     *
     * @param {string} url - Оригинальный URL изображения.
     * @returns {string} - Очищенный URL.
     */
    function cleanURL(url) {
        // 1. Сначала проверяем, нет ли уже готового URL в кэше
        if (urlCleanCache.has(url)) {
            return urlCleanCache.get(url);
        }

        // 2. Если в кэше нет, проводим анализ и очистку
        let urlParts = url.split('?');
        
        // Если URL вообще не содержит параметров (нет '?'),
        // он уже "чистый". Кэшируем его как есть.
        if (urlParts.length < 2) {
            urlCleanCache.set(url, url); // Кэшируем "чистый" URL (он же = оригиналу)
            return url;
        }

        let baseUrl = urlParts[0];
        // Собираем обратно строку запроса, на случай если в ней был '?'
        let queryString = urlParts.slice(1).join('?'); 
        let searchParams = new URLSearchParams(queryString);

        // 3. Главное условие:
        // Если в строке запроса есть "&croped" (или "croped" как первый параметр),
        // мы НЕ трогаем 'mw' и 'mh', т.к. они нужны для кадрирования.
        // Мы проверяем именно `queryString`, а не `searchParams.has`
        // для точного соответствия оригинальной логике (поиск подстроки).
        if (!queryString.includes('&croped') && !queryString.startsWith('croped=')) {
            // Удаляем параметры, отвечающие за ограничение размера
            searchParams.delete('mw');
            searchParams.delete('mh');
        }

        // 4. Собираем URL обратно
        const newQueryString = searchParams.toString();
        const finalUrl = newQueryString ? `${baseUrl}?${newQueryString}` : baseUrl;

        // 5. Сохраняем результат в кэш
        urlCleanCache.set(url, finalUrl);
        return finalUrl;
    }

    // --- КАТЕГОРИЯ: ОБРАБОТКА DOM ---
    // Функции, которые взаимодействуют с элементами на странице.

    /**
     * Обрабатывает один DOM-узел <img>.
     * Проверяет, является ли он целевым изображением Boosty,
     * и применяет к нему `cleanURL` для `src` и `srcset`.
     *
     * @param {Node} node - DOM-узел для проверки.
     */
    function processImage(node) {
        // Убеждаемся, что это:
        // 1. Элемент (nodeType === 1)
        // 2. Тег <img> с `src`, содержащим "boosty.to/image"
        // 3. Элемент, который мы еще не обрабатывали (нет `data-cleaned="true"`)
        // Селектор :not([data-cleaned="true"]) - ключевой для производительности.
        if (node.nodeType === 1 &&
            node.matches('img[src*="boosty.to/image"]:not([data-cleaned="true"])')) {

            // Помечаем сразу, чтобы избежать повторной обработки
            // этим же скриптом или другим колбэком MutationObserver.
            node.dataset.cleaned = 'true';

            // Чистим основной `src`
            // (cleanURL быстро возьмет из кэша, если URL уже встречался)
            if (node.src) {
                node.src = cleanURL(node.src);
            }

            // Чистим `srcset`, который используется для адаптивных изображений
            if (node.srcset) {
                // `srcset` - это строка вида "url1 100w, url2 200w, ..."
                node.srcset = node.srcset.split(',') // -> ["url1 100w", " url2 200w"]
                    .map(srcEntry => {
                        const parts = srcEntry.trim().split(' '); // -> ["url1", "100w"] или ["url2"]
                        const url = parts[0];
                        const size = parts[1] || ''; // '100w' или пустая строка
                        
                        // Чистим только URL, а дескриптор размера (size) оставляем
                        return `${cleanURL(url)} ${size}`;
                    })
                    .join(', '); // Собираем обратно в строку
            }
        }
    }

    // --- КАТЕГОРИЯ: НАБЛЮДЕНИЕ ЗА DOM И ИНИЦИАЛИЗАЦИЯ ---
    // Код, который запускает скрипт и отслеживает будущие изменения.

    // 1. Создание Наблюдателя (MutationObserver)
    // Это современный и очень эффективный способ реагировать
    // на добавление новых элементов на страницу (например, при
    // бесконечной прокрутке или открытии комментариев).
    const observer = new MutationObserver((mutations) => {
        // Перебираем все зафиксированные мутации
        for (const mutation of mutations) {
            // Нас интересуют только *добавленные* узлы
            for (const node of mutation.addedNodes) {
                // А. Если добавлен сам <img>
                // (nodeType === 1 нужен, чтобы отсеять текст, комменты и т.д.)
                if (node.nodeType === 1) {
                    processImage(node);
                }

                // Б. Если добавлен контейнер (например, <div> с постом),
                // ищем <img> *внутри* него.
                // `node.querySelectorAll` существует только у элементов.
                if (node.querySelectorAll) {
                    node.querySelectorAll('img[src*="boosty.to/image"]:not([data-cleaned="true"])')
                        .forEach(processImage);
                }
            }
        }
    });

    // 2. Запуск наблюдателя
    // Мы "прикрепляем" его к document.body и говорим:
    // - childList: true -> следи за добавлением/удалением дочерних элементов
    // - subtree: true   -> следи за всеми потомками, а не только прямыми
    observer.observe(document.body, { childList: true, subtree: true });

    // 3. Первоначальная обработка
    // Наблюдатель `observer` сработает только на *новые* элементы.
    // Эта строка находит и обрабатывает все картинки, которые
    // уже были на странице в момент запуска скрипта.
    document.querySelectorAll('img[src*="boosty.to/image"]:not([data-cleaned="true"])')
        .forEach(processImage);
})();