Modrinthify

Redirect curseforge.com mod pages to modrinth.com when possible

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name        Modrinthify
// @namespace   Violentmonkey Scripts
// @match       *://*.curseforge.com/minecraft/*
// @grant       none
// @version     1.6.3
// @author      devBoi76
// @license     MIT
// @description Redirect curseforge.com mod pages to modrinth.com when possible
// ==/UserScript==

/* jshint esversion: 6 */

function htmlToElements(html) {
    var t = document.createElement("template");
    t.innerHTML = html;
    return t.content;
}

function similarity(s1, s2) {
    var longer = s1;
    var shorter = s2;
    if (s1.length < s2.length) {
        longer = s2;
        shorter = s1;
    }
    var longerLength = longer.length;
    if (longerLength == 0) {
        return 1.0;
    }
    return (
        (longerLength - editDistance(longer, shorter)) /
        parseFloat(longerLength)
    );
}

function editDistance(s1, s2) {
    s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();

    var costs = new Array();
    for (var i = 0; i <= s1.length; i++) {
        var lastValue = i;
        for (var j = 0; j <= s2.length; j++) {
            if (i == 0) costs[j] = j;
            else {
                if (j > 0) {
                    var newValue = costs[j - 1];
                    if (s1.charAt(i - 1) != s2.charAt(j - 1))
                        newValue =
                            Math.min(Math.min(newValue, lastValue), costs[j]) +
                            1;
                    costs[j - 1] = lastValue;
                    lastValue = newValue;
                }
            }
        }
        if (i > 0) costs[s2.length] = lastValue;
    }
    return costs[s2.length];
}

const new_design_button =
    '<a id="modrinth-body" href="REDIRECT" target="_blank" style="width: fit-content; overflow: hidden; margin-top: 1rem; display: flex" > <style>#modrinth-body:hover {background-color: #4d4d4d;}#modrinth-body{background-color: #333; height: 36px; text-decoration: none; font-weight: 600; font-family: sans-serif; color: #e5e5e5; --mr-green: #30b27b; transition: background-color 0.15s ease;}#modrinth-body > div{display: flex; align-items: center;}#modrinthify-redirect{display: flex; height: 100%; align-items: center; background-color: var(--mr-green); font-weight: 600; padding-inline: 0.5rem;}#modrinthify-redirect > svg{height: 30px; margin-right: 0.5rem; fill: #e5e5e5;}</style> <div> <img style="display: inline-block; height: 36px; width: 36px" src="ICON_SOURCE"/> <div style="display: inline-block; margin-inline: 1rem; font-weight: 600" > MOD_NAME </div><div id="modrinthify-redirect" data-tooltip="Get on Modrinth" > <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 141.73 141.73" aria-hidden="true" > <g> <path d="M159.07,89.29A70.94,70.94,0,1,0,20,63.52H32A58.78,58.78,0,0,1,145.23,49.93l-11.66,3.12a46.54,46.54,0,0,0-29-26.52l-2.15,12.13a34.31,34.31,0,0,1,2.77,63.26l3.19,11.9a46.52,46.52,0,0,0,28.33-49l11.62-3.1A57.94,57.94,0,0,1,147.27,85Z" transform="translate(-19.79)" fill-rule="evenodd" ></path> <path transform="translate(-19.79)" d="M108.92,139.3A70.93,70.93,0,0,1,19.79,76h12a59.48,59.48,0,0,0,1.78,9.91,58.73,58.73,0,0,0,3.63,9.91l10.68-6.41a46.58,46.58,0,0,1,44.72-65L90.43,36.54A34.38,34.38,0,0,0,57.36,79.75C57.67,80.88,58,82,58.43,83l13.66-8.19L68,63.93l12.9-13.25,16.31-3.51L101.9,53l-7.52,7.61-6.55,2.06-4.69,4.82,2.3,6.38s4.64,4.94,4.65,4.94l6.57-1.74,4.67-5.13,10.2-3.24,3,6.84L104.05,88.43,86.41,94l-7.92-8.81L64.7,93.48a34.44,34.44,0,0,0,28.72,11.59L96.61,117A46.6,46.6,0,0,1,54.13,99.83l-10.64,6.38a58.81,58.81,0,0,0,99.6-9.77l11.8,4.29A70.77,70.77,0,0,1,108.92,139.3Z" ></path> </g> </svg> Get on Modrinth </div></div></a>';

