::GOG-Games Links::

Adds YouTube and changelog search buttons per game card.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ::GOG-Games Links::
// @namespace    masterofobzene-GOG-Games
// @version      3.4
// @description  Adds YouTube and changelog search buttons per game card.
// @author       masterofobzene
// @homepage     https://github.com/masterofobzene/UserScriptRepo
// @license      GNU GPLv3
// @match        *://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 CHANGELOG_BUTTON_CLASS = 'changelog-search-unique';
    const BUTTON_CONTAINER_CLASS = 'game-search-buttons-container';
    const PROCESSED_ATTR = 'data-yt-processed-v3';
    const PURPLE_COLOR = '#6a1b9a';
    const HOVER_PURPLE = '#4a148c';
    const ORANGE_COLOR = '#e65100';
    const HOVER_ORANGE = '#bf360c';
    let processing = false;

    function createSearchButton(gameName, type) {
        const isYouTube = type === 'youtube';
        const button = document.createElement('button');
        button.className = isYouTube ? YT_BUTTON_CLASS : CHANGELOG_BUTTON_CLASS;
        button.textContent = isYouTube ? 'Gameplay Video' : 'Changelog';
        button.style.cssText = `
            padding: 4px 8px !important;
            background: ${isYouTube ? PURPLE_COLOR : ORANGE_COLOR} !important;
            color: white !important;
            border: none !important;
            border-radius: 4px !important;
            cursor: pointer !important;
            margin: 4px 2px !important;
            font-family: Arial !important;
            font-size: 12px !important;
            transition: background 0.2s !important;
            display: inline-block !important;
            position: relative !important;
            z-index: 1000 !important;
            line-height: 1.2 !important;
            flex: 1 !important;
            min-width: 70px !important;
        `;

        const handleClick = (event) => {
            event.stopImmediatePropagation();
            event.preventDefault();
            const searchQuery = isYouTube
                ? `${gameName} no commentary`
                : `"${gameName} - Steam News Hub"`;

            if (isYouTube) {
                window.open(`https://youtube.com/results?search_query=${encodeURIComponent(searchQuery)}`, '_blank');
            } else {
                // Use Firefox default search engine if available
                if (typeof browser !== 'undefined' && browser.search && browser.search.search) {
                    browser.search.search({ query: searchQuery, disposition: 'NEW_TAB' });
                } else {
                    // Fallback to DuckDuckGo
                    window.open(`https://duckduckgo.com/?q=${encodeURIComponent(searchQuery)}`, '_blank');
                }
            }
        };

        button.addEventListener('mouseover', () => {
            button.style.background = isYouTube ? HOVER_PURPLE : HOVER_ORANGE;
        });
        button.addEventListener('mouseout', () => {
            button.style.background = isYouTube ? PURPLE_COLOR : ORANGE_COLOR;
        });
        button.addEventListener('click', handleClick, true);
        button.addEventListener('auxclick', handleClick, true);

        return button;
    }

    function createButtonContainer(gameName) {
        const container = document.createElement('div');
        container.className = BUTTON_CONTAINER_CLASS;
        container.style.cssText = `
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;
            gap: 4px !important;
            margin: 4px 0 !important;
            width: 100% !important;
        `;

        container.appendChild(createSearchButton(gameName, 'youtube'));
        container.appendChild(createSearchButton(gameName, 'changelog'));

        return container;
    }

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

        processing = true;
        try {
            const existingContainer = card.querySelector(`.${BUTTON_CONTAINER_CLASS}`);
            if (existingContainer) {
                existingContainer.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(`.${BUTTON_CONTAINER_CLASS}`)) {
                const buttonContainer = createButtonContainer(gameName);
                container.insertBefore(buttonContainer, container.firstChild);
                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});

    // Mutation observer for dynamic content
    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
    });
})();