搜索引擎切换器 / Search Engine Switcher

🚀 一键切换多个搜索引擎!支持Google、Bing、百度、ChatGPT、Perplexity等13大搜索平台。可拖拽、自动隐藏,提升您的搜索效率。适配暗黑模式,让搜索更智能、更便捷!

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.

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

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         搜索引擎切换器 / Search Engine Switcher
// @namespace    http://tampermonkey.net/
// @version      0.2.1
// @description  🚀 一键切换多个搜索引擎!支持Google、Bing、百度、ChatGPT、Perplexity等13大搜索平台。可拖拽、自动隐藏,提升您的搜索效率。适配暗黑模式,让搜索更智能、更便捷!
// @author       WUJI (微信: wujiai666)
// @match        *://www.google.com*/search*
// @match        *://www.bing.com/search*
// @match        *://cn.bing.com/search*
// @match        *://www.baidu.com/s*
// @match        *://www.baidu.com/baidu*
// @match        *://chatgpt.com/*
// @match        *://metaso.cn/*
// @match        *://weixin.sogou.com/weixin*
// @match        *://search.bilibili.com/all*
// @match        *://www.youtube.com/results*
// @match        *://m.youtube.com/results*
// @match        *://www.zhihu.com/search*
// @match        *://github.com/search*
// @match        *://www.xiaohongshu.com/explore*
// @match        *://www.douyin.com/search/*
// @match        *://www.perplexity.ai/*
// @grant        unsafeWindow
// @grant        window.onload
// @run-at       document-body
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const urlMapping = [
        { name: "Google", searchUrl: "https://www.google.com/search?q=", keyName: "q", testUrl: /https:\/\/www\.google\.(com|com\.hk)\/search.*/ },
        { name: "Bing", searchUrl: "https://www.bing.com/search?q=", keyName: "q", testUrl: /https:\/\/(www|cn)\.bing\.com\/search.*/ },
        { name: "百度", searchUrl: "https://www.baidu.com/s?wd=", keyName: "wd", testUrl: /https:\/\/www\.baidu\.com\/(s|baidu).*/ },
        { name: "ChatGPT", searchUrl: "https://chatgpt.com/?hints=search&q=", keyName: "q", testUrl: /https:\/\/chatgpt\.com\/.*/ },
        { name: "秘塔", searchUrl: "https://metaso.cn/?q=", keyName: "q", testUrl: /https:\/\/metaso\.cn\/.*/ },
        { name: "微信", searchUrl: "https://weixin.sogou.com/weixin?type=2&s_from=input&query=", keyName: "query", testUrl: /https:\/\/weixin\.sogou\.com\/weixin.*/ },
        { name: "哔站", searchUrl: "https://search.bilibili.com/all?keyword=", keyName: "keyword", testUrl: /https:\/\/search\.bilibili\.com\/all.*/ },
        { name: "油管", searchUrl: "https://www.youtube.com/results?search_query=", keyName: "search_query", testUrl: /https:\/\/(www|m)\.youtube\.com\/results.*/ },
        { name: "知乎", searchUrl: "https://www.zhihu.com/search?q=", keyName: "q", testUrl: /https:\/\/www\.zhihu\.com\/search.*/ },
        { name: "GitHub", searchUrl: "https://github.com/search?q=", keyName: "q", testUrl: /https:\/\/github\.com\/search.*/ },
        { name: "小红书", searchUrl: "https://www.xiaohongshu.com/explore?q=", keyName: "q", testUrl: /https:\/\/www\.xiaohongshu\.com\/explore.*/ },
        { name: "抖音", searchUrl: "https://www.douyin.com/search/", keyName: "q", testUrl: /https:\/\/www\.douyin\.com\/search\/.*/ },
        { name: "Perplexity", searchUrl: "https://www.perplexity.ai/?q=", keyName: "q", testUrl: /https:\/\/www\.perplexity\.ai\/.*/ },
    ];

    const ICON_SIZE = '32px';
    const LIST_WIDTH = '100px';
    const FONT_SIZE = '14px';
    const AUTO_HIDE_DELAY = 5000; // 5 seconds

    function getQueryVariable(variable) {
        const query = window.location.search.substring(1);
        const vars = query.split('&');
        for (let i = 0; i < vars.length; i++) {
            const pair = vars[i].split('=');
            if (decodeURIComponent(pair[0]) === variable) {
                return decodeURIComponent(pair[1]);
            }
        }
        if (variable === "q" && window.location.pathname.startsWith("/search/")) {
            return decodeURIComponent(window.location.pathname.replace("/search/", ""));
        }
        return "";
    }

    function getKeywords() {
        for (const item of urlMapping) {
            if (item.testUrl.test(window.location.href)) {
                return getQueryVariable(item.keyName);
            }
        }
        return "";
    }

    function isDarkMode() {
        return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    }

    function createStyle() {
        const style = document.createElement('style');
        style.textContent = `
            #search-app-box {
                position: fixed;
                top: 100px;
                left: 0;
                width: ${ICON_SIZE};
                height: ${ICON_SIZE};
                background-color: transparent;
                z-index: 2147483647;
                cursor: move;
                font-size: ${FONT_SIZE};
                transition: left 0.3s ease-in-out;
            }
            #search-app-icon {
                width: 100%;
                height: 100%;
                display: flex;
                justify-content: center;
                align-items: center;
                font-size: ${ICON_SIZE};
                user-select: none;
                background-color: rgba(255, 255, 255, 0.7);
                border-radius: 0 50% 50% 0;
            }
            #search-engine-list {
                position: absolute;
                top: 0;
                left: ${ICON_SIZE};
                width: ${LIST_WIDTH};
                max-height: 70vh;
                overflow-y: auto;
                background-color: rgba(255, 255, 255, 0.9);
                border-radius: 8px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                opacity: 0;
                visibility: hidden;
                transform: translateX(-10px);
                transition: opacity 0.3s, visibility 0.3s, transform 0.3s;
            }
            #search-engine-list a {
                display: block;
                padding: 8px 12px;
                color: #333;
                text-decoration: none;
                transition: background-color 0.3s;
            }
            #search-engine-list a:hover {
                background-color: rgba(0, 0, 0, 0.1);
            }
            .dark-mode #search-app-icon {
                background-color: rgba(50, 50, 50, 0.7);
            }
            .dark-mode #search-engine-list {
                background-color: rgba(50, 50, 50, 0.9);
            }
            .dark-mode #search-engine-list a {
                color: #fff;
            }
            .dark-mode #search-engine-list a:hover {
                background-color: rgba(255, 255, 255, 0.1);
            }
            #search-app-box.hidden {
                left: -8px;
            }
            #search-app-box.dragging {
                transition: none;
            }
        `;
        document.head.appendChild(style);
    }

    function createSearchBox() {
        const div = document.createElement('div');
        div.id = 'search-app-box';

        const icon = document.createElement('div');
        icon.id = 'search-app-icon';
        icon.innerText = '🔍';
        div.appendChild(icon);

        const listContainer = document.createElement('div');
        listContainer.id = 'search-engine-list';

        for (const item of urlMapping) {
            const a = document.createElement('a');
            a.href = item.searchUrl + encodeURIComponent(getKeywords());
            a.innerText = item.name;
            a.addEventListener('click', (e) => {
                e.preventDefault();
                window.location.href = a.href;
            });
            listContainer.appendChild(a);
        }

        div.appendChild(listContainer);
        document.body.appendChild(div);

        let hideTimeout;

        function showSearchBox() {
            div.classList.remove('hidden');
            clearTimeout(hideTimeout);
        }

        function hideSearchBox() {
            div.classList.add('hidden');
        }

        function resetHideTimer() {
            clearTimeout(hideTimeout);
            hideTimeout = setTimeout(hideSearchBox, AUTO_HIDE_DELAY);
        }

        div.addEventListener('mouseenter', () => {
            showSearchBox();
            listContainer.style.opacity = '1';
            listContainer.style.visibility = 'visible';
            listContainer.style.transform = 'translateX(0)';
        });

        div.addEventListener('mouseleave', () => {
            listContainer.style.opacity = '0';
            listContainer.style.visibility = 'hidden';
            listContainer.style.transform = 'translateX(-10px)';
            resetHideTimer();
        });

        // 拖拽功能
        let isDragging = false;
        let startX, startY, startLeft, startTop;

        div.addEventListener('mousedown', (e) => {
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            startLeft = div.offsetLeft;
            startTop = div.offsetTop;
            showSearchBox();
            e.preventDefault();
            div.classList.add('dragging');
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            div.style.left = `${startLeft + dx}px`;
            div.style.top = `${startTop + dy}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            resetHideTimer();
            div.classList.remove('dragging');
        });

        // 初始化隐藏定时器
        resetHideTimer();
    }

    function updateTheme() {
        document.body.classList.toggle('dark-mode', isDarkMode());
    }

    function init() {
        createStyle();
        createSearchBox();
        updateTheme();

        // 监听主题变化
        window.matchMedia('(prefers-color-scheme: dark)').addListener(updateTheme);
    }

    // 使用 MutationObserver 来确保脚本在动态加载的页面上也能正常工作
    const observer = new MutationObserver((mutations, obs) => {
        const body = document.querySelector('body');
        if (body) {
            init();
            obs.disconnect();
        }
    });

    observer.observe(document.documentElement, {
        childList: true,
        subtree: true
    });
})();