about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/javascripts/services.js1702
-rw-r--r--src/config/config.json1220
-rw-r--r--src/instances/get_instances.py161
3 files changed, 1550 insertions, 1533 deletions
diff --git a/src/assets/javascripts/services.js b/src/assets/javascripts/services.js
index d02e03f6..e67ed842 100644
--- a/src/assets/javascripts/services.js
+++ b/src/assets/javascripts/services.js
@@ -1,851 +1,851 @@
-window.browser = window.browser || window.chrome

-

-import utils from "./utils.js"

-

-let config, options, redirects, targets

-

-function init() {

-	return new Promise(async resolve => {

-		browser.storage.local.get(["options", "redirects", "targets"], r => {

-			options = r.options

-			redirects = r.redirects

-			targets = r.targets

-			fetch("/config/config.json")

-				.then(response => response.text())

-				.then(configData => {

-					config = JSON.parse(configData)

-					resolve()

-				})

-		})

-	})

-}

-

-init()

-browser.storage.onChanged.addListener(init)

-

-function fetchFrontendInstanceList(service, frontend, redirects, options, config) {

-	let tmp = []

-	if (config.services[service].frontends[frontend].instanceList) {

-		for (const network in config.networks) {

-			tmp.push(...redirects[network], ...options[frontend][network].custom)

-		}

-	} else if (config.services[service].frontends[frontend].singleInstance) tmp = config.services[service].frontends[frontend].singleInstance

-	return tmp

-}

-

-function all(service, frontend, options, config, redirects) {

-	let instances = []

-	if (!frontend) {

-		for (const frontend in config.services[service].frontends) {

-			instances.push(...fetchFrontendInstanceList(service, frontend, redirects[frontend], options, config))

-		}

-	} else {

-		instances.push(...fetchFrontendInstanceList(service, frontend, redirects[frontend], options, config))

-	}

-	return instances

-}

-

-function regexArray(service, url, config) {

-	if (config.services[service].targets == "datajson") {

-		if (targets[service].includes(utils.protocolHost(url))) return true

-	} else {

-		const targetList = config.services[service].targets

-		for (const targetString in targetList) {

-			const target = new RegExp(targetList[targetString])

-			if (target.test(url.href)) return true

-		}

-	}

-	return false

-}

-

-function redirect(url, type, initiator, forceRedirection) {

-	if (type != "main_frame" && type != "sub_frame") return

-	let randomInstance

-	let frontend

-	for (const service in config.services) {

-		if (!forceRedirection && !options[service].enabled) continue

-		if (config.services[service].embeddable && type != options[service].redirectType && options[service].redirectType != "both") continue

-		if (!config.services[service].embeddable && type != "main_frame") continue

-		// let targets = new RegExp(config.services[service].targets.join("|"), "i")

-

-		if (!regexArray(service, url, config)) continue

-		// if (initiator) {

-		// 	console.log(initiator.host)

-		// 	if (targets.test(initiator.host)) continue

-		// 	//if (all(service, null, options, config, redirects).includes(initiator.origin) && reverse(initiator) == url) return "BYPASSTAB"

-		// }

-

-		if (Object.keys(config.services[service].frontends).length > 1) {

-			if (type == "sub_frame" && config.services[service].embeddable && !config.services[service].frontends[options[service].frontend].embeddable) frontend = options[service].embedFrontend

-			else frontend = options[service].frontend

-		} else frontend = Object.keys(config.services[service].frontends)[0]

-

-		if (config.services[service].frontends[frontend].instanceList) {

-			let instanceList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]

-			if (instanceList.length === 0 && options.networkFallback) instanceList = [...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom]

-			if (instanceList.length === 0) return

-			randomInstance = utils.getRandomInstance(instanceList)

-		} else if (config.services[service].frontends[frontend].singleInstance) randomInstance = config.services[service].frontends[frontend].singleInstance

-		break

-	}

-	if (!frontend) return

-

-	// Here is a (temperory) space for defining constants required in 2 or more switch cases.

-	// When possible, try have the two switch cases share all their code as done with searx and searxng.

-	// Do not do that when they do not share 100% of their code.

-

-	const mapCentreRegex = /@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/

-	const dataLatLngRegex = /!3d(-?[0-9]{1,}.[0-9]{1,})!4d(-?[0-9]{1,}.[0-9]{1,})/

-	const placeRegex = /\/place\/(.*)\//

-	function convertMapCentre() {

-		let [lat, lon, zoom] = [null, null, null]

-		if (url.pathname.match(mapCentreRegex)) {

-			// Set map centre if present

-			;[lat, lon, zoom] = url.pathname.match(mapCentreRegex)

-		} else if (url.searchParams.has("center")) {

-			;[lat, lon] = url.searchParams.get("center").split(",")

-			zoom = url.searchParams.get("zoom") ?? "17"

-		}

-		return [zoom, lon, lat]

-	}

-

-	switch (frontend) {

-		// This is where all instance-specific code must be ran to convert the service url to one that can be understood by the frontend.

-		case "beatbump":

-			return `${randomInstance}${url.pathname}${url.search}`

-				.replace("/watch?v=", "/listen?id=")

-				.replace("/channel/", "/artist/")

-				.replace("/playlist?list=", "/playlist/VL")

-				.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/") + "?filter=all")

-		case "hyperpipe":

-			return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/"))

-		case "bibliogram":

-			const reservedPaths = ["u", "p", "privacy"]

-			if (url.pathname === "/" || reservedPaths.includes(url.pathname.split("/")[1])) return `${randomInstance}${url.pathname}${url.search}`

-			if (url.pathname.startsWith("/reel") || url.pathname.startsWith("/tv")) return `${randomInstance}/p${url.pathname.replace(/\/reel|\/tv/i, "")}${url.search}`

-			else return `${randomInstance}/u${url.pathname}${url.search}` // Likely a user profile, redirect to '/u/...'

-		case "lbryDesktop":

-			return url.href.replace(/^https?:\/{2}odysee\.com\//, "lbry://").replace(/:(?=[a-zA-Z0-9])/g, "#")

-		case "neuters":

-			if (url.pathname.startsWith("/article/") || url.pathname.startsWith("/pf/") || url.pathname.startsWith("/arc/") || url.pathname.startsWith("/resizer/")) return null

-			else if (url.pathname.endsWith("/")) return `${randomInstance}${url.pathname}`

-			else return `${randomInstance}${url.pathname}/`

-		case "searx":

-		case "searxng":

-			return `${randomInstance}/?q=${encodeURIComponent(url.searchParams.get("q"))}`

-		case "whoogle":

-			return `${randomInstance}/search?q=${encodeURIComponent(url.searchParams.get("q"))}`

-		case "librex":

-			return `${randomInstance}/search.php?q=${encodeURIComponent(url.searchParams.get("q"))}`

-		case "send":

-			return randomInstance

-		case "nitter":

-			let search = new URLSearchParams(url.search)

-

-			search.delete("ref_src")

-			search.delete("ref_url")

-

-			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}`

-		case "yattee":

-			return url.href.replace(/^https?:\/{2}/, "yattee://")

-		case "freetube":

-			return `freetube://https://youtu.be${url.pathname}${url.search}`.replace(/watch\?v=/, "")

-		case "simplyTranslate":

-			return `${randomInstance}/${url.search}`

-		case "libreTranslate":

-			return `${randomInstance}/${url.search}`

-				.replace(/(?<=\/?)sl/, "source")

-				.replace(/(?<=&)tl/, "target")

-				.replace(/(?<=&)text/, "q")

-		case "osm": {

-			if (initiator && initiator.host === "earth.google.com") return

-			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.

-			}

-

-			function addressToLatLng(address) {

-				const xmlhttp = new XMLHttpRequest()

-				xmlhttp.open("GET", `https://nominatim.openstreetmap.org/search/${address}?format=json&limit=1`, false)

-				xmlhttp.send()

-				if (xmlhttp.status === 200) {

-					const json = JSON.parse(xmlhttp.responseText)[0]

-					if (json) {

-						console.log("json", json)

-						return [`${json.lat},${json.lon}`, `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`]

-					}

-				}

-				console.info("Error: Status is " + xmlhttp.status)

-			}

-

-			let mapCentre = "#"

-			let prefs = {}

-

-			const mapCentreData = convertMapCentre()

-			if (mapCentreData[0] && mapCentreData[1] && mapCentreData[2]) mapCentre = `#map=${mapCentreData[0]}/${mapCentreData[1]}/${mapCentreData[2]}`

-			if (url.searchParams.get("layer")) prefs.layers = osmLayers[url.searchParams.get("layer")]

-

-			if (url.pathname.includes("/embed")) {

-				// Handle Google Maps Embed API

-				// https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France

-				//console.log("embed life")

-

-				let query = ""

-				if (url.searchParams.has("q")) query = url.searchParams.get("q")

-				else if (url.searchParams.has("query")) query = url.searchParams.has("query")

-				else if (url.searchParams.has("pb"))

-					try {

-						query = url.searchParams.get("pb").split(/!2s(.*?)!/)[1]

-					} catch (error) {

-						console.error(error)

-					} // Unable to find map marker in URL.

-

-				let [coords, boundingbox] = addressToLatLng(query)

-				prefs.bbox = boundingbox

-				prefs.marker = coords

-				prefs.layer = "mapnik"

-				let prefsEncoded = new URLSearchParams(prefs).toString()

-				return `${randomInstance}/export/embed.html?${prefsEncoded}`

-			} else if (url.pathname.includes("/dir")) {

-				// Handle Google Maps Directions

-				// https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling

-

-				let travMod = url.searchParams.get("travelmode")

-				if (url.searchParams.has("travelmode")) prefs.engine = travelModes[travMod]

-

-				let orgVal = url.searchParams.get("origin")

-				let destVal = url.searchParams.get("destination")

-

-				let org = addressToLatLng(orgVal)

-				let dest = addressToLatLng(destVal)

-				prefs.route = `${org};${dest}`

-

-				let prefsEncoded = new URLSearchParams(prefs).toString()

-				return `${randomInstance}/directions?${prefsEncoded}${mapCentre}`

-			} else if (url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex)) {

-				// Get marker from data attribute

-				// https://www.google.com/maps/place/41%C2%B001'58.2%22N+40%C2%B029'18.2%22E/@41.032833,40.4862063,17z/data=!3m1!4b1!4m6!3m5!1s0x0:0xf64286eaf72fc49d!7e2!8m2!3d41.0328329!4d40.4883948

-				//console.log("data life")

-

-				let [, mlat, mlon] = url.pathname.match(dataLatLngRegex)

-

-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`

-			} else if (url.searchParams.has("ll")) {

-				// Get marker from ll param

-				// https://maps.google.com/?ll=38.882147,-76.99017

-				//console.log("ll life")

-

-				const [mlat, mlon] = url.searchParams.get("ll").split(",")

-

-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`

-			} else if (url.searchParams.has("viewpoint")) {

-				// Get marker from viewpoint param.

-				// https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80

-				//console.log("viewpoint life")

-

-				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")

-

-				return `${randomInstance}/search?query=${mlat}%2C${mlon}`

-			} else {

-				// Use query as search if present.

-				//console.log("normal life")

-

-				let query

-				if (url.searchParams.has("q")) query = url.searchParams.get("q")

-				else if (url.searchParams.has("query")) query = url.searchParams.get("query")

-				else if (url.pathname.match(placeRegex)) query = url.pathname.match(placeRegex)[1]

-

-				let prefsEncoded = new URLSearchParams(prefs).toString()

-				if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded}`

-			}

-

-			let prefsEncoded = new URLSearchParams(prefs).toString()

-			console.log("mapCentre", mapCentre)

-			console.log("prefs", prefs)

-			console.log("prefsEncoded", prefsEncoded)

-			return `${randomInstance}/${mapCentre}&${prefsEncoded}`

-		}

-		case "facil": {

-			if (initiator && initiator.host === "earth.google.com") return

-			const travelModes = {

-				driving: "car",

-				walking: "pedestrian",

-				bicycling: "bicycle",

-				transit: "car", // not implemented on Facil, default to car.

-			}

-			const mapCentreData = convertMapCentre()

-			let mapCentre = "#"

-			if (mapCentreData[0] && mapCentreData[1] && mapCentreData[2]) mapCentre = `#${mapCentreData[0]}/${mapCentreData[1]}/${mapCentreData[2]}`

-

-			if (url.pathname.includes("/embed")) {

-				// Handle Google Maps Embed API

-				// https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France

-				//console.log("embed life")

-

-				let query = ""

-				if (url.searchParams.has("q")) query = url.searchParams.get("q")

-				else if (url.searchParams.has("query")) query = url.searchParams.has("query")

-				else if (url.searchParams.has("pb"))

-					try {

-						query = url.searchParams.get("pb").split(/!2s(.*?)!/)[1]

-					} catch (error) {

-						console.error(error)

-					} // Unable to find map marker in URL.

-

-				return `${randomInstance}/#q=${query}`

-			} else if (url.pathname.includes("/dir")) {

-				// Handle Google Maps Directions

-				// https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling

-

-				let travMod = url.searchParams.get("travelmode")

-

-				let orgVal = url.searchParams.get("origin")

-				let destVal = url.searchParams.get("destination")

-

-				return `${randomInstance}/#q=${orgVal}%20to%20${destVal}%20by%20${travelModes[travMod]}`

-			} else if (url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex)) {

-				// Get marker from data attribute

-				// https://www.google.com/maps/place/41%C2%B001'58.2%22N+40%C2%B029'18.2%22E/@41.032833,40.4862063,17z/data=!3m1!4b1!4m6!3m5!1s0x0:0xf64286eaf72fc49d!7e2!8m2!3d41.0328329!4d40.4883948

-				//console.log("data life")

-

-				let [, mlat, mlon] = url.pathname.match(dataLatLngRegex)

-

-				return `${randomInstance}/#q=${mlat}%2C${mlon}`

-			} else if (url.searchParams.has("ll")) {

-				// Get marker from ll param

-				// https://maps.google.com/?ll=38.882147,-76.99017

-				//console.log("ll life")

-

-				const [mlat, mlon] = url.searchParams.get("ll").split(",")

-

-				return `${randomInstance}/#q=${mlat}%2C${mlon}`

-			} else if (url.searchParams.has("viewpoint")) {

-				// Get marker from viewpoint param.

-				// https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80

-				//console.log("viewpoint life")

-

-				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")

-

-				return `${randomInstance}/#q=${mlat}%2C${mlon}`

-			} else {

-				// Use query as search if present.

-				//console.log("normal life")

-

-				let query

-				if (url.searchParams.has("q")) query = url.searchParams.get("q")

-				else if (url.searchParams.has("query")) query = url.searchParams.get("query")

-				else if (url.pathname.match(placeRegex)) query = url.pathname.match(placeRegex)[1]

-

-				if (query) return `${randomInstance}/${mapCentre}/Mpnk/${query}`

-			}

