URL Modifier for Search Engines

Modify URLs in search results of search engines

目前為 2024-01-22 提交的版本,檢視 最新版本

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