From 65243b5b9354034d2d46cbf69dfa4d96b0a76632 Mon Sep 17 00:00:00 2001 From: Hygna Date: Tue, 25 Oct 2022 12:43:59 +0100 Subject: [PATCH] Improved the instance fetcher Changed the image used in CI Started fetching Whoogle & SimplyTranslate tor & i2p instances Started using a custom user agent for transparency --- .prettierrc.json | 1 + .woodpecker.yml | 2 +- README.md | 3 +- src/assets/javascripts/services.js | 1702 ++++++++++++++-------------- src/config/config.json | 1220 ++++++++++---------- src/instances/get_instances.py | 161 +-- web-ext-config.js | 4 +- 7 files changed, 1556 insertions(+), 1537 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 3ac20a0..ce2a27b 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -5,6 +5,7 @@ "arrowParens": "avoid", "printWidth": 200, "bracketSameLine": true, + "endOfLine": "lf", "overrides": [ { "files": ["*.js", "*.json"], diff --git a/.woodpecker.yml b/.woodpecker.yml index 177368e..3c7d58b 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,6 +1,6 @@ pipeline: instancefetch: - image: python:alpine + image: python:slim secrets: [token, mail] when: - event: cron diff --git a/README.md b/README.md index 099717a..09ddf43 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ A web extension that redirects YouTube, Twitter, Instagram... requests to altern [![Codeberg](https://raw.githubusercontent.com/ManeraKai/manerakai/main/icons/codeberg.svg)](https://codeberg.org/LibRedirect/libredirect)   [![GitHub](https://raw.githubusercontent.com/ManeraKai/manerakai/main/icons/github.svg)](https://github.com/libredirect/libredirect/)   - ## Translate [![Weblate](./img/weblate.svg)](https://hosted.weblate.org/projects/libredirect/extension) @@ -72,9 +71,11 @@ npm install ``` To generate html that uses `config.json` (needed to develop/build the extention), run: + ``` npm run ejs ``` + Afterwards, you will need to run it if you modify `config.json` or any files ending with .ejs. ### Build the extention zip archive: diff --git a/src/assets/javascripts/services.js b/src/assets/javascripts/services.js index d02e03f..e67ed84 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 ef598ea..b1cbd03 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": [ - "(?:.*\\.)*(?= 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'] diff --git a/web-ext-config.js b/web-ext-config.js index dd90dec..8c691b0 100644 --- a/web-ext-config.js +++ b/web-ext-config.js @@ -5,6 +5,6 @@ module.exports = { browserConsole: true, }, build: { - overwriteDest: true - } + overwriteDest: true, + }, }