about summary refs log tree commit diff stats
path: root/src/assets
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-01-29 17:19:04 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-01-29 17:19:04 +0100
commit0cfcd80f3a715a77821aa83e0c89245ec2f53eec (patch)
treeaec2aea5c24c15f287bc05e695b60d45a26e6dc8 /src/assets
parentchore(merge): Merge remote-tracking branch 'origin/master' (diff)
parentAdded Structables, and made it the default https://codeberg.org/LibRedirect/b... (diff)
downloadlibredirect-0cfcd80f3a715a77821aa83e0c89245ec2f53eec.zip
Merge remote-tracking branch 'origin/master'
Diffstat (limited to '')
-rw-r--r--src/assets/images/chatGpt-icon-light.svg10
-rw-r--r--src/assets/images/chatGpt-icon.svg1
-rw-r--r--src/assets/images/chefkoch-icon.svg43
-rw-r--r--src/assets/javascripts/services.js188
-rw-r--r--src/assets/javascripts/utils.js20
5 files changed, 219 insertions, 43 deletions
diff --git a/src/assets/images/chatGpt-icon-light.svg b/src/assets/images/chatGpt-icon-light.svg
new file mode 100644
index 00000000..b3df81fb
--- /dev/null
+++ b/src/assets/images/chatGpt-icon-light.svg
@@ -0,0 +1,10 @@
+<svg width="320" height="320" viewBox="0 0 320 320" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_10145_290560)">
+<path d="M297.06 130.97C304.32 109.18 301.82 85.31 290.21 65.49C272.75 35.09 237.65 19.45 203.37 26.81C188.12 9.63002 166.21 -0.139981 143.24 1.93887e-05C108.2 -0.0799806 77.11 22.48 66.33 55.82C43.82 60.43 24.39 74.52 13.02 94.49C-4.57001 124.81 -0.560007 163.03 22.94 189.03C15.68 210.82 18.18 234.69 29.79 254.51C47.25 284.91 82.35 300.55 116.63 293.19C131.87 310.37 153.79 320.14 176.76 319.99C211.82 320.08 242.92 297.5 253.7 264.13C276.21 259.52 295.64 245.43 307.01 225.46C324.58 195.14 320.56 156.95 297.07 130.95L297.06 130.97ZM176.78 299.08C162.75 299.1 149.16 294.19 138.39 285.2C138.88 284.94 139.73 284.47 140.28 284.13L204 247.33C207.26 245.48 209.26 242.01 209.24 238.26V148.43L236.17 163.98C236.46 164.12 236.65 164.4 236.69 164.72V239.11C236.65 272.19 209.86 299.01 176.78 299.08ZM47.94 244.05C40.91 231.91 38.38 217.68 40.79 203.87C41.26 204.15 42.09 204.66 42.68 205L106.4 241.8C109.63 243.69 113.63 243.69 116.87 241.8L194.66 196.88V227.98C194.68 228.3 194.53 228.61 194.28 228.81L129.87 266C101.18 282.52 64.54 272.7 47.95 244.05H47.94ZM31.17 104.96C38.17 92.8 49.22 83.5 62.38 78.67C62.38 79.22 62.35 80.19 62.35 80.87V154.48C62.33 158.22 64.33 161.69 67.58 163.54L145.37 208.45L118.44 224C118.17 224.18 117.83 224.21 117.53 224.08L53.11 186.86C24.48 170.28 14.66 133.65 31.16 104.97L31.17 104.96ZM252.43 156.45L174.64 111.53L201.57 95.99C201.84 95.81 202.18 95.78 202.48 95.91L266.9 133.1C295.58 149.67 305.41 186.36 288.84 215.04C281.83 227.18 270.79 236.48 257.64 241.32V165.51C257.67 161.77 255.68 158.31 252.44 156.45H252.43ZM279.23 116.11C278.76 115.82 277.93 115.32 277.34 114.98L213.62 78.18C210.39 76.29 206.39 76.29 203.15 78.18L125.36 123.1V92C125.34 91.68 125.49 91.37 125.74 91.17L190.15 54.01C218.84 37.46 255.52 47.31 272.06 76.01C279.05 88.13 281.58 102.32 279.21 116.11H279.23ZM110.72 171.54L83.78 155.99C83.49 155.85 83.3 155.57 83.26 155.25V80.86C83.28 47.74 110.15 20.9 143.27 20.92C157.28 20.92 170.84 25.84 181.61 34.8C181.12 35.06 180.28 35.53 179.72 35.87L116 72.67C112.74 74.52 110.74 77.98 110.76 81.73L110.72 171.52V171.54ZM125.35 140L160 119.99L194.65 139.99V180L160 200L125.35 180V140Z" fill="white"/>
+</g>
+<defs>
+<clipPath id="clip0_10145_290560">
+<rect width="320" height="320" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/src/assets/images/chatGpt-icon.svg b/src/assets/images/chatGpt-icon.svg
new file mode 100644
index 00000000..e04db75a
--- /dev/null
+++ b/src/assets/images/chatGpt-icon.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg"><path d="m297.06 130.97c7.26-21.79 4.76-45.66-6.85-65.48-17.46-30.4-52.56-46.04-86.84-38.68-15.25-17.18-37.16-26.95-60.13-26.81-35.04-.08-66.13 22.48-76.91 55.82-22.51 4.61-41.94 18.7-53.31 38.67-17.59 30.32-13.58 68.54 9.92 94.54-7.26 21.79-4.76 45.66 6.85 65.48 17.46 30.4 52.56 46.04 86.84 38.68 15.24 17.18 37.16 26.95 60.13 26.8 35.06.09 66.16-22.49 76.94-55.86 22.51-4.61 41.94-18.7 53.31-38.67 17.57-30.32 13.55-68.51-9.94-94.51zm-120.28 168.11c-14.03.02-27.62-4.89-38.39-13.88.49-.26 1.34-.73 1.89-1.07l63.72-36.8c3.26-1.85 5.26-5.32 5.24-9.07v-89.83l26.93 15.55c.29.14.48.42.52.74v74.39c-.04 33.08-26.83 59.9-59.91 59.97zm-128.84-55.03c-7.03-12.14-9.56-26.37-7.15-40.18.47.28 1.3.79 1.89 1.13l63.72 36.8c3.23 1.89 7.23 1.89 10.47 0l77.79-44.92v31.1c.02.32-.13.63-.38.83l-64.41 37.19c-28.69 16.52-65.33 6.7-81.92-21.95zm-16.77-139.09c7-12.16 18.05-21.46 31.21-26.29 0 .55-.03 1.52-.03 2.2v73.61c-.02 3.74 1.98 7.21 5.23 9.06l77.79 44.91-26.93 15.55c-.27.18-.61.21-.91.08l-64.42-37.22c-28.63-16.58-38.45-53.21-21.95-81.89zm221.26 51.49-77.79-44.92 26.93-15.54c.27-.18.61-.21.91-.08l64.42 37.19c28.68 16.57 38.51 53.26 21.94 81.94-7.01 12.14-18.05 21.44-31.2 26.28v-75.81c.03-3.74-1.96-7.2-5.2-9.06zm26.8-40.34c-.47-.29-1.3-.79-1.89-1.13l-63.72-36.8c-3.23-1.89-7.23-1.89-10.47 0l-77.79 44.92v-31.1c-.02-.32.13-.63.38-.83l64.41-37.16c28.69-16.55 65.37-6.7 81.91 22 6.99 12.12 9.52 26.31 7.15 40.1zm-168.51 55.43-26.94-15.55c-.29-.14-.48-.42-.52-.74v-74.39c.02-33.12 26.89-59.96 60.01-59.94 14.01 0 27.57 4.92 38.34 13.88-.49.26-1.33.73-1.89 1.07l-63.72 36.8c-3.26 1.85-5.26 5.31-5.24 9.06l-.04 89.79zm14.63-31.54 34.65-20.01 34.65 20v40.01l-34.65 20-34.65-20z"/></svg>
\ No newline at end of file
diff --git a/src/assets/images/chefkoch-icon.svg b/src/assets/images/chefkoch-icon.svg
new file mode 100644
index 00000000..12e1f3ba
--- /dev/null
+++ b/src/assets/images/chefkoch-icon.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   class="w-logo"
+   width="35.532116"
+   height="35.532116"
+   viewBox="0 0 35.532115 35.532115"
+   fill="none"
+   data-v-73c3726b=""
+   version="1.1"
+   id="svg9"
+   sodipodi:docname="logo.svg"
+   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs9" />
+  <sodipodi:namedview
+     id="namedview9"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:zoom="5.6568542"
+     inkscape:cx="10.69499"
+     inkscape:cy="32.880465"
+     inkscape:window-width="1888"
+     inkscape:window-height="1052"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg9" />
+  <path
+     class="w-logo__hat"
+     data-testid="logo-hat"
+     d="m 28.25197,6.8567782 -1.5339,15.1350798 -0.3608,3.3157 c -0.0677,0.3158 -0.2707,0.609 -0.8572,0.7669 -0.1579,0.0451 -0.3158,0.0902 -0.4962,0.1128 -0.3158,0.0677 -0.6316,-0.1805 -0.5865,-0.5188 l 1.3308,-17.3680898 c 0.0452,-0.47368 -0.3157,-0.87969 -0.7894,-0.90224 h -0.0902 c -0.4737,-0.04511 -0.8797,0.31578 -0.9023,0.78946 l -1.3533,17.7740698 c -0.0226,0.3835 -0.3158,0.6767 -0.6993,0.7218 -0.2255,0.0226 -0.4737,0.0451 -0.6992,0.0677 -0.3383,0.0225 -0.6316,-0.2481 -0.6316,-0.5865 l 0.3609,-18.4958598 c 0.0226,-0.47368 -0.3609,-0.85713 -0.812,-0.87968 h -0.0902 c -0.4737,-0.02256 -0.8571,0.36089 -0.8797,0.81201 l -0.3609,18.6312298 c -0.0226,0.3609 -0.3158,0.6541 -0.6541,0.6541 -0.2481,0 -0.4963,0 -0.7444,0 -0.3609,0 -0.6541,-0.2932 -0.6766,-0.6541 L 16.29727,8.9093782 c -0.0226,-0.47368 -0.406,-0.83457 -0.8797,-0.81202 h -0.09022 c -0.47368,0.02256 -0.834569,0.40601 -0.812009,0.87969 L 14.9439,26.164658 c 0,0.3384 -0.270669,0.609 -0.631569,0.5865 -0.22556,-0.0226 -0.45112,-0.0451 -0.67668,-0.0677 -0.38345,-0.0451 -0.67668,-0.3383 -0.69923,-0.7218 L 11.628171,8.3905882 c -0.04511,-0.47367 -0.45112,-0.81201 -0.90224,-0.78946 h -0.09022 c -0.47368,0.04511 -0.8120199,0.45112 -0.7894599,0.90224 L 11.131941,25.668458 c 0.02256,0.3383 -0.27067,0.5865 -0.58646,0.5188 -0.18044,-0.0451 -0.36089,-0.0902 -0.54134,-0.1353 -0.6090098,-0.1805 -0.7894598,-0.4737 -0.8571298,-0.8121 -0.04511,-0.4736 -0.31578,-3.1578 -0.33833,-3.248 -0.33834,-3.2932 -1.51125,-14.8418498 -1.51125,-14.8418498 -0.02256,-1.10524 -0.04512,-1.57892 1.08268,-2.14282 3.0450598,-1.51125 6.9246888,-0.06766 6.9246888,-0.06766 l 2.00747,-0.76691 c 2.6842,-0.9699 6.5638,-0.87968 9.4058,0.76691 0.5414,0.31578 1.6241,0.83457 1.5339,1.91725 z m -2.0752,21.7439798 c 0,0 0,0.7669 -0.0226,1.1503 -0.0225,0.4737 -0.0225,1.0376 -0.9473,1.2632 -4.8721,1.2857 -9.9923,1.2405 -14.886949,0 -0.9473498,-0.2481 -0.9247898,-0.7444 -0.9473498,-1.1955 -0.02255,-0.3834 -0.02255,-1.218 -0.02255,-1.2406 -0.02256,-0.4737 0.3383399,-0.7443 0.8120098,-0.6541 5.007429,0.9925 10.172739,0.9925 15.180139,0 0.4737,-0.0902 0.8572,0.203 0.8346,0.6767 z m 3.5864,-23.7063398 c -0.5865,-0.81202 -2.5037,-1.80448 -3.8571,-2.345826 -3.4961,-1.263135 -7.466,-0.924796 -10.57872,0.496226 l -0.40601,-0.11278 c -2.639039,-0.857123 -5.7743288,-0.406003 -7.5787988,0.5639 -1.127802,0.60901 -1.6465891,1.10525 -1.8270361,1.89471 -0.1127804,0.31578 -0.1804486,0.87968 -0.090225,1.8947 0,0.04511 -4e-7,0.06767 0.022556,0.11278 0.2706721,3.2029498 2.0300352,18.9921298 2.0525952,19.0147298 0.29323,3.1352 -0.15789,4.3984 0.47367,5.2329 0.04512,0.0677 0.09023,0.1128 0.13534,0.1579 0.27067,0.2707 0.65412,0.4963 1.26313,0.7218 0.11278,0.0451 0.24812,0.0902 0.3608998,0.1354 0.4962299,0.1804 1.0375699,0.3383 1.6014699,0.4736 2.12026,0.4963 4.375829,0.6316 6.428429,0.6316 2.0526,0 4.3308,-0.1353 6.4285,-0.6316 0.5639,-0.1353 1.1278,-0.2932 1.6015,-0.4736 0.1127,-0.0452 0.2481,-0.0903 0.3608,-0.1354 0.6091,-0.2481 1.0151,-0.4737 1.2632,-0.7218 0.0451,-0.0451 0.0902,-0.0902 0.1353,-0.1579 0.6316,-0.8345 0.203,-2.0977 0.4737,-5.2329 0,-0.0226 1.7819,-15.81178 2.0526,-19.0147298 0,-0.04511 0,-0.09023 0.0225,-0.11278 0.1579,-1.6917 -0.1127,-2.0526 -0.3383,-2.39093 z"
+     fill="#3b8047"
+     id="path1" />
+</svg>
diff --git a/src/assets/javascripts/services.js b/src/assets/javascripts/services.js
index 7213380f..961759c2 100644
--- a/src/assets/javascripts/services.js
+++ b/src/assets/javascripts/services.js
@@ -54,9 +54,10 @@ function regexArray(service, url, config, options, frontend) {
  * @param {URL} url
  * @param {string} frontend
  * @param {string} randomInstance
+ * @param {string} type
  * @returns {undefined|string}
  */
-function rewrite(url, originUrl, frontend, randomInstance) {
+function rewrite(url, originUrl, frontend, randomInstance, type) {
   switch (frontend) {
     case "hyperpipe":
       for (const key of [...url.searchParams.keys()]) if (key !== "q") url.searchParams.delete(key)
@@ -99,6 +100,7 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance
       return `${randomInstance}${url.pathname}${url.search}`
     }
+    case "small":
     case "libMedium":
     case "scribe": {
       const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/)
@@ -220,9 +222,9 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       return `${randomInstance}${url.pathname}${url.search}`
     case "redlib":
     case "libreddit": {
-      const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/)
+      const subdomain = url.hostname.match(/^(?:((?:external-)?preview|i)\.)?redd\.it/)
       if (!subdomain) return `${randomInstance}${url.pathname}${url.search}`
-      switch (subdomain[0]) {
+      switch (subdomain[1]) {
         case "preview":
           return `${randomInstance}/preview/pre${url.pathname}${url.search}`
         case "external-preview":
@@ -230,7 +232,7 @@ function rewrite(url, originUrl, frontend, randomInstance) {
         case "i":
           return `${randomInstance}/img${url.pathname}`
       }
-      return randomInstance
+      return `${randomInstance}/comments${url.pathname}`
     }
     case "teddit":
       if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) {
@@ -238,6 +240,7 @@ function rewrite(url, originUrl, frontend, randomInstance) {
         else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}`
       }
       return `${randomInstance}${url.pathname}${url.search}`
+    case "troddit":
     case "eddrit":
       if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) return randomInstance
       return `${randomInstance}${url.pathname}${url.search}`
@@ -267,8 +270,11 @@ function rewrite(url, originUrl, frontend, randomInstance) {
         // https://stackexchange.com or https://superuser.com
         return `${randomInstance}${url.pathname}${url.search}`
       }
-      const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.)?stackexchange\.com\//)
+      const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.(meta\.)?)?stackexchange\.com\//)
       if (regex && regex.length > 1) {
+        if (regex[2]) {
+          return `${randomInstance}/exchange/${url.hostname}${url.pathname}${url.search}`
+        }
         const subdomain = regex[1]
         return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}`
       }
@@ -282,6 +288,15 @@ function rewrite(url, originUrl, frontend, randomInstance) {
     }
     case "biblioReads":
       return `${randomInstance}${url.pathname}${url.search}`
+    case "wikimore": {
+      let hostSplit = url.host.split(".")
+      // wikiless doesn't have mobile view support yet
+      if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") {
+        const lang = url.hostname.split(".")[0]
+        return `${randomInstance}/wiki/${lang}${url.pathname}${url.search}${url.hash}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}${url.hash}`
+    }
     case "wikiless": {
       let hostSplit = url.host.split(".")
       // wikiless doesn't have mobile view support yet
@@ -292,6 +307,7 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       }
       return `${randomInstance}${url.pathname}${url.search}${url.hash}`
     }
+    case "offtiktok":
     case "proxiTok":
       if (url.pathname.startsWith("/email")) return randomInstance
       return `${randomInstance}${url.pathname}${url.search}`
@@ -353,7 +369,10 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       }
     }
     case "binternet":
