URL Modifier for Search Engines

Modify URLs in search results of search engines

目前为 2024-01-14 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name URL Modifier for Search Engines
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0.5
  5. // @description Modify URLs in search results of search engines
  6. // @author Domenic
  7. // @match *://www.google.com/search?*q=*
  8. // @match *://search.disroot.org/search*
  9. // @match *://searx.tiekoetter.com/search*
  10. // @match *://search.bus-hit.me/search*
  11. // @match *://search.inetol.net/search*
  12. // @match *://priv.au/search*
  13. // @match *://searx.be/search*
  14. // @match *://searxng.site/search*
  15. // @match *://search.hbubli.cc/search*
  16. // @match *://search.im-in.space/search*
  17. // @match *://opnxng.com/search*
  18. // @match *://search.upinmars.com/search*
  19. // @match *://search.sapti.me/search*
  20. // @match *://freesearch.club/search*
  21. // @match *://xo.wtf/search*
  22. // @match *://www.gruble.de/search*
  23. // @match *://searx.tuxcloud.net/search*
  24. // @match *://baresearch.org/search*
  25. // @match *://searx.daetalytica.io/search*
  26. // @match *://etsi.me/search*
  27. // @match *://search.leptons.xyz/search*
  28. // @match *://search.rowie.at/search*
  29. // @match *://search.mdosch.de/search*
  30. // @match *://searx.catfluori.de/search*
  31. // @match *://searx.si/search*
  32. // @match *://searx.namejeff.xyz/search*
  33. // @match *://search.itstechtime.com/search*
  34. // @match *://s.mble.dk/search*
  35. // @match *://searx.kutay.dev/search*
  36. // @match *://ooglester.com/search*
  37. // @match *://searx.ox2.fr/search*
  38. // @match *://searx.techsaviours.org/search*
  39. // @match *://searx.perennialte.ch/search*
  40. // @match *://s.trung.fun/search*
  41. // @match *://search.in.projectsegfau.lt/search*
  42. // @match *://search.projectsegfau.lt/search*
  43. // @match *://darmarit.org/searx/search*
  44. // @match *://searx.lunar.icu/search*
  45. // @match *://nyc1.sx.ggtyler.dev/search*
  46. // @match *://search.rhscz.eu/search*
  47. // @match *://paulgo.io/search*
  48. // @match *://northboot.xyz/search*
  49. // @match *://searx.zhenyapav.com/search*
  50. // @match *://searxng.ch/search*
  51. // @match *://copp.gg/search*
  52. // @match *://searx.sev.monster/search*
  53. // @match *://searx.oakleycord.dev/search*
  54. // @match *://searx.juancord.xyz/search*
  55. // @match *://searx.work/search*
  56. // @match *://search.ononoki.org/search*
  57. // @match *://search.demoniak.ch/search*
  58. // @match *://searx.cthd.icu/search*
  59. // @match *://searx.fmhy.net/search*
  60. // @match *://searx.headpat.exchange/search*
  61. // @match *://sex.finaltek.net/search*
  62. // @match *://search.gcomm.ch/search*
  63. // @match *://search.smnz.de/search*
  64. // @match *://searx.ankha.ac/search*
  65. // @match *://search.lvkaszus.pl/search*
  66. // @match *://searx.nobulart.com/search*
  67. // @match *://sx.t-1.org/search*
  68. // @match *://www.jabber-germany.de/searx/search*
  69. // @match *://sx.catgirl.cloud/search*
  70. // @match *://www.startpage.com/search*
  71. // @match *://www.startpage.com/sp/search*
  72. // @match *://search.brave.com/search*
  73. // @match *://duckduckgo.com
  74. // @match *://duckduckgo.com/?*q=*
  75. // @match *://metager.org/meta/meta.ger3*
  76. // @match *://metager.de/meta/meta.ger3*
  77. // @match *://www.mojeek.com/search?q=*
  78. // @match *://www.qwant.com/?q=*
  79. // @grant none
  80. // @run-at document-end
  81. // @license GPL-2.0-only
  82. // ==/UserScript==
  83.  
  84. (function() {
  85. 'use strict';
  86.  
  87. // Define URL modification rules with precompiled regex
  88. const urlModificationRules = [
  89. {
  90. matchRegex: new RegExp(/^https?:\/\/www\.reddit\.com(.*)/),
  91. replaceWith: 'https://old.reddit.com$1'
  92. },
  93. {
  94. matchRegex: new RegExp(/^https?:\/\/twitter\.com\/([A-Za-z_][\w]+)(\/status\/(\d+))?.*/),
  95. replaceWith: 'https://nitter.net/$1$2'
  96. },
  97. // {
  98. // matchRegex: new RegExp(/^https?:\/\/(?:www\.)?youtube\.com\/(@[\w-]+|watch\?v=[\w-]+|playlist\?list=[\w-]+)/),
  99. // replaceWith: 'https://yewtu.be/$1'
  100. // // replaceWith: 'https://piped.video/$1'
  101. // },
  102. // {
  103. // matchRegex: new RegExp(/^https?:\/\/stackoverflow\.com(\/questions\/\d+\/.*)/),
  104. // replaceWith: 'https://code.whatever.social$1'
  105. // },
  106. {
  107. matchRegex: new RegExp(/^https?:\/\/(?:en\.?m?|simple)\.wikipedia\.org\/wiki\/(?!Special:Search)(.*)/),
  108. replaceWith: 'https://www.wikiwand.com/en/$1'
  109. },
  110. {
  111. matchRegex: new RegExp(/^https?:\/\/zh\.?m?\.wikipedia\.org\/(?:zh-hans|wiki)\/(.*)/),
  112. replaceWith: 'https://www.wikiwand.com/zh-hans/$1'
  113. },
  114. {
  115. matchRegex: new RegExp(/^https?:\/\/((?!test)[a-z]+)\.?m?\.wikipedia\.org\/(?:[a-z]+|wiki)\/(.*)/),
  116. replaceWith: 'https://www.wikiwand.com/$1/$2'
  117. },
  118. {
  119. matchRegex: new RegExp(/^https?:\/\/((?:(?:\w+\.)?medium|towardsdatascience)\.com\/.*)/),
  120. replaceWith: 'https://freedium.cfd/https://$1'
  121. },
  122. {
  123. matchRegex: new RegExp(/^https?:\/\/imgur\.com\/(a\/)?((?!gallery)\w+)/),
  124. replaceWith: 'https://rimgo.totaldarkness.net/a/$1$2'
  125. },
  126. {
  127. matchRegex: new RegExp(/^https?:\/\/www\.npr\.org\/(?:\d{4}\/\d{2}\/\d{2}|sections)\/(?:[A-Za-z-]+\/\d{4}\/\d{2}\/\d{2}\/)?(\d+)\/.*/),
  128. replaceWith: 'https://text.npr.org/$1'
  129. },
  130. {
  131. matchRegex: new RegExp(/^https?:\/\/(?:m|www)\.imdb\.com(.*)/),
  132. replaceWith: 'https://ld.vern.cc$1'
  133. },
  134. {
  135. matchRegex: new RegExp(/^https?:\/\/(?:[a-z]+)\.slashdot\.org(.*)/),
  136. replaceWith: 'https://slashdot.org$1'
  137. },
  138. {
  139. matchRegex: new RegExp(/^https?:\/\/(?:(?:.*)arxiv\.org\/pdf|arxiv-export-lb\.library\.cornell\.edu\/(?:pdf|abs))\/(\d{4}\.\d{4,5}(v\d)?)(?:.*)/),
  140. replaceWith: 'https://arxiv.org/abs/$1'
  141. },
  142. {
  143. matchRegex: new RegExp(/^https?:\/\/(ieeexplore\.ieee\.org\/document\/\d+)\//),
  144. replaceWith: 'https://$1'
  145. },
  146. {
  147. matchRegex: new RegExp(/^https?:\/\/github\.ink\/(.*)/),
  148. replaceWith: 'https://github.com/$1'
  149. }
  150. // Add more rules here as needed
  151. ];
  152.  
  153. // Define enhanced selector rules for each search engine
  154. const selectorRules = {
  155. 'google': [
  156. {
  157. selector: 'div.yuRUbf div span a',
  158. childSelector: 'div.byrV5b cite',
  159. updateChildText: true,
  160. useTopLevelDomain: true, // Flag for using top-level domain
  161. containProtocol: true,
  162. displayMethod: 1
  163. },
  164. {
  165. // Selector for sub-results
  166. selector: 'table tr h3 a'
  167. }
  168. // ... [Other rules for Google]
  169. ],
  170. 'searx': [
  171. {
  172. selector: 'article a.url_wrapper',
  173. childSelector: 'span span',
  174. updateChildText: true,
  175. useTopLevelDomain: true,
  176. containProtocol: true,
  177. displayMethod: 1,
  178. multiElementsForUrlDisplay: true
  179. },
  180. {
  181. selector: 'h3 a'
  182. }
  183. ],
  184. 'startpage': [
  185. {
  186. selector: 'a.w-gl__result-url.result-link',
  187. updateText: true,
  188. displayMethod: 2
  189. },
  190. {
  191. selector: 'a.w-gl__result-title.result-link'
  192. }
  193. ],
  194. 'brave': [
  195. {
  196. selector: 'a.h.svelte-1dihpoi',
  197. childSelector: 'cite.snippet-url.svelte-1ygzem6 span',
  198. updateChildText: true,
  199. containProtocol: false,
  200. displayMethod: 1,
  201. multiElementsForUrlDisplay: true
  202. }
  203. ],
  204. 'duckduckgo': [
  205. {
  206. selector: 'a.eVNpHGjtxRBq_gLOfGDr.LQNqh2U1kzYxREs65IJu'
  207. },
  208. {
  209. selector: 'a.Rn_JXVtoPVAFyGkcaXyK',
  210. childSelector: 'span',
  211. updateChildText: true,
  212. containProtocol: true,
  213. displayMethod: 1,
  214. multiElementsForUrlDisplay: true
  215. },
  216. {
  217. // Selector for sub-results
  218. selector: 'ul.b269SZlC2oyR13Fcc4Iy li a.f3uDrYrWF3Exrfp1m3Og'
  219. }
  220. ],
  221. 'qwant': [
  222. {
  223. selector: 'div._35zId._3A7p7.RMB_d.eoseI a.external'
  224. },
  225. {
  226. selector: 'div._35zId._3WA-c a.external',
  227. childSelector: 'span',
  228. updateChildText: true,
  229. containProtocol: false,
  230. displayMethod: 1,
  231. multiElementsForUrlDisplay: true
  232. },
  233. {
  234. // Selector for sub-results
  235. subResultSelector: 'div._12BMd div._2-LMx._2E8gc._16lFV.Ks7KS.tCpbb.m_hqb a.external'
  236. }
  237. ],
  238. 'metager': [
  239. {
  240. selector: 'h2.result-title a'
  241. },
  242. {
  243. selector: 'div.result-subheadline a',
  244. updateText: true,
  245. containProtocol: false,
  246. displayMethod: 3
  247. }
  248. ],
  249. 'mojeek': [
  250. {
  251. selector: 'li a.ob',
  252. childSelector: 'span.url',
  253. updateChildText: true,
  254. useTopLevelDomain: true,
  255. containProtocol: true,
  256. displayMethod: 1
  257. }
  258. // ... [Other rules for Mojeek]
  259. ]
  260. // Additional search engines can be defined here...
  261. };
  262.  
  263. // User-defined list of search engine instance URLs
  264. const searchEngines = {
  265. 'google': {
  266. hosts: ['www.google.com'],
  267. // search results container
  268. // you can ignore this parameter if you don't want to set it, just delete it
  269. // defult value is 'body'
  270. resultContainerSelectors: ['div.GyAeWb#rcnt']
  271. },
  272. 'searx': {
  273. hosts: [
  274. 'search.disroot.org',
  275. 'searx.tiekoetter.com',
  276. 'search.bus-hit.me',
  277. 'search.inetol.net',
  278. 'priv.au',
  279. 'searx.be',
  280. 'searxng.site',
  281. 'search.hbubli.cc',
  282. 'search.im-in.space',
  283. 'opnxng.com',
  284. 'search.upinmars.com',
  285. 'search.sapti.me',
  286. 'freesearch.club',
  287. 'xo.wtf',
  288. 'www.gruble.de',
  289. 'searx.tuxcloud.net',
  290. 'baresearch.org',
  291. 'searx.daetalytica.io',
  292. 'etsi.me',
  293. 'search.leptons.xyz',
  294. 'search.rowie.at',
  295. 'search.mdosch.de',
  296. 'searx.catfluori.de',
  297. 'searx.si',
  298. 'searx.namejeff.xyz',
  299. 'search.itstechtime.com',
  300. 's.mble.dk',
  301. 'searx.kutay.dev',
  302. 'ooglester.com',
  303. 'searx.ox2.fr',
  304. 'searx.techsaviours.org',
  305. 'searx.perennialte.ch',
  306. 's.trung.fun',
  307. 'search.in.projectsegfau.lt',
  308. 'search.projectsegfau.lt',
  309. 'darmarit.org',
  310. 'searx.lunar.icu',
  311. 'nyc1.sx.ggtyler.dev',
  312. 'search.rhscz.eu',
  313. 'paulgo.io',
  314. 'northboot.xyz',
  315. 'searx.zhenyapav.com',
  316. 'searxng.ch',
  317. 'copp.gg',
  318. 'searx.sev.monster',
  319. 'searx.oakleycord.dev',
  320. 'searx.juancord.xyz',
  321. 'searx.work',
  322. 'search.ononoki.org',
  323. 'search.demoniak.ch',
  324. 'searx.cthd.icu',
  325. 'searx.fmhy.net',
  326. 'searx.headpat.exchange',
  327. 'sex.finaltek.net',
  328. 'search.gcomm.ch',
  329. 'search.smnz.de',
  330. 'searx.ankha.ac',
  331. 'search.lvkaszus.pl',
  332. 'searx.nobulart.com',
  333. 'sx.t-1.org',
  334. 'www.jabber-germany.de',
  335. 'sx.catgirl.cloud'
  336. ],
  337. resultContainerSelectors: [
  338. 'main#main_results'
  339. // 'maindiv#main_results div#urls'
  340. // 'div#sidebar div#infoboxes'
  341. ]
  342. },
  343. 'startpage': {
  344. hosts: ['www.startpage.com'],
  345. resultContainerSelectors: [
  346. 'div.show-results'
  347. // 'div.sidebar-results'
  348. ]
  349. },
  350. 'brave': {
  351. hosts: ['search.brave.com'],
  352. resultContainerSelectors: [
  353. 'main.main-column'
  354. // 'aside.sidebar'
  355. ]
  356. },
  357. 'duckduckgo': {
  358. hosts: ['duckduckgo.com'],
  359. resultContainerSelectors: [
  360. 'section[data-testid="mainline"][data-area="mainline"]'
  361. // 'section[data-testid="sidebar"][data-area="sidebar"]'
  362. ]
  363. },
  364. 'qwant': {
  365. hosts: ['qwant.com'],
  366. resultContainerSelectors: [
  367. 'div._35zId'
  368. ]
  369. },
  370. 'metager': {
  371. hosts: [
  372. 'metager.org',
  373. 'metager.de'
  374. ],
  375. resultContainerSelectors: ['div#results']
  376. },
  377. 'mojeek': {
  378. hosts: ['mojeek.com']
  379. }
  380. // ... more search engines
  381. };
  382.  
  383. // Function to modify URLs and optionally text
  384. const modifyUrls = (engine, observer, resultContainer) => {
  385. try {
  386. const selectors = selectorRules[engine];
  387. if (selectors) {
  388. // Disconnect the observer to prevent recursive triggering
  389. observer.disconnect();
  390.  
  391. // Modify results
  392. selectors.forEach(rule => {
  393. processElements(rule.selector, rule, engine);
  394. });
  395.  
  396. // Reconnect the observer after DOM modifications are done
  397. observer.observe(resultContainer, { childList: true, subtree: true });
  398. }
  399. } catch (error) {
  400. console.error("URL Modifier Script Error: ", error);
  401. }
  402. };
  403.  
  404. // Function to process elements based on selector and rule
  405. const processElements = (selector, rule, engine) => {
  406. const elements = document.querySelectorAll(selector);
  407. if (elements.length > 0) {
  408. elements.forEach(element => {
  409. urlModificationRules.forEach(urlRule => {
  410. if (element.href && urlRule.matchRegex.test(element.href)) {
  411. const newHref = element.href.replace(urlRule.matchRegex, urlRule.replaceWith);
  412. element.href = newHref;
  413. updateTextContent(element, rule, newHref);
  414. }
  415. });
  416. });
  417. }
  418. };
  419.  
  420. // Function to update text content
  421. const updateTextContent = (element, rule, newUrl) => {
  422. if (rule.updateText || (rule.updateChildText && rule.childSelector)) {
  423. // Special handling for DuckDuckGo and Brave
  424. if (rule.multiElementsForUrlDisplay) {
  425. updateDoubleElementContent(element, rule, newUrl);
  426. } else {
  427. // General handling for other search engines
  428. const targetElement = rule.childSelector ? element.querySelector(rule.childSelector) : element;
  429. updateSingleElementText(targetElement, rule, newUrl);
  430. }
  431. }
  432. };
  433.  
  434. // Function to clear existing content of an element
  435. const clearElementContent = (element) => {
  436. if (element) {
  437. element.textContent = '';
  438. }
  439. };
  440.  
  441. // Function to update text for multi elements (i.e. DuckDuckGo, Brave)
  442. const updateDoubleElementContent = (element, rule, newUrl) => {
  443. // Remove the "https://" protocol if containProtocol is false
  444. newUrl = rule.containProtocol ? newUrl : removeProtocol(newUrl);
  445.  
  446. let formattedUrl = formatMethod1(newUrl, 70); // Assume max length 70 for splitting
  447. let urlParts = formattedUrl.split(' › ');
  448.  
  449. // Correctly select the first and second <span> elements
  450. let spans = element.querySelectorAll(rule.childSelector);
  451.  
  452. if (spans && spans.length >= 2) {
  453. spans.forEach(clearElementContent);
  454. spans[0].textContent = urlParts[0]; // Update the first part
  455. spans[1].textContent = ' › ' + urlParts.slice(1).join(' › '); // Update the second part
  456. } else {
  457. console.error("Script: Expected structure not found for Double Element URL update!");
  458. }
  459. };
  460.  
  461. // Function to update text for a single element
  462. const updateSingleElementText = (targetElement, rule, newUrl) => {
  463. if (targetElement) {
  464. clearElementContent(targetElement);
  465. let formattedUrl = '';
  466. switch (rule.displayMethod) {
  467. case 1:
  468. formattedUrl = formatMethod1(newUrl, rule.maxLength);
  469. break;
  470. case 2:
  471. formattedUrl = newUrl; // Full URL with protocol
  472. break;
  473. case 3:
  474. formattedUrl = decodeURIComponent(removeProtocol(newUrl)); // Full URL without protocol
  475. break;
  476. }
  477. targetElement.textContent = formattedUrl;
  478. } else {
  479. console.error("Script: Expected element not found for Single Element URL update!");
  480. }
  481. };
  482.  
  483. // Function for Method 1 (Breadcrumb Style URLs), leaving 'https://' intact
  484. const formatMethod1 = (url, maxLength) => {
  485. // Split the URL while keeping 'https://' intact
  486. let parts = url.replace('https://', 'https›').split('/');
  487. parts[0] = parts[0].replace('https›', 'https://'); // Restore 'https://'
  488.  
  489. // Join the URL parts with ' › ' and check if it exceeds maxLength
  490. let joinedUrl = parts.join(' › ');
  491. if (joinedUrl.length > maxLength) {
  492. // Apply truncation based on maxLength
  493. let truncatedUrl = joinedUrl.slice(0, maxLength - 3); // Reserve space for '...'
  494. truncatedUrl += '...';
  495. joinedUrl = truncatedUrl;
  496. }
  497.  
  498. // Decode the URL to convert encoded characters to their original form
  499. return decodeURIComponent(joinedUrl);
  500. };
  501.  
  502. const removeProtocol = (url) => {
  503. return url.replace(/^https?:\/\//, '');
  504. };
  505.  
  506. // Improved function to determine the search engine
  507. const getSearchEngineInfo = () => {
  508. try {
  509. const host = window.location.host;
  510. for (const engine in searchEngines) {
  511. if (searchEngines[engine].hosts.some(instanceHost => host.includes(instanceHost))) {
  512. const selectors = searchEngines[engine].resultContainerSelectors || ['body']; // Default to 'body' if not specified
  513. return {
  514. engine,
  515. selectors: selectors
  516. };
  517. }
  518. }
  519. } catch (error) {
  520. console.error("Error determining search engine: ", error);
  521. }
  522. };
  523.  
  524. const observeToExecute = (engine, selector) => {
  525. const resultContainers = document.querySelectorAll(selector);
  526. if (resultContainers) {
  527. resultContainers.forEach(resultContainer => {
  528. // Observe changes in each result container
  529. const observer = new MutationObserver(() => modifyUrls(engine, observer, resultContainer));
  530. observer.observe(resultContainer, { childList: true, subtree: true });
  531. modifyUrls(engine, observer, resultContainer);
  532. });
  533. }
  534. };
  535.  
  536. // Run the script for the current search engine
  537. try {
  538. const engineInfo = getSearchEngineInfo();
  539. if (engineInfo) {
  540. engineInfo.selectors.forEach(containerSelector => {
  541. observeToExecute(engineInfo.engine, containerSelector);
  542. });
  543. }
  544. } catch (error) {
  545. console.error("Error executing URL Modifier Script: ", error);
  546. }
  547. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址