Ecosia 搜索引擎重定向器

这是一个将 Ecosia 重定向到其他搜索引擎的脚本。主要是解决 Safari 自定义搜索引擎能力缺失的问题。

// ==UserScript==
// @name         Ecosia Search Redirector
// @name:zh-CN   Ecosia 搜索引擎重定向器
// @name:zh-TW   Ecosia 搜尋引擎重定向器
// @namespace    https://www.ecosia.org/
// @version      1.4
// @description  A Safari-focused userscript that redirects Ecosia searches to other engines using keywords, providing search engine customization that Safari lacks
// @description:zh-cn 这是一个将 Ecosia 重定向到其他搜索引擎的脚本。主要是解决 Safari 自定义搜索引擎能力缺失的问题。
// @description:zh-tw 這是一個將 Ecosia 重定向到其他搜尋引擎的腳本。主要是解決 Safari 自定義搜尋引擎能力缺失的問題。
// @author       https://github.com/hxueh
// @match        *://*.ecosia.org/search*
// @match        *://*.ecosia.org/
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @license      MIT
// @icon         https://www.ecosia.org/favicon.ico
// ==/UserScript==

const STORAGE_KEY = "ecosia_redirector_default";
const searchEngines = {
  g: `https://www.google.com/search?q=%s`,
  google: `https://www.google.com/search?q=%s`,
  sp: `https://www.startpage.com/search?q=%s`,
  startpage: `https://www.startpage.com/search?q=%s`,
  ecosia: `https://www.ecosia.org/search?q=%s`,
  kagi: `https://kagi.com/search?q=%s`,
  caixin: `https://search.caixin.com/newsearch/caixinsearch?keyword=%s`,
  ndb: `https://neodb.social/search?q=%s`,
  brave: `https://search.brave.com/search?q=%s`,
  gpt: `https://chatgpt.com/?temporary-chat=true&q=%s`,
  chatgpt: `https://chatgpt.com/?temporary-chat=true&q=%s`,
  claude: `https://claude.ai/new?q=%s`,
  dict: `https://www.collinsdictionary.com/us/dictionary/english/%s`,
  neodb: `https://neodb.social/search?q=%s`,
  podcast: `https://www.listennotes.com/search/?q=%s`,
  pp: `https://www.perplexity.ai/search?q=%s`,
  perplexity: `https://www.perplexity.ai/search?q=%s`,
  reddit: `https://www.reddit.com/search?q=%s`,
  so: `https://stackoverflow.com/search?q=%s`,
  twitter: `https://twitter.com/search?q=%s`,
  yt: `https://youtube.com/results?search_query=%s`,
  youtube: `https://youtube.com/results?search_query=%s`,
  taobao: `https://s.taobao.com/search?q=%s`,
  jd: `https://search.jd.com/Search?keyword=%s`,
  bili: `https://www.bilibili.com/search?keyword=%s`,
  xhs: `https://www.xiaohongshu.com/search_result?keyword=%s`,
  weibo: `https://s.weibo.com/weibo/%s`,
  youdao: `https://dict.youdao.com/search?q=%s`,
  zhihu: `https://www.zhihu.com/search?type=content&q=%s`,
  douban: `https://www.douban.com/search/?q=%s`,
};

// Add CSS for the settings window
GM_addStyle(`
  .search-settings-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 999999;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .search-settings-window {
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    width: 300px;
  }

  .search-settings-window h2 {
    margin: 0 0 20px 0;
    font-size: 18px;
    color: #333;
  }

  .search-settings-window select {
    width: 100%;
    padding: 8px;
    border: 1px solid #ddd;
    border-radius: 4px;
    margin-bottom: 20px;
  }

  .search-settings-window .buttons {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
  }

  .search-settings-window button {
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  .search-settings-window .save-btn {
    background: #4CAF50;
    color: white;
  }

  .search-settings-window .cancel-btn {
    background: #f5f5f5;
    color: #333;
  }

  .search-settings-window button:hover {
    opacity: 0.9;
  }
`);

function getDefaultEngine() {
  return GM_getValue(STORAGE_KEY, "kagi");
}

function showSettingsWindow() {
  const overlay = document.createElement("div");
  overlay.className = "search-settings-overlay";

  const window = document.createElement("div");
  window.className = "search-settings-window";
  window.innerHTML = `
    <h2>Default Search Engine Settings</h2>
    <select id="default-engine">
      ${Object.entries(searchEngines)
        .map(
          ([key]) => `
          <option value="${key}" ${
            key === getDefaultEngine() ? "selected" : ""
          }>
            ${key}
          </option>
        `
        )
        .join("")}
    </select>
    <div class="buttons">
      <button class="cancel-btn">Cancel</button>
      <button class="save-btn">Save</button>
    </div>
  `;

  function closeSettings() {
    document.body.removeChild(overlay);
  }

  // Event Handlers
  window.querySelector(".save-btn").addEventListener("click", () => {
    const engine = window.querySelector("#default-engine").value;
    GM_setValue(STORAGE_KEY, engine);
    closeSettings();
  });

  window.querySelector(".cancel-btn").addEventListener("click", closeSettings);
  overlay.addEventListener("click", (e) => {
    if (e.target === overlay) closeSettings();
  });

  overlay.appendChild(window);
  document.body.appendChild(overlay);
}

// Register settings menu command
GM_registerMenuCommand("⚙️ Default Search Engine Settings", showSettingsWindow);

(function () {
  "use strict";

  // Get the URL parameters
  const urlParams = new URLSearchParams(window.location.search);

  // Check if the safari refer argument is present
  // if present, do redirect
  const ttsParam = urlParams.get("tts");
  if (!ttsParam) {
    return;
  }

  const defaultSearchEngine = getDefaultEngine();
  if (defaultSearchEngine === "ecosia") {
    return;
  }

  // Clear the document content
  document.documentElement.innerHTML = "<html></html>";

  // Get the search query from the URL
  const query = urlParams.get("q");

  if (query) {
    // Check if the query starts with a keyword in the searchEngines mapping
    const keyword = Object.keys(searchEngines).find((k) =>
      query.toLowerCase().startsWith(k + " ")
    );

    let redirectUrl;
    if (keyword) {
      // Get the search term after the keyword
      const searchTerm = query.slice(keyword.length).trim();
      redirectUrl = searchEngines[keyword].replace(
        "%s",
        encodeURIComponent(searchTerm)
      );
    } else {
      // Default search engine
      redirectUrl = searchEngines[defaultSearchEngine].replace(
        "%s",
        encodeURIComponent(query)
      );
    }

    window.location.href = redirectUrl;
  }
})();