AlwaysBing

永遠使用 Bing 搜索

// ==UserScript==
// @name               AlwaysBing
// @namespace          AlwaysBing
// @version            1.0.0
// @description        Always search with Bing
// @description:en     Always search with Bing
// @description:zh-CN  永远使用 Bing 搜索
// @description:zh-TW  永遠使用 Bing 搜索
// @icon               https://cn.bing.com/favicon.ico
// @author             lingbopro
// @homepage           https://github.com/lingbopro/AlwaysBing
// @license            MIT
// @match              *://*.bing.com/*
// @match              *://*.baidu.com/*
// @grant              GM_setValue
// @grant              GM_getValue
// @grant              GM_registerMenuCommand
// @grant              GM_addStyle
// @grant              GM_log
// @grant              window.location
// @noframes
// ==/UserScript==

(function () {
    'use strict';

    /*
     * AlwaysBing
     * 这是一个开源的用户脚本
     * 遵循 MIT 协议
     * GitHub 存储库: https://github.com/lingbopro/AlwaysBing
     * GreasyFork: https://greasyfork.org/zh-CN/scripts/496648
     * 贡献代码前,先用 Prettier 格式化啊~
     */

    //SECTION - 配置 / CONFIG
    /*
     * --------------------
     * 这里是配置内容。
     * 如果你要修改配置,请在这里修改。
     * PS: 不要删JSDoc啊
     * --------------------
     */

    /**
     * 规则列表
     * @type {array}
     */
    let rules = [
        // TODO: 完善规则
        {
            name: '百度', // 名称
            matches: [
                // 子规则
                {
                    type: 'search', // 类型(用来匹配下方的Bing URL)
                    url: 'baidu.com/s', // URL匹配
                    query: 'wd', // 包含搜索内容的参数名称
                },
                {
                    type: 'image',
                    url: 'image.baidu.com/search',
                    query: 'word',
                },
                {
                    type: 'video',
                    url: 'baidu.com/sf/vsearch',
                    query: 'wd',
                },
            ],
        },
    ];
    /**
     * 必应的 URL
     * @type {object}
     */
    let bing = {
        search: 'https://bing.com/search?q=',
        image: 'https://bing.com/images/search?q=',
        video: 'https://bing.com/videos/search?q=',
    };
    /**
     * 读取的配置
     * @type {object}
     */
    let config = {
        rules: [
            // 每项分别对应规则的对应项
            {
                action: 'redirect',
            },
        ],
    };
    //!SECTION

    //SECTION - 逻辑 / LOGIC
    /*
     * --------------------
     * 这里是核心逻辑。
     * 如果你要修改配置,就别动这。
     * 注释不太多,将就看吧:D
     * :)
     * --------------------
     */

    let modal_open = false; // 防止叠弹窗
    /**
     * 读取配置
     */
    let readConfig = function () {
        GM_log(`AlwaysBing: read config`);
        config = GM_getValue('config', config); // 读取配置
        config.rules.forEach((value, index) => {
            // 将配置写到规则表中
            rules[index].action = value.action;
        });
        GM_log(`AlwaysBing: config:\n${JSON.stringify(config)}`);
    };
    /**
     * 写入配置
     */
    let writeConfig = function () {
        GM_log(`AlwaysBing: write config`);
        rules.forEach((value, index) => {
            // 将配置从规则表再写回到配置表
            config.rules[index].action = value.action;
        });
        GM_setValue('config', config); // 写入配置
    };
    /**
     * 上下文菜单回调
     * @param {number} index
     */
    let menuCallback = function (index) {
        GM_log(`AlwaysBing: menu callback: ${index}`);
        let rule = rules[index];
        switch (rule.action) {
            case 'redirect':
                rule.action = 'modal';
                break;
            case 'modal':
                rule.action = 'modal_closeable';
                break;
            case 'modal_closeable':
                rule.action = 'nothing';
                break;
            case 'nothing':
                rule.action = 'redirect';
                break;
            default:
                break;
        }
        rules[index] = rule;
        writeConfig();
        refreshMenu();
    };
    /**
     * 刷新上下文菜单
     */
    let refreshMenu = function () {
        GM_log(`AlwaysBing: refresh menu`);
        rules.forEach((value, index) => {
            let text = '';
            switch (value.action) {
                case 'redirect':
                    text = '🔗重定向';
                    break;
                case 'modal':
                    text = '💬提示';
                    break;
                case 'modal_closeable':
                    text = '💬提示(可关闭)';
                    break;
                case 'nothing':
                    text = '❌什么也不做';
                    break;
                default:
                    break;
            }
            GM_registerMenuCommand(
                `🔍${value.name}: ${text}`,
                function () {
                    menuCallback(index);
                },
                {
                    id: 'menu-' + index,
                    autoClose: false,
                    title: `🔍规则: ${value.name}\n▶动作: ${text}`,
                }
            );
        });
    };
    /**
     * 处理和匹配规则
     */
    let process = function () {
        GM_log(`AlwaysBing: process`);
        rules.forEach((value, index) => {
            // 遍历规则
            let currentRule = value;
            GM_log(`AlwaysBing: process rule ${value.name}`);
            value.matches.forEach((value, index) => {
                // 遍历子规则
                if (window.location.href.includes(value.url)) {
                    // URL匹配
                    GM_log(`AlwaysBing: URL matches rule ${currentRule.name}`);
                    let searchParams = new URLSearchParams(
                        window.location.search
                    );
                    let query = searchParams.get(value.query); // 获取参数
                    let url = bing[value.type] + query; // 对应的必应URL
                    switch (currentRule.action) {
                        case 'redirect':
                            window.location.href = url;
                            break;
                        case 'modal':
                            showModal(url, false);
                            break;
                        case 'modal_closeable':
                            showModal(url, true);
                            break;
                        case 'nothing':
                            break;
                        default:
                            break;
                    }
                }
            });
        });
    };
    /**
     * 显示提示窗
     * @param {string} url 跳转的URL
     * @param {boolean} closeable 是否可关闭
     */
    let showModal = function (url, closeable) {
        if (modal_open) {
            return;
        }
        modal_open = true;
        GM_log(
            `AlwaysBing: show modal\n(url: ${url}\n closeable: ${closeable})`
        );
        let modalEl = document.createElement('div');
        modalEl.id = 'alwaysbing-modal-wrapper';
        // 样式
        GM_addStyle(`
#alwaysbing-modal-wrapper {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #00000060;
    -webkit-user-select: none;
    user-select: none;
    z-index: 1145; /* 故意不小心的 */
}
#alwaysbing-modal {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    padding: 10px 8px;
    width: -moz-fit-content;
    height: -moz-fit-content;
    width: fit-content;
    height: fit-content;
    max-width: 30%;
    max-height: 50%;
    border-radius: 10px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background-color: #ffffff;
    text-align: center;
}

/* Bing 暗色主题 */
/* 好像没啥用... */
.b_dark .alwaysbing-modal {
    background-color: #1b1a19;
    color: #ffffff;
}
.b_dark .alwaysbing-modal * {
    color: #ffffff;
}

#alwaysbing-modal>* {
    padding: 10px;
    margin: 15px;
}
#alwaysbing-modal>.close-btn {
    text-align: right;
    align-self: flex-end;
    width: 20px;
    height: 20px;
    cursor: pointer;
}
#alwaysbing-modal>.img-wrapper {
    margin: 10px;
    padding: 10px;
}
#alwaysbing-modal>.img-wrapper>.img {
    display: block;
    width: 60px;
    height: 60px;
    text-align: center;
}
#alwaysbing-modal>.tit {
    font-size: large;
    font-weight: bold;
}
#alwaysbing-modal>.btn {
    padding: 18px;
    background-color: #35adfd;
    color: #ffffff;
    border-radius: 10px;
    cursor: pointer;
}
#alwaysbing-modal>.btn:hover {
    background-color: #7ec9fc;
}
`);
        // HTML
        modalEl.innerHTML = `
<div id="alwaysbing-modal">
    <div class="close-btn" align="right"  
        onclick="document.getElementById('alwaysbing-modal-wrapper').hidden = true;" 
        ${closeable ? '' : 'hidden'}>
        <!-- 关闭按钮 -->
        <!-- https://icons.bootcss.com/icons/x-lg/ -->
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
            <path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z"/>
        </svg>
    </div>
    <div align="center" class="img-wrapper">
        <!-- Bing图标 -->
        <img class="img" src="https://cn.bing.com/favicon.ico" />
    </div>
    <div class="tit">
        <!-- 标题 -->
        建议使用 Bing 搜索
    </div>
    <div class="content">
        Microsoft Bing 拥有更好的搜索能力,更少的广告,更精准的结果
    </div>
    <div class="btn" onclick="window.open('${url}','_self')">使用 Bing</div>
</div>
`;
        document.body.appendChild(modalEl);
    };

    /**
     * 初始化
     */
    let init = function () {
        GM_log(`AlwaysBing: init`);
        readConfig();
        refreshMenu();
        process();
    };
    init();
    //!SECTION
})();