-      if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}`
+      if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${encodeURIComponent(url.href)}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "painterest":
+      if (url.hostname == "i.pinimg.com") return `${randomInstance}/_/proxy?url=${encodeURIComponent(url.href)}`
       return `${randomInstance}${url.pathname}${url.search}`
     case "laboratory": {
       let path = url.pathname
@@ -378,11 +397,24 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       }
       return `${randomInstance}${url.pathname}${url.search}`
     }
+    case "vixipy": {
+      const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname)
+      if (regex) {
+        let path = regex[1]
+        if (path.startsWith("tags/")) path = path.replace(/tags/, "tag")
+        return `${randomInstance}/${path}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
     case "invidious": {
+      // tracker
       url.searchParams.delete("si")
+
+      if (type == "sub_frame") url.searchParams.append("autoplay", "0")
+
       if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) {
         const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1)
-        return `${randomInstance}/watch?v=${watch}${url.search.replace("?", "&")}`
+        return `${randomInstance}/watch?v=${watch}&${url.search.substring(1)}`
       }
       if (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/redirect?")) return url.href
       return `${randomInstance}${url.pathname}${url.search}`
@@ -501,6 +533,7 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       }
       return `${randomInstance}${url.pathname}${url.search}`
     }
+    case "ultimateTab":
     case "freetar":
       if (url.pathname.startsWith("/search.php")) {
         url.searchParams.set("search_term", url.searchParams.get("value"))
@@ -519,16 +552,16 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       return `${randomInstance}${url.pathname}${url.search}`
     }
     case "skunkyArt": {
-      if (url.pathname.startsWith("/search")) return `${randomInstance}${url.pathname}${url.search}&scope=all`
+      if (url.pathname.startsWith("/search")) return `${randomInstance}${url.pathname}${url.search}&type=all`
 
-      const artReg = /^\/.*?\/art\/(.*)\/?/.exec(url.pathname)
-      if (artReg) return `${randomInstance}/post/art/${artReg[1]}${url.search}`
+      const artReg = /^\/(.*?)\/art\/(.*)\/?/.exec(url.pathname)
+      if (artReg) return `${randomInstance}/post/${artReg[1]}/${artReg[2]}${url.search}`
 
       const userReg = /^\/([^\/]+)$/.exec(url.pathname)
-      if (userReg) return `${randomInstance}/user/${userReg[1]}${url.search}`
+      if (userReg) return `${randomInstance}/group_user?q=${userReg[1]}&type=about`
 
-      const galleryReg = /^\/.*?\/gallery(\/$|$)$/.exec(url.pathname)
-      if (galleryReg) return `${randomInstance}/user/${userReg[1]}?a=gallery`
+      const galleryReg = /^\/(.*?)\/gallery(\/$|$)$/.exec(url.pathname)
+      if (galleryReg) return `${randomInstance}/group_user?q=${galleryReg[1]}&type=gallery`
 
       return `${randomInstance}${url.pathname}${url.search}`
     }
@@ -550,6 +583,43 @@ function rewrite(url, originUrl, frontend, randomInstance) {
       const accountReg = /^\/([^\/]+)\/?$/.exec(url.pathname)
       if (accountReg) return `${randomInstance}/account${url.pathname}${url.search}`
 
+    case "duckDuckGoAiChat":
+      return "https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1"
+
+    case "soundcloak":
+      if (url.pathname.startsWith("/feed") || url.pathname.startsWith("/stream")) {
+        // this feature requires authentication and is unsupported, so just redirect to main page
+        return randomInstance
+      }
+
+      if (url.pathname.startsWith("/search")) {
+        if (!url.search) {
+          return randomInstance
+        }
+
+        let type = ""
+        if (url.pathname.startsWith("/search/sounds")) {
+          type = "tracks"
+        } else if (url.pathname.startsWith("/search/people")) {
+          type = "users"
+        } else if (url.pathname.startsWith("/search/albums") || url.pathname.startsWith("/search/sets")) {
+          type = "playlists"
+        }
+
+        if (type) {
+          type = "&type=" + type
+        } else {
+          return randomInstance // fallback for unsupported search types (searching for anything for example)
+        }
+
+        return `${randomInstance}/search${url.search}${type}`
+      }
+
+      if (url.host == "on.soundcloud.com") {
+        return `${randomInstance}/on${url.pathname}`
+      }
+
+      return `${randomInstance}${url.pathname}${url.search}`
     case "piped":
     case "pipedMaterial":
     case "cloudtube":
@@ -627,7 +697,7 @@ function redirect(url, type, originUrl, documentUrl, incognito, forceRedirection
   }
   if (!frontend) return
 
-  return rewrite(url, originUrl, frontend, randomInstance)
+  return rewrite(url, originUrl, frontend, randomInstance, type)
 }
 
 /**
@@ -647,30 +717,19 @@ async function redirectAsync(url, type, originUrl, documentUrl, incognito, force
 /**
  * @param {URL} url
  */
-function computeService(url) {
-  return new Promise(async resolve => {
-    const config = await utils.getConfig()
-    const options = await utils.getOptions()
-    for (const service in config.services) {
-      if (regexArray(service, url, config, options)) {
-        resolve(service)
-        return
-      } else {
-        for (const frontend in config.services[service].frontends) {
-          if (all(service, frontend, options, config).findIndex(instance => url.href.startsWith(instance)) >= 0) {
-            return resolve(service)
-          }
-        }
-      }
-    }
-    resolve()
-  })
-}
-export function computeFrontend(url) {
+async function computeServiceFrontend(url) {
+  const config = await utils.getConfig()
+  const options = await utils.getOptions()
   for (const service in config.services) {
-    for (const frontend in config.services[service].frontends) {
-      if (all(service, frontend, options, config).findIndex(instance => url.href.startsWith(instance)) >= 0) {
-        return { service, frontend }
+    if (regexArray(service, url, config, options)) {
+      return { service, frontend: null }
+    } else {
+      for (const frontend in config.services[service].frontends) {
+        const instances = all(service, frontend, options, config)
+        const i = instances.findIndex(instance => url.href.startsWith(instance))
+        if (i >= 0) {
+          return { service, frontend }
+        }
       }
     }
   }
@@ -765,6 +824,39 @@ async function reverse(url) {
         return `${config.services[service].url}/${url.search.slice(1)}`
       case "goodreads":
         return `https://goodreads.com${url.pathname}${url.search}`
+      case "soundcloud":
+        if (frontend == "soundcloak") {
+          if (url.pathname.includes("/_/")) {
+            // soundcloak-specific pages
+            return `${config.services[service].url}${url.pathname.split("/_/")[0]}`
+          }
+
+          if (url.pathname == "/search") {
+            let type = url.searchParams.get("type")
+            switch (type) {
+              case "playlists":
+                type = "sets"
+                break
+              case "tracks":
+                type = "sounds"
+                break
+              case "users":
+                type = "people"
+                break
+              default:
+                type = ""
+            }
+
+            url.searchParams.delete("type")
+            if (!type) {
+              return `${config.services[service].url}/search?${url.searchParams.toString()}`
+            } else {
+              return `${config.services[service].url}/search/${type}?${url.searchParams.toString()}`
+            }
+          }
+
+          return `${config.services[service].url}${url.pathname}`
+        }
       default:
         return
     }
@@ -783,9 +875,12 @@ const defaultInstances = {
   poketube: ["https://poketube.fun"],
   proxiTok: ["https://proxitok.pabloferreiro.es"],
   redlib: ["https://libreddit.vhack.eu"],
+  offtiktok: ["https://www.offtiktok.com"],
   eddrit: ["https://eddrit.com"],
+  troddit: ["https://www.troddit.com"],
   scribe: ["https://scribe.rip"],
   libMedium: ["https://md.vern.cc"],
+  small: ["https://small.bloat.cat"],
   quetre: ["https://quetre.iket.me"],
   libremdb: ["https://libremdb.iket.me"],
   simplyTranslate: ["https://simplytranslate.org"],
@@ -811,27 +906,36 @@ const defaultInstances = {
   wolfreeAlpha: ["https://gqq.gitlab.io", "https://uqq.gitlab.io"],
   laboratory: ["https://lab.vern.cc"],
   binternet: ["https://bn.bloat.cat"],
+  painterest: ["https://pt.bloat.cat"],
   pixivFe: ["https://pixivfe.exozy.me"],
   liteXiv: ["https://litexiv.exozy.me"],
+  vixipy: ["https://vx.maid.zone"],
   indestructables: ["https://indestructables.private.coffee"],
   destructables: ["https://ds.vern.cc"],
+  structables: ["https://structables.private.coffee"],
   safetwitch: ["https://safetwitch.drgns.space"],
   twineo: ["https://twineo.exozy.me"],
   proxigram: ["https://ig.opnxng.com"],
-  tuboYoutube: ["https://tubo.migalmoreno.com"],
-  tuboSoundcloud: ["https://tubo.migalmoreno.com"],
+  tuboYoutube: ["https://tubo.media"],
+  tuboSoundcloud: ["https://tubo.media"],
   tekstoLibre: ["https://davilarek.github.io/TekstoLibre"],
   skyview: ["https://skyview.social"],
   priviblur: ["https://pb.bloat.cat"],
   nitter: ["https://nitter.privacydev.net"],
   pasted: ["https://pasted.drakeerv.com"],
+  pasty: ["https://pasty.lus.pm"],
   freetar: ["https://freetar.de"],
+  ultimateTab: ["https://ultimate-tab.com"],
   ratAintTieba: ["https://rat.fis.land"],
   shoelace: ["https://shoelace.mint.lgbt"],
   skunkyArt: ["https://skunky.bloat.cat"],
-  ytify: ["https://ytify.netlify.app"],
+  ytify: ["https://ytify.us.kg"],
   nerdsForNerds: ["https://nn.vern.cc"],
+  ducksForDucks: ["https://ducksforducks.private.coffee"],
   koub: ["https://koub.clovius.club"],
+  soundcloak: ["https://soundcloak.fly.dev"],
+  gocook: ["https://cook.adminforge.de"],
+  wikimore: ["https://wikimore.private.coffee"],
 }
 
 async function getDefaults() {
@@ -942,6 +1046,7 @@ async function copyRaw(url) {
  * @param {URL} url
  */
 function isException(url) {
+  if (!options) return false
   if (!options.exceptions) return false
   let exceptions = options.exceptions
   if (exceptions && url) {
@@ -968,12 +1073,11 @@ function isException(url) {
 export default {
   redirect,
   redirectAsync,
-  computeService,
+  computeServiceFrontend,
   reverse,
   initDefaults,
   processUpdate,
   copyRaw,
   switchInstance,
   isException,
-  computeFrontend,
 }
diff --git a/src/assets/javascripts/utils.js b/src/assets/javascripts/utils.js
index e5b8ba46..f360a15b 100644
--- a/src/assets/javascripts/utils.js
+++ b/src/assets/javascripts/utils.js
@@ -31,7 +31,8 @@ function protocolHost(url) {
   if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io"))
     return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
 
-  return `${url.protocol}//${url.host}${url.pathname}`
+  const pathname = url.pathname != "/" ? url.pathname : ""
+  return `${url.protocol}//${url.host}${pathname}`
 }
 
 /**
@@ -221,6 +222,22 @@ export function randomInstances(clearnet, n) {
   }
   return instances
 }
+
+async function autoPickInstance(clearnet, url) {
+  if (url) {
+    const i = clearnet.findIndex(instance => url.href.startsWith(instance))
+    if (i >= 0) clearnet.splice(i, 1)
+  }
+  const random = randomInstances(clearnet, 5)
+  const pings = await Promise.all([
+    ...random.map(async instance => {
+      return [instance, await ping(instance)]
+    }),
+  ])
+  pings.sort((a, b) => a[1] - b[1])
+  return pings[0][0]
+}
+
 export function style(options, window) {
   const vars = cssVariables(options, window)
   return `--text: ${vars.text};
@@ -276,4 +293,5 @@ export default {
   convertMapCentre,
   randomInstances,
   style,
+  autoPickInstance,
 }