const new_design_donation = `<a id="donate-button" target="_blank" href="REDIRECT" data-tooltip="Support the Author" > <style>#donate-button{color: color: #e5e5e5; background-color: #ff5e5b; font-weight: 600; text-decoration: none; display: flex; align-items: center; padding-right: 0.5rem;}#donate-button img{height: 100%; width: 36px;}</style> <img src="https://i.ibb.co/Y2Xgd4Q/kofilogo.png"> Support the Author </a>`;

const svg =
    '<svg class="h-full absolute" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 777 141.73" aria-hidden="true" class="text-logo"><g><path d="M159.07,89.29A70.94,70.94,0,1,0,20,63.52H32A58.78,58.78,0,0,1,145.23,49.93l-11.66,3.12a46.54,46.54,0,0,0-29-26.52l-2.15,12.13a34.31,34.31,0,0,1,2.77,63.26l3.19,11.9a46.52,46.52,0,0,0,28.33-49l11.62-3.1A57.94,57.94,0,0,1,147.27,85Z" transform="translate(-19.79)" fill="var(--color-brand)" fill-rule="evenodd"></path><path transform="translate(-19.79)" fill="var(--color-brand)" d="M108.92,139.3A70.93,70.93,0,0,1,19.79,76h12a59.48,59.48,0,0,0,1.78,9.91,58.73,58.73,0,0,0,3.63,9.91l10.68-6.41a46.58,46.58,0,0,1,44.72-65L90.43,36.54A34.38,34.38,0,0,0,57.36,79.75C57.67,80.88,58,82,58.43,83l13.66-8.19L68,63.93l12.9-13.25,16.31-3.51L101.9,53l-7.52,7.61-6.55,2.06-4.69,4.82,2.3,6.38s4.64,4.94,4.65,4.94l6.57-1.74,4.67-5.13,10.2-3.24,3,6.84L104.05,88.43,86.41,94l-7.92-8.81L64.7,93.48a34.44,34.44,0,0,0,28.72,11.59L96.61,117A46.6,46.6,0,0,1,54.13,99.83l-10.64,6.38a58.81,58.81,0,0,0,99.6-9.77l11.8,4.29A70.77,70.77,0,0,1,108.92,139.3Z"></path></g></svg>';

const HTML = ` \
<div id="modrinthify-redirect" class="button" style="background-color: #30B27B;\
border-top-right-radius: 0;\
border-bottom-right-radius: 0;\
font-weight: 600;"\
 data-tooltip="Get on Modrinth">\
<figure class="icon icon-margin relative w-5 h-4" >\
${svg}
</figure> \
Get on Modrinth\
</div>  \
`;

const DONATE_HTML = `\
<a target="_blank" href="REDIRECT" class="button" style="background-color: #FF5E5B;\
border-top-left-radius: 0;\
border-bottom-left-radius: 0;\
font-weight: 600;"\
data-tooltip="Support the Author"> \
<figure class="icon icon-margin relative w-5 h-4" >\
<img src="https://i.ibb.co/Y2Xgd4Q/kofilogo.png">\
</figure>\
Support the Author
</a> \
`;

const REGEX =
    /[\(\[](forge|fabric|forge\/fabric|fabric\/forge|unused|deprecated)[\)\]]/gim;

const MOD_PAGE_HTML = `<a id="modrinth-body" href="REDIRECT" target="_blank" class="box flex" style="overflow: hidden; height: max-content; margin-top: -1px;"><div>\
<img style="display:inline-block; height: 3rem" src="ICON_SOURCE">\
<div class="mx-2 font-bold" style="display: inline-block">MOD_NAME</div>\
<div style="display: inline-block">BUTTON_HTML</div></div></a>`;

const SEARCH_PAGE_HTML = `<a href="REDIRECT" target="_blank" class="box flex" style="overflow: hidden"><div>\
<img style="display:inline-block; height: 3rem" src="ICON_SOURCE">\
<div class="mx-2 font-bold" style="display: inline-block">MOD_NAME</div>\
<div style="display: inline-block">BUTTON_HTML</div></div></a>`;

let query = "head title";
const tab_title = document.querySelector(query).innerText;
let mod_name = undefined;
let mod_name_noloader = undefined;
let page = undefined;

function main() {
    const url = document.URL.split("/");
    page = url[4];

    const is_new_design = !location.hostname.startsWith("old.curseforge.com");

    const is_search = is_new_design
        ? url[4].split("?")[0] == "search"
        : url[5].startsWith("search") && url[5].split("?").length >= 2;

    if (is_search) {
        if (is_new_design) {
            search_query = document.querySelector(".search-input-field").value;
        } else {
            search_query = document
                .querySelector(".mt-6 > h2:nth-child(1)")
                .textContent.match(/Search results for '(.*)'/)[1];
        }
    } else {
        if (is_new_design) {
            // search_query = document.querySelector(".project-header > h1:nth-child(2)").innerText
            search_query = document.title.split(" - Minecraft ")[0];
        } else {
            search_query = document
                .querySelector("head meta[property='og:title']")
                .getAttribute("content");
        }
    }

    mod_name = search_query;
    mod_name_noloader = mod_name.replace(REGEX, "");

    if (is_search && is_new_design) {
        page_re = /.*&class=(.*?)&.*/;
        page = (page.match(page_re) || ["", "all"])[1];
    }

    api_facets = "";
    switch (page) {
        //=Mods===============
        case "mc-mods":
            api_facets = `facets=[["categories:'forge'","categories:'fabric'","categories:'quilt'","categories:'liteloader'","categories:'modloader'","categories:'rift'"],["project_type:mod"]]`;
            break;
        //=Server=Plugins=====
        case "mc-addons":
            return;
        case "customization":
            api_facets = `facets=[["project_type:shader"]]`;
            break;
        case "bukkit-plugins":
            api_facets = `facets=[["categories:'bukkit'","categories:'spigot'","categories:'paper'","categories:'purpur'","categories:'sponge'","categories:'bungeecord'","categories:'waterfall'","categories:'velocity'"],["project_type:mod"]]`;
            break;
        //=Resource=Packs=====
        case "texture-packs":
            api_facets = `facets=[["project_type:resourcepack"]]`;
            break;
        //=Modpacks===========
        case "modpacks":
            api_facets = `facets=[["project_type:modpack"]]`;
            break;
        case "all":
            api_facets = ``;
            break;
    }

    fetch(
        `https://api.modrinth.com/v2/search?limit=3&query=${mod_name_noloader}&${api_facets}`,
        { method: "GET", mode: "cors" },
    )
        .then((response) => response.json())
        .then((resp) => {
            let bd = document.querySelector("#modrinth-body");
            if (bd) {
                bd.remove();
            }

            if (page == undefined) {
                return;
            }

            if (resp.hits.length == 0) {
                return;
            }

            let max_sim = 0;
            let max_hit = undefined;

            for (const hit of resp.hits) {
                if (similarity(hit.title.trim(), mod_name) > max_sim) {
                    max_sim = similarity(hit.title.trim(), mod_name.trim());
                    max_hit = hit;
                }
                if (similarity(hit.title.trim(), mod_name_noloader) > max_sim) {
                    max_sim = similarity(
                        hit.title.trim(),
                        mod_name_noloader.trim(),
                    );
                    max_hit = hit;
                }
            }

            if (max_sim <= 0.7) {
                return;
            }
            // Add the buttons

            if (is_search) {
                if (is_new_design) {
                    // query = ".results-count"
                    query = ".search-tags";
                    let s = document.querySelector(query);
                    let buttonElement = htmlToElements(
                        new_design_button
                            .replace("ICON_SOURCE", max_hit.icon_url)
                            .replace("MOD_NAME", max_hit.title.trim())
                            .replace(
                                "REDIRECT",
                                `https://modrinth.com/${max_hit.project_type}/${max_hit.slug}`,
                            )
                            .replace("BUTTON_HTML", HTML),
                    );
                    buttonElement.childNodes[0].style.marginLeft = "auto";
                    s.appendChild(buttonElement);
                } else {
                    query = ".mt-6 > div:nth-child(3)";
                    let s = document.querySelector(query);
                    let buttonElement = htmlToElements(
                        SEARCH_PAGE_HTML.replace(
                            "ICON_SOURCE",
                            max_hit.icon_url,
                        )
                            .replace("MOD_NAME", max_hit.title.trim())
                            .replace(
                                "REDIRECT",
                                `https://modrinth.com/${max_hit.project_type}/${max_hit.slug}`,
                            )
                            .replace("BUTTON_HTML", HTML),
                    );
                    s.appendChild(buttonElement);
                }
            } else {
                if (is_new_design) {
                    let buttonElement = htmlToElements(
                        new_design_button
                            .replace("ICON_SOURCE", max_hit.icon_url)
                            .replace("MOD_NAME", max_hit.title.trim())
                            .replace(
                                "REDIRECT",
                                `https://modrinth.com/${max_hit.project_type}/${max_hit.slug}`,
                            ),
                    );

                    const injectButton = () => {
                        query = ".breadcrumbs";
                        if (document.querySelector("#modrinth-body")) return;

                        let s = document.querySelector(query);
                        if (!s) return;
                        console.log("Inject", s);
                        s.appendChild(buttonElement);
                    };
                    injectButton();
                    const observer = new MutationObserver(() => injectButton());
                    observer.observe(document.body, {
                        childList: true,
                        subtree: true,
                    });
                } else {
                    query = "div.-mx-1:nth-child(1)";
                    let s = document.querySelector(query);
                    let buttonElement = htmlToElements(
                        MOD_PAGE_HTML.replace("ICON_SOURCE", max_hit.icon_url)
                            .replace("MOD_NAME", max_hit.title.trim())
                            .replace(
                                "REDIRECT",
                                `https://modrinth.com/${max_hit.project_type}/${max_hit.slug}`,
                            )
                            .replace("BUTTON_HTML", HTML),
                    );
                    s.appendChild(buttonElement);
                }
            }
            // Add donation button if present
            fetch(`https://api.modrinth.com/v2/project/${max_hit.slug}`, {
                method: "GET",
                mode: "cors",
            })
                .then((response_p) => response_p.json())
                .then((resp_p) => {
                    if (document.querySelector("#donate-button")) {
                        return;
                    }
                    if (resp_p.donation_urls.length > 0) {
                        let redir = document.getElementById("modrinth-body");

                        if (is_new_design) {
                            redir.innerHTML += new_design_donation.replace(
                                "REDIRECT",
                                resp_p.donation_urls[0].url,
                            );
                            if (is_search) {
                                redir.style.marginRight = "-195.5px";
                            } else {
                                redir.style.marginRight = "-195.5px";
                            }
                        } else {
                            let donations = resp_p.donation_urls;
                            let dbutton = document.createElement("div");
                            dbutton.innerHTML = DONATE_HTML.replace(
                                "REDIRECT",
                                donations[0].url,
                            );
                            dbutton.style.display = "inline-block";
                            let redir = document.getElementById(
                                "modrinthify-redirect",
                            );
                            redir.after(dbutton);
                            if (!is_search) {
                                redir.parentNode.parentNode.parentNode.style.marginRight =
                                    "-150px";
                            }
                        }
                    }
                });
        });
}

main();

// document.querySelector(".classes-list").childNodes.forEach( (el) => {
// 	el.childNodes[0].addEventListener("click", main)
// })

let lastURL = document.URL;
new MutationObserver(() => {
    let url = document.URL;
    if (url != lastURL) {
        lastURL = url;
        main();
    }
}).observe(document, { subtree: true, childList: true });