F95 Utility buttons

Added a search button for Compressed, Update only, Walkthrough .... with Settings!

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

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!)

// ==UserScript==
// @name         F95 Utility buttons
// @namespace    http://tampermonkey.net/
// @description  Added a search button for Compressed, Update only, Walkthrough .... with Settings!
// @version      3.0
// @author       GGD40727
// @match        https://f95zone.to/threads/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=f95zone.to
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- State & Settings ---
    let openInNewTab = GM_getValue('openInNewTab', true);

    const DEFAULT_BUTTONS = [
        { label: 'Update', query: 'Update', useTitle: true },
        { label: 'New+Compressed', query: 'Compressed', useTitle: true },
        { label: 'Compressed', query: 'Compressed', useTitle: false },
        { label: 'Walkthrough', query: 'Walkthrough', useTitle: true },
        { label: 'Mod', query: 'Mod', useTitle: false },
        { label: 'Cheats', query: 'Cheats', useTitle: true }
    ];

    let customButtons = GM_getValue('f95_custom_buttons', DEFAULT_BUTTONS);

    // Register Tampermonkey Menu Command for Settings
    GM_registerMenuCommand('⚙️ Script Settings', openSettingsModal);

    const SELECTORS = {
        title: 'div.p-title h1.p-title-value',
        searchInput: '.uix_searchInput',
        searchButton: 'button.button--primary.button--icon--search'
    };

    // --- Styling ---
    function injectStyles() {
        if (document.getElementById('f95-styles')) return;
        const style = document.createElement('style');
        style.id = 'f95-styles';
        style.innerHTML = `
            #f95-btn-container {
                position: fixed;
                right: 0px;
                top: 35%;
                z-index: 9998;
                display: flex;
                flex-direction: column;
                gap: 2px;
                align-items: flex-end; /* Aligns buttons to the right edge */
            }
            .f95-quick-search-btn {
                line-height: 30px;
                font-size: 14px;
                cursor: pointer;
                padding: 3px 10px;
                border: 1px solid #444;
                border-radius: 4px 0 0 4px;
                background-color: #2d2d2d;
                color: #efefef;
                text-align: center;
                transition: background-color 0.2s;
                width: max-content; /* Ensures button fits its text exactly */
            }
            .f95-quick-search-btn:hover { background-color: #444; }

            /* Modal Styles */
            #f95-settings-overlay {
                position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
                background: rgba(0,0,0,0.75); z-index: 99999; display: flex;
                align-items: center; justify-content: center;
            }
            #f95-settings-modal {
                background: #1e1e1e; color: #eee; padding: 20px; border-radius: 8px;
                width: 600px; max-width: 90vw; max-height: 90vh; overflow-y: auto;
                border: 1px solid #444; font-family: sans-serif;
                box-shadow: 0 4px 15px rgba(0,0,0,0.5);
            }
            #f95-settings-modal h2 { margin-top: 0; color: #fff; border-bottom: 1px solid #333; padding-bottom: 10px; }
            .f95-global-settings {
                background: #222; padding: 12px; border-radius: 6px;
                margin-bottom: 15px; border: 1px solid #333;
            }
            .f95-settings-row {
                display: flex; gap: 8px; margin-bottom: 10px; align-items: center;
                background: #2a2a2a; padding: 8px; border-radius: 4px;
            }
            .f95-settings-row input[type="text"] {
                padding: 6px; background: #111; color: #fff;
                border: 1px solid #444; border-radius: 3px;
            }
            .f95-input-label { flex: 1; }
            .f95-input-query { flex: 1.2; }
            .f95-toggle-title {
                display: flex; align-items: center; gap: 4px; font-size: 12px;
                background: #111; padding: 6px 8px; border-radius: 3px; border: 1px solid #444;
                cursor: pointer; user-select: none;
            }
            .f95-toggle-title input { cursor: pointer; }
            .f95-modal-btn {
                background: #333; color: white; border: 1px solid #555;
                padding: 6px 10px; cursor: pointer; border-radius: 3px;
            }
            .f95-modal-btn:hover { background: #555; }
            .f95-btn-danger { background: #632323; }
            .f95-btn-danger:hover { background: #8a3131; }
            .f95-btn-primary { background: #23527c; }
            .f95-btn-primary:hover { background: #337ab7; }
            .f95-modal-actions { margin-top: 15px; display: flex; justify-content: space-between; border-top: 1px solid #333; padding-top: 15px;}
        `;
        document.head.appendChild(style);
    }

    // --- Core Logic ---
    function getGameTitle() {
        const titleElement = document.querySelector(SELECTORS.title);
        if (!titleElement) return null;
        const match = titleElement.textContent.match(/\[(.*?)\]/);
        return match ? match[1] : null;
    }

    function getThreadId() {
        const match = window.location.pathname.match(/threads\/[^\/]+\.(\d+)/);
        return match ? match[1] : null;
    }

    function performSearch(query, useTitle, gameTitle) {
        let searchTerm = query.trim();

        // Prepend game title if toggled on and title exists
        if (useTitle && gameTitle) {
            searchTerm = `${gameTitle} ${searchTerm}`.trim();
        }

        if (!searchTerm) return;

        if (openInNewTab) {
            const threadId = getThreadId();
            let searchUrl = `https://f95zone.to/search/1/?q=${encodeURIComponent(searchTerm)}&t=post&o=relevance`;
            if (threadId) {
                searchUrl += `&c%5Bthread%5D=${threadId}`;
            }
            window.open(searchUrl, '_blank');
        } else {
            const searchInput = document.querySelector(SELECTORS.searchInput);
            const searchButton = document.querySelector(SELECTORS.searchButton);
            if (!searchInput || !searchButton) return;

            searchInput.value = searchTerm;
            searchInput.dispatchEvent(new Event('input', { bubbles: true }));
            searchButton.click();
        }
    }

    function initButtons() {
        // Remove existing container if re-rendering
        const existing = document.getElementById('f95-btn-container');
        if (existing) existing.remove();

        const container = document.createElement('div');
        container.id = 'f95-btn-container';

        const gameTitle = getGameTitle();

        customButtons.forEach(config => {
            const button = document.createElement('input');
            button.type = 'button';
            button.value = config.label;
            button.className = 'f95-quick-search-btn';

            button.addEventListener('click', () => {
                performSearch(config.query, config.useTitle, gameTitle);
            });
            container.appendChild(button);
        });

        document.body.appendChild(container);
    }

    // --- Settings UI ---
    function openSettingsModal() {
        if (document.getElementById('f95-settings-overlay')) return;

        const overlay = document.createElement('div');
        overlay.id = 'f95-settings-overlay';

        const modal = document.createElement('div');
        modal.id = 'f95-settings-modal';

        modal.innerHTML = `
            <h2>Script Settings</h2>

            <!-- Global Settings Block -->
            <div class="f95-global-settings">
                <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px;">
                    <input type="checkbox" id="f95-setting-newtab" ${openInNewTab ? 'checked' : ''} style="cursor: pointer; width: 16px; height: 16px;">
                    <strong>Open searches in a New Tab</strong>
                </label>
                <p style="font-size: 12px; color: #aaa; margin: 5px 0 0 24px;">
                    If unchecked, searches will fill the forum's search bar on the current page instead.
                </p>
            </div>

            <p style="font-size: 13px; color: #bbb; margin-bottom: 15px;">
                Check <b>+Title</b> to automatically include the game's title before your search term.<br>
                <em>Example: Toggling +Title on "Update" searches for "GameName Update".</em>
            </p>

            <!-- Buttons List -->
            <div id="f95-settings-list"></div>

            <div class="f95-modal-actions">
                <button id="f95-add-row" class="f95-modal-btn">+ Add New Button</button>
                <div>
                    <button id="f95-cancel" class="f95-modal-btn">Cancel</button>
                    <button id="f95-save" class="f95-modal-btn f95-btn-primary" style="margin-left: 8px;">Save & Apply</button>
                </div>
            </div>
        `;

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        const listContainer = document.getElementById('f95-settings-list');

        // Render rows
        customButtons.forEach(btn => addRowToSettings(listContainer, btn.label, btn.query, btn.useTitle));

        // Event Listeners
        document.getElementById('f95-add-row').addEventListener('click', () => addRowToSettings(listContainer, 'New Button', 'Term', true));

        document.getElementById('f95-cancel').addEventListener('click', () => overlay.remove());

        document.getElementById('f95-save').addEventListener('click', () => {
            saveSettings(listContainer);
            overlay.remove();
            initButtons(); // Refresh buttons on page dynamically
        });
    }

    function addRowToSettings(container, labelText = '', queryText = '', useTitle = true) {
        const row = document.createElement('div');
        row.className = 'f95-settings-row';
        row.innerHTML = `
            <input type="text" class="f95-input-label" placeholder="Button Name" value="${labelText}">
            <input type="text" class="f95-input-query" placeholder="Search Term" value="${queryText}">
            <label class="f95-toggle-title" title="Append the game's title to this search?">
                <input type="checkbox" class="f95-input-title" ${useTitle ? 'checked' : ''}> +Title
            </label>
            <button class="f95-modal-btn btn-up" title="Move Up">▲</button>
            <button class="f95-modal-btn btn-down" title="Move Down">▼</button>
            <button class="f95-modal-btn f95-btn-danger btn-del" title="Delete">✖</button>
        `;

        // Movement logic
        row.querySelector('.btn-up').addEventListener('click', () => {
            if (row.previousElementSibling) row.parentNode.insertBefore(row, row.previousElementSibling);
        });
        row.querySelector('.btn-down').addEventListener('click', () => {
            if (row.nextElementSibling) row.parentNode.insertBefore(row.nextElementSibling, row);
        });
        row.querySelector('.btn-del').addEventListener('click', () => row.remove());

        container.appendChild(row);
    }

    function saveSettings(container) {
        // Save Global Settings
        const newTabCheckbox = document.getElementById('f95-setting-newtab');
        if (newTabCheckbox) {
            openInNewTab = newTabCheckbox.checked;
            GM_setValue('openInNewTab', openInNewTab);
        }

        // Save Button Array
        const rows = container.querySelectorAll('.f95-settings-row');
        const newData = [];

        rows.forEach(row => {
            const label = row.querySelector('.f95-input-label').value.trim();
            const query = row.querySelector('.f95-input-query').value.trim();
            const useTitle = row.querySelector('.f95-input-title').checked;

            if (label && query) {
                newData.push({ label, query, useTitle });
            }
        });

        if (newData.length > 0) {
            customButtons = newData;
            GM_setValue('f95_custom_buttons', customButtons);
        } else {
            alert('Settings not saved: You must have at least one button.');
        }
    }

    // --- Initialization ---
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            injectStyles();
            initButtons();
        });
    } else {
        injectStyles();
        initButtons();
    }
})();