Multi-Platform Search Buttons for DLsite

Advanced search buttons for DLsite with settings dialog (gear icon).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         Multi-Platform Search Buttons for DLsite
// @namespace    http://tampermonkey.net/
// @version      4.12.0
// @description  Advanced search buttons for DLsite with settings dialog (gear icon).
// @author       FunkyJustin
// @match        https://www.dlsite.com/maniax/work/=/product_id/*
// @match        https://www.dlsite.com/pro/work/=/product_id/*
// @match        https://www.dlsite.com/maniax/announce/=/product_id/*
// @match        https://www.dlsite.com/pro/announce/=/product_id/*
// @match        https://f95zone.to/*
// @grant        GM_openInTab
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ==================== F95Zone Handler ====================
    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 match = response.url.match(/\/search\/(\d+)\//);
                window.location.href = match 
                    ? `https://f95zone.to/search/${match[1]}/?q=${encodeURIComponent(query)}&o=relevance`
                    : `https://f95zone.to/search/?q=${encodeURIComponent(query)}&o=relevance`;
            })
            .catch(() => {
                window.location.href = `https://f95zone.to/search/?q=${encodeURIComponent(query)}&o=relevance`;
            })
            .finally(() => {
                if (form.parentNode) document.body.removeChild(form);
            });
        }
        return;
    }

    // ==================== SETTINGS ====================
    const SETTINGS = {
        get defaultOpen() {
            return GM_getValue('dropdown_default_open', true);
        },
        set defaultOpen(value) {
            GM_setValue('dropdown_default_open', value);
        }
    };

    // ==================== CONFIGURATION ====================
    const CONFIG = {
        groups: [
            {
                name: 'F95Zone',
                buttons: [
                    { text: 'Search on F95Zone', url: (d) => `https://f95zone.to/sam/latest_alpha/#/cat=games/page=1/search=${encodeURIComponent(d.gameTitle)}`, icon: 'f95zone' },
                    { text: 'Search on F95Zone Forums', query: (d) => d.gameTitle, isF95Forum: true, icon: 'f95zone' },
                    { text: 'Search Product ID on F95Zone Forums', query: (d) => d.productId, isF95Forum: true, icon: 'f95zone' },
                ]
            },
            {
                name: 'Utilities',
                buttons: [
                    { text: 'Copy Product ID', action: 'copyPID' },
                    { text: 'Search Product ID on Google', url: (d) => `https://www.google.com/search?q="${d.productId}"`, icon: 'google' },
                    { text: 'Search Title on Google', url: (d) => `https://www.google.com/search?q=${encodeURIComponent(d.gameTitle)}`, icon: 'google' },
                    { text: 'Copy Title', action: 'copyTitle' },
                ]
            },
            {
                name: 'OtomiGames',
                buttons: [
                    { text: 'Search on OtomiGames', url: (d) => `https://otomi-games.com/?s=${encodeURIComponent(d.gameTitle)}`, icon: 'otomi' },
                    { text: 'Search Product ID on OtomiGames', url: (d) => `https://otomi-games.com/?s=${encodeURIComponent(d.productId)}`, icon: 'otomi' },
                ]
            },
            {
                name: 'Ryuugames',
                buttons: [
                    { text: 'Search on Ryuugames', url: (d) => `https://www.ryuugames.com/?s=${encodeURIComponent(d.gameTitle)}`, icon: 'ryuugames' },
                    { text: 'Search Product ID on Ryuugames', url: (d) => `https://www.ryuugames.com/?s=${encodeURIComponent(d.productId)}`, icon: 'ryuugames' },
                ]
            }
        ]
    };

    // ==================== CSS ====================
    const style = document.createElement('style');
    style.innerHTML = `
        :root {
            --button-bg-start: #D6BCFA;
            --button-bg-end: #FBB6CE;
            --button-shadow: rgba(0, 0, 0, 0.1);
            --button-border: #B794F4;
            --dropdown-bg: #f9f9f9;
        }
        .search-btn {
            display: inline-flex;
            align-items: center;
            padding: 5px 9px;
            margin: 3px;
            background: linear-gradient(45deg, var(--button-bg-start), var(--button-bg-end));
            color: #800080 !important;
            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: 14.5px;
            font-weight: bold;
            transition: all 0.2s;
            cursor: pointer;
            user-select: auto !important;
            white-space: nowrap;
        }
        .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;
        }

        .dropdown-container {
            max-width: 680px;
            margin: 12px 0 20px 0;
            position: relative;
        }
        .dropdown-header {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .dropdown-toggle {
            display: flex;
            align-items: center;
            padding: 6px 12px;
            background-color: #E91E63;
            color: #fff;
            border: none;
            border-radius: 12px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: bold;
            cursor: pointer;
            flex: 1;
        }
        .dropdown-toggle::after {
            content: ' ▼';
            margin-left: 8px;
            transition: transform 0.3s;
        }
        .dropdown-toggle.collapsed::after {
            transform: rotate(180deg);
        }

        .gear-btn {
            background: none;
            border: none;
            font-size: 18px;
            cursor: pointer;
            padding: 6px;
            border-radius: 50%;
            color: #fff;
            opacity: 0.9;
        }
        .gear-btn:hover {
            background: rgba(255,255,255,0.25);
        }

        .dropdown-content {
            display: block;
            background-color: var(--dropdown-bg);
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            padding: 12px;
            margin-top: 6px;
        }
        .dropdown-content.hidden {
            display: none;
        }

        .version-label {
            position: absolute;
            bottom: 6px;
            right: 12px;
            font-size: 11px;
            color: #888;
            pointer-events: none;
        }

        .button-group { margin-bottom: 12px; }
        .group-label {
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: bold;
            color: #333;
            margin-bottom: 6px;
        }
        .button-group-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
            gap: 6px;
        }
        .circle-btn-wrapper {
            display: flex;
            align-items: center;
            margin-top: 6px;
            gap: 6px;
            flex-wrap: wrap;
        }

        /* Settings Modal */
        .settings-modal {
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(0,0,0,0.65);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 99999;
        }
        .modal-content {
            background: white;
            padding: 24px;
            border-radius: 12px;
            width: 340px;
            box-shadow: 0 8px 30px rgba(0,0,0,0.3);
        }
        .modal-content h3 { margin: 0 0 16px 0; color: #333; }
        .modal-content label { display: block; margin: 14px 0; font-size: 15px; cursor: pointer; }
        .modal-buttons { margin-top: 24px; text-align: right; }
        .modal-buttons button {
            padding: 8px 18px;
            margin-left: 10px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
        }
        .modal-buttons .cancel { background: #ddd; color: #333; }
        .modal-buttons .save { background: #E91E63; color: white; }
    `;
    document.head.appendChild(style);

    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',
        google: 'https://www.google.com/favicon.ico'
    };

    // ==================== HELPERS ====================
    function createButton(btnConfig, data) {
        let element;
        if (btnConfig.isF95Forum) {
            element = document.createElement('button');
            element.type = 'button';
            element.addEventListener('mousedown', (e) => {
                if (e.button === 0 || e.button === 1) {
                    e.preventDefault();
                    GM_openInTab(`https://f95zone.to/?query=${encodeURIComponent(btnConfig.query(data))}`, {
                        active: e.button === 0,
                        setParent: true
                    });
                }
            });
        } else {
            element = document.createElement('a');
            element.href = btnConfig.url ? btnConfig.url(data) : '#';
            element.target = '_blank';
        }

        element.className = 'search-btn';
        element.title = btnConfig.text;

        if (btnConfig.icon && icons[btnConfig.icon]) {
            const img = document.createElement('img');
            img.src = icons[btnConfig.icon];
            element.appendChild(img);
        }

        element.appendChild(document.createTextNode(btnConfig.text));
        return element;
    }

    function createGroup(groupConfig, data) {
        const group = document.createElement('div');
        group.className = 'button-group';

        const label = document.createElement('div');
        label.className = 'group-label';
        label.textContent = groupConfig.name;
        group.appendChild(label);

        const grid = document.createElement('div');
        grid.className = 'button-group-grid';

        groupConfig.buttons.forEach(btnConfig => {
            let btn;
            if (btnConfig.action === 'copyPID') {
                btn = createButton({text: btnConfig.text}, data);
                btn.addEventListener('click', (e) => {
                    e.preventDefault();
                    navigator.clipboard.writeText(data.productId).then(() => {
                        const orig = btn.textContent;
                        btn.textContent = '✓ Copied!';
                        setTimeout(() => btn.textContent = orig, 1800);
                    });
                });
            } else if (btnConfig.action === 'copyTitle') {
                btn = createButton({text: btnConfig.text}, data);
                btn.addEventListener('click', (e) => {
                    e.preventDefault();
                    navigator.clipboard.writeText(data.gameTitle).then(() => {
                        const orig = btn.textContent;
                        btn.textContent = '✓ Copied!';
                        setTimeout(() => btn.textContent = orig, 1800);
                    });
                });
            } else {
                btn = createButton(btnConfig, data);
            }
            grid.appendChild(btn);
        });

        group.appendChild(grid);
        return group;
    }

    function getProductData() {
        const titleEl = document.querySelector('h1#work_name') || 
                       document.querySelector('h1[itemprop="name"]') || 
                       document.querySelector('h1');
        let gameTitle = titleEl ? titleEl.innerText.trim() : '';
        gameTitle = gameTitle.replace(/\s*\[.*?\]\s*$/, '').trim();

        const circleSelectors = ['span.maker_name a', 'span[itemprop="brand"] a', 'a[href*="maker_id"]'];
        let circleName = '';
        let circleLink = null;
        for (const sel of circleSelectors) {
            const el = document.querySelector(sel);
            if (el) {
                circleName = el.innerText.trim();
                circleLink = el;
                break;
            }
        }

        const pidMatch = window.location.href.match(/product_id\/(RJ\d+)/i);
        const productId = pidMatch ? pidMatch[1] : '';

        return { gameTitle, circleName, productId, circleLink };
    }

    // ==================== SETTINGS DIALOG ====================
    function showSettings() {
        let modal = document.getElementById('dlsite-settings-modal');
        if (!modal) {
            modal = document.createElement('div');
            modal.id = 'dlsite-settings-modal';
            modal.className = 'settings-modal';
            modal.innerHTML = `
                <div class="modal-content">
                    <h3>⚙️ Script Settings</h3>
                    <label>
                        <input type="checkbox" id="default-open"> 
                        Open "Search Options" dropdown by default
                    </label>
                    <div class="modal-buttons">
                        <button class="cancel" id="modal-cancel">Cancel</button>
                        <button class="save" id="modal-save">Save & Reload</button>
                    </div>
                </div>
            `;
            document.body.appendChild(modal);

            document.getElementById('modal-cancel').onclick = () => modal.style.display = 'none';
            document.getElementById('modal-save').onclick = () => {
                SETTINGS.defaultOpen = document.getElementById('default-open').checked;
                modal.style.display = 'none';
                location.reload();
            };
        }

        document.getElementById('default-open').checked = SETTINGS.defaultOpen;
        modal.style.display = 'flex';
    }

    // ==================== MAIN FUNCTION ====================
    function addSearchButtons() {
        const data = getProductData();
        if (!data.gameTitle || !data.productId) {
            console.warn('[DLsite Search Buttons] Could not extract required data.');
            return;
        }

        const titleContainer = document.querySelector('.base_title_br') || 
                              document.querySelector('h1')?.closest('div, section, header');

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

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

            const toggle = document.createElement('button');
            toggle.className = 'dropdown-toggle';
            toggle.textContent = 'Search Options';

            const gear = document.createElement('button');
            gear.className = 'gear-btn';
            gear.innerHTML = '⚙️';
            gear.title = 'Script Settings';
            gear.onclick = (e) => {
                e.stopImmediatePropagation();
                showSettings();
            };

            header.append(toggle, gear);

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

            if (!SETTINGS.defaultOpen) {
                content.classList.add('hidden');
                toggle.classList.add('collapsed');
            }

            toggle.addEventListener('click', () => {
                content.classList.toggle('hidden');
                toggle.classList.toggle('collapsed');
            });

            const version = document.createElement('div');
            version.className = 'version-label';
            version.textContent = 'v4.12.0';

            dropdownContainer.append(header, content, version);

            CONFIG.groups.forEach(group => {
                content.appendChild(createGroup(group, data));
            });

            titleContainer.parentNode.insertBefore(dropdownContainer, titleContainer.nextSibling);
        }

        // Circle Buttons
        if (data.circleName && data.circleLink) {
            const wrapper = document.createElement('span');
            wrapper.className = 'circle-btn-wrapper';

            const circleConfigs = [
                { text: 'Search Circle on F95Zone', url: (d) => `https://f95zone.to/sam/latest_alpha/#/cat=games/page=1/creator=${encodeURIComponent(d.circleName)}`, icon: 'f95zone' },
                { text: 'Search Circle on F95Zone Forums', query: (d) => d.circleName, isF95Forum: true, icon: 'f95zone' },
                { text: 'Search Circle on Ryuugames', url: (d) => `https://www.ryuugames.com/?s=${encodeURIComponent(d.circleName)}`, icon: 'ryuugames' },
                { text: 'Search Circle on OtomiGames', url: (d) => `https://otomi-games.com/?s=${encodeURIComponent(d.circleName)}`, icon: 'otomi' },
            ];

            circleConfigs.forEach(cfg => wrapper.appendChild(createButton(cfg, data)));

            const makerContainer = data.circleLink.closest('span.maker_name') || data.circleLink.parentNode;
            makerContainer.parentNode.insertBefore(wrapper, makerContainer.nextSibling);
        }
    }

    // ==================== RUN ====================
    if (window.location.href.includes('dlsite.com')) {
        const observer = new MutationObserver((_, obs) => {
            if (document.querySelector('h1')) {
                addSearchButtons();
                obs.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }
})();