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