::GOG-Games Links::

Adds a YouTube button per game card to search for no-commentary gameplay videos. It was made with deepseek AI.

Versione datata 04/03/2025. Vedi la nuova versione l'ultima versione.

// ==UserScript==
// @name         ::GOG-Games Links::
// @namespace    masterofobzene-GOG-Games
// @version      3.0
// @description  Adds a YouTube button per game card to search for no-commentary gameplay videos. It was made with deepseek AI.
// @author       masterofobzene
// @license      MIT
// @match        https://gog-games.to/*
// @grant        none
// @icon         https://files.mastodon.social/accounts/avatars/114/061/563/113/485/047/original/f9c6c7664af152f1.png
// ==/UserScript==

(function() {
    'use strict';

    const YT_BUTTON_CLASS = 'yt-search-unique';
    const PROCESSED_ATTR = 'data-yt-processed-v2';
    const PURPLE_COLOR = '#6a1b9a';
    const HOVER_COLOR = '#4a148c';
    let processing = false;

    function createYouTubeButton(gameName) {
        const button = document.createElement('button');
        button.className = YT_BUTTON_CLASS;
        button.textContent = 'YouTube Search';
        button.style.cssText = `
            padding: 6px 12px !important;
            background: ${PURPLE_COLOR} !important;
            color: white !important;
            border: none !important;
            border-radius: 4px !important;
            cursor: pointer !important;
            margin: 8px 0 !important;
            font-family: Arial !important;
            transition: background 0.2s !important;
            display: inline-block !important;
            position: relative !important;
            z-index: 1000 !important;
        `;

        const handleClick = (event) => {
            event.stopImmediatePropagation();
            event.preventDefault();
            window.open(`https://youtube.com/results?search_query=${encodeURIComponent(gameName + ' no commentary')}`, '_blank');
        };

        button.addEventListener('mouseover', () => button.style.background = HOVER_COLOR);
        button.addEventListener('mouseout', () => button.style.background = PURPLE_COLOR);
        button.addEventListener('click', handleClick, true); // Use capturing phase
        button.addEventListener('auxclick', handleClick, true);

        return button;
    }

    function processCard(card) {
        if (processing || card.hasAttribute(PROCESSED_ATTR)) return;

        processing = true;
        try {
            const existingButton = card.querySelector(`.${YT_BUTTON_CLASS}`);
            if (existingButton) {
                existingButton.remove();
            }

            const gameName = [
                () => card.querySelector('img[alt]')?.alt?.trim(),
                () => card.querySelector('[class*="title"]')?.textContent?.trim(),
                () => card.querySelector('h3, h4')?.textContent?.trim()
            ].reduce((acc, fn) => acc || fn(), '');

            if (!gameName) return;

            const container = card.querySelector('.actions, .card-footer') || card.querySelector('a')?.parentElement || card;
            if (container && !container.querySelector(`.${YT_BUTTON_CLASS}`)) {
                container.prepend(createYouTubeButton(gameName));
                card.setAttribute(PROCESSED_ATTR, 'true');
            }
        } finally {
            processing = false;
        }
    }

    function processAllCards() {
        const cards = document.querySelectorAll('[class*="card"]:not([${PROCESSED_ATTR}])');
        cards.forEach(card => {
            if (!card.hasAttribute(PROCESSED_ATTR)) {
                processCard(card);
            }
        });
    }

    // Initial processing after full load
    window.addEventListener('load', () => {
        setTimeout(processAllCards, 2000);
    }, {once: true});

    // Targeted mutation observation
    const mainContent = document.getElementById('main') || document.querySelector('main') || document.body;
    const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE && node.matches('[class*="card"]')) {
                        processCard(node);
                    }
                });
            }
        });
    });

    observer.observe(mainContent, {
        childList: true,
        subtree: true
    });
})();