dotfiles/qutebrowser/config/freedirect/freedirect.py
Marty Oehme 86306afc08
qutebrowser: Manually redirect to FOSS genius frontends
Since farside linking currently does not work we take the work into our
own hands.
2025-02-22 19:35:33 +01:00

215 lines
6.6 KiB
Python

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=["translate.google.com"], target=["lingva", "simplytranslate"]),
Service(source=["deepl.com"], target=["simplytranslate"]),
Service(source=["bandcamp.com"], target=["tent"]),
Service(
custom_targets=True,
source=["genius.com"],
target=[
"dumb.privacydev.net",
"dumb.privacydev.net",
"dm.vern.cc",
"sing.whatever.social",
"dumb.nunosempere.com",
"dumb.lunar.icu",
"dumb.esmailelbob.xyz",
],
),
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()