URL Modifier for Search Engines

Modify URLs in search results of search engines

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

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