about summary refs log tree commit diff stats
path: root/src/assets
diff options
context:
space:
mode:
Diffstat (limited to 'src/assets')
-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,
 }