您需要先安装一个扩展,例如 篡改猴、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();
- })();