URL Modifier for Search Engines

Modify URLs in search results of search engines

当前为 2024-01-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name URL Modifier for Search Engines
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8
  5. // @description Modify URLs in search results of search engines
  6. // @author Domenic
  7. // @match *://www.google.com/search?*q=*
  8. // @match *://searx.tiekoetter.com/search*
  9. // @match *://search.disroot.org/search*
  10. // @match *://www.startpage.com/search*
  11. // @match *://www.startpage.com/sp/search*
  12. // @match *://search.brave.com/search*
  13. // @match *://duckduckgo.com
  14. // @match *://duckduckgo.com/?*q=*
  15. // @grant none
  16. // @run-at document-end
  17. // @license GPL-2.0-only
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. // Define URL modification rules with precompiled regex
  24. const urlModificationRules = [
  25. {
  26. matchRegex: new RegExp(/^https?:\/\/www\.reddit\.com(.*)/),
  27. replaceWith: 'https://old.reddit.com$1'
  28. },
  29. {
  30. matchRegex: new RegExp(/^https?:\/\/twitter\.com\/([A-Za-z_][\w]+)(\/status\/(\d+))?.*/),
  31. replaceWith: 'https://nitter.net/$1$2'
  32. },
  33. {
  34. matchRegex: new RegExp(/^https?:\/\/www\.youtube\.com\/(@[\w-]+|watch\?v=[\w-]+|playlist\?list=[\w-]+)/),
  35. replaceWith: 'https://yewtu.be/$1'
  36. },
  37. {
  38. matchRegex: new RegExp(/^https?:\/\/stackoverflow\.com(\/questions\/\d+\/.*)/),
  39. replaceWith: 'https://code.whatever.social$1'
  40. },
  41. {
  42. matchRegex: new RegExp(/^https?:\/\/(?:en.?m?|simple)\.wikipedia.org\/wiki\/(?!Special:Search)(.*)/),
  43. replaceWith: 'https://www.wikiwand.com/en/$1'
  44. },
  45. {
  46. matchRegex: new RegExp(/^https?:\/\/zh\.?m?\.wikipedia\.org\/(?:zh-hans|wiki)\/(.*)/),
  47. replaceWith: 'https://www.wikiwand.com/zh-hans/$1'
  48. },
  49. {
  50. matchRegex: new RegExp(/^https?:\/\/((\w+\.)?medium\.com\/.*)/),
  51. replaceWith: 'https://freedium.cfd/https://$1'
  52. },
  53. {
  54. matchRegex: new RegExp(/^https?:\/\/imgur.com\/(a\/)?((?!gallery)\w+)/),
  55. replaceWith: 'https://rimgo.totaldarkness.net/a/$1$2'
  56. },
  57. {
  58. matchRegex: new RegExp(/^https?:\/\/(?:(?:.*)arxiv\.org\/pdf|arxiv-export-lb\.library\.cornell\.edu\/(?:pdf|abs))\/(\d{4}\.\d{4,5}(v\d)?)(?:.*)/),
  59. replaceWith: 'https://arxiv.org/abs/$1'
  60. },
  61. {
  62. matchRegex: new RegExp(/^https?:\/\/(ieeexplore\.ieee\.org\/document\/\d+)\//),
  63. replaceWith: 'https://$1'
  64. }
  65. // Add more rules here as needed
  66. ];
  67.  
  68. // Define enhanced selector rules for each search engine
  69. const selectorRules = {
  70. 'google': [
  71. {
  72. selector: 'div.yuRUbf div span a',
  73. childSelector: 'div.byrV5b cite',
  74. updateChildText: true,
  75. useTopLevelDomain: true, // Flag for using top-level domain
  76. containProtocol: true
  77. }
  78. ],
  79. 'searx': [
  80. {
  81. selector: 'article a.url_wrapper',
  82. childSelector: '.url_i1',
  83. updateChildText: true,
  84. useTopLevelDomain: true,
  85. containProtocol: true
  86. },
  87. {
  88. selector: 'h3 a'
  89. }
  90. ],
  91. 'startpage': [
  92. {
  93. selector: 'a.w-gl__result-url.result-link',
  94. updateText: true
  95. },
  96. {
  97. selector: 'a.w-gl__result-title.result-link'
  98. }
  99. ],
  100. 'brave': [
  101. {
  102. selector: 'a.h.svelte-1dihpoi',
  103. childSelector: 'cite.snippet-url.svelte-1ygzem6 span.netloc.text-small-bold.svelte-1ygzem6',
  104. updateChildText: true,
  105. useTopLevelDomain: true,
  106. containProtocol: false
  107. }
  108. ],
  109. 'duckduckgo': [
  110. {
  111. selector: 'a.eVNpHGjtxRBq_gLOfGDr.LQNqh2U1kzYxREs65IJu'
  112. },
  113. {
  114. selector: 'a.Rn_JXVtoPVAFyGkcaXyK',
  115. childSelector: 'span',
  116. updateChildText: true,
  117. useTopLevelDomain: true,
  118. containProtocol: true
  119. }
  120. ]
  121. // Additional search engines can be defined here...
  122. };
  123.  
  124. // User-defined list of search engine instance URLs
  125. const searchEngines = {
  126. 'google': {
  127. hosts: ['www.google.com'],
  128. // search results container
  129. // you can ignore this parameter if you don't want to set it, just delete it
  130. // defult value is 'body'
  131. resultContainerSelectors: ['div.GyAeWb#rcnt']
  132. },
  133. 'searx': {
  134. hosts: [
  135. 'searx.tiekoetter.com',
  136. 'search.disroot.org'
  137. ],
  138. resultContainerSelectors: [
  139. 'main#main_results'
  140. // 'maindiv#main_results div#urls'
  141. // 'div#sidebar div#infoboxes'
  142. ]
  143. },
  144. 'startpage': {
  145. hosts: ['www.startpage.com'],
  146. resultContainerSelectors: [
  147. 'div.show-results'
  148. // 'div.sidebar-results'
  149. ]
  150. },
  151. 'brave': {
  152. hosts: ['search.brave.com'],
  153. resultContainerSelectors: [
  154. 'main.main-column'
  155. // 'aside.sidebar'
  156. ]
  157. },
  158. 'duckduckgo': {
  159. hosts: ['duckduckgo.com'],
  160. resultContainerSelectors: [
  161. 'section[data-testid="mainline"][data-area="mainline"]'
  162. // 'section[data-testid="sidebar"][data-area="sidebar"]'
  163. ]
  164. },
  165. // ... more search engines
  166. };
  167.  
  168. // Function to modify URLs and optionally text
  169. const modifyUrls = (engine) => {
  170. try {
  171. const selectors = selectorRules[engine];
  172. if (selectors) {
  173. selectors.forEach(rule => {
  174. const elements = document.querySelectorAll(rule.selector);
  175. if (elements.length > 0) {
  176. elements.forEach(element => {
  177. urlModificationRules.forEach(urlRule => {
  178. if (element.href && urlRule.matchRegex.test(element.href)) {
  179. const newHref = element.href.replace(urlRule.matchRegex, urlRule.replaceWith);
  180. element.href = newHref;
  181. updateTextContent(element, rule, newHref);
  182. }
  183. });
  184. });
  185. }
  186. });
  187. }
  188. } catch (error) {
  189. console.error("URL Modifier Script Error: ", error);
  190. }
  191. };
  192.  
  193. // Function to update text content
  194. const updateTextContent = (element, rule, newHref) => {
  195. if (rule.updateText) {
  196. element.textContent = getUpdatedText(newHref, rule);
  197. }
  198. if (rule.updateChildText && rule.childSelector) {
  199. const childElement = element.querySelector(rule.childSelector);
  200. if (childElement) {
  201. childElement.textContent = getUpdatedText(newHref, rule);
  202. }
  203. }
  204. };
  205.  
  206. // Function to get updated text
  207. const getUpdatedText = (url, rule) => {
  208. return rule.useTopLevelDomain ? extractTopLevelDomain(url, rule.containProtocol) : url;
  209. };
  210.  
  211. // Function to extract top-level domain from a URL
  212. const extractTopLevelDomain = (url, containProtocol) => {
  213. const regex = containProtocol ? /^(https?:\/\/[^\/]+)/ : /^(?:https?:\/\/)?([^\/]+)/;
  214. const matches = url.match(regex);
  215. return matches ? matches[1] : url;
  216. };
  217.  
  218. // Improved function to determine the search engine
  219. const getSearchEngineInfo = () => {
  220. try {
  221. const host = window.location.host;
  222. for (const engine in searchEngines) {
  223. if (searchEngines[engine].hosts.some(instanceHost => host.includes(instanceHost))) {
  224. const selectors = searchEngines[engine].resultContainerSelectors || ['body']; // Default to 'body' if not specified
  225. return {
  226. engine,
  227. selectors: selectors
  228. };
  229. }
  230. }
  231. } catch (error) {
  232. console.error("Error determining search engine: ", error);
  233. }
  234. };
  235.  
  236. const observeToExecute = (engine, selector) => {
  237. const resultContainers = document.querySelectorAll(selector);
  238. if (resultContainers) {
  239. resultContainers.forEach(resultContainer => {
  240. modifyUrls(engine.engine);
  241. // Observe changes in each result container
  242. const observer = new MutationObserver(() => modifyUrls(engine));
  243. observer.observe(resultContainer, { childList: true, subtree: true });
  244. });
  245. }
  246. // else {
  247. // // Check again after a short delay if the container is not found
  248. // setTimeout(() => setUpObserver(engine, selector), 500);
  249. // }
  250. };
  251.  
  252. // Run the script for the current search engine
  253. try {
  254. const engineInfo = getSearchEngineInfo();
  255. if (engineInfo) {
  256. engineInfo.selectors.forEach(containerSelector => {
  257. observeToExecute(engineInfo.engine, containerSelector);
  258. });
  259. }
  260. } catch (error) {
  261. console.error("Error executing URL Modifier Script: ", error);
  262. }
  263. })();