diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-09-11 18:44:14 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-09-11 18:44:14 +0200 |
commit | 2af0f5e64e47c59e575802249983feb8968959b1 (patch) | |
tree | a2d80749d6b297ef05b1893949081b678f9e8677 /src/assets/javascripts | |
parent | chore(Merge): remote-tracking branch 'origin/master' (diff) | |
parent | Translated using Weblate (Arabic) (diff) | |
download | libredirect-2af0f5e64e47c59e575802249983feb8968959b1.zip |
chore(merge): Merge remote-tracking branch 'origin/master'
Following Conflicts have been resolved: README.md src/_locales/bs/messages.json src/_locales/de/messages.json src/_locales/en/messages.json src/_locales/fr/messages.json src/_locales/ko/messages.json src/_locales/nb_NO/messages.json src/_locales/pt/messages.json src/_locales/pt_BR/messages.json src/_locales/sr/messages.json src/_locales/vi/messages.json src/assets/images/libredirect.svg src/assets/javascripts/services.js src/config.json src/manifest.json src/updates/updates.xml
Diffstat (limited to 'src/assets/javascripts')
-rw-r--r-- | src/assets/javascripts/localise.js | 34 | ||||
-rw-r--r-- | src/assets/javascripts/services.js | 1713 | ||||
-rw-r--r-- | src/assets/javascripts/utils.js | 345 |
3 files changed, 1101 insertions, 991 deletions
diff --git a/src/assets/javascripts/localise.js b/src/assets/javascripts/localise.js deleted file mode 100644 index c0936873..00000000 --- a/src/assets/javascripts/localise.js +++ /dev/null @@ -1,34 +0,0 @@ -window.browser = window.browser || window.chrome - -function localisePage() { - /** - * @param {string} tag - */ - function getMessage(tag) { - return tag.replace(/__MSG_(\w+)__/g, (_match, v1) => { - return v1 ? browser.i18n.getMessage(v1) : null - }) - } - - const elements = document.querySelectorAll("[data-localise]") - for (let i in elements) - if (elements.hasOwnProperty(i)) { - const obj = elements[i] - const tag = obj.getAttribute("data-localise").toString() - const msg = getMessage(tag) - if (msg && msg !== tag) obj.textContent = msg - } - - const placeholders = document.querySelectorAll("[data-localise-placeholder]") - for (let i in placeholders) - if (placeholders.hasOwnProperty(i)) { - const obj = placeholders[i] - const tag = obj.getAttribute("data-localise-placeholder").toString() - const msg = getMessage(tag) - if (msg && msg !== tag) obj.placeholder = msg - } -} - -export default { - localisePage, -} diff --git a/src/assets/javascripts/services.js b/src/assets/javascripts/services.js index 0aea56cc..7213380f 100644 --- a/src/assets/javascripts/services.js +++ b/src/assets/javascripts/services.js @@ -5,604 +5,675 @@ window.browser = window.browser || window.chrome let config, options -function init() { - return new Promise(async resolve => { - options = await utils.getOptions() - config = await utils.getConfig() - resolve() - }) +async function init() { + options = await utils.getOptions() + config = await utils.getConfig() } init() browser.storage.onChanged.addListener(init) function all(service, frontend, options, config) { - let instances = [] - if (!frontend) { - for (const frontend in config.services[service].frontends) { - if (options[frontend]) { - instances.push(...options[frontend]) - } - } - } else if (options[frontend]) { - instances = options[frontend] - } - return instances + let instances = [] + if (!frontend) { + for (const frontend in config.services[service].frontends) { + if (options[frontend]) { + instances.push(...options[frontend]) + } + } + } else if (options[frontend]) { + instances = options[frontend] + } + return instances } /** * @param {string} service * @param {URL} url * @param {{}} config + * @param {{}} options * @param {string} frontend */ -function regexArray(service, url, config, frontend) { - let targetList = config.services[service].targets - if (frontend && 'excludeTargets' in config.services[service].frontends[frontend]) { - targetList = targetList.filter(val => - !config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val)) - ) - } - for (const targetString in targetList) { - const target = new RegExp(targetList[targetString]) - if (target.test(url.href)) return true - } - return false +function regexArray(service, url, config, options, frontend) { + let targetList = config.services[service].targets + if (frontend && "excludeTargets" in config.services[service].frontends[frontend]) { + if (service !== "search" || !options["search"].redirectGoogle) { + targetList = targetList.filter( + val => !config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val)) + ) + } + } + for (const targetString in targetList) { + const target = new RegExp(targetList[targetString]) + if (target.test(url.href)) return true + } + return false +} + +/** + * @param {URL} url + * @param {string} frontend + * @param {string} randomInstance + * @returns {undefined|string} + */ +function rewrite(url, originUrl, frontend, randomInstance) { + switch (frontend) { + case "hyperpipe": + for (const key of [...url.searchParams.keys()]) if (key !== "q") url.searchParams.delete(key) + return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => + searchQuery.replace("?q=", "/") + ) + case "searx": + case "searxng": + for (const key of [...url.searchParams.keys()]) if (key !== "q") url.searchParams.delete(key) + return `${randomInstance}/${url.search}` + case "whoogle": + for (const key of [...url.searchParams.keys()]) if (key !== "q") url.searchParams.delete(key) + return `${randomInstance}/search${url.search}` + case "4get": { + const s = url.searchParams.get("q") + if (s !== null) return `${randomInstance}/web?s=${encodeURIComponent(s)}` + return randomInstance + } + case "librey": + for (const key in url.searchParams.keys()) if (key != "q") url.searchParams.delete(key) + return `${randomInstance}/search.php${url.search}` + case "yattee": + url.searchParams.delete("si") + return url.href.replace(/^https?:\/{2}/, "yattee://") + case "freetube": + url.searchParams.delete("si") + return "freetube://" + url.href + case "freetubePwa": + url.searchParams.delete("si") + return "freetube://" + url.href + case "poketube": { + url.searchParams.delete("si") + if (url.pathname.startsWith("/channel")) { + const reg = /\/channel\/(.*)\/?$/.exec(url.pathname) + if (reg) { + const id = reg[1] + return `${randomInstance}/channel?id=${id}${url.search}` + } + } + if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance + return `${randomInstance}${url.pathname}${url.search}` + } + case "libMedium": + case "scribe": { + const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/) + if (regex && regex.length > 1) { + const subdomain = regex[1] + if (subdomain != "link" || !subdomain.startsWith("cdn-images")) { + return `${randomInstance}/@${subdomain}${url.pathname}${url.search}` + } + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "translite": + case "simplyTranslate": + return `${randomInstance}/${url.search}` + case "send": + case "mozhi": + return randomInstance + case "libreTranslate": + return `${randomInstance}/${url.search.replace("sl", "source").replace("tl", "target").replace("text", "q")}` + case "osm": { + if (originUrl && originUrl.host === "earth.google.com") return randomInstance + + let prefs = { layers: "mapnik" } + + let mapCentre = "#" + const mapCentreData = utils.convertMapCentre(url) + if (mapCentreData.zoom && mapCentreData.lon && mapCentreData.lat) { + mapCentre = `#map=${mapCentreData.zoom}/${mapCentreData.lon}/${mapCentreData.lat}` + } + + if (url.pathname.includes("/embed")) { + // https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France + const query = utils.getQuery(url) + let { coordinate, boundingbox } = utils.addressToLatLng(query) + prefs.bbox = boundingbox + prefs.marker = coordinate + return `${randomInstance}/export/embed.html?${utils.prefsEncoded(prefs)}` + } + + if (url.pathname.includes("/dir")) { + if (url.searchParams.has("travelmode")) { + const travelModes = { + driving: "fossgis_osrm_car", + walking: "fossgis_osrm_foot", + bicycling: "fossgis_osrm_bike", + transit: "fossgis_osrm_car", // not implemented on OSM, default to car. + } + prefs.engine = travelModes[url.searchParams.get("travelmode")] + } + const regex1 = /\/dir\/([^@/]+)\/([^@/]+)\/@-?\d[0-9.]*,-?\d[0-9.]*,\d{1,2}[.z]/.exec(url.pathname) + const regex2 = /\/dir\/([^@/]+)\//.exec(url.pathname) + if (regex1) { + // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/M%C3%A9dip%C3%B4le+Lyon-Villeurbanne/@45.760254,4.8486298,13z?travelmode=bicycling + const origin = utils.addressToLatLng(decodeURIComponent(regex1[1])).coordinate ?? "" + const destination = utils.addressToLatLng(decodeURIComponent(regex1[2])).coordinate ?? "" + prefs.route = `${origin};${destination}` + } else if (regex2) { + // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/@45.760254,4.8486298,13z?travelmode=bicycling + const origin = utils.addressToLatLng(decodeURIComponent(regex2[1])).coordinate ?? "" + prefs.route = `${origin};` + } else { + // https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling + const origin = utils.addressToLatLng(url.searchParams.get("origin")).coordinate ?? "" + const destination = utils.addressToLatLng(url.searchParams.get("destination")).coordinate ?? "" + prefs.route = `${origin};${destination}` + } + return `${randomInstance}/directions?${utils.prefsEncoded(prefs)}${mapCentre}` + } + + const placeRegex = /\/place\/(.*?)\// + if (url.pathname.match(placeRegex)) { + // https://www.google.com/maps/place/H%C3%B4tel+de+Londres+Eiffel/@40.9845265,28.7081268,14z + const query = url.pathname.match(placeRegex)[1] + return `${randomInstance}/search?query=${query}${mapCentre}` + } + + if (url.searchParams.has("ll")) { + // https://maps.google.com/?ll=38.882147,-76.99017 + const [mlat, mlon] = url.searchParams.get("ll").split(",") + return `${randomInstance}/search?query=${mlat}%2C${mlon}` + } + + if (url.searchParams.has("viewpoint")) { + // https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80 + const [mlat, mlon] = url.searchParams.get("viewpoint").split(",") + return `${randomInstance}/search?query=${mlat}%2C${mlon}` + } + + const query = utils.getQuery(url) + if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${utils.prefsEncoded(prefs)}` + return `${randomInstance}/${mapCentre}&${utils.prefsEncoded(prefs)}` + } + case "breezeWiki": { + let wiki, + urlpath = "" + if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) { + wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/) + if (wiki == "www" || !wiki) wiki = "" + else wiki = `/${wiki}` + urlpath = url.pathname + } else { + wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/) + if (!wiki) wiki = "" + else { + wiki = "/" + wiki + "/wiki/" + urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/) + } + } + if (url.href.search(/Special:Search\?query/) > -1) { + return `${randomInstance}${wiki}${urlpath}${url.search}` + .replace(/Special:Search\?query/, "search?q") + .replace(/\/wiki/, "") + } + return `${randomInstance}${wiki}${urlpath}${url.search}` + } + case "rimgo": + if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) + return `${randomInstance}/stack${url.pathname}${url.search}` + return `${randomInstance}${url.pathname}${url.search}` + case "redlib": + case "libreddit": { + const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/) + if (!subdomain) return `${randomInstance}${url.pathname}${url.search}` + switch (subdomain[0]) { + case "preview": + return `${randomInstance}/preview/pre${url.pathname}${url.search}` + case "external-preview": + return `${randomInstance}/preview/external-pre${url.pathname}${url.search}` + case "i": + return `${randomInstance}/img${url.pathname}` + } + return randomInstance + } + case "teddit": + if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) { + if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}` + else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}` + } + return `${randomInstance}${url.pathname}${url.search}` + case "eddrit": + if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) return randomInstance + return `${randomInstance}${url.pathname}${url.search}` + case "neuters": { + const p = url.pathname + if (p.startsWith("/article/") || p.startsWith("/pf/") || p.startsWith("/arc/") || p.startsWith("/resizer/")) { + return randomInstance + } + return `${randomInstance}${p}` + } + case "dumb": + if (url.pathname.endsWith("-lyrics")) return `${randomInstance}${url.pathname}` + return `${randomInstance}${url.pathname}${url.search}` + case "intellectual": + return `${randomInstance}${url.pathname}${url.search}` + case "ruralDictionary": + if (!url.pathname.includes("/define.php") && !url.pathname.includes("/random.php") && url.pathname != "/") + return randomInstance + return `${randomInstance}${url.pathname}${url.search}` + case "anonymousOverflow": { + if (url.hostname == "stackoverflow.com") { + const threadID = /^\/a\/(\d+)\/?/.exec(url.pathname) + if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}` + return `${randomInstance}${url.pathname}${url.search}` + } + if (url.pathname == "/" || url.pathname == "") { + // https://stackexchange.com or https://superuser.com + return `${randomInstance}${url.pathname}${url.search}` + } + const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.)?stackexchange\.com\//) + if (regex && regex.length > 1) { + const subdomain = regex[1] + return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}` + } + const notExchangeRegex = url.hostname.match( + /(?:[a-zA-Z]+\.)?(?:askubuntu\.com|mathoverflow\.net|serverfault\.com|stackapps\.com|superuser\.com|stackoverflow\.com)/ + ) + if (notExchangeRegex) { + return `${randomInstance}/exchange/${notExchangeRegex[0]}${url.pathname}${url.search}` + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "biblioReads": + return `${randomInstance}${url.pathname}${url.search}` + case "wikiless": { + let hostSplit = url.host.split(".") + // wikiless doesn't have mobile view support yet + if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") { + if (hostSplit[0] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile") + else url.searchParams.append("lang", hostSplit[0]) + if (hostSplit[1] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile") + } + return `${randomInstance}${url.pathname}${url.search}${url.hash}` + } + case "proxiTok": + if (url.pathname.startsWith("/email")) return randomInstance + return `${randomInstance}${url.pathname}${url.search}` + case "waybackClassic": { + const regex = /^\/\web\/(?:[0-9]+)?\*\/(.*)/.exec(url.pathname) + if (regex) { + const link = regex[1] + return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}` + } + const regex2 = /(^\/\web\/([0-9]+)\/.*)/.exec(url.pathname) + if (regex2) { + let link = regex2[1] + link = link.replace(regex2[2], regex2[2] + "if_") + return `https://web.archive.org${link}` + } + return + } + case "gothub": + if (url.hostname == "gist.github.com") return `${randomInstance}/gist${url.pathname}${url.search}` + if (url.hostname == "raw.githubusercontent.com") return `${randomInstance}/raw${url.pathname}${url.search}` + return `${randomInstance}${url.pathname}${url.search}` + case "mikuInvidious": + if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == "b23.tv") + return `${randomInstance}${url.pathname}${url.search}` + if (url.hostname == "space.bilibili.com") return `${randomInstance}/space${url.pathname}${url.search}` + case "tent": { + if (url.hostname == "bandcamp.com" && url.pathname == "/search") { + const query = url.searchParams.get("q") + return `${randomInstance}/search.php?query=${encodeURIComponent(query)}` + } + if (url.hostname.endsWith("bandcamp.com")) { + const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname) + const artist = regex[1] + if (url.pathname == "/" || url.pathname == "/music") { + return `${randomInstance}/artist.php?name=${artist}` + } else { + const regex = /^\/(.*)\/(.*)/.exec(url.pathname) + if (regex) { + const type = regex[1] + const name = regex[2] + return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}` + } + } + } + if (url.hostname == "f4.bcbits.com") { + const regex = /\/img\/(.*)/.exec(url.pathname) + const image = regex[1] + return `${randomInstance}/image.php?file=${image}` + } + if (url.hostname == "t4.bcbits.com") { + const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname) + if (regex) { + const directory = regex[1] + const format = regex[2] + const file = regex[3] + const token = url.searchParams.get("token") + return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}` + } + } + } + case "binternet": + if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}` + return `${randomInstance}${url.pathname}${url.search}` + case "laboratory": { + let path = url.pathname + if (path == "/") path = "" + return `${randomInstance}/${url.hostname}${path}${url.search}` + } + case "quetre": { + const regex = /([a-z]+)\.quora\.com/.exec(url.hostname) + if (regex) { + const lang = regex[1] + url.searchParams.append("lang", lang) + return `${randomInstance}${url.pathname}${url.search}` + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "liteXiv": + case "pixivFe": { + const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname) + if (regex) { + const path = regex[1] + return `${randomInstance}/${path}${url.search}` + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "invidious": { + url.searchParams.delete("si") + if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) { + const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1) + return `${randomInstance}/watch?v=${watch}${url.search.replace("?", "&")}` + } + if (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/redirect?")) return url.href + return `${randomInstance}${url.pathname}${url.search}` + } + case "freetubeMusic": { + if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) { + const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1) + return `freetube://youtube.com/watch?v=${watch}` + } + return "freetube://" + url.href + } + case "invidiousMusic": { + if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) { + const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1) + return `${randomInstance}/watch?v=${watch}` + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "materialious": { + url.searchParams.delete("si") + if (url.hostname == "youtu.be" || (url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live"))) { + const watch = url.pathname.substring(url.pathname.lastIndexOf("/") + 1) + return `${randomInstance}/watch/${watch}${url.search.replace("?", "&")}` + } + if (url.hostname.endsWith("youtube.com")) { + if (url.pathname.startsWith("/watch")) { + if (url.searchParams.has("v")) { + const watch = url.searchParams.get("v") + url.searchParams.delete("v") + return `${randomInstance}/watch/${watch}${url.search.replace("?", "&")}` + } + return `${randomInstance}/watch/${url.search.replace("?", "&")}` + } + if (url.pathname.startsWith("/results")) { + if (url.searchParams.has("search_query")) { + const search = url.searchParams.get("search_query") + url.searchParams.delete("search_query") + return `${randomInstance}/search/${search}${url.search.replace("?", "&")}` + } + return `${randomInstance}/search/${url.search.replace("?", "&")}` + } + if (url.pathname.startsWith("/redirect?")) { + return url.href + } + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "libremdb": { + if (url.pathname.startsWith("/Name")) { + for (const [key, value] of url.searchParams.entries()) { + return `${randomInstance}/title/${encodeURIComponent(key)}` + } + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "tuboYoutube": + url.searchParams.delete("si") + if (url.pathname.startsWith("/channel")) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}` + if (url.pathname.startsWith("/watch")) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}` + return randomInstance + case "tuboSoundcloud": + if (url.pathname == "/") return `${randomInstance}?kiosk?serviceId=1` + if (url.pathname.match(/^\/[^\/]+(\/$|$)/)) return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}` + if (url.pathname.match(/^\/[^\/]+\/[^\/]+/)) return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}` + return randomInstance + case "twineo": + case "safetwitch": + if (url.hostname.startsWith("clips.")) return `${randomInstance}/clip${url.pathname}${url.search}` + return `${randomInstance}${url.pathname}${url.search}` + + case "tekstoLibre": + return `${randomInstance}/?${url.pathname.slice(1)}` + case "skyview": + if (url.pathname == "/") return randomInstance + return `${randomInstance}?url=${encodeURIComponent(url.href)}` + case "nitter": { + let search = new URLSearchParams(url.search) + + search.delete("ref_src") + search.delete("ref_url") + search.delete("s") // type of device that shared the link + search.delete("t") // some sort of tracking ID + + search = search.toString() + if (search !== "") search = `?${search}` + + if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") { + try { + const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/) + const query = encodeURIComponent(`${id}.${format}?${extra}`) + return `${randomInstance}/pic${url.pathname}${query}` + } catch { + return `${randomInstance}/pic${url.pathname}${search}` + } + } + if (url.pathname.split("/").includes("tweets")) + return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}` + if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}` + return `${randomInstance}${url.pathname}${search}#m` + } + case "priviblur": { + if (url.hostname == "www.tumblr.com") return `${randomInstance}${url.pathname}${url.search}` + if (url.hostname.startsWith("assets")) return `${randomInstance}/tblr/assets${url.pathname}${url.search}` + if (url.hostname.startsWith("static")) return `${randomInstance}/tblr/static${url.pathname}${url.search}` + + const reg = /^([0-9]+)\.media\.tumblr\.com/.exec(url.hostname) // *.media.tumblr.com + if (reg) return `${randomInstance}/tblr/media/${reg[1]}${url.pathname}${url.search}` + + const blogregex = /^(?:www\.)?([a-z\d-]+)\.tumblr\.com/.exec(url.hostname) // <blog>.tumblr.com + if (blogregex) { + const blog_name = blogregex[1] + // Under the <blog>.tumblr.com domain posts are under a /post path + if (url.pathname.startsWith("/post")) + return `${randomInstance}/${blog_name}${url.pathname.slice(5)}${url.search}` + else return `${randomInstance}/${blog_name}${url.pathname}${url.search}` + } + return `${randomInstance}${url.pathname}${url.search}` + } + case "freetar": + if (url.pathname.startsWith("/search.php")) { + url.searchParams.set("search_term", url.searchParams.get("value")) + url.searchParams.delete("value") + url.searchParams.delete("search_type") + return `${randomInstance}/search${url.search}` + } + if (url.pathname.startsWith("/artist")) return + return `${randomInstance}${url.pathname}${url.search}` + case "ratAintTieba": + url.searchParams.delete("ie") + return `${randomInstance}${url.pathname}${url.search}` + case "shoelace": { + const reg = /^\/(?:(?:(?:[^\/])?\/post)|t)\/([^\/])/.exec(url.pathname) + if (reg) return `${randomInstance}/t/${reg[1]}${url.search}` + return `${randomInstance}${url.pathname}${url.search}` + } + case "skunkyArt": { + if (url.pathname.startsWith("/search")) return `${randomInstance}${url.pathname}${url.search}&scope=all` + + const artReg = /^\/.*?\/art\/(.*)\/?/.exec(url.pathname) + if (artReg) return `${randomInstance}/post/art/${artReg[1]}${url.search}` + + const userReg = /^\/([^\/]+)$/.exec(url.pathname) + if (userReg) return `${randomInstance}/user/${userReg[1]}${url.search}` + + const galleryReg = /^\/.*?\/gallery(\/$|$)$/.exec(url.pathname) + if (galleryReg) return `${randomInstance}/user/${userReg[1]}?a=gallery` + + return `${randomInstance}${url.pathname}${url.search}` + } + case "ytify": { + if (url.pathname.startsWith("/watch")) + return `${randomInstance}/?s=${encodeURIComponent(url.searchParams.get("v"))}` + + const channelReg = /\/channel\/([^\/]+)/.exec(url.pathname) + if (channelReg) return `${randomInstance}/list?channel=${channelReg[1]}` + + if (url.pathname.startsWith("/playlist")) + return `${randomInstance}/list?playlists=${encodeURIComponent(url.searchParams.get("list"))}` + return `${randomInstance}${url.pathname}${url.search}` + } + case "koub": + if (url.pathname.startsWith("/view/") || url.pathname.startsWith("/stories/")) { + return `${randomInstance}${url.pathname}${url.search}` + } + const accountReg = /^\/([^\/]+)\/?$/.exec(url.pathname) + if (accountReg) return `${randomInstance}/account${url.pathname}${url.search}` + + case "piped": + case "pipedMaterial": + case "cloudtube": + case "lightTube": + case "viewtube": + url.searchParams.delete("si") + default: + return `${randomInstance}${url.pathname}${url.search}` + } } /** * @param {URL} url * @param {string} type - * @param {URL} initiator + * @param {URL} originUrl + * @param {URL} documentUrl + * @param {boolean} incognito * @param {boolean} forceRedirection + * @returns {string | undefined} */ -async function redirectAsync(url, type, initiator, forceRedirection) { - await init() - return redirect(url, type, initiator, forceRedirection) +function redirect(url, type, originUrl, documentUrl, incognito, forceRedirection) { + if (type != "main_frame" && type != "sub_frame" && type != "image") return + let randomInstance + let frontend + if (!forceRedirection && options.redirectOnlyInIncognito == true && !incognito) return + for (const service in config.services) { + if (!forceRedirection && !options[service].enabled) continue + if (!forceRedirection && options[service].redirectOnlyInIncognito == true && !incognito) continue + + frontend = options[service].frontend + + if ( + config.services[service].frontends[frontend].desktopApp && + type != "main_frame" && + options[service].redirectType != "main_frame" + ) + frontend = options[service].embedFrontend + + if (!regexArray(service, url, config, options, frontend)) { + frontend = null + continue + } + + if (type != "main_frame" && documentUrl && options[service].redirectType == "sub_frame") { + if (regexArray(service, documentUrl, config, options, frontend)) { + return + } + } + + if ( + config.services[service].embeddable && + type != options[service].redirectType && + options[service].redirectType != "both" + ) { + if (options[service].unsupportedUrls == "block") return "CANCEL" + return + } + + let instanceList = options[frontend] + if (instanceList === undefined) break // should not happen if settings are correct + + if (config.services[service].frontends[frontend].localhost && options[service].instance == "localhost") { + randomInstance = `http://${frontend}.localhost:8080` + } else if (instanceList.length === 0) { + return `https://no-instance.libredirect.invalid?frontend=${encodeURIComponent(frontend)}&url=${encodeURIComponent(url.href)}` + } else { + randomInstance = utils.getRandomInstance(instanceList) + } + + if (originUrl && instanceList.includes(originUrl.origin)) { + if (type == "main_frame") return "BYPASSTAB" + else return null + } + break + } + if (!frontend) return + + return rewrite(url, originUrl, frontend, randomInstance) } /** * @param {URL} url * @param {string} type - * @param {URL} initiator + * @param {URL} originUrl + * @param {URL} documentUrl + * @param {boolean} incognito * @param {boolean} forceRedirection - * @returns {string | undefined} + * @returns {Promise<string | undefined>} */ -function redirect(url, type, initiator, forceRedirection, incognito) { - if (type != "main_frame" && type != "sub_frame" && type != "image") return - let randomInstance - let frontend - if (!forceRedirection && options.redirectOnlyInIncognito == true && !incognito) return - for (const service in config.services) { - if (!forceRedirection && !options[service].enabled) continue - - frontend = options[service].frontend - - - if (config.services[service].frontends[frontend].desktopApp && type != "main_frame" && options[service].redirectType != "main_frame") - frontend = options[service].embedFrontend - - - if (!regexArray(service, url, config, frontend)) { - frontend = null - continue - } - - if ( - config.services[service].embeddable && - type != options[service].redirectType && options[service].redirectType != "both" - ) { - if (options[service].unsupportedUrls == 'block') return 'CANCEL' - return - } - - let instanceList = options[frontend] - if (instanceList === undefined) break - if (instanceList.length === 0) return null - - if ( - initiator - && - instanceList.includes(initiator.origin) - ) return "BYPASSTAB" - - randomInstance = utils.getRandomInstance(instanceList) - if (config.services[service].frontends[frontend].localhost && options[service].instance == "localhost") { - randomInstance = `http://${frontend}.localhost:8080` - } - break - } - if (!frontend) return - - switch (frontend) { - case "hyperpipe": { - return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/")) - } - case "searx": - case "searxng": - return `${randomInstance}/${url.search}` - case "whoogle": { - return `${randomInstance}/search${url.search}` - } - case "4get": { - const s = url.searchParams.get("q") - if (s !== null) { - return `${randomInstance}/web?s=${encodeURIComponent(s)}` - } - return randomInstance - } - case "librex": { - return `${randomInstance}/search.php${url.search}` - } - case "send": { - return randomInstance - } - case "nitter": { - let search = new URLSearchParams(url.search) - - search.delete("ref_src") - search.delete("ref_url") - search.delete("s") // type of device that shared the link - search.delete("t") // some sort of tracking ID - - search = search.toString() - if (search !== "") search = `?${search}` - - if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") { - try { - const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/) - const query = encodeURIComponent(`${id}.${format}?${extra}`) - return `${randomInstance}/pic${url.pathname}${query}` - } catch { - return `${randomInstance}/pic${url.pathname}${search}` - } - } - if (url.pathname.split("/").includes("tweets")) return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}` - if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}` - return `${randomInstance}${url.pathname}${search}#m` - } - case "yattee": { - return url.href.replace(/^https?:\/{2}/, "yattee://") - } - case "freetube": { - return 'freetube://' + url.href - } - case "freetubePwa": { - return 'freetube://' + url.href - } - - case "poketube": { - if (url.pathname.startsWith('/channel')) { - const reg = /\/channel\/(.*)\/?$/.exec(url.pathname) - if (reg) { - const id = reg[1] - return `${randomInstance}/channel?id=${id}${url.search}` - } - } - if (/\/@[a-z]+\//.exec(url.pathname)) return randomInstance - return `${randomInstance}${url.pathname}${url.search}` - } - case "libMedium": - case "scribe": { - const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/) - if (regex && regex.length > 1) { - const subdomain = regex[1] - if (subdomain != "link" || !subdomain.startsWith("cdn-images")) { - return `${randomInstance}/@${subdomain}${url.pathname}${url.search}` - } - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "simplyTranslate": { - return `${randomInstance}/${url.search}` - } - case "mozhi": { - return `${randomInstance}` - } - case "libreTranslate": { - let search = url.search - .replace("sl", "source") - .replace("tl", "target") - .replace("text", "q") - return `${randomInstance}/${search}` - } - case "osm": { - 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] - const reg = url.pathname.match(/@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/) - if (reg) { - [, lon, lat, zoom] = reg - } else if (url.searchParams.has("center")) { - // Set map centre if present - [lat, lon] = url.searchParams.get("center").split(",") - zoom = url.searchParams.get("zoom") ?? "17" - } - return { zoom, lon, lat } - } - if (initiator && initiator.host === "earth.google.com") return randomInstance - 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 http = new XMLHttpRequest() - http.open("GET", `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`, false) - http.send() - if (http.status == 200) { - const json = JSON.parse(http.responseText)[0] - if (json) { - return { - coordinate: `${json.lat},${json.lon}`, - boundingbox: `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}` - } - } - return {} - } - } - - let mapCentre = "#" - let prefs = {} - - const mapCentreData = convertMapCentre() - if (mapCentreData.zoom && mapCentreData.lon && mapCentreData.lat) mapCentre = `#map=${mapCentreData.zoom}/${mapCentreData.lon}/${mapCentreData.lat}` - 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 - 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) { - // Unable to find map marker in URL. - console.error(error) - } - - let { coordinate, boundingbox } = addressToLatLng(query) - prefs.bbox = boundingbox - prefs.marker = coordinate - prefs.layers = "mapnik" - let prefsEncoded = new URLSearchParams(prefs).toString() - return `${randomInstance}/export/embed.html?${prefsEncoded}` - } else if (url.pathname.includes("/dir")) { - // Handle Google Maps Directions - if (url.searchParams.has("travelmode")) { - prefs.engine = travelModes[url.searchParams.get("travelmode")] - } - const regex1 = /\/dir\/([^@/]+)\/([^@/]+)\/@-?\d[0-9.]*,-?\d[0-9.]*,\d{1,2}[.z]/.exec(url.pathname) - const regex2 = /\/dir\/([^@/]+)\//.exec(url.pathname) - if (regex1) { - // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/M%C3%A9dip%C3%B4le+Lyon-Villeurbanne/@45.760254,4.8486298,13z?travelmode=bicycling - const origin = addressToLatLng(decodeURIComponent(regex1[1])).coordinate ?? '' - const destination = addressToLatLng(decodeURIComponent(regex1[2])).coordinate ?? '' - prefs.route = `${origin};${destination}` - } else if (regex2) { - // https://www.google.com/maps/dir/92+Rue+Moncey,+69003+Lyon,+France/@45.760254,4.8486298,13z?travelmode=bicycling - const origin = addressToLatLng(decodeURIComponent(regex2[1])).coordinate ?? '' - prefs.route = `${origin};` - } else { - // https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling - const origin = addressToLatLng(url.searchParams.get("origin")).coordinate ?? '' - const destination = addressToLatLng(url.searchParams.get("destination")).coordinate ?? '' - prefs.route = `${origin};${destination}` - } - const 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 - 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 - 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 - const [mlat, mlon] = url.searchParams.get("viewpoint").split(",") - return `${randomInstance}/search?query=${mlat}%2C${mlon}` - } else { - // Use query as search if present. - 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() - return `${randomInstance}/${mapCentre}&${prefsEncoded}` - } - case "breezeWiki": { - let wiki, urlpath = "" - if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) { - wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/) - if (wiki == "www" || !wiki) wiki = "" - else wiki = `/${wiki}` - urlpath = url.pathname - } else { - wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/) - if (!wiki) wiki = "" - else { - wiki = "/" + wiki + "/wiki/" - urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/) - } - } - if (url.href.search(/Special:Search\?query/) > -1) { - return `${randomInstance}${wiki}${urlpath}${url.search}`.replace(/Special:Search\?query/, "search?q").replace(/\/wiki/, "") - } - return `${randomInstance}${wiki}${urlpath}${url.search}` - } - case "rimgo": { - if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) { - return `${randomInstance}/stack${url.pathname}${url.search}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "libreddit": { - const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/) - if (!subdomain) return `${randomInstance}${url.pathname}${url.search}` - switch (subdomain[0]) { - case "preview": - return `${randomInstance}/preview/pre${url.pathname}${url.search}` - case "external-preview": - return `${randomInstance}/preview/external-pre${url.pathname}${url.search}` - case "i": - return `${randomInstance}/img${url.pathname}` - } - return randomInstance - } - case "teddit": { - if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) { - if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}` - else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "neuters": { - const p = url.pathname - if (p.startsWith('/article/') || p.startsWith('/pf/') || p.startsWith('/arc/') || p.startsWith('/resizer/')) { - return randomInstance - } - return `${randomInstance}${p}` - } - case "dumb": { - if (url.pathname.endsWith('-lyrics')) { - return `${randomInstance}${url.pathname}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "intellectual": { - if (url.pathname.endsWith('-lyrics')) { - return `${randomInstance}/lyrics?path=${encodeURIComponent(url.pathname)}` - } - if (url.pathname.startsWith('/artists/')) { - return `${randomInstance}/artist?path=${url.pathname}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "ruralDictionary": { - if (!url.pathname.includes('/define.php') && !url.pathname.includes('/random.php') && url.pathname != '/') return randomInstance - return `${randomInstance}${url.pathname}${url.search}` - } - case "anonymousOverflow": { - if (url.hostname == "stackoverflow.com") { - const threadID = /^\/a\/(\d+)\/?/.exec(url.pathname) - if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}` - return `${randomInstance}${url.pathname}${url.search}` - } - const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.)?stackexchange\.com\//) - if (regex && regex.length > 1) { - const subdomain = regex[1] - return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}` - } - } - case "biblioReads": { - return `${randomInstance}${url.pathname}${url.search}` - } - case "wikiless": { - let hostSplit = url.host.split(".") - // wikiless doesn't have mobile view support yet - if (hostSplit[0] != "wikipedia" && hostSplit[0] != "www") { - if (hostSplit[0] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile") - else url.searchParams.append("lang", hostSplit[0]) - if (hostSplit[1] == "m") url.searchParams.append("mobileaction", "toggle_view_mobile") - } - return `${randomInstance}${url.pathname}${url.search}${url.hash}` - } - case "proxiTok": { - if (url.pathname.startsWith('/email')) return randomInstance - return `${randomInstance}${url.pathname}${url.search}` - } - case "waybackClassic": { - const regex = /^\/\web\/(?:[0-9]+)?\*\/(.*)/.exec(url.pathname) - if (regex) { - const link = regex[1] - return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}` - } - const regex2 = /(^\/\web\/([0-9]+)\/.*)/.exec(url.pathname) - if (regex2) { - let link = regex2[1] - link = link.replace(regex2[2], regex2[2] + 'if_') - return `https://web.archive.org${link}` - } - return - } - case "gothub": { - if (url.hostname == "gist.github.com") return `${randomInstance}/gist${url.pathname}${url.search}` - return `${randomInstance}${url.pathname}${url.search}` - } - case "mikuInvidious": { - console.log("Hello?") - if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == 'b23.tv') { - console.log('wewe') - return `${randomInstance}${url.pathname}${url.search}` - } - if (url.hostname == "space.bilibili.com") { - return `${randomInstance}/space${url.pathname}${url.search}` - } - } - case "tent": { - if (url.hostname == 'bandcamp.com' && url.pathname == '/search') { - const query = url.searchParams.get('q') - return `${randomInstance}/search.php?query=${encodeURIComponent(query)}` - } - if (url.hostname.endsWith('bandcamp.com')) { - const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname) - const artist = regex[1] - if (url.pathname == '/') { - return `${randomInstance}/artist.php?name=${artist}` - } else { - const regex = /^\/(.*)\/(.*)/.exec(url.pathname) - if (regex) { - const type = regex[1] - const name = regex[2] - return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}` - } - } - } - if (url.hostname == 'f4.bcbits.com') { - const regex = /\/img\/(.*)/.exec(url.pathname) - const image = regex[1] - return `${randomInstance}/image.php?file=${image}` - } - if (url.hostname == 't4.bcbits.com') { - const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname) - if (regex) { - const directory = regex[1] - const format = regex[2] - const file = regex[3] - const token = url.searchParams.get('token') - return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}` - } - } - } - case "binternet": { - if (url.hostname == "i.pinimg.com") return `${randomInstance}/image_proxy.php?url=${url.href}` - return randomInstance - } - case "laboratory": { - let path = url.pathname - if (path == "/") path = "" - return `${randomInstance}/${url.hostname}${path}${url.search}` - } - case "quetre": { - const regex = /([a-z]+)\.quora\.com/.exec(url.hostname) - console.log(regex) - if (regex) { - const lang = regex[1] - url.searchParams.append("lang", lang) - return `${randomInstance}${url.pathname}${url.search}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "pixivFe": { - const regex = /\/[a-z]{1,3}\/(.*)/.exec(url.pathname) - if (regex) { - const path = regex[1] - return `${randomInstance}/${path}${url.search}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "invidious": { - if (url.hostname == "youtu.be" || url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live")) { - const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1) - return `${randomInstance}/watch?v=${watch}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "invidiousMusic": { - if (url.hostname == "youtu.be" || url.hostname.endsWith("youtube.com") && url.pathname.startsWith("/live")) { - const watch = url.pathname.substring(url.pathname.lastIndexOf('/') + 1) - return `${randomInstance}/watch?v=${watch}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "libremdb": { - if (url.pathname.startsWith("/Name")) { - for (const [key, value] of url.searchParams.entries()) { - return `${randomInstance}/title/${encodeURIComponent(key)}` - } - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "tuboYoutube": { - if (url.pathname.startsWith("/channel")) { - return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}` - } - if (url.pathname.startsWith("/watch")) { - return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}` - } - return `${randomInstance}` - } - case "tuboSoundcloud": { - if (url.pathname == '/') return `${randomInstance}?kiosk?serviceId=1` - if (url.pathname.match(/^\/[^\/]+(\/$|$)/)) { - return `${randomInstance}/channel?url=${encodeURIComponent(url.href)}` - } - if (url.pathname.match(/^\/[^\/]+\/[^\/]+/)) { - return `${randomInstance}/stream?url=${encodeURIComponent(url.href)}` - } - return `${randomInstance}` - } - case "twineo": - case "safetwitch": { - if (url.hostname.startsWith("clips.")) { - return `${randomInstance}/clip${url.pathname}${url.search}` - } - return `${randomInstance}${url.pathname}${url.search}` - } - case "tekstoLibre": { - return `${randomInstance}/?${url.pathname.slice(1)}`; - } - case "skyview": { - if (url.pathname == '/') return randomInstance - return `${randomInstance}?url=${encodeURIComponent(url.href)}` - } - default: { - return `${randomInstance}${url.pathname}${url.search}` - } - } +async function redirectAsync(url, type, originUrl, documentUrl, incognito, forceRedirection) { + await init() + return redirect(url, type, originUrl, documentUrl, incognito, forceRedirection) } /** * @param {URL} url - * @param {*} returnFrontend */ -function computeService(url, returnFrontend) { - return new Promise(async resolve => { - const config = await utils.getConfig() - const options = await utils.getOptions() - for (const service in config.services) { - if (regexArray(service, url, config)) { - resolve(service) - return - } else { - for (const frontend in config.services[service].frontends) { - if (all(service, frontend, options, config).includes(utils.protocolHost(url))) { - if (returnFrontend) - resolve([service, frontend, utils.protocolHost(url)]) - else - resolve(service) - return - } - } - } - } - resolve() - }) +function computeService(url) { + return new Promise(async resolve => { + const config = await utils.getConfig() + const options = await utils.getOptions() + for (const service in config.services) { + if (regexArray(service, url, config, options)) { + resolve(service) + return + } else { + for (const frontend in config.services[service].frontends) { + if (all(service, frontend, options, config).findIndex(instance => url.href.startsWith(instance)) >= 0) { + return resolve(service) + } + } + } + } + resolve() + }) +} +export function computeFrontend(url) { + for (const service in config.services) { + for (const frontend in config.services[service].frontends) { + if (all(service, frontend, options, config).findIndex(instance => url.href.startsWith(instance)) >= 0) { + return { service, frontend } + } + } + } } /** @@ -610,279 +681,299 @@ function computeService(url, returnFrontend) { * @param {string} customService */ function switchInstance(url, customService) { - return new Promise(async resolve => { - let options = await utils.getOptions() - let config = await utils.getConfig() - - const protocolHost = utils.protocolHost(url) - if (customService) { - const instancesList = options[options[customService].frontend] - if (instancesList !== undefined) { - resolve(`${utils.getNextInstance(url.origin, instancesList)}${url.pathname}${url.search}`) - } - } else { - for (const service in config.services) { - let instancesList = options[options[service].frontend] - if (instancesList === undefined) continue - if (!instancesList.includes(protocolHost)) continue - - instancesList.splice(instancesList.indexOf(protocolHost), 1) - if (instancesList.length === 0) { - resolve() - return - } - resolve(`${utils.getNextInstance(url.origin, instancesList)}${url.pathname}${url.search}`) - return - } - } - resolve() - }) + return new Promise(async resolve => { + let options = await utils.getOptions() + let config = await utils.getConfig() + + const protocolHost = utils.protocolHost(url) + if (customService) { + const instancesList = options[options[customService].frontend] + if (instancesList !== undefined) { + const newInstance = utils.getNextInstance(url.origin, instancesList) + if (newInstance) { + return resolve(`${newInstance}${url.pathname}${url.search}`) + } + } + } else { + for (const service in config.services) { + let instancesList = options[options[service].frontend] + if (instancesList === undefined) continue + const index = instancesList.findIndex(instance => url.href.startsWith(instance)) + if (index < 0) continue + instancesList.splice(index, 1) + if (instancesList.length === 0) return resolve() + const newInstance = utils.getNextInstance(url.origin, instancesList) + if (newInstance) { + return resolve(`${newInstance}${url.pathname}${url.search}`) + } + } + } + resolve() + }) } /** * @param {URL} url */ async function reverse(url) { - let options = await utils.getOptions() - let config = await utils.getConfig() - let protocolHost = utils.protocolHost(url) - for (const service in config.services) { - let frontend = options[service].frontend - if (options[frontend] == undefined) continue - if (!options[frontend].includes(protocolHost) && protocolHost != `http://${frontend}.localhost:8080`) continue - switch (service) { - case "youtube": - case "imdb": - case "imgur": - case "tiktok": - case "twitter": - case "reddit": - case "imdb": - case "snopes": - case "urbanDictionary": - case "quora": - case "medium": - return `${config.services[service].url}${url.pathname}${url.search}` - case "fandom": - let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/(.*)/) - if (regex) return `https://${regex[1]}.fandom.com/wiki/${regex[2]}` - return - case "wikipedia": { - const lang = url.searchParams.get("lang") - if (lang != null) { - return `https://${lang}.wikipedia.org${url.pathname}${url.search}${url.hash}` - } - return `https://wikipedia.org${url.pathname}${url.search}${url.hash}` - } - case "stackOverflow": { - if (url.pathname.startsWith("/questions/")) { - return `https://stackoverflow.com${url.pathname}${url.search}` - } - if (url.pathname.startsWith("/exchange/")) { - const regex = /\/exchange\/(.*?)(\/.*)/.exec(url.pathname) - if (regex) return `https://${regex[1]}.stackexchange.com${regex[2]}` - } - return - } - case "tekstowo": { - return `${config.services[service].url}/${url.search.slice(1)}` - } - default: - return - } - } - return + let options = await utils.getOptions() + let config = await utils.getConfig() + for (const service in config.services) { + let frontend = options[service].frontend + if (options[frontend] == undefined) continue + if ( + options[frontend].findIndex(instance => url.href.startsWith(instance)) < 0 && + !url.href.startsWith(`http://${frontend}.localhost:8080`) + ) + continue + switch (service) { + case "youtube": + case "imdb": + case "imgur": + case "tiktok": + case "reddit": + case "imdb": + case "snopes": + case "urbanDictionary": + case "quora": + case "twitter": + case "medium": + return `${config.services[service].url}${url.pathname}${url.search}` + case "fandom": { + let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/(.*)/) + if (regex) return `https://${regex[1]}.fandom.com/wiki/${regex[2]}` + return + } + case "wikipedia": { + const lang = url.searchParams.get("lang") + if (lang != null) { + return `https://${lang}.wikipedia.org${url.pathname}${url.search}${url.hash}` + } + return `https://wikipedia.org${url.pathname}${url.search}${url.hash}` + } + case "stackOverflow": { + if (url.pathname.startsWith("/questions/")) { + return `https://stackoverflow.com${url.pathname}${url.search}` + } + if (url.pathname.startsWith("/exchange/")) { + const regex = /\/exchange\/(.*?)(\/.*)/.exec(url.pathname) + if (regex) return `https://${regex[1]}.stackexchange.com${regex[2]}` + } + return + } + case "tekstowo": + return `${config.services[service].url}/${url.search.slice(1)}` + case "goodreads": + return `https://goodreads.com${url.pathname}${url.search}` + default: + return + } + } + return } const defaultInstances = { - 'invidious': [ 'https://invidious.vhack.eu' ], // still a little bit flaky, thus not active - 'piped': ['https://pipedapi-libre.kavin.rocks'], - 'pipedMaterial': ['https://piped-material.xn--17b.net'], - 'cloudtube': ['https://tube.cadence.moe'], - 'poketube': ['https://poketube.fun'], - 'proxiTok': ['https://proxitok.pabloferreiro.es'], - 'nitter': ['https://nitter.net'], - 'libreddit': [ 'https://libreddit.vhack.eu' ], - 'teddit': ['https://teddit.net'], - 'scribe': ['https://scribe.rip'], - 'libMedium': ['https://md.vern.cc'], - 'quetre': ['https://quetre.iket.me'], - 'libremdb': ['https://libremdb.iket.me'], - 'simplyTranslate': ['https://simplytranslate.org'], - 'mozhi': ['https://mozhi.aryak.me'], - 'searxng': ['https://search.bus-hit.me'], - '4get': ['https://4get.ca'], - 'rimgo': ['https://rimgo.vern.cc'], - 'hyperpipe': ['https://hyperpipe.surge.sh'], - 'facil': [' https://facilmap.org '], - 'osm': ['https://www.openstreetmap.org'], - 'breezeWiki': ['https://breezewiki.com'], - 'neuters': ['https://neuters.de'], - 'dumb': ['https://dm.vern.cc'], - "intellectual": ['https://intellectual.insprill.net'], - 'ruralDictionary': ['https://rd.vern.cc'], - 'anonymousOverflow': ['https://code.whatever.social'], - 'biblioReads': ['https://biblioreads.ml'], - 'wikiless': ['https://wikiless.org'], - 'suds': ['https://sd.vern.cc'], - 'waybackClassic': ['https://wayback-classic.net'], - 'gothub': ['https://gh.odyssey346.dev'], - 'mikuInvidious': ['https://mikuinv.resrv.org'], - "tent": ['https://tent.sny.sh'], - "wolfreeAlpha": ['https://gqq.gitlab.io', 'https://uqq.gitlab.io'], - "laboratory": ['https://lab.vern.cc'], - 'binternet': ['https://binternet.ahwx.org'], - 'pixivFe': ['https://pixivfe.exozy.me'], - 'indestructables': ['https://indestructables.private.coffee'], - 'destructables': ['https://ds.vern.cc'], - 'safetwitch': ['https://safetwitch.drgns.space'], - 'twineo': ['https://twineo.exozy.me'], - 'proxigram': ['https://proxigram.privacyfrontends.repl.co'], - 'tuboYoutube': ['https://tubo.migalmoreno.com'], - 'tuboSoundcloud': ['https://tubo.migalmoreno.com'], - 'tekstoLibre': ['https://davilarek.github.io/TekstoLibre'], - 'skyview': ['https://skyview.social'], + invidious: ["https://invidious.vhack.eu"], + materialious: ["https://app.materialio.us"], + viewtube: ["https://viewtube.io"], + piped: ["https://pipedapi-libre.kavin.rocks"], + pipedMaterial: ["https://piped-material.xn--17b.net"], + cloudtube: ["https://tube.cadence.moe"], + lightTube: ["https://tube.kuylar.dev"], + poketube: ["https://poketube.fun"], + proxiTok: ["https://proxitok.pabloferreiro.es"], + redlib: ["https://libreddit.vhack.eu"], + eddrit: ["https://eddrit.com"], + scribe: ["https://scribe.rip"], + libMedium: ["https://md.vern.cc"], + quetre: ["https://quetre.iket.me"], + libremdb: ["https://libremdb.iket.me"], + simplyTranslate: ["https://simplytranslate.org"], + translite: ["https://tl.bloat.cat"], + mozhi: ["https://mozhi.aryak.me"], + searxng: ["https://search.bus-hit.me"], + "4get": ["https://4get.ca"], + rimgo: ["https://rimgo.vern.cc"], + hyperpipe: ["https://hyperpipe.surge.sh"], + osm: ["https://www.openstreetmap.org"], + breezeWiki: ["https://breezewiki.com"], + neuters: ["https://neuters.de"], + dumb: ["https://dm.vern.cc"], + intellectual: ["https://intellectual.insprill.net"], + ruralDictionary: ["https://rd.vern.cc"], + anonymousOverflow: ["https://code.whatever.social"], + suds: ["https://sd.vern.cc"], + unfunny: ["https://uf.vern.cc"], + soprano: ["https://sp.vern.cc"], + meme: ["https://mm.vern.cc"], + waybackClassic: ["https://wayback-classic.net"], + tent: ["https://tent.sny.sh"], + wolfreeAlpha: ["https://gqq.gitlab.io", "https://uqq.gitlab.io"], + laboratory: ["https://lab.vern.cc"], + binternet: ["https://bn.bloat.cat"], + pixivFe: ["https://pixivfe.exozy.me"], + liteXiv: ["https://litexiv.exozy.me"], + indestructables: ["https://indestructables.private.coffee"], + destructables: ["https://ds.vern.cc"], + safetwitch: ["https://safetwitch.drgns.space"], + twineo: ["https://twineo.exozy.me"], + proxigram: ["https://ig.opnxng.com"], + tuboYoutube: ["https://tubo.migalmoreno.com"], + tuboSoundcloud: ["https://tubo.migalmoreno.com"], + tekstoLibre: ["https://davilarek.github.io/TekstoLibre"], + skyview: ["https://skyview.social"], + priviblur: ["https://pb.bloat.cat"], + nitter: ["https://nitter.privacydev.net"], + pasted: ["https://pasted.drakeerv.com"], + freetar: ["https://freetar.de"], + ratAintTieba: ["https://rat.fis.land"], + shoelace: ["https://shoelace.mint.lgbt"], + skunkyArt: ["https://skunky.bloat.cat"], + ytify: ["https://ytify.netlify.app"], + nerdsForNerds: ["https://nn.vern.cc"], + koub: ["https://koub.clovius.club"], } -function initDefaults() { - return new Promise(resolve => { - browser.storage.local.clear(async () => { - let config = await utils.getConfig() - let options = {} - for (const service in config.services) { - options[service] = {} - for (const defaultOption in config.services[service].options) { - options[service][defaultOption] = config.services[service].options[defaultOption] - } - for (const frontend in config.services[service].frontends) { - if (config.services[service].frontends[frontend].instanceList) { - options[frontend] = [] - } - } - } - options['exceptions'] = { - url: [], - regex: [], - } - options.theme = "detect" - options.popupServices = ["youtube", "twitter", "tiktok", "imgur", "reddit", "quora", "translate", "maps"] - options.fetchInstances = 'github' - options.redirectOnlyInIncognito = false - - options = { ...options, ...defaultInstances } - - browser.storage.local.set({ options }, - () => resolve() - ) - }) - }) +async function getDefaults() { + let config = await utils.getConfig() + let options = {} + for (const service in config.services) { + options[service] = {} + for (const defaultOption in config.services[service].options) { + options[service][defaultOption] = config.services[service].options[defaultOption] + } + for (const frontend in config.services[service].frontends) { + if (config.services[service].frontends[frontend].instanceList) { + options[frontend] = [] + } + } + } + options.exceptions = { + url: [], + regex: [], + } + options.theme = "detect" + options.popupServices = ["youtube", "tiktok", "imgur", "reddit", "quora", "translate", "maps"] + options.fetchInstances = "github" + options.redirectOnlyInIncognito = false + options = { ...options, ...defaultInstances } + return options } -function upgradeOptions() { - return new Promise(async resolve => { - let options = await utils.getOptions() - - browser.storage.local.clear(() => { - browser.storage.local.set({ options }, () => { - resolve() - }) - }) - }) +function initDefaults() { + return new Promise(resolve => { + browser.storage.local.clear(async () => { + options = await getDefaults() + browser.storage.local.set({ options }, () => resolve()) + }) + }) } -function processUpdate() { - return new Promise(async resolve => { - let config = await utils.getConfig() - let options = await utils.getOptions() - for (const service in config.services) { - if (!options[service]) options[service] = {} - - if (!(options[service].frontend in config.services[service].frontends)) { - options[service] = config.services[service].options - delete options[options[service].frontend] - } - - 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 (options[frontend] === undefined && config.services[service].frontends[frontend].instanceList) { - options[frontend] = defaultInstances[frontend] - } - else if (frontend in options && !(frontend in config.services[service].frontends)) { - delete options[frontend] - } - } - - for (const frontend of options.popupServices) { - if (!Object.keys(config.services).includes(frontend)) { - const i = options.popupServices.indexOf(frontend); - if (i > -1) options.popupServices.splice(i, 1); - } - } - } - browser.storage.local.set({ options }, () => { - resolve() - }) - }) +function processUpdate(_options) { + return new Promise(async resolve => { + const config = await utils.getConfig() + let options = _options ?? (await utils.getOptions()) + + const defaults = await getDefaults() + + // Remove any unknown option or subOption + for (const optionName in options) { + if (!(optionName in defaults)) delete options[optionName] + else if (typeof optionName === "object" && optionName !== null) { + for (const subOptionName in options[optionName]) { + if (!(subOptionName in defaults[optionName])) delete options[optionName][subOptionName] + } + } + } + + // Remove any unknwon popupService + options.popupServices = options.popupServices.filter(service => service in config.services) + + // Add missing options + for (const [defaultName, defaultValue] of Object.entries(defaults)) { + if (!(defaultName in options)) { + options[defaultName] = defaultValue + } + } + + for (const [serviceName, serviceValue] of Object.entries(config.services)) { + // Reset service options if selected frontend is deprecated + if (!(options[serviceName].frontend in serviceValue.frontends)) { + options[serviceName] = serviceValue.options + } + + // Add a default service option if it's not present + for (const optionName in serviceValue.options) { + if (!(optionName in options[serviceName])) { + options[serviceName][optionName] = serviceValue.options[optionName] + } + } + } + + browser.storage.local.clear(() => { + browser.storage.local.set({ options }, () => { + resolve(options) + }) + }) + }) } /** * @param {URL} url - * @param {boolean} test */ -async function copyRaw(url, test) { - const newUrl = await reverse(url) - if (newUrl) { - if (!test) { - if (!isChrome) { - navigator.clipboard.writeText(newUrl) - } else { - var copyFrom = document.createElement("textarea"); - copyFrom.textContent = newUrl; - document.body.appendChild(copyFrom); - copyFrom.select() - document.execCommand('copy') - copyFrom.blur(); - document.body.removeChild(copyFrom); - } - } - return newUrl - } +async function copyRaw(url) { + const newUrl = await reverse(url) + if (newUrl) { + if (!isChrome) { + navigator.clipboard.writeText(newUrl) + } else { + var copyFrom = document.createElement("textarea") + copyFrom.textContent = newUrl + document.body.appendChild(copyFrom) + copyFrom.select() + document.execCommand("copy") + copyFrom.blur() + document.body.removeChild(copyFrom) + } + } } /** * @param {URL} url */ function isException(url) { - if (!options.exceptions) return false - let exceptions = options.exceptions - if (exceptions && url) { - if (exceptions.url) { - for (let item of exceptions.url) { - item = new URL(item) - item = item.href - item = item.replace(/^http:\/\//, 'https://') - if (item == url.href) return true - } - } - if (exceptions.regex) for (const item of exceptions.regex) if (new RegExp(item).test(url.href)) return true - } - return false + if (!options.exceptions) return false + let exceptions = options.exceptions + if (exceptions && url) { + if (exceptions.url) { + for (let item of exceptions.url) { + item = new URL(item) + item = item.href.replace(/^http:\/\//, "https://") + if (item == url.href) { + return true + } + } + } + if (exceptions.regex) { + for (const item of exceptions.regex) { + if (new RegExp(item).test(url.href)) { + return true + } + } + } + } + return false } export default { - redirect, - redirectAsync, - computeService, - reverse, - initDefaults, - upgradeOptions, - processUpdate, - copyRaw, - switchInstance, - isException + redirect, + redirectAsync, + computeService, + reverse, + initDefaults, + processUpdate, + copyRaw, + switchInstance, + isException, + computeFrontend, } diff --git a/src/assets/javascripts/utils.js b/src/assets/javascripts/utils.js index fe08e576..e5b8ba46 100644 --- a/src/assets/javascripts/utils.js +++ b/src/assets/javascripts/utils.js @@ -1,11 +1,11 @@ window.browser = window.browser || window.chrome /** - * @param {Array.<T>} instances + * @param {Array.<T>} instances * @returns {T} */ function getRandomInstance(instances) { - return instances[~~(instances.length * Math.random())] + return instances[~~(instances.length * Math.random())] } /** @@ -14,32 +14,24 @@ function getRandomInstance(instances) { * @returns {T} */ function getNextInstance(currentInstanceUrl, instances) { - const currentInstanceIndex = instances.indexOf(currentInstanceUrl); - - if (currentInstanceIndex === -1){ - return getRandomInstance(instances); - } - - const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length; - - return instances[nextInstanceIndex]; -} - -/** - * @param {string} str - */ -function camelCase(str) { - return str.charAt(0).toUpperCase() + str.slice(1) + const currentInstanceIndex = instances.indexOf(currentInstanceUrl) + if (currentInstanceIndex === -1) return getRandomInstance(instances) + const nextInstanceIndex = (currentInstanceIndex + 1) % instances.length + return instances[nextInstanceIndex] } /** * @param {URL} url */ function protocolHost(url) { - if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}` - if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io")) // workaround - return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}` - return `${url.protocol}//${url.host}` + url.pathname = url.pathname.replace(/\/$/, "") + if (url.username && url.password) return `${url.protocol}//${url.username}:${url.password}@${url.host}${url.pathname}` + + // workaround + if (url.pathname == "/TekstoLibre/" && url.host.endsWith("github.io")) + return `${url.protocol}//${url.host}${url.pathname.slice(0, -1)}` + + return `${url.protocol}//${url.host}${url.pathname}` } /** @@ -64,14 +56,14 @@ function protocolHost(url) { * @returns {Promise<Config>} */ function getConfig() { - return new Promise(resolve => { - fetch("/config.json") - .then(response => response.text()) - .then(json => { - resolve(JSON.parse(json)) - return - }) - }) + return new Promise(resolve => { + fetch("/config.json") + .then(response => response.text()) + .then(json => { + resolve(JSON.parse(json)) + return + }) + }) } /** @@ -83,144 +75,205 @@ function getConfig() { * @returns {Promise<Object.<string, Option | string[]>>} */ function getOptions() { - return new Promise(resolve => - browser.storage.local.get("options", r => { - resolve(r.options) - }) - ) + return new Promise(resolve => browser.storage.local.get("options", r => resolve(r.options))) } function getPingCache() { - return new Promise(resolve => - browser.storage.local.get("pingCache", r => { - resolve(r.pingCache ?? {}) - }) - ) + return new Promise(resolve => browser.storage.local.get("pingCache", r => resolve(r.pingCache ?? {}))) } function getBlacklist(options) { - return new Promise(resolve => { - let url - if (options.fetchInstances == 'github') { - url = 'https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json' - } - else if (options.fetchInstances == 'codeberg') { - url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json' - } - else { - resolve('disabled') - return - } - const http = new XMLHttpRequest() - http.open("GET", url, true) - http.onreadystatechange = () => { - if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) { - resolve(JSON.parse(http.responseText)) - return - } - } - http.onerror = () => { - resolve() - return - } - http.ontimeout = () => { - resolve() - return - } - http.send(null) - }) + return new Promise(resolve => { + let url + if (options.fetchInstances == "github") + url = "https://raw.githubusercontent.com/libredirect/instances/main/blacklist.json" + else if (options.fetchInstances == "codeberg") + url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/blacklist.json" + else return resolve("disabled") + const http = new XMLHttpRequest() + http.open("GET", url, true) + http.onreadystatechange = () => { + if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) resolve(JSON.parse(http.responseText)) + } + http.onerror = () => resolve() + http.ontimeout = () => resolve() + http.send(null) + }) } function getList(options) { - return new Promise(resolve => { - let url - if (options.fetchInstances == 'github') { - url = 'https://raw.githubusercontent.com/libredirect/instances/main/data.json' - } - else if (options.fetchInstances == 'codeberg') { - url = 'https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json' - } - else { - resolve('disabled') - return - } - const http = new XMLHttpRequest() - http.open("GET", url, true) - http.onreadystatechange = () => { - if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) { - resolve(JSON.parse(http.responseText)) - return - } - } - http.onerror = () => { - resolve() - return - } - http.ontimeout = () => { - resolve() - return - } - http.send(null) - }) + return new Promise(resolve => { + let url + if (options.fetchInstances == "github") + url = "https://raw.githubusercontent.com/libredirect/instances/main/data.json" + else if (options.fetchInstances == "codeberg") + url = "https://codeberg.org/LibRedirect/instances/raw/branch/main/data.json" + else return resolve("disabled") + const http = new XMLHttpRequest() + http.open("GET", url, true) + http.onreadystatechange = () => { + if (http.status === 200 && http.readyState == XMLHttpRequest.DONE) return resolve(JSON.parse(http.responseText)) + } + http.onerror = () => resolve() + http.ontimeout = () => resolve() + http.send(null) + }) } /** * @param {string} href */ function pingOnce(href) { - return new Promise(async resolve => { - let started - let http = new XMLHttpRequest() - http.timeout = 5000 - http.ontimeout = () => resolve(5000) - http.onerror = () => resolve() - http.onreadystatechange = () => { - if (http.readyState == 2) { - if (http.status == 200) { - let ended = new Date().getTime() - http.abort() - resolve(ended - started) - } else { - resolve(5000 + http.status) - } - } - } - http.open("GET", `${href}?_=${new Date().getTime()}`, true) - started = new Date().getTime() - http.send(null) - }) + return new Promise(async resolve => { + let started + let http = new XMLHttpRequest() + http.timeout = 5000 + http.ontimeout = () => resolve(5000) + http.onerror = () => resolve() + http.onreadystatechange = () => { + if (http.readyState == 2) { + if (http.status == 200) { + let ended = new Date().getTime() + http.abort() + resolve(ended - started) + } else { + resolve(5000 + http.status) + } + } + } + http.open("GET", `${href}?_=${new Date().getTime()}`, true) + started = new Date().getTime() + http.send(null) + }) } /** * @param {string} href */ function ping(href) { - return new Promise(async resolve => { - let average = 0 - let time - for (let i = 0; i < 3; i++) { - time = await pingOnce(href) - if (i == 0) continue - if (time >= 5000) { - resolve(time) - return - } - average += time - } - average = parseInt(average / 3) - resolve(average) - }) + return new Promise(async resolve => { + let average = 0 + let time + for (let i = 0; i < 3; i++) { + time = await pingOnce(href) + if (i == 0) continue + if (time >= 5000) { + resolve(time) + return + } + average += time + } + average = parseInt(average / 3) + resolve(average) + }) +} + +function addressToLatLng(address) { + const http = new XMLHttpRequest() + http.open( + "GET", + `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`, + false + ) + http.send() + if (http.status == 200) { + const json = JSON.parse(http.responseText)[0] + if (json) { + return { + coordinate: `${json.lat},${json.lon}`, + boundingbox: `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`, + } + } + return {} + } +} + +function getQuery(url) { + let query = "" + if (url.searchParams.has("q")) query = url.searchParams.get("q") + else if (url.searchParams.has("query")) query = url.searchParams.has("query") + return query +} +function prefsEncoded(prefs) { + return new URLSearchParams(prefs).toString() +} + +function convertMapCentre(url) { + let [lat, lon, zoom] = [null, null, null] + const reg = url.pathname.match(/@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/) + if (reg) { + ;[, lon, lat, zoom] = reg + } else if (url.searchParams.has("center")) { + // Set map centre if present + ;[lat, lon] = url.searchParams.get("center").split(",") + zoom = url.searchParams.get("zoom") ?? "17" + } + return { zoom, lon, lat } +} + +export function randomInstances(clearnet, n) { + let instances = [] + if (n > clearnet.length) n = clearnet.length + for (let i = 0; i < n; i++) { + const randomNumber = Math.floor(Math.random() * clearnet.length) + const randomInstance = clearnet[randomNumber] + instances.push(randomInstance) + } + return instances +} +export function style(options, window) { + const vars = cssVariables(options, window) + return `--text: ${vars.text}; + --bg-main: ${vars.bgMain}; + --bg-secondary: ${vars.bgSecondary}; + --active: ${vars.active}; + --danger: ${vars.danger}; + --light-grey: ${vars.lightGrey};` +} + +function cssVariables(options, window) { + const dark = { + text: "#fff", + bgMain: "#121212", + bgSecondary: "#202020", + active: "#fbc117", + danger: "#f04141", + lightGrey: "#c3c3c3", + } + + const light = { + text: "black", + bgMain: "white", + bgSecondary: "#e4e4e4", + active: "#fb9817", + danger: "#f04141", + lightGrey: "#c3c3c3", + } + if (options.theme == "dark") { + return dark + } else if (options.theme == "light") { + return light + } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return dark + } else { + return light + } } export default { - getRandomInstance, - getNextInstance, - protocolHost, - getList, - getBlacklist, - camelCase, - getConfig, - getOptions, - getPingCache, - ping, + getRandomInstance, + getNextInstance, + protocolHost, + getList, + getBlacklist, + getConfig, + getOptions, + getPingCache, + ping, + addressToLatLng, + getQuery, + prefsEncoded, + convertMapCentre, + randomInstances, + style, } |