Modrinth Version Filter

Ever spent hours clicking through mod pages like a lost villager in a maze?

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name        Modrinth Version Filter
// @match       https://modrinth.com/collection/*/mods
// @grant       none
// @version     1.0.1
// @author      https://github.com/Staninna
// @description Ever spent hours clicking through mod pages like a lost villager in a maze? 
//             Fear not! This script is like having a coordinated army of parrots checking 
//             version numbers for you. No more manual hunting - we've automated that 
//             tedious task faster than you can say "Creeper? Aww man!" 🦜✨
// @esversion   11
// @license     MIT
// @namespace https://greasyfork.org/users/1406392
// ==/UserScript==

(function() {
    'use strict';

    let isFilterSetup = false;
    let processingMods = false;

    function setupModVersionFilter() {
        if (isFilterSetup) return;

        const nav = document.querySelector('.navigation-card');
        if (!nav) return;

        if (document.getElementById('mcVersionFilter')) return;

        isFilterSetup = true;
        initializeFilter(nav);
    }

    function initializeFilter(nav) {
        const filterDiv = document.createElement('div');
        filterDiv.style.display = 'flex';
        filterDiv.style.alignItems = 'center';
        filterDiv.style.marginLeft = 'auto';
        filterDiv.style.marginRight = '16px';
        filterDiv.innerHTML = `
            <input type="text" id="mcVersionFilter" placeholder="MC Version (e.g. 1.20.1)"
                   style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; margin-right: 8px; width: 160px;">
            <button id="filterButton"
                    style="padding: 4px 12px; background: var(--color-brand); color: white; border-radius: 4px; font-size: 14px;">
                Filter
            </button>
        `;
        nav.appendChild(filterDiv);

        if (!processingMods) {
            processModCards();
        }
    }

    function cleanVersions(versions) {
        return versions
            .filter(v => /^\d+\.\d+(\.\d+)?$/.test(v))
            .sort((a, b) => {
                const partsA = a.split('.').map(Number);
                const partsB = b.split('.').map(Number);
                for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
                    const partA = partsA[i] || 0;
                    const partB = partsB[i] || 0;
                    if (partA !== partB) return partB - partA;
                }
                return 0;
            });
    }

    async function processModCards() {
        if (processingMods) return;
        processingMods = true;

        const modCards = document.querySelectorAll('.project-card');
        const modVersions = new Map();

        // Create array of promises for all mod version fetches
        const fetchPromises = Array.from(modCards).map(async card => {
            if (card.querySelector('.mod-versions-display')) return null;

            const link = card.querySelector('a[href^="/mod/"]')?.getAttribute('href');
            if (!link) return null;

            const modId = link.split('/mod/')[1];

            try {
                const response = await fetch(`https://api.modrinth.com/v2/project/${modId}/version`);
                const versions = await response.json();
                return { card, versions };
            } catch (error) {
                console.error(`Error fetching versions for mod ${modId}:`, error);
                return null;
            }
        });

        try {
            // Wait for all fetches to complete
            const results = await Promise.all(fetchPromises);

            // Process results and update UI
            results.forEach(result => {
                if (!result) return;

                const { card, versions } = result;
                const gameVersions = cleanVersions([...new Set(versions.flatMap(v => v.game_versions))]);
                modVersions.set(card, gameVersions);

                // Add version display
                const statsDiv = card.querySelector('.stats');
                if (statsDiv) {
                    const versionDiv = document.createElement('div');
                    versionDiv.classList.add('mod-versions-display');
                    versionDiv.style.fontSize = '0.875rem';
                    versionDiv.style.color = 'var(--color-text-secondary)';
                    versionDiv.style.marginTop = '8px';
                    versionDiv.textContent = `MC: ${gameVersions.slice(0, 3).join(', ')}${gameVersions.length > 3 ? '...' : ''}`;
                    statsDiv.appendChild(versionDiv);
                }
            });

            setupFilterHandlers(modVersions);
        } catch (error) {
            console.error('Error processing mod versions:', error);
        } finally {
            processingMods = false;
        }
    }

    function setupFilterHandlers(modVersions) {
        const filterButton = document.getElementById('filterButton');
        const filterInput = document.getElementById('mcVersionFilter');

        const newFilterButton = filterButton.cloneNode(true);
        const newFilterInput = filterInput.cloneNode(true);
        filterButton.parentNode.replaceChild(newFilterButton, filterButton);
        filterInput.parentNode.replaceChild(newFilterInput, filterInput);

        function filterMods() {
            const filterVersion = newFilterInput.value.trim();
            if (!filterVersion) return;

            modVersions.forEach((versions, card) => {
                card.style.display = versions.includes(filterVersion) ? 'block' : 'none';
            });
        }

        newFilterButton.addEventListener('click', filterMods);
        newFilterInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') filterMods();
        });
    }

    let debounceTimeout;
    const observer = new MutationObserver((mutations) => {
        clearTimeout(debounceTimeout);
        debounceTimeout = setTimeout(() => {
            const shouldSetup = mutations.some(mutation =>
                Array.from(mutation.addedNodes).some(node =>
                    node.nodeType === 1 && node.querySelector?.('.project-card')
                )
            );
            if (shouldSetup) {
                isFilterSetup = false;
                setupModVersionFilter();
            }
        }, 500);
    });

    // Wait 5 seconds before starting the script
    setTimeout(() => {
        observer.observe(document.body, { childList: true, subtree: true });
        setupModVersionFilter();
    }, 5000);

})();