搜索引擎切换器 / Search Engine Switcher (右侧固定版)

在右侧固定显示多个搜索引擎选项,支持一键切换Google、Bing、百度、ChatGPT等11大搜索平台。新标签页打开。

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.5.0
// @description   在右侧固定显示多个搜索引擎选项,支持一键切换Google、Bing、百度、ChatGPT等11大搜索平台。新标签页打开。
// @author       PengzhanYin [Modified version]
// @match        *://www.google.com.hk*/search*
// @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        *://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/*
// @grant        GM_addStyle
// @run-at       document-start
// @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://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\/.*/ },
    ];

    const FONT_SIZE = '14px';
    const SIDEBAR_WIDTH = '110px';

    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-sidebar {
                position: fixed;
                top: 100px;
                right: 0;
                width: ${SIDEBAR_WIDTH};
                max-height: 70vh;
                overflow-y: auto;
                background-color: rgba(255, 255, 255, 0.9);
                border-radius: 8px 0 0 8px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                z-index: 2147483647;
                font-size: ${FONT_SIZE};
                padding: 8px 0;
                transition: all 0.3s ease;
            }
            #search-sidebar.collapsed {
                right: -${SIDEBAR_WIDTH};
            }
            #search-sidebar-toggle {
                position: absolute;
                left: -20px;
                top: 0;
                width: 20px;
                height: 50px;
                background-color: rgba(255, 255, 255, 0.9);
                border-radius: 8px 0 0 8px;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.1);
                user-select: none;
                z-index: 2147483646;
            }
            #search-sidebar-toggle:after {
                content: "<";
                font-size: 12px;
            }
            #search-sidebar.collapsed #search-sidebar-toggle:after {
                content: ">";
            }
            #search-sidebar-title {
                font-weight: bold;
                text-align: center;
                padding: 5px 0;
                margin-bottom: 5px;
                border-bottom: 1px solid #ddd;
                user-select: none;
            }
            #search-sidebar-content {
                transition: opacity 0.3s ease;
            }
            #search-sidebar.collapsed #search-sidebar-content {
                opacity: 0;
                pointer-events: none;
            }
            #search-sidebar a {
                display: block;
                padding: 8px 12px;
                color: #333;
                text-decoration: none;
                transition: background-color 0.3s;
                cursor: pointer;
            }
            #search-sidebar a:hover {
                background-color: rgba(0, 0, 0, 0.1);
            }
            #search-sidebar a.current {
                background-color: rgba(0, 0, 0, 0.05);
                font-weight: bold;
            }
            .dark-mode #search-sidebar, .dark-mode #search-sidebar-toggle {
                background-color: rgba(50, 50, 50, 0.9);
            }
            .dark-mode #search-sidebar-title {
                border-bottom: 1px solid #444;
            }
            .dark-mode #search-sidebar a {
                color: #fff;
            }
            .dark-mode #search-sidebar a:hover {
                background-color: rgba(255, 255, 255, 0.1);
            }
            .dark-mode #search-sidebar a.current {
                background-color: rgba(255, 255, 255, 0.05);
            }
        `;
        document.head.appendChild(style);
    }

    function createSearchSidebar() {
        const sidebar = document.createElement('div');
        sidebar.id = 'search-sidebar';

        // 读取本地存储的折叠状态
        const isCollapsed = localStorage.getItem('search-sidebar-collapsed') === 'true';
        if (isCollapsed) {
            sidebar.classList.add('collapsed');
        }

        // 创建折叠/展开按钮
        const toggle = document.createElement('div');
        toggle.id = 'search-sidebar-toggle';
        toggle.addEventListener('click', function(e) {
            e.stopPropagation();
            sidebar.classList.toggle('collapsed');
            // 保存折叠状态到本地存储
            localStorage.setItem('search-sidebar-collapsed', sidebar.classList.contains('collapsed'));
        });
        sidebar.appendChild(toggle);

        const title = document.createElement('div');
        title.id = 'search-sidebar-title';
        title.innerText = '搜索引擎';
        sidebar.appendChild(title);

        // 创建内容容器
        const content = document.createElement('div');
        content.id = 'search-sidebar-content';
        sidebar.appendChild(content);

        // 确定当前搜索引擎
        let currentEngine = '';
        for (const item of urlMapping) {
            if (item.testUrl.test(window.location.href)) {
                currentEngine = item.name;
                break;
            }
        }

        const keywords = getKeywords();

        for (const item of urlMapping) {
            const a = document.createElement('a');

            // 使用urlBuilder函数(如果存在)或直接连接searchUrl和关键词
            if (item.urlBuilder) {
                a.href = item.urlBuilder(keywords);
            } else {
                a.href = item.searchUrl + encodeURIComponent(keywords);
            }

            a.innerText = item.name;
            a.target = '_blank'; // 在新标签页打开

            // 标记当前搜索引擎
            if (item.name === currentEngine) {
                a.classList.add('current');
            }

            content.appendChild(a);
        }

        document.body.appendChild(sidebar);
    }

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

    function init() {
        // 确保只初始化一次
        if (document.getElementById('search-sidebar')) {
            return;
        }

        createStyle();
        createSearchSidebar();
        updateTheme();

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

        console.log("[搜索引擎切换器] 初始化完成");
    }

    // 谷歌搜索页面可能是动态加载的,所以需要使用多种方法确保脚本运行

    // 方法1: DOMContentLoaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            setTimeout(init, 500); // 延迟确保DOM完全准备好
        });
    }

    // 方法2: window.onload
    window.addEventListener('load', function() {
        setTimeout(init, 1000); // 延迟更长时间以确保所有动态内容加载完成
    });

    // 方法3: 立即执行 + 定时检查
    setTimeout(init, 500);

    // 方法4: 定期检查是否需要重新初始化
    setInterval(function() {
        if (!document.getElementById('search-sidebar') && document.body) {
            console.log("[搜索引擎切换器] 重新初始化");
            init();
        }
    }, 2000);

    // 方法5: MutationObserver 监视DOM变化
    const observer = new MutationObserver((mutations, obs) => {
        if (document.body && !document.getElementById('search-sidebar')) {
            console.log("[搜索引擎切换器] 通过MutationObserver初始化");
            setTimeout(init, 500); // 延迟初始化
        }
    });

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

    // 方法6: 监听URL变化 (对Google SPA特别有用)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            console.log("[搜索引擎切换器] URL变化,重新检查初始化");
            if (!document.getElementById('search-sidebar') && document.body) {
                setTimeout(init, 500);
            }
        }
    }).observe(document, {subtree: true, childList: true});
})();