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/javascripts/localise.js50
-rw-r--r--src/assets/javascripts/services.js1641
-rw-r--r--src/assets/javascripts/utils.js197
3 files changed, 957 insertions, 931 deletions
diff --git a/src/assets/javascripts/localise.js b/src/assets/javascripts/localise.js
index c0936873..d26d07d4 100644
--- a/src/assets/javascripts/localise.js
+++ b/src/assets/javascripts/localise.js
@@ -1,34 +1,34 @@
 window.browser = window.browser || window.chrome
 
 function localisePage() {
-	/**
-	 * @param {string} tag
-	 */
-	function getMessage(tag) {
-		return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => {
-			return v1 ? browser.i18n.getMessage(v1) : null
-		})
-	}
+  /**
+   * @param {string} tag
+   */
+  function getMessage(tag) {
+    return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => {
+      return v1 ? browser.i18n.getMessage(v1) : null
+    })
+  }
 
-	const elements = document.querySelectorAll("[data-localise]")
-	for (let i in elements)
-		if (elements.hasOwnProperty(i)) {
-			const obj = elements[i]
-			const tag = obj.getAttribute("data-localise").toString()
-			const msg = getMessage(tag)
-			if (msg && msg !== tag) obj.textContent = msg
-		}
+  const elements = document.querySelectorAll("[data-localise]")
+  for (let i in elements)
+    if (elements.hasOwnProperty(i)) {
+      const obj = elements[i]
+      const tag = obj.getAttribute("data-localise").toString()
+      const msg = getMessage(tag)
+      if (msg && msg !== tag) obj.textContent = msg
+    }
 
-	const placeholders = document.querySelectorAll("[data-localise-placeholder]")
-	for (let i in placeholders)
-		if (placeholders.hasOwnProperty(i)) {
-			const obj = placeholders[i]
-			const tag = obj.getAttribute("data-localise-placeholder").toString()
-			const msg = getMessage(tag)
-			if (msg && msg !== tag) obj.placeholder = msg
-		}
+  const placeholders = document.querySelectorAll("[data-localise-placeholder]")
+  for (let i in placeholders)
+    if (placeholders.hasOwnProperty(i)) {
+      const obj = placeholders[i]
+      const tag = obj.getAttribute("data-localise-placeholder").toString()
+      const msg = getMessage(tag)
+      if (msg && msg !== tag) obj.placeholder = msg
+    }
 }
 
 export default {
-	localisePage,
+  localisePage,
 }