-		}

-		case "wikiless":

-			let GETArguments = []

-			if (url.search.length > 0) {

-				let search = url.search.substring(1) //get rid of '?'

-				let argstrings = search.split("&")

-				for (let i = 0; i < argstrings.length; i++) {

-					let args = argstrings[i].split("=")

-					GETArguments.push([args[0], args[1]])

-				}

-			}

-

-			let link = `${randomInstance}${url.pathname}`

-			let urlSplit = url.host.split(".")

-			if (urlSplit[0] != "wikipedia" && urlSplit[0] != "www") {

-				if (urlSplit[0] == "m") GETArguments.push(["mobileaction", "toggle_view_mobile"])

-				else GETArguments.push(["lang", urlSplit[0]])

-				if (urlSplit[1] == "m") GETArguments.push(["mobileaction", "toggle_view_mobile"])

-				// wikiless doesn't have mobile view support yet

-			}

-			for (let i = 0; i < GETArguments.length; i++) link += (i == 0 ? "?" : "&") + GETArguments[i][0] + "=" + GETArguments[i][1]

-			return link

-

-		case "lingva":

-			let params_arr = url.search.split("&")

-			params_arr[0] = params_arr[0].substring(1)

-			let params = {}

-			for (let i = 0; i < params_arr.length; i++) {

-				let pair = params_arr[i].split("=")

-				params[pair[0]] = pair[1]

-			}

-			if (params.sl && params.tl && params.text) {

-				return `${randomInstance}/${params.sl}/${params.tl}/${params.text}`

-			}

-			return randomInstance

-		case "breezeWiki":

-			let wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.fandom\.com)/)

-			if (wiki == "www" || !wiki) wiki = ""

-			else wiki = "/" + wiki

-			if (url.href.search(/Special:Search\?query/) > -1) return `${randomInstance}${wiki}${url.pathname}${url.search}`.replace(/Special:Search\?query/, "search?q").replace(/\/wiki/, "")

-			else return `${randomInstance}${wiki}${url.pathname}${url.search}`

-		case "rimgo":

-			if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) return `${randomInstance}/stack${url.pathname}${url.search}`

-			else return `${randomInstance}${url.pathname}${url.search}`

-		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}`

-			}

-		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}`

-		default:

-			return `${randomInstance}${url.pathname}${url.search}`

-	}

-}

-

-function computeService(url, returnFrontend) {

-	return new Promise(resolve => {

-		fetch("/config/config.json")

-			.then(response => response.text())

-			.then(configData => {

-				const config = JSON.parse(configData)

-				browser.storage.local.get(["redirects", "options"], r => {

-					const redirects = r.redirects

-					const options = r.options

-					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, redirects).includes(utils.protocolHost(url))) {

-									if (returnFrontend) resolve([service, frontend, utils.protocolHost(url)])

-									else resolve(service)

-									return

-								}

-							}

-						}

-					}

-					resolve()

-				})

-			})

-	})

-}

-

-function switchInstance(url) {

-	return new Promise(async resolve => {

-		await init()

-		const protocolHost = utils.protocolHost(url)

-		for (const service in config.services) {

-			if (!all(service, null, options, config, redirects).includes(protocolHost)) continue

-

-			let instancesList

-			if (Object.keys(config.services[service].frontends).length == 1) {

-				const frontend = Object.keys(config.services[service].frontends)[0]

-				instancesList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]

-				if (instancesList.length === 0 && options.networkFallback) instancesList = [...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom]

-			} else {

-				const frontend = options[service].frontend

-				instancesList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]

-				if (instancesList.length === 0 && options.networkFallback) instancesList = [...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom]

-			}

-

-			let oldInstance

-			const i = instancesList.indexOf(protocolHost)

-			if (i > -1) {

-				oldInstance = instancesList[i]

-				instancesList.splice(i, 1)

-			}

-			if (instancesList.length === 0) {

-				resolve()

-				return

-			}

-			const randomInstance = utils.getRandomInstance(instancesList)

-			const oldUrl = `${oldInstance}${url.pathname}${url.search}`

-			// This is to make instance switching work when the instance depends on the pathname, eg https://darmarit.org/searx

-			// Doesn't work because of .includes array method, not a top priotiry atm

-			resolve(oldUrl.replace(oldInstance, randomInstance))

-			return

-		}

-		resolve()

-	})

-}

-

-function reverse(url, urlString) {

-	return new Promise(async resolve => {

-		await init()

-		let protocolHost

-		if (!urlString) protocolHost = utils.protocolHost(url)

-		else protocolHost = url.match(/https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+/)[0]

-		for (const service in config.services) {

-			if (!all(service, null, options, config, redirects).includes(protocolHost)) continue

-

-			switch (service) {

-				case "instagram":

-				case "youtube":

-				case "imdb":

-				case "imgur":

-				case "tiktok":

-				case "twitter":

-				case "reddit":

-				case "imdb":

-				case "reuters":

-				case "quora":

-				case "medium":

-					if (!urlString) resolve(config.services[service].url + url.pathname + url.search)

-					else resolve(url.replace(/https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+/, config.services[service].url))

-					return

-				default:

-					resolve()

-					return

-			}

-		}

-		resolve()

-	})

-}

-

-function unifyPreferences(url, tabId) {

-	return new Promise(async resolve => {

-		await init()

-		const protocolHost = utils.protocolHost(url)

-		for (const service in config.services) {

-			for (const frontend in config.services[service].frontends) {

-				if (all(service, frontend, options, config, redirects).includes(protocolHost)) {

-					let instancesList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]

-					if (options.networkFallback && options.network != "clearnet") instancesList.push(...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom)

-

-					const frontendObject = config.services[service].frontends[frontend]

-					if ("cookies" in frontendObject.preferences) {

-						for (const cookie of frontendObject.preferences.cookies) {

-							await utils.copyCookie(url, instancesList, cookie)

-						}

-					}

-					if ("localstorage" in frontendObject.preferences) {

-						browser.storage.local.set({ tmp: [frontend, frontendObject.preferences.localstorage] })

-						browser.tabs.executeScript(tabId, {

-							file: "/assets/javascripts/get-localstorage.js",

-							runAt: "document_start",

-						})

-						for (const instance of instancesList)

-							browser.tabs.create({ url: instance }, tab =>

-								browser.tabs.executeScript(tab.id, {

-									file: "/assets/javascripts/set-localstorage.js",

-									runAt: "document_start",

-								})

-							)

-					}

-					/*

-					if ("indexeddb" in frontendObject.preferences) {

-					}

-					if ("token" in frontendObject.preferences) {

-					}

-					*/

-					resolve(true)

-					return

-				}

-			}

-		}

-	})

-}

-

-function setRedirects(passedRedirects) {

-	return new Promise(resolve => {

-		fetch("/config/config.json")

-			.then(response => response.text())

-			.then(configData => {

-				browser.storage.local.get(/* [ */ "options" /* , "blacklists"] */, async r => {

-					let redirects = passedRedirects

-					let options = r.options

-					const config = JSON.parse(configData)

-					let targets = {}

-					for (const service in config.services) {

-						if (config.services[service].targets == "datajson") {

-							targets[service] = redirects[service]

-							delete redirects[service]

-						}

-						for (const frontend in config.services[service].frontends) {

-							if (config.services[service].frontends[frontend].instanceList) {

-								for (const network in config.networks) {

-									for (const instance of options[frontend][network].enabled) {

-										let i = redirects[frontend][network].indexOf(instance)

-										if (i < 0) options[frontend][network].enabled.splice(i, 1)

-									}

-								}

-							}

-						}

-						/*

-						for (const frontend in config.services[service].frontends) {

-							if (config.services[service].frontends[frontend].instanceList) {

-								for (const network in config.networks) {

-									options[frontend][network].enabled = redirects[frontend][network]

-								}

-								for (const blacklist in r.blacklists) {

-									for (const instance of blacklist) {

-										let i = options[frontend].clearnet.enabled.indexOf(instance)

-										if (i > -1) options[frontend].clearnet.enabled.splice(i, 1)

-									}

-								}

-							}

-						}

-						*/

-						// The above will be implemented with https://github.com/libredirect/libredirect/issues/334

-					}

-					for (const frontend in redirects) {

-						let exists = false

-						for (const service in config.services) if (config.services[service].frontends[frontend]) exists = true

-						if (!exists) delete redirects[frontend]

-						else for (const network in redirects[frontend]) if (!config.networks[network]) delete redirects[frontend][network]

-					}

-					browser.storage.local.set({ redirects, targets, options }, () => resolve())

-				})

-			})

-	})

-}

-

-function initDefaults() {

-	return new Promise(resolve => {

-		fetch("/instances/data.json")

-			.then(response => response.text())

-			.then(data => {

-				fetch("/config/config.json")

-					.then(response => response.text())

-					.then(configData => {

-						browser.storage.local.get(["options", "blacklists"], r => {

-							let redirects = JSON.parse(data)

-							let options = r.options

-							let targets = {}

-							let config = JSON.parse(configData)

-							const localstorage = {}

-							const latency = {}

-							for (const service in config.services) {

-								options[service] = {}

-								if (config.services[service].targets == "datajson") {

-									targets[service] = redirects[service]

-									delete redirects[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] = {}

-										for (const network in config.networks) {

-											options[frontend][network] = {}

-											options[frontend][network].enabled = JSON.parse(data)[frontend][network]

-											options[frontend][network].custom = []

-										}

-										for (const blacklist in r.blacklists) {

-											for (const instance of r.blacklists[blacklist]) {

-												let i = options[frontend].clearnet.enabled.indexOf(instance)

-												if (i > -1) options[frontend].clearnet.enabled.splice(i, 1)

-											}

-										}

-									}

-								}

-							}

-							browser.storage.local.set({ redirects, options, targets, latency, localstorage })

-							resolve()

-						})

-					})

-			})

-	})

-}

-

-function upgradeOptions() {

-	return new Promise(resolve => {

-		fetch("/config/config.json")

-			.then(response => response.text())

-			.then(configData => {

-				browser.storage.local.get(null, r => {

-					let options = r.options

-					let latency = {}

-					const config = JSON.parse(configData)

-					options.exceptions = r.exceptions

-					if (r.theme != "DEFAULT") options.theme = r.theme

-					options.popupServices = r.popupFrontends

-					let tmp = options.popupServices.indexOf("tikTok")

-					if (tmp > -1) {

-						options.popupServices.splice(tmp, 1)

-						options.popupServices.push("tiktok")

-					}

-					tmp = options.popupServices.indexOf("sendTarget")

-					if (tmp > -1) {

-						options.popupServices.splice(tmp, 1)

-						options.popupServices.push("sendFiles")

-					}

-					options.autoRedirect = r.autoRedirect

-					switch (r.onlyEmbeddedVideo) {

-						case "onlyNotEmbedded":

-							options.youtube.redirectType = "main_frame"

-						case "onlyEmbedded":

-							options.youtube.redirectType = "sub_frame"

-						case "both":

-							options.youtube.redirectType = "both"

-					}

-					for (const service in config.services) {

-						let oldService

-						switch (service) {

-							case "tiktok":

-								oldService = "tikTok"

-								break

-							case "sendFiles":

-								oldService = "sendTarget"

-								break

-							default:

-								oldService = service

-						}

-						options[service].enabled = !r["disable" + utils.camelCase(oldService)]

-						if (r[oldService + "Frontend"]) {

-							if (r[oldService + "Frontend"] == "yatte") options[service].frontend = "yattee"

-							else options[service].frontend = r[oldService + "Frontend"]

-						}

-						if (r[oldService + "RedirectType"]) options[service].redirectType = r[oldService + "RedirectType"]

-						if (r[oldService + "EmbedFrontend"] && (service != "youtube" || r[oldService + "EmbedFrontend"] == "invidious" || r[oldService + "EmbedFrontend"] == "piped"))

-							options[service].embedFrontend = r[oldService + "EmbedFrontend"]

-						for (const frontend in config.services[service].frontends) {

-							if (r[frontend + "Latency"]) latency[frontend] = r[frontend + "Latency"]

-							for (const network in config.networks) {

-								let protocol

-								if (network == "clearnet") protocol = "normal"

-								else protocol = network

-								if (r[frontend + utils.camelCase(protocol) + "RedirectsChecks"]) {

-									options[frontend][network].enabled = r[frontend + utils.camelCase(protocol) + "RedirectsChecks"]

-									options[frontend][network].custom = r[frontend + utils.camelCase(protocol) + "CustomRedirects"]

-									for (const instance of options[frontend][network].enabled) {

-										let i = r.redirects[frontend][network].indexOf(instance)

-										if (i < 0) options[frontend][network].enabled.splice(i, 1)

-									}

-								}

-							}

-						}

-					}

-					browser.storage.local.set({ options, latency }, () => resolve())

-				})

-			})

-	})

-}

-

-function processUpdate() {

-	return new Promise(resolve => {

-		fetch("/instances/data.json")

-			.then(response => response.text())

-			.then(data => {

-				fetch("/config/config.json")

-					.then(response => response.text())

-					.then(configData => {

-						browser.storage.local.get(["options", "blacklists", "targets"], r => {

-							let redirects = JSON.parse(data)

-							let options = r.options

-							let targets = r.targets

-							let config = JSON.parse(configData)

-							for (const service in config.services) {

-								if (!options[service]) options[service] = {}

-								if (config.services[service].targets == "datajson") {

-									targets[service] = redirects[service]

-									delete redirects[service]

-								}

-								for (const defaultOption in config.services[service].options) {

-									if (options[service][defaultOption] === undefined) {

-										options[service][defaultOption] = config.services[service].options[defaultOption]

-									}

-								}

-								for (const frontend in config.services[service].frontends) {

-									if (config.services[service].frontends[frontend].instanceList) {

-										if (!options[frontend]) options[frontend] = {}

-										for (const network in config.networks) {

-											if (!options[frontend][network]) {

-												options[frontend][network] = {}

-												options[frontend][network].enabled = JSON.parse(data)[frontend][network]

-												options[frontend][network].custom = []

-												if (network == "clearnet") {

-													for (const blacklist in r.blacklists) {

-														for (const instance of r.blacklists[blacklist]) {

-															let i = options[frontend].clearnet.enabled.indexOf(instance)

-															if (i > -1) options[frontend].clearnet.enabled.splice(i, 1)

-														}

-													}

-												}

-											} else {

-												for (const instance of options[frontend][network].enabled) {

-													let i = redirects[frontend][network].indexOf(instance)

-													if (i < 0) options[frontend][network].enabled.splice(i, 1)

-												}

-											}

-										}

-									}

-								}

-							}

-							browser.storage.local.set({ redirects, options, targets })

-							resolve()

-						})

-					})

-			})

-	})

-}

-

-// For websites that have a strict policy that would not normally allow these frontends to be embedded within the website.

-function modifyContentSecurityPolicy(details) {

-	let isChanged = false

-	if (details.type == "main_frame") {

-		for (const header in details.responseHeaders) {

-			if (details.responseHeaders[header].name == "content-security-policy") {

-				let instancesList = []

-				for (const service in config.services) {

-					if (config.services[service].embeddable) {

-						for (const frontend in config.services[service].frontends) {

-							if (config.services[service].frontends[frontend].embeddable) {

-								for (const network in config.networks) {

-									instancesList.push(...options[frontend][network].enabled, ...options[frontend][network].custom)

-								}

-							}

-						}

-					}

-				}

-				let securityPolicyList = details.responseHeaders[header].value.split(";")

-				for (const i in securityPolicyList) securityPolicyList[i] = securityPolicyList[i].trim()

-				let newSecurity = ""

-				for (const item of securityPolicyList) {

-					if (item.trim() == "") continue

-					let regex = item.match(/([a-z-]{0,}) (.*)/)

-					if (regex == null) continue

-					let [, key, vals] = regex

-					if (key == "frame-src") vals = vals + " " + instancesList.join(" ")

-					newSecurity += key + " " + vals + "; "

-				}

-

-				details.responseHeaders[header].value = newSecurity

-				isChanged = true

-			}

-		}

-		if (isChanged) return { responseHeaders: details.responseHeaders }

-	}

-}

-

-export default {

-	redirect,

-	computeService,

-	switchInstance,

-	reverse,

-	unifyPreferences,

-	setRedirects,

-	initDefaults,

-	upgradeOptions,

-	processUpdate,

-	modifyContentSecurityPolicy,

-}

+window.browser = window.browser || window.chrome
+
+import utils from "./utils.js"
+
+let config, options, redirects, targets
+
+function init() {
+	return new Promise(async resolve => {
+		browser.storage.local.get(["options", "redirects", "targets"], r => {
+			options = r.options
+			redirects = r.redirects
+			targets = r.targets
+			fetch("/config/config.json")
+				.then(response => response.text())
+				.then(configData => {
+					config = JSON.parse(configData)
+					resolve()
+				})
+		})
+	})
+}
+
+init()
+browser.storage.onChanged.addListener(init)
+
+function fetchFrontendInstanceList(service, frontend, redirects, options, config) {
+	let tmp = []
+	if (config.services[service].frontends[frontend].instanceList) {
+		for (const network in config.networks) {
+			tmp.push(...redirects[network], ...options[frontend][network].custom)
+		}
+	} else if (config.services[service].frontends[frontend].singleInstance) tmp = config.services[service].frontends[frontend].singleInstance
+	return tmp
+}
+
+function all(service, frontend, options, config, redirects) {
+	let instances = []
+	if (!frontend) {
+		for (const frontend in config.services[service].frontends) {
+			instances.push(...fetchFrontendInstanceList(service, frontend, redirects[frontend], options, config))
+		}
+	} else {
+		instances.push(...fetchFrontendInstanceList(service, frontend, redirects[frontend], options, config))
+	}
+	return instances
+}
+
+function regexArray(service, url, config) {
+	if (config.services[service].targets == "datajson") {
+		if (targets[service].includes(utils.protocolHost(url))) return true
+	} else {
+		const targetList = config.services[service].targets
+		for (const targetString in targetList) {
+			const target = new RegExp(targetList[targetString])
+			if (target.test(url.href)) return true
+		}
+	}
+	return false
+}
+
+function redirect(url, type, initiator, forceRedirection) {
+	if (type != "main_frame" && type != "sub_frame") return
+	let randomInstance
+	let frontend
+	for (const service in config.services) {
+		if (!forceRedirection && !options[service].enabled) continue
+		if (config.services[service].embeddable && type != options[service].redirectType && options[service].redirectType != "both") continue
+		if (!config.services[service].embeddable && type != "main_frame") continue
+		// let targets = new RegExp(config.services[service].targets.join("|"), "i")
+
+		if (!regexArray(service, url, config)) continue
+		// if (initiator) {
+		// 	console.log(initiator.host)
+		// 	if (targets.test(initiator.host)) continue
+		// 	//if (all(service, null, options, config, redirects).includes(initiator.origin) && reverse(initiator) == url) return "BYPASSTAB"
+		// }
+
+		if (Object.keys(config.services[service].frontends).length > 1) {
+			if (type == "sub_frame" && config.services[service].embeddable && !config.services[service].frontends[options[service].frontend].embeddable) frontend = options[service].embedFrontend
+			else frontend = options[service].frontend
+		} else frontend = Object.keys(config.services[service].frontends)[0]
+
+		if (config.services[service].frontends[frontend].instanceList) {
+			let instanceList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]
+			if (instanceList.length === 0 && options.networkFallback) instanceList = [...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom]
+			if (instanceList.length === 0) return
+			randomInstance = utils.getRandomInstance(instanceList)
+		} else if (config.services[service].frontends[frontend].singleInstance) randomInstance = config.services[service].frontends[frontend].singleInstance
+		break
+	}
+	if (!frontend) return
+
+	// Here is a (temperory) space for defining constants required in 2 or more switch cases.
+	// When possible, try have the two switch cases share all their code as done with searx and searxng.
+	// Do not do that when they do not share 100% of their code.
+
+	const mapCentreRegex = /@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/
+	const dataLatLngRegex = /!3d(-?[0-9]{1,}.[0-9]{1,})!4d(-?[0-9]{1,}.[0-9]{1,})/
+	const placeRegex = /\/place\/(.*)\//
+	function convertMapCentre() {
+		let [lat, lon, zoom] = [null, null, null]
+		if (url.pathname.match(mapCentreRegex)) {
+			// Set map centre if present
+			;[lat, lon, zoom] = url.pathname.match(mapCentreRegex)
+		} else if (url.searchParams.has("center")) {
+			;[lat, lon] = url.searchParams.get("center").split(",")
+			zoom = url.searchParams.get("zoom") ?? "17"
+		}
+		return [zoom, lon, lat]
+	}
+
+	switch (frontend) {
+		// This is where all instance-specific code must be ran to convert the service url to one that can be understood by the frontend.
+		case "beatbump":
+			return `${randomInstance}${url.pathname}${url.search}`
+				.replace("/watch?v=", "/listen?id=")
+				.replace("/channel/", "/artist/")
+				.replace("/playlist?list=", "/playlist/VL")
+				.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/") + "?filter=all")
+		case "hyperpipe":
+			return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/"))
+		case "bibliogram":
+			const reservedPaths = ["u", "p", "privacy"]
+			if (url.pathname === "/" || reservedPaths.includes(url.pathname.split("/")[1])) return `${randomInstance}${url.pathname}${url.search}`
+			if (url.pathname.startsWith("/reel") || url.pathname.startsWith("/tv")) return `${randomInstance}/p${url.pathname.replace(/\/reel|\/tv/i, "")}${url.search}`
+			else return `${randomInstance}/u${url.pathname}${url.search}` // Likely a user profile, redirect to '/u/...'
+		case "lbryDesktop":
+			return url.href.replace(/^https?:\/{2}odysee\.com\//, "lbry://").replace(/:(?=[a-zA-Z0-9])/g, "#")
+		case "neuters":
+			if (url.pathname.startsWith("/article/") || url.pathname.startsWith("/pf/") || url.pathname.startsWith("/arc/") || url.pathname.startsWith("/resizer/")) return null
+			else if (url.pathname.endsWith("/")) return `${randomInstance}${url.pathname}`
+			else return `${randomInstance}${url.pathname}/`
+		case "searx":
+		case "searxng":
+			return `${randomInstance}/?q=${encodeURIComponent(url.searchParams.get("q"))}`
+		case "whoogle":
+			return `${randomInstance}/search?q=${encodeURIComponent(url.searchParams.get("q"))}`
+		case "librex":
+			return `${randomInstance}/search.php?q=${encodeURIComponent(url.searchParams.get("q"))}`
+		case "send":
+			return randomInstance
+		case "nitter":
+			let search = new URLSearchParams(url.search)
+
+			search.delete("ref_src")
+			search.delete("ref_url")
+
+			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}`
+		case "yattee":
+			return url.href.replace(/^https?:\/{2}/, "yattee://")
+		case "freetube":
+			return `freetube://https://youtu.be${url.pathname}${url.search}`.replace(/watch\?v=/, "")
+		case "simplyTranslate":
+			return `${randomInstance}/${url.search}`
+		case "libreTranslate":
+			return `${randomInstance}/${url.search}`
+				.replace(/(?<=\/?)sl/, "source")
+				.replace(/(?<=&)tl/, "target")
+				.replace(/(?<=&)text/, "q")
+		case "osm": {
+			if (initiator && initiator.host === "earth.google.com") return
+			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.
+			}
+
+			function addressToLatLng(address) {
+				const xmlhttp = new XMLHttpRequest()
+				xmlhttp.open("GET", `https://nominatim.openstreetmap.org/search/${address}?format=json&limit=1`, false)
+				xmlhttp.send()
+				if (xmlhttp.status === 200) {
+					const json = JSON.parse(xmlhttp.responseText)[0]
+					if (json) {
+						console.log("json", json)
+						return [`${json.lat},${json.lon}`, `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`]
+					}
+				}
+				console.info("Error: Status is " + xmlhttp.status)
+			}
+
+			let mapCentre = "#"
+			let prefs = {}
+
+			const mapCentreData = convertMapCentre()
+			if (mapCentreData[0] && mapCentreData[1] && mapCentreData[2]) mapCentre = `#map=${mapCentreData[0]}/${mapCentreData[1]}/${mapCentreData[2]}`
+			if (url.searchParams.get("layer")) prefs.layers = osmLayers[url.searchParams.get("layer")]
+
+			if (url.pathname.includes("/embed")) {
+				// Handle Google Maps Embed API
+				// https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
+				//console.log("embed life")
+
+				let query = ""
+				if (url.searchParams.has("q")) query = url.searchParams.get("q")
+				else if (url.searchParams.has("query")) query = url.searchParams.has("query")
+				else if (url.searchParams.has("pb"))
+					try {
+						query = url.searchParams.get("pb").split(/!2s(.*?)!/)[1]
+					} catch (error) {
+						console.error(error)
+					} // Unable to find map marker in URL.
+
+				let [coords, boundingbox] = addressToLatLng(query)
+				prefs.bbox = boundingbox
+				prefs.marker = coords
+				prefs.layer = "mapnik"
+				let prefsEncoded = new URLSearchParams(prefs).toString()
+				return `${randomInstance}/export/embed.html?${prefsEncoded}`
+			} else if (url.pathname.includes("/dir")) {
+				// Handle Google Maps Directions
+				// https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling
+
+				let travMod = url.searchParams.get("travelmode")
+				if (url.searchParams.has("travelmode")) prefs.engine = travelModes[travMod]
+
+				let orgVal = url.searchParams.get("origin")
+				let destVal = url.searchParams.get("destination")
+
+				let org = addressToLatLng(orgVal)
+				let dest = addressToLatLng(destVal)
+				prefs.route = `${org};${dest}`
+
+				let prefsEncoded = new URLSearchParams(prefs).toString()
+				return `${randomInstance}/directions?${prefsEncoded}${mapCentre}`
+			} else if (url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex)) {
+				// Get marker from data attribute
+				// https://www.google.com/maps/place/41%C2%B001'58.2%22N+40%C2%B029'18.2%22E/@41.032833,40.4862063,17z/data=!3m1!4b1!4m6!3m5!1s0x0:0xf64286eaf72fc49d!7e2!8m2!3d41.0328329!4d40.4883948
+				//console.log("data life")
+
+				let [, mlat, mlon] = url.pathname.match(dataLatLngRegex)
+
+				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+			} else if (url.searchParams.has("ll")) {
+				// Get marker from ll param
+				// https://maps.google.com/?ll=38.882147,-76.99017
+				//console.log("ll life")
+
+				const [mlat, mlon] = url.searchParams.get("ll").split(",")
+
+				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+			} else if (url.searchParams.has("viewpoint")) {
+				// Get marker from viewpoint param.
+				// https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
+				//console.log("viewpoint life")
+
+				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")
+
+				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
+			} else {
+				// Use query as search if present.
+				//console.log("normal life")
+
+				let query
+				if (url.searchParams.has("q")) query = url.searchParams.get("q")
+				else if (url.searchParams.has("query")) query = url.searchParams.get("query")
+				else if (url.pathname.match(placeRegex)) query = url.pathname.match(placeRegex)[1]
+
+				let prefsEncoded = new URLSearchParams(prefs).toString()
+				if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded}`
+			}
+
+			let prefsEncoded = new URLSearchParams(prefs).toString()
+			console.log("mapCentre", mapCentre)
+			console.log("prefs", prefs)
+			console.log("prefsEncoded", prefsEncoded)
+			return `${randomInstance}/${mapCentre}&${prefsEncoded}`
+		}
+		case "facil": {
+			if (initiator && initiator.host === "earth.google.com") return
+			const travelModes = {
+				driving: "car",
+				walking: "pedestrian",
+				bicycling: "bicycle",
+				transit: "car", // not implemented on Facil, default to car.
+			}
+			const mapCentreData = convertMapCentre()
+			let mapCentre = "#"
+			if (mapCentreData[0] && mapCentreData[1] && mapCentreData[2]) mapCentre = `#${mapCentreData[0]}/${mapCentreData[1]}/${mapCentreData[2]}`
+
+			if (url.pathname.includes("/embed")) {
+				// Handle Google Maps Embed API
+				// https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
+				//console.log("embed life")
+
+				let query = ""
+				if (url.searchParams.has("q")) query = url.searchParams.get("q")
+				else if (url.searchParams.has("query")) query = url.searchParams.has("query")
+				else if (url.searchParams.has("pb"))
+					try {
+						query = url.searchParams.get("pb").split(/!2s(.*?)!/)[1]
+					} catch (error) {
+						console.error(error)
+					} // Unable to find map marker in URL.
+
+				return `${randomInstance}/#q=${query}`
+			} else if (url.pathname.includes("/dir")) {
+				// Handle Google Maps Directions
+				// https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling
+
+				let travMod = url.searchParams.get("travelmode")
+
+				let orgVal = url.searchParams.get("origin")
+				let destVal = url.searchParams.get("destination")
+
+				return `${randomInstance}/#q=${orgVal}%20to%20${destVal}%20by%20${travelModes[travMod]}`
+			} else if (url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex)) {
+				// Get marker from data attribute
+				// https://www.google.com/maps/place/41%C2%B001'58.2%22N+40%C2%B029'18.2%22E/@41.032833,40.4862063,17z/data=!3m1!4b1!4m6!3m5!1s0x0:0xf64286eaf72fc49d!7e2!8m2!3d41.0328329!4d40.4883948
+				//console.log("data life")
+
+				let [, mlat, mlon] = url.pathname.match(dataLatLngRegex)
+
+				return `${randomInstance}/#q=${mlat}%2C${mlon}`
+			} else if (url.searchParams.has("ll")) {
+				// Get marker from ll param
+				// https://maps.google.com/?ll=38.882147,-76.99017
+				//console.log("ll life")
+
+				const [mlat, mlon] = url.searchParams.get("ll").split(",")
+
+				return `${randomInstance}/#q=${mlat}%2C${mlon}`
+			} else if (url.searchParams.has("viewpoint")) {
+				// Get marker from viewpoint param.
+				// https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
+				//console.log("viewpoint life")
+
+				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")
+
+				return `${randomInstance}/#q=${mlat}%2C${mlon}`
+			} else {
+				// Use query as search if present.
+				//console.log("normal life")
+
+				let query
+				if (url.searchParams.has("q")) query = url.searchParams.get("q")
+				else if (url.searchParams.has("query")) query = url.searchParams.get("query")
+				else if (url.pathname.match(placeRegex)) query = url.pathname.match(placeRegex)[1]
+
+				if (query) return `${randomInstance}/${mapCentre}/Mpnk/${query}`
+			}
+		}
+		case "wikiless":
+			let GETArguments = []
+			if (url.search.length > 0) {
+				let search = url.search.substring(1) //get rid of '?'
+				let argstrings = search.split("&")
+				for (let i = 0; i < argstrings.length; i++) {
+					let args = argstrings[i].split("=")
+					GETArguments.push([args[0], args[1]])
+				}
+			}
+
+			let link = `${randomInstance}${url.pathname}`
+			let urlSplit = url.host.split(".")
+			if (urlSplit[0] != "wikipedia" && urlSplit[0] != "www") {
+				if (urlSplit[0] == "m") GETArguments.push(["mobileaction", "toggle_view_mobile"])
+				else GETArguments.push(["lang", urlSplit[0]])
+				if (urlSplit[1] == "m") GETArguments.push(["mobileaction", "toggle_view_mobile"])
+				// wikiless doesn't have mobile view support yet
+			}
+			for (let i = 0; i < GETArguments.length; i++) link += (i == 0 ? "?" : "&") + GETArguments[i][0] + "=" + GETArguments[i][1]
+			return link
+
+		case "lingva":
+			let params_arr = url.search.split("&")
+			params_arr[0] = params_arr[0].substring(1)
+			let params = {}
+			for (let i = 0; i < params_arr.length; i++) {
+				let pair = params_arr[i].split("=")
+				params[pair[0]] = pair[1]
+			}
+			if (params.sl && params.tl && params.text) {
+				return `${randomInstance}/${params.sl}/${params.tl}/${params.text}`
+			}
+			return randomInstance
+		case "breezeWiki":
+			let wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.fandom\.com)/)
+			if (wiki == "www" || !wiki) wiki = ""
+			else wiki = "/" + wiki
+			if (url.href.search(/Special:Search\?query/) > -1) return `${randomInstance}${wiki}${url.pathname}${url.search}`.replace(/Special:Search\?query/, "search?q").replace(/\/wiki/, "")
+			else return `${randomInstance}${wiki}${url.pathname}${url.search}`
+		case "rimgo":
+			if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) return `${randomInstance}/stack${url.pathname}${url.search}`
+			else return `${randomInstance}${url.pathname}${url.search}`
+		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}`
+			}
+		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}`
+		default:
+			return `${randomInstance}${url.pathname}${url.search}`
+	}
+}
+
+function computeService(url, returnFrontend) {
+	return new Promise(resolve => {
+		fetch("/config/config.json")
+			.then(response => response.text())
+			.then(configData => {
+				const config = JSON.parse(configData)
+				browser.storage.local.get(["redirects", "options"], r => {
+					const redirects = r.redirects
+					const options = r.options
+					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, redirects).includes(utils.protocolHost(url))) {
+									if (returnFrontend) resolve([service, frontend, utils.protocolHost(url)])
+									else resolve(service)
+									return
+								}
+							}
+						}
+					}
+					resolve()
+				})
+			})
+	})
+}
+
+function switchInstance(url) {
+	return new Promise(async resolve => {
+		await init()
+		const protocolHost = utils.protocolHost(url)
+		for (const service in config.services) {
+			if (!all(service, null, options, config, redirects).includes(protocolHost)) continue
+
+			let instancesList
+			if (Object.keys(config.services[service].frontends).length == 1) {
+				const frontend = Object.keys(config.services[service].frontends)[0]
+				instancesList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]
+				if (instancesList.length === 0 && options.networkFallback) instancesList = [...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom]
+			} else {
+				const frontend = options[service].frontend
+				instancesList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]
+				if (instancesList.length === 0 && options.networkFallback) instancesList = [...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom]
+			}
+
+			let oldInstance
+			const i = instancesList.indexOf(protocolHost)
+			if (i > -1) {
+				oldInstance = instancesList[i]
+				instancesList.splice(i, 1)
+			}
+			if (instancesList.length === 0) {
+				resolve()
+				return
+			}
+			const randomInstance = utils.getRandomInstance(instancesList)
+			const oldUrl = `${oldInstance}${url.pathname}${url.search}`
+			// This is to make instance switching work when the instance depends on the pathname, eg https://darmarit.org/searx
+			// Doesn't work because of .includes array method, not a top priotiry atm
+			resolve(oldUrl.replace(oldInstance, randomInstance))
+			return
+		}
+		resolve()
+	})
+}
+
+function reverse(url, urlString) {
+	return new Promise(async resolve => {
+		await init()
+		let protocolHost
+		if (!urlString) protocolHost = utils.protocolHost(url)
+		else protocolHost = url.match(/https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+/)[0]
+		for (const service in config.services) {
+			if (!all(service, null, options, config, redirects).includes(protocolHost)) continue
+
+			switch (service) {
+				case "instagram":
+				case "youtube":
+				case "imdb":
+				case "imgur":
+				case "tiktok":
+				case "twitter":
+				case "reddit":
+				case "imdb":
+				case "reuters":
+				case "quora":
+				case "medium":
+					if (!urlString) resolve(config.services[service].url + url.pathname + url.search)
+					else resolve(url.replace(/https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+/, config.services[service].url))
+					return
+				default:
+					resolve()
+					return
+			}
+		}
+		resolve()
+	})
+}
+
+function unifyPreferences(url, tabId) {
+	return new Promise(async resolve => {
+		await init()
+		const protocolHost = utils.protocolHost(url)
+		for (const service in config.services) {
+			for (const frontend in config.services[service].frontends) {
+				if (all(service, frontend, options, config, redirects).includes(protocolHost)) {
+					let instancesList = [...options[frontend][options.network].enabled, ...options[frontend][options.network].custom]
+					if (options.networkFallback && options.network != "clearnet") instancesList.push(...options[frontend].clearnet.enabled, ...options[frontend].clearnet.custom)
+
+					const frontendObject = config.services[service].frontends[frontend]
+					if ("cookies" in frontendObject.preferences) {
+						for (const cookie of frontendObject.preferences.cookies) {
+							await utils.copyCookie(url, instancesList, cookie)
+						}
+					}
+					if ("localstorage" in frontendObject.preferences) {
+						browser.storage.local.set({ tmp: [frontend, frontendObject.preferences.localstorage] })
+						browser.tabs.executeScript(tabId, {
+							file: "/assets/javascripts/get-localstorage.js",
+							runAt: "document_start",
+						})
+						for (const instance of instancesList)
+							browser.tabs.create({ url: instance }, tab =>
+								browser.tabs.executeScript(tab.id, {
+									file: "/assets/javascripts/set-localstorage.js",
+									runAt: "document_start",
+								})
+							)
+					}
+					/*
+					if ("indexeddb" in frontendObject.preferences) {
+					}
+					if ("token" in frontendObject.preferences) {
+					}
+					*/
+					resolve(true)
+					return
+				}
+			}
+		}
+	})
+}
+
+function setRedirects(passedRedirects) {
+	return new Promise(resolve => {
+		fetch("/config/config.json")
+			.then(response => response.text())
+			.then(configData => {
+				browser.storage.local.get(/* [ */ "options" /* , "blacklists"] */, async r => {
+					let redirects = passedRedirects
+					let options = r.options
+					const config = JSON.parse(configData)
+					let targets = {}
+					for (const service in config.services) {
+						if (config.services[service].targets == "datajson") {
+							targets[service] = redirects[service]
+							delete redirects[service]
+						}
+						for (const frontend in config.services[service].frontends) {
+							if (config.services[service].frontends[frontend].instanceList) {
+								for (const network in config.networks) {
+									for (const instance of options[frontend][network].enabled) {
+										let i = redirects[frontend][network].indexOf(instance)
+										if (i < 0) options[frontend][network].enabled.splice(i, 1)
+									}
+								}
+							}
+						}
+						/*
+						for (const frontend in config.services[service].frontends) {
+							if (config.services[service].frontends[frontend].instanceList) {
+								for (const network in config.networks) {
+									options[frontend][network].enabled = redirects[frontend][network]
+								}
+								for (const blacklist in r.blacklists) {
+									for (const instance of blacklist) {
+										let i = options[frontend].clearnet.enabled.indexOf(instance)
+										if (i > -1) options[frontend].clearnet.enabled.splice(i, 1)
+									}
+								}
+							}
+						}
+						*/
+						// The above will be implemented with https://github.com/libredirect/libredirect/issues/334
+					}
+					for (const frontend in redirects) {
+						let exists = false
+						for (const service in config.services) if (config.services[service].frontends[frontend]) exists = true
+						if (!exists) delete redirects[frontend]
+						else for (const network in redirects[frontend]) if (!config.networks[network]) delete redirects[frontend][network]
+					}
+					browser.storage.local.set({ redirects, targets, options }, () => resolve())
+				})
+			})
+	})
+}
+
+function initDefaults() {
+	return new Promise(resolve => {
+		fetch("/instances/data.json")
+			.then(response => response.text())
+			.then(data => {
+				fetch("/config/config.json")
+					.then(response => response.text())
+					.then(configData => {
+						browser.storage.local.get(["options", "blacklists"], r => {
+							let redirects = JSON.parse(data)
+							let options = r.options
+							let targets = {}
+							let config = JSON.parse(configData)
+							const localstorage = {}
+							const latency = {}
+							for (const service in config.services) {
+								options[service] = {}
+								if (config.services[service].targets == "datajson") {
+									targets[service] = redirects[service]
+									delete redirects[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] = {}
+										for (const network in config.networks) {
+											options[frontend][network] = {}
+											options[frontend][network].enabled = JSON.parse(data)[frontend][network]
+											options[frontend][network].custom = []
+										}
+										for (const blacklist in r.blacklists) {
+											for (const instance of r.blacklists[blacklist]) {
+												let i = options[frontend].clearnet.enabled.indexOf(instance)
+												if (i > -1) options[frontend].clearnet.enabled.splice(i, 1)
+											}
+										}
+									}
+								}
+							}
+							browser.storage.local.set({ redirects, options, targets, latency, localstorage })
+							resolve()
+						})
+					})
+			})
+	})
+}
+
+function upgradeOptions() {
+	return new Promise(resolve => {
+		fetch("/config/config.json")
+			.then(response => response.text())
+			.then(configData => {
+				browser.storage.local.get(null, r => {
+					let options = r.options
+					let latency = {}
+					const config = JSON.parse(configData)
+					options.exceptions = r.exceptions
+					if (r.theme != "DEFAULT") options.theme = r.theme
+					options.popupServices = r.popupFrontends
+					let tmp = options.popupServices.indexOf("tikTok")
+					if (tmp > -1) {
+						options.popupServices.splice(tmp, 1)
+						options.popupServices.push("tiktok")
+					}
+					tmp = options.popupServices.indexOf("sendTarget")
+					if (tmp > -1) {
+						options.popupServices.splice(tmp, 1)
+						options.popupServices.push("sendFiles")
+					}
+					options.autoRedirect = r.autoRedirect
+					switch (r.onlyEmbeddedVideo) {
+						case "onlyNotEmbedded":
+							options.youtube.redirectType = "main_frame"
+						case "onlyEmbedded":
+							options.youtube.redirectType = "sub_frame"
+						case "both":
+							options.youtube.redirectType = "both"
+					}
+					for (const service in config.services) {
+						let oldService
+						switch (service) {
+							case "tiktok":
+								oldService = "tikTok"
+								break
+							case "sendFiles":
+								oldService = "sendTarget"
+								break
+							default:
+								oldService = service
+						}
+						options[service].enabled = !r["disable" + utils.camelCase(oldService)]
+						if (r[oldService + "Frontend"]) {
+							if (r[oldService + "Frontend"] == "yatte") options[service].frontend = "yattee"
+							else options[service].frontend = r[oldService + "Frontend"]
+						}
+						if (r[oldService + "RedirectType"]) options[service].redirectType = r[oldService + "RedirectType"]
+						if (r[oldService + "EmbedFrontend"] && (service != "youtube" || r[oldService + "EmbedFrontend"] == "invidious" || r[oldService + "EmbedFrontend"] == "piped"))
+							options[service].embedFrontend = r[oldService + "EmbedFrontend"]
+						for (const frontend in config.services[service].frontends) {
+							if (r[frontend + "Latency"]) latency[frontend] = r[frontend + "Latency"]
+							for (const network in config.networks) {
+								let protocol
+								if (network == "clearnet") protocol = "normal"
+								else protocol = network
+								if (r[frontend + utils.camelCase(protocol) + "RedirectsChecks"]) {
+									options[frontend][network].enabled = r[frontend + utils.camelCase(protocol) + "RedirectsChecks"]
+									options[frontend][network].custom = r[frontend + utils.camelCase(protocol) + "CustomRedirects"]
+									for (const instance of options[frontend][network].enabled) {
+										let i = r.redirects[frontend][network].indexOf(instance)
+										if (i < 0) options[frontend][network].enabled.splice(i, 1)
+									}
+								}
+							}
+						}
+					}
+					browser.storage.local.set({ options, latency }, () => resolve())
+				})
+			})
+	})
+}
+
+function processUpdate() {
+	return new Promise(resolve => {
+		fetch("/instances/data.json")
+			.then(response => response.text())
+			.then(data => {
+				fetch("/config/config.json")
+					.then(response => response.text())
+					.then(configData => {
+						browser.storage.local.get(["options", "blacklists", "targets"], r => {
+							let redirects = JSON.parse(data)
+							let options = r.options
+							let targets = r.targets
+							let config = JSON.parse(configData)
+							for (const service in config.services) {
+								if (!options[service]) options[service] = {}
+								if (config.services[service].targets == "datajson") {
+									targets[service] = redirects[service]
+									delete redirects[service]
+								}
+								for (const defaultOption in config.services[service].options) {
+									if (options[service][defaultOption] === undefined) {
+										options[service][defaultOption] = config.services[service].options[defaultOption]
+									}
+								}
+								for (const frontend in config.services[service].frontends) {
+									if (config.services[service].frontends[frontend].instanceList) {
+										if (!options[frontend]) options[frontend] = {}
+										for (const network in config.networks) {
+											if (!options[frontend][network]) {
+												options[frontend][network] = {}
+												options[frontend][network].enabled = JSON.parse(data)[frontend][network]
+												options[frontend][network].custom = []
+												if (network == "clearnet") {
+													for (const blacklist in r.blacklists) {
+														for (const instance of r.blacklists[blacklist]) {
+															let i = options[frontend].clearnet.enabled.indexOf(instance)
+															if (i > -1) options[frontend].clearnet.enabled.splice(i, 1)
+														}
+													}
+												}
+											} else {
+												for (const instance of options[frontend][network].enabled) {
+													let i = redirects[frontend][network].indexOf(instance)
+													if (i < 0) options[frontend][network].enabled.splice(i, 1)
+												}
+											}
+										}
+									}
+								}
+							}
+							browser.storage.local.set({ redirects, options, targets })
+							resolve()
+						})
+					})
+			})
+	})
+}
+
+// For websites that have a strict policy that would not normally allow these frontends to be embedded within the website.
+function modifyContentSecurityPolicy(details) {
+	let isChanged = false
+	if (details.type == "main_frame") {
+		for (const header in details.responseHeaders) {
+			if (details.responseHeaders[header].name == "content-security-policy") {
+				let instancesList = []
+				for (const service in config.services) {
+					if (config.services[service].embeddable) {
+						for (const frontend in config.services[service].frontends) {
+							if (config.services[service].frontends[frontend].embeddable) {
+								for (const network in config.networks) {
+									instancesList.push(...options[frontend][network].enabled, ...options[frontend][network].custom)
+								}
+							}
+						}
+					}
+				}
+				let securityPolicyList = details.responseHeaders[header].value.split(";")
+				for (const i in securityPolicyList) securityPolicyList[i] = securityPolicyList[i].trim()
+				let newSecurity = ""
+				for (const item of securityPolicyList) {
+					if (item.trim() == "") continue
+					let regex = item.match(/([a-z-]{0,}) (.*)/)
+					if (regex == null) continue
+					let [, key, vals] = regex
+					if (key == "frame-src") vals = vals + " " + instancesList.join(" ")
+					newSecurity += key + " " + vals + "; "
+				}
+
+				details.responseHeaders[header].value = newSecurity
+				isChanged = true
+			}
+		}
+		if (isChanged) return { responseHeaders: details.responseHeaders }
+	}
+}
+
+export default {
+	redirect,
+	computeService,
+	switchInstance,
+	reverse,
+	unifyPreferences,
+	setRedirects,
+	initDefaults,
+	upgradeOptions,
+	processUpdate,
+	modifyContentSecurityPolicy,
+}
diff --git a/src/config/config.json b/src/config/config.json
index ef598ea8..b1cbd036 100644
--- a/src/config/config.json
+++ b/src/config/config.json
@@ -1,610 +1,610 @@
-{

-	"networks": {

-		"clearnet": {

-			"tld": "org",

-			"name": "Clearnet"

-		},

-		"tor": {

-			"tld": "onion",

-			"name": "Tor"

-		},

-		"i2p": {

-			"tld": "i2p",

-			"name": "I2P"

-		},

-		"loki": {

-			"tld": "loki",

-			"name": "Lokinet"

-		}

-	},

-	"services": {

-		"youtube": {

-			"frontends": {

-				"invidious": {

-					"preferences": {

-						"cookies": ["PREFS"],

-						"localstorage": ["dark_mode"]

-					},

-					"name": "Invidious",

-					"embeddable": true,

-					"instanceList": true

-				},

-				"piped": {

-					"preferences": {

-						"localstorage": [

-							"bufferGoal",

-							"comments",

-							"disableLBRY",

-							"enabledCodecs",

-							"hl",

-							"homepage",

-							"instance",

-							"listen",

-							"minimizeDescription",

-							"playerAutoPlay",

-							"proxyLBRY",

-							"quality",

-							"region",

-							"selectedSkip",

-							"sponsorblock",

-							"theme",

-							"volume",

-							"watchHistory",

-							"localSubscriptions"

-						]

-					},

-					"name": "Piped",

-					"embeddable": true,

-					"instanceList": true

-				},

-				"pipedMaterial": {

-					"preferences": {

-						"localstorage": ["PREFERENCES"]

-					},

-					"name": "Piped-Material",

-					"embeddable": false,

-					"instanceList": true

-				},

-				"cloudtube": {

-					"preferences": {

-						"token": "token",

-						"fetchEndpoint": "/api/settings",

-						"setEndpoint": "/settings"

-					},

-					"name": "CloudTube",

-					"embeddable": false,

-					"instanceList": true

-				},

-				"freetube": {

-					"name": "FreeTube",

-					"embeddable": false,

-					"instanceList": false

-				},

-				"yattee": {

-					"name": "Yattee",

-					"embeddable": false,

-					"instanceList": false

-				}

-			},

-			"targets": [

-				"^https?:\\/{2}(?:www\\.|m\\.|)youtube.com(\\/|$)(?!iframe_api\\/|redirect\\/)",

-				"^https?:\\/{2}img\\.youtube.com\\/vi\\/.*\\/..*",

-				"^https?:\\/{2}(?:i|s)\\.ytimg.com\\/vi\\/.*\\/..*",

-				"^https?:\\/{2}(?:www\\.|)youtube.com\\/watch?v=..*",

-				"^https?:\\/{2}youtu\\.be\\/..*",

-				"^https?:\\/{2}(?:www\\.|)(youtube|youtube-nocookie)\\.com\\/embed\\/..*"

-			],

-			"name": "Youtube",

-			"options": {

-				"enabled": true,

-				"redirectType": "both",

-				"frontend": "invidious",

-				"embedFrontend": "invidious"

-			},

-			"imageType": "png",

-			"embeddable": true,

-			"url": "https://youtube.com"

-		},

-		"youtubeMusic": {

-			"frontends": {

-				"beatbump": {

-					"preferences": {

-						"localstorage": ["settings"],

-						"indexeddb": "beatbump"

-					},

-					"name": "Beatbump",

-					"instanceList": true

-				},

-				"hyperpipe": {

-					"preferences": {

-						"localstorage": ["api", "authapi", "codec", "locale", "next", "pipedapi", "quality", "theme", "vol"],

-						"indexeddb": "hyperpipedb"

-					},

-					"name": "Hyperpipe",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}music\\.youtube\\.com(\\/|$)"],

-			"name": "YT Music",

-			"options": {

-				"enabled": true,

-				"frontend": "beatbump"

-			},

-			"imageType": "png",

-			"embeddable": false,

-			"url": "https://music.youtube.com"

-		},

-		"twitter": {

-			"frontends": {

-				"nitter": {

-					"preferences": {

-						"cookies": [

-							"autoplayGifs",

-							"bidiSupport",

-							"hideBanner",

-							"hidePins",

-							"hideReplies",

-							"hideTweetStats",

-							"hlsPlayback",

-							"infiniteScroll",

-							"mp4Playback",

-							"muteVideos",

-							"proxyVideos",

-							"replaceInstagram",

-							"replaceReddit",

-							"replaceTwitter",

-							"replaceYouTube",

-							"squareAvatars",

-							"theme"

-						]

-					},

-					"name": "Nitter",

-					"embeddable": true,

-					"instanceList": true

-				}

-			},

-			"targets": [

-				"^https?:\\/{2}(www\\.|mobile\\.|)twitter\\.com(\\/|$)",

-				"^https?:\\/{2}(pbs\\.|video\\.|)twimg\\.com(\\/|$)",

-				"^https?:\\/{2}platform\\.twitter\\.com/embed(\\/|$)",

-				"^https?:\\/{2}t\\.co(\\/|$)"

-			],

-			"name": "Twitter",

-			"options": {

-				"enabled": true,

-				"redirectType": "both"

-			},

-			"imageType": "png",

-			"embeddable": true,

-			"url": "https://twitter.com"

-		},

-		"instagram": {

-			"frontends": {

-				"bibliogram": {

-					"preferences": {

-						"token": "token",

-						"fetchEndpoint": "/settings.json",

-						"setEndpoint": "/applysettings"

-					},

-					"name": "Bibliogram",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(www\\.)?instagram\\.com\\/?(p\\/|$)"],

-			"name": "Instagram",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "png",

-			"embeddable": false,

-			"url": "https://instagram.com"

-		},

-		"tiktok": {

-			"frontends": {

-				"proxiTok": {

-					"preferences": {

-						"cookies": ["api-test_endpoints", "theme"]

-					},

-					"name": "ProxiTok",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(www\\.|)tiktok\\.com(\\/|$)"],

-			"name": "TikTok",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "png",

-			"embeddable": false,

-			"url": "https://tiktok.com"

-		},

-		"reddit": {

-			"frontends": {

-				"libreddit": {

-					"preferences": {

-						"cookies": ["theme", "front_page", "layout", "wide", "post_sort", "comment_sort", "show_nsfw", "autoplay_videos", "use_hls", "hide_hls_notification", "subscriptions", "filters"]

-					},

-					"name": "Libreddit",

-					"instanceList": true

-				},

-				"teddit": {

-					"preferences": {

-						"cookies": [

-							"collapse_child_comments",

-							"default_comment_sort",

-							"domain_instagram",

-							"domain_twitter",

-							"domain_youtube",

-							"flairs",

-							"highlight_controversial",

-							"nsfw_enabled",

-							"post_media_max_height",

-							"prefer_frontpage",

-							"show_large_gallery_images",

-							"show_upvoted_percentage",

-							"show_upvotes",

-							"subbed_subreddits",

-							"theme",

-							"videos_muted"

-						]

-					},

-					"name": "Teddit",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(www\\.|old\\.|np\\.|new\\.|amp\\.|)reddit\\.com(?=\\/u(ser)?\\/|\\/r\\/|\\/?$)", "^https?:\\/{2}(i|(external-)?preview)\\.redd\\.it"],

-			"name": "Reddit",

-			"options": {

-				"enabled": true,

-				"frontend": "libreddit"

-			},

-			"imageType": "png",

-			"embeddable": false,

-			"url": "https://reddit.com"

-		},

-		"imgur": {

-			"frontends": {

-				"rimgo": {

-					"name": "rimgo",

-					"embeddable": true,

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}([im]\\.)?(stack\\.)?imgur\\.(com|io)(\\/|$)"],

-			"name": "Imgur",

-			"options": {

-				"enabled": true,

-				"redirectType": "both"

-			},

-			"imageType": "png",

-			"embeddable": true,

-			"url": "https://imgur.com"

-		},

-		"wikipedia": {

-			"frontends": {

-				"wikiless": {

-					"preferences": {

-						"cookies": ["theme", "default_lang"]

-					},

-					"name": "Wikiless",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(?:[a-z]+\\.)*wikipedia\\.org(\\/|$)"],

-			"name": "Wikipedia",

-			"options": {

-				"enabled": false

-			},

-			"imageType": "svg",

-			"embeddable": false,

-			"url": "https://wikipedia.org"

-		},

-		"medium": {

-			"frontends": {

-				"scribe": {

-					"name": "Scribe",

-					"instanceList": true

-				}

-			},

-			"targets": [

-				"(?:.*\\.)*(?<!(link\\.|cdn\\-images\\-\\d+\\.))medium\\.com(\\/.*)?$",

-				"^https?:\\/{2}towardsdatascience\\.com(\\/|$)",

-				"^https?:\\/{2}uxdesign\\.cc(\\/|$)",

-				"^https?:\\/{2}uxplanet\\.org(\\/|$)",

-				"^https?:\\/{2}betterprogramming\\.pub(\\/|$)",

-				"^https?:\\/{2}aninjusticemag\\.com(\\/|$)",

-				"^https?:\\/{2}betterhumans\\.pub(\\/|$)",

-				"^https?:\\/{2}psiloveyou\\.xyz(\\/|$)",

-				"^https?:\\/{2}entrepreneurshandbook\\.co(\\/|$)",

-				"^https?:\\/{2}blog\\.coinbase\\.com(\\/|$)",

-				"^https?:\\/{2}levelup\\.gitconnected\\.com(\\/|$)",

-				"^https?:\\/{2}javascript\\.plainenglish\\.io(\\/|$)",

-				"^https?:\\/{2}blog\\.bitsrc\\.io(\\/|$)",

-				"^https?:\\/{2}itnext\\.io(\\/|$)",

-				"^https?:\\/{2}codeburst\\.io(\\/|$)",

-				"^https?:\\/{2}infosecwriteups\\.com(\\/|$)",

-				"^https?:\\/{2}blog\\.devgenius\\.io(\\/|$)",

-				"^https?:\\/{2}writingcooperative\\.com(\\/|$)"

-			],

-			"name": "Medium",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "svgMono",

-			"embeddable": false,

-			"url": "https://medium.com"

-		},

-		"quora": {

-			"frontends": {

-				"quetre": {

-					"preferences": {

-						"localstorage": ["theme"]

-					},

-					"name": "Quetre",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}([a-zA-Z0-9-]+\\.)*quora\\.com(\\/|$)"],

-			"name": "Quora",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "png",

-			"embeddable": false,

-			"url": "https://quora.com"

-		},

-		"imdb": {

-			"frontends": {

-				"libremdb": {

-					"preferences": {

-						"localstorage": ["theme"]

-					},

-					"name": "libremdb",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(?:www\\.|)imdb\\.com\\/title"],

-			"name": "IMDb",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "svg",

-			"embeddable": false,

-			"url": "https://imdb.com"

-		},

-		"reuters": {

-			"frontends": {

-				"neuters": {

-					"name": "Neuters",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(www\\.|)reuters\\.com(\\/|$)"],

-			"name": "Reuters",

-			"options": {

-				"enabled": false

-			},

-			"imageType": "svg",

-			"embeddable": false,

-			"url": "https://reuters.com"

-		},

-		"fandom": {

-			"frontends": {

-				"breezeWiki": {

-					"name": "BreezeWiki",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}(?:[a-zA-Z0-9-]+\\.)?fandom\\.com(?=\\/wiki|\\/?$)"],

-			"name": "Fandom",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "svg",

-			"embeddable": false,

-			"url": "https://fandom.com"

-		},

-		"peertube": {

-			"frontends": {

-				"simpleertube": {

-					"name": "SimpleerTube",

-					"instanceList": true

-				}

-			},

-			"targets": "datajson",

-			"name": "PeerTube",

-			"options": {

-				"enabled": false

-			},

-			"imageType": "svg",

-			"embeddable": false,

-			"url": "https://joinpeertube.org"

-		},

-		"lbry": {

-			"frontends": {

-				"librarian": {

-					"preferences": {

-						"cookies": ["nsfw", "theme"],

-						"localstorage": ["autoplay", "autoplayNextVid", "collapseComments", "plyr", "sb_categories", "showRelated"]

-					},

-					"name": "Librarian",

-					"embeddable": true,

-					"instanceList": true

-				},

-				"lbryDesktop": {

-					"name": "LBRY Desktop",

-					"embeddable": false,

-					"instanceList": false

-				}

-			},

-			"targets": ["^https?:\\/{2}odysee\\.com(\\/|$)", "^https?:\\/{2}lbry\\.tv(\\/|$)"],

-			"name": "LBRY",

-			"options": {

-				"enabled": true,

-				"frontend": "librarian",

-				"redirectType": "both",

-				"embedFrontend": "librarian"

-			},

-			"imageType": "png",

-			"embeddable": true,

-			"url": "https://odysee.com"

-		},

-		"search": {

-			"frontends": {

-				"searx": {

-					"preferences": {

-						"cookies": [

-							"advanced_search",

-							"autocomplete",

-							"categories",

-							"disabled_engines",

-							"disabled_plugins",

-							"doi_resolver",

-							"enabled_engines",

-							"enabled_plugins",

-							"image_proxy",

-							"language",

-							"locale",

-							"method",

-							"oscar-style",

-							"results_on_new_tab",

-							"safesearch",

-							"theme",

-							"tokens"

-						]

-					},

-					"name": "SearX",

-					"instanceList": true

-				},

-				"searxng": {

-					"preferences": {

-						"cookies": [

-							"autocomplete",

-							"categories",

-							"center_alignment",

-							"disabled_engines",

-							"disabled_plugins",

-							"doi_resolver",

-							"enabled_plugins",

-							"enabled_engines",

-							"image_proxy",

-							"infinite_scroll",

-							"language",

-							"locale",

-							"maintab",

-							"method",

-							"query_in_title",

-							"results_on_new_tab",

-							"safesearch",

-							"simple_style",

-							"theme",

-							"tokens"

-						]

-					},

-					"name": "SearXNG",

-					"instanceList": true

-				},

-				"whoogle": {

-					"name": "Whoogle",

-					"instanceList": true

-				},

-				"librex": {

-					"preferences": {

-						"cookies": ["bibliogram", "disable_frontends", " disable_special", "invidious", "libreddit", "nitter", "proxitok", "save", "theme", "wikiless"]

-					},

-					"name": "LibreX",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}search\\.libredirect\\.invalid"],

-			"name": "Search",

-			"options": {

-				"enabled": true,

-				"frontend": "searxng"

-			},

-			"imageType": "svgMono",

-			"embeddable": false,

-			"url": "https://search.libredirect.invalid"

-		},

-		"translate": {

-			"frontends": {

-				"simplyTranslate": {

-					"preferences": {

-						"cookies": ["from_lang", "to_lang", "tts_enabled", "use_text_fields"]

-					},

-					"name": "SimplyTranslate",

-					"instanceList": true

-				},

-				"lingva": {

-					"preferences": {

-						"localstorage": ["isauto", "source", "target", "chakra-ui-color-mode"]

-					},

-					"name": "Lingva Translate",

-					"instanceList": true

-				},

-				"libreTranslate": {

-					"name": "LibreTranslate",

-					"instanceList": true

-				}

-			},

-			"targets": ["^https?:\\/{2}translate\\.google(\\.[a-z]{2,3}){1,2}\\/", "^https?:\\/{2}translate\\.libredirect\\.invalid"],

-			"name": "Translate",

-			"options": {

-				"enabled": true,

-				"frontend": "simplyTranslate"

-			},

-			"imageType": "svgMono",

-			"embeddable": false,

-			"url": "https://translate.libredirect.invalid"

-		},

-		"maps": {

-			"frontends": {

-				"facil": {

-					"name": "FacilMap",

-					"instanceList": true

-				},

-				"osm": {

-					"name": "OpenStreetMap",

-					"instanceList": false,

-					"singleInstance": "https://www.openstreetmap.org"

-				}

-			},

-			"targets": ["^https?:\\/{2}maps\\.libredirect\\.invalid", "^https?:\\/{2}(((www|maps)\\.)?(google\\.).*(\\/maps)|maps\\.(google\\.).*)"],

-			"name": "Maps",

-			"options": {

-				"enabled": true,

-				"frontend": "osm"

-			},

-			"imageType": "svgMono",

-			"embeddable": false,

-			"url": "https://maps.libredirect.invalid"

-		},

-		"sendFiles": {

-			"frontends": {

-				"send": {

-					"name": "Send",

-					"instanceList": "true"

-				}

-			},

-			"targets": ["^https?:\\/{2}send\\.libredirect\\.invalid", "^https?:\\/{2}send\\.firefox\\.com\\/?$", "^https?:\\/{2}sendfiles\\.online\\/?$"],

-			"name": "Send Files",

-			"options": {

-				"enabled": true

-			},

-			"imageType": "svgMono",

-			"embeddable": false,

-			"url": "https://send.libredirect.invalid"

-		}

-	},

-	"blacklist": {

-		"cloudflare": {

-			"color": "red"

-		},

-		"authenticate": {

-			"color": "orange"

-		},

-		"offline": {

-			"color": "grey"

-		}

-	}

-}

+{
+	"networks": {
+		"clearnet": {
+			"tld": "org",
+			"name": "Clearnet"
+		},
+		"tor": {
+			"tld": "onion",
+			"name": "Tor"
+		},
+		"i2p": {
+			"tld": "i2p",
+			"name": "I2P"
+		},
+		"loki": {
+			"tld": "loki",
+			"name": "Lokinet"
+		}
+	},
+	"services": {
+		"youtube": {
+			"frontends": {
+				"invidious": {
+					"preferences": {
+						"cookies": ["PREFS"],
+						"localstorage": ["dark_mode"]
+					},
+					"name": "Invidious",
+					"embeddable": true,
+					"instanceList": true
+				},
+				"piped": {
+					"preferences": {
+						"localstorage": [
+							"bufferGoal",
+							"comments",
+							"disableLBRY",
+							"enabledCodecs",
+							"hl",
+							"homepage",
+							"instance",
+							"listen",
+							"minimizeDescription",
+							"playerAutoPlay",
+							"proxyLBRY",
+							"quality",
+							"region",
+							"selectedSkip",
+							"sponsorblock",
+							"theme",
+							"volume",
+							"watchHistory",
+							"localSubscriptions"
+						]
+					},
+					"name": "Piped",
+					"embeddable": true,
+					"instanceList": true
+				},
+				"pipedMaterial": {
+					"preferences": {
+						"localstorage": ["PREFERENCES"]
+					},
+					"name": "Piped-Material",
+					"embeddable": false,
+					"instanceList": true
+				},
+				"cloudtube": {
+					"preferences": {
+						"token": "token",
+						"fetchEndpoint": "/api/settings",
+						"setEndpoint": "/settings"
+					},
+					"name": "CloudTube",
+					"embeddable": false,
+					"instanceList": true
+				},
+				"freetube": {
+					"name": "FreeTube",
+					"embeddable": false,
+					"instanceList": false
+				},
+				"yattee": {
+					"name": "Yattee",
+					"embeddable": false,
+					"instanceList": false
+				}
+			},
+			"targets": [
+				"^https?:\\/{2}(?:www\\.|m\\.|)youtube.com(\\/|$)(?!iframe_api\\/|redirect\\/)",
+				"^https?:\\/{2}img\\.youtube.com\\/vi\\/.*\\/..*",
+				"^https?:\\/{2}(?:i|s)\\.ytimg.com\\/vi\\/.*\\/..*",
+				"^https?:\\/{2}(?:www\\.|)youtube.com\\/watch?v=..*",
+				"^https?:\\/{2}youtu\\.be\\/..*",
+				"^https?:\\/{2}(?:www\\.|)(youtube|youtube-nocookie)\\.com\\/embed\\/..*"
+			],
+			"name": "Youtube",
+			"options": {
+				"enabled": true,
+				"redirectType": "both",
+				"frontend": "invidious",
+				"embedFrontend": "invidious"
+			},
+			"imageType": "png",
+			"embeddable": true,
+			"url": "https://youtube.com"
+		},
+		"youtubeMusic": {
+			"frontends": {
+				"beatbump": {
+					"preferences": {
+						"localstorage": ["settings"],
+						"indexeddb": "beatbump"
+					},
+					"name": "Beatbump",
+					"instanceList": true
+				},
+				"hyperpipe": {
+					"preferences": {
+						"localstorage": ["api", "authapi", "codec", "locale", "next", "pipedapi", "quality", "theme", "vol"],
+						"indexeddb": "hyperpipedb"
+					},
+					"name": "Hyperpipe",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}music\\.youtube\\.com(\\/|$)"],
+			"name": "YT Music",
+			"options": {
+				"enabled": true,
+				"frontend": "beatbump"
+			},
+			"imageType": "png",
+			"embeddable": false,
+			"url": "https://music.youtube.com"
+		},
+		"twitter": {
+			"frontends": {
+				"nitter": {
+					"preferences": {
+						"cookies": [
+							"autoplayGifs",
+							"bidiSupport",
+							"hideBanner",
+							"hidePins",
+							"hideReplies",
+							"hideTweetStats",
+							"hlsPlayback",
+							"infiniteScroll",
+							"mp4Playback",
+							"muteVideos",
+							"proxyVideos",
+							"replaceInstagram",
+							"replaceReddit",
+							"replaceTwitter",
+							"replaceYouTube",
+							"squareAvatars",
+							"theme"
+						]
+					},
+					"name": "Nitter",
+					"embeddable": true,
+					"instanceList": true
+				}
+			},
+			"targets": [
+				"^https?:\\/{2}(www\\.|mobile\\.|)twitter\\.com(\\/|$)",
+				"^https?:\\/{2}(pbs\\.|video\\.|)twimg\\.com(\\/|$)",
+				"^https?:\\/{2}platform\\.twitter\\.com/embed(\\/|$)",
+				"^https?:\\/{2}t\\.co(\\/|$)"
+			],
+			"name": "Twitter",
+			"options": {
+				"enabled": true,
+				"redirectType": "both"
+			},
+			"imageType": "png",
+			"embeddable": true,
+			"url": "https://twitter.com"
+		},
+		"instagram": {
+			"frontends": {
+				"bibliogram": {
+					"preferences": {
+						"token": "token",
+						"fetchEndpoint": "/settings.json",
+						"setEndpoint": "/applysettings"
+					},
+					"name": "Bibliogram",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(www\\.)?instagram\\.com\\/?(p\\/|$)"],
+			"name": "Instagram",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "png",
+			"embeddable": false,
+			"url": "https://instagram.com"
+		},
+		"tiktok": {
+			"frontends": {
+				"proxiTok": {
+					"preferences": {
+						"cookies": ["api-test_endpoints", "theme"]
+					},
+					"name": "ProxiTok",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(www\\.|)tiktok\\.com(\\/|$)"],
+			"name": "TikTok",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "png",
+			"embeddable": false,
+			"url": "https://tiktok.com"
+		},
+		"reddit": {
+			"frontends": {
+				"libreddit": {
+					"preferences": {
+						"cookies": ["theme", "front_page", "layout", "wide", "post_sort", "comment_sort", "show_nsfw", "autoplay_videos", "use_hls", "hide_hls_notification", "subscriptions", "filters"]
+					},
+					"name": "Libreddit",
+					"instanceList": true
+				},
+				"teddit": {
+					"preferences": {
+						"cookies": [
+							"collapse_child_comments",
+							"default_comment_sort",
+							"domain_instagram",
+							"domain_twitter",
+							"domain_youtube",
+							"flairs",
+							"highlight_controversial",
+							"nsfw_enabled",
+							"post_media_max_height",
+							"prefer_frontpage",
+							"show_large_gallery_images",
+							"show_upvoted_percentage",
+							"show_upvotes",
+							"subbed_subreddits",
+							"theme",
+							"videos_muted"
+						]
+					},
+					"name": "Teddit",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(www\\.|old\\.|np\\.|new\\.|amp\\.|)reddit\\.com(?=\\/u(ser)?\\/|\\/r\\/|\\/?$)", "^https?:\\/{2}(i|(external-)?preview)\\.redd\\.it"],
+			"name": "Reddit",
+			"options": {
+				"enabled": true,
+				"frontend": "libreddit"
+			},
+			"imageType": "png",
+			"embeddable": false,
+			"url": "https://reddit.com"
+		},
+		"imgur": {
+			"frontends": {
+				"rimgo": {
+					"name": "rimgo",
+					"embeddable": true,
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}([im]\\.)?(stack\\.)?imgur\\.(com|io)(\\/|$)"],
+			"name": "Imgur",
+			"options": {
+				"enabled": true,
+				"redirectType": "both"
+			},
+			"imageType": "png",
+			"embeddable": true,
+			"url": "https://imgur.com"
+		},
+		"wikipedia": {
+			"frontends": {
+				"wikiless": {
+					"preferences": {
+						"cookies": ["theme", "default_lang"]
+					},
+					"name": "Wikiless",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(?:[a-z]+\\.)*wikipedia\\.org(\\/|$)"],
+			"name": "Wikipedia",
+			"options": {
+				"enabled": false
+			},
+			"imageType": "svg",
+			"embeddable": false,
+			"url": "https://wikipedia.org"
+		},
+		"medium": {
+			"frontends": {
+				"scribe": {
+					"name": "Scribe",
+					"instanceList": true
+				}
+			},
+			"targets": [
+				"(?:.*\\.)*(?<!(link\\.|cdn\\-images\\-\\d+\\.))medium\\.com(\\/.*)?$",
+				"^https?:\\/{2}towardsdatascience\\.com(\\/|$)",
+				"^https?:\\/{2}uxdesign\\.cc(\\/|$)",
+				"^https?:\\/{2}uxplanet\\.org(\\/|$)",
+				"^https?:\\/{2}betterprogramming\\.pub(\\/|$)",
+				"^https?:\\/{2}aninjusticemag\\.com(\\/|$)",
+				"^https?:\\/{2}betterhumans\\.pub(\\/|$)",
+				"^https?:\\/{2}psiloveyou\\.xyz(\\/|$)",
+				"^https?:\\/{2}entrepreneurshandbook\\.co(\\/|$)",
+				"^https?:\\/{2}blog\\.coinbase\\.com(\\/|$)",
+				"^https?:\\/{2}levelup\\.gitconnected\\.com(\\/|$)",
+				"^https?:\\/{2}javascript\\.plainenglish\\.io(\\/|$)",
+				"^https?:\\/{2}blog\\.bitsrc\\.io(\\/|$)",
+				"^https?:\\/{2}itnext\\.io(\\/|$)",
+				"^https?:\\/{2}codeburst\\.io(\\/|$)",
+				"^https?:\\/{2}infosecwriteups\\.com(\\/|$)",
+				"^https?:\\/{2}blog\\.devgenius\\.io(\\/|$)",
+				"^https?:\\/{2}writingcooperative\\.com(\\/|$)"
+			],
+			"name": "Medium",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "svgMono",
+			"embeddable": false,
+			"url": "https://medium.com"
+		},
+		"quora": {
+			"frontends": {
+				"quetre": {
+					"preferences": {
+						"localstorage": ["theme"]
+					},
+					"name": "Quetre",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}([a-zA-Z0-9-]+\\.)*quora\\.com(\\/|$)"],
+			"name": "Quora",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "png",
+			"embeddable": false,
+			"url": "https://quora.com"
+		},
+		"imdb": {
+			"frontends": {
+				"libremdb": {
+					"preferences": {
+						"localstorage": ["theme"]
+					},
+					"name": "libremdb",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(?:www\\.|)imdb\\.com\\/title"],
+			"name": "IMDb",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "svg",
+			"embeddable": false,
+			"url": "https://imdb.com"
+		},
+		"reuters": {
+			"frontends": {
+				"neuters": {
+					"name": "Neuters",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(www\\.|)reuters\\.com(\\/|$)"],
+			"name": "Reuters",
+			"options": {
+				"enabled": false
+			},
+			"imageType": "svg",
+			"embeddable": false,
+			"url": "https://reuters.com"
+		},
+		"fandom": {
+			"frontends": {
+				"breezeWiki": {
+					"name": "BreezeWiki",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}(?:[a-zA-Z0-9-]+\\.)?fandom\\.com(?=\\/wiki|\\/?$)"],
+			"name": "Fandom",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "svg",
+			"embeddable": false,
+			"url": "https://fandom.com"
+		},
+		"peertube": {
+			"frontends": {
+				"simpleertube": {
+					"name": "SimpleerTube",
+					"instanceList": true
+				}
+			},
+			"targets": "datajson",
+			"name": "PeerTube",
+			"options": {
+				"enabled": false
+			},
+			"imageType": "svg",
+			"embeddable": false,
+			"url": "https://joinpeertube.org"
+		},
+		"lbry": {
+			"frontends": {
+				"librarian": {
+					"preferences": {
+						"cookies": ["nsfw", "theme"],
+						"localstorage": ["autoplay", "autoplayNextVid", "collapseComments", "plyr", "sb_categories", "showRelated"]
+					},
+					"name": "Librarian",
+					"embeddable": true,
+					"instanceList": true
+				},
+				"lbryDesktop": {
+					"name": "LBRY Desktop",
+					"embeddable": false,
+					"instanceList": false
+				}
+			},
+			"targets": ["^https?:\\/{2}odysee\\.com(\\/|$)", "^https?:\\/{2}lbry\\.tv(\\/|$)"],
+			"name": "LBRY",
+			"options": {
+				"enabled": true,
+				"frontend": "librarian",
+				"redirectType": "both",
+				"embedFrontend": "librarian"
+			},
+			"imageType": "png",
+			"embeddable": true,
+			"url": "https://odysee.com"
+		},
+		"search": {
+			"frontends": {
+				"searx": {
+					"preferences": {
+						"cookies": [
+							"advanced_search",
+							"autocomplete",
+							"categories",
+							"disabled_engines",
+							"disabled_plugins",
+							"doi_resolver",
+							"enabled_engines",
+							"enabled_plugins",
+							"image_proxy",
+							"language",
+							"locale",
+							"method",
+							"oscar-style",
+							"results_on_new_tab",
+							"safesearch",
+							"theme",
+							"tokens"
+						]
+					},
+					"name": "SearX",
+					"instanceList": true
+				},
+				"searxng": {
+					"preferences": {
+						"cookies": [
+							"autocomplete",
+							"categories",
+							"center_alignment",
+							"disabled_engines",
+							"disabled_plugins",
+							"doi_resolver",
+							"enabled_plugins",
+							"enabled_engines",
+							"image_proxy",
+							"infinite_scroll",
+							"language",
+							"locale",
+							"maintab",
+							"method",
+							"query_in_title",
+							"results_on_new_tab",
+							"safesearch",
+							"simple_style",
+							"theme",
+							"tokens"
+						]
+					},
+					"name": "SearXNG",
+					"instanceList": true
+				},
+				"whoogle": {
+					"name": "Whoogle",
+					"instanceList": true
+				},
+				"librex": {
+					"preferences": {
+						"cookies": ["bibliogram", "disable_frontends", " disable_special", "invidious", "libreddit", "nitter", "proxitok", "save", "theme", "wikiless"]
+					},
+					"name": "LibreX",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}search\\.libredirect\\.invalid"],
+			"name": "Search",
+			"options": {
+				"enabled": true,
+				"frontend": "searxng"
+			},
+			"imageType": "svgMono",
+			"embeddable": false,
+			"url": "https://search.libredirect.invalid"
+		},
+		"translate": {
+			"frontends": {
+				"simplyTranslate": {
+					"preferences": {
+						"cookies": ["from_lang", "to_lang", "tts_enabled", "use_text_fields"]
+					},
+					"name": "SimplyTranslate",
+					"instanceList": true
+				},
+				"lingva": {
+					"preferences": {
+						"localstorage": ["isauto", "source", "target", "chakra-ui-color-mode"]
+					},
+					"name": "Lingva Translate",
+					"instanceList": true
+				},
+				"libreTranslate": {
+					"name": "LibreTranslate",
+					"instanceList": true
+				}
+			},
+			"targets": ["^https?:\\/{2}translate\\.google(\\.[a-z]{2,3}){1,2}\\/", "^https?:\\/{2}translate\\.libredirect\\.invalid"],
+			"name": "Translate",
+			"options": {
+				"enabled": true,
+				"frontend": "simplyTranslate"
+			},
+			"imageType": "svgMono",
+			"embeddable": false,
+			"url": "https://translate.libredirect.invalid"
+		},
+		"maps": {
+			"frontends": {
+				"facil": {
+					"name": "FacilMap",
+					"instanceList": true
+				},
+				"osm": {
+					"name": "OpenStreetMap",
+					"instanceList": false,
+					"singleInstance": "https://www.openstreetmap.org"
+				}
+			},
+			"targets": ["^https?:\\/{2}maps\\.libredirect\\.invalid", "^https?:\\/{2}(((www|maps)\\.)?(google\\.).*(\\/maps)|maps\\.(google\\.).*)"],
+			"name": "Maps",
+			"options": {
+				"enabled": true,
+				"frontend": "osm"
+			},
+			"imageType": "svgMono",
+			"embeddable": false,
+			"url": "https://maps.libredirect.invalid"
+		},
+		"sendFiles": {
+			"frontends": {
+				"send": {
+					"name": "Send",
+					"instanceList": "true"
+				}
+			},
+			"targets": ["^https?:\\/{2}send\\.libredirect\\.invalid", "^https?:\\/{2}send\\.firefox\\.com\\/?$", "^https?:\\/{2}sendfiles\\.online\\/?$"],
+			"name": "Send Files",
+			"options": {
+				"enabled": true
+			},
+			"imageType": "svgMono",
+			"embeddable": false,
+			"url": "https://send.libredirect.invalid"
+		}
+	},
+	"blacklist": {
+		"cloudflare": {
+			"color": "red"
+		},
+		"authenticate": {
+			"color": "orange"
+		},
+		"offline": {
+			"color": "grey"
+		}
+	}
+}
diff --git a/src/instances/get_instances.py b/src/instances/get_instances.py
index 9c0543ca..8a0258c0 100644
--- a/src/instances/get_instances.py
+++ b/src/instances/get_instances.py
@@ -12,12 +12,15 @@ import socket
 mightyList = {}
 config = {}
 
-startRegex = r"https?:\/{2}(?:[^\s\/]+\.)+"
+startRegex = r"https?:\/{2}(?:[^\s\/]+\.)*"
 endRegex = "(?:\/[^\s\/]+)*\/?"
 torRegex = startRegex + "onion" + endRegex
 i2pRegex = startRegex + "i2p" + endRegex
 lokiRegex = startRegex + "loki" + endRegex
-authRegex = r"https?:\/{2}\S+:\S+@(?:[^\s\/]+\.)+[a-zA-Z0-9]+" + endRegex
+authRegex = r"https?:\/{2}\S+:\S+@(?:[^\s\/]+\.)*[a-zA-Z0-9]+" + endRegex
+
+# 2.0 because Libredirect is currently on version 2.x.x
+headers = {'User-Agent': 'Libredirect-instance-fetcher/2.0'}
 
 with open('./src/config/config.json', 'rt') as tmp:
     config['networks'] = json.load(tmp)['networks']
@@ -92,7 +95,8 @@ def is_cloudflare(url):
         instance_bin_masked = instance_bin[:mask]
 
         if cloudflare_bin_masked == instance_bin_masked:
-            print(url + ' is behind ' + Fore.RED + 'cloudflare' + Style.RESET_ALL)
+            print(url + ' is behind ' + Fore.RED +
+                  'cloudflare' + Style.RESET_ALL)
             return True
     return False
 
@@ -100,11 +104,13 @@ def is_cloudflare(url):
 def is_authenticate(url):
     try:
         if re.match(authRegex, url):
-            print(url + ' requires ' + Fore.RED + 'authentication' + Style.RESET_ALL)
+            print(url + ' requires ' + Fore.RED +
+                  'authentication' + Style.RESET_ALL)
             return True
-        r = requests.get(url, timeout=5)
+        r = requests.get(url, timeout=5, headers=headers)
         if 'www-authenticate' in r.headers:
-            print(url + ' requires ' + Fore.RED + 'authentication' + Style.RESET_ALL)
+            print(url + ' requires ' + Fore.RED +
+                  'authentication' + Style.RESET_ALL)
             return True
     except Exception:
         return False
@@ -113,7 +119,7 @@ def is_authenticate(url):
 
 def is_offline(url):
     try:
-        r = requests.get(url, timeout=5)
+        r = requests.get(url, timeout=5, headers=headers)
         if r.status_code >= 400:
             print(url + ' is ' + Fore.RED + 'offline' + Style.RESET_ALL)
             print("Status code")
@@ -126,9 +132,12 @@ def is_offline(url):
 
 
 def fetchCache(frontend, name):
-    with open('./src/instances/data.json') as file:
-        mightyList[frontend] = json.load(file)[frontend]
-    print(Fore.YELLOW + 'Failed' + Style.RESET_ALL + ' to fetch ' + name)
+    try:
+        with open('./src/instances/data.json') as file:
+            mightyList[frontend] = json.load(file)[frontend]
+        print(Fore.YELLOW + 'Failed' + Style.RESET_ALL + ' to fetch ' + name)
+    except Exception:
+        print(Fore.RED + 'Failed' + Style.RESET_ALL + ' to get cached ' + name)
 
 
 def fetchFromFile(frontend, name):
@@ -139,7 +148,7 @@ def fetchFromFile(frontend, name):
 
 def fetchJsonList(frontend, name, url, urlItem, jsonObject):
     try:
-        r = requests.get(url)
+        r = requests.get(url, headers=headers)
         rJson = json.loads(r.text)
         if jsonObject:
             rJson = rJson['instances']
@@ -178,7 +187,7 @@ def fetchJsonList(frontend, name, url, urlItem, jsonObject):
 
 def fetchRegexList(frontend, name, url, regex):
     try:
-        r = requests.get(url)
+        r = requests.get(url, headers=headers)
         _list = {}
         for network in config['networks']:
             _list[network] = []
@@ -205,23 +214,32 @@ def fetchRegexList(frontend, name, url, regex):
 
 def fetchTextList(frontend, name, url, prepend):
     try:
-        r = requests.get(url)
-        tmp = r.text.strip().split('\n')
-
         _list = {}
         for network in config['networks']:
             _list[network] = []
 
-        for item in tmp:
-            item = prepend + item
-            if re.search(torRegex, item):
-                _list['tor'].append(item)
-            elif re.search(i2pRegex, item):
-                _list['i2p'].append(item)
-            elif re.search(lokiRegex, item):
-                _list['loki'].append(item)
-            else:
-                _list['clearnet'].append(item)
+        if type(url) == dict:
+            for network in config['networks']:
+                if url[network] is not None:
+                    r = requests.get(url[network], headers=headers)
+                    tmp = r.text.strip().split('\n')
+                    for item in tmp:
+                        item = prepend[network] + item
+                        _list[network].append(item)
+        else:
+            r = requests.get(url, headers=headers)
+            tmp = r.text.strip().split('\n')
+
+            for item in tmp:
+                item = prepend + item
+                if re.search(torRegex, item):
+                    _list['tor'].append(item)
+                elif re.search(i2pRegex, item):
+                    _list['i2p'].append(item)
+                elif re.search(lokiRegex, item):
+                    _list['loki'].append(item)
+                else:
+                    _list['clearnet'].append(item)
         mightyList[frontend] = _list
         print(Fore.GREEN + 'Fetched ' + Style.RESET_ALL + name)
     except Exception:
@@ -239,7 +257,7 @@ def invidious():
         _list['tor'] = []
         _list['i2p'] = []
         _list['loki'] = []
-        r = requests.get(url)
+        r = requests.get(url, headers=headers)
         rJson = json.loads(r.text)
         for instance in rJson:
             if instance[1]['type'] == 'https':
@@ -265,13 +283,13 @@ def piped():
         _list['i2p'] = []
         _list['loki'] = []
         r = requests.get(
-            'https://raw.githubusercontent.com/wiki/TeamPiped/Piped/Instances.md')
+            'https://raw.githubusercontent.com/wiki/TeamPiped/Piped/Instances.md', headers=headers)
 
         tmp = re.findall(
             r'(?:[^\s\/]+\.)+[a-zA-Z]+ (?:\(Official\) )?\| (https:\/{2}(?:[^\s\/]+\.)+[a-zA-Z]+) \| ', r.text)
         for item in tmp:
             try:
-                url = requests.get(item, timeout=5).url
+                url = requests.get(item, timeout=5, headers=headers).url
                 if url.strip("/") == item:
                     continue
                 else:
@@ -287,7 +305,8 @@ def piped():
 
 
 def pipedMaterial():
-    fetchRegexList('pipedMaterial', 'Piped-Material', 'https://raw.githubusercontent.com/mmjee/Piped-Material/master/README.md', r"\| (https?:\/{2}(?:\S+\.)+[a-zA-Z0-9]*) +\| Production")
+    fetchRegexList('pipedMaterial', 'Piped-Material', 'https://raw.githubusercontent.com/mmjee/Piped-Material/master/README.md',
+                   r"\| (https?:\/{2}(?:\S+\.)+[a-zA-Z0-9]*) +\| Production")
 
 
 def cloudtube():
@@ -295,15 +314,18 @@ def cloudtube():
 
 
 def proxitok():
-    fetchRegexList('proxiTok', 'ProxiTok', 'https://raw.githubusercontent.com/wiki/pablouser1/ProxiTok/Public-instances.md', r"\| \[.*\]\(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)(?: \(Official\))? +\|(?:(?: [A-Z]*.*\|.*\|)|(?:$))")
+    fetchRegexList('proxiTok', 'ProxiTok', 'https://raw.githubusercontent.com/wiki/pablouser1/ProxiTok/Public-instances.md',
+                   r"\| \[.*\]\(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)(?: \(Official\))? +\|(?:(?: [A-Z]*.*\|.*\|)|(?:$))")
 
 
 def send():
-    fetchRegexList('send', 'Send', 'https://gitlab.com/timvisee/send-instances/-/raw/master/README.md', r"- ([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z0-9]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)*\|*[A-Z]{0,}")
+    fetchRegexList('send', 'Send', 'https://gitlab.com/timvisee/send-instances/-/raw/master/README.md',
+                   r"- ([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z0-9]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)*\|*[A-Z]{0,}")
 
 
 def nitter():
-    fetchRegexList('nitter', 'Nitter', 'https://raw.githubusercontent.com/wiki/zedeus/nitter/Instances.md', r"(?:(?:\| )|(?:-   ))\[(?:(?:\S+\.)+[a-zA-Z0-9]+)\/?\]\((https?:\/{2}(?:\S+\.)+[a-zA-Z0-9]+)\/?\)(?:(?: (?:\((?:\S+ ?\S*)\) )? *\| [^❌]{1,4} +\|(?:(?:\n)|(?: ❌)|(?: ✅)|(?: ❓)|(?: \[)))|(?:\n))")
+    fetchRegexList('nitter', 'Nitter', 'https://raw.githubusercontent.com/wiki/zedeus/nitter/Instances.md',
+                   r"(?:(?:\| )|(?:-   ))\[(?:(?:\S+\.)+[a-zA-Z0-9]+)\/?\]\((https?:\/{2}(?:\S+\.)+[a-zA-Z0-9]+)\/?\)(?:(?: (?:\((?:\S+ ?\S*)\) )? *\| [^❌]{1,4} +\|(?:(?:\n)|(?: ❌)|(?: ✅)|(?: ❓)|(?: \[)))|(?:\n))")
 
 
 def bibliogram():
@@ -311,65 +333,53 @@ def bibliogram():
 
 
 def libreddit():
-    fetchJsonList('libreddit', 'Libreddit', 'https://github.com/ferritreader/libreddit-instances/raw/master/instances.json', {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, True)
+    fetchJsonList('libreddit', 'Libreddit', 'https://github.com/ferritreader/libreddit-instances/raw/master/instances.json',
+                  {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, True)
 
 
 def teddit():
-    fetchJsonList('teddit', 'Teddit', 'https://codeberg.org/teddit/teddit/raw/branch/main/instances.json', {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, False)
+    fetchJsonList('teddit', 'Teddit', 'https://codeberg.org/teddit/teddit/raw/branch/main/instances.json',
+                  {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, False)
 
 
 def wikiless():
-    fetchJsonList('wikiless', 'Wikiless', 'https://wikiless.org/instances.json', {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, False)
+    fetchJsonList('wikiless', 'Wikiless', 'https://wikiless.org/instances.json',
+                  {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, False)
 
 
 def scribe():
-    fetchJsonList('scribe', 'Scribe', 'https://git.sr.ht/~edwardloveall/scribe/blob/main/docs/instances.json', None, False)
+    fetchJsonList('scribe', 'Scribe',
+                  'https://git.sr.ht/~edwardloveall/scribe/blob/main/docs/instances.json', None, False)
 
 
 def quetre():
-    fetchRegexList('quetre', 'Quetre', 'https://raw.githubusercontent.com/zyachel/quetre/main/README.md', r"\| \[.*\]\(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z0-9]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)*\|*[A-Z]{0,}.*\|.*\|")
+    fetchRegexList('quetre', 'Quetre', 'https://raw.githubusercontent.com/zyachel/quetre/main/README.md',
+                   r"\| \[.*\]\(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z0-9]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)*\|*[A-Z]{0,}.*\|.*\|")
 
 
 def libremdb():
-    fetchRegexList('libremdb', 'libremdb', 'https://raw.githubusercontent.com/zyachel/libremdb/main/README.md', r"\| \[.*\]\(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z0-9]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)*\|*[A-Z]{0,}.*\|.*\|")
+    fetchRegexList('libremdb', 'libremdb', 'https://raw.githubusercontent.com/zyachel/libremdb/main/README.md',
+                   r"\| \[.*\]\(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,}\.[a-z0-9]{2,}\b(?:\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?)\)*\|*[A-Z]{0,}.*\|.*\|")
 
 
 def simpleertube():
-    fetchTextList('simpleertube', 'SimpleerTube', 'https://simple-web.org/instances/simpleertube', 'https://')
+    fetchTextList('simpleertube', 'SimpleerTube', {'clearnet': 'https://simple-web.org/instances/simpleertube', 'tor': 'https://simple-web.org/instances/simpleertube_onion',
+                  'i2p': 'https://simple-web.org/instances/simpleertube_i2p', 'loki': None}, {'clearnet': 'https://', 'tor': 'http://', 'i2p': 'http://', 'loki': 'http://'})
 
 
 def simplytranslate():
-    r = requests.get('https://simple-web.org/instances/simplytranslate')
-    simplyTranslateList = {}
-    simplyTranslateList['clearnet'] = []
-    for item in r.text.strip().split('\n'):
-        simplyTranslateList['clearnet'].append('https://' + item)
-
-    r = requests.get('https://simple-web.org/instances/simplytranslate_onion')
-    simplyTranslateList['tor'] = []
-    for item in r.text.strip().split('\n'):
-        simplyTranslateList['tor'].append('http://' + item)
-
-    r = requests.get('https://simple-web.org/instances/simplytranslate_i2p')
-    simplyTranslateList['i2p'] = []
-    for item in r.text.strip().split('\n'):
-        simplyTranslateList['i2p'].append('http://' + item)
-
-    r = requests.get('https://simple-web.org/instances/simplytranslate_loki')
-    simplyTranslateList['loki'] = []
-    for item in r.text.strip().split('\n'):
-        simplyTranslateList['loki'].append('http://' + item)
-
-    mightyList['simplyTranslate'] = simplyTranslateList
-    print(Fore.GREEN + 'Fetched ' + Style.RESET_ALL + 'SimplyTranslate')
+    fetchTextList('simplyTranslate', 'SimplyTranslate', {'clearnet': 'https://simple-web.org/instances/simplytranslate', 'tor': 'https://simple-web.org/instances/simplytranslate_onion',
+                  'i2p': 'https://simple-web.org/instances/simplytranslate_i2p', 'loki': 'https://simple-web.org/instances/simplytranslate_loki'}, {'clearnet': 'https://', 'tor': 'http://', 'i2p': 'http://', 'loki': 'http://'})
 
 
 def linvgatranslate():
-    fetchJsonList('lingva', 'LingvaTranslate', 'https://raw.githubusercontent.com/TheDavidDelta/lingva-translate/main/instances.json', None, False)
+    fetchJsonList('lingva', 'LingvaTranslate',
+                  'https://raw.githubusercontent.com/TheDavidDelta/lingva-translate/main/instances.json', None, False)
 
 
 def searx_searxng():
-    r = requests.get('https://searx.space/data/instances.json')
+    r = requests.get(
+        'https://searx.space/data/instances.json', headers=headers)
     rJson = json.loads(r.text)
     searxList = {}
     searxList['clearnet'] = []
@@ -404,19 +414,23 @@ def searx_searxng():
 
 
 def whoogle():
-    fetchTextList('whoogle', 'Whoogle', 'https://raw.githubusercontent.com/benbusby/whoogle-search/main/misc/instances.txt', '')
+    fetchRegexList('whoogle', 'Whoogle', 'https://raw.githubusercontent.com/benbusby/whoogle-search/main/README.md',
+                   r"\| \[https?:\/{2}(?:[^\s\/]+\.)*(?:[^\s\/]+\.)+[a-zA-Z0-9]+\]\((https?:\/{2}(?:[^\s\/]+\.)*(?:[^\s\/]+\.)+[a-zA-Z0-9]+)\/?\) \| ")
 
 
 def librex():
-    fetchJsonList('librex', 'LibreX', 'https://raw.githubusercontent.com/hnhx/librex/main/instances.json', {'clearnet': 'clearnet', 'tor': 'tor', 'i2p': 'i2p', 'loki': None}, True)
+    fetchJsonList('librex', 'LibreX', 'https://raw.githubusercontent.com/hnhx/librex/main/instances.json',
+                  {'clearnet': 'clearnet', 'tor': 'tor', 'i2p': 'i2p', 'loki': None}, True)
 
 
 def rimgo():
-    fetchJsonList('rimgo', 'rimgo', 'https://codeberg.org/video-prize-ranch/rimgo/raw/branch/main/instances.json', {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, False)
+    fetchJsonList('rimgo', 'rimgo', 'https://codeberg.org/video-prize-ranch/rimgo/raw/branch/main/instances.json',
+                  {'clearnet': 'url', 'tor': 'onion', 'i2p': 'i2p', 'loki': None}, False)
 
 
 def librarian():
-    fetchJsonList('librarian', 'Librarian', 'https://codeberg.org/librarian/librarian/raw/branch/main/instances.json', 'url', True)
+    fetchJsonList('librarian', 'Librarian',
+                  'https://codeberg.org/librarian/librarian/raw/branch/main/instances.json', 'url', True)
 
 
 def neuters():
@@ -428,7 +442,8 @@ def beatbump():
 
 
 def hyperpipe():
-    fetchJsonList('hyperpipe', 'Hyperpipe', 'https://codeberg.org/Hyperpipe/pages/raw/branch/main/api/frontend.json', 'url', False)
+    fetchJsonList('hyperpipe', 'Hyperpipe',
+                  'https://codeberg.org/Hyperpipe/pages/raw/branch/main/api/frontend.json', 'url', False)
 
 
 def facil():
@@ -436,17 +451,19 @@ def facil():
 
 
 def libreTranslate():
-    fetchRegexList('libreTranslate', 'LibreTranslate', 'https://raw.githubusercontent.com/LibreTranslate/LibreTranslate/main/README.md', r"\[(?:[^\s\/]+\.)+[a-zA-Z0-9]+\]\((https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+)\/?\)\|")
+    fetchRegexList('libreTranslate', 'LibreTranslate', 'https://raw.githubusercontent.com/LibreTranslate/LibreTranslate/main/README.md',
+                   r"\[(?:[^\s\/]+\.)+[a-zA-Z0-9]+\]\((https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+)\/?\)\|")
 
 
 def breezeWiki():
-    fetchRegexList('breezeWiki', 'BreezeWiki', 'https://gitdab.com/cadence/breezewiki-docs/raw/branch/main/docs.scrbl', r"\(\"[^\n\s\r\t\f\v\"]+\" \"https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+(?:\/[^\s\/]+)*\" \"(https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+(?:\/[^\s\/]+)*)\"\)")
+    fetchRegexList('breezeWiki', 'BreezeWiki', 'https://gitdab.com/cadence/breezewiki-docs/raw/branch/main/docs.scrbl',
+                   r"\(\"[^\n\s\r\t\f\v\"]+\" \"https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+(?:\/[^\s\/]+)*\" \"(https?:\/{2}(?:[^\s\/]+\.)+[a-zA-Z0-9]+(?:\/[^\s\/]+)*)\"\)")
 
 
 def peertube():
     try:
         r = requests.get(
-            'https://instances.joinpeertube.org/api/v1/instances?start=0&count=1045&sort=-createdAt')
+            'https://instances.joinpeertube.org/api/v1/instances?start=0&count=1045&sort=-createdAt', headers=headers)
         rJson = json.loads(r.text)
 
         myList = ['https://search.joinpeertube.org']