Greasy Fork

Greasy Fork is available in English.

搜索引擎切换器 / Search Engine Switcher

在搜索引擎页面显示一个可拖拽的快速切换列表,支持更多搜索引擎,提高搜索效率。

当前为 2024-10-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         搜索引擎切换器 / Search Engine Switcher
// @namespace    http://tampermonkey.net/
// @version      0.1.0
// @description  在搜索引擎页面显示一个可拖拽的快速切换列表,支持更多搜索引擎,提高搜索效率。
// @author       WUJI
// @match        *://www.google.com/search*
// @match        *://www.google.com.hk/search*
// @match        *://www.bing.com/search*
// @match        *://cn.bing.com/search*
// @match        *://www.baidu.com/s*
// @match        *://www.baidu.com/baidu*
// @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        *://metaso.cn/*
// @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: "微信", 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: "秘塔", searchUrl: "https://metaso.cn/?q=", keyName: "q", testUrl: /https:\/\/metaso\.cn\/.*/ },
    ];

    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
    });
})();