"use strict"; import commonHelper from "../../assets/javascripts/helpers/common.js"; import twitterHelper from "../../assets/javascripts/helpers/twitter.js"; import youtubeHelper from "../../assets/javascripts/helpers/youtube.js"; import instagramHelper from "../../assets/javascripts/helpers/instagram.js"; import mapsHelper from "../../assets/javascripts/helpers/google-maps.js"; import redditHelper from "../../assets/javascripts/helpers/reddit.js"; import searchHelper from "../../assets/javascripts/helpers/google-search.js"; const nitterInstances = twitterHelper.redirects; const twitterDomains = twitterHelper.targets; const youtubeDomains = youtubeHelper.targets; const invidiousInstances = youtubeHelper.redirects; const instagramDomains = instagramHelper.targets; const bibliogramInstances = instagramHelper.redirects; const instagramReservedPaths = instagramHelper.reservedPaths; const bibliogramBypassPaths = instagramHelper.bypassPaths; const osmDefault = mapsHelper.redirects[0]; const googleMapsRegex = mapsHelper.targets; const mapCentreRegex = mapsHelper.mapCentreRegex; const dataLatLngRegex = mapsHelper.dataLatLngRegex; const placeRegex = mapsHelper.placeRegex; const travelModes = mapsHelper.travelModes; const layers = mapsHelper.layers; const redditInstances = redditHelper.redirects; const redditDomains = redditHelper.targets; const redditBypassPaths = redditHelper.bypassPaths; const redditDefault = redditHelper.redirects[0]; const googleSearchRegex = searchHelper.targets; const searchEngineInstances = searchHelper.redirects; let disableNitter; let disableInvidious; let disableBibliogram; let disableOsm; let disableReddit; let disableSearchEngine; let nitterInstance; let invidiousInstance; let bibliogramInstance; let osmInstance; let redditInstance; let searchEngineInstance; let alwaysProxy; let onlyEmbeddedVideo; let videoQuality; let invidiousDarkMode; let invidiousVolume; let invidiousPlayerStyle; let invidiousSubtitles; let invidiousAutoplay; let useFreeTube; let nitterRandomPool; let invidiousRandomPool; let bibliogramRandomPool; let exceptions; window.browser = window.browser || window.chrome; browser.storage.sync.get( [ "nitterInstance", "invidiousInstance", "bibliogramInstance", "osmInstance", "redditInstance", "searchEngineInstance", "disableNitter", "disableInvidious", "disableBibliogram", "disableOsm", "disableReddit", "disableSearchEngine", "alwaysProxy", "onlyEmbeddedVideo", "videoQuality", "invidiousDarkMode", "invidiousVolume", "invidiousPlayerStyle", "invidiousSubtitles", "invidiousAutoplay", "useFreeTube", "nitterRandomPool", "invidiousRandomPool", "bibliogramRandomPool", "exceptions", ], (result) => { nitterInstance = result.nitterInstance; invidiousInstance = result.invidiousInstance; bibliogramInstance = result.bibliogramInstance; osmInstance = result.osmInstance || osmDefault; redditInstance = result.redditInstance || redditDefault; searchEngineInstance = result.searchEngineInstance; disableNitter = result.disableNitter; disableInvidious = result.disableInvidious; disableBibliogram = result.disableBibliogram; disableOsm = result.disableOsm; disableReddit = result.disableReddit; disableSearchEngine = result.disableSearchEngine; alwaysProxy = result.alwaysProxy; onlyEmbeddedVideo = result.onlyEmbeddedVideo; videoQuality = result.videoQuality; invidiousDarkMode = result.invidiousDarkMode; exceptions = result.exceptions ? result.exceptions.map((e) => { return new RegExp(e); }) : []; invidiousVolume = result.invidiousVolume; invidiousPlayerStyle = result.invidiousPlayerStyle; invidiousSubtitles = result.invidiousSubtitles || ""; invidiousAutoplay = result.invidiousAutoplay; useFreeTube = result.useFreeTube; nitterRandomPool = result.nitterRandomPool ? result.nitterRandomPool.split(",") : commonHelper.filterInstances(nitterInstances); invidiousRandomPool = result.invidiousRandomPool ? result.invidiousRandomPool.split(",") : commonHelper.filterInstances(invidiousInstances); bibliogramRandomPool = result.bibliogramRandomPool ? result.bibliogramRandomPool.split(",") : commonHelper.filterInstances(bibliogramInstances); } ); browser.storage.onChanged.addListener((changes) => { if ("nitterInstance" in changes) { nitterInstance = changes.nitterInstance.newValue; } if ("invidiousInstance" in changes) { invidiousInstance = changes.invidiousInstance.newValue; } if ("bibliogramInstance" in changes) { bibliogramInstance = changes.bibliogramInstance.newValue; } if ("osmInstance" in changes) { osmInstance = changes.osmInstance.newValue || osmDefault; } if ("redditInstance" in changes) { redditInstance = changes.redditInstance.newValue || redditDefault; } if ("searchEngineInstance" in changes) { searchEngineInstance = changes.searchEngineInstance.newValue; } if ("disableNitter" in changes) { disableNitter = changes.disableNitter.newValue; } if ("disableInvidious" in changes) { disableInvidious = changes.disableInvidious.newValue; } if ("disableBibliogram" in changes) { disableBibliogram = changes.disableBibliogram.newValue; } if ("disableOsm" in changes) { disableOsm = changes.disableOsm.newValue; } if ("disableReddit" in changes) { disableReddit = changes.disableReddit.newValue; } if ("disableSearchEngine" in changes) { disableSearchEngine = changes.disableSearchEngine.newValue; } if ("alwaysProxy" in changes) { alwaysProxy = changes.alwaysProxy.newValue; } if ("onlyEmbeddedVideo" in changes) { onlyEmbeddedVideo = changes.onlyEmbeddedVideo.newValue; } if ("videoQuality" in changes) { videoQuality = changes.videoQuality.newValue; } if ("invidiousDarkMode" in changes) { invidiousDarkMode = changes.invidiousDarkMode.newValue; } if ("invidiousVolume" in changes) { invidiousVolume = changes.invidiousVolume.newValue; } if ("invidiousPlayerStyle" in changes) { invidiousPlayerStyle = changes.invidiousPlayerStyle.newValue; } if ("invidiousSubtitles" in changes) { invidiousSubtitles = changes.invidiousSubtitles.newValue; } if ("invidiousAutoplay" in changes) { invidiousAutoplay = changes.invidiousAutoplay.newValue; } if ("useFreeTube" in changes) { useFreeTube = changes.useFreeTube.newValue; } if ("nitterRandomPool" in changes) { nitterRandomPool = changes.nitterRandomPool.newValue.split(","); } if ("invidiousRandomPool" in changes) { invidiousRandomPool = changes.invidiousRandomPool.newValue.split(","); } if ("bibliogramRandomPool" in changes) { bibliogramRandomPool = changes.bibliogramRandomPool.newValue.split(","); } if ("exceptions" in changes) { exceptions = changes.exceptions.newValue.map((e) => { return new RegExp(e); }); } }); function isException(url, initiator) { return ( exceptions.some((regex) => regex.test(url.href)) || (initiator && exceptions.some((regex) => regex.test(initiator.href))) ); } function isFirefox() { return typeof InstallTrigger !== "undefined"; } function redirectYouTube(url, initiator, type) { if (disableInvidious || isException(url, initiator)) { return null; } if ( initiator && (initiator.origin === invidiousInstance || invidiousInstances.includes(initiator.origin) || youtubeDomains.includes(initiator.host)) ) { return null; } if (url.pathname.match(/iframe_api/) || url.pathname.match(/www-widgetapi/)) { // Don't redirect YouTube Player API. return null; } if (url.host.split(".")[0] === "studio") { // Avoid redirecting `studio.youtube.com` return null; } if (onlyEmbeddedVideo && type !== "sub_frame") { return null; } if (useFreeTube && type === "main_frame") { return `freetube://${url}`; } // Apply settings if (alwaysProxy) { url.searchParams.append("local", true); } if (videoQuality) { url.searchParams.append("quality", videoQuality); } if (invidiousDarkMode) { url.searchParams.append("dark_mode", invidiousDarkMode); } if (invidiousVolume) { url.searchParams.append("volume", invidiousVolume); } if (invidiousPlayerStyle) { url.searchParams.append("player_style", invidiousPlayerStyle); } if (invidiousSubtitles) { url.searchParams.append("subtitles", invidiousSubtitles); } url.searchParams.append("autoplay", invidiousAutoplay ? 1 : 0); return `${ invidiousInstance || commonHelper.getRandomInstance(invidiousRandomPool) }${url.pathname.replace("/shorts", "")}${url.search}`; } function redirectTwitter(url, initiator) { if (disableNitter || isException(url, initiator)) { return null; } if (url.pathname.includes("/home")) { return null; } if ( isFirefox() && initiator && (initiator.origin === nitterInstance || nitterInstances.includes(initiator.origin) || twitterDomains.includes(initiator.host)) ) { browser.storage.sync.set({ redirectBypassFlag: true, }); return null; } if (url.host.split(".")[0] === "pbs") { return `${ nitterInstance || commonHelper.getRandomInstance(nitterRandomPool) }/pic/${encodeURIComponent(url.href)}`; } else if (url.host.split(".")[0] === "video") { return `${ nitterInstance || commonHelper.getRandomInstance(nitterRandomPool) }/gif/${encodeURIComponent(url.href)}`; } else if (url.pathname.includes("tweets")) { return `${ nitterInstance || commonHelper.getRandomInstance(nitterRandomPool) }${url.pathname.replace("/tweets", "")}${url.search}`; } else { return `${ nitterInstance || commonHelper.getRandomInstance(nitterRandomPool) }${url.pathname}${url.search}`; } } function redirectInstagram(url, initiator, type) { if (disableBibliogram || isException(url, initiator)) { return null; } // Do not redirect Bibliogram view on Instagram links if ( initiator && (initiator.origin === bibliogramInstance || bibliogramInstances.includes(initiator.origin) || instagramDomains.includes(initiator.host)) ) { return null; } // Do not redirect /accounts, /embeds.js, or anything other than main_frame if (type !== "main_frame" || url.pathname.match(bibliogramBypassPaths)) { return null; } if ( url.pathname === "/" || instagramReservedPaths.includes(url.pathname.split("/")[1]) ) { return `${ bibliogramInstance || commonHelper.getRandomInstance(bibliogramRandomPool) }${url.pathname}${url.search}`; } else { // Likely a user profile, redirect to '/u/...' return `${ bibliogramInstance || commonHelper.getRandomInstance(bibliogramRandomPool) }/u${url.pathname}${url.search}`; } } function redirectGoogleMaps(url, initiator) { if (disableOsm || isException(url, initiator)) { return null; } let redirect; let mapCentre = ""; let params = ""; // Set map centre if present if (url.pathname.match(mapCentreRegex)) { const [, lat, lon, zoom] = url.pathname.match(mapCentreRegex); mapCentre = `#map=${zoom}/${lat}/${lon}`; } else if (url.search.includes("center=")) { const [lat, lon] = url.searchParams.get("center").split(","); mapCentre = `#map=${url.searchParams.get("zoom") || "17"}/${lat}/${lon}`; // Set default zoom if mapCentre not present } else { params = "&zoom=17"; } // Set map layer params = `${params}&layers=${ layers[url.searchParams.get("layer")] || layers["none"] }`; // Handle Google Maps Embed API if (url.pathname.includes("/embed")) { 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 marker, bbox; mapsHelper.addressToLatLng(query, (coords, boundingbox) => { marker = coords; bbox = boundingbox; }); redirect = `${osmInstance}/export/embed.html?bbox=${bbox}&layer=mapnik&marker=${marker}`; // Handle Google Maps Directions } else if (url.pathname.includes("/dir")) { const travelMode = travelModes[url.searchParams.get("travelmode")] || travelModes["driving"]; let origin; mapsHelper.addressToLatLng(url.searchParams.get("origin"), (coords) => { origin = coords; }); let destination; mapsHelper.addressToLatLng( url.searchParams.get("destination"), (coords) => { destination = coords; } ); redirect = `${osmInstance}/directions?engine=${travelMode}&route=${origin}%3B${destination}${mapCentre}${params}`; // Get marker from data attribute } else if ( url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex) ) { const [mlat, mlon] = url.pathname.match(dataLatLngRegex); redirect = `${osmInstance}/?mlat=${mlat.replace( "!3d", "" )}&mlon=${mlon.replace("!4d", "")}${mapCentre}${params}`; // Get marker from ll param } else if (url.searchParams.has("ll")) { const [mlat, mlon] = url.searchParams.get("ll").split(","); redirect = `${osmInstance}/?mlat=${mlat}&mlon=${mlon}${mapCentre}${params}`; // Get marker from viewpoint param. } else if (url.searchParams.has("viewpoint")) { const [mlat, mlon] = url.searchParams.get("viewpoint").split(","); redirect = `${osmInstance}/?mlat=${mlat}&mlon=${mlon}${mapCentre}${params}`; // Use query as search if present. } else { 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]; } redirect = `${osmInstance}/${query ? "search?query=" + query : ""}${ mapCentre || "#" }${params}`; } return redirect; } function redirectReddit(url, initiator, type) { if (disableReddit || isException(url, initiator)) { return null; } // Do not redirect when already on the selected view if ( (initiator && initiator.origin === redditInstance) || url.origin === redditInstance ) { return null; } // Do not redirect exclusions nor anything other than main_frame if (type !== "main_frame" || url.pathname.match(redditBypassPaths)) { return null; } return `${redditInstance}${url.pathname}${url.search}`; } function redirectSearchEngine(url, initiator) { if (disableSearchEngine || isException(url, initiator)) { return null; } const searchEngine = searchEngineInstance || commonHelper.getRandomInstance(searchEngineInstances); let search = ""; url.search .slice(1) .split("&") .forEach(function (input) { if (input.startsWith("q=")) search = input; }); return `${searchEngine.link}${searchEngine.q}?${search}`; } browser.webRequest.onBeforeRequest.addListener( (details) => { const url = new URL(details.url); let initiator; if (details.originUrl) { initiator = new URL(details.originUrl); } else if (details.initiator) { initiator = new URL(details.initiator); } let redirect; if (youtubeDomains.includes(url.host)) { redirect = { redirectUrl: redirectYouTube(url, initiator, details.type), }; } else if (twitterDomains.includes(url.host)) { redirect = { redirectUrl: redirectTwitter(url, initiator), }; } else if (instagramDomains.includes(url.host)) { redirect = { redirectUrl: redirectInstagram(url, initiator, details.type), }; } else if (url.href.match(googleMapsRegex)) { redirect = { redirectUrl: redirectGoogleMaps(url, initiator), }; } else if ( redditDomains.includes(url.host) || redditInstances.includes(url.origin) ) { redirect = { redirectUrl: redirectReddit(url, initiator, details.type), }; } else if (url.href.match(googleSearchRegex)) { redirect = { redirectUrl: redirectSearchEngine(url, initiator), }; } if (redirect && redirect.redirectUrl) { console.info( "Redirecting", `"${url.href}"`, "=>", `"${redirect.redirectUrl}"` ); console.info("Details", details); } return redirect; }, { urls: [""], }, ["blocking"] ); browser.runtime.onInstalled.addListener((details) => { if (details.reason === "update") { browser.storage.sync.get( ["whitelist", "exceptions", "invidiousInstance"], (result) => { if (result.whitelist) { let whitelist = result.whitelist.map((e) => e.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") ); browser.storage.sync.set({ exceptions: result.exceptions.concat(whitelist), whitelist: null, }); } if (result.invidiousInstance === "https://invidio.us") { browser.storage.sync.set({ invidiousInstance: null, }); } } ); } });