Simple Search Engines

简洁的适配国内地区的搜索切换脚本.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Simple Search Engines
// @namespace    https://www.iklfy.com
// @version      0.2.5
// @description  简洁的适配国内地区的搜索切换脚本.
// @author       Ancient
// @match        *://cn.bing.com/search*
// @match        *://www.baidu.com/s*
// @match        *://www.yandex.com/search*
// @match        *://www.sogou.com/web*
// @match        *://www.zhihu.com/search*
// @match        *://so.csdn.net/so/search*
// @grant        none
// @license      MIT
// ==/UserScript==
/**
 * 搜索引擎配置管理器类,用于根据浏览器类型动态调整搜索引擎配置,
 * 并在页面上创建一个搜索引擎切换容器,提升用户体验。
 */
class SearchEngineManager
{
    urlMapsConfig = {};

    /**
     * 构造函数,初始化搜索引擎配置。
     * @param urlMapsConfig
     */
    constructor(urlMapsConfig)
    {
        this.urlMapsConfig = urlMapsConfig;
    }

    /**
     * 从URL查询字符串中提取指定变量的值。
     *
     * @param {string} variable - 要提取的查询参数名。
     * @return {string|null} - 查询参数的值,若不存在则返回null。
     */
    getQueryVariable(variable)
    {
        let query = window.location.search.substring(1);
        if (!query) {
            return null;
        }
        const pairs = query.split('&');
        for (const pair of pairs) {
            const [key, value] = pair.split('=');
            // 对键和值都进行解码,保持一致性。
            const decodedKey   = decodeURIComponent(key);
            const decodedValue = decodeURIComponent(value);
            if (decodedKey === variable) {
                return decodedValue;
            }
        }
        return null;
    }

    /**
     * 根据当前URL获取关键词。
     *
     * @return {string} - 当前搜索的关键词。
     */
    getKeywords()
    {
        for (const item of this.urlMapsConfig) {
            if (item.testUrl.test(window.location.href)) {
                return this.getQueryVariable(item.keyName);
            }
        }
        return '';
    }

    /**
     * 检测是否为Firefox浏览器并相应调整配置。
     */
    checkAndAdjustForFirefox()
    {
        // 使用功能检测代替User-Agent检测
        if ('MozWebSocket' in window) { // 假设Firefox特有的API是MozWebSocket
            console.info('[ Firefox ] 🚀');
            if (this.urlMapsConfig.length > 0) {
                this.urlMapsConfig[0].searchUrl = 'https://www.baidu.com/baidu?wd=';
                this.urlMapsConfig[0].testUrl   = /https:\/\/www\.baidu\.com\/baidu.*/;
            }
        }
    }

    /**
     * 添加样式
     */
    addStyleToHead()
    {
        // 检查是否已存在该样式,如果不存在再进行添加
        if (!document.getElementById('search-container-style')) {
            const style       = document.createElement('style');
            style.id          = 'search-container-style';
            // 将样式内容赋值给style节点的textContent,代替innerHTML,提高安全性
            style.textContent = `
                #search-container{width:80px;background-color:#f1f6f9d9;z-index:99999;position:fixed;display:flex;align-items:center;justify-content:center;padding:10px 0;top:150px;left:50px;border-radius:10px}
                #search-container ul{padding:initial;margin:initial}
                #search-container li.title{font-weight:700;user-select:none}
                #search-container li{display:block;margin:8px 0;text-align:center}
                #search-container a{color:#24578f;display:block}
            `;
            // 将style节点添加到head中
            document.getElementsByTagName('head')[0].appendChild(style);
        }
    }

    /**
     * 添加容器
     */
    createSearchContainer()
    {
        this.checkAndAdjustForFirefox();
        // div#search-container
        const container = document.createElement('div');
        container.id    = 'search-container';
        document.body.insertBefore(container, document.body.firstChild);
        //document.body.insertAdjacentElement('afterbegin', container);
        // ul
        const ul = document.createElement('ul');
        container.appendChild(ul);
        // li.title
        let titleLi         = document.createElement('li');
        titleLi.textContent = 'Engine';
        titleLi.className   = 'title';
        ul.appendChild(titleLi);
        // 优化DOM操作
        const fragment = document.createDocumentFragment();
        // 搜索列表
        this.urlMapsConfig.forEach(item =>
        {
            // li > a
            const li      = document.createElement('li');
            const a       = document.createElement('a');
            a.textContent = item.name;
            a.className   = 'search-engine-a';
            a.href        = `${item.searchUrl}${this.getKeywords()}`;
            // ul > li > a
            li.appendChild(a);
            fragment.appendChild(li);
        });
        ul.appendChild(fragment);
    }

    /**
     * 初始化并运行搜索容器的创建流程。
     */
    initialize()
    {
        this.addStyleToHead();
        this.createSearchContainer();
    }
}

(function ()
{
    'use strict';
    /**
     * 用于配置URL映射的对象。每个映射包含名称、键名、搜索URL字符串和测试URL的正则表达式。
     *
     * @typedef {Object} urlMapsConfig
     * @property {string} name - 映射的名称。不能为空。
     * @property {string} keyName - 映射的键名。不能为空。
     * @property {string} searchUrl - 用于搜索的URL字符串。必须是合法的URL格式。
     * @property {RegExp} testUrl - 用于测试URL是否匹配的正则表达式对象。必须是有效的正则表达式。
     */
    const urlMapsConfig = [
        {
            name: 'Bing', searchUrl: 'https://cn.bing.com/search?q=', keyName: 'q', testUrl: /https:\/\/cn.bing.com\/search.*/
        }, {
            name: '百度', searchUrl: 'https://www.baidu.com/s?wd=', keyName: 'wd', testUrl: /https:\/\/www.baidu.com\/s.*/
        }, {
            name: 'Yandex', searchUrl: 'https://www.yandex.com/search/?text=', keyName: 'text', testUrl: /https:\/\/www.yandex.com\/search.*/
        }, {
            name: '搜狗', searchUrl: 'https://www.sogou.com/web?query=', keyName: 'query', testUrl: /https:\/\/www.sogou.com\/web.*/
        }, {
            name: '知乎', searchUrl: 'https://www.zhihu.com/search?q=', keyName: 'q', testUrl: /https:\/\/www.zhihu.com\/search.*/
        }, {
            name: 'CSDN', searchUrl: 'https://so.csdn.net/so/search?q=', keyName: 'q', testUrl: /https:\/\/so.csdn.net\/so\/search.*/
        }
    ];

    /**
     * 初始化管理器
     * @returns {Promise<void>}
     */
    async function initializeManager()
    {
        const manager = new SearchEngineManager(urlMapsConfig);
        try {
            // 使用async-await优化异步逻辑
            await manager.initialize();
            console.log('Manager initialized successfully.');
        } catch (error) {
            console.error('Error initializing manager:', error);
        }
    }

    /**
     * 确保只添加一个事件监听器,避免内存泄露
     */
    window.addEventListener('load', () =>
    {
        initializeManager().catch(error =>
        {
            console.error('Error during manager initialization:', error);
        });
    }, {once: true});
})();