// ==UserScript== // @name 移动端搜索引擎快捷跳转(仿via // @namespace https://tampermonkey.net/ // @version 1.7 // @description // @author 小骨 // @match *://*.google.com/* // @match *://*.google.com.hk/* // @match *://*.baidu.com/* // @match *://*.bing.com/* // @match *://*.yandex.com/* // @match *://*.yandex.ru/* // @match *://*.sogou.com/* // @match *://*.so.com/* // @match *://search.brave.com/* // @run-at document-end // @grant none // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'mobile_engine_switcher_config_v3'; // 1. 默认搜索引擎列表配置 const DEFAULT_ENGINES = [ { name: '百度', host: 'baidu.com', url: 'https://m.baidu.com/s?word={q}' }, { name: '谷歌', host: 'google.com', url: 'https://www.google.com/search?q={q}' }, { name: '必应', host: 'bing.com', url: 'https://cn.bing.com/search?q={q}' }, { name: 'Yandex图源', host: 'yandex.com/images', url: 'https://yandex.com/images/search?text={q}' }, { name: 'Yandex', host: 'yandex.com', url: 'https://yandex.com/search/?text={q}' }, { name: '搜狗', host: 'sogou.com', url: 'https://m.sogou.com/web/searchList.jsp?keyword={q}' }, { name: 'Brave', host: 'brave.com', url: 'https://search.brave.com/search?q={q}' } ]; // 读取本地存储的引擎列表 function getSavedEngines() { try { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { const parsed = JSON.parse(saved); if (Array.isArray(parsed) && parsed.length > 0) { return parsed; } } } catch (e) { console.error('读取设置失败', e); } return DEFAULT_ENGINES; } // 保存引擎列表 function saveEngines(engines) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(engines)); } catch (e) { console.error('保存设置失败', e); } } // 构建带关键词的链接 function buildEngineUrl(urlTemplate, query) { if (urlTemplate.includes('{q}')) { return urlTemplate.replace(/\{q\}/g, encodeURIComponent(query)); } const separator = urlTemplate.includes('?') ? '&' : '?'; return `${urlTemplate}${separator}q=${encodeURIComponent(query)}`; } // 提取当前页面的搜索关键词(含 Yandex 的 text 参数) function getSearchQuery() { let q = ''; // 1. 优先从 URL Query 参数提取 const searchParams = new URLSearchParams(window.location.search); q = searchParams.get('q') || searchParams.get('word') || searchParams.get('wd') || searchParams.get('keyword') || searchParams.get('query') || searchParams.get('text'); // 2. 支持 Hash 路由参数 if (!q && window.location.hash.includes('?')) { const hashQuery = window.location.hash.substring(window.location.hash.indexOf('?')); const hashParams = new URLSearchParams(hashQuery); q = hashParams.get('q') || hashParams.get('word') || hashParams.get('wd') || hashParams.get('keyword') || hashParams.get('query') || hashParams.get('text'); } // 3. 若 URL 中无参数,尝试从搜索框获取 if (!q) { const input = document.querySelector('input[type="search"], input[name="q"], input[name="wd"], input[name="word"], input[name="keyword"], input[name="text"]'); if (input && input.value) { q = input.value; } } return q ? q.trim() : ''; } // 判断是否为当前搜索引擎 function isCurrentEngine(host) { if (!host) return false; const currentUrl = window.location.href.toLowerCase(); const targetHost = host.toLowerCase(); // 包含路径的 host 匹配(如 yandex.com/images) if (targetHost.includes('/')) { return currentUrl.includes(targetHost); } // 特殊处理:访问 Yandex 图搜页时,主站 Yandex 不进行高亮显示 if (targetHost.includes('yandex') && !targetHost.includes('/images') && currentUrl.includes('/images')) { return false; } return window.location.hostname.includes(targetHost); } // 清理底栏及 CSS function removeSwitcher() { const bar = document.getElementById('mobile-engine-switcher-bar'); if (bar) bar.remove(); const style = document.getElementById('mobile-engine-switcher-style'); if (style) style.remove(); } // HTML 字符转义,防止 XSS function escapeHtml(str) { return (str || '').replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } // 创建或更新底部跳转工具栏 function renderSwitcher() { const query = getSearchQuery(); if (!query) { removeSwitcher(); return; } const engines = getSavedEngines(); // 注入样式 if (!document.getElementById('mobile-engine-switcher-style')) { const style = document.createElement('style'); style.id = 'mobile-engine-switcher-style'; style.textContent = ` #mobile-engine-switcher-bar { position: fixed !important; bottom: 0 !important; left: 0 !important; right: 0 !important; width: 100% !important; z-index: 2147483647 !important; display: flex !important; align-items: center !important; gap: 8px !important; padding: 6px 10px calc(6px + env(safe-area-inset-bottom, 0px)) 10px !important; background-color: #000000 !important; border-top: 1px solid rgba(255, 255, 255, 0.05) !important; overflow-x: auto !important; white-space: nowrap !important; -webkit-overflow-scrolling: touch !important; box-sizing: border-box !important; } #mobile-engine-switcher-bar::-webkit-scrollbar { display: none !important; } .engine-pill-btn { flex: 0 0 auto !important; height: 27px !important; padding: 0 14px !important; border-radius: 9999px !important; font-size: 13px !important; font-weight: 400 !important; line-height: 1 !important; text-decoration: none !important; color: #e3e3e3 !important; background-color: #000000 !important; border: 1px solid #303030 !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; box-sizing: border-box !important; user-select: none !important; -webkit-tap-highlight-color: transparent !important; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif !important; } .engine-pill-btn.active { border-color: #8ab4f8 !important; color: #8ab4f8 !important; background-color: rgba(138, 180, 248, 0.12) !important; font-weight: 400 !important; } .engine-pill-btn.settings-btn { padding: 0 9px !important; color: #888888 !important; border-color: #2a2a2a !important; } .engine-pill-btn.settings-btn:active { color: #ffffff !important; border-color: #505050 !important; background-color: #111111 !important; } body { padding-bottom: 42px !important; } /* 弹窗样式 */ #mobile-engine-modal-overlay { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; height: 100vh !important; background: rgba(0, 0, 0, 0.8) !important; backdrop-filter: blur(4px) !important; -webkit-backdrop-filter: blur(4px) !important; z-index: 2147483648 !important; display: flex !important; align-items: center !important; justify-content: center !important; padding: 16px !important; box-sizing: border-box !important; } .engine-modal-card { background: #141416 !important; border: 1px solid #2d2d32 !important; border-radius: 14px !important; width: 100% !important; max-width: 360px !important; max-height: 80vh !important; display: flex !important; flex-direction: column !important; box-shadow: 0 16px 36px rgba(0,0,0,0.8) !important; color: #e3e3e3 !important; font-family: -apple-system, BlinkMacSystemFont, sans-serif !important; overflow: hidden !important; } .engine-modal-header { display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 12px 16px !important; border-bottom: 1px solid #26262a !important; font-size: 14px !important; font-weight: 500 !important; color: #ffffff !important; } .engine-modal-close { background: transparent !important; border: none !important; color: #888888 !important; font-size: 20px !important; cursor: pointer !important; padding: 0 4px !important; line-height: 1 !important; } .engine-modal-body { padding: 14px 16px !important; overflow-y: auto !important; flex: 1 !important; } .engine-modal-section-title { font-size: 11px !important; color: #777777 !important; margin-bottom: 8px !important; letter-spacing: 0.5px !important; } .engine-list-item { display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 8px 10px !important; background: #1c1c20 !important; border: 1px solid #28282e !important; border-radius: 8px !important; margin-bottom: 6px !important; } .engine-item-info { display: flex !important; flex-direction: column !important; gap: 2px !important; overflow: hidden !important; margin-right: 8px !important; } .engine-item-name { font-size: 13px !important; color: #ffffff !important; } .engine-item-host { font-size: 11px !important; color: #666666 !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; } .engine-item-actions { display: flex !important; align-items: center !important; gap: 4px !important; flex-shrink: 0 !important; } .engine-action-btn { background: #26262c !important; border: 1px solid #36363e !important; color: #cccccc !important; border-radius: 4px !important; padding: 2px 7px !important; font-size: 11px !important; cursor: pointer !important; line-height: 1.4 !important; } .engine-action-btn.delete-btn { color: #ff6b6b !important; border-color: rgba(255,107,107,0.2) !important; background: rgba(255,107,107,0.08) !important; } .engine-add-form { background: #18181c !important; border: 1px solid #26262c !important; border-radius: 10px !important; padding: 12px !important; margin-top: 12px !important; } .engine-input-group { margin-bottom: 8px !important; } .engine-input-group label { display: block !important; font-size: 11px !important; color: #777777 !important; margin-bottom: 4px !important; } .engine-input { width: 100% !important; box-sizing: border-box !important; background: #101012 !important; border: 1px solid #2a2a30 !important; border-radius: 6px !important; color: #ffffff !important; padding: 6px 10px !important; font-size: 12px !important; outline: none !important; } .engine-input:focus { border-color: #8ab4f8 !important; } .engine-add-btn { width: 100% !important; background: #8ab4f8 !important; color: #000000 !important; border: none !important; border-radius: 6px !important; padding: 7px !important; font-size: 12px !important; font-weight: 500 !important; cursor: pointer !important; margin-top: 4px !important; } .engine-modal-footer { padding: 10px 16px !important; border-top: 1px solid #26262a !important; display: flex !important; justify-content: space-between !important; align-items: center !important; background: #141416 !important; } .engine-reset-btn { background: transparent !important; border: none !important; color: #777777 !important; font-size: 12px !important; cursor: pointer !important; text-decoration: underline !important; } .engine-confirm-btn { background: #2a2a30 !important; border: 1px solid #3a3a42 !important; color: #ffffff !important; border-radius: 6px !important; padding: 5px 14px !important; font-size: 12px !important; cursor: pointer !important; } `; document.head.appendChild(style); } // 创建或清空容器 let bar = document.getElementById('mobile-engine-switcher-bar'); if (!bar) { bar = document.createElement('div'); bar.id = 'mobile-engine-switcher-bar'; document.body.appendChild(bar); } else { bar.innerHTML = ''; } // 渲染引擎跳转按钮 engines.forEach(engine => { const btn = document.createElement('a'); btn.className = 'engine-pill-btn' + (isCurrentEngine(engine.host) ? ' active' : ''); btn.textContent = engine.name; btn.href = 'javascript:void(0);'; btn.addEventListener('click', (e) => { e.preventDefault(); if (isCurrentEngine(engine.host)) return; const currentQuery = getSearchQuery(); if (currentQuery) { window.location.href = buildEngineUrl(engine.url, currentQuery); } }); bar.appendChild(btn); }); // 拼接最右侧极简黑白设置按钮(SVG 图标) const settingsBtn = document.createElement('a'); settingsBtn.className = 'engine-pill-btn settings-btn'; settingsBtn.title = '设置搜索引擎'; settingsBtn.href = 'javascript:void(0);'; settingsBtn.innerHTML = ` `; settingsBtn.addEventListener('click', (e) => { e.preventDefault(); openSettingsModal(); }); bar.appendChild(settingsBtn); } // 打开设置弹窗 function openSettingsModal() { if (document.getElementById('mobile-engine-modal-overlay')) return; const overlay = document.createElement('div'); overlay.id = 'mobile-engine-modal-overlay'; overlay.innerHTML = `
设置快捷搜索引擎
已添加引擎列表
添加自定义搜索引擎
`; document.body.appendChild(overlay); // 渲染弹窗中的引擎列表 function renderModalList() { const listContainer = document.getElementById('engine-modal-list'); if (!listContainer) return; listContainer.innerHTML = ''; const engines = getSavedEngines(); engines.forEach((engine, index) => { const item = document.createElement('div'); item.className = 'engine-list-item'; item.innerHTML = `
${escapeHtml(engine.name)}
${escapeHtml(engine.host || '未配置域名')}
${index > 0 ? `` : ''} ${index < engines.length - 1 ? `` : ''}
`; listContainer.appendChild(item); }); // 绑定列表操作逻辑 listContainer.querySelectorAll('.move-up-btn').forEach(btn => { btn.addEventListener('click', (e) => { const idx = parseInt(e.target.getAttribute('data-index'), 10); if (idx > 0) { const engines = getSavedEngines(); const temp = engines[idx]; engines[idx] = engines[idx - 1]; engines[idx - 1] = temp; saveEngines(engines); renderModalList(); renderSwitcher(); } }); }); listContainer.querySelectorAll('.move-down-btn').forEach(btn => { btn.addEventListener('click', (e) => { const idx = parseInt(e.target.getAttribute('data-index'), 10); const engines = getSavedEngines(); if (idx < engines.length - 1) { const temp = engines[idx]; engines[idx] = engines[idx + 1]; engines[idx + 1] = temp; saveEngines(engines); renderModalList(); renderSwitcher(); } }); }); listContainer.querySelectorAll('.delete-btn').forEach(btn => { btn.addEventListener('click', (e) => { const idx = parseInt(e.target.getAttribute('data-index'), 10); const engines = getSavedEngines(); if (engines.length <= 1) { alert('至少保留一个搜索引擎!'); return; } engines.splice(idx, 1); saveEngines(engines); renderModalList(); renderSwitcher(); }); }); } renderModalList(); const closeModal = () => { overlay.remove(); }; document.getElementById('modal-close-btn').addEventListener('click', closeModal); document.getElementById('modal-done-btn').addEventListener('click', closeModal); overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); }); // 恢复默认按钮 document.getElementById('modal-reset-btn').addEventListener('click', () => { if (confirm('确定恢复默认搜索引擎列表吗?')) { saveEngines(DEFAULT_ENGINES); renderModalList(); renderSwitcher(); } }); // 添加搜索引擎提交 document.getElementById('add-engine-submit').addEventListener('click', () => { const name = document.getElementById('add-engine-name').value.trim(); const host = document.getElementById('add-engine-host').value.trim(); let url = document.getElementById('add-engine-url').value.trim(); if (!name || !url) { alert('请填写名称和搜索 URL!'); return; } if (!url.includes('{q}')) { const separator = url.includes('?') ? '&' : '?'; url = `${url}${separator}q={q}`; } const engines = getSavedEngines(); engines.push({ name, host: host || name, url }); saveEngines(engines); // 清空输入框 document.getElementById('add-engine-name').value = ''; document.getElementById('add-engine-host').value = ''; document.getElementById('add-engine-url').value = ''; renderModalList(); renderSwitcher(); }); } // 初始化渲染 renderSwitcher(); // 监听 URL 变化 (针对单页应用 SPA) let lastUrl = location.href; const observer = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(renderSwitcher, 300); } }); observer.observe(document.body, { childList: true, subtree: true }); })();