// ==UserScript==
// @name 手机端快捷搜索助手(油猴干净版)
// @namespace https://github.com/yourname
// @version 2.0
// @description 搜索框下方永远显示另外3个引擎 / 关键词高亮 / 可视化设置
// @author You
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
/* ========== 1. 引擎数据 ========== */
const ENGINE_DB = [
{
key: 'google',
name: 'Google',
logo: 'https://www.google.com/favicon.ico',
searchUrl: 'https://www.google.com/search?q={q}',
mirrors: [
'https://www.google.com/search?q={q}',
'https://www.google.com.hk/search?q={q}',
'https://www.google.co.jp/search?q={q}'
]
},
{
key: 'bing',
name: 'Bing',
logo: 'https://www.bing.com/favicon.ico',
searchUrl: 'https://www.bing.com/search?q={q}',
mirrors: [
'https://www.bing.com/search?q={q}',
'https://cn.bing.com/search?q={q}'
]
},
{
key: 'yandex',
name: 'Yandex',
logo: 'https://yandex.com/favicon.ico',
searchUrl: 'https://yandex.com/search/?text={q}'
},
{
key: 'metaso',
name: 'Metaso',
logo: 'https://metaso.cn/favicon.ico',
searchUrl: 'https://metaso.cn/?q={q}'
}
];
/* ========== 2. 工具函数 ========== */
const $ = (s, el) => (el || document).querySelector(s);
const $$ = (s, el) => [...(el || document).querySelectorAll(s)];
function detectCurrentEngine() {
const h = location.hostname;
if (/^www\.google\./.test(h)) return 'google';
if (/bing\.(com|cn)/.test(h)) return 'bing';
if (/yandex\.(com|ru)/.test(h)) return 'yandex';
if (/metaso\.cn/.test(h)) return 'metaso';
return '';
}
function getQuery() {
const q = new URLSearchParams(location.search).get('q') ||
new URLSearchParams(location.search).get('wd') ||
new URLSearchParams(location.search).get('text') || '';
return decodeURIComponent(q);
}
/* ========== 3. 关键词高亮 ========== */
function highlightKeyword(keyword) {
if (!keyword) return;
const reg = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
const walk = (n) => {
if (n.nodeType === 3) {
if (reg.test(n.textContent)) {
const span = document.createElement('span');
span.innerHTML = n.textContent.replace(reg, '$1');
n.parentNode.replaceChild(span, n);
}
} else {
n.childNodes.forEach(walk);
}
};
$$('body *').forEach(el => walk(el));
}
/* ========== 4. 样式 ========== */
const STYLE = `
#quickEngineBar{
position:relative;
margin:8px 0 12px;
display:flex;
gap:8px;
overflow-x:auto;
-webkit-overflow-scrolling:touch;
scrollbar-width:none; /* 隐藏横向条 */
}
#quickEngineBar::-webkit-scrollbar{display:none}
.qe-btn{
flex:0 0 auto;
display:flex;
align-items:center;
background:#fff;
border:1px solid #dfe1e5;
border-radius:18px;
padding:5px 9px;
font-size:13px;
color:#202124;
text-decoration:none;
white-space:nowrap;
}
.qe-btn img{
width:16px!important;
height:16px!important;
margin-right:5px;
border-radius:2px;
}
.qe-btn:active{background:#f1f3f4}
#qeSettings{position:fixed;top:10px;right:10px;z-index:9999;background:#fff;border:1px solid #dadce0;border-radius:8px;padding:12px;width:260px;box-shadow:0 4px 12px rgba(0,0,0,.15);font-size:14px;display:none;}
#qeSettings h4{margin:0 0 8px;font-size:16px;}
#qeSettings label{display:block;margin-bottom:6px;}
#qeSettings input[type=text]{width:100%;padding:4px 6px;margin-top:4px;}
#qeSettings button{margin-top:8px;margin-right:6px;}
`;
/* ========== 5. 生成按钮条 ========== */
function buildBar() {
const kw = getQuery();
if (!kw) return;
const current = detectCurrentEngine();
/* 修复:默认选中 metaso */
const selectedKeys = GM_getValue('selectedEngines', ['google', 'bing', 'yandex', 'metaso']);
const customEngines = GM_getValue('customEngines', []);
const all = [...ENGINE_DB, ...customEngines];
const oldBar = $('#quickEngineBar');
if (oldBar) oldBar.remove();
const bar = document.createElement('div');
bar.id = 'quickEngineBar';
all.filter(e => selectedKeys.includes(e.key) && e.key !== current)
.forEach(e => {
const url = (e.mirrors ? e.mirrors[GM_getValue('mirror_' + e.key, 0)] : e.searchUrl)
.replace('{q}', encodeURIComponent(kw));
const a = document.createElement('a');
a.className = 'qe-btn';
a.href = url;
/* 懒加载 + 错误兜底 */
a.innerHTML = `${e.name}`;
bar.appendChild(a);
});
const anchor =
$('input[name="q"], input[name="wd"], input[name="text"]')?.closest('form') ||
$('form[role="search"], form[action*="/search"]') ||
$('#search-form') ||
document.body;
if (anchor) {
if (anchor.nextSibling) anchor.parentNode.insertBefore(bar, anchor.nextSibling);
else anchor.parentNode.appendChild(bar);
highlightKeyword(kw);
return true;
}
return false;
}
/* ========== 6. 持续保活 ========== */
function keepBarAlive() {
setTimeout(() => {
let retry = 0;
const tryInsert = () => {
if (buildBar() || retry++ > 20) return;
setTimeout(tryInsert, 500);
};
tryInsert();
const observer = new MutationObserver(() => {
if (!document.contains($('#quickEngineBar'))) buildBar();
});
observer.observe(document.body, { childList: true, subtree: true });
}, 1000);
}
/* ========== 7. 设置面板 ========== */
function openSettings() {
let panel = $('#qeSettings');
if (panel) { panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; return; }
panel = document.createElement('div');
panel.id = 'qeSettings';
panel.innerHTML = `