Greasy Fork 支持简体中文。

DDSteam for IGDB and Steam

Add download/search links for games on IGDB and Steam pages.

// ==UserScript==
// @name         DDSteam for IGDB and Steam
// @name:es      DDSteam para IGDB y Steam
// @version      4.0
// @description  Add download/search links for games on IGDB and Steam pages.
// @description:es  Añadir enlaces de descarga/búsqueda de juegos en las páginas de IGDB y Steam.
// @author       johnromerobot
// @license      MIT
// @match        https://www.igdb.com/search*
// @match        https://www.igdb.com/games/*
// @match        https://store.steampowered.com/app/*
// @namespace    https://greasyfork.org/users/1243768
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration: Define each site's info and default enabled state ---
    const siteConfigs = [
        {
            id: "steamgg",
            name: "SteamGG",
            tooltip: "Search on SteamGG",
            icon: "https://i.ibb.co/XCj45HD/1728102863385.png",
            urlTemplate: "https://steamgg.net/?s={game}",
            defaultEnabled: true
        },
      {
            id: "games4u",
            name: "Games4U",
            tooltip: "Search on Games4U",
            icon: "https://games4u.org/wp-content/uploads/2024/07/Games4u.webp",
            urlTemplate: "https://games4u.org/?s={game}",
            defaultEnabled: true
        },
        {
            id: "steamrip",
            name: "SteamRIP",
            tooltip: "Search on SteamRIP",
            icon: "https://i.imgur.com/tmvOT86.png",
            urlTemplate: "https://steamrip.com/?s={game}",
            defaultEnabled: true
        },
        {
            id: "compupc",
            name: "CompuPC",
            tooltip: "Search on CompuPC",
            icon: "https://img.compu-pc.com/i/a5bbf162b8926d1eff5cdbec5ee6e09a/logo.png",
            urlTemplate: "https://compu-pc.com/?s={game}",
            defaultEnabled: true
        },
        {
            id: "juegosdepcfull",
            name: "JuegosdePCFull",
            tooltip: "Search on JuegosdePCFull",
            icon: "https://www.gamezfull.com/wp-content/themes/MystiqueR3/favicon_gf.ico",
            urlTemplate: "https://juegosdepcfull.com/?s={game}",
            defaultEnabled: true
        },
        {
            id: "csrinru",
            name: "CS.RIN.RU",
            tooltip: "Search on CS.RIN.RU (Login required)",
            icon: "https://i.ibb.co/RYQkz8t/site-logo-2.png",
            urlTemplate: "https://cs.rin.ru/forum/search.php?keywords={game}&terms=all&author=&sc=1&sf=titleonly&sk=t&sd=d&sr=topics&st=0&ch=300&t=0&submit=Search",
            defaultEnabled: true
        },
        {
            id: "gog",
            name: "GOG",
            tooltip: "Search on GOGGames",
            icon: "https://i.imgur.com/wXfz72C.png",
            urlTemplate: "https://www.gog-games.to/?q={game}",
            defaultEnabled: true
        },
        {
            id: "fitgirl",
            name: "FitGirl",
            tooltip: "Search on FitGirl",
            icon: "https://i.imgur.com/GOFbweI.png",
            urlTemplate: "https://fitgirl-repacks.site/?s={game}",
            defaultEnabled: true
        },
        {
            id: "ovagames",
            name: "OvaGames",
            tooltip: "Search on OvaGames",
            icon: "https://i.ibb.co/MxtCWQxG/ovagames-logo-Photoroom.png",
            urlTemplate: "https://www.ovagames.com/?s={game}",
            defaultEnabled: true
        },
        {
            id: "crocdb",
            name: "CrocDB",
            tooltip: "Search on CrocDB",
            icon: "https://crocdb.net/static/img/croc-512x512.png",
            urlTemplate: "https://crocdb.net/search/?title={game}",
            defaultEnabled: true
        },
      {
            id: "retrogametalk",
            name: "RetroGameTalk",
            tooltip: "Search on RetroGameTalk",
            icon: "https://retrogametalk.com/repository/wp-content/uploads/2025/02/rgt-logo.png",
            urlTemplate: "https://retrogametalk.com/repository/?s={game}",
            defaultEnabled: true
        },
      {
            id: "romsfun",
            name: "RomsFun",
            tooltip: "Search on RomsFun",
            icon: "https://romsfun.com/wp-content/uploads/2023/08/LOGO.png",
            urlTemplate: "https://romsfun.com/?s={game}",
            defaultEnabled: true
        },
      {
            id: "nxbrew",
            name: "NXBrew",
            tooltip: "Search on NXBrew",
            icon: "https://nxbrew.net/wp-content/uploads/2019/05/cropped-NXbrewlogo-1.png",
            urlTemplate: "https://nxbrew.net/?s={game}",
            defaultEnabled: true
        },
      {
            id: "nopaystation",
            name: "NoPayStation",
            tooltip: "Search on NoPayStation",
            icon: "https://i.ibb.co/22msVjw/87ef1b4993784afe5a15d20e5936253b.webp",
            urlTemplate: "https://nopaystation.com/search?query={game}&limit=50&orderBy=completionDate&sort=DESC&missing=Show",
            defaultEnabled: true
        },
        {
            id: "pcgamingwiki",
            name: "PCGamingWiki",
            tooltip: "Search on PCGamingWiki",
            icon: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTwZgcgIYqscsLY6OnFI3sC7ZRYui2OghZWIg&s",
            urlTemplate: "https://www.pcgamingwiki.com/w/index.php?search={game}",
            defaultEnabled: true
        }
    ];

    // --- Configuration Storage ---
    function getSiteOrder(platform) {
        const key = `ddsteam_order_${platform}`;
        return GM_getValue(key, siteConfigs.map(site => site.id));
    }

    function setSiteOrder(platform, order) {
        const key = `ddsteam_order_${platform}`;
        GM_setValue(key, order);
    }

    function isSiteEnabled(siteId, platform) {
        const key = `ddsteam_${platform}_${siteId}`;
        const site = siteConfigs.find(s => s.id === siteId);
        return GM_getValue(key, site.defaultEnabled);
    }

    function setSiteEnabled(siteId, platform, enabled) {
        const key = `ddsteam_${platform}_${siteId}`;
        GM_setValue(key, enabled);
    }

    // --- Popup Menu ---
    function createPopupMenu() {
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.top = '50%';
        modal.style.left = '50%';
        modal.style.transform = 'translate(-50%, -50%)';
        modal.style.backgroundColor = '#1a1a1a'; // Dark background
        modal.style.padding = '20px';
        modal.style.zIndex = '10000';
        modal.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        modal.style.borderRadius = '8px';
        modal.style.maxHeight = '80vh';
        modal.style.overflowY = 'auto';
        modal.style.color = '#fff'; // White text for contrast

        const title = document.createElement('h2');
        title.textContent = 'DDSteam Configuration';
        title.style.marginTop = '0';
        title.style.color = '#fff';
        modal.appendChild(title);

        const tabs = document.createElement('div');
        tabs.style.display = 'flex';
        tabs.style.marginBottom = '10px';

        const steamTab = document.createElement('button');
        steamTab.textContent = 'Steam';
        steamTab.style.flex = '1';
        steamTab.style.padding = '5px';
        steamTab.style.backgroundColor = '#333'; // Darker gray for inactive tab
        steamTab.style.border = 'none';
        steamTab.style.cursor = 'pointer';
        steamTab.style.color = '#fff';

        const igdbTab = document.createElement('button');
        igdbTab.textContent = 'IGDB';
        igdbTab.style.flex = '1';
        igdbTab.style.padding = '5px';
        igdbTab.style.backgroundColor = '#444'; // Slightly lighter gray for active tab
        igdbTab.style.border = 'none';
        igdbTab.style.cursor = 'pointer';
        igdbTab.style.color = '#fff';

        tabs.appendChild(steamTab);
        tabs.appendChild(igdbTab);
        modal.appendChild(tabs);

        const steamContainer = document.createElement('div');
        const igdbContainer = document.createElement('div');
        igdbContainer.style.display = 'none';

        function populateContainer(container, platform) {
            container.innerHTML = '';
            const order = getSiteOrder(platform);
            order.forEach(siteId => {
                const site = siteConfigs.find(s => s.id === siteId);
                const item = document.createElement('div');
                item.draggable = true;
                item.style.display = 'flex';
                item.style.alignItems = 'center';
                item.style.margin = '5px 0';
                item.style.cursor = 'move';
                item.style.padding = '5px';
                item.style.backgroundColor = '#2a2a2a'; // Dark background for items
                item.style.borderRadius = '4px';
                item.style.color = '#fff';

                const img = new Image();
                img.src = site.icon;
                img.style.width = '56px';
                img.style.height = '28px';
                img.style.objectFit = 'contain';
                img.style.marginRight = '10px';
                img.title = site.tooltip;

                const status = document.createElement('span');
                status.textContent = isSiteEnabled(site.id, platform) ? '✔' : '✘';
                status.style.fontSize = '20px';
                status.style.marginLeft = '10px';
                status.style.cursor = 'pointer';
                status.style.color = isSiteEnabled(site.id, platform) ? '#00ff00' : '#ff0000';

                status.addEventListener('click', () => {
                    const newState = !isSiteEnabled(site.id, platform);
                    setSiteEnabled(site.id, platform, newState);
                    status.textContent = newState ? '✔' : '✘';
                    status.style.color = newState ? '#00ff00' : '#ff0000';
                });

                item.appendChild(img);
                item.appendChild(document.createTextNode(site.name));
                item.appendChild(status);
                container.appendChild(item);

                item.addEventListener('dragstart', (e) => {
                    e.dataTransfer.setData('text/plain', site.id);
                    item.style.opacity = '0.5';
                });

                item.addEventListener('dragend', () => {
                    item.style.opacity = '1';
                });

                item.addEventListener('dragover', (e) => e.preventDefault());

                item.addEventListener('drop', (e) => {
                    e.preventDefault();
                    const draggedId = e.dataTransfer.getData('text/plain');
                    const newOrder = order.filter(id => id !== draggedId);
                    const dropIndex = order.indexOf(site.id);
                    newOrder.splice(dropIndex, 0, draggedId);
                    setSiteOrder(platform, newOrder);
                    populateContainer(container, platform);
                });
            });
        }

        populateContainer(steamContainer, 'steam');
        populateContainer(igdbContainer, 'igdb');
        modal.appendChild(steamContainer);
        modal.appendChild(igdbContainer);

        steamTab.addEventListener('click', () => {
            steamContainer.style.display = 'block';
            igdbContainer.style.display = 'none';
            steamTab.style.backgroundColor = '#444';
            igdbTab.style.backgroundColor = '#333';
        });

        igdbTab.addEventListener('click', () => {
            igdbContainer.style.display = 'block';
            steamContainer.style.display = 'none';
            igdbTab.style.backgroundColor = '#444';
            steamTab.style.backgroundColor = '#333';
        });

        const resetButton = document.createElement('button');
        resetButton.textContent = 'Reset to Defaults';
        resetButton.style.marginTop = '10px';
        resetButton.style.padding = '5px 10px';
        resetButton.style.backgroundColor = '#444';
        resetButton.style.border = 'none';
        resetButton.style.color = '#fff';
        resetButton.style.cursor = 'pointer';
        resetButton.addEventListener('click', () => {
            const platform = steamContainer.style.display === 'block' ? 'steam' : 'igdb';
            setSiteOrder(platform, siteConfigs.map(site => site.id));
            siteConfigs.forEach(site => setSiteEnabled(site.id, platform, site.defaultEnabled));
            populateContainer(steamContainer, 'steam');
            populateContainer(igdbContainer, 'igdb');
        });
        modal.appendChild(resetButton);

        const okButton = document.createElement('button');
        okButton.textContent = 'OK';
        okButton.style.marginTop = '10px';
        okButton.style.marginLeft = '10px';
        okButton.style.padding = '5px 10px';
        okButton.style.backgroundColor = '#444';
        okButton.style.border = 'none';
        okButton.style.color = '#fff';
        okButton.style.cursor = 'pointer';
        okButton.addEventListener('click', () => {
            document.body.removeChild(modal);
            window.location.reload(); // Refresh the page
        });
        modal.appendChild(okButton);

        // Add click outside handler
        document.addEventListener('click', (e) => {
            if (!modal.contains(e.target)) {
                document.body.removeChild(modal);
            }
        });

        document.body.appendChild(modal);
    }

    GM_registerMenuCommand("Configure DDSteam Links", createPopupMenu);

    // --- Utility Functions ---
    function getSites(formattedGameName, platform) {
        const order = getSiteOrder(platform);
        return order
            .map(siteId => siteConfigs.find(site => site.id === siteId))
            .filter(site => isSiteEnabled(site.id, platform))
            .map(site => ({
                name: site.name,
                tooltip: site.tooltip,
                icon: site.icon,
                url: site.urlTemplate.replace('{game}', formattedGameName)
            }));
    }

    function createButton(searchLink, buttonText, tooltipText, iconPath, iconWidth, iconHeight) {
        const linkButton = document.createElement("a");
        linkButton.href = searchLink;
        linkButton.target = "_blank";
        linkButton.title = tooltipText;
        linkButton.style.display = 'inline-block';
        linkButton.style.marginRight = '10px';
        linkButton.style.marginTop = '5px';

        const img = new Image();
        img.src = iconPath;
        img.alt = buttonText;
        img.style.width = iconWidth;
        img.style.height = iconHeight;
        img.style.objectFit = 'contain';
        img.style.transition = 'transform 0.3s ease-in-out';
        img.style.borderRadius = '8px';
        img.style.boxShadow = '0 0 5px rgba(0, 0, 0, 0.3)';
        img.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';

        linkButton.appendChild(img);
        return linkButton;
    }

    function formatGameName(gameName) {
        return gameName.trim().toLowerCase().replace(/'/g, '').replace(/_/g, ' ').replace(/[^a-zA-Z0-9 ]/g, '');
    }

    // --- Processing Functions ---
    function processSearchResults() {
        const gameLinks = document.querySelectorAll("a.overflow-wrap.link-dark.h4.mt-0");
        gameLinks.forEach(link => {
            if (!link.href.includes('/games/')) return;
            let gameName = link.firstChild && link.firstChild.nodeType === Node.TEXT_NODE ?
                link.firstChild.textContent.trim() : link.textContent.trim();
            if (link.nextElementSibling && link.nextElementSibling.classList.contains("ddsteam-container")) return;
            const container = document.createElement('div');
            container.classList.add("ddsteam-container");
            container.style.marginTop = '5px';
            container.style.display = 'flex';
            container.style.flexWrap = 'wrap';
            container.style.gap = '5px';
            const formattedGameName = formatGameName(gameName);
            const sites = getSites(formattedGameName, 'igdb');
            sites.forEach(site => {
                const btn = createButton(site.url, site.name, site.tooltip, site.icon, '56px', '28px');
                container.appendChild(btn);
            });
            link.insertAdjacentElement('afterend', container);
        });
    }

    function processGameInfoPage() {
        const linksContainer = document.querySelector('.MuiGrid2-grid-xs-12');
        if (!linksContainer || linksContainer.querySelector('.ddsteam-game-links')) return;
        const container = document.createElement('div');
        container.classList.add('ddsteam-game-links');
        container.style.marginTop = '8px';
        container.style.display = 'flex';
        container.style.flexWrap = 'wrap';
        container.style.gap = '8px';
        container.style.justifyContent = 'center';

        const gameTitleElement = document.querySelector("h1.MuiTypography-h3") || document.querySelector("h1");
        if (!gameTitleElement) return;
        const gameName = gameTitleElement.textContent.trim();
        const formattedGameName = formatGameName(gameName);
        const sites = getSites(formattedGameName, 'igdb');
        sites.forEach(site => {
            const btn = createButton(site.url, site.name, site.tooltip, site.icon, '48px', '48px');
            container.appendChild(btn);
        });

        const hrElem = linksContainer.querySelector('hr');
        if (hrElem) hrElem.insertAdjacentElement('beforebegin', container);
        else linksContainer.appendChild(container);
    }

    function processGamePage() {
        const gameTitleElement = document.querySelector('.apphub_AppName');
        if (!gameTitleElement) return;
        const parent = gameTitleElement.parentElement;
        if (!parent || parent.querySelector('.ddsteam-container')) return;

        const gameName = gameTitleElement.textContent.trim();
        const formattedGameName = formatGameName(gameName);
        const sites = getSites(formattedGameName, 'steam');

        const container = document.createElement('div');
        container.classList.add("ddsteam-container");
        container.style.marginTop = '10px';
        container.style.display = 'flex';
        container.style.flexWrap = 'wrap';
        container.style.gap = '5px';

        sites.forEach(site => {
            const btn = createButton(site.url, site.name, site.tooltip, site.icon, '56px', '28px');
            container.appendChild(btn);
        });

        parent.appendChild(container);
    }

    // --- Styles ---
    const styles = `
        a:hover img {
            transform: scale(1.1);
        }
    `;
    const styleSheet = document.createElement("style");
    styleSheet.textContent = styles;
    document.head.appendChild(styleSheet);

    // --- Initialization ---
    const url = window.location.href;
    if (url.includes('igdb.com/search')) {
        processSearchResults();
        const observer = new MutationObserver(() => processSearchResults());
        observer.observe(document.body, { childList: true, subtree: true });
    } else if (url.includes('igdb.com/games/')) {
        setTimeout(processGameInfoPage, 1000);
    } else if (url.includes('store.steampowered.com/app/')) {
        processGamePage();
    }
})();