diff --git a/src/assets/javascripts/services.js b/src/assets/javascripts/services.js
index b9ee64e5..39347845 100644
--- a/src/assets/javascripts/services.js
+++ b/src/assets/javascripts/services.js
@@ -6,25 +6,25 @@ window.browser = window.browser || window.chrome
 let config, options
 
 async function init() {
-	options = await utils.getOptions()
-	config = await utils.getConfig()
+  options = await utils.getOptions()
+  config = await utils.getConfig()
 }
 
 init()
 browser.storage.onChanged.addListener(init)
 
 function all(service, frontend, options, config) {
-	let instances = []
-	if (!frontend) {
-		for (const frontend in config.services[service].frontends) {
-			if (options[frontend]) {
-				instances.push(...options[frontend])
-			}
-		}
-	} else if (options[frontend]) {
-		instances = options[frontend]
-	}
-	return instances
+  let instances = []
+  if (!frontend) {
+    for (const frontend in config.services[service].frontends) {
+      if (options[frontend]) {
+        instances.push(...options[frontend])
+      }
+    }
+  } else if (options[frontend]) {
+    instances = options[frontend]
+  }
+  return instances
 }
 
 /**
@@ -34,17 +34,17 @@ function all(service, frontend, options, config) {
  * @param {string} frontend
  */
 function regexArray(service, url, config, frontend) {
-	let targetList = config.services[service].targets
-	if (frontend && 'excludeTargets' in config.services[service].frontends[frontend]) {
-		targetList = targetList.filter(val =>
-			!config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val))
-		)
-	}
-	for (const targetString in targetList) {
-		const target = new RegExp(targetList[targetString])
-		if (target.test(url.href)) return true
-	}
-	return false
+  let targetList = config.services[service].targets
+  if (frontend && "excludeTargets" in config.services[service].frontends[frontend]) {
+    targetList = targetList.filter(
+      val => !config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val))
+    )
+  }
+  for (const targetString in targetList) {
+    const target = new RegExp(targetList[targetString])
+    if (target.test(url.href)) return true
+  }
+  return false
 }
 
 /**
@@ -54,8 +54,8 @@ function regexArray(service, url, config, frontend) {
  * @param {boolean} forceRedirection
  */
 async function redirectAsync(url, type, initiator, forceRedirection) {
-	await init()
-	return redirect(url, type, initiator, forceRedirection)
+  await init()
+  return redirect(url, type, initiator, forceRedirection)
 }
 
 /**
@@ -65,458 +65,481 @@ async function redirectAsync(url, type, initiator, forceRedirection) {
  * @returns {undefined|string}
  */
 function rewrite(url, frontend, randomInstance) {
-	switch (frontend) {
-		case "hyperpipe":
-			return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/"))
-		case "searx":
-		case "searxng":
-			return `${randomInstance}/${url.search}`
-		case "whoogle":
-			return `${randomInstance}/search${url.search}`
-		case "4get": {
-			const s = url.searchParams.get("q")
-			if (s !== null) return `${randomInstance}/web?s=${encodeURIComponent(s)}`
-			return randomInstance
-		}
-		case "librey":
-			return `${randomInstance}/search.php${url.search}`
-		case "yattee":
-			url.searchParams.delete("si")
-			return url.href.replace(/^https?:\/{2}/, "yattee://")
-		case "freetube":
-			url.searchParams.delete("si")
-			return 'freetube://' + url.href
-		case "freetubePwa":
-			url.searchParams.delete("si")
-			return 'freetube://' + url.href
-		case "poketube": {
-			url.searchParams.delete("si")
-			if (url.pathname.startsWith('/channel')) {
-				const reg = /\/channel\/(.*)\/?$/.exec(url.pathname)
-				if (reg) {
-					const id = reg[1]
-					return `${randomInstance}/channel?id=${id}${url.search}`
-				}
-			}
-			if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "libMedium":
-		case "scribe": {
-			const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/)
-			if (regex && regex.length > 1) {
-				const subdomain = regex[1]
-				if (subdomain != "link" || !subdomain.startsWith("cdn-images")) {
-					return `${randomInstance}/@${subdomain}${url.pathname}${url.search}`
-				}
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "simplyTranslate":
-			return `${randomInstance}/${url.search}`
-		case "send":
-		case "mozhi":
-			return randomInstance
-		case "libreTranslate":
-			return `${randomInstance}/${url.search.replace("sl", "source").replace("tl", "target").replace("text", "q")}`
-		case "osm": {
-			const placeRegex = /\/place\/(.*?)\//
-			function convertMapCentre(url) {
-				let [lat, lon, zoom] = [null, null, null]
-				const reg = url.pathname.match(/@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/)
-				if (reg) {
-					[, lon, lat, zoom] = reg
-				} else if (url.searchParams.has("center")) {
-					// Set map centre if present
-					[lat, lon] = url.searchParams.get("center").split(",")
-					zoom = url.searchParams.get("zoom") ?? "17"
-				}
-				return { zoom, lon, lat }
-			}
-			function addressToLatLng(address) {
-				const http = new XMLHttpRequest()
-				http.open("GET", `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`, false)
-				http.send()
-				if (http.status == 200) {
-					const json = JSON.parse(http.responseText)[0]
-					if (json) {
-						return {
-							coordinate: `${json.lat},${json.lon}`,
-							boundingbox: `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`
-						}
-					}
-					return {}
-				}
-			}
-			function getQuery(url) {
-				let query = ""
-				if (url.searchParams.has("q")) query = url.searchParams.get("q")
-				else if (url.searchParams.has("query")) query = url.searchParams.has("query")
-				return query
-			}
-			function prefsEncoded(prefs) {
-				return new URLSearchParams(prefs).toString()
-			}
-
-			if (initiator && initiator.host === "earth.google.com") return randomInstance
-
-			let mapCentre = "#"
-			let prefs = { layers: "mapnik" }
-
-			const mapCentreData = convertMapCentre(url)
-			if (mapCentreData.zoom && mapCentreData.lon && mapCentreData.lat) mapCentre = `#map=${mapCentreData.zoom}/${mapCentreData.lon}/${mapCentreData.lat}`
-
-			if (url.pathname.includes("/embed")) { // https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
-				const query = getQuery(url)
-				let { coordinate, boundingbox } = addressToLatLng(query)
-				prefs.bbox = boundingbox
-				prefs.marker = coordinate
-				return `${randomInstance}/export/embed.html?${prefsEncoded(prefs)}`
-			} else if (url.pathname.includes("/dir")) {
-				if (url.searchParams.has("travelmode")) {
-					const travelModes = {
-						driving: "fossgis_osrm_car",
-						walking: "fossgis_osrm_foot",
-						bicycling: "fossgis_osrm_bike",
-						transit: "fossgis_osrm_car", // not implemented on OSM, default to car.
-					}
-					prefs.engine = travelModes[url.searchParams.get("travelmode")]
-				}
-				const regex1 = /\/dir\/([^@/]+)\/([^@/]+)\/@-?\d[0-9.]*,-?\d[0-9.]*,\d{1,2}[.z]/.exec(url.pathname)
-				const regex2 = /\/dir\/([^@/]+)\//.exec(url.pathname)
-				if (regex1) { // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/M%C3%A9dip%C3%B4le+Lyon-Villeurbanne/@45.760254,4.8486298,13z?travelmode=bicycling
-					const origin = addressToLatLng(decodeURIComponent(regex1[1])).coordinate ?? ''
-					const destination = addressToLatLng(decodeURIComponent(regex1[2])).coordinate ?? ''
-					prefs.route = `${origin};${destination}`
-				} else if (regex2) { // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/@45.760254,4.8486298,13z?travelmode=bicycling
-					const origin = addressToLatLng(decodeURIComponent(regex2[1])).coordinate ?? ''
-					prefs.route = `${origin};`
-				} else { // https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling
-					const origin = addressToLatLng(url.searchParams.get("origin")).coordinate ?? ''
-					const destination = addressToLatLng(url.searchParams.get("destination")).coordinate ?? ''
-					prefs.route = `${origin};${destination}`
-				}
-				return `${randomInstance}/directions?${prefsEncoded(prefs)}${mapCentre}`
-			} else if (url.pathname.match(placeRegex)) { // https://www.google.com/maps/place/H%C3%B4tel+de+Londres+Eiffel/@40.9845265,28.7081268,14z
-				const query = url.pathname.match(placeRegex)[1]
-				return `${randomInstance}/search?query=${query}${mapCentre}`
-			} else if (url.searchParams.has("ll")) { // https://maps.google.com/?ll=38.882147,-76.99017
-				const [mlat, mlon] = url.searchParams.get("ll").split(",")
-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
-			} else if (url.searchParams.has("viewpoint")) { // https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
-				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")
-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
-			} else {
-				const query = getQuery(url)
-				if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded(prefs)}`
-			}
-			return `${randomInstance}/${mapCentre}&${prefsEncoded(prefs)}`
-		}
-		case "breezeWiki": {
-			let wiki, urlpath = ""
-			if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) {
-				wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/)
-				if (wiki == "www" || !wiki) wiki = ""
-				else wiki = `/${wiki}`
-				urlpath = url.pathname
-			} else {
-				wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/)
-				if (!wiki) wiki = ""
-				else {
-					wiki = "/" + wiki + "/wiki/"
-					urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/)
-				}
-			}
-			if (url.href.search(/Special:Search\?query/) > -1) {
-				return `${randomInstance}${wiki}${urlpath}${url.search}`.replace(/Special:Search\?query/, "search?q").replace(/\/wiki/, "")
-			}
-			return `${randomInstance}${wiki}${urlpath}${url.search}`
-		}
-		case "rimgo":
-			if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) return `${randomInstance}/stack${url.pathname}${url.search}`
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "redlib":
-		case "libreddit": {
-			const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/)
-			if (!subdomain) return `${randomInstance}${url.pathname}${url.search}`
-			switch (subdomain[0]) {
-				case "preview":
-					return `${randomInstance}/preview/pre${url.pathname}${url.search}`
-				case "external-preview":
-					return `${randomInstance}/preview/external-pre${url.pathname}${url.search}`
-				case "i":
-					return `${randomInstance}/img${url.pathname}`
-			}
-			return randomInstance
-		}
-		case "teddit":
-			if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) {
-				if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}`
-				else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "neuters": {
-			const p = url.pathname
-			if (p.startsWith('/article/') || p.startsWith('/pf/') || p.startsWith('/arc/') || p.startsWith('/resizer/')) {
-				return randomInstance
-			}
-			return `${randomInstance}${p}`
-		}
-		case "dumb":
-			if (url.pathname.endsWith('-lyrics')) return `${randomInstance}${url.pathname}`
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "intellectual":
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "ruralDictionary":
-			if (!url.pathname.includes('/define.php') && !url.pathname.includes('/random.php') && url.pathname != '/') return randomInstance
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "anonymousOverflow": {
-			if (url.hostname == "stackoverflow.com") {
-				const threadID = /^\/a\/(\d+)\/?/.exec(url.pathname)
-				if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}`
-				return `${randomInstance}${url.pathname}${url.search}`
-			}
-			if (url.pathname == "/" || url.pathname == "") {
-				// 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\//)
-			if (regex && regex.length > 1) {
-				const subdomain = regex[1]
-				return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}`
-			}
-			const notExchangeRegex = url.hostname.match(/(?:[a-zA-Z]+\.)?(?:askubuntu\.com|mathoverflow\.net|serverfault\.com|stackapps\.com|superuser\.com|stackoverflow\.com)/)
-			if (notExchangeRegex) {
-				return `${randomInstance}/exchange/${notExchangeRegex[0]}${url.pathname}${url.search}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "biblioReads":
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "wikiless": {
-			let hostSplit = url.host.split(".")
-			// wikiless doesn't have mobile view support yet
-			if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") {
-				if (hostSplit[0] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
-				else url.searchParams.append("lang", hostSplit[0])
-				if (hostSplit[1] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
-			}
-			return `${randomInstance}${url.pathname}${url.search}${url.hash}`
-		}
-		case "proxiTok":
-			if (url.pathname.startsWith('/email')) return randomInstance
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "waybackClassic": {
-			const regex = /^\/\web\/(?:[0-9]+)?\*\/(.*)/.exec(url.pathname)
-			if (regex) {
-				const link = regex[1]
-				return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}`
-			}
-			const regex2 = /(^\/\web\/([0-9]+)\/.*)/.exec(url.pathname)
-			if (regex2) {
-				let link = regex2[1]
-				link = link.replace(regex2[2], regex2[2] + 'if_')
-				return `https://web.archive.org${link}`
-			}
-			return
-		}
-		case "gothub":
-			if (url.hostname == "gist.github.com") return `${randomInstance}/gist${url.pathname}${url.search}`
-			if (url.hostname == "raw.githubusercontent.com") return `${randomInstance}/raw${url.pathname}${url.search}`
-			return `${randomInstance}${url.pathname}${url.search}`
-		case "mikuInvidious":
-			if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == 'b23.tv') return `${randomInstance}${url.pathname}${url.search}`
-			if (url.hostname == "space.bilibili.com") return `${randomInstance}/space${url.pathname}${url.search}`
-		case "tent": {
-			if (url.hostname == 'bandcamp.com' && url.pathname == '/search') {
-				const query = url.searchParams.get('q')
-				return `${randomInstance}/search.php?query=${encodeURIComponent(query)}`
-			}
-			if (url.hostname.endsWith('bandcamp.com')) {
-				const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname)
-				const artist = regex[1]
-				if (url.pathname == '/' || url.pathname == '/music') {
-					return `${randomInstance}/artist.php?name=${artist}`
-				} else {
-					const regex = /^\/(.*)\/(.*)/.exec(url.pathname)
-					if (regex) {
-						const type = regex[1]
-						const name = regex[2]
-						return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}`
-					}
-				}
-			}
-			if (url.hostname == 'f4.bcbits.com') {
-				const regex = /\/img\/(.*)/.exec(url.pathname)
-				const image = regex[1]
-				return `${randomInstance}/image.php?file=${image}`
-			}
-			if (url.hostname == 't4.bcbits.com') {
-				const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname)
-				if (regex) {
-					const directory = regex[1]
-					const format = regex[2]
-					const file = regex[3]
-					const token = url.searchParams.get('token')
-					return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}`
-				}
-			}
-		}
-		case "binternet":
-			if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}`
-		case "laboratory": {
-			let path = url.pathname
-			if (path == "/") path = ""
-			return `${randomInstance}/${url.hostname}${path}${url.search}`
-		}
-		case "quetre": {
-			const regex = /([a-z]+)\.quora\.com/.exec(url.hostname)
-			if (regex) {
-				const lang = regex[1]
-				url.searchParams.append("lang", lang)
-				return `${randomInstance}${url.pathname}${url.search}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "pixivFe": {
-			const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname)
-			if (regex) {
-				const path = regex[1]
-				return `${randomInstance}/${path}${url.search}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "invidious": {
-			url.searchParams.delete("si")
-			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("?", "&")}`
-			}
-			if (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/redirect?"))
-				return url.href
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "freetubeMusic": {
-			if (url.hostname == "youtu.be" || url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live")) {
-				const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1)
-				return `freetube://youtube.com/watch?v=${watch}`
-			}
-			return 'freetube://' + url.href
-		}
-		case "invidiousMusic": {
-			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}`
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case 'materialious': {
-			url.searchParams.delete('si')
-			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/${watch}${url.search.replace('?', '&')}`
-			}
-			if (url.hostname.endsWith("youtube.com")) {
-				if (url.pathname.startsWith('/watch')) {
-					if (url.searchParams.has('v')) {
-						const watch = url.searchParams.get('v')
-						url.searchParams.delete('v')
-						return `${randomInstance}/watch/${watch}${url.search.replace('?', '&')}`
-					}
-					return `${randomInstance}/watch/${url.search.replace('?', '&')}`
-				}
-				if (url.pathname.startsWith('/results')) {
-					if (url.searchParams.has('search_query')) {
-						const search = url.searchParams.get('search_query')
-						url.searchParams.delete('search_query')
-						return `${randomInstance}/search/${search}${url.search.replace('?', '&')}`
-					}
-					return `${randomInstance}/search/${url.search.replace('?', '&')}`
-				}
-				if (url.pathname.startsWith('/redirect?')) {
-					return url.href
-				}
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "libremdb": {
-			if (url.pathname.startsWith("/Name")) {
-				for (const [key, value] of url.searchParams.entries()) {
-					return `${randomInstance}/title/${encodeURIComponent(key)}`
-				}
-			}
-			return `${randomInstance}${url.pathname}${url.search}`
-		}
-		case "tuboYoutube":
-			url.searchParams.delete("si")
-			if (url.pathname.startsWith("/channel")) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
-			if (url.pathname.startsWith("/watch")) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
-			return randomInstance
-		case "tuboSoundcloud":
-			if (url.pathname == '/') return `${randomInstance}?kiosk?serviceId=1`
-			if (url.pathname.match(/^\/[^\/]+(\/$|$)/)) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
-			if (url.pathname.match(/^\/[^\/]+\/[^\/]+/)) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
-			return randomInstance
-		case "twineo":
-		case "safetwitch":
-			if (url.hostname.startsWith("clips.")) return `${randomInstance}/clip${url.pathname}${url.search}`
-			return `${randomInstance}${url.pathname}${url.search}`
-
-		case "tekstoLibre":
-			return `${randomInstance}/?${url.pathname.slice(1)}`;
-		case "skyview":
-			if (url.pathname == '/') return randomInstance
-			return `${randomInstance}?url=${encodeURIComponent(url.href)}`
-		case "nitter": {
-			let search = new URLSearchParams(url.search)
-
-			search.delete("ref_src")
-			search.delete("ref_url")
-			search.delete("s") // type of device that shared the link
-			search.delete("t") // some sort of tracking ID
-
-			search = search.toString()
-			if (search !== "") search = `?${search}`
-
-			if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") {
-				try {
-					const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/)
-					const query = encodeURIComponent(`${id}.${format}?${extra}`)
-					return `${randomInstance}/pic${url.pathname}${query}`
-				} catch {
-					return `${randomInstance}/pic${url.pathname}${search}`
-				}
-			}
-			if (url.pathname.split("/").includes("tweets")) return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}`
-			if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}`
-			return `${randomInstance}${url.pathname}${search}#m`
-		}
-		case "priviblur": {
-			if (url.hostname == "www.tumblr.com") return `${randomInstance}${url.pathname}${url.search}`
-			if (url.hostname.startsWith("assets")) return `${randomInstance}/tblr/assets${url.pathname}${url.search}`
-			if (url.hostname.startsWith("static")) return `${randomInstance}/tblr/static${url.pathname}${url.search}`
-
-			const reg = /^([0-9]+)\.media\.tumblr\.com/.exec(url.hostname) // *.media.tumblr.com
-			if (reg) return `${randomInstance}/tblr/media/${reg[1]}${url.pathname}${url.search}`
-
-			const blogregex = /^(?:www\.)?([a-z\d-]+)\.tumblr\.com/.exec(url.hostname) // <blog>.tumblr.com
-			if (blogregex) {
-				const blog_name = blogregex[1];
-				// Under the <blog>.tumblr.com domain posts are under a /post path
-				if (url.pathname.startsWith("/post")) return `${randomInstance}/${blog_name}${url.pathname.slice(5)}${url.search}`
-				else return `${randomInstance}/${blog_name}${url.pathname}${url.search}`;
-			}
-			return `${randomInstance}${url.pathname}${url.search}`;
-		}
-		case "piped":
-		case "pipedMaterial":
-		case "cloudtube":
-		case "lightTube":
-		case "viewtube":
-			url.searchParams.delete("si")
-		default:
-			return `${randomInstance}${url.pathname}${url.search}`
-	}
+  switch (frontend) {
+    case "hyperpipe":
+      return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery =>
+        searchQuery.replace("?q=", "/")
+      )
+    case "searx":
+    case "searxng":
+      return `${randomInstance}/${url.search}`
+    case "whoogle":
+      return `${randomInstance}/search${url.search}`
+    case "4get": {
+      const s = url.searchParams.get("q")
+      if (s !== null) return `${randomInstance}/web?s=${encodeURIComponent(s)}`
+      return randomInstance
+    }
+    case "librey":
+      return `${randomInstance}/search.php${url.search}`
+    case "yattee":
+      url.searchParams.delete("si")
+      return url.href.replace(/^https?:\/{2}/, "yattee://")
+    case "freetube":
+      url.searchParams.delete("si")
+      return "freetube://" + url.href
+    case "freetubePwa":
+      url.searchParams.delete("si")
+      return "freetube://" + url.href
+    case "poketube": {
+      url.searchParams.delete("si")
+      if (url.pathname.startsWith("/channel")) {
+        const reg = /\/channel\/(.*)\/?$/.exec(url.pathname)
+        if (reg) {
+          const id = reg[1]
+          return `${randomInstance}/channel?id=${id}${url.search}`
+        }
+      }
+      if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "libMedium":
+    case "scribe": {
+      const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/)
+      if (regex && regex.length > 1) {
+        const subdomain = regex[1]
+        if (subdomain != "link" || !subdomain.startsWith("cdn-images")) {
+          return `${randomInstance}/@${subdomain}${url.pathname}${url.search}`
+        }
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "simplyTranslate":
+      return `${randomInstance}/${url.search}`
+    case "send":
+    case "mozhi":
+      return randomInstance
+    case "libreTranslate":
+      return `${randomInstance}/${url.search.replace("sl", "source").replace("tl", "target").replace("text", "q")}`
+    case "osm": {
+      const placeRegex = /\/place\/(.*?)\//
+      function convertMapCentre(url) {
+        let [lat, lon, zoom] = [null, null, null]
+        const reg = url.pathname.match(/@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/)
+        if (reg) {
+          ;[, lon, lat, zoom] = reg
+        } else if (url.searchParams.has("center")) {
+          // Set map centre if present
+          ;[lat, lon] = url.searchParams.get("center").split(",")
+          zoom = url.searchParams.get("zoom") ?? "17"
+        }
+        return { zoom, lon, lat }
+      }
+      function addressToLatLng(address) {
+        const http = new XMLHttpRequest()
+        http.open(
+          "GET",
+          `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`,
+          false
+        )
+        http.send()
+        if (http.status == 200) {
+          const json = JSON.parse(http.responseText)[0]
+          if (json) {
+            return {
+              coordinate: `${json.lat},${json.lon}`,
+              boundingbox: `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`,
+            }
+          }
+          return {}
+        }
+      }
+      function getQuery(url) {
+        let query = ""
+        if (url.searchParams.has("q")) query = url.searchParams.get("q")
+        else if (url.searchParams.has("query")) query = url.searchParams.has("query")
+        return query
+      }
+      function prefsEncoded(prefs) {
+        return new URLSearchParams(prefs).toString()
+      }
+
+      if (initiator && initiator.host === "earth.google.com") return randomInstance
+
+      let mapCentre = "#"
+      let prefs = { layers: "mapnik" }
+
+      const mapCentreData = convertMapCentre(url)
+      if (mapCentreData.zoom && mapCentreData.lon && mapCentreData.lat)
+        mapCentre = `#map=${mapCentreData.zoom}/${mapCentreData.lon}/${mapCentreData.lat}`
+
+      if (url.pathname.includes("/embed")) {
+        // https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
+        const query = getQuery(url)
+        let { coordinate, boundingbox } = addressToLatLng(query)
+        prefs.bbox = boundingbox
+        prefs.marker = coordinate
+        return `${randomInstance}/export/embed.html?${prefsEncoded(prefs)}`
+      } else if (url.pathname.includes("/dir")) {
+        if (url.searchParams.has("travelmode")) {
+          const travelModes = {
+            driving: "fossgis_osrm_car",
+            walking: "fossgis_osrm_foot",
+            bicycling: "fossgis_osrm_bike",
+            transit: "fossgis_osrm_car", // not implemented on OSM, default to car.
+          }
+          prefs.engine = travelModes[url.searchParams.get("travelmode")]
+        }
+        const regex1 = /\/dir\/([^@/]+)\/([^@/]+)\/@-?\d[0-9.]*,-?\d[0-9.]*,\d{1,2}[.z]/.exec(url.pathname)
+        const regex2 = /\/dir\/([^@/]+)\//.exec(url.pathname)
+        if (regex1) {
+          // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/M%C3%A9dip%C3%B4le+Lyon-Villeurbanne/@45.760254,4.8486298,13z?travelmode=bicycling
+          const origin = addressToLatLng(decodeURIComponent(regex1[1])).coordinate ?? ""
+          const destination = addressToLatLng(decodeURIComponent(regex1[2])).coordinate ?? ""
+          prefs.route = `${origin};${destination}`
+        } else if (regex2) {
+          // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/@45.760254,4.8486298,13z?travelmode=bicycling
+          const origin = addressToLatLng(decodeURIComponent(regex2[1])).coordinate ?? ""
+          prefs.route = `${origin};`
+        } else {
+          // https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling
+          const origin = addressToLatLng(url.searchParams.get("origin")).coordinate ?? ""
+          const destination = addressToLatLng(url.searchParams.get("destination")).coordinate ?? ""
+          prefs.route = `${origin};${destination}`
+        }
+        return `${randomInstance}/directions?${prefsEncoded(prefs)}${mapCentre}`
+      } else if (url.pathname.match(placeRegex)) {
+        // https://www.google.com/maps/place/H%C3%B4tel+de+Londres+Eiffel/@40.9845265,28.7081268,14z
+        const query = url.pathname.match(placeRegex)[1]
+        return `${randomInstance}/search?query=${query}${mapCentre}`
+      } else if (url.searchParams.has("ll")) {
+        // https://maps.google.com/?ll=38.882147,-76.99017
+        const [mlat, mlon] = url.searchParams.get("ll").split(",")
+        return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+      } else if (url.searchParams.has("viewpoint")) {
+        // https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
+        const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")
+        return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+      } else {
+        const query = getQuery(url)
+        if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded(prefs)}`
+      }
+      return `${randomInstance}/${mapCentre}&${prefsEncoded(prefs)}`
+    }
+    case "breezeWiki": {
+      let wiki,
+        urlpath = ""
+      if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) {
+        wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/)
+        if (wiki == "www" || !wiki) wiki = ""
+        else wiki = `/${wiki}`
+        urlpath = url.pathname
+      } else {
+        wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/)
+        if (!wiki) wiki = ""
+        else {
+          wiki = "/" + wiki + "/wiki/"
+          urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/)
+        }
+      }
+      if (url.href.search(/Special:Search\?query/) > -1) {
+        return `${randomInstance}${wiki}${urlpath}${url.search}`
+          .replace(/Special:Search\?query/, "search?q")
+          .replace(/\/wiki/, "")
+      }
+      return `${randomInstance}${wiki}${urlpath}${url.search}`
+    }
+    case "rimgo":
+      if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1)
+        return `${randomInstance}/stack${url.pathname}${url.search}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "redlib":
+    case "libreddit": {
+      const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/)
+      if (!subdomain) return `${randomInstance}${url.pathname}${url.search}`
+      switch (subdomain[0]) {
+        case "preview":
+          return `${randomInstance}/preview/pre${url.pathname}${url.search}`
+        case "external-preview":
+          return `${randomInstance}/preview/external-pre${url.pathname}${url.search}`
+        case "i":
+          return `${randomInstance}/img${url.pathname}`
+      }
+      return randomInstance
+    }
+    case "teddit":
+      if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) {
+        if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}`
+        else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "neuters": {
+      const p = url.pathname
+      if (p.startsWith("/article/") || p.startsWith("/pf/") || p.startsWith("/arc/") || p.startsWith("/resizer/")) {
+        return randomInstance
+      }
+      return `${randomInstance}${p}`
+    }
+    case "dumb":
+      if (url.pathname.endsWith("-lyrics")) return `${randomInstance}${url.pathname}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "intellectual":
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "ruralDictionary":
+      if (!url.pathname.includes("/define.php") && !url.pathname.includes("/random.php") && url.pathname != "/")
+        return randomInstance
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "anonymousOverflow": {
+      if (url.hostname == "stackoverflow.com") {
+        const threadID = /^\/a\/(\d+)\/?/.exec(url.pathname)
+        if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}`
+        return `${randomInstance}${url.pathname}${url.search}`
+      }
+      if (url.pathname == "/" || url.pathname == "") {
+        // 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\//)
+      if (regex && regex.length > 1) {
+        const subdomain = regex[1]
+        return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}`
+      }
+      const notExchangeRegex = url.hostname.match(
+        /(?:[a-zA-Z]+\.)?(?:askubuntu\.com|mathoverflow\.net|serverfault\.com|stackapps\.com|superuser\.com|stackoverflow\.com)/
+      )
+      if (notExchangeRegex) {
+        return `${randomInstance}/exchange/${notExchangeRegex[0]}${url.pathname}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "biblioReads":
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "wikiless": {
+      let hostSplit = url.host.split(".")
+      // wikiless doesn't have mobile view support yet
+      if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") {
+        if (hostSplit[0] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
+        else url.searchParams.append("lang", hostSplit[0])
+        if (hostSplit[1] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile")
+      }
+      return `${randomInstance}${url.pathname}${url.search}${url.hash}`
+    }
+    case "proxiTok":
+      if (url.pathname.startsWith("/email")) return randomInstance
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "waybackClassic": {
+      const regex = /^\/\web\/(?:[0-9]+)?\*\/(.*)/.exec(url.pathname)
+      if (regex) {
+        const link = regex[1]
+        return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}`
+      }
+      const regex2 = /(^\/\web\/([0-9]+)\/.*)/.exec(url.pathname)
+      if (regex2) {
+        let link = regex2[1]
+        link = link.replace(regex2[2], regex2[2] + "if_")
+        return `https://web.archive.org${link}`
+      }
+      return
+    }
+    case "gothub":
+      if (url.hostname == "gist.github.com") return `${randomInstance}/gist${url.pathname}${url.search}`
+      if (url.hostname == "raw.githubusercontent.com") return `${randomInstance}/raw${url.pathname}${url.search}`
+      return `${randomInstance}${url.pathname}${url.search}`
+    case "mikuInvidious":
+      if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == "b23.tv")
+        return `${randomInstance}${url.pathname}${url.search}`
+      if (url.hostname == "space.bilibili.com") return `${randomInstance}/space${url.pathname}${url.search}`
+    case "tent": {
+      if (url.hostname == "bandcamp.com" && url.pathname == "/search") {
+        const query = url.searchParams.get("q")
+        return `${randomInstance}/search.php?query=${encodeURIComponent(query)}`
+      }
+      if (url.hostname.endsWith("bandcamp.com")) {
+        const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname)
+        const artist = regex[1]
+        if (url.pathname == "/" || url.pathname == "/music") {
+          return `${randomInstance}/artist.php?name=${artist}`
+        } else {
+          const regex = /^\/(.*)\/(.*)/.exec(url.pathname)
+          if (regex) {
+            const type = regex[1]
+            const name = regex[2]
+            return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}`
+          }
+        }
+      }
+      if (url.hostname == "f4.bcbits.com") {
+        const regex = /\/img\/(.*)/.exec(url.pathname)
+        const image = regex[1]
+        return `${randomInstance}/image.php?file=${image}`
+      }
+      if (url.hostname == "t4.bcbits.com") {
+        const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname)
+        if (regex) {
+          const directory = regex[1]
+          const format = regex[2]
+          const file = regex[3]
+          const token = url.searchParams.get("token")
+          return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}`
+        }
+      }
+    }
+    case "binternet":
+      if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}`
+    case "laboratory": {
+      let path = url.pathname
+      if (path == "/") path = ""
+      return `${randomInstance}/${url.hostname}${path}${url.search}`
+    }
+    case "quetre": {
+      const regex = /([a-z]+)\.quora\.com/.exec(url.hostname)
+      if (regex) {
+        const lang = regex[1]
+        url.searchParams.append("lang", lang)
+        return `${randomInstance}${url.pathname}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "pixivFe": {
+      const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname)
+      if (regex) {
+        const path = regex[1]
+        return `${randomInstance}/${path}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "invidious": {
+      url.searchParams.delete("si")
+      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("?", "&")}`
+      }
+      if (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/redirect?")) return url.href
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "freetubeMusic": {
+      if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) {
+        const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1)
+        return `freetube://youtube.com/watch?v=${watch}`
+      }
+      return "freetube://" + url.href
+    }
+    case "invidiousMusic": {
+      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}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "materialious": {
+      url.searchParams.delete("si")
+      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/${watch}${url.search.replace("?", "&")}`
+      }
+      if (url.hostname.endsWith("youtube.com")) {
+        if (url.pathname.startsWith("/watch")) {
+          if (url.searchParams.has("v")) {
+            const watch = url.searchParams.get("v")
+            url.searchParams.delete("v")
+            return `${randomInstance}/watch/${watch}${url.search.replace("?", "&")}`
+          }
+          return `${randomInstance}/watch/${url.search.replace("?", "&")}`
+        }
+        if (url.pathname.startsWith("/results")) {
+          if (url.searchParams.has("search_query")) {
+            const search = url.searchParams.get("search_query")
+            url.searchParams.delete("search_query")
+            return `${randomInstance}/search/${search}${url.search.replace("?", "&")}`
+          }
+          return `${randomInstance}/search/${url.search.replace("?", "&")}`
+        }
+        if (url.pathname.startsWith("/redirect?")) {
+          return url.href
+        }
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "libremdb": {
+      if (url.pathname.startsWith("/Name")) {
+        for (const [key, value] of url.searchParams.entries()) {
+          return `${randomInstance}/title/${encodeURIComponent(key)}`
+        }
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "tuboYoutube":
+      url.searchParams.delete("si")
+      if (url.pathname.startsWith("/channel")) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
+      if (url.pathname.startsWith("/watch")) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
+      return randomInstance
+    case "tuboSoundcloud":
+      if (url.pathname == "/") return `${randomInstance}?kiosk?serviceId=1`
+      if (url.pathname.match(/^\/[^\/]+(\/$|$)/)) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}`
+      if (url.pathname.match(/^\/[^\/]+\/[^\/]+/)) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}`
+      return randomInstance
+    case "twineo":
+    case "safetwitch":
+      if (url.hostname.startsWith("clips.")) return `${randomInstance}/clip${url.pathname}${url.search}`
+      return `${randomInstance}${url.pathname}${url.search}`
+
+    case "tekstoLibre":
+      return `${randomInstance}/?${url.pathname.slice(1)}`
+    case "skyview":
+      if (url.pathname == "/") return randomInstance
+      return `${randomInstance}?url=${encodeURIComponent(url.href)}`
+    case "nitter": {
+      let search = new URLSearchParams(url.search)
+
+      search.delete("ref_src")
+      search.delete("ref_url")
+      search.delete("s") // type of device that shared the link
+      search.delete("t") // some sort of tracking ID
+
+      search = search.toString()
+      if (search !== "") search = `?${search}`
+
+      if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") {
+        try {
+          const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/)
+          const query = encodeURIComponent(`${id}.${format}?${extra}`)
+          return `${randomInstance}/pic${url.pathname}${query}`
+        } catch {
+          return `${randomInstance}/pic${url.pathname}${search}`
+        }
+      }
+      if (url.pathname.split("/").includes("tweets"))
+        return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}`
+      if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}`
+      return `${randomInstance}${url.pathname}${search}#m`
+    }
+    case "priviblur": {
+      if (url.hostname == "www.tumblr.com") return `${randomInstance}${url.pathname}${url.search}`
+      if (url.hostname.startsWith("assets")) return `${randomInstance}/tblr/assets${url.pathname}${url.search}`
+      if (url.hostname.startsWith("static")) return `${randomInstance}/tblr/static${url.pathname}${url.search}`
+
+      const reg = /^([0-9]+)\.media\.tumblr\.com/.exec(url.hostname) // *.media.tumblr.com
+      if (reg) return `${randomInstance}/tblr/media/${reg[1]}${url.pathname}${url.search}`
+
+      const blogregex = /^(?:www\.)?([a-z\d-]+)\.tumblr\.com/.exec(url.hostname) // <blog>.tumblr.com
+      if (blogregex) {
+        const blog_name = blogregex[1]
+        // Under the <blog>.tumblr.com domain posts are under a /post path
+        if (url.pathname.startsWith("/post"))
+          return `${randomInstance}/${blog_name}${url.pathname.slice(5)}${url.search}`
+        else return `${randomInstance}/${blog_name}${url.pathname}${url.search}`
+      }
+      return `${randomInstance}${url.pathname}${url.search}`
+    }
+    case "piped":
+    case "pipedMaterial":
+    case "cloudtube":
+    case "lightTube":
+    case "viewtube":
+      url.searchParams.delete("si")
+    default:
+      return `${randomInstance}${url.pathname}${url.search}`
+  }
 }
 
 /**
@@ -527,50 +550,54 @@ function rewrite(url, frontend, randomInstance) {
  * @returns {string | undefined}
  */
 function redirect(url, type, initiator, forceRedirection, incognito) {
-	if (type != "main_frame" && type != "sub_frame" && type != "image") return
-	let randomInstance
-	let frontend
-	if (!forceRedirection && options.redirectOnlyInIncognito == true && !incognito) return
-	for (const service in config.services) {
-		if (!forceRedirection && !options[service].enabled) continue
-
-		frontend = options[service].frontend
-
-		if (config.services[service].frontends[frontend].desktopApp && type != "main_frame" && options[service].redirectType != "main_frame")
-			frontend = options[service].embedFrontend
-
-		if (!regexArray(service, url, config, frontend)) {
-			frontend = null
-			continue
-		}
-
-		if (
-			config.services[service].embeddable
-			&&
-			type != options[service].redirectType && options[service].redirectType != "both"
-		) {
-			if (options[service].unsupportedUrls == 'block') return 'CANCEL'
-			return
-		}
-
-		let instanceList = options[frontend]
-		if (instanceList === undefined) break
-		if (instanceList.length === 0) return null
-
-		if (initiator && instanceList.includes(initiator.origin)) {
-			if (type != "main_frame") return null
-			else return "BYPASSTAB"
-		}
-
-		randomInstance = utils.getRandomInstance(instanceList)
-		if (config.services[service].frontends[frontend].localhost && options[service].instance == "localhost") {
-			randomInstance = `http://${frontend}.localhost:8080`
-		}
-		break
-	}
-	if (!frontend) return
-
-	return rewrite(url, frontend, randomInstance)
+  if (type != "main_frame" && type != "sub_frame" && type != "image") return
+  let randomInstance
+  let frontend
+  if (!forceRedirection && options.redirectOnlyInIncognito == true && !incognito) return
+  for (const service in config.services) {
+    if (!forceRedirection && !options[service].enabled) continue
+
+    frontend = options[service].frontend
+
+    if (
+      config.services[service].frontends[frontend].desktopApp &&
+      type != "main_frame" &&
+      options[service].redirectType != "main_frame"
+    )
+      frontend = options[service].embedFrontend
+
+    if (!regexArray(service, url, config, frontend)) {
+      frontend = null
+      continue
+    }
+
+    if (
+      config.services[service].embeddable &&
+      type != options[service].redirectType &&
+      options[service].redirectType != "both"
+    ) {
+      if (options[service].unsupportedUrls == "block") return "CANCEL"
+      return
+    }
+
+    let instanceList = options[frontend]
+    if (instanceList === undefined) break
+    if (instanceList.length === 0) return null
+
+    if (initiator && instanceList.includes(initiator.origin)) {
+      if (type != "main_frame") return null
+      else return "BYPASSTAB"
+    }
+
+    randomInstance = utils.getRandomInstance(instanceList)
+    if (config.services[service].frontends[frontend].localhost && options[service].instance == "localhost") {
+      randomInstance = `http://${frontend}.localhost:8080`
+    }
+    break
+  }
+  if (!frontend) return
+
+  return rewrite(url, frontend, randomInstance)
 }
 
 /**
@@ -578,27 +605,25 @@ function redirect(url, type, initiator, forceRedirection, incognito) {
  * @param {*} returnFrontend
  */
 function computeService(url, returnFrontend) {
-	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)) {
-				resolve(service)
-				return
-			} else {
-				for (const frontend in config.services[service].frontends) {
-					if (all(service, frontend, options, config).includes(utils.protocolHost(url))) {
-						if (returnFrontend)
-							resolve([service, frontend, utils.protocolHost(url)])
-						else
-							resolve(service)
-						return
-					}
-				}
-			}
-		}
-		resolve()
-	})
+  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)) {
+        resolve(service)
+        return
+      } else {
+        for (const frontend in config.services[service].frontends) {
+          if (all(service, frontend, options, config).includes(utils.protocolHost(url))) {
+            if (returnFrontend) resolve([service, frontend, utils.protocolHost(url)])
+            else resolve(service)
+            return
+          }
+        }
+      }
+    }
+    resolve()
+  })
 }
 
 /**
@@ -606,303 +631,301 @@ function computeService(url, returnFrontend) {
  * @param {string} customService
  */
 function switchInstance(url, customService) {
-	return new Promise(async resolve => {
-		let options = await utils.getOptions()
-		let config = await utils.getConfig()
-
-		const protocolHost = utils.protocolHost(url)
-		if (customService) {
-			const instancesList = options[options[customService].frontend]
-			if (instancesList !== undefined) {
-				const newInstance = utils.getNextInstance(url.origin, instancesList)
-				if (newInstance) {
-					resolve(`${newInstance}${url.pathname}${url.search}`)
-					return
-				}
-			}
-		} else {
-			for (const service in config.services) {
-				let instancesList = options[options[service].frontend]
-				if (instancesList === undefined) continue
-				if (!instancesList.includes(protocolHost)) continue
-
-				instancesList.splice(instancesList.indexOf(protocolHost), 1)
-				if (instancesList.length === 0) {
-					resolve()
-					return
-				}
-				const newInstance = utils.getNextInstance(url.origin, instancesList)
-				if (newInstance) {
-					resolve(`${newInstance}${url.pathname}${url.search}`)
-					return
-				}
-			}
-		}
-		resolve()
-	})
+  return new Promise(async resolve => {
+    let options = await utils.getOptions()
+    let config = await utils.getConfig()
+
+    const protocolHost = utils.protocolHost(url)
+    if (customService) {
+      const instancesList = options[options[customService].frontend]
+      if (instancesList !== undefined) {
+        const newInstance = utils.getNextInstance(url.origin, instancesList)
+        if (newInstance) {
+          resolve(`${newInstance}${url.pathname}${url.search}`)
+          return
+        }
+      }
+    } else {
+      for (const service in config.services) {
+        let instancesList = options[options[service].frontend]
+        if (instancesList === undefined) continue
+        if (!instancesList.includes(protocolHost)) continue
+
+        instancesList.splice(instancesList.indexOf(protocolHost), 1)
+        if (instancesList.length === 0) {
+          resolve()
+          return
+        }
+        const newInstance = utils.getNextInstance(url.origin, instancesList)
+        if (newInstance) {
+          resolve(`${newInstance}${url.pathname}${url.search}`)
+          return
+        }
+      }
+    }
+    resolve()
+  })
 }
 
 /**
  * @param {URL} url
  */
 async function reverse(url) {
-	let options = await utils.getOptions()
-	let config = await utils.getConfig()
-	let protocolHost = utils.protocolHost(url)
-	for (const service in config.services) {
-		let frontend = options[service].frontend
-		if (options[frontend] == undefined) continue
-		if (!options[frontend].includes(protocolHost) && protocolHost != `http://${frontend}.localhost:8080`) continue
-		switch (service) {
-			case "youtube":
-			case "imdb":
-			case "imgur":
-			case "tiktok":
-			case "reddit":
-			case "imdb":
-			case "snopes":
-			case "urbanDictionary":
-			case "quora":
-			case "medium":
-				return `${config.services[service].url}${url.pathname}${url.search}`
-			case "fandom": {
-				let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/(.*)/)
-				if (regex) return `https://${regex[1]}.fandom.com/wiki/${regex[2]}`
-				return
-			}
-			case "wikipedia": {
-				const lang = url.searchParams.get("lang")
-				if (lang != null) {
-					return `https://${lang}.wikipedia.org${url.pathname}${url.search}${url.hash}`
-				}
-				return `https://wikipedia.org${url.pathname}${url.search}${url.hash}`
-			}
-			case "stackOverflow": {
-				if (url.pathname.startsWith("/questions/")) {
-					return `https://stackoverflow.com${url.pathname}${url.search}`
-				}
-				if (url.pathname.startsWith("/exchange/")) {
-					const regex = /\/exchange\/(.*?)(\/.*)/.exec(url.pathname)
-					if (regex) return `https://${regex[1]}.stackexchange.com${regex[2]}`
-				}
-				return
-			}
-			case "tekstowo":
-				return `${config.services[service].url}/${url.search.slice(1)}`
-			case "goodreads":
-				return `https://goodreads.com${url.pathname}${url.search}`
-			default:
-				return
-		}
-	}
-	return
+  let options = await utils.getOptions()
+  let config = await utils.getConfig()
+  let protocolHost = utils.protocolHost(url)
+  for (const service in config.services) {
+    let frontend = options[service].frontend
+    if (options[frontend] == undefined) continue
+    if (!options[frontend].includes(protocolHost) && protocolHost != `http://${frontend}.localhost:8080`) continue
+    switch (service) {
+      case "youtube":
+      case "imdb":
+      case "imgur":
+      case "tiktok":
+      case "reddit":
+      case "imdb":
+      case "snopes":
+      case "urbanDictionary":
+      case "quora":
+      case "medium":
+        return `${config.services[service].url}${url.pathname}${url.search}`
+      case "fandom": {
+        let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/(.*)/)
+        if (regex) return `https://${regex[1]}.fandom.com/wiki/${regex[2]}`
+        return
+      }
+      case "wikipedia": {
+        const lang = url.searchParams.get("lang")
+        if (lang != null) {
+          return `https://${lang}.wikipedia.org${url.pathname}${url.search}${url.hash}`
+        }
+        return `https://wikipedia.org${url.pathname}${url.search}${url.hash}`
+      }
+      case "stackOverflow": {
+        if (url.pathname.startsWith("/questions/")) {
+          return `https://stackoverflow.com${url.pathname}${url.search}`
+        }
+        if (url.pathname.startsWith("/exchange/")) {
+          const regex = /\/exchange\/(.*?)(\/.*)/.exec(url.pathname)
+          if (regex) return `https://${regex[1]}.stackexchange.com${regex[2]}`
+        }
+        return
+      }
+      case "tekstowo":
+        return `${config.services[service].url}/${url.search.slice(1)}`
+      case "goodreads":
+        return `https://goodreads.com${url.pathname}${url.search}`
+      default:
+        return
+    }
+  }
+  return
 }
 
 const defaultInstances = {
-	'invidious': ['https://inv.vern.cc'],
-	'materialious': ['https://app.materialio.us'],
-	'viewtube': ['https://viewtube.io'],
-	'piped': ['https://pipedapi-libre.kavin.rocks'],
-	'pipedMaterial': ['https://piped-material.xn--17b.net'],
-	'cloudtube': ['https://tube.cadence.moe'],
-	'lightTube': ['https://tube.kuylar.dev'],
-	'poketube': ['https://poketube.fun'],
-	'proxiTok': ['https://proxitok.pabloferreiro.es'],
-	'redlib': ['https://safereddit.com'],
-	'libreddit': ['https://libreddit.spike.codes'],
-	'teddit': ['https://teddit.net'],
-	'scribe': ['https://scribe.rip'],
-	'libMedium': ['https://md.vern.cc'],
-	'quetre': ['https://quetre.iket.me'],
-	'libremdb': ['https://libremdb.iket.me'],
-	'simplyTranslate': ['https://simplytranslate.org'],
-	'mozhi': ['https://mozhi.aryak.me'],
-	'searxng': ['https://search.bus-hit.me'],
-	'4get': ['https://4get.ca'],
-	'rimgo': ['https://rimgo.vern.cc'],
-	'hyperpipe': ['https://hyperpipe.surge.sh'],
-	'osm': ['https://www.openstreetmap.org'],
-	'breezeWiki': ['https://breezewiki.com'],
-	'neuters': ['https://neuters.de'],
-	'dumb': ['https://dm.vern.cc'],
-	"intellectual": ['https://intellectual.insprill.net'],
-	'ruralDictionary': ['https://rd.vern.cc'],
-	'anonymousOverflow': ['https://code.whatever.social'],
-	'biblioReads': ['https://biblioreads.ml'],
-	'wikiless': ['https://wikiless.org'],
-	'suds': ['https://sd.vern.cc'],
-	'unfunny': ['https://uf.vern.cc'],
-	'soprano': ['https://sp.vern.cc'],
-	'meme': ['https://mm.vern.cc'],
-	'waybackClassic': ['https://wayback-classic.net'],
-	'gothub': ['https://gh.odyssey346.dev'],
-	'mikuInvidious': ['https://mikuinv.resrv.org'],
-	"tent": ['https://tent.sny.sh'],
-	"wolfreeAlpha": ['https://gqq.gitlab.io', 'https://uqq.gitlab.io'],
-	"laboratory": ['https://lab.vern.cc'],
-	'binternet': ['https://bn.bloat.cat'],
-	'pixivFe': ['https://pixivfe.exozy.me'],
-	'indestructables': ['https://indestructables.private.coffee'],
-	'destructables': ['https://ds.vern.cc'],
-	'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'],
-	'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'],
+  invidious: ["https://inv.vern.cc"],
+  materialious: ["https://app.materialio.us"],
+  viewtube: ["https://viewtube.io"],
+  piped: ["https://pipedapi-libre.kavin.rocks"],
+  pipedMaterial: ["https://piped-material.xn--17b.net"],
+  cloudtube: ["https://tube.cadence.moe"],
+  lightTube: ["https://tube.kuylar.dev"],
+  poketube: ["https://poketube.fun"],
+  proxiTok: ["https://proxitok.pabloferreiro.es"],
+  redlib: ["https://safereddit.com"],
+  libreddit: ["https://libreddit.spike.codes"],
+  teddit: ["https://teddit.net"],
+  scribe: ["https://scribe.rip"],
+  libMedium: ["https://md.vern.cc"],
+  quetre: ["https://quetre.iket.me"],
+  libremdb: ["https://libremdb.iket.me"],
+  simplyTranslate: ["https://simplytranslate.org"],
+  mozhi: ["https://mozhi.aryak.me"],
+  searxng: ["https://search.bus-hit.me"],
+  "4get": ["https://4get.ca"],
+  rimgo: ["https://rimgo.vern.cc"],
+  hyperpipe: ["https://hyperpipe.surge.sh"],
+  osm: ["https://www.openstreetmap.org"],
+  breezeWiki: ["https://breezewiki.com"],
+  neuters: ["https://neuters.de"],
+  dumb: ["https://dm.vern.cc"],
+  intellectual: ["https://intellectual.insprill.net"],
+  ruralDictionary: ["https://rd.vern.cc"],
+  anonymousOverflow: ["https://code.whatever.social"],
+  biblioReads: ["https://biblioreads.ml"],
+  wikiless: ["https://wikiless.org"],
+  suds: ["https://sd.vern.cc"],
+  unfunny: ["https://uf.vern.cc"],
+  soprano: ["https://sp.vern.cc"],
+  meme: ["https://mm.vern.cc"],
+  waybackClassic: ["https://wayback-classic.net"],
+  gothub: ["https://gh.odyssey346.dev"],
+  mikuInvidious: ["https://mikuinv.resrv.org"],
+  tent: ["https://tent.sny.sh"],
+  wolfreeAlpha: ["https://gqq.gitlab.io", "https://uqq.gitlab.io"],
+  laboratory: ["https://lab.vern.cc"],
+  binternet: ["https://bn.bloat.cat"],
+  pixivFe: ["https://pixivfe.exozy.me"],
+  indestructables: ["https://indestructables.private.coffee"],
+  destructables: ["https://ds.vern.cc"],
+  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"],
+  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"],
 }
 
 function initDefaults() {
-	return new Promise(resolve => {
-		browser.storage.local.clear(async () => {
-			let config = await utils.getConfig()
-			let options = {}
-			for (const service in config.services) {
-				options[service] = {}
-				for (const defaultOption in config.services[service].options) {
-					options[service][defaultOption] = config.services[service].options[defaultOption]
-				}
-				for (const frontend in config.services[service].frontends) {
-					if (config.services[service].frontends[frontend].instanceList) {
-						options[frontend] = []
-					}
-				}
-			}
-			options.exceptions = {
-				url: [],
-				regex: [],
-			}
-			options.theme = "detect"
-			options.popupServices = ["youtube", "tiktok", "imgur", "reddit", "quora", "translate", "maps"]
-			options.fetchInstances = 'github'
-			options.redirectOnlyInIncognito = false
-
-			options = { ...options, ...defaultInstances }
-
-			browser.storage.local.set({ options },
-				() => resolve()
-			)
-		})
-	})
+  return new Promise(resolve => {
+    browser.storage.local.clear(async () => {
+      let config = await utils.getConfig()
+      let options = {}
+      for (const service in config.services) {
+        options[service] = {}
+        for (const defaultOption in config.services[service].options) {
+          options[service][defaultOption] = config.services[service].options[defaultOption]
+        }
+        for (const frontend in config.services[service].frontends) {
+          if (config.services[service].frontends[frontend].instanceList) {
+            options[frontend] = []
+          }
+        }
+      }
+      options.exceptions = {
+        url: [],
+        regex: [],
+      }
+      options.theme = "detect"
+      options.popupServices = ["youtube", "tiktok", "imgur", "reddit", "quora", "translate", "maps"]
+      options.fetchInstances = "github"
+      options.redirectOnlyInIncognito = false
+
+      options = { ...options, ...defaultInstances }
+
+      browser.storage.local.set({ options }, () => resolve())
+    })
+  })
 }
 
 function upgradeOptions() {
-	return new Promise(async resolve => {
-		let options = await utils.getOptions()
-
-		browser.storage.local.clear(() => {
-			browser.storage.local.set({ options }, () => {
-				resolve()
-			})
-		})
-	})
+  return new Promise(async resolve => {
+    let options = await utils.getOptions()
+
+    browser.storage.local.clear(() => {
+      browser.storage.local.set({ options }, () => {
+        resolve()
+      })
+    })
+  })
 }
 
 function processUpdate() {
-	return new Promise(async resolve => {
-		let frontends = []
-		const config = await utils.getConfig()
-		let options = await utils.getOptions()
-		for (const service in config.services) {
-			if (!options[service]) options[service] = {}
-
-			if (!(options[service].frontend in config.services[service].frontends)) {
-				options[service] = config.services[service].options // Reset settings for service
-				delete options[options[service].frontend] // Remove deprecated frontend
-			}
-
-			for (const defaultOption in config.services[service].options) {
-				if (!(defaultOption in options[service])) {
-					options[service][defaultOption] = config.services[service].options[defaultOption]
-				}
-			}
-
-			for (const frontend in config.services[service].frontends) {
-				frontends.push(frontend)
-				if (!(frontend in options) && config.services[service].frontends[frontend].instanceList) {
-					options[frontend] = defaultInstances[frontend] || []
-				}
-			}
-
-			for (const frontend of options.popupServices) {
-				if (!Object.keys(config.services).includes(frontend)) {
-					const i = options.popupServices.indexOf(frontend);
-					if (i > -1) options.popupServices.splice(i, 1);
-				}
-			}
-		}
-		const general = ['theme', 'popupServices', 'fetchInstances', 'redirectOnlyInIncognito']
-		const combined = [
-			...Object.keys(config.services),
-			...frontends,
-			...general,
-			'exceptions',
-			'popupServices',
-			'version',
-		]
-		for (const key in options) {
-			if (combined.indexOf(key) < 0) {
-				delete options[key] // Remove any unknown settings in options
-			}
-		}
-		browser.storage.local.set({ options }, () => {
-			resolve()
-		})
-	})
+  return new Promise(async resolve => {
+    let frontends = []
+    const config = await utils.getConfig()
+    let options = await utils.getOptions()
+    for (const service in config.services) {
+      if (!options[service]) options[service] = {}
+
+      if (!(options[service].frontend in config.services[service].frontends)) {
+        options[service] = config.services[service].options // Reset settings for service
+        delete options[options[service].frontend] // Remove deprecated frontend
+      }
+
+      for (const defaultOption in config.services[service].options) {
+        if (!(defaultOption in options[service])) {
+          options[service][defaultOption] = config.services[service].options[defaultOption]
+        }
+      }
+
+      for (const frontend in config.services[service].frontends) {
+        frontends.push(frontend)
+        if (!(frontend in options) && config.services[service].frontends[frontend].instanceList) {
+          options[frontend] = defaultInstances[frontend] || []
+        }
+      }
+
+      for (const frontend of options.popupServices) {
+        if (!Object.keys(config.services).includes(frontend)) {
+          const i = options.popupServices.indexOf(frontend)
+          if (i > -1) options.popupServices.splice(i, 1)
+        }
+      }
+    }
+    const general = ["theme", "popupServices", "fetchInstances", "redirectOnlyInIncognito"]
+    const combined = [
+      ...Object.keys(config.services),
+      ...frontends,
+      ...general,
+      "exceptions",
+      "popupServices",
+      "version",
+    ]
+    for (const key in options) {
+      if (combined.indexOf(key) < 0) {
+        delete options[key] // Remove any unknown settings in options
+      }
+    }
+    browser.storage.local.set({ options }, () => {
+      resolve()
+    })
+  })
 }
 
 /**
  * @param {URL} url
  */
 async function copyRaw(url) {
-	const newUrl = await reverse(url)
-	if (newUrl) {
-		if (!isChrome) {
-			navigator.clipboard.writeText(newUrl)
-		} else {
-			var copyFrom = document.createElement("textarea");
-			copyFrom.textContent = newUrl;
-			document.body.appendChild(copyFrom);
-			copyFrom.select()
-			document.execCommand('copy')
-			copyFrom.blur();
-			document.body.removeChild(copyFrom);
-		}
-	}
+  const newUrl = await reverse(url)
+  if (newUrl) {
+    if (!isChrome) {
+      navigator.clipboard.writeText(newUrl)
+    } else {
+      var copyFrom = document.createElement("textarea")
+      copyFrom.textContent = newUrl
+      document.body.appendChild(copyFrom)
+      copyFrom.select()
+      document.execCommand("copy")
+      copyFrom.blur()
+      document.body.removeChild(copyFrom)
+    }
+  }
 }
 
 /**
  * @param {URL} url
  */
 function isException(url) {
-	if (!options.exceptions) return false
-	let exceptions = options.exceptions
-	if (exceptions && url) {
-		if (exceptions.url) {
-			for (let item of exceptions.url) {
-				item = new URL(item)
-				item = item.href.replace(/^http:\/\//, 'https://')
-				if (item == url.href) return true
-			}
-		}
-		if (exceptions.regex) for (const item of exceptions.regex) if (new RegExp(item).test(url.href)) return true
-	}
-	return false
+  if (!options.exceptions) return false
+  let exceptions = options.exceptions
+  if (exceptions && url) {
+    if (exceptions.url) {
+      for (let item of exceptions.url) {
+        item = new URL(item)
+        item = item.href.replace(/^http:\/\//, "https://")
+        if (item == url.href) return true
+      }
+    }
+    if (exceptions.regex) for (const item of exceptions.regex) if (new RegExp(item).test(url.href)) return true
+  }
+  return false
 }
 
 export default {
-	redirect,
-	redirectAsync,
-	computeService,
-	reverse,
-	initDefaults,
-	upgradeOptions,
-	processUpdate,
-	copyRaw,
-	switchInstance,
-	isException
+  redirect,
+  redirectAsync,
+  computeService,
+  reverse,
+  initDefaults,
+  upgradeOptions,
+  processUpdate,
+  copyRaw,
+  switchInstance,
+  isException,
 }
diff --git a/src/assets/javascripts/utils.js b/src/assets/javascripts/utils.js
index d28f9701..439826dd 100644
--- a/src/assets/javascripts/utils.js
+++ b/src/assets/javascripts/utils.js
@@ -1,11 +1,11 @@
 window.browser = window.browser || window.chrome
 
 /**
- * @param {Array.<T>} instances 
+ * @param {Array.<T>} instances
  * @returns {T}
  */
 function getRandomInstance(instances) {
-	return instances[~~(instances.length * Math.random())]
+  return instances[~~(instances.length * Math.random())]
 }
 
 /**
@@ -14,27 +14,28 @@ function getRandomInstance(instances) {
  * @returns {T}
  */
 function getNextInstance(currentInstanceUrl, instances) {
-	const currentInstanceIndex = instances.indexOf(currentInstanceUrl);
-	if (currentInstanceIndex === -1) return getRandomInstance(instances);
-	const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length;
-	return instances[nextInstanceIndex];
+  const currentInstanceIndex = instances.indexOf(currentInstanceUrl)
+  if (currentInstanceIndex === -1) return getRandomInstance(instances)
+  const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length
+  return instances[nextInstanceIndex]
 }
 
 /**
  * @param {string} str
  */
 function camelCase(str) {
-	return str.charAt(0).toUpperCase() + str.slice(1)
+  return str.charAt(0).toUpperCase() + str.slice(1)
 }
 
 /**
  * @param {URL} url
  */
 function protocolHost(url) {
-	if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}`
-	if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io")) // workaround
-		return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
-	return `${url.protocol}//${url.host}`
+  if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}`
+  if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io"))
+    // workaround
+    return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}`
+  return `${url.protocol}//${url.host}`
 }
 
 /**
@@ -59,14 +60,14 @@ function protocolHost(url) {
  * @returns {Promise<Config>}
  */
 function getConfig() {
-	return new Promise(resolve => {
-		fetch("/config.json")
-			.then(response => response.text())
-			.then(json => {
-				resolve(JSON.parse(json))
-				return
-			})
-	})
+  return new Promise(resolve => {
+    fetch("/config.json")
+      .then(response => response.text())
+      .then(json => {
+        resolve(JSON.parse(json))
+        return
+      })
+  })
 }
 
 /**
@@ -78,106 +79,108 @@ function getConfig() {
  * @returns {Promise<Object.<string, Option | string[]>>}
  */
 function getOptions() {
-	return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options)))
+  return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options)))
 }
 
 function getPingCache() {
-	return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {})))
+  return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {})))
 }
 
 function getBlacklist(options) {
-	return new Promise(resolve => {
-		let url
-		if (options.fetchInstances == 'github') url = 'https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json'
-		else if (options.fetchInstances == 'codeberg') url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json'
-		else return resolve('disabled')
-		const http = new XMLHttpRequest()
-		http.open("GET", url, true)
-		http.onreadystatechange = () => {
-			if (http.status === 200 && http.readyState == XMLHttpRequest.DONE)
-				resolve(JSON.parse(http.responseText))
-		}
-		http.onerror = () => resolve()
-		http.ontimeout = () => resolve()
-		http.send(null)
-	})
+  return new Promise(resolve => {
+    let url
+    if (options.fetchInstances == "github")
+      url = "https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json"
+    else if (options.fetchInstances == "codeberg")
+      url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json"
+    else return resolve("disabled")
+    const http = new XMLHttpRequest()
+    http.open("GET", url, true)
+    http.onreadystatechange = () => {
+      if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) resolve(JSON.parse(http.responseText))
+    }
+    http.onerror = () => resolve()
+    http.ontimeout = () => resolve()
+    http.send(null)
+  })
 }
 
 function getList(options) {
-	return new Promise(resolve => {
-		let url
-		if (options.fetchInstances == 'github') url = 'https://raw.githubusercontent.com/libredirect/instances/main/data.json'
-		else if (options.fetchInstances == 'codeberg') url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json'
-		else return resolve('disabled')
-		const http = new XMLHttpRequest()
-		http.open("GET", url, true)
-		http.onreadystatechange = () => {
-			if (http.status === 200 && http.readyState == XMLHttpRequest.DONE)
-				return resolve(JSON.parse(http.responseText))
-		}
-		http.onerror = () => resolve()
-		http.ontimeout = () => resolve()
-		http.send(null)
-	})
+  return new Promise(resolve => {
+    let url
+    if (options.fetchInstances == "github")
+      url = "https://raw.githubusercontent.com/libredirect/instances/main/data.json"
+    else if (options.fetchInstances == "codeberg")
+      url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json"
+    else return resolve("disabled")
+    const http = new XMLHttpRequest()
+    http.open("GET", url, true)
+    http.onreadystatechange = () => {
+      if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) return resolve(JSON.parse(http.responseText))
+    }
+    http.onerror = () => resolve()
+    http.ontimeout = () => resolve()
+    http.send(null)
+  })
 }
 
 /**
  * @param {string} href
  */
 function pingOnce(href) {
-	return new Promise(async resolve => {
-		let started
-		let http = new XMLHttpRequest()
-		http.timeout = 5000
-		http.ontimeout = () => resolve(5000)
-		http.onerror = () => resolve()
-		http.onreadystatechange = () => {
-			if (http.readyState == 2) {
-				if (http.status == 200) {
-					let ended = new Date().getTime()
-					http.abort()
-					resolve(ended - started)
-				} else {
-					resolve(5000 + http.status)
-				}
-			}
-		}
-		http.open("GET", `${href}?_=${new Date().getTime()}`, true)
-		started = new Date().getTime()
-		http.send(null)
-	})
+  return new Promise(async resolve => {
+    let started
+    let http = new XMLHttpRequest()
+    http.timeout = 5000
+    http.ontimeout = () => resolve(5000)
+    http.onerror = () => resolve()
+    http.onreadystatechange = () => {
+      if (http.readyState == 2) {
+        if (http.status == 200) {
+          let ended = new Date().getTime()
+          http.abort()
+          resolve(ended - started)
+        } else {
+          resolve(5000 + http.status)
+        }
+      }
+    }
+    http.open("GET", `${href}?_=${new Date().getTime()}`, true)
+    started = new Date().getTime()
+    http.send(null)
+  })
 }
 
 /**
  * @param {string} href
  */
 function ping(href) {
-	return new Promise(async resolve => {
-		let average = 0
-		let time
-		for (let i = 0; i < 3; i++) {
-			time = await pingOnce(href)
-			if (i == 0) continue
-			if (time >= 5000) {
-				resolve(time)
-				return
-			}
-			average += time
-		}
-		average = parseInt(average / 3)
-		resolve(average)
-	})
+  return new Promise(async resolve => {
+    let average = 0
+    let time
+    for (let i = 0; i < 3; i++) {
+      time = await pingOnce(href)
+      if (i == 0) continue
+      if (time >= 5000) {
+        resolve(time)
+        return
+      }
+      average += time
+    }
+    average = parseInt(average / 3)
+    resolve(average)
+  })
 }
 
 export default {
-	getRandomInstance,
-	getNextInstance,
-	protocolHost,
-	getList,
-	getBlacklist,
-	camelCase,
-	getConfig,
-	getOptions,
-	getPingCache,
-	ping,
+  getRandomInstance,
+  getNextInstance,
+  protocolHost,
+  getList,
+  getBlacklist,
+  camelCase,
+  getConfig,
+  getOptions,
+  getPingCache,
+  ping,
 }