Greasy Fork 支持简体中文。

Duck Google

Adds DuckDuckGo !bangs to Google search

目前為 2025-02-28 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Duck Google
  3. // @namespace garcialnk.com
  4. // @license MIT
  5. // @version 1.0.1
  6. // @description Adds DuckDuckGo !bangs to Google search
  7. // @author GarciaLnk
  8. // @match *://www.google.com/search*
  9. // @match *://google.com/search*
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @run-at document-start
  14. // @sandbox DOM
  15. // @tag utilities
  16. // @connect duckduckgo.com
  17. // ==/UserScript==
  18. /* jshint esversion: 11 */
  19.  
  20. (function () {
  21. "use strict";
  22.  
  23. /**
  24. * DuckDuckGo bang object
  25. * @typedef {Object} Bang
  26. * @property {string} s - The search engine name
  27. * @property {string} t - The bang command
  28. * @property {string} u - The search URL with {{{s}}} as the search query
  29. */
  30.  
  31. const CUSTOM_BANGS = [
  32. {
  33. s: "T3 Chat",
  34. t: "t3",
  35. u: "https://www.t3.chat/new?q={{{s}}}",
  36. },
  37. ];
  38.  
  39. const BANG_CACHE_KEY = "bangs-cache";
  40. const BANG_CACHE_EXPIRY_KEY = "bangs-cache-expiry";
  41. const CACHE_EXPIRY_MS = 1000 * 60 * 60 * 24 * 7; // 7 days
  42.  
  43. /**
  44. * Fetches the DuckDuckGo bangs from the cache or the server
  45. * @returns {Promise<Bang[]>} The list of bangs
  46. */
  47. function getBangs() {
  48. return new Promise((resolve) => {
  49. const cachedBangs = GM_getValue(BANG_CACHE_KEY);
  50. const cacheExpiry = GM_getValue(BANG_CACHE_EXPIRY_KEY, 0);
  51.  
  52. if (cachedBangs && Date.now() < cacheExpiry) {
  53. resolve([...CUSTOM_BANGS, ...cachedBangs]);
  54. return;
  55. }
  56.  
  57. GM_xmlhttpRequest({
  58. method: "GET",
  59. url: "https://duckduckgo.com/bang.js",
  60. onload: function (response) {
  61. try {
  62. const bangs = JSON.parse(response.responseText);
  63. GM_setValue(BANG_CACHE_KEY, bangs);
  64. GM_setValue(BANG_CACHE_EXPIRY_KEY, Date.now() + CACHE_EXPIRY_MS);
  65.  
  66. resolve([...CUSTOM_BANGS, ...bangs]);
  67. } catch (e) {
  68. console.error("Error parsing bangs:", e);
  69. resolve(CUSTOM_BANGS);
  70. }
  71. },
  72. onerror: function () {
  73. console.error("Failed to fetch bangs");
  74. resolve(CUSTOM_BANGS);
  75. },
  76. });
  77. });
  78. }
  79.  
  80. /**
  81. * Parses the bang from the query
  82. * @param {string} query The search query
  83. * @returns {{bang: string, cleanQuery: string} | null} The bang and the clean query
  84. */
  85. function parseBang(query) {
  86. // regex to match !bang at start or end of query
  87. const match = query.match(/^!(\S+)\s+(.+)$|^(.+)\s+!(\S+)$/i);
  88.  
  89. if (match) {
  90. if (match[1]) {
  91. // !bang query
  92. return {
  93. bang: match[1].toLowerCase(),
  94. cleanQuery: match[2].trim(),
  95. };
  96. } else {
  97. // query !bang
  98. return {
  99. bang: match[4].toLowerCase(),
  100. cleanQuery: match[3].trim(),
  101. };
  102. }
  103. }
  104.  
  105. return null;
  106. }
  107.  
  108. /**
  109. * Redirects to the search engine URL
  110. * @param {Bang[]} bangs The list of bangs
  111. * @returns {void}
  112. */
  113. function redirectToBang(bangs) {
  114. const url = new URL(window.location.href);
  115. const query = url.searchParams.get("q")?.trim() ?? "";
  116.  
  117. if (!query) return;
  118.  
  119. const bangData = parseBang(query);
  120. if (!bangData) return;
  121.  
  122. const selectedBang = bangs.find((b) => b.t === bangData.bang);
  123. if (!selectedBang) return;
  124.  
  125. // Format of the url is:
  126. // https://www.google.com/search?q={{{s}}}
  127. const searchUrl = selectedBang.u.replace(
  128. "{{{s}}}",
  129. // Replace %2F with / to fix formats like "!ghr+t3dotgg/unduck"
  130. encodeURIComponent(bangData.cleanQuery).replace(/%2F/g, "/"),
  131. );
  132.  
  133. if (searchUrl) {
  134. window.location.replace(searchUrl);
  135. }
  136. }
  137.  
  138. /**
  139. * Initializes the script
  140. * @returns {void}
  141. */
  142. async function init() {
  143. const bangs = await getBangs();
  144. redirectToBang(bangs);
  145. }
  146.  
  147. init();
  148. })();