Multi-Platform Search Buttons for DLsite

Adds a compact, open-by-default dropdown menu for search buttons on DLsite pages, with grouped buttons for gaming forums, correct favicons, and a design that matches DLsite's aesthetic. F95Zone forum and product ID buttons open f95zone.to in a focused tab on left-click and unfocused tab on middle-click, fetch search IDs, and redirect to results in the same tab. Supports simultaneous searches without interference and ensures button text is highlightable.

// ==UserScript==
// @name         Multi-Platform Search Buttons for DLsite
// @namespace    http://tampermonkey.net/
// @version      3.30.0
// @description  Adds a compact, open-by-default dropdown menu for search buttons on DLsite pages, with grouped buttons for gaming forums, correct favicons, and a design that matches DLsite's aesthetic. F95Zone forum and product ID buttons open f95zone.to in a focused tab on left-click and unfocused tab on middle-click, fetch search IDs, and redirect to results in the same tab. Supports simultaneous searches without interference and ensures button text is highlightable.
// @author       FunkyJustin
// @match        https://www.dlsite.com/maniax/work/=/product_id/*
// @match        https://www.dlsite.com/pro/work/=/product_id/*
// @match        https://f95zone.to/*
// @grant        GM_openInTab
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Check if on f95zone.to to handle search
    if (window.location.href.includes('f95zone.to')) {
        const urlParams = new URLSearchParams(window.location.search);
        const query = urlParams.get('query');
        if (query) {
            const form = document.createElement('form');
            form.method = 'POST';
            form.action = 'https://f95zone.to/search/search';
            form.style.display = 'none';

            const inputs = {
                'keywords': query,
                'order': 'relevance',
                '_xfToken': document.querySelector('input[name="_xfToken"]')?.value || ''
            };

            for (const [name, value] of Object.entries(inputs)) {
                const input = document.createElement('input');
                input.type = 'hidden';
                input.name = name;
                input.value = value;
                form.appendChild(input);
            }

            document.body.appendChild(form);

            fetch(form.action, {
                method: 'POST',
                body: new FormData(form),
                redirect: 'follow'
            })
            .then(response => {
                const finalUrl = response.url;
                const match = finalUrl.match(/\/search\/(\d+)\//);
                if (match && match[1]) {
                    const searchId = match[1];
                    const searchUrl = `https://f95zone.to/search/${searchId}/?q=${encodeURIComponent(query)}&o=relevance`;
                    window.location.href = searchUrl; // Redirect in same tab
                } else {
                    console.error('Could not extract search ID for query:', query, 'URL:', finalUrl);
                    alert('Failed to fetch search ID for "' + query + '". Redirecting to fallback URL.');
                    window.location.href = `https://f95zone.to/search/?q=${encodeURIComponent(query)}&o=relevance`;
                }
            })
            .catch(error => {
                console.error('Error performing search for query:', query, error);
                alert('Error fetching search ID for "' + query + '". Redirecting to fallback URL.');
                window.location.href = `https://f95zone.to/search/?q=${encodeURIComponent(query)}&o=relevance`;
            })
            .finally(() => {
                document.body.removeChild(form);
            });
        }
        return;
    }

    // Inject CSS styles for DLsite
    const style = document.createElement('style');
    style.innerHTML = `
        :root {
            --button-bg-start: #D6BCFA;
            --button-bg-end: #FBB6CE;
            --button-text: #f5f5f5;
            --button-shadow: rgba(0, 0, 0, 0.1);
            --button-border: #B794F4;
            --dropdown-bg: #f9f9f9;
        }
        .search-btn {
            display: inline-flex;
            align-items: center;
            padding: 4px 8px;
            margin: 2px;
            background: linear-gradient(45deg, var(--button-bg-start), var(--button-bg-end));
            color: #800080 !important; /* Match purple color of original non-forum buttons */
            text-decoration: none;
            border-radius: 12px;
            border: 1px solid var(--button-border);
            box-shadow: 0 2px 4px var(--button-shadow);
            font-family: Arial, sans-serif;
            font-size: 15px;
            font-weight: bold;
            transition: box-shadow 0.2s, opacity 0.3s;
            cursor: pointer;
            appearance: none; /* Reset browser-specific button styles */
            -webkit-appearance: none;
            -moz-appearance: none;
            background-clip: padding-box;
            user-select: auto !important; /* Allow text selection */
        }
        .search-btn:hover {
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
            opacity: 0.95;
        }
        .search-btn img {
            width: 18px;
            height: 18px;
            margin-right: 6px;
        }
        .circle-btn-wrapper .search-btn {
            padding: 3px 6px;
            font-size: 14px;
        }
        .dropdown-container {
            max-width: 600px;
            margin-top: 10px;
            margin-bottom: 20px;
            position: relative;
        }
        .dropdown-toggle {
            display: flex;
            align-items: center;
            padding: 5px 10px;
            background-color: #E91E63;
            color: #fff;
            border: none;
            border-radius: 12px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: bold;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        .dropdown-toggle:hover {
            background-color: #C2185B;
        }
        .dropdown-toggle::after {
            content: '▼';
            margin-left: 5px;
            transition: transform 0.2s;
        }
        .dropdown-toggle.collapsed::after {
            transform: rotate(180deg);
        }
        .dropdown-content {
            display: block; /* Open by default */
            background-color: var(--dropdown-bg);
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            padding: 10px;
            margin-top: 5px;
        }
        .dropdown-content.hidden {
            display: none;
        }
        .button-group {
            margin-bottom: 8px;
        }
        .group-label {
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: bold;
            color: #333;
            margin-bottom: 5px;
        }
        .button-group-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(100px, auto));
            gap: 5px;
        }
        .circle-btn-wrapper {
            display: flex;
            align-items: center;
            margin-top: 5px;
            gap: 5px;
        }
        .version-label {
            position: absolute;
            top: 5px;
            right: 5px;
            font-family: Arial, sans-serif;
            font-size: 12px;
            color: #666;
        }
    `;
    document.head.appendChild(style);

    // Icon URLs for each website
    const icons = {
        f95zone: 'https://f95zone.to/favicon.ico',
        ryuugames: 'https://www.ryuugames.com/wp-content/uploads/2020/05/cropped-ryuugames_logo-1-32x32.png',
        otomi: 'https://otomi-games.com/favicon.ico'
    };

    // Function to create a styled button
    function createButton(text, queryOrUrl, iconUrl, isF95Forum = false) {
        const element = isF95Forum ? document.createElement('button') : document.createElement('a');
        element.className = 'search-btn';
        element.title = text;

        if (isF95Forum) {
            element.type = 'button'; // Ensure button behavior
            element.addEventListener('mousedown', (event) => {
                if (event.button === 0 || event.button === 1) { // Left or middle-click
                    event.preventDefault();
                    // Open tab with query in URL parameter
                    GM_openInTab(`https://f95zone.to/?query=${encodeURIComponent(queryOrUrl)}`, { active: event.button === 0, setParent: true });
                    // Add small delay to prevent browser overload
                    return new Promise(resolve => setTimeout(resolve, 100));
                }
            });
        } else {
            element.href = queryOrUrl;
            element.target = '_blank';
        }

        if (iconUrl) {
            const img = document.createElement('img');
            img.src = iconUrl;
            img.alt = '';
            element.appendChild(img);
        }
        element.appendChild(document.createTextNode(text));
        return element;
    }

    // Main function to add buttons
    function addSearchButtons() {
        const gameTitleEl = document.querySelector('h1#work_name');
        const circleNameEl = document.querySelector('span[itemprop="brand"] a');
        if (!gameTitleEl || !circleNameEl) {
            console.warn('Required elements not found.');
            return;
        }

        const gameTitle = gameTitleEl.innerText.trim();
        const circleName = circleNameEl.innerText.trim();
        const productIdMatch = window.location.href.match(/product_id\/([^\/]+)/);
        const productId = productIdMatch ? productIdMatch[1].replace(/\.html#?$/, '') : '';

        const buttons = {
            f95Game: createButton('Search on F95Zone', `https://f95zone.to/sam/latest_alpha/#/cat=games/page=1/search=${encodeURIComponent(gameTitle)}`, icons.f95zone),
            f95Forum: createButton('Search on F95Zone Forums', gameTitle, icons.f95zone, true),
            f95PID: createButton('Search Product ID on F95Zone Forums', productId, icons.f95zone, true),
            f95Circle: createButton('Search Circle on F95Zone', `https://f95zone.to/sam/latest_alpha/#/cat=games/page=1/creator=${encodeURIComponent(circleName)}`, icons.f95zone),
            f95ForumCircle: createButton('Search Circle on F95Zone Forums', circleName, icons.f95zone, true),
            otomiGame: createButton('Search on OtomiGames', `https://otomi-games.com/?s=${encodeURIComponent(gameTitle)}`, icons.otomi),
            otomiPID: createButton('Search Product ID on OtomiGames', `https://otomi-games.com/?s=${encodeURIComponent(productId)}`, icons.otomi),
            otomiCircle: createButton('Search Circle on OtomiGames', `https://otomi-games.com/?s=${encodeURIComponent(circleName)}`, icons.otomi),
            ryuuGame: createButton('Search on Ryuugames', `https://www.ryuugames.com/?s=${encodeURIComponent(gameTitle)}`, icons.ryuugames),
            ryuuPID: createButton('Search Product ID on Ryuugames', `https://www.ryuugames.com/?s=${encodeURIComponent(productId)}`, icons.ryuugames),
            ryuuCircle: createButton('Search Circle on Ryuugames', `https://www.ryuugames.com/?s=${encodeURIComponent(circleName)}`, icons.ryuugames)
        };

        const titleContainer = document.querySelector('.base_title_br');
        if (titleContainer) {
            const dropdownContainer = document.createElement('div');
            dropdownContainer.className = 'dropdown-container';

            const toggleButton = document.createElement('button');
            toggleButton.className = 'dropdown-toggle';
            toggleButton.textContent = 'Search Options';
            toggleButton.addEventListener('click', () => {
                dropdownContent.classList.toggle('hidden');
                toggleButton.classList.toggle('collapsed');
            });

            const dropdownContent = document.createElement('div');
            dropdownContent.className = 'dropdown-content';

            const versionLabel = document.createElement('div');
            versionLabel.className = 'version-label';
            versionLabel.textContent = 'v3.30.0';
            dropdownContainer.appendChild(versionLabel);

            const f95Group = document.createElement('div');
            f95Group.className = 'button-group';
            const f95Label = document.createElement('div');
            f95Label.className = 'group-label';
            f95Label.textContent = 'F95Zone';
            const f95Grid = document.createElement('div');
            f95Grid.className = 'button-group-grid';
            [buttons.f95Game, buttons.f95Forum, buttons.f95PID].forEach(btn => f95Grid.appendChild(btn));
            f95Group.appendChild(f95Label);
            f95Group.appendChild(f95Grid);

            const otomiGroup = document.createElement('div');
            otomiGroup.className = 'button-group';
            const otomiLabel = document.createElement('div');
            otomiLabel.className = 'group-label';
            otomiLabel.textContent = 'OtomiGames';
            const otomiGrid = document.createElement('div');
            otomiGrid.className = 'button-group-grid';
            [buttons.otomiGame, buttons.otomiPID].forEach(btn => otomiGrid.appendChild(btn));
            otomiGroup.appendChild(otomiLabel);
            otomiGroup.appendChild(otomiGrid);

            const ryuuGroup = document.createElement('div');
            ryuuGroup.className = 'button-group';
            const ryuuLabel = document.createElement('div');
            ryuuLabel.className = 'group-label';
            ryuuLabel.textContent = 'Ryuugames';
            const ryuuGrid = document.createElement('div');
            ryuuGrid.className = 'button-group-grid';
            [buttons.ryuuGame, buttons.ryuuPID].forEach(btn => ryuuGrid.appendChild(btn));
            ryuuGroup.appendChild(ryuuLabel);
            ryuuGroup.appendChild(ryuuGrid);

            dropdownContent.appendChild(f95Group);
            dropdownContent.appendChild(otomiGroup);
            dropdownContent.appendChild(ryuuGroup);

            dropdownContainer.appendChild(toggleButton);
            dropdownContainer.appendChild(dropdownContent);
            titleContainer.parentNode.insertBefore(dropdownContainer, titleContainer.nextSibling);
        }

        const circleWrapper = document.createElement('span');
        circleWrapper.className = 'circle-btn-wrapper';
        [buttons.f95Circle, buttons.f95ForumCircle, buttons.ryuuCircle, buttons.otomiCircle].forEach(btn => circleWrapper.appendChild(btn));
        circleNameEl.parentNode.insertBefore(circleWrapper, circleNameEl.nextSibling);
    }

    // Only run button creation on DLsite pages
    if (window.location.href.includes('dlsite.com')) {
        const observer = new MutationObserver((mutations, obs) => {
            if (document.querySelector('h1#work_name') && document.querySelector('span[itemprop="brand"] a')) {
                addSearchButtons();
                obs.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }
})();