Lemmy Universal Link Switcher

Ensures that all URLs to Lemmy instances always point to your main/home instance.

  1. // ==UserScript==
  2. // @name Lemmy Universal Link Switcher
  3. // @namespace http://azzurite.tv/
  4. // @license GPLv3
  5. // @version 1.3.4
  6. // @description Ensures that all URLs to Lemmy instances always point to your main/home instance.
  7. // @homepageURL https://gitlab.com/azzurite/lemmy-universal-link-switcher
  8. // @supportURL https://gitlab.com/azzurite/lemmy-universal-link-switcher/-/issues
  9. // @author Azzurite
  10. // @match *://*/*
  11. // @icon https://gitlab.com/azzurite/lemmy-universal-link-switcher/-/raw/main/resources/favicon.png?inline=true
  12. // @grant GM.setValue
  13. // @grant GM.getValue
  14. // @grant GM.xmlHttpRequest
  15. // @grant GM.registerMenuCommand
  16. // @connect *
  17. // @require https://unpkg.com/@popperjs/core@2
  18. // @require https://unpkg.com/tippy.js@6
  19. // ==/UserScript==
  20.  
  21. (() => {
  22. // src/debug.js
  23. var DEBUG = false;
  24. function debug() {
  25. if (DEBUG)
  26. console.debug(`Rewriter | `, ...arguments);
  27. }
  28. function trace() {
  29. if (DEBUG === `trace`)
  30. console.debug(`Rewriter Trace | `, ...arguments);
  31. }
  32.  
  33. // src/instances.js
  34. function isLemmyInstance(url) {
  35. return isInstance(INSTANCES_LEMMY, url);
  36. }
  37. function isKbinInstance(url) {
  38. return isInstance(INSTANCES_KBIN, url);
  39. }
  40. function isInstance(instances, url) {
  41. if (url.origin) {
  42. return instances.has(url.origin);
  43. } else {
  44. return false;
  45. }
  46. }
  47. trace(`Define instances sets start`);
  48. var INSTANCES_LEMMY = /* @__PURE__ */ new Set([
  49. "http://lemmy.brdsnest.net",
  50. "https://0d.gs",
  51. "https://0xdd.org.ru",
  52. "https://1337lemmy.com",
  53. "https://acqrs.co.uk",
  54. "https://adultswim.fan",
  55. "https://aggregatet.org",
  56. "https://agoraverse.sh",
  57. "https://alien.top",
  58. "https://ani.social",
  59. "https://ascy.mooo.com",
  60. "https://aussie.zone",
  61. "https://awful.systems",
  62. "https://badatbeing.social",
  63. "https://baraza.africa",
  64. "https://bbs.9tail.net",
  65. "https://bdl.owu.one",
  66. "https://beehaw.org",
  67. "https://belfry.rip",
  68. "https://biglemmowski.win",
  69. "https://bin.pztrn.name",
  70. "https://bitforged.space",
  71. "https://blendit.bsd.cafe",
  72. "https://board.minimally.online",
  73. "https://bolha.forum",
  74. "https://bookwormstory.social",
  75. "https://borg.chat",
  76. "https://buddyverse.one",
  77. "https://campfyre.nickwebster.dev",
  78. "https://casavaga.com",
  79. "https://catata.fish",
  80. "https://champserver.net",
  81. "https://chinese.lol",
  82. "https://civilloquy.com",
  83. "https://communick.news",
  84. "https://corndog.social",
  85. "https://corrigan.space",
  86. "https://crazypeople.online",
  87. "https://dendarii.alaeron.com",
  88. "https://dev.automationwise.com",
  89. "https://diagonlemmy.social",
  90. "https://digipres.cafe",
  91. "https://discover.deltanauten.de",
  92. "https://discuss.icewind.me",
  93. "https://discuss.jacen.moe",
  94. "https://discuss.online",
  95. "https://discuss.tchncs.de",
  96. "https://distress.digital",
  97. "https://dit.reformed.social",
  98. "https://doomscroll.n8e.dev",
  99. "https://dormi.zone",
  100. "https://downonthestreet.eu",
  101. "https://drlemmy.net",
  102. "https://ds9.lemmy.ml",
  103. "https://dubvee.org",
  104. "https://dumbdomain.xyz",
  105. "https://endlesstalk.org",
  106. "https://enterprise.lemmy.ml",
  107. "https://eventfrontier.com",
  108. "https://eviltoast.org",
  109. "https://expats.zone",
  110. "https://falconry.party",
  111. "https://fanaticus.social",
  112. "https://fasheng.ing",
  113. "https://fed.dyne.org",
  114. "https://feddit.cl",
  115. "https://feddit.dk",
  116. "https://feddit.eu",
  117. "https://feddit.it",
  118. "https://feddit.nl",
  119. "https://feddit.nu",
  120. "https://feddit.org",
  121. "https://feddit.rocks",
  122. "https://feddit.site",
  123. "https://feddit.uk",
  124. "https://federation.red",
  125. "https://fedii.me",
  126. "https://fedit.pl",
  127. "https://feditown.com",
  128. "https://feed.newt.wtf",
  129. "https://fenmou.cyou",
  130. "https://fjdk.uk",
  131. "https://flamewar.social",
  132. "https://foros.fediverso.gal",
  133. "https://forum.ayom.media",
  134. "https://forum.uncomfortable.business",
  135. "https://futurology.today",
  136. "https://g00n.cloud",
  137. "https://gearhead.town",
  138. "https://gioia.news",
  139. "https://gregtech.eu",
  140. "https://group.lt",
  141. "https://h4x0r.host",
  142. "https://hackertalks.com",
  143. "https://halubilo.social",
  144. "https://happysl.app",
  145. "https://hardware.watch",
  146. "https://hexbear.net",
  147. "https://hilariouschaos.com",
  148. "https://hobbit.world",
  149. "https://hoihoi.superboi.eu.org",
  150. "https://info.prou.be",
  151. "https://infosec.pub",
  152. "https://jlai.lu",
  153. "https://krabb.org",
  154. "https://kutsuya.dev",
  155. "https://kuu.kohana.fi",
  156. "https://kyu.de",
  157. "https://l.7rg1nt.moe",
  158. "https://l.dongxi.ca",
  159. "https://l.henlo.fi",
  160. "https://l.hostux.net",
  161. "https://l.mathers.fr",
  162. "https://l.mchome.net",
  163. "https://l.roofo.cc",
  164. "https://l.shoddy.site",
  165. "https://l.sw0.com",
  166. "https://l3mmy.com",
  167. "https://lazysoci.al",
  168. "https://le.meto.lol",
  169. "https://leaf.dance",
  170. "https://lebowski.social",
  171. "https://lef.li",
  172. "https://leftopia.org",
  173. "https://lem.a3a2.uk",
  174. "https://lem.afiz.org",
  175. "https://lem.cochrun.xyz",
  176. "https://lem.free.as",
  177. "https://lem.monster",
  178. "https://lem.ph3j.com",
  179. "https://lem.serkozh.me",
  180. "https://lem.trashbrain.org",
  181. "https://lemdro.id",
  182. "https://leminal.space",
  183. "https://lemm.ee",
  184. "https://lemmings.sopelj.ca",
  185. "https://lemmings.world",
  186. "https://lemmit.online",
  187. "https://lemmus.org",
  188. "https://lemmy-api.ten4ward.social",
  189. "https://lemmy-mormonsatan-u23030.vm.elestio.app",
  190. "https://lemmy.0upti.me",
  191. "https://lemmy.100010101.xyz",
  192. "https://lemmy.4d2.org",
  193. "https://lemmy.86thumbs.net",
  194. "https://lemmy.8bitar.io",
  195. "https://lemmy.8th.world",
  196. "https://lemmy.absolutesix.com",
  197. "https://lemmy.activitypub.academy",
  198. "https://lemmy.ahall.se",
  199. "https://lemmy.aicampground.com",
  200. "https://lemmy.amethyst.name",
  201. "https://lemmy.amxl.com",
  202. "https://lemmy.ananace.dev",
  203. "https://lemmy.anonion.social",
  204. "https://lemmy.anymore.nl",
  205. "https://lemmy.asc6.org",
  206. "https://lemmy.asudox.dev",
  207. "https://lemmy.azamserver.com",
  208. "https://lemmy.baie.me",
  209. "https://lemmy.balamb.fr",
  210. "https://lemmy.beagle.quest",
  211. "https://lemmy.belegost.net",
  212. "https://lemmy.beru.co",
  213. "https://lemmy.best",
  214. "https://lemmy.bestiver.se",
  215. "https://lemmy.billiam.net",
  216. "https://lemmy.bismith.net",
  217. "https://lemmy.bit-refined.eu",
  218. "https://lemmy.blackeco.com",
  219. "https://lemmy.blahaj.zone",
  220. "https://lemmy.blugatch.tube",
  221. "https://lemmy.bmck.au",
  222. "https://lemmy.bosio.info",
  223. "https://lemmy.brad.ee",
  224. "https://lemmy.brandyapple.com",
  225. "https://lemmy.brief.guru",
  226. "https://lemmy.browntown.dev",
  227. "https://lemmy.byrdcrouse.com",
  228. "https://lemmy.byteunion.com",
  229. "https://lemmy.ca",
  230. "https://lemmy.cafe",
  231. "https://lemmy.caliban.io",
  232. "https://lemmy.cat",
  233. "https://lemmy.catgirl.biz",
  234. "https://lemmy.ch3n2k.com",
  235. "https://lemmy.chiisana.net",
  236. "https://lemmy.ciechom.eu",
  237. "https://lemmy.cixoelectronic.pl",
  238. "https://lemmy.cloudhub.social",
  239. "https://lemmy.co.nz",
  240. "https://lemmy.cogindo.net",
  241. "https://lemmy.com.tr",
  242. "https://lemmy.conorab.com",
  243. "https://lemmy.coupou.fr",
  244. "https://lemmy.crimedad.work",
  245. "https://lemmy.cringecollective.io",
  246. "https://lemmy.criticalbasics.xyz",
  247. "https://lemmy.croc.pw",
  248. "https://lemmy.cronyakatsuki.xyz",
  249. "https://lemmy.cryonex.net",
  250. "https://lemmy.csupes.page",
  251. "https://lemmy.cultimean.group",
  252. "https://lemmy.darvit.nl",
  253. "https://lemmy.dbzer0.com",
  254. "https://lemmy.decronym.xyz",
  255. "https://lemmy.deedium.nl",
  256. "https://lemmy.deepspace.gay",
  257. "https://lemmy.dexlit.xyz",
  258. "https://lemmy.digitalcharon.in",
  259. "https://lemmy.digitalfall.net",
  260. "https://lemmy.doesnotexist.club",
  261. "https://lemmy.dogboy.xyz",
  262. "https://lemmy.dormedas.com",
  263. "https://lemmy.dropdoos.nl",
  264. "https://lemmy.duck.cafe",
  265. "https://lemmy.dudeami.win",
  266. "https://lemmy.eco.br",
  267. "https://lemmy.emerald.show",
  268. "https://lemmy.enchanted.social",
  269. "https://lemmy.esquiretheduke.nohost.me",
  270. "https://lemmy.eus",
  271. "https://lemmy.evangineer.net",
  272. "https://lemmy.fait.ch",
  273. "https://lemmy.federate.cc",
  274. "https://lemmy.federate.lol",
  275. "https://lemmy.fedi.zutto.fi",
  276. "https://lemmy.fedifriends.social",
  277. "https://lemmy.fediverse.jp",
  278. "https://lemmy.fish",
  279. "https://lemmy.fosshost.com",
  280. "https://lemmy.foxden.party",
  281. "https://lemmy.freewilltiger.page",
  282. "https://lemmy.fromshado.ws",
  283. "https://lemmy.frozeninferno.xyz",
  284. "https://lemmy.funami.tech",
  285. "https://lemmy.fwgx.uk",
  286. "https://lemmy.gf4.pw",
  287. "https://lemmy.giftedmc.com",
  288. "https://lemmy.glasgow.social",
  289. "https://lemmy.graphics",
  290. "https://lemmy.greatpyramid.social",
  291. "https://lemmy.grey.fail",
  292. "https://lemmy.grys.it",
  293. "https://lemmy.hacktheplanet.be",
  294. "https://lemmy.haley.io",
  295. "https://lemmy.halfbro.xyz",
  296. "https://lemmy.helios42.de",
  297. "https://lemmy.helvetet.eu",
  298. "https://lemmy.hogru.ch",
  299. "https://lemmy.horwood.cloud",
  300. "https://lemmy.hosted.frl",
  301. "https://lemmy.hybridsarcasm.xyz",
  302. "https://lemmy.imagisphe.re",
  303. "https://lemmy.imontheweb.net",
  304. "https://lemmy.inbutts.lol",
  305. "https://lemmy.installation00.org",
  306. "https://lemmy.itsallbadsyntax.com",
  307. "https://lemmy.iys.io",
  308. "https://lemmy.jacaranda.club",
  309. "https://lemmy.jamesj999.co.uk",
  310. "https://lemmy.janiak.cc",
  311. "https://lemmy.javant.xyz",
  312. "https://lemmy.jaypg.pw",
  313. "https://lemmy.jelliefrontier.net",
  314. "https://lemmy.jhjacobs.nl",
  315. "https://lemmy.jlh.name",
  316. "https://lemmy.johnnei.org",
  317. "https://lemmy.jrvs.cc",
  318. "https://lemmy.kaytse.fun",
  319. "https://lemmy.kde.social",
  320. "https://lemmy.kfed.org",
  321. "https://lemmy.killtime.online",
  322. "https://lemmy.klein.ruhr",
  323. "https://lemmy.kmoneyserver.com",
  324. "https://lemmy.kokomo.cloud",
  325. "https://lemmy.kopieczek.com",
  326. "https://lemmy.kya.moe",
  327. "https://lemmy.laitinlok.com",
  328. "https://lemmy.lantian.pub",
  329. "https://lemmy.libertarianfellowship.org",
  330. "https://lemmy.librebun.com",
  331. "https://lemmy.linden.social",
  332. "https://lemmy.linuxuserspace.show",
  333. "https://lemmy.lqx.net",
  334. "https://lemmy.lukeog.com",
  335. "https://lemmy.lundgrensjostrom.com",
  336. "https://lemmy.magnor.ovh",
  337. "https://lemmy.mariusdavid.fr",
  338. "https://lemmy.marud.fr",
  339. "https://lemmy.masto.community",
  340. "https://lemmy.mats.ooo",
  341. "https://lemmy.max-p.me",
  342. "https://lemmy.mbl.social",
  343. "https://lemmy.mebitek.com",
  344. "https://lemmy.meissners.me",
  345. "https://lemmy.menf.in",
  346. "https://lemmy.mengsk.org",
  347. "https://lemmy.menos.gotdns.org",
  348. "https://lemmy.michaelsasser.org",
  349. "https://lemmy.mindoki.com",
  350. "https://lemmy.minecloud.ro",
  351. "https://lemmy.minie4.de",
  352. "https://lemmy.mkwarman.com",
  353. "https://lemmy.ml",
  354. "https://lemmy.mlaga97.space",
  355. "https://lemmy.mods4ever.com",
  356. "https://lemmy.monster",
  357. "https://lemmy.moocloud.party",
  358. "https://lemmy.moonling.nl",
  359. "https://lemmy.mrm.one",
  360. "https://lemmy.muffalings.com",
  361. "https://lemmy.multivers.cc",
  362. "https://lemmy.my-box.dev",
  363. "https://lemmy.myserv.one",
  364. "https://lemmy.nannoda.com",
  365. "https://lemmy.nauk.io",
  366. "https://lemmy.ndlug.org",
  367. "https://lemmy.nekusoul.de",
  368. "https://lemmy.nerdcore.social",
  369. "https://lemmy.nexus",
  370. "https://lemmy.nicknakin.com",
  371. "https://lemmy.nikore.net",
  372. "https://lemmy.nine-hells.net",
  373. "https://lemmy.noellesporn.de",
  374. "https://lemmy.nope.ly",
  375. "https://lemmy.norbipeti.eu",
  376. "https://lemmy.notmy.cloud",
  377. "https://lemmy.nowsci.com",
  378. "https://lemmy.nz",
  379. "https://lemmy.obrell.se",
  380. "https://lemmy.ohaa.xyz",
  381. "https://lemmy.okr765.com",
  382. "https://lemmy.oldtr.uk",
  383. "https://lemmy.one",
  384. "https://lemmy.onlylans.io",
  385. "https://lemmy.org",
  386. "https://lemmy.packitsolutions.net",
  387. "https://lemmy.parastor.net",
  388. "https://lemmy.paulstevens.org",
  389. "https://lemmy.pe1uca.dev",
  390. "https://lemmy.peoplever.se",
  391. "https://lemmy.petecca.com",
  392. "https://lemmy.physfluids.fr",
  393. "https://lemmy.pierre-couy.fr",
  394. "https://lemmy.pit.ninja",
  395. "https://lemmy.pixelcollider.net",
  396. "https://lemmy.plaureano.nohost.me",
  397. "https://lemmy.prograhamming.com",
  398. "https://lemmy.pt",
  399. "https://lemmy.pussthecat.org",
  400. "https://lemmy.radio",
  401. "https://lemmy.razbot.xyz",
  402. "https://lemmy.remorse.us",
  403. "https://lemmy.reysic.com",
  404. "https://lemmy.rhymelikedi.me",
  405. "https://lemmy.rimkus.it",
  406. "https://lemmy.rochegmr.com",
  407. "https://lemmy.runesmite.com",
  408. "https://lemmy.saik0.com",
  409. "https://lemmy.scam-mail.me",
  410. "https://lemmy.schlunker.com",
  411. "https://lemmy.schoenwolf-schroeder.com",
  412. "https://lemmy.sdf.org",
  413. "https://lemmy.sebbem.se",
  414. "https://lemmy.secnd.me",
  415. "https://lemmy.self-hosted.site",
  416. "https://lemmy.selfhostcat.com",
  417. "https://lemmy.services.coupou.fr",
  418. "https://lemmy.setzman.synology.me",
  419. "https://lemmy.shiny-task.com",
  420. "https://lemmy.shtuf.eu",
  421. "https://lemmy.sieprawski.pl",
  422. "https://lemmy.sietch.online",
  423. "https://lemmy.simpl.website",
  424. "https://lemmy.skoops.social",
  425. "https://lemmy.skyjake.fi",
  426. "https://lemmy.smgames.club",
  427. "https://lemmy.snoot.tube",
  428. "https://lemmy.socdojo.com",
  429. "https://lemmy.sotu.casa",
  430. "https://lemmy.spacestation14.com",
  431. "https://lemmy.spronkus.xyz",
  432. "https://lemmy.ssba.com",
  433. "https://lemmy.stad.social",
  434. "https://lemmy.staphup.nl",
  435. "https://lemmy.starlightkel.xyz",
  436. "https://lemmy.stefanoprenna.com",
  437. "https://lemmy.steken.xyz",
  438. "https://lemmy.stonansh.org",
  439. "https://lemmy.stuart.fun",
  440. "https://lemmy.studio",
  441. "https://lemmy.sudovanilla.org",
  442. "https://lemmy.sumuun.net",
  443. "https://lemmy.svlachos.duckdns.org",
  444. "https://lemmy.syrasu.com",
  445. "https://lemmy.t-rg.ws",
  446. "https://lemmy.team",
  447. "https://lemmy.technosorcery.net",
  448. "https://lemmy.technowizardry.net",
  449. "https://lemmy.telaax.com",
  450. "https://lemmy.tespia.org",
  451. "https://lemmy.teuto.icu",
  452. "https://lemmy.tf",
  453. "https://lemmy.tgxn.net",
  454. "https://lemmy.thebias.nl",
  455. "https://lemmy.thefloatinglab.world",
  456. "https://lemmy.thewooskeys.com",
  457. "https://lemmy.tobyvin.dev",
  458. "https://lemmy.today",
  459. "https://lemmy.toldi.eu",
  460. "https://lemmy.tomkoreny.com",
  461. "https://lemmy.toot.pt",
  462. "https://lemmy.trevor.coffee",
  463. "https://lemmy.trippy.pizza",
  464. "https://lemmy.ubergeek77.chat",
  465. "https://lemmy.uhhoh.com",
  466. "https://lemmy.umucat.day",
  467. "https://lemmy.unboiled.info",
  468. "https://lemmy.unfiltered.social",
  469. "https://lemmy.uninsane.org",
  470. "https://lemmy.unryzer.eu",
  471. "https://lemmy.urbanhost.top",
  472. "https://lemmy.vg",
  473. "https://lemmy.vyizis.tech",
  474. "https://lemmy.w9r.de",
  475. "https://lemmy.wentam.net",
  476. "https://lemmy.whynotdrs.org",
  477. "https://lemmy.world",
  478. "https://lemmy.wtf",
  479. "https://lemmy.xeviousx.eu",
  480. "https://lemmy.xoynq.com",
  481. "https://lemmy.yachts",
  482. "https://lemmy.z0r.co",
  483. "https://lemmy.zhukov.al",
  484. "https://lemmy.zimage.com",
  485. "https://lemmy.zip",
  486. "https://lemmy.zwanenburg.info",
  487. "https://lemmygrad.ml",
  488. "https://lemmyis.fun",
  489. "https://lemmyland.com",
  490. "https://lemmynsfw.com",
  491. "https://lemmyusa.com",
  492. "https://lemux.minnix.dev",
  493. "https://lemy.leuker.me",
  494. "https://lemy.lol",
  495. "https://lemy.nl",
  496. "https://level-up.zone",
  497. "https://libretechni.ca",
  498. "https://linkage.ds8.zone",
  499. "https://links.gayfr.online",
  500. "https://links.rocks",
  501. "https://linux.community",
  502. "https://linz.city",
  503. "https://literature.cafe",
  504. "https://lm.boing.icu",
  505. "https://lm.inu.is",
  506. "https://lm.korako.me",
  507. "https://lm.madiator.cloud",
  508. "https://lm.paradisus.day",
  509. "https://lm.sethp.cc",
  510. "https://lm.williampuckering.com",
  511. "https://lmy.sagf.io",
  512. "https://lonestarlemmy.mooo.com",
  513. "https://lsmu.schmurian.xyz",
  514. "https://lu.skbo.net",
  515. "https://lululemmy.com",
  516. "https://lx.pontual.social",
  517. "https://mander.xyz",
  518. "https://meinreddit.com",
  519. "https://metapowers.org",
  520. "https://midwest.social",
  521. "https://mimiclem.me",
  522. "https://mlem.eldritch.gift",
  523. "https://monero.town",
  524. "https://monyet.cc",
  525. "https://moose.best",
  526. "https://moto.teamswollen.org",
  527. "https://mouse.chitanda.moe",
  528. "https://mtgzone.com",
  529. "https://mujico.org",
  530. "https://news.idlestate.org",
  531. "https://no.lastname.nz",
  532. "https://nodesphere.site",
  533. "https://notdigg.com",
  534. "https://nsfwaiclub.com",
  535. "https://odin.lanofthedead.xyz",
  536. "https://orbi.camp",
  537. "https://orbiting.observer",
  538. "https://orcas.enjoying.yachts",
  539. "https://overctrl.dbzer0.com",
  540. "https://parenti.sh",
  541. "https://pawb.social",
  542. "https://ponder.cat",
  543. "https://popplesburger.hilciferous.nl",
  544. "https://poptalk.scrubbles.tech",
  545. "https://preserve.games",
  546. "https://pricefield.org",
  547. "https://programming.dev",
  548. "https://proit.org",
  549. "https://providence.root.sx",
  550. "https://quokk.au",
  551. "https://r-sauna.fi",
  552. "https://r.nf",
  553. "https://radiation.party",
  554. "https://rblind.com",
  555. "https://real.lemmy.fan",
  556. "https://realbitcoin.cash",
  557. "https://reddeet.com",
  558. "https://reddthat.com",
  559. "https://redlemmy.com",
  560. "https://rekabu.ru",
  561. "https://rentadrunk.org",
  562. "https://retarded.dev",
  563. "https://retrolemmy.com",
  564. "https://roanoke.social",
  565. "https://rollenspiel.forum",
  566. "https://rqd2.net",
  567. "https://sammich.es",
  568. "https://scribe.disroot.org",
  569. "https://selfhosted.forum",
  570. "https://sh.itjust.works",
  571. "https://sha1.nl",
  572. "https://showeq.com",
  573. "https://slangenettet.pyjam.as",
  574. "https://slrpnk.net",
  575. "https://soc.ebmn.io",
  576. "https://soccer.forum",
  577. "https://social.belowland.com",
  578. "https://social.ggbox.fr",
  579. "https://social.jears.at",
  580. "https://social.jrruethe.info",
  581. "https://social.nerdhouse.io",
  582. "https://social.p80.se",
  583. "https://social.packetloss.gg",
  584. "https://social.pwned.page",
  585. "https://social.rocketsfall.net",
  586. "https://social.sour.is",
  587. "https://social2.williamyam.com",
  588. "https://sopuli.xyz",
  589. "https://spgrn.com",
  590. "https://stammtisch.hallertau.social",
  591. "https://startrek.website",
  592. "https://sub.wetshaving.social",
  593. "https://supernova.place",
  594. "https://suppo.fi",
  595. "https://swg-empire.de",
  596. "https://switter.su",
  597. "https://szmer.info",
  598. "https://t.bobamilktea.xyz",
  599. "https://tacobu.de",
  600. "https://thelemmy.club",
  601. "https://timesink.p3nguin.org",
  602. "https://tkohhh.social",
  603. "https://toast.ooo",
  604. "https://treadst.one",
  605. "https://ttrpg.network",
  606. "https://tucson.social",
  607. "https://ukfli.uk",
  608. "https://unreachable.cloud",
  609. "https://upvote.au",
  610. "https://usenet.lol",
  611. "https://va11halla.bar",
  612. "https://vegantheoryclub.org",
  613. "https://vger.social",
  614. "https://voyager.lemmy.ml",
  615. "https://walledgarden.xyz",
  616. "https://welppp.com",
  617. "https://whemic.xyz",
  618. "https://wired.bluemarch.art",
  619. "https://x69.org",
  620. "https://xn--mh-fkaaaaaa.schuetze.link",
  621. "https://yall.theatl.social",
  622. "https://yiffit.net",
  623. "https://ythreektech.com",
  624. "https://zerobytes.monster"
  625. ]);
  626. var INSTANCES_KBIN = /* @__PURE__ */ new Set([
  627. "https://artemis.camp",
  628. "https://atakbin.spacehost.dev",
  629. "https://bin.pol.social",
  630. "https://community.yshi.org",
  631. "https://dgngrnder.com",
  632. "https://feddit.online",
  633. "https://fedinews.net",
  634. "https://fusionpatrol.social",
  635. "https://hotaudiofiction.social",
  636. "https://jlailu.social",
  637. "https://k.fe.derate.me",
  638. "https://karab.in",
  639. "https://kayb.ee",
  640. "https://kbin-u3.vm.elestio.app",
  641. "https://kbin.bitgoblin.tech",
  642. "https://kbin.cafe",
  643. "https://kbin.chat",
  644. "https://kbin.ectolab.net",
  645. "https://kbin.fedi.cr",
  646. "https://kbin.life",
  647. "https://kbin.nz",
  648. "https://kbin.pieho.me",
  649. "https://kbin.pithyphrase.net",
  650. "https://kbin.projectsegfau.lt",
  651. "https://kbin.social",
  652. "https://kbin.tenkuu.social",
  653. "https://kbin.thicknahalf.com",
  654. "https://kbin.wangwood.house",
  655. "https://kglitch.social",
  656. "https://kopnij.in",
  657. "https://kx.pontual.social",
  658. "https://link.fossdle.org",
  659. "https://longley.ws",
  660. "https://nadajnik.org",
  661. "https://polesie.pol.social",
  662. "https://rainy.place",
  663. "https://social.tath.link",
  664. "https://supermeter.social",
  665. "https://teacup.social",
  666. "https://wiku.hu"
  667. ]);
  668. trace(`Define instances sets end`);
  669.  
  670. // src/our-changes.js
  671. var OUR_CHANGES = { addedNodes: {} };
  672. function getAddedNodesSelectors() {
  673. return Object.values(OUR_CHANGES.addedNodes);
  674. }
  675. function registerAddedNode(id, selector) {
  676. OUR_CHANGES.addedNodes[id] = selector;
  677. }
  678.  
  679. // src/constants.js
  680. var constants_default = {
  681. ICON_CLASS: withNS(`icon`),
  682. ICON_LOADING_CLASS: withNS(`loading`),
  683. ICON_STYLES_ID: withNS(`icon-styles`),
  684. ICON_LINK_CLASS: withNS(`icon-link`),
  685. ICON_LINK_SYMBOL_ID: withNS(`icon-link-symbol`),
  686. ICON_SPINNER_CLASS: withNS(`icon-spinner`),
  687. ICON_SPINNER_SYMBOL_ID: withNS(`icon-spinner-symbol`),
  688. ORIGINAL_LINK_CLASS: withNS(`original-link`),
  689. SHOW_AT_HOME_BUTTON_CLASS: withNS(`show-at-home`),
  690. ICON_SVG_TEMPLATE_ID: withNS(`icon-template`),
  691. AUTH_WRONG: `AUTH_WRONG`,
  692. AUTH_MISSING: `AUTH_MISSING`,
  693. REWRITE_STATUS: withNSCamelCase(`localUrlStatus`),
  694. REWRITE_STATUS_PENDING: `pending`,
  695. REWRITE_STATUS_SUCCESS: `success`,
  696. REWRITE_STATUS_ERROR: `error`,
  697. REWRITE_STATUS_UNRESOLVED: `unresolved`,
  698. SETUP_AUTH_MESSAGE: `Lemmy Universal Link Switcher: Visit your home instance once to set up post/comment rewriting`,
  699. SETTINGS_BUTTON_ID: withNS(`open-settings-button`),
  700. SETTINGS_MENU_ID: withNS(`settings`),
  701. SETTINGS_STYLES_ID: withNS(`settings-styles`)
  702. };
  703. function withNS(identifier) {
  704. return `lemmy-rewrite-urls-` + identifier;
  705. }
  706. function withNSCamelCase(identifier) {
  707. return `lemmyRewriteUrls` + identifier.charAt(0).toUpperCase() + identifier.slice(1);
  708. }
  709.  
  710. // src/rewriting/helpers.js
  711. function isHashLink(link) {
  712. return link.hash && link.origin + link.pathname + link.search === location.origin + location.pathname + location.search;
  713. }
  714. function isSamePage(url1, url2) {
  715. return url1.host === url2.host && url1.pathname === url2.pathname;
  716. }
  717. function isV17() {
  718. return window.isoData?.site_res?.version.startsWith(`0.17`);
  719. }
  720. var stopEventHandler = (event) => {
  721. event.preventDefault();
  722. event.stopPropagation();
  723. };
  724.  
  725. // src/gm.js
  726. async function setValue(key, value) {
  727. trace(`GM.setValue key ${key}, value ${value}`);
  728. await GM.setValue(key, value);
  729. }
  730. async function getValue(key) {
  731. return await GM.getValue(key);
  732. }
  733. function parseResponse(response) {
  734. try {
  735. return JSON.parse(response.response);
  736. } catch (e) {
  737. debug(`Error parsing response JSON`, e);
  738. return response.response;
  739. }
  740. }
  741. function logRequest(response, data) {
  742. trace(
  743. `FinalUrl`,
  744. response.finalUrl,
  745. `status`,
  746. response.status,
  747. `text`,
  748. response.statusText,
  749. `response`,
  750. data || response.response
  751. );
  752. trace(`responseHeaders`, response.responseHeaders);
  753. }
  754. function performXmlHttpRequest(url, doneCallback) {
  755. GM.xmlHttpRequest({
  756. url,
  757. onloadend: (response) => {
  758. const data = parseResponse(response);
  759. logRequest(response, data);
  760. doneCallback(response, data);
  761. }
  762. });
  763. }
  764. function registerMenuCommand(name, onClick) {
  765. GM.registerMenuCommand(name, onClick);
  766. }
  767.  
  768. // src/rewriting/auth.js
  769. var AUTH;
  770. function getAuthFromCookie() {
  771. return document.cookie.split("; ").find((row) => row.startsWith("jwt="))?.split("=")[1];
  772. }
  773. async function setAuth(auth) {
  774. AUTH = auth;
  775. await setValue(`auth`, auth);
  776. }
  777. async function initAuth() {
  778. const curAuth = await getAuth();
  779. if (curAuth) {
  780. AUTH = curAuth;
  781. return;
  782. }
  783. if (location.origin === HOME) {
  784. const newAuth = getAuthFromCookie();
  785. await setAuth(newAuth);
  786. if (newAuth && await getValue(`authNoticeShown`)) {
  787. alert(`Lemmy Universal Link Switcher: Post/comment rewriting has been set up successfully`);
  788. await setValue(`authNoticeShown`, null);
  789. }
  790. } else if (HOME && !await getValue(`authNoticeShown`)) {
  791. await setValue(`authNoticeShown`, `true`);
  792. alert(constants_default.SETUP_AUTH_MESSAGE);
  793. }
  794. }
  795. function updateAuthPeriodically() {
  796. setInterval(async () => {
  797. const prev = AUTH;
  798. const newAuth = location.origin === HOME ? getAuthFromCookie() : await getAuth();
  799. if (prev !== newAuth) {
  800. debug(`Auth changed`);
  801. await setAuth(newAuth);
  802. clearMissingUrlsInCache();
  803. triggerRewrite();
  804. }
  805. }, 1234);
  806. }
  807. async function getAuth() {
  808. return await getValue(`auth`);
  809. }
  810.  
  811. // src/rewriting/url-mapping.js
  812. function splitPaths(url) {
  813. return url.pathname.split(`/`).slice(1);
  814. }
  815. function isRemoteLemmyUrl(url) {
  816. return !isHomeInstance(url) && isLemmyInstance(url);
  817. }
  818. function isRemoteKbinUrl(url) {
  819. return !isHomeInstance(url) && isKbinInstance(url);
  820. }
  821. function isRemoteUrl(url) {
  822. return isRemoteLemmyUrl(url) || isRemoteKbinUrl(url);
  823. }
  824. function findLocalUrlForStandardAtFormat(url, rootPath) {
  825. const paths = splitPaths(url);
  826. const name = paths[1].includes(`@`) ? paths[1] : paths[1] + `@` + url.host;
  827. return `${HOME}/${rootPath || paths[0]}/${name}` + url.search + url.hash;
  828. }
  829. function findLocalUrlForLemmyUrl(url) {
  830. if (isLemmyUserOrCommunityUrl(url)) {
  831. return findLocalUrlForStandardAtFormat(url);
  832. } else {
  833. return null;
  834. }
  835. }
  836. function findLocalUrlForKbinUserUrl(url) {
  837. const paths = splitPaths(url);
  838. const user = paths[1].startsWith(`@`) ? paths[1].substring(1) : paths[1];
  839. const name = user.includes(`@`) ? user : user + `@` + url.host;
  840. return `${HOME}/u/${name}` + url.search + url.hash;
  841. }
  842. function findLocalUrlForKbinUrl(url) {
  843. if (isKbinMagazineUrl(url)) {
  844. return findLocalUrlForStandardAtFormat(url, mappedKbinRootPath(url));
  845. } else if (isKbinUserUrl(url)) {
  846. return findLocalUrlForKbinUserUrl(url);
  847. } else {
  848. return null;
  849. }
  850. }
  851. function findLocalUrl(url) {
  852. if (isRemoteLemmyUrl(url))
  853. return findLocalUrlForLemmyUrl(url);
  854. if (isRemoteKbinUrl(url))
  855. return findLocalUrlForKbinUrl(url);
  856. return null;
  857. }
  858. async function fetchApIdFromRemote(url) {
  859. const endpoint = isLemmyPostUrl(url) ? `post` : `comment`;
  860. const paths = splitPaths(url);
  861. const id = paths[1];
  862. return new Promise((resolve, reject) => {
  863. performXmlHttpRequest(`${url.origin}/api/v3/${endpoint}?id=${id}`, (response, data) => {
  864. const apId = data[`${endpoint}_view`]?.[endpoint]?.ap_id;
  865. if (response.status === 200 && apId) {
  866. resolve(apId);
  867. } else {
  868. handleFailedRequest(`fetching AP ID`, response, reject);
  869. }
  870. });
  871. });
  872. }
  873. function handleFailedRequest(requestName, response, reject) {
  874. if (response.status >= 200 && response.status <= 299) {
  875. reject(`${requestName}: Unhandled successful response, status: ${response.status}`);
  876. } else if (response.status >= 400 && response.status <= 599) {
  877. reject(`${requestName}: Error, status: ${response.status}`);
  878. } else {
  879. reject(`${requestName}: Something weird happened, status: ${response.status}`);
  880. }
  881. }
  882. async function resolveObjectFromHome(url) {
  883. return new Promise(async (resolve, reject) => {
  884. const auth = await getAuth();
  885. if (!auth) {
  886. debug(`No auth token found`);
  887. reject(constants_default.AUTH_MISSING);
  888. }
  889. performXmlHttpRequest(
  890. `${HOME}/api/v3/resolve_object?auth=${auth}&q=${encodeURIComponent(url.href)}`,
  891. (response, data) => {
  892. if (response.status === 200 && data.post?.post?.id) {
  893. resolve(`${HOME}/post/${data.post.post.id}${url.search}${url.hash}`);
  894. } else if (response.status === 200 && data.comment?.comment?.id) {
  895. resolve(`${HOME}/comment/${data.comment.comment.id}${url.search}${url.hash}`);
  896. } else if (response.status === 400 && data?.error === `couldnt_find_object`) {
  897. resolve(null);
  898. } else if (response.status === 400 && data?.error === `not_logged_in`) {
  899. reject(constants_default.AUTH_WRONG);
  900. } else {
  901. handleFailedRequest(`resolving object`, response, reject);
  902. }
  903. }
  904. );
  905. });
  906. }
  907. var urlCache = {};
  908. function clearMissingUrlsInCache() {
  909. for (const value of Object.values(urlCache)) {
  910. if (value.error)
  911. delete value.error;
  912. if (value.localUrl === null)
  913. delete value.localUrl;
  914. }
  915. }
  916. function getCacheKey(url) {
  917. return url.host + url.pathname + url.search;
  918. }
  919. function cacheResult(url, localUrl) {
  920. const key = getCacheKey(url);
  921. if (!urlCache[key]) {
  922. urlCache[key] = {};
  923. }
  924. if (urlCache[key].error)
  925. delete urlCache[key].error;
  926. urlCache[key].localUrl = localUrl;
  927. return localUrl;
  928. }
  929. function cacheErrorResult(url, error) {
  930. const key = getCacheKey(url);
  931. if (!urlCache[key]) {
  932. urlCache[key] = {};
  933. }
  934. urlCache[key].error = error;
  935. }
  936. function getLocalUrlfromCache(url) {
  937. const key = getCacheKey(url);
  938. if (urlCache[key]?.error) {
  939. throw urlCache[key]?.error;
  940. } else {
  941. return urlCache[key]?.localUrl;
  942. }
  943. }
  944. async function fetchLocalUrl(url, loadFromCache = true) {
  945. if (loadFromCache) {
  946. const cached = getLocalUrlfromCache(url);
  947. if (cached !== void 0) {
  948. trace(`Found URL ${url} in cache: ${cached}`);
  949. return cached;
  950. }
  951. }
  952. try {
  953. return cacheResult(url, await fetchLocalUrlNoCache(url));
  954. } catch (e) {
  955. debug(`fetchLocalUrl error`, e);
  956. cacheErrorResult(url, e);
  957. throw e;
  958. }
  959. }
  960. async function fetchLocalUrlNoCache(url) {
  961. trace(`Trying to resolve URL ${url} directly`);
  962. const localUrl = await resolveObjectFromHome(url);
  963. if (localUrl !== null) {
  964. return localUrl;
  965. } else {
  966. trace(`Did not find URL ${url} directly`);
  967. }
  968. const apId = new URL(await fetchApIdFromRemote(url));
  969. trace(`Found AP ID for URL ${url}: ${apId}`);
  970. if (!apId.search) {
  971. apId.search = url.search;
  972. }
  973. if (!apId.hash) {
  974. apId.hash = url.hash;
  975. }
  976. if (isSamePage(url, apId)) {
  977. trace(`Previous URL was AP ID already, URL not federated for some reason`);
  978. return null;
  979. } else {
  980. return await resolveObjectFromHome(apId);
  981. }
  982. }
  983. function isInstantlyRewritable(url) {
  984. return isRemoteLemmyUrl(url) && isLemmyUserOrCommunityUrl(url) || isRemoteKbinUrl(url) && (isKbinMagazineUrl(url) || isKbinUserUrl(url));
  985. }
  986. function isRewritableAfterResolving(url) {
  987. return isRemoteLemmyUrl(url) && (isLemmyPostUrl(url) || isLemmyCommentUrl(url));
  988. }
  989. function isLemmyPostUrl(url) {
  990. const paths = splitPaths(url);
  991. return paths[0] === `post`;
  992. }
  993. function isLemmyCommentUrl(url) {
  994. const paths = splitPaths(url);
  995. return paths[0] === `comment`;
  996. }
  997. function isLemmyUserOrCommunityUrl(url) {
  998. const paths = splitPaths(url);
  999. return paths[0] === `c` || paths[0] === `u`;
  1000. }
  1001. function isKbinPostUrl(url) {
  1002. const paths = splitPaths(url);
  1003. return paths[0] === `m` && paths.length > 2 && paths[2] === `t`;
  1004. }
  1005. function isKbinMicroblogUrl(url) {
  1006. const paths = splitPaths(url);
  1007. return paths[0] === `m` && paths.length > 2 && paths[2] === `p`;
  1008. }
  1009. function isKbinMicroblogOverviewUrl(url) {
  1010. const paths = splitPaths(url);
  1011. return paths[0] === `m` && paths.length > 2 && paths[2] === `microblog`;
  1012. }
  1013. function isKbinMagazinePeopleUrl(url) {
  1014. const paths = splitPaths(url);
  1015. return paths[0] === `m` && paths.length > 2 && paths[2] === `people`;
  1016. }
  1017. function isKbinMagazineUrl(url) {
  1018. const paths = splitPaths(url);
  1019. return paths[0] === `m` && !isKbinPostUrl(url) && !isKbinMagazinePeopleUrl(url) && !isKbinMicroblogUrl(url) && !isKbinMicroblogOverviewUrl(url);
  1020. }
  1021. function mappedKbinRootPath(url) {
  1022. const paths = splitPaths(url);
  1023. if (paths[0] === `m`) {
  1024. return `c`;
  1025. } else {
  1026. return null;
  1027. }
  1028. }
  1029. function isKbinUserUrl(url) {
  1030. const paths = splitPaths(url);
  1031. return paths.length === 2 && paths[0] === `u`;
  1032. }
  1033.  
  1034. // src/rewriting/links/icon.js
  1035. function getIcon(link) {
  1036. return link.querySelector(`.` + constants_default.ICON_CLASS);
  1037. }
  1038. function createIcon(link) {
  1039. ensureTemplateAvailable();
  1040. ensureIconStylesAdded();
  1041. const wrapper = document.createElement(`span`);
  1042. registerAddedNode(constants_default.ICON_CLASS, `.` + constants_default.ICON_CLASS);
  1043. wrapper.classList.add(constants_default.ICON_CLASS);
  1044. if (link.children.length === 0 || getComputedStyle(link.children[link.children.length - 1]).marginRight === `0px`) {
  1045. wrapper.style.marginLeft = `0.5em`;
  1046. }
  1047. const linkIcon = createSVG();
  1048. linkIcon.classList.add(constants_default.ICON_LINK_CLASS);
  1049. linkIcon.innerHTML = `<use href=#${constants_default.ICON_LINK_SYMBOL_ID} />`;
  1050. wrapper.append(linkIcon);
  1051. const spinnerIcon = createSVG();
  1052. spinnerIcon.classList.add(constants_default.ICON_SPINNER_CLASS);
  1053. spinnerIcon.innerHTML = `<use href=#${constants_default.ICON_SPINNER_SYMBOL_ID} />`;
  1054. wrapper.append(spinnerIcon);
  1055. return wrapper;
  1056. }
  1057. function createSVG() {
  1058. return document.createElementNS(`http://www.w3.org/2000/svg`, `svg`);
  1059. }
  1060. function ensureTemplateAvailable() {
  1061. if (document.querySelector(`#` + constants_default.ICON_SVG_TEMPLATE_ID))
  1062. return;
  1063. const template = createSVG();
  1064. template.id = constants_default.ICON_SVG_TEMPLATE_ID;
  1065. template.innerHTML = `<defs>
  1066. <symbol id=${constants_default.ICON_LINK_SYMBOL_ID} viewBox="0 0 100 100"><path d="M52.8 34.6c.8.8 1.8 1.2 2.8 1.2s2-.4 2.8-1.2c1.5-1.5 1.5-4 0-5.6l-5.2-5.2h26v30.6c0 2.2 1.8 3.9 3.9 3.9 2.2 0 3.9-1.8 3.9-3.9V19.8c0-2.2-1.8-3.9-3.9-3.9h-30l5.2-5.2c1.5-1.5 1.5-4 0-5.6s-4-1.5-5.6 0l-11.8 12c-1.5 1.5-1.5 4 0 5.6l11.9 11.9zM31.1 28.7V11c0-3-2.5-5.5-5.5-5.5H8C5 5.5 2.5 8 2.5 11v17.7c0 3 2.5 5.5 5.5 5.5h17.7c3 0 5.4-2.5 5.4-5.5zM47.2 65.4c-1.5-1.5-4-1.5-5.6 0s-1.5 4 0 5.6l5.2 5.2h-26V45.6c0-2.2-1.8-3.9-3.9-3.9S13 43.5 13 45.6v34.5c0 2.2 1.8 3.9 3.9 3.9h30l-5.2 5.2c-1.5 1.5-1.5 4 0 5.6.8.8 1.8 1.2 2.8 1.2s2-.4 2.8-1.2l11.9-11.9c1.5-1.5 1.5-4 0-5.6l-12-11.9zM92 65.8H74.4c-3 0-5.5 2.5-5.5 5.5V89c0 3 2.5 5.5 5.5 5.5H92c3 0 5.5-2.5 5.5-5.5V71.3c0-3-2.5-5.5-5.5-5.5z"/></symbol>
  1067. <symbol id=${constants_default.ICON_SPINNER_SYMBOL_ID} viewBox="0 0 32 32"><path d="M16 32c-4.274 0-8.292-1.664-11.314-4.686s-4.686-7.040-4.686-11.314c0-3.026 0.849-5.973 2.456-8.522 1.563-2.478 3.771-4.48 6.386-5.791l1.344 2.682c-2.126 1.065-3.922 2.693-5.192 4.708-1.305 2.069-1.994 4.462-1.994 6.922 0 7.168 5.832 13 13 13s13-5.832 13-13c0-2.459-0.69-4.853-1.994-6.922-1.271-2.015-3.066-3.643-5.192-4.708l1.344-2.682c2.615 1.31 4.824 3.313 6.386 5.791 1.607 2.549 2.456 5.495 2.456 8.522 0 4.274-1.664 8.292-4.686 11.314s-7.040 4.686-11.314 4.686z"/></symbol>
  1068. </defs>`;
  1069. registerAddedNode(constants_default.ICON_SVG_TEMPLATE_ID, `#` + constants_default.ICON_SVG_TEMPLATE_ID);
  1070. document.head.append(template);
  1071. }
  1072. function ensureIconStylesAdded() {
  1073. if (document.querySelector(`#` + constants_default.ICON_STYLES_ID))
  1074. return;
  1075. const style = document.createElement(`style`);
  1076. style.id = constants_default.ICON_STYLES_ID;
  1077. style.innerHTML = `
  1078. .${constants_default.ICON_SPINNER_CLASS} {
  1079. display: none;
  1080. animation: spins 2s linear infinite;
  1081. }
  1082. .${constants_default.ICON_LINK_CLASS} {
  1083. display: inline-block;
  1084. }
  1085. .${constants_default.ICON_LOADING_CLASS} > .${constants_default.ICON_LINK_CLASS} {
  1086. display: none;
  1087. }
  1088. .${constants_default.ICON_LOADING_CLASS} > .${constants_default.ICON_SPINNER_CLASS} {
  1089. display: inline-block;
  1090. }
  1091. .${constants_default.ICON_CLASS} > svg {
  1092. vertical-align: sub;
  1093. height: 1em; width: 1em;
  1094. stroke: currentColor;
  1095. fill: currentColor;
  1096. }`;
  1097. registerAddedNode(constants_default.ICON_STYLES_ID, `#` + constants_default.ICON_STYLES_ID);
  1098. document.head.append(style);
  1099. }
  1100.  
  1101. // src/tippy.js
  1102. var tippy_default = window.tippy;
  1103.  
  1104. // src/rewriting/links/tooltip.js
  1105. function getOriginalLinkHtml(originalHref) {
  1106. registerAddedNode(constants_default.ORIGINAL_LINK_CLASS, `.` + constants_default.ORIGINAL_LINK_CLASS);
  1107. return `Original link: <a class="${constants_default.ORIGINAL_LINK_CLASS}" href="${originalHref}">${originalHref}</a>`;
  1108. }
  1109. function defaultOptions(link) {
  1110. return {
  1111. appendTo: () => link.parentNode,
  1112. allowHTML: true,
  1113. interactive: true,
  1114. animation: false,
  1115. placement: "bottom",
  1116. hideOnClick: false
  1117. };
  1118. }
  1119. function createOriginalLinkTooltip(link, originalHref) {
  1120. trace(`Create original link tooltip`, link, originalHref);
  1121. getIcon(link).addEventListener(`click`, stopEventHandler);
  1122. return createLinkTooltip(link, getOriginalLinkHtml(originalHref));
  1123. }
  1124. function createLinkTooltip(link, content) {
  1125. return tippy_default(getIcon(link), {
  1126. ...defaultOptions(link),
  1127. content
  1128. });
  1129. }
  1130. function createLinkLoadTooltip(link) {
  1131. trace(`Create link load tooltip`, link);
  1132. getIcon(link).classList.add(constants_default.ICON_LOADING_CLASS);
  1133. return createLinkTooltip(link, `Loading home URL...<br />Don't want to wait? ${getOriginalLinkHtml(link.href)}`);
  1134. }
  1135. function linkLoadTooltipSuccess(tooltip, originalHref) {
  1136. linkLoadResult(tooltip, `\u2714\uFE0F Changed link to home instance`, getOriginalLinkHtml(originalHref));
  1137. }
  1138. function linkLoadTooltipError(tooltip, error) {
  1139. linkLoadResult(tooltip, `\u274C ` + error);
  1140. }
  1141. function linkLoadResult(tooltip, result, finalContent = result) {
  1142. const icon = tooltip.reference;
  1143. icon.classList.remove(constants_default.ICON_LOADING_CLASS);
  1144. icon.addEventListener(`click`, stopEventHandler);
  1145. if (tooltip.state.isVisible) {
  1146. tooltip.setContent(result);
  1147. setTimeout(() => {
  1148. tooltip.hide();
  1149. tooltip.setContent(finalContent);
  1150. }, 2e3);
  1151. } else {
  1152. tooltip.setContent(finalContent);
  1153. }
  1154. }
  1155.  
  1156. // src/rewriting/links/links.js
  1157. function changeLinkHref(link, localUrl) {
  1158. const treeWalker = document.createTreeWalker(link, NodeFilter.SHOW_TEXT, (node) => {
  1159. if (node.textContent.toLowerCase().trim() === link.href.toLowerCase().trim()) {
  1160. return NodeFilter.FILTER_ACCEPT;
  1161. } else {
  1162. return NodeFilter.FILTER_SKIP;
  1163. }
  1164. });
  1165. let textNode;
  1166. while ((textNode = treeWalker.nextNode()) !== null) {
  1167. textNode.textContent = localUrl;
  1168. }
  1169. link.href = localUrl;
  1170. link.addEventListener(`click`, (event) => {
  1171. if (event.button === 0 && !event.ctrlKey && !event.metaKey && link.target !== `_blank`) {
  1172. location.href = localUrl;
  1173. }
  1174. });
  1175. }
  1176. function appendIconTo(elem, icon) {
  1177. if (elem.children.length === 0 || getComputedStyle(elem.children[elem.children.length - 1]).display !== `inline-block`) {
  1178. elem.append(icon);
  1179. } else {
  1180. appendIconTo(elem.children[elem.children.length - 1], icon);
  1181. }
  1182. }
  1183. function addFetchLocalUrlHandler(link) {
  1184. let tooltip;
  1185. const handler = async (event) => {
  1186. if (event.type === `click`) {
  1187. stopEventHandler(event);
  1188. if (tooltip)
  1189. tooltip.show();
  1190. return;
  1191. }
  1192. link.removeEventListener(`focus`, handler);
  1193. link.removeEventListener(`mouseenter`, handler);
  1194. if (link.dataset[constants_default.REWRITE_STATUS] === constants_default.REWRITE_STATUS_PENDING)
  1195. return;
  1196. link.dataset[constants_default.REWRITE_STATUS] = constants_default.REWRITE_STATUS_PENDING;
  1197. tooltip = createLinkLoadTooltip(link);
  1198. try {
  1199. const localUrl = await fetchLocalUrl(link);
  1200. if (!localUrl) {
  1201. debug(`Local URL for ${link.href} could not be found`);
  1202. linkLoadTooltipError(tooltip, `Home URL could not be found`);
  1203. return;
  1204. }
  1205. trace(`Local URL for ${link.href} found: ${localUrl}`);
  1206. const oldHref = link.href;
  1207. changeLinkHref(link, localUrl);
  1208. linkLoadTooltipSuccess(tooltip, oldHref);
  1209. link.dataset[constants_default.REWRITE_STATUS] = constants_default.REWRITE_STATUS_SUCCESS;
  1210. } catch (e) {
  1211. debug(`Error while trying to resolve local URL`, e);
  1212. let msg;
  1213. if (e === constants_default.AUTH_WRONG) {
  1214. msg = `Saved login expired. Return to your home instance and log in again.`;
  1215. } else if (e === constants_default.AUTH_MISSING) {
  1216. msg = constants_default.SETUP_AUTH_MESSAGE;
  1217. } else {
  1218. msg = `Error while trying to find home URL`;
  1219. }
  1220. linkLoadTooltipError(tooltip, msg);
  1221. link.dataset[constants_default.REWRITE_STATUS] = constants_default.REWRITE_STATUS_ERROR;
  1222. } finally {
  1223. link.removeEventListener(`click`, handler);
  1224. }
  1225. };
  1226. link.addEventListener(`click`, handler);
  1227. link.addEventListener(`focus`, handler);
  1228. link.addEventListener(`mouseenter`, handler);
  1229. }
  1230. function isFediverseLink(link) {
  1231. const svg = link.querySelector(`svg`);
  1232. if (!svg)
  1233. return false;
  1234. if (svg.children.length === 0)
  1235. return false;
  1236. return svg.children[0].getAttribute(`xlink:href`)?.includes(`#icon-fedilink`);
  1237. }
  1238. function rewriteToLocal(link) {
  1239. if (!link.parentNode)
  1240. return false;
  1241. if (link.classList.contains(constants_default.ORIGINAL_LINK_CLASS))
  1242. return false;
  1243. if (link.dataset[constants_default.REWRITE_STATUS] === constants_default.REWRITE_STATUS_SUCCESS)
  1244. return false;
  1245. if (isHashLink(link))
  1246. return false;
  1247. if (!isRemoteUrl(link))
  1248. return false;
  1249. if (isFediverseLink(link))
  1250. return false;
  1251. if (isInstantlyRewritable(link)) {
  1252. const localUrl = findLocalUrl(link);
  1253. if (!localUrl)
  1254. return false;
  1255. if (isSamePage(new URL(localUrl), location))
  1256. return false;
  1257. const oldHref = link.href;
  1258. changeLinkHref(link, localUrl);
  1259. const icon = createIcon(link);
  1260. appendIconTo(link, icon);
  1261. createOriginalLinkTooltip(link, oldHref);
  1262. link.dataset[constants_default.REWRITE_STATUS] = constants_default.REWRITE_STATUS_SUCCESS;
  1263. trace(`Rewrite link`, link, ` from`, oldHref, `to`, localUrl);
  1264. return true;
  1265. } else if (isRewritableAfterResolving(link)) {
  1266. if (!getIcon(link)) {
  1267. appendIconTo(link, createIcon(link));
  1268. }
  1269. if (!link.dataset[constants_default.REWRITE_STATUS]) {
  1270. link.dataset[constants_default.REWRITE_STATUS] = constants_default.REWRITE_STATUS_UNRESOLVED;
  1271. addFetchLocalUrlHandler(link);
  1272. }
  1273. }
  1274. }
  1275. function findLinksInChange(change) {
  1276. if (change.type === `childList`) {
  1277. const links = Array.from(change.addedNodes).flatMap((addedNode) => {
  1278. if (addedNode.tagName?.toLowerCase() === `a`) {
  1279. return addedNode;
  1280. } else if (addedNode.querySelectorAll) {
  1281. return Array.from(addedNode.querySelectorAll(`a`));
  1282. } else {
  1283. return [];
  1284. }
  1285. });
  1286. if (links.length > 0)
  1287. trace(`Change`, change, `contained the links`, links);
  1288. return links;
  1289. } else if (change.type === `attributes`) {
  1290. return change.target.matches?.(`a`) ? change.target : [];
  1291. } else {
  1292. return [];
  1293. }
  1294. }
  1295. function findLinksToRewrite(changes) {
  1296. if (!changes) {
  1297. return document.querySelectorAll(`a`);
  1298. }
  1299. return changes.flatMap(findLinksInChange);
  1300. }
  1301. async function rewriteLinksToLocal(changes) {
  1302. const links = findLinksToRewrite(changes);
  1303. const chunkSize = 50;
  1304. return await async function processChunk(currentChunk) {
  1305. const startIdx = currentChunk * chunkSize;
  1306. const endChunkIdx = (currentChunk + 1) * chunkSize;
  1307. const endIdx = Math.min(links.length, endChunkIdx);
  1308. debug(
  1309. `Processing ${links.length} links, current chunk `,
  1310. currentChunk,
  1311. `processing links ${startIdx} to ${endIdx}`
  1312. );
  1313. let anyRewritten = false;
  1314. for (let i = startIdx; i < endIdx; ++i) {
  1315. const rewritten = rewriteToLocal(links[i]);
  1316. anyRewritten = anyRewritten || rewritten;
  1317. }
  1318. debug(`Processed links ${startIdx} to ${endIdx}`);
  1319. if (endChunkIdx >= links.length) {
  1320. return anyRewritten;
  1321. }
  1322. const chunkResult = await new Promise((resolve) => setTimeout(async () => {
  1323. resolve(await processChunk(currentChunk + 1));
  1324. }, 0));
  1325. return anyRewritten || chunkResult;
  1326. }(0);
  1327. }
  1328.  
  1329. // src/settings.js
  1330. var refreshMethods = [];
  1331. async function refresh() {
  1332. for (const refreshMethod of refreshMethods) {
  1333. await refreshMethod();
  1334. }
  1335. }
  1336. function closeSettings() {
  1337. document.querySelector(`#` + constants_default.SETTINGS_MENU_ID)?.remove();
  1338. refreshMethods = [];
  1339. }
  1340. function initSettings() {
  1341. registerMenuCommand(`Open Settings`, showSettings);
  1342. }
  1343. function styles(elem, style) {
  1344. for (const [prop, val] of Object.entries(style)) {
  1345. elem.style[prop] = val;
  1346. }
  1347. }
  1348. var defaultBorder = `1px solid #AAA`;
  1349. function createButton(content, options = {}) {
  1350. const button = document.createElement(`div`);
  1351. button.innerHTML = content;
  1352. styles(button, {
  1353. display: options.inline ? `inline-block` : `block`,
  1354. backgroundColor: `#666`,
  1355. textAlign: `center`,
  1356. border: defaultBorder,
  1357. cursor: `pointer`,
  1358. borderRadius: `8px 8px`,
  1359. padding: `5px`
  1360. });
  1361. return button;
  1362. }
  1363. function ensureMenuStylesAdded() {
  1364. if (document.querySelector(`#` + constants_default.SETTINGS_STYLES_ID))
  1365. return;
  1366. const style = document.createElement(`style`);
  1367. style.id = constants_default.SETTINGS_STYLES_ID;
  1368. style.innerHTML = `
  1369. #${constants_default.SETTINGS_MENU_ID} * {
  1370. box-sizing: border-box;
  1371. }
  1372. `;
  1373. document.head.append(style);
  1374. }
  1375. function showSettings() {
  1376. if (document.querySelector(`#` + constants_default.SETTINGS_MENU_ID))
  1377. return;
  1378. if (window !== window.top)
  1379. return;
  1380. ensureMenuStylesAdded();
  1381. const background = document.createElement(`div`);
  1382. background.id = constants_default.SETTINGS_MENU_ID;
  1383. registerAddedNode(constants_default.SETTINGS_MENU_ID, `#` + constants_default.SETTINGS_MENU_ID);
  1384. styles(background, {
  1385. position: `fixed`,
  1386. top: `0`,
  1387. left: `0`,
  1388. zIndex: 9999,
  1389. width: `100vw`,
  1390. height: `100vh`,
  1391. backgroundColor: `#00000099`,
  1392. backdropFilter: `blur(6px)`,
  1393. display: `grid`,
  1394. grid: `grid`,
  1395. alignItems: `center`,
  1396. justifyContent: `space-around`
  1397. });
  1398. background.addEventListener(`click`, (event) => {
  1399. if (event.target === background) {
  1400. closeSettings();
  1401. }
  1402. });
  1403. const menu = document.createElement(`div`);
  1404. styles(menu, {
  1405. position: `relative`,
  1406. color: `#ddd`,
  1407. maxWidth: `90vw`,
  1408. maxHeight: `90vh`,
  1409. backgroundColor: `#555`,
  1410. padding: `20px`,
  1411. border: defaultBorder,
  1412. borderRadius: `8px`,
  1413. overflow: `hidden auto`
  1414. });
  1415. background.append(menu);
  1416. const closeButton = createButton(`\u{1F5D9}`);
  1417. styles(closeButton, {
  1418. position: `absolute`,
  1419. top: `-1px`,
  1420. right: `-1px`,
  1421. width: `30px`,
  1422. height: `30px`,
  1423. lineHeight: `25px`,
  1424. fontSize: `25px`,
  1425. borderRadius: `0 8px`,
  1426. padding: 0
  1427. });
  1428. closeButton.addEventListener(`click`, () => closeSettings());
  1429. menu.append(closeButton);
  1430. const menuHeader = createSettingHeader(`Lemmy Universal Link Switcher Settings`, 1.5);
  1431. styles(menuHeader, {
  1432. textDecoration: `underline`,
  1433. marginTop: 0
  1434. });
  1435. menu.append(menuHeader);
  1436. addMainHomeInstanceSetting(menu);
  1437. addMoreHomeInstancesSettings(menu);
  1438. document.body.append(background);
  1439. }
  1440. function createSettingHeader(text, sizeMultiplicator = 1) {
  1441. const header = document.createElement(`div`);
  1442. header.innerHTML = text;
  1443. styles(header, {
  1444. marginRight: `10px`,
  1445. fontSize: `${sizeMultiplicator * 1.4}em`,
  1446. lineHeight: `${sizeMultiplicator * 1.4}em`,
  1447. fontWeight: `bold`,
  1448. marginTop: `${sizeMultiplicator * 25}px`,
  1449. marginBottom: `${sizeMultiplicator * 10}px`
  1450. });
  1451. return header;
  1452. }
  1453. function addInfo(elem, content) {
  1454. const info = document.createElement(`span`);
  1455. info.innerHTML = ` \u{1F6C8}`;
  1456. tippy_default(info, {
  1457. content,
  1458. placement: "bottom",
  1459. triggerTarget: elem
  1460. });
  1461. elem.append(info);
  1462. }
  1463. function createInput(options) {
  1464. const inputElement = document.createElement(`input`);
  1465. styles(inputElement, {
  1466. width: `100%`,
  1467. margin: 0
  1468. });
  1469. if (options.placeholder) {
  1470. inputElement.placeholder = options.placeholder;
  1471. }
  1472. if (options.getter) {
  1473. Promise.resolve(options.getter()).then((result) => inputElement.value = result);
  1474. }
  1475. if (!inputElement.value) {
  1476. inputElement.setCustomValidity(`empty`);
  1477. }
  1478. styles(inputElement, { border: defaultBorder });
  1479. if (!options.validator && !options.setter) {
  1480. return;
  1481. }
  1482. let validator = options.validator || (() => true);
  1483. let setter = options.setter || (() => {
  1484. });
  1485. inputElement.addEventListener(`input`, async () => {
  1486. const validated = await validator(inputElement.value);
  1487. if (!inputElement.value || validated) {
  1488. inputElement.setCustomValidity(`empty`);
  1489. styles(inputElement, {
  1490. border: defaultBorder
  1491. });
  1492. }
  1493. if (inputElement.value) {
  1494. if (validated) {
  1495. inputElement.setCustomValidity(``);
  1496. await setter(inputElement.value);
  1497. } else if (!validated) {
  1498. inputElement.setCustomValidity(`fail`);
  1499. styles(inputElement, {
  1500. border: `1px solid red`
  1501. });
  1502. }
  1503. }
  1504. });
  1505. return inputElement;
  1506. }
  1507. var urlValidator = (value) => {
  1508. try {
  1509. new URL(value);
  1510. return true;
  1511. } catch (e) {
  1512. return false;
  1513. }
  1514. };
  1515. var instancePlaceholder = `The full link (including http(s)://) to the instance`;
  1516. function addMainHomeInstanceSetting(addTo) {
  1517. const header = createSettingHeader(`Main Home Instance`);
  1518. addInfo(header, `All links will be rewritten to this instance, except for links to your secondary home instances.`);
  1519. addTo.append(header);
  1520. const mainInstance = createInput({
  1521. getter: () => HOME,
  1522. setter: (value) => {
  1523. const url = new URL(value);
  1524. if (HOME !== url.origin) {
  1525. setHome(url.origin);
  1526. refresh();
  1527. }
  1528. },
  1529. validator: urlValidator,
  1530. placeholder: instancePlaceholder
  1531. });
  1532. addTo.append(mainInstance);
  1533. const homeButtonWrapper = document.createElement(`div`);
  1534. const refreshMakeHomeButton = () => {
  1535. homeButtonWrapper.replaceChildren();
  1536. if (location.origin === HOME)
  1537. return;
  1538. const makeHomeButton = createButton(`Use current page as home instance`);
  1539. styles(makeHomeButton, {
  1540. borderRadius: `0 0 8px 8px`
  1541. });
  1542. makeHomeButton.addEventListener(`click`, () => {
  1543. mainInstance.value = location.origin;
  1544. mainInstance.dispatchEvent(new Event(`input`));
  1545. });
  1546. homeButtonWrapper.append(makeHomeButton);
  1547. };
  1548. refreshMakeHomeButton();
  1549. refreshMethods.push(refreshMakeHomeButton);
  1550. addTo.append(homeButtonWrapper);
  1551. }
  1552. async function addMoreHomeInstancesSettings(addTo) {
  1553. const header = createSettingHeader(`Secondary Home Instances`);
  1554. addInfo(header, `All links pointing to these instances will not be changed.`);
  1555. addTo.append(header);
  1556. addTo.append(createListInput(
  1557. async () => await getSecondaryHomeInstances(),
  1558. async (value) => {
  1559. const homeInstances = await getSecondaryHomeInstances();
  1560. homeInstances.push(new URL(value).origin);
  1561. await setSecondaryHomeInstances(homeInstances);
  1562. },
  1563. async (value) => {
  1564. const homeInstances = await getSecondaryHomeInstances();
  1565. var index = homeInstances.indexOf(new URL(value).origin);
  1566. if (index > -1) {
  1567. homeInstances.splice(index, 1);
  1568. }
  1569. await setSecondaryHomeInstances(homeInstances);
  1570. }
  1571. ));
  1572. }
  1573. function createListItem(item, onClick) {
  1574. const listItem = createButton(item + ` \u{1F5D9}`, { inline: true });
  1575. styles(listItem, {
  1576. margin: `0px 5px 5px 0`
  1577. });
  1578. listItem.addEventListener(`click`, onClick);
  1579. return listItem;
  1580. }
  1581. function createListInput(getter, add, remove) {
  1582. const wrapper = document.createElement(`div`);
  1583. const list = document.createElement(`div`);
  1584. styles(list, {
  1585. marginBottom: `8px`
  1586. });
  1587. const refreshList = async () => {
  1588. const items = (await getter()).sort().map((item) => createListItem(item, async () => {
  1589. await remove(item);
  1590. refresh();
  1591. }));
  1592. if (items.length) {
  1593. list.replaceChildren(...items);
  1594. } else {
  1595. list.replaceChildren(`<None>`);
  1596. }
  1597. };
  1598. refreshList();
  1599. refreshMethods.push(refreshList);
  1600. wrapper.append(list);
  1601. const addInput = createInput({
  1602. validator: async (value) => {
  1603. return urlValidator(value) && !(await getter()).includes(value);
  1604. },
  1605. placeholder: instancePlaceholder
  1606. });
  1607. wrapper.append(addInput);
  1608. const buttonWrapper = document.createElement(`div`);
  1609. const refreshButtons = async () => {
  1610. buttonWrapper.replaceChildren();
  1611. const isCurrentPageHome = HOME === location.origin || (await getter()).includes(location.origin);
  1612. const addButton2 = createButton(`Add`);
  1613. styles(addButton2, {
  1614. borderRadius: isCurrentPageHome ? `0 0 8px 8px` : `0`
  1615. });
  1616. addButton2.addEventListener(`click`, async () => {
  1617. if (addInput.validity.valid) {
  1618. await add(addInput.value);
  1619. addInput.value = ``;
  1620. refresh();
  1621. }
  1622. });
  1623. buttonWrapper.append(addButton2);
  1624. if (!isCurrentPageHome) {
  1625. const addCurrentButton = createButton(`Add current page`);
  1626. styles(addCurrentButton, {
  1627. borderRadius: `0 0 8px 8px`
  1628. });
  1629. addCurrentButton.addEventListener(`click`, async () => {
  1630. await add(location.origin);
  1631. addInput.value = ``;
  1632. refresh();
  1633. });
  1634. buttonWrapper.append(addCurrentButton);
  1635. }
  1636. };
  1637. refreshButtons();
  1638. refreshMethods.push(refreshButtons);
  1639. wrapper.append(buttonWrapper);
  1640. return wrapper;
  1641. }
  1642.  
  1643. // src/rewriting/settings-buttons.js
  1644. function addSettingsButton() {
  1645. if (location.pathname !== `/settings`)
  1646. return false;
  1647. if (!document.querySelector(`[name="Description"][content="Lemmy"]`))
  1648. return false;
  1649. if (document.querySelector(`#` + constants_default.SETTINGS_BUTTON_ID))
  1650. return false;
  1651. const insertAfter = document.querySelector(`#user-password`)?.closest(`.card`);
  1652. if (!insertAfter)
  1653. return;
  1654. const button = document.createElement(`button`);
  1655. button.id = constants_default.SETTINGS_BUTTON_ID;
  1656. button.setAttribute(`class`, `btn btn-block btn-primary mr-4 w-100`);
  1657. button.innerHTML = `Lemmy Universal Link Switcher Settings`;
  1658. button.addEventListener(`click`, showSettings);
  1659. registerAddedNode(constants_default.SETTINGS_BUTTON_ID, `#` + constants_default.SETTINGS_BUTTON_ID);
  1660. insertAfter.insertAdjacentElement("afterend", button);
  1661. return true;
  1662. }
  1663.  
  1664. // src/rewriting/show-at-home.js
  1665. function showAtHomeButtonText() {
  1666. const host = new URL(HOME).hostname;
  1667. return `Show on ${host}`;
  1668. }
  1669. function createShowAtHomeAnchor(localUrl) {
  1670. const showAtHome = document.createElement(`a`);
  1671. showAtHome.dataset.creationHref = location.href;
  1672. showAtHome.classList.add(constants_default.SHOW_AT_HOME_BUTTON_CLASS);
  1673. showAtHome.innerHTML = showAtHomeButtonText();
  1674. showAtHome.href = localUrl;
  1675. registerAddedNode(constants_default.SHOW_AT_HOME_BUTTON_CLASS, `.` + constants_default.SHOW_AT_HOME_BUTTON_CLASS);
  1676. return showAtHome;
  1677. }
  1678. function addLemmyShowAtHomeButton(localUrl) {
  1679. const logo = document.querySelector(`a.navbar-brand`);
  1680. const navbarIcons = isV17() ? document.querySelector(`[title="Search"]`)?.closest(`.navbar-nav`) : document.querySelector(`#navbarIcons`);
  1681. if (!logo || !navbarIcons) {
  1682. debug(`Could not find position to insert ShowAtHomeButton at`);
  1683. return false;
  1684. }
  1685. const mobile = createShowAtHomeAnchor(localUrl);
  1686. mobile.classList.add(`d-md-none`);
  1687. mobile.style[`margin-right`] = `8px`;
  1688. mobile.style[`margin-left`] = `auto`;
  1689. if (isV17()) {
  1690. document.querySelector(`.navbar-nav.ml-auto`)?.classList.remove(`ml-auto`);
  1691. }
  1692. logo.insertAdjacentElement("afterend", mobile);
  1693. const desktop = createShowAtHomeAnchor(localUrl);
  1694. desktop.classList.add(`d-md-inline`);
  1695. desktop.style[`margin-right`] = `8px`;
  1696. navbarIcons.insertAdjacentElement("beforebegin", desktop);
  1697. return true;
  1698. }
  1699. function addKbinShowAtHomeButton(localUrl) {
  1700. const prependTo = document.querySelector(`#header menu:not(.head-nav__menu)`);
  1701. if (!prependTo) {
  1702. debug(`Could not find position to insert ShowAtHomeButton at`);
  1703. return false;
  1704. }
  1705. const item = document.createElement(`li`);
  1706. item.append(createShowAtHomeAnchor(localUrl));
  1707. prependTo.prepend(item);
  1708. return true;
  1709. }
  1710. function addButton(localUrl) {
  1711. const oldButton = document.querySelectorAll(`.` + constants_default.SHOW_AT_HOME_BUTTON_CLASS);
  1712. if (oldButton.length > 0 && oldButton[0].dataset.creationHref !== location.href) {
  1713. debug(`Removing old show at home button`);
  1714. oldButton.forEach((btn) => btn.remove());
  1715. } else if (oldButton.length > 0) {
  1716. debug(`Old show at home button still exists`);
  1717. return false;
  1718. }
  1719. if (!localUrl) {
  1720. debug(`No local URL for show at home button found`);
  1721. return false;
  1722. } else if (isRemoteLemmyUrl(location)) {
  1723. return addLemmyShowAtHomeButton(localUrl);
  1724. } else if (isRemoteKbinUrl(location)) {
  1725. return addKbinShowAtHomeButton(localUrl);
  1726. } else {
  1727. return false;
  1728. }
  1729. }
  1730. async function addShowAtHomeButton() {
  1731. if (isInstantlyRewritable(location)) {
  1732. return addButton(findLocalUrl(location));
  1733. } else if (isRewritableAfterResolving(location)) {
  1734. try {
  1735. return addButton(await fetchLocalUrl(location));
  1736. } catch (e) {
  1737. debug(`Error while trying to add "show at home" button`, e);
  1738. }
  1739. }
  1740. }
  1741.  
  1742. // src/rewriting/rewrite.js
  1743. function triggerRewrite() {
  1744. doAllDomChanges();
  1745. }
  1746. function isOrHasOurAddedNode(node) {
  1747. return getAddedNodesSelectors().some((selector) => node.matches?.(selector) || node.querySelector?.(selector));
  1748. }
  1749. function isIrrelevantChange(change) {
  1750. if (change.type === `childList`) {
  1751. if (Array.from(change.removedNodes).some(isOrHasOurAddedNode)) {
  1752. trace(`Change`, change, `is relevant because a removed node is/has ours`);
  1753. return false;
  1754. }
  1755. if (!Array.from(change.addedNodes).every(isOrHasOurAddedNode)) {
  1756. trace(`Change`, change, `is relevant because not every added node is/has ours`);
  1757. return false;
  1758. }
  1759. } else if (change.type === `attributes` && isRemoteUrl(new URL(change.target.href, location.origin))) {
  1760. trace(`Change`, change, `is relevant because href changed to a remote URL`);
  1761. return false;
  1762. }
  1763. trace(`Change`, change, `is irrelevant`);
  1764. return true;
  1765. }
  1766. async function startRewriting() {
  1767. new MutationObserver((changes, observer) => {
  1768. debug(`MutationObserver triggered`, changes);
  1769. if (changes.every(isIrrelevantChange)) {
  1770. debug(`All observed changes are irrelevant`);
  1771. return;
  1772. }
  1773. doAllDomChanges(changes);
  1774. }).observe(document.body, {
  1775. subtree: true,
  1776. childList: true,
  1777. attributeFilter: [`href`]
  1778. });
  1779. await doAllDomChanges();
  1780. }
  1781. async function doAllDomChanges(changes) {
  1782. debug(`doAllDomChanges start`);
  1783. const addedSettingsButtons = addSettingsButton();
  1784. if (addedSettingsButtons)
  1785. debug(`Added Settings Buttons`);
  1786. const addedShowAtHomeButton = HOME ? addShowAtHomeButton() : false;
  1787. if (addedShowAtHomeButton)
  1788. debug(`Added Show At Home Button`);
  1789. const rewrittenLinks = HOME ? await rewriteLinksToLocal(changes) : false;
  1790. if (rewrittenLinks)
  1791. debug(`Rewritten some links`);
  1792. debug(`doAllDomChanges end`);
  1793. }
  1794.  
  1795. // src/home.js
  1796. var HOME;
  1797. var secondaryHomes = [];
  1798. async function initHome() {
  1799. HOME = await getValue(`home`);
  1800. secondaryHomes = await getSecondaryHomeInstances();
  1801. if (!HOME && isLemmyInstance(location) && confirm(`Lemmy Universal Link Switcher: Set this instance to be your home instance to which all URLs get rewritten to?`)) {
  1802. setHome(location.origin);
  1803. }
  1804. }
  1805. async function setHome(newHome) {
  1806. trace(`Set HOME from ${HOME} to ${newHome}`);
  1807. if (typeof newHome !== `string`) {
  1808. newHome = null;
  1809. }
  1810. HOME = newHome;
  1811. await setValue(`home`, newHome);
  1812. }
  1813. async function getHome() {
  1814. return await getValue(`home`);
  1815. }
  1816. function updateHomePeriodically() {
  1817. trace(`Current HOME`, HOME);
  1818. setInterval(async () => {
  1819. const prevHome = HOME;
  1820. const prevSecondaries = secondaryHomes;
  1821. HOME = await getHome();
  1822. secondaryHomes = await getSecondaryHomeInstances();
  1823. if (prevHome !== HOME) {
  1824. debug(`HOME changed externally from`, prevHome, `to`, HOME);
  1825. triggerRewrite();
  1826. } else if (prevSecondaries.length !== secondaryHomes.length || !prevSecondaries.every((v) => secondaryHomes.includes(v))) {
  1827. debug(`secondaryHomes changed externally from`, prevSecondaries, `to`, secondaryHomes);
  1828. triggerRewrite();
  1829. }
  1830. }, 1337);
  1831. }
  1832. function isHomeInstance(url) {
  1833. return secondaryHomes.concat(HOME).includes(url.origin);
  1834. }
  1835. async function getSecondaryHomeInstances() {
  1836. const homeInstancesStr = await getValue(`secondaryHomes`);
  1837. return homeInstancesStr ? JSON.parse(homeInstancesStr) : [];
  1838. }
  1839. async function setSecondaryHomeInstances(homeInstances) {
  1840. await setValue(`secondaryHomes`, JSON.stringify(homeInstances));
  1841. }
  1842.  
  1843. // src/main.js
  1844. (async () => {
  1845. await initHome();
  1846. updateHomePeriodically();
  1847. await initAuth();
  1848. updateAuthPeriodically();
  1849. initSettings();
  1850. startRewriting();
  1851. })();
  1852. })();