Instant Search Switcher

Changes search engine from one site to another without deleting search query. Draggable UI.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Instant Search Switcher
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Changes search engine from one site to another without deleting search query. Draggable UI.
// @author       Faisal Bhuiyan
// @match        *://*.bing.com/*search*
// @match        *://*.bing.com/chat*
// @match        https://www.google.com/search*
// @match        https://yandex.com/*search*
// @match        https://search.brave.com/*search*
// @match        https://searx.fmhy.net/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
    'use strict';

    const searchEngines = [
        {
            name: 'Google',
            url: 'https://www.google.com/search',
            param: '?q'
        },
        {
            name: 'Bing',
            url: 'https://www.bing.com/search',
            param: '?q'
        },
        {
            name: 'Copilot',
            url: 'https://www.bing.com/chat',
            param: '?q',
            additionalParams: '&sendquery=1&FORM=SCCODX'
        },
        {
            name: 'Brave',
            url: 'https://search.brave.com/search',
            param: '?q'
        },
        {
            name: 'Yandex',
            url: 'https://yandex.com/search',
            param: '?text'
        },
        {
            name: 'Perplexity',
            url: 'https://www.perplexity.ai/search',
            param: '?q'
        },
        {
            name: 'G-AI Mode',
            url: 'https://www.google.com/search?udm=50&aep=11&hl=en&',
            param: 'q'
        },
        {
            name: 'Grok',
            url: 'https://grok.com/',
            param: '?q'
        },
        {
            name: 'ChatGPT',
            url: 'https://chatgpt.com/',
            param: '?q'
        }
    ];

    // Default position - define this as a constant
    const defaultPosition = { top: '20px', right: '5rem', left: 'auto' };

    // Try to get saved position or use default
    let position;
    try {
        const savedPosition = GM_getValue('switcherPosition');
        // Only use saved position if it exists and has been manually set
        position = (savedPosition && GM_getValue('positionModified')) ? savedPosition : defaultPosition;
    } catch (e) {
        // If GM_getValue is not available, use defaultPosition
        position = defaultPosition;
    }

    // Create container for draggable functionality
    const container = document.createElement('div');
    container.style.position = 'fixed';
    container.style.top = position.top;
    container.style.right = position.right;
    container.style.left = position.left;
    container.style.zIndex = '9999';
    container.style.cursor = 'move';
    container.style.userSelect = 'none';
    container.style.touchAction = 'none';
    container.style.display = 'flex';
    container.style.flexDirection = 'row';
    container.style.alignItems = 'center';
    container.style.padding = '4px';
    container.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
    container.style.borderRadius = '4px';
    container.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';

    // Create a handle for dragging
    const dragHandle = document.createElement('div');
    dragHandle.innerHTML = '⋮⋮'; // vertical dots as drag indicator
    dragHandle.style.marginRight = '5px';
    dragHandle.style.cursor = 'move';
    dragHandle.style.color = '#555';
    dragHandle.style.fontSize = '16px';
    dragHandle.style.paddingRight = '5px';
    dragHandle.title = 'Drag to move';

    // Create reset button
    const resetButton = document.createElement('div');
    resetButton.innerHTML = '↺'; // reset icon
    resetButton.style.marginLeft = '5px';
    resetButton.style.cursor = 'pointer';
    resetButton.style.color = '#555';
    resetButton.style.fontSize = '16px';
    resetButton.style.padding = '0 5px';
    resetButton.title = 'Reset position';
    resetButton.style.display = 'flex';
    resetButton.style.alignItems = 'center';
    resetButton.style.justifyContent = 'center';

    // Create the floating select box
    const selectBox = document.createElement('select');
    selectBox.style.fontSize = '16px';
    selectBox.style.padding = '5px';
    selectBox.style.borderRadius = '4px';
    selectBox.style.backgroundColor = '#fff';
    selectBox.style.color = '#000'; // Force black text
    selectBox.style.border = '1px solid #ccc';
    selectBox.style.outline = 'none';
    selectBox.style.cursor = 'pointer';

    // Add CSS to ensure text remains visible in both light and dark themes
    const style = document.createElement('style');
    style.textContent = `
        .search-switcher-select {
            color: #000 !important;
            -webkit-text-fill-color: #000 !important;
        }
        .search-switcher-select option {
            background-color: #fff !important;
            color: #000 !important;
            -webkit-text-fill-color: #000 !important;
        }
    `;
    document.head.appendChild(style);

    selectBox.className = 'search-switcher-select';

    // Add an empty option as the first element
    const emptyOption = document.createElement('option');
    emptyOption.value = '';
    emptyOption.textContent = 'Select';
    emptyOption.disabled = true;
    emptyOption.selected = true;
    selectBox.appendChild(emptyOption);

    // Add search engines to the select box
    searchEngines.forEach(engine => {
        const option = document.createElement('option');
        option.value = engine.url;
        option.textContent = engine.name;
        selectBox.appendChild(option);
    });

    // Add elements to container
    container.appendChild(dragHandle);
    container.appendChild(selectBox);
    container.appendChild(resetButton);

    // Append the container to the body
    document.body.appendChild(container);

    // Reset button functionality
    resetButton.addEventListener('click', (e) => {
        e.stopPropagation();

        // Apply default position
        container.style.top = defaultPosition.top;
        container.style.right = defaultPosition.right;
        container.style.left = defaultPosition.left;

        // Save the default settings
        try {
            GM_setValue('switcherPosition', defaultPosition);
            GM_setValue('positionModified', false);
        } catch (e) {
            // Silently fail if GM_setValue is not available
        }
    });

    // Detect changes to the select box
    selectBox.addEventListener('change', () => {
        const selectedEngine = searchEngines[selectBox.selectedIndex - 1];
        // Try to get query parameter from either 'q' or 'text' depending on current search engine
        const currentQuery = new URLSearchParams(window.location.search).get('q') ||
            new URLSearchParams(window.location.search).get('text');

        if (currentQuery && selectedEngine) {
            const additionalParams = selectedEngine.additionalParams || '';
            window.location.href = `${selectedEngine.url}${selectedEngine.param}=${encodeURIComponent(currentQuery)}${additionalParams}`;
        }
    });

    // Make the container draggable
    let isDragging = false;
    let dragOffsetX, dragOffsetY;

    // Function to start dragging
    function startDrag(clientX, clientY) {
        isDragging = true;

        // Calculate the offset of the mouse within the container
        const rect = container.getBoundingClientRect();
        dragOffsetX = clientX - rect.left;
        dragOffsetY = clientY - rect.top;

        // Prevent text selection during drag
        document.body.style.userSelect = 'none';
    }

    // Function to handle dragging
    function drag(clientX, clientY) {
        if (!isDragging) return;

        // Calculate new position based on mouse/touch position and offset
        const newLeft = clientX - dragOffsetX;
        const newTop = clientY - dragOffsetY;

        // Ensure the element stays within viewport bounds
        const maxX = window.innerWidth - container.offsetWidth;
        const maxY = window.innerHeight - container.offsetHeight;

        const boundedLeft = Math.max(0, Math.min(newLeft, maxX));
        const boundedTop = Math.max(0, Math.min(newTop, maxY));

        // Set new position
        container.style.left = boundedLeft + 'px';
        container.style.top = boundedTop + 'px';
        container.style.right = 'auto'; // Clear right positioning to avoid conflicts
    }

    // Function to end dragging
    function endDrag() {
        if (isDragging) {
            isDragging = false;
            document.body.style.userSelect = '';

            // Save the new position and mark as modified
            try {
                GM_setValue('switcherPosition', {
                    top: container.style.top,
                    right: 'auto',
                    left: container.style.left
                });
                GM_setValue('positionModified', true);
            } catch (e) {
                // Silently fail if GM_setValue is not available
            }
        }
    }

    // Mouse event listeners
    dragHandle.addEventListener('mousedown', (e) => {
        e.preventDefault();
        startDrag(e.clientX, e.clientY);
    });

    document.addEventListener('mousemove', (e) => {
        drag(e.clientX, e.clientY);
    });

    document.addEventListener('mouseup', endDrag);

    // Touch event listeners
    dragHandle.addEventListener('touchstart', (e) => {
        if (e.touches.length === 1) {
            e.preventDefault();
            const touch = e.touches[0];
            startDrag(touch.clientX, touch.clientY);
        }
    });

    document.addEventListener('touchmove', (e) => {
        if (isDragging && e.touches.length === 1) {
            e.preventDefault();
            const touch = e.touches[0];
            drag(touch.clientX, touch.clientY);
        }
    });

    document.addEventListener('touchend', endDrag);
    document.addEventListener('touchcancel', endDrag);

    // Prevent selectbox from triggering drag
    selectBox.addEventListener('mousedown', (e) => {
        e.stopPropagation();
    });

    selectBox.addEventListener('touchstart', (e) => {
        e.stopPropagation();
    });
})();