+import hashlib
+import sysconfig
+from html.parser import HTMLParser
+from shutil import rmtree
+from subprocess import check_output, run
+from urllib.request import urlopen
+from xml.etree import ElementTree
+
+from localapps import AppBase, find_html_attr
+
+
+class DownloadFinder(HTMLParser):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.h2 = False
+ self.attention = False
+ self.data = []
+
+ def handle_starttag(self, tag, attrs):
+ tag = tag.upper()
+ if tag == "H2":
+ self.h2 = True
+ elif self.attention:
+ if tag == "A":
+ self.data[-1].append(find_html_attr(attrs, "href"))
+ elif tag == "LI":
+ self.data.append([])
+
+ def handle_data(self, data):
+ if self.h2 and data.strip().upper() == "DOWNLOADS":
+ self.attention = True
+ elif self.attention and self.data and data.strip():
+ self.data[-1].append(data)
+
+ def handle_endtag(self, tag):
+ tag = tag.upper()
+ if self.h2 and tag == "H2":
+ self.h2 = False
+
+
+class UngoogledChromium(AppBase):
+ NAME = "Ungoogled-Chromium"
+ RELEASE_URL = (
+ "https://ungoogled-software.github.io/ungoogled-chromium-binaries/feed.xml"
+ )
+ BIN_PATH = AppBase.APPS_DIR / NAME.lower() / "chrome"
+ NS = {"": "http://www.w3.org/2005/Atom"}
+ PLATFORMS = {"linux-x86_64": "Portable Linux 64-bit"}
+ DESKTOP_URL = (
+ "https://raw.githubusercontent.com/ungoogled-software"
+ "/ungoogled-chromium-portablelinux/refs/heads/master/package"
+ "/ungoogled-chromium.desktop"
+ )
+ ICON_URLS = (
+ (
+ "https://github.com/chromium/chromium/raw/refs/heads/main/chrome/app/theme"
+ "/chromium/product_logo_{size}.png"
+ ),
+ (
+ "https://github.com/chromium/chromium/raw/refs/heads/main/chrome/app/theme"
+ "/default_100_percent/chromium/product_logo_{size}.png"
+ ),
+ )
+
+ @classmethod
+ def get_latest_version(cls):
+ return dict(
+ entry.find("title", cls.NS).text.split(":", 1)
+ for entry in ElementTree.parse(
+ urlopen(cls.RELEASE_URL)
+ ).getroot().findall("entry", cls.NS)
+ )[cls.PLATFORMS[sysconfig.get_platform()]].strip()
+
+ @classmethod
+ def get_installed_version(cls):
+ if cls.has_executable():
+ output = check_output([cls.BIN_PATH, "--version"], text=True).strip()
+ return output.split(maxsplit=1)[1]
+ return None
+
+ @classmethod
+ def install(cls):
+ url = dict(
+ (key, url)
+ for key, url in (
+ (
+ entry.find("title", cls.NS).text.split(":", 1)[0],
+ entry.find("link", cls.NS).attrib["href"]
+ )
+ for entry in ElementTree.parse(
+ urlopen(cls.RELEASE_URL)
+ ).getroot().findall("entry", cls.NS)
+ )
+ )[cls.PLATFORMS[sysconfig.get_platform()]]
+ download_finder = DownloadFinder()
+ download_finder.feed(urlopen(url).read().decode())
+ content = cls.cached_download(download_finder.data[0][0])
+ for algo, digest in (
+ (a.strip().strip(":").lower(), b) for a, b in download_finder.data[1:]
+ ):
+ m = getattr(hashlib, algo)(content)
+ assert m.hexdigest() == digest
+ app_dir = cls.BIN_PATH.parent
+ app_dir.mkdir(0o755, parents=True, exist_ok=True)
+ run(
+ ["tar", "xJ", "--strip-components=1", "-C", str(app_dir)],
+ check=True,
+ input=content
+ )
+ for size in (16, 24, 32, 48, 64, 128, 256):
+ icon_path = (
+ cls.get_xdg_home()
+ / "icons"
+ / "hicolor"
+ / f"{size}x{size}"
+ / "apps"
+ / "chromium.png"
+ )
+ icon_path.parent.mkdir(0o755, parents = True, exist_ok=True)
+ with icon_path.open("wb") as fh:
+ fh.write(
+ urlopen(cls.ICON_URLS[size in (16, 32)].format(size=size)).read()
+ )
+ desktop_file = urlopen(cls.DESKTOP_URL).read().decode().replace(
+ "Exec=chromium", f"Exec={app_dir / 'chrome'}"
+ )
+ with (
+ cls.get_xdg_home() / "applications" / "ungoogled-chromium.desktop"
+ ).open("wt") as fh:
+ fh.write(desktop_file)
+
+ @classmethod
+ def uninstall(cls):
+ rmtree(cls.BIN_PATH.parent)
+ (
+ cls.get_xdg_home() / "applications" / "ungoogled-chromium.desktop"
+ ).unlink(missing_ok=True)
+ for size in (16, 24, 32, 48, 64, 128, 256):
+ (
+ cls.get_xdg_home()
+ / "icons"
+ / "hicolor"
+ / f"{size}x{size}"
+ / "apps"
+ / "chromium.png"
+ ).unlink(missing_ok=True)