您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
将搜索请求从一个搜索引擎重定向到另一个(支持多个搜索引擎),并管理搜索引擎重定向规则
// ==UserScript== // @name Search Engine Redirector, Search Engine Manager // @name:zh-CN 搜索引擎重定向插件,搜索引擎管理助手 // @name:zh-TW 搜尋引擎重定向插件,搜尋引擎管理助手 // @name:en Search Engine Redirector, Search Engine Manager // @name:ja 検索エンジンリダイレクター・検索エンジン管理 // @name:ko 검색 엔진 리디렉터, 검색 엔진 관리자 // @name:ru Перенаправление поисковых систем, Менеджер поисковых систем // @name:fr Redirection de moteur de recherche, Gestionnaire de moteurs de recherche // @name:es Redireccionador de motores de búsqueda, Gestor de motores de búsqueda // @name:de Suchmaschinen-Umleiter, Suchmaschinen-Manager // @name:pt-BR Redirecionador de Mecanismos de Busca, Gerenciador de Mecanismos de Busca // @name:it Reindirizzatore motore di ricerca, Gestore motore di ricerca // @name:tr Arama Motoru Yönlendirici, Arama Motoru Yöneticisi // @name:vi Trình chuyển hướng công cụ tìm kiếm, Trình quản lý công cụ tìm kiếm // @name:pl Przekierowywacz wyszukiwarek, Menedżer wyszukiwarek // @name:uk Перенаправлення пошукових систем, Менеджер пошукових систем // @name:ar معيد توجيه محرك البحث، مدير محرك البحث // @name:hi सर्च इंजन रीडायरेक्टर, सर्च इंजन मैनेजर // @description Redirect search requests from one engine to another (supports multiple engines) and manage search engine redirection rules // @description:zh-CN 将搜索请求从一个搜索引擎重定向到另一个(支持多个搜索引擎),并管理搜索引擎重定向规则 // @description:zh-TW 將搜尋請求從一個搜尋引擎重定向到另一個(支援多個搜尋引擎),並管理搜尋引擎重定向規則 // @description:en Redirect search requests from one engine to another (supports multiple engines) and manage search engine redirection rules // @description:ja 検索リクエストを別の検索エンジンにリダイレクト(複数エンジン対応)、リダイレクトルールを管理 // @description:ko 검색 요청을 다른 검색 엔진으로 리디렉션(여러 엔진 지원), 검색 엔진 리디렉션 규칙 관리 // @description:ru Перенаправляет поисковые запросы с одной системы на другую (поддержка нескольких систем) и управляет правилами перенаправления // @description:fr Redirige les requêtes de recherche d'un moteur à un autre (plusieurs moteurs pris en charge) et gère les règles de redirection // @description:es Redirige las búsquedas de un motor a otro (soporta múltiples motores) y gestiona reglas de redirección // @description:de Leitet Suchanfragen von einer Suchmaschine zu einer anderen um (unterstützt mehrere Suchmaschinen) und verwaltet Umleitungsregeln // @description:pt-BR Redireciona buscas de um mecanismo para outro (suporta múltiplos mecanismos) e gerencia regras de redirecionamento // @description:it Reindirizza le ricerche da un motore all'altro (supporta più motori) e gestisce le regole di reindirizzamento // @description:tr Arama isteklerini bir motordan diğerine yönlendirir (birden fazla motor desteklenir) ve yönlendirme kurallarını yönetir // @description:vi Chuyển hướng tìm kiếm từ một công cụ sang công cụ khác (hỗ trợ nhiều công cụ), quản lý quy tắc chuyển hướng // @description:pl Przekierowuje zapytania z jednej wyszukiwarki do innej (obsługuje wiele wyszukiwarek) i zarządza regułami przekierowań // @description:uk Перенаправляє пошукові запити з однієї системи на іншу (підтримка декількох систем) і керує правилами перенаправлення // @description:ar يعيد توجيه طلبات البحث من محرك إلى آخر (يدعم عدة محركات) ويدير قواعد إعادة التوجيه (يدعم الصينية والإنجليزية) // @description:hi खोज अनुरोधों को एक इंजन से दूसरे में रीडायरेक्ट करें (कई इंजन समर्थित), रीडायरेक्शन नियम प्रबंधित करें // @namespace https://github.com/r6hk/search-engine-redirect/ // @homepage https://github.com/r6hk/search-engine-redirect/ // @supportURL https://github.com/r6hk/search-engine-redirect/ // @version 1.0.1 // @author r6hk // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_getResourceText // @grant GM_addStyle // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // 搜索引擎信息 const SEARCH_ENGINES = { Google: { prefix: 'https://www.google.com/search', param: 'q' }, Bing: { prefix: 'https://www.bing.com/search', param: 'q' }, DuckDuckGo: { prefix: 'https://duckduckgo.com/', param: 'q' }, Yandex: { prefix: 'https://yandex.com/search', param: 'text' }, 'Brave Search': { prefix: 'https://search.brave.com/search', param: 'q' }, Startpage: { prefix: 'https://www.startpage.com/do/search', param: 'q' }, Ecosia: { prefix: 'https://www.ecosia.org/search', param: 'q' } }; // 存储键名 const STORAGE_KEYS = { REDIRECT_LIST: 'redirect_list', RULES: 'rules' }; // 国际化文本 const i18n = { en: { title: "Search Engine Redirector Settings", redirectLabel: "Search engines I want to redirect:", enabledRules: "Enabled Rules", disabledRules: "Disabled Rules", addButton: "Add", name: "Name", keyword: "Keyword", urlFormat: "URL Format (use %s for query)", actions: "Actions", setDefault: "Set Default", disable: "Disable", enable: "Enable", delete: "Delete", save: "Save", cancel: "Cancel", addRule: "Add Rule", ruleName: "Rule Name", ruleKeyword: "Shortcut Keyword", ruleUrl: "URL Format", required: "Required", invalidUrl: "URL must contain '%s'", defaultSet: "Default set", ruleAdded: "Rule added", ruleDeleted: "Rule deleted", ruleEnabled: "Rule enabled", ruleDisabled: "Rule disabled", settingsSaved: "Settings saved" }, zh: { title: "搜索引擎重定向设置", redirectLabel: "我希望重定向的搜索引擎:", enabledRules: "已启用规则", disabledRules: "已禁用规则", addButton: "添加", name: "名称", keyword: "快捷字词", urlFormat: "网址格式(用\"%s\"代替搜索字词)", actions: "操作", setDefault: "设为默认", disable: "禁用", enable: "启用", delete: "删除", save: "保存", cancel: "取消", addRule: "添加规则", ruleName: "规则名称", ruleKeyword: "快捷字词", ruleUrl: "网址格式", required: "必填", invalidUrl: "URL必须包含'%s'", defaultSet: "已设为默认", ruleAdded: "规则已添加", ruleDeleted: "规则已删除", ruleEnabled: "规则已启用", ruleDisabled: "规则已禁用", settingsSaved: "设置已保存" } }; // 获取语言 const lang = navigator.language.startsWith('zh') ? 'zh' : 'en'; const text = i18n[lang]; // 初始化存储 function initializeStorage() { if (GM_getValue(STORAGE_KEYS.REDIRECT_LIST) === undefined) { GM_setValue(STORAGE_KEYS.REDIRECT_LIST, []); } if (GM_getValue(STORAGE_KEYS.RULES) === undefined) { GM_setValue(STORAGE_KEYS.RULES, [ { id: 1, name: "Brave", keyword: "br", url: "https://search.brave.com/search?q=%s&source=desktop", enabled: true, isDefault: false }, { id: 2, name: "DuckDuckGo", keyword: "d", url: "https://duckduckgo.com/?q=%s&t=brave", enabled: true, isDefault: false } ]); } } // 主重定向逻辑 function performRedirect() { const redirectList = GM_getValue(STORAGE_KEYS.REDIRECT_LIST, []); const rules = GM_getValue(STORAGE_KEYS.RULES, []); const currentUrl = window.location.href; if (/([&?])redirect=false(?!\w)/.test(currentUrl)) return; let matchedEngine = null; for (const engineName of redirectList) { const engine = SEARCH_ENGINES[engineName]; if (engine && currentUrl.startsWith(engine.prefix)) { matchedEngine = engine; break; } } if (!matchedEngine) return; // 提取搜索关键词 const urlObj = new URL(currentUrl); let query = urlObj.searchParams.get(matchedEngine.param); if (!query) return; // 分割关键词 const words = query.trim().split(/\s+/); const firstWord = words[0]; let remainingQuery = words.slice(1).join(' '); // 查找匹配规则 let matchedRule = null; let defaultRule = null; for (const rule of rules) { if (!rule.enabled) continue; if (rule.keyword === firstWord) { matchedRule = rule; break; } if (rule.isDefault) { defaultRule = rule; } } // 使用默认规则(如果存在) if (!matchedRule && defaultRule) { matchedRule = defaultRule; remainingQuery = query; // 使用完整查询 } if (matchedRule) { let targetUrl = matchedRule.url.replace('%s', encodeURIComponent(remainingQuery)); if (targetUrl.includes('?')) { targetUrl += '&redirect=false'; } else { targetUrl += '?redirect=false'; } window.location.replace(targetUrl); } } // 创建设置UI function createSettingsUI() { const style = ` .redirector-settings { font-family: system-ui, -apple-system, sans-serif; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 9999; overflow-y: auto; padding: 20px; box-sizing: border-box; } .settings-container { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); width: 100%; max-width: 900px; max-height: 90vh; overflow-y: auto; } .settings-header { padding: 20px; border-bottom: 1px solid #eaeaea; display: flex; justify-content: space-between; align-items: center; } .settings-title { font-size: 1.5rem; font-weight: 600; color: #333; margin: 0; } .close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #666; padding: 5px; } .settings-body { padding: 20px; } .section { margin-bottom: 30px; } .section-title { font-size: 1.2rem; margin: 0 0 15px 0; color: #444; font-weight: 600; } .engines-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; } .engine-item { display: flex; align-items: center; } .engine-item input { margin-right: 8px; } .rules-container { display: flex; flex-direction: column; gap: 20px; } .rules-table { width: 100%; border-collapse: collapse; } .rules-table th { background: #f8f9fa; text-align: left; padding: 12px 15px; font-weight: 600; color: #495057; border-bottom: 1px solid #dee2e6; } .rules-table td { padding: 12px 15px; border-bottom: 1px solid #eaeaea; } .rules-table tr:nth-child(even) { background-color: #f9f9f9; } .rules-table tr:hover { background-color: #f0f7ff; } .action-btn { background: none; border: 1px solid #d1d5db; border-radius: 4px; padding: 5px 10px; margin: 0 3px; cursor: pointer; font-size: 0.85rem; transition: all 0.2s; } .action-btn:hover { background: #f0f7ff; border-color: #3b82f6; color: #3b82f6; } .add-btn { background: #3b82f6; color: white; border: none; border-radius: 6px; padding: 8px 16px; cursor: pointer; font-weight: 500; display: flex; align-items: center; gap: 5px; margin-top: 10px; transition: background 0.2s; } .add-btn:hover { background: #2563eb; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; } .modal-content { background: white; border-radius: 10px; width: 90%; max-width: 500px; padding: 25px; box-shadow: 0 10px 25px rgba(0,0,0,0.2); } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-title { font-size: 1.3rem; font-weight: 600; margin: 0; } .form-group { margin-bottom: 20px; } .form-label { display: block; margin-bottom: 8px; font-weight: 500; color: #444; } .form-input { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 1rem; box-sizing: border-box; } .form-input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); } .error-message { color: #e53e3e; font-size: 0.85rem; margin-top: 5px; display: none; } .modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .btn { padding: 10px 20px; border-radius: 6px; font-weight: 500; cursor: pointer; transition: all 0.2s; } .btn-primary { background: #3b82f6; color: white; border: none; } .btn-primary:hover { background: #2563eb; } .btn-secondary { background: #f3f4f6; color: #4b5563; border: 1px solid #d1d5db; } .btn-secondary:hover { background: #e5e7eb; } .toast { position: fixed; bottom: 20px; right: 20px; background: #333; color: white; padding: 12px 20px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10001; opacity: 0; transform: translateY(20px); transition: all 0.3s; } .toast.show { opacity: 1; transform: translateY(0); } `; GM_addStyle(style); const settingsContainer = document.createElement('div'); settingsContainer.className = 'redirector-settings'; settingsContainer.innerHTML = ` <div class="settings-container"> <div class="settings-header"> <h2 class="settings-title">${text.title}</h2> <button class="close-btn">×</button> </div> <div class="settings-body"> <div class="section"> <h3 class="section-title">${text.redirectLabel}</h3> <div class="engines-grid" id="engines-grid"></div> </div> <div class="rules-container"> <div class="section"> <div style="display: flex; justify-content: space-between; align-items: center;"> <h3 class="section-title">${text.enabledRules}</h3> <button class="add-btn" id="add-enabled-btn">+ ${text.addButton}</button> </div> <table class="rules-table" id="enabled-table"> <thead> <tr> <th>${text.name}</th> <th>${text.keyword}</th> <th>${text.urlFormat}</th> <th>${text.actions}</th> </tr> </thead> <tbody id="enabled-rules-body"></tbody> </table> </div> <div class="section"> <div style="display: flex; justify-content: space-between; align-items: center;"> <h3 class="section-title">${text.disabledRules}</h3> <button class="add-btn" id="add-disabled-btn">+ ${text.addButton}</button> </div> <table class="rules-table" id="disabled-table"> <thead> <tr> <th>${text.name}</th> <th>${text.keyword}</th> <th>${text.urlFormat}</th> <th>${text.actions}</th> </tr> </thead> <tbody id="disabled-rules-body"></tbody> </table> </div> </div> </div> </div> `; document.body.appendChild(settingsContainer); // 关闭按钮 settingsContainer.querySelector('.close-btn').addEventListener('click', () => { document.body.removeChild(settingsContainer); }); // 渲染设置 renderSettings(); // 添加规则按钮 document.getElementById('add-enabled-btn').addEventListener('click', () => { showAddRuleModal(true); }); document.getElementById('add-disabled-btn').addEventListener('click', () => { showAddRuleModal(false); }); } // 渲染设置 function renderSettings() { const redirectList = GM_getValue(STORAGE_KEYS.REDIRECT_LIST, []); const rules = GM_getValue(STORAGE_KEYS.RULES, []); // 渲染搜索引擎选择 const enginesGrid = document.getElementById('engines-grid'); enginesGrid.innerHTML = ''; Object.keys(SEARCH_ENGINES).forEach(engine => { const isChecked = redirectList.includes(engine); const engineItem = document.createElement('label'); engineItem.className = 'engine-item'; engineItem.innerHTML = ` <input type="checkbox" value="${engine}" ${isChecked ? 'checked' : ''}> ${engine} `; enginesGrid.appendChild(engineItem); }); // 为复选框添加事件 enginesGrid.querySelectorAll('input').forEach(checkbox => { checkbox.addEventListener('change', () => { const newRedirectList = [...enginesGrid.querySelectorAll('input:checked')].map(cb => cb.value); GM_setValue(STORAGE_KEYS.REDIRECT_LIST, newRedirectList); }); }); // 渲染规则表 renderRulesTable('enabled-rules-body', rules.filter(r => r.enabled)); renderRulesTable('disabled-rules-body', rules.filter(r => !r.enabled)); } // 渲染规则表 function renderRulesTable(tableId, rules) { const tableBody = document.getElementById(tableId); tableBody.innerHTML = ''; if (rules.length === 0) { const row = document.createElement('tr'); row.innerHTML = `<td colspan="4" style="text-align: center; padding: 20px; color: #777;">${lang === 'zh' ? '没有规则' : 'No rules'}</td>`; tableBody.appendChild(row); return; } rules.forEach(rule => { const isDefault = rule.isDefault; const row = document.createElement('tr'); row.innerHTML = ` <td>${rule.name} ${isDefault ? '<span style="color:#3b82f6;font-size:0.8em;">(默认)</span>' : ''}</td> <td>${rule.keyword}</td> <td style="max-width: 300px; word-break: break-all;">${rule.url}</td> <td> ${rule.enabled ? `<button class="action-btn set-default-btn" data-id="${rule.id}">${text.setDefault}</button> <button class="action-btn disable-btn" data-id="${rule.id}" ${isDefault ? 'disabled style="opacity:0.5;cursor:not-allowed;"' : ''}>${text.disable}</button>` : `<button class="action-btn enable-btn" data-id="${rule.id}">${text.enable}</button>` } <button class="action-btn delete-btn" data-id="${rule.id}" ${isDefault ? 'disabled style="opacity:0.5;cursor:not-allowed;"' : ''}>${text.delete}</button> </td> `; tableBody.appendChild(row); }); // 事件处理 tableBody.querySelectorAll('.set-default-btn').forEach(btn => { btn.addEventListener('click', () => setDefaultRule(btn.dataset.id)); }); tableBody.querySelectorAll('.disable-btn').forEach(btn => { if (btn.hasAttribute('disabled')) return; btn.addEventListener('click', () => toggleRule(btn.dataset.id, false)); }); tableBody.querySelectorAll('.enable-btn').forEach(btn => { btn.addEventListener('click', () => toggleRule(btn.dataset.id, true)); }); tableBody.querySelectorAll('.delete-btn').forEach(btn => { if (btn.hasAttribute('disabled')) return; btn.addEventListener('click', () => deleteRule(btn.dataset.id)); }); } // 设置默认规则 function setDefaultRule(ruleId) { const rules = GM_getValue(STORAGE_KEYS.RULES, []); const updatedRules = rules.map(rule => ({ ...rule, isDefault: rule.id.toString() === ruleId })); GM_setValue(STORAGE_KEYS.RULES, updatedRules); renderSettings(); showToast(text.defaultSet); } // 切换规则状态 function toggleRule(ruleId, enabled) { const rules = GM_getValue(STORAGE_KEYS.RULES, []); const updatedRules = rules.map(rule => rule.id.toString() === ruleId ? {...rule, enabled} : rule ); GM_setValue(STORAGE_KEYS.RULES, updatedRules); renderSettings(); showToast(enabled ? text.ruleEnabled : text.ruleDisabled); } // 删除规则 function deleteRule(ruleId) { const rules = GM_getValue(STORAGE_KEYS.RULES, []); const updatedRules = rules.filter(rule => rule.id.toString() !== ruleId); GM_setValue(STORAGE_KEYS.RULES, updatedRules); renderSettings(); showToast(text.ruleDeleted); } // 显示添加规则模态框 function showAddRuleModal(enabled) { const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = ` <div class="modal-content"> <div class="modal-header"> <h3 class="modal-title">${text.addRule}</h3> <button class="close-btn">×</button> </div> <div class="form-group"> <label class="form-label">${text.ruleName} <span style="color:red">*</span></label> <input type="text" class="form-input" id="rule-name"> <div class="error-message" id="name-error">${text.required}</div> </div> <div class="form-group"> <label class="form-label">${text.ruleKeyword} <span style="color:red">*</span></label> <input type="text" class="form-input" id="rule-keyword"> <div class="error-message" id="keyword-error">${text.required}</div> </div> <div class="form-group"> <label class="form-label">${text.ruleUrl} <span style="color:red">*</span></label> <input type="text" class="form-input" id="rule-url" placeholder="https://example.com/search?q=%s"> <div class="error-message" id="url-error">${text.invalidUrl.replace('%s', '%s')}</div> </div> <div class="modal-footer"> <button class="btn btn-secondary" id="cancel-btn">${text.cancel}</button> <button class="btn btn-primary" id="save-btn">${text.save}</button> </div> </div> `; document.body.appendChild(modal); // 关闭按钮 modal.querySelector('.close-btn').addEventListener('click', () => { document.body.removeChild(modal); }); // 取消按钮 modal.querySelector('#cancel-btn').addEventListener('click', () => { document.body.removeChild(modal); }); // 保存按钮 modal.querySelector('#save-btn').addEventListener('click', () => { const name = modal.querySelector('#rule-name').value.trim(); const keyword = modal.querySelector('#rule-keyword').value.trim(); const url = modal.querySelector('#rule-url').value.trim(); // 验证输入 let valid = true; if (!name) { modal.querySelector('#name-error').style.display = 'block'; valid = false; } else { modal.querySelector('#name-error').style.display = 'none'; } if (!keyword) { modal.querySelector('#keyword-error').style.display = 'block'; valid = false; } else { modal.querySelector('#keyword-error').style.display = 'none'; } if (!url || !url.includes('%s')) { modal.querySelector('#url-error').style.display = 'block'; valid = false; } else { modal.querySelector('#url-error').style.display = 'none'; } if (!valid) return; // 保存规则 const rules = GM_getValue(STORAGE_KEYS.RULES, []); const newId = rules.length > 0 ? Math.max(...rules.map(r => r.id)) + 1 : 1; rules.push({ id: newId, name, keyword, url, enabled, isDefault: false }); GM_setValue(STORAGE_KEYS.RULES, rules); document.body.removeChild(modal); renderSettings(); showToast(text.ruleAdded); }); } // 显示提示消息 function showToast(message) { let toast = document.querySelector('.toast'); if (!toast) { toast = document.createElement('div'); toast.className = 'toast'; document.body.appendChild(toast); } toast.textContent = message; toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, 3000); } // 初始化 initializeStorage(); // 注册菜单命令 GM_registerMenuCommand(text.title, createSettingsUI); // 执行重定向 performRedirect(); })();