URL Modifier for Search Engines

Modify URLs in search results of search engines

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

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