diff --git a/qutebrowser/config/__init__.py b/qutebrowser/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 914ed43..a2d97ae 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -3,16 +3,18 @@ from typing import cast # pylint: disable=C0111 from qutebrowser.config.config import ConfigContainer # noqa: F401 -from qutebrowser.config.configfiles import ConfigAPI # noqa: F401 +from qutebrowser.config.configfiles import ConfigAPI + +from freedirect.freedirect import Redirects config: ConfigAPI = cast(ConfigAPI, config) # noqa: F821 pylint: disable=E0602,C0103 c: ConfigContainer = cast(ConfigContainer, c) # noqa: F821 pylint: disable=E0602,C0103 + # Autogenerated config.py # Documentation: # qute://help/configuring.html # qute://help/settings.html - # load additional settings configured via autoconfig.yml _ = config.load_autoconfig() @@ -59,7 +61,9 @@ config.source("alias.py") config.source("maps.py") config.source("content.py") config.source("searchengines.py") -config.source("redirects.py") + +_ = Redirects() + # Tab-Bar # have tab bar on the right, not on the top diff --git a/qutebrowser/config/freedirect/__init__.py b/qutebrowser/config/freedirect/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qutebrowser/config/freedirect/freedirect.py b/qutebrowser/config/freedirect/freedirect.py new file mode 100644 index 0000000..b18e3f0 --- /dev/null +++ b/qutebrowser/config/freedirect/freedirect.py @@ -0,0 +1,203 @@ +import random +import re +from dataclasses import dataclass, field +from typing import Callable +from urllib import parse + +from qutebrowser.api import interceptor +from qutebrowser.extensions.interceptors import QUrl, RedirectException +from qutebrowser.utils import message + + +@dataclass +class Service: + source: list[str] = field(default_factory=lambda: []) + target: list[str] = field(default_factory=lambda: []) + custom_targets: bool = False + preprocess: Callable[[QUrl], QUrl] | None = None + postprocess: Callable[[QUrl], QUrl] | None = None + + def __contains__(self, item: str): + for source in self.source: + if re.search(source, item): + return True + return False + + +def scribe_global_identity(url: QUrl): + """Fix external medium blog to scribe translation. + Some paths from medium will go through a 'global identity' + path which messes up the actual url path we want to go + to and puts it in queries. This puts it back on the path. + """ + path = parse.unquote(f"{url.path()}{url.query()}", encoding="ascii") + url.setQuery(None) + new_path = re.sub(r"m/global-identity-2redirectUrl=", "", path) + url.setPath( + parse.quote(new_path), + mode=QUrl.ParsingMode.StrictMode, + ) + return url + + +def breezewiki_host_to_path(url: QUrl): + host = url.host() + if wiki := host[0 : host.find(".fandom.com")]: + url.setPath(f"/{wiki}{url.path()}") + return url + + +default_services = [ + Service(source=["youtube.com"], target=["invidious"]), + Service(source=["stackoverflow.com"], target=["anonymousoverflow"]), + Service(source=["odysee.com"], target=["librarian"]), + Service(source=["reddit.com"], target=["redlib"]), + Service(source=["instagram.com"], target=["proxigram"]), + Service(source=["twitter.com"], target=["nitter"]), + Service(source=["imdb.com"], target=["libremdb"]), + Service(source=["tiktok.com"], target=["proxitok"]), + Service(source=["imgur.com"], target=["rimgo"]), + Service( + source=["medium.com"], target=["scribe"], postprocess=scribe_global_identity + ), + Service( + source=["fandom.com"], target=["breezewiki"], preprocess=breezewiki_host_to_path + ), + Service(source=["quora.com"], target=["quetre"]), + Service(source=["google.com"], target=["whoogle"]), + Service(source=["genius.com"], target=["dumb"]), + Service(source=["translate.google.com"], target=["lingva", "simplytranslate"]), + Service(source=["deepl.com"], target=["simplytranslate"]), + Service(source=["bandcamp.com"], target=["tent"]), + Service( + custom_targets=True, + source=["pinterest.com"], + target=[ + "pain.thirtysix.pw", + "pt.bloat.cat", + "painterest.gitro.xyz", + ], + ), + Service( + custom_targets=True, + source=["tumblr.com"], + target=[ + "pb.bloat.cat", + "tb.opnxng.com", + "priviblur.pussthecat.org", + "priviblur.thebunny.zone", + "priviblur.gitro.xyz", + "priviblur.canine.tools", + ], + ), + Service( + custom_targets=True, + source=["twitch.com"], + target=[ + "safetwitch.drgns.space", + "safetwitch.projectsegfau.lt", + "stream.whateveritworks.org", + "safetwitch.datura.network", + "ttv.vern.cc", + "safetwitch.frontendfriendly.xyz", + "ttv.femboy.band", + "twitch.seitan-ayoub.lol", + "twitch.sudovanilla.org", + "safetwitch.r4fo.com", + "safetwitch.ducks.party", + "safetwitch.privacyredirect.com", + "st.ngn.tf", + "safetwitch.darkness.services", + "safetwitch.adminforge.de", + ], + ), + Service( + custom_targets=True, + source=["goodreads.com"], + target=[ + "biblioreads.eu.org", + "biblioreads.vercel.app", + "biblioreads.mooo.com", + "bl.vern.cc", + "biblioreads.lunar.icu", + "read.whateveritworks.org", + "biblioreads.privacyfucking.rocks", + "read.seitan-ayoub.lol", + "read.freedit.eu", + "biblioreads.ducks.party", + "biblioreads.snine.nl", + "biblioreads.privacyredirect.com", + "reads.nezumi.party", + "br.bloat.cat", + "read.canine.tools", + ], + ), +] + + +@dataclass +class Redirects: + services: list[Service] = field(default_factory=lambda: default_services) + selector: Callable[[list[str]], str] = lambda c: c[ + random.randint(0, len(c) - 1) + ] # selection algorithm + farside_service: str = ( + "farside.link" # Contains url for farside-like redirector (e.g. 'fastsi.de') + ) + + def __post_init__(self): + interceptor.register(self.intercept) + + def intercept(self, request: interceptor.Request) -> None: + # TODO: Implement config check (maybe 'privacy.redirect = False?') + # if config.get(name="content.oss_redirects") is False: + # return + + if ( + request.resource_type != interceptor.ResourceType.main_frame + or request.request_url.scheme() in {"data", "blob"} + ): + return + + url = request.request_url + if url in self: + try: + request.redirect(self.get(url)) + except RedirectException as e: + message.error(str(e)) + + def get(self, url: QUrl) -> QUrl: + service = self.get_service(url) + if not service: + return url + + foss_host = self.selector(service.target) + if service.preprocess: + url = service.preprocess(url) + + try: + if service.custom_targets: + url.setHost(foss_host) + else: + url.setHost(self.farside_service) + url.setPath(f"/{foss_host}{url.path()}") + except RedirectException as e: + message.error(str(e)) + + if service.postprocess: + url = service.postprocess(url) + return url + + def get_service(self, url: QUrl) -> Service | None: + host = url.host() + for service in self.services: + if host in service: + return service + + def __contains__(self, item: QUrl): + if self.get_service(item): + return True + return False + + +_ = Redirects() diff --git a/qutebrowser/config/redirects.py b/qutebrowser/config/redirects.py deleted file mode 100644 index d6db906..0000000 --- a/qutebrowser/config/redirects.py +++ /dev/null @@ -1,207 +0,0 @@ -import random -import re -from typing import Callable -from urllib import parse - -from qutebrowser.api import interceptor -from qutebrowser.extensions.interceptors import QUrl, RedirectException -from qutebrowser.utils import message - - -def fixScribePath(url: QUrl): - """Fix external medium blog to scribe translation. - Some paths from medium will go through a 'global identity' - path which messes up the actual url path we want to go - to and puts it in queries. This puts it back on the path. - """ - # double unquoting necessary! - # I suppose we double-wrap it earlier somewhere? - # unquoted = parse.unquote( - # url.path(options=QUrl.ComponentFormattingOption.FullyEncoded) - # ) - path = parse.unquote(f"{url.path()}{url.query()}", encoding="ascii") - url.setQuery(None) - new_path = re.sub(r"m/global-identity-2redirectUrl=", "", path) - url.setPath( - parse.quote(new_path), - mode=QUrl.ParsingMode.StrictMode, - ) - return url - - -type Service = dict[str, list[str]] -type Redirects = dict[str, Service] - - -redirects: Redirects = { - "youtube": { - "source": ["youtube.com"], - "farside": ["invidious"], - }, - "stackoverflow": { - "source": ["stackoverflow.com", "askubuntu.com"], - "farside": ["anonymousoverflow"], - }, - "lbry": { - "source": ["odysee.com"], - "farside": ["librarian"], - }, - "reddit": { - "source": ["reddit.com"], - "farside": ["redlib"], - }, - "instagram": { - "source": ["instagram.com"], - "farside": ["proxigram"], - }, - "twitter": { - "source": ["twitter.com"], - "farside": ["nitter"], - }, - "imdb": { - "source": ["imdb.com"], - "farside": ["libremdb"], - }, - "translate": { - "source": ["translate.google.com"], - "farside": ["lingva"], - }, - "tiktok": { - "source": ["tiktok.com"], - "farside": ["proxitok"], - }, - "imgur": { - "source": ["imgur.com"], - "farside": ["rimgo"], - }, - "medium": { - "source": ["medium.com"], - "farside": ["scribe"], - # "postprocess": fixScribePath - }, - "fandom": { - "source": ["fandom.com"], - "farside": ["breezewiki"], - }, - "quora": { - "source": ["quora.com"], - "farside": ["quetre"], - # "postprocess": lambda url: message.info(f"CALLING QUORA WITH {url}") - }, - "google": { - "source": ["google.com"], - "target": [ - "search.albony.xyz", - "search.garudalinux.org", - "search.dr460nf1r3.org", - "s.tokhmi.xyz", - "search.sethforprivacy.com", - "whoogle.dcs0.hu", - "gowogle.voring.me", - "whoogle.privacydev.net", - "wg.vern.cc", - "whoogle.hxvy0.gq", - "whoogle.hostux.net", - "whoogle.lunar.icu", - "wgl.frail.duckdns.org", - "whoogle.no-logs.com", - "whoogle.ftw.lol", - "whoogle-search--replitcomreside.repl.co", - "search.notrustverify.ch", - "whoogle.datura.network", - "whoogle.yepserver.xyz", - "search.nezumi.party", - ], - }, - "biblioreads": { - "source": ["goodreads.com"], - "target": [ - "biblioreads.eu.org", - "biblioreads.vercel.app", - "biblioreads.mooo.com", - "bl.vern.cc", - "biblioreads.lunar.icu", - "read.whateveritworks.org", - "biblioreads.privacyfucking.rocks", - "read.seitan-ayoub.lol", - "read.freedit.eu", - "biblioreads.ducks.party", - "biblioreads.snine.nl", - "biblioreads.privacyredirect.com", - ], - }, - "safetwitch": { - "source": ["twitch.tv"], - "target": [ - "safetwitch.drgns.space", - "safetwitch.projectsegfau.lt", - "stream.whateveritworks.org", - "safetwitch.datura.network", - "ttv.vern.cc", - "safetwitch.frontendfriendly.xyz", - "ttv.femboy.band", - "twitch.seitan-ayoub.lol", - "st.ggtyler.dev", - "safetwitch.lunar.icu", - "twitch.sudovanilla.com", - "safetwitch.r4fo.com", - "safetwitch.ducks.party", - "safetwitch.nogafam.fr", - "safetwitch.privacyredirect.com", - "st.ngn.tf", - ], - }, -} - - -def rewrite(request: interceptor.Request) -> None: - # if config.get(name="content.oss_redirects") is False: - # return - - if ( - request.resource_type != interceptor.ResourceType.main_frame - or request.request_url.scheme() in {"data", "blob"} - ): - return - - url = request.request_url - - if service := _should_be_redirected(url.host()): - url = _farside_redirect( - url, _pick_random(service["farside" if "farside" in service else "target"]) - ) - try: - request.redirect(url) - except RedirectException as e: - message.error(str(e)) - - if "postprocess" in service and isinstance(service["postprocess"], Callable): - url = service["postprocess"](url) - - -def _farside_redirect(url: QUrl, service: str, use_fastside: bool = True) -> QUrl: - try: - url.setHost("fastside.link" if use_fastside else "farside.link") - url.setPath(f"/{service}{url.path()}") - except RedirectException as e: - message.error(str(e)) - return url - - -def _pick_random[T](choices: list[T]) -> T: - return choices[random.randint(0, len(choices) - 1)] - - -def _should_be_redirected( - # TODO: Update to use typedefs/classes instead of this jumble - host: str, - redirects: Redirects = redirects, -) -> Service | None: - for service in redirects.values(): - for source in service["source"]: - if re.search(source, host): - return service - return None - - -interceptor.register(rewrite)