From ac9ee1531ab4df5bdf29508bca2f18bac7f4f9cd Mon Sep 17 00:00:00 2001 From: daijro Date: Sat, 7 Feb 2026 23:16:16 -0600 Subject: [PATCH] Clean up manager UI & add more info in version cli - Rewrite `camoufox version` to show storage/path info and more python packages - In camoufox active/set, show active channels. Fall back to original/stable if none is set - Remove config files in `camoufox remove --all` - Spoof the camoufox python library version in UI debug mode - Fix scrollbar always showing on windows - Other UI cleanup --- pythonlib/README.md | 2 +- pythonlib/camoufox/__main__.py | 248 ++++++++++++++++++++-------- pythonlib/camoufox/gui/backend.py | 75 ++++++--- pythonlib/camoufox/gui/qml/main.qml | 163 ++++++++++-------- pythonlib/camoufox/multiversion.py | 9 + pythonlib/camoufox/pkgman.py | 8 +- 6 files changed, 334 insertions(+), 171 deletions(-) diff --git a/pythonlib/README.md b/pythonlib/README.md index 8162c13..7704c34 100644 --- a/pythonlib/README.md +++ b/pythonlib/README.md @@ -57,7 +57,7 @@ Manage installed browsers, active version, IP geolocation databases, and package More updates on it will be coming soon. -manager-ui +ui-screenshot
diff --git a/pythonlib/camoufox/__main__.py b/pythonlib/camoufox/__main__.py index 23744b9..a8b7576 100644 --- a/pythonlib/camoufox/__main__.py +++ b/pythonlib/camoufox/__main__.py @@ -21,8 +21,11 @@ from .geolocation import ( save_geoip_config, ) from .multiversion import ( + BROWSERS_DIR, + CONFIG_FILE, REPO_CACHE_FILE, InstalledVersion, + get_default_channel, list_installed, load_config, load_repo_cache, @@ -241,8 +244,8 @@ def fetch(version): channel = config.get('channel', '') repo_name = channel.split('/')[0] if '/' in channel else channel ver_str = config['pinned'] - elif config.get('channel'): - channel = config['channel'] + else: + channel = config.get('channel') or get_default_channel() if '/' in channel: repo_name, ctype = channel.split('/', 1) else: @@ -261,9 +264,6 @@ def fetch(version): else: rprint(f"No versions found for channel '{channel}'.", fg="red") return - else: - rprint("No channel set. Run 'camoufox select' first.", fg="red") - return for repo_data in cache.get('repos', []): if repo_data['name'].lower() != repo_name.lower(): @@ -319,7 +319,7 @@ def _set_channel(repo_name: str, channel_type: str): for inst in list_installed(): if inst.version.build == latest_build and inst.repo_name == repo_name.lower(): set_active(inst.relative_path) - click.secho(f"Latest is installed: {inst.channel_path}", fg="green") + click.secho(f"Using latest: {inst.channel_path} (installed)", fg="green") return break @@ -421,15 +421,13 @@ def set_cmd(specifier, geoip): channels.append((name, 'prerelease', prereleases[0])) config = load_config() - channel = config.get('channel', '') + channel = config.get('channel') or get_default_channel() pinned = config.get('pinned') if pinned: click.secho(f"Pinned: {channel.lower()}/{pinned}", fg="cyan") - elif channel: - click.secho(f"Channel: {channel.lower()}", fg="cyan") else: - click.secho("(no channel set)", fg="yellow") + click.secho(f"Channel: {channel.lower()}", fg="cyan") click.echo() channel_versions = {} @@ -667,7 +665,8 @@ def remove(version_path, remove_all, yes): has_geoip = GEOIP_DIR.exists() if remove_all or version_path == 'all': - if not installed and not has_geoip: + has_config = CONFIG_FILE.exists() or REPO_CACHE_FILE.exists() + if not installed and not has_geoip and not has_config: rprint("Nothing to remove.", fg="yellow") return if installed and (yes or click.confirm(f"Remove all {len(installed)} browser version(s)?")): @@ -676,6 +675,13 @@ def remove(version_path, remove_all, yes): rprint(f"Removed {len(installed)} version(s).", fg="green") if has_geoip and (yes or click.confirm("Remove GeoIP database?")): remove_mmdb() + # Clean up config files + for f in (CONFIG_FILE, REPO_CACHE_FILE): + if f.exists(): + f.unlink() + # Remove install dir if empty + if INSTALL_DIR.exists() and not any(INSTALL_DIR.iterdir()): + INSTALL_DIR.rmdir() return if not installed: @@ -747,67 +753,167 @@ def gui(debug): rprint("GUI requires PySide6. Install with: pip install 'camoufox\\[gui]'", fg="red") +class VersionInfo: + def __init__(self): + from rich.table import Table + from rich.text import Text + + from .pkgman import console + + self.Text = Text + self.console = console + self.t = Table.grid(padding=(0, 2)) + + def _row(self, label, value, style="green"): + """ + Print a row to the table + """ + self.t.add_row(self.Text(f" {label}", style="dim"), self.Text(value, style=style)) + + def _header(self, title): + """ + Print a section title to the table + """ + self.t.add_row(self.Text(title, style="bold"), self.Text("")) + + def _pkg(self, label, pkg_name): + try: + self._row(label, f"v{pkg_version(pkg_name)}") + except PackageNotFoundError: + self._row(label, "?", style="dim") + + def packages(self): + """ + Gets installed package versions + """ + self._header("Python Packages") + self._pkg("Camoufox", "camoufox") + self._pkg("Browserforge", "browserforge") + self._pkg("Apify Fingerprints", "apify_fingerprint_datapoints") + self._pkg("Playwright", "playwright") + + def browser(self): + """ + Gets active browser, installed version, and sync status + """ + from datetime import datetime, timezone + + self._header("Browser") + + config = load_config() + pinned = config.get('pinned') + channel = config.get('channel') or get_default_channel() + + # Active: what was set (channel or pinned version) + if pinned: + self._row("Active", f"{channel.lower()}/{pinned}") + else: + self._row("Active", channel.lower()) + + # Find the active installed version + active_v = None + for v in list_installed(): + if v.is_active: + active_v = v + break + + # Browser version + if active_v: + self._row("Browser", f"v{active_v.version.full_string}") + else: + self._row("Browser", "—", style="dim") + + # Is installed? + if active_v: + self._row("Installed", "Yes", style="green") + else: + self._row("Installed", "No", style="red") + + # Check if installed version is the latest in its own channel + if active_v: + ctype = "prerelease" if active_v.is_prerelease else "stable" + repo_ch = f"{active_v.repo_name}/{ctype}" + is_latest = False + cache = load_repo_cache() + for repo_data in cache.get('repos', []): + if repo_data['name'].lower() != active_v.repo_name.lower(): + continue + candidates = [v for v in repo_data.get('versions', []) if v.get('is_prerelease', False) == active_v.is_prerelease] + if candidates and active_v.version.build == candidates[0]['build']: + is_latest = True + break + self._row( + f"Latest in {repo_ch}?", + "Yes" if is_latest else "No", + style="green" if is_latest else "red", + ) + + # Last repo sync time from cache file mtime + if REPO_CACHE_FILE.exists(): + mtime = REPO_CACHE_FILE.stat().st_mtime + dt = datetime.fromtimestamp(mtime, tz=timezone.utc).astimezone() + self._row("Last Sync", dt.strftime('%Y-%m-%d %H:%M'), style="dim") + else: + self._row("Last Sync", "Never", style="red") + + def geoip(self): + """ + Get info about the geoip db and check if its there + """ + from datetime import datetime, timezone + + self._header("GeoIP") + if not ALLOW_GEOIP: + # geoip2 package not installed + self._row("Status", "Not supported (install camoufox[geoip])", style="dim") + else: + mmdb_path = get_mmdb_path() + if mmdb_path.exists(): + # Show active database name and last update time + geoip_cfg = load_geoip_config() + self._row("Database", geoip_cfg.get('name', 'Unknown')) + mtime = mmdb_path.stat().st_mtime + dt = datetime.fromtimestamp(mtime, tz=timezone.utc).astimezone() + self._row("Updated", dt.strftime('%Y-%m-%d %H:%M'), style="dim") + else: + self._row("Database", "Not installed", style="dim") + + def _dir_size(self, path) -> str: + if not path.exists(): + return "—" + total = sum(f.stat().st_size for f in path.rglob('*') if f.is_file()) + for unit in ('B', 'KB', 'MB'): + if total < 1024: + return f"{total:.1f} {unit}" if unit != 'B' else f"{total} B" + total /= 1024 + return f"{total:.1f} GB" + + def storage(self): + """ + Get paths and directory sizes + """ + self._header("Storage") + self._row("Install path", str(INSTALL_DIR), style="cyan") + self._row("Browser(s) directory size", self._dir_size(BROWSERS_DIR), style="dim") + if ALLOW_GEOIP: + self._row("GeoIP database size", self._dir_size(GEOIP_DIR), style="dim") + self._row("Config file", str(CONFIG_FILE), style="cyan") + self._row("Repo cache", str(REPO_CACHE_FILE), style="cyan") + + def print_all(self): + self.packages() + self.browser() + self.geoip() + self.storage() + self.console.print(self.t) + + @cli.command(name='version') def version(): """ - Display version info + Display version, package, browser, and storage info """ - try: - rprint(f"Pip package:\t v{pkg_version('camoufox')}", fg="green") - except PackageNotFoundError: - rprint("Pip package:\t Not installed!", fg="red") - - active_v = None - for v in list_installed(): - if v.is_active: - active_v = v - break - - if not active_v: - rprint("Active:\t\t Not installed!", fg="red") - return - - config = load_config() - pinned = config.get('pinned') - channel = config.get('channel', '') - - # Channel - if pinned: - rprint(f"Channel:\t {channel.lower()} (Version pinned)", fg="cyan") - elif channel: - rprint(f"Channel:\t {channel.lower()} (Following updates)", fg="cyan") - - # Version with update status - rprint(f"Version:\t v{active_v.version.full_string} ", fg="green", nl=False) - if pinned: - click.echo() - elif channel: - repo_name, ctype = channel.split('/', 1) if '/' in channel else ('', '') - is_pre = ctype == "prerelease" - latest_build = None - cache = load_repo_cache() - for repo_data in cache.get('repos', []): - if repo_data['name'].lower() != repo_name.lower(): - continue - candidates = [v for v in repo_data.get('versions', []) if v.get('is_prerelease', False) == is_pre] - if candidates: - latest_build = candidates[0]['build'] - break - if latest_build and active_v.version.build == latest_build: - rprint("(Up to date!)", fg="yellow") - elif latest_build: - rprint(f"(Latest: {latest_build})", fg="red") - else: - click.echo() - else: - click.echo() - - if REPO_CACHE_FILE.exists(): - from datetime import datetime, timezone - - mtime = REPO_CACHE_FILE.stat().st_mtime - dt = datetime.fromtimestamp(mtime, tz=timezone.utc).astimezone() - rprint(f"Last repo sync:\t {dt.strftime('%Y-%m-%d %H:%M')}", fg="bright_black") + VersionInfo().print_all() @cli.command(name='active') @@ -823,15 +929,13 @@ def active_cmd(): config = load_config() pinned = config.get('pinned') - channel = config.get('channel', '') + channel = config.get('channel') or get_default_channel() if pinned: click.echo(f"{channel.lower()}/{pinned} ", nl=False) rprint("(not installed)", fg="yellow") - elif channel: + else: click.echo(f"{channel.lower()} ", nl=False) rprint("(not installed)", fg="yellow") - else: - rprint("No active version.", fg="yellow") @cli.command(name='path') diff --git a/pythonlib/camoufox/gui/backend.py b/pythonlib/camoufox/gui/backend.py index 5fa9f1d..2a9e14e 100644 --- a/pythonlib/camoufox/gui/backend.py +++ b/pythonlib/camoufox/gui/backend.py @@ -22,6 +22,7 @@ from PySide6.QtQuickControls2 import QQuickStyle from ..multiversion import ( BROWSERS_DIR, get_cached_versions, + get_default_channel, get_repo_name, list_installed, load_config, @@ -85,10 +86,11 @@ class DownloadWorker(Worker): class SyncWorker(Worker): - def __init__(self, spoof_os=None, spoof_arch=None): + def __init__(self, spoof_os=None, spoof_arch=None, spoof_lib_ver=None): super().__init__() self.spoof_os = spoof_os self.spoof_arch = spoof_arch + self.spoof_lib_ver = spoof_lib_ver def run(self): try: @@ -99,7 +101,7 @@ class SyncWorker(Worker): self.status.emit("Syncing...") cache = {'repos': []} - for rc in RepoConfig.load_repos(): + for rc in RepoConfig.load_repos(spoof_library_version=self.spoof_lib_ver): self.status.emit(f"Syncing {rc.name}...") versions = list_available_versions( rc, @@ -126,6 +128,7 @@ class SyncWorker(Worker): cache['spoof_os'] = self.spoof_os cache['spoof_arch'] = self.spoof_arch + cache['spoof_lib_ver'] = self.spoof_lib_ver _dfmt = '%#m/%#d/%Y %#I:%M %p' if sys.platform == 'win32' else '%-m/%-d/%Y %-I:%M %p' cache['sync_time'] = datetime.now().strftime(_dfmt) save_repo_cache(cache) @@ -287,6 +290,7 @@ class Backend(QObject): self._spoof_os_idx = 0 self._spoof_arch_idx = 0 + self._spoof_lib_ver = "" self._load_spoof_from_cache() self._load_geoip() @@ -385,23 +389,20 @@ class Backend(QObject): cfg = load_config() if cfg.get('pinned'): return f"v{cfg['pinned']}" - channel = cfg.get('channel', '') - if channel: - _, keys, latest = self._build_channels() - try: - return latest[keys.index(channel)] or channel - except ValueError: - return channel - return "(no channel set)" + channel = cfg.get('channel') or get_default_channel() + _, keys, latest = self._build_channels() + try: + return latest[keys.index(channel)] or channel + except ValueError: + return channel @Property(str, notify=infoChanged) def activeBrowserColor(self): - cfg = load_config() - return "#26a69a" if cfg.get('channel') or cfg.get('pinned') else "#888888" + return "#26a69a" @Property(str, notify=infoChanged) def followedChannel(self): - return load_config().get('channel', '') + return load_config().get('channel') or get_default_channel() @Property(list, notify=infoChanged) def channels(self): @@ -415,20 +416,24 @@ class Backend(QObject): def channelLatest(self): return self._build_channels()[2] + @Property(str, notify=infoChanged) + def activeBrowserLabel(self): + cfg = load_config() + return "Pinned Version" if cfg.get('pinned') else "Active Channel" + @Property(str, notify=infoChanged) def activeLabel(self): cfg = load_config() - channel = cfg.get('channel', '') pinned = cfg.get('pinned') - if channel: - channels, keys, _ = self._build_channels() - try: - return f"Active: following {channels[keys.index(channel)]}" - except ValueError: - return f"Active: following {channel}" if pinned: - return f"Active: pinned to v{pinned}" - return "" + return f"Pinned version: v{pinned}" + channel = cfg.get('channel') or get_default_channel() + parts = channel.split('/', 1) + repo = parts[0].capitalize() + ctype = parts[1] if len(parts) > 1 else 'stable' + if ctype == 'stable': + return f"Following channel: {repo}" + return f"Following channel: {repo}/{ctype}" @Property(str, notify=infoChanged) def libraryVersion(self): @@ -442,12 +447,16 @@ class Backend(QObject): def browserforgeVersion(self): return self._pkg_version('browserforge') + @Property(str, notify=infoChanged) + def fingerprintVersion(self): + return self._pkg_version('apify_fingerprint_datapoints') + @Property(str, notify=infoChanged) def lastSyncTime(self): cache = load_repo_cache() - raw = cache.get('sync_time', 'Never') if cache else 'Never' - if raw == 'Never': - return raw + raw = cache.get('sync_time') if cache else None + if not raw: + return "" try: from datetime import datetime @@ -482,6 +491,10 @@ class Backend(QObject): def spoofArchIndex(self): return self._spoof_arch_idx + @Property(str, notify=debugChanged) + def spoofLibVer(self): + return self._spoof_lib_ver + @Property(int, notify=currentRepoChanged) def currentRepoIndex(self): if self._current_repo: @@ -661,7 +674,8 @@ class Backend(QObject): def sync(self): spoof_os = OS_OPTIONS[self._spoof_os_idx] if self._spoof_os_idx > 0 else None spoof_arch = ARCH_OPTIONS[self._spoof_arch_idx] if self._spoof_arch_idx > 0 else None - self._run_worker(SyncWorker(spoof_os, spoof_arch), self._on_done) + spoof_lib = self._spoof_lib_ver or None + self._run_worker(SyncWorker(spoof_os, spoof_arch, spoof_lib), self._on_done) @Slot() def cancelOperation(self): @@ -727,6 +741,11 @@ class Backend(QObject): self._spoof_arch_idx = index self.debugChanged.emit() + @Slot(str) + def setSpoofLibVer(self, ver): + self._spoof_lib_ver = ver.strip() + self.debugChanged.emit() + @Slot() def openGeoipFolder(self): from ..geolocation import MMDB_DIR @@ -836,10 +855,13 @@ class Backend(QObject): return spoof_os = cache.get('spoof_os') spoof_arch = cache.get('spoof_arch') + spoof_lib = cache.get('spoof_lib_ver') if spoof_os and spoof_os in OS_OPTIONS: self._spoof_os_idx = OS_OPTIONS.index(spoof_os) if spoof_arch and spoof_arch in ARCH_OPTIONS: self._spoof_arch_idx = ARCH_OPTIONS.index(spoof_arch) + if spoof_lib: + self._spoof_lib_ver = spoof_lib def _refresh(self): items = [] @@ -856,7 +878,6 @@ class Backend(QObject): versions = get_cached_versions(self._current_repo.name) if not versions: - self._set_status("No cache. Run Sync.", "#f39c12") self._version_model.set_items(items) return diff --git a/pythonlib/camoufox/gui/qml/main.qml b/pythonlib/camoufox/gui/qml/main.qml index b8e6f4b..0c661f0 100644 --- a/pythonlib/camoufox/gui/qml/main.qml +++ b/pythonlib/camoufox/gui/qml/main.qml @@ -519,60 +519,6 @@ ApplicationWindow { anchors.right: parent.right anchors.rightMargin: 1 - Rectangle { - width: parent.width - height: row - color: "transparent" - - Header { - anchors.left: parent.left - anchors.leftMargin: s3 - anchors.verticalCenter: parent.verticalCenter - text: "CHANNEL" - } - } - - Repeater { - model: backend.channels - - Rectangle { - width: parent ? parent.width : 0 - height: row + s2 - color: chMa.containsMouse ? c.raised : "transparent" - - Column { - anchors.left: parent.left - anchors.leftMargin: s4 - anchors.right: parent.right - anchors.rightMargin: s3 - anchors.verticalCenter: parent.verticalCenter - spacing: 1 - - Row { - spacing: s2 - Check { on: backend.followedChannel === backend.channelKeys[index]; anchors.verticalCenter: parent.verticalCenter } - T { text: (backend.followedChannel === backend.channelKeys[index] ? "Following " : "Follow ") + modelData; anchors.verticalCenter: parent.verticalCenter } - } - - Muted { - leftPadding: s3 + s2 - text: backend.channelLatest[index] ? ("(latest: " + backend.channelLatest[index] + ")") : "(sync first)" - font.pixelSize: Math.round(10 * scale) - } - } - - MouseArea { - id: chMa - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: backend.setFollowedChannel(index) - } - } - } - - Rule {} - Rectangle { width: parent.width height: row @@ -612,6 +558,60 @@ ApplicationWindow { Component.onCompleted: backend.selectRepo(0) } + + Rule {} + + Rectangle { + width: parent.width + height: row + color: "transparent" + + Header { + anchors.left: parent.left + anchors.leftMargin: s3 + anchors.verticalCenter: parent.verticalCenter + text: "FOLLOW CHANNEL" + } + } + + Repeater { + model: backend.channels + + Rectangle { + width: parent ? parent.width : 0 + height: row + s2 + color: chMa.containsMouse ? c.raised : "transparent" + + Column { + anchors.left: parent.left + anchors.leftMargin: s4 + anchors.right: parent.right + anchors.rightMargin: s3 + anchors.verticalCenter: parent.verticalCenter + spacing: 1 + + Row { + spacing: s2 + Check { on: backend.followedChannel === backend.channelKeys[index]; anchors.verticalCenter: parent.verticalCenter } + T { text: (backend.followedChannel === backend.channelKeys[index] ? "Following " : "Follow ") + modelData; anchors.verticalCenter: parent.verticalCenter } + } + + Muted { + leftPadding: s3 + s2 + text: backend.channelLatest[index] ? ("Latest: " + backend.channelLatest[index]) : "(sync first)" + font.pixelSize: Math.round(10 * scale) + } + } + + MouseArea { + id: chMa + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: backend.setFollowedChannel(index) + } + } + } } } @@ -678,7 +678,11 @@ ApplicationWindow { clip: true model: backend.versionModel + property real scrollBarWidth: vScrollBar.visible ? vScrollBar.width : 0 + ScrollBar.vertical: ScrollBar { + id: vScrollBar + policy: vList.contentHeight > vList.height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff contentItem: Rectangle { implicitWidth: s2 radius: s1 @@ -690,7 +694,7 @@ ApplicationWindow { id: vrow property bool hov: hover.hovered - width: vList.width + width: vList.width - vList.scrollBarWidth height: row color: model.isHeader ? c.fg : model.isActive ? Qt.rgba(c.accent.r, c.accent.g, c.accent.b, 0.06) : @@ -1101,7 +1105,7 @@ ApplicationWindow { columnSpacing: s4 * 2 rowSpacing: s2 - Muted { text: "Active Browser" } + Muted { text: backend.activeBrowserLabel } Bold { text: backend.activeBrowserText; color: backend.activeBrowserColor } Muted { text: "Python Library" } @@ -1113,11 +1117,11 @@ ApplicationWindow { Muted { text: "Browserforge" } T { text: backend.browserforgeVersion } - Muted { text: "Last Sync" } - T { text: backend.lastSyncTime } + Muted { text: "Fingerprints" } + T { text: backend.fingerprintVersion } - Muted { text: "Repos" } - T { text: backend.reposInfo } + Muted { text: "Last Sync" } + T { text: backend.lastSyncTime || "Never" } Muted { text: "Website" } T { @@ -1156,13 +1160,38 @@ ApplicationWindow { implicitWidth: Math.round(90 * scale) } } + + Row { + spacing: s2 + Muted { text: "Lib Version"; anchors.verticalCenter: parent.verticalCenter } + Rectangle { + width: Math.round(80 * scale) + height: Math.round(24 * scale) + color: "#1affffff" + radius: Math.round(3 * scale) + TextInput { + anchors.fill: parent + anchors.margins: Math.round(4 * scale) + color: "#fff" + font.pixelSize: Math.round(11 * scale) + text: backend.spoofLibVer + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + property string placeholder: "(auto)" + Text { + text: parent.placeholder + color: "#80ffffff" + font: parent.font + visible: !parent.text && !parent.activeFocus + anchors.verticalCenter: parent.verticalCenter + } + onEditingFinished: backend.setSpoofLibVer(text) + } + } + } } - Muted { - visible: debugMode - text: "Set spoof options then Sync on Browsers tab" - font.pixelSize: Math.round(10 * scale) - } Rule { visible: debugMode } @@ -1238,14 +1267,14 @@ ApplicationWindow { Btn { icon: "\uE895" - text: "Sync Upstream" + text: "Sync Repos" accent: c.accent on: !backend.busy onClicked: backend.sync() } Muted { - text: "Synced: " + backend.lastSyncTime + text: backend.lastSyncTime ? "Synced: " + backend.lastSyncTime : "Sync has not been ran yet." visible: !backend.busy } diff --git a/pythonlib/camoufox/multiversion.py b/pythonlib/camoufox/multiversion.py index d056476..9df273d 100644 --- a/pythonlib/camoufox/multiversion.py +++ b/pythonlib/camoufox/multiversion.py @@ -35,6 +35,15 @@ def load_config() -> Dict: return {} +def get_default_channel() -> str: + """ + Get the default repo's stable channel string (like official/stable) + """ + from .pkgman import RepoConfig + + return f"{RepoConfig.get_default_name().lower()}/stable" + + def save_config(config: Dict) -> None: """ Save user config to disk diff --git a/pythonlib/camoufox/pkgman.py b/pythonlib/camoufox/pkgman.py index 82f7aa6..b58b27c 100644 --- a/pythonlib/camoufox/pkgman.py +++ b/pythonlib/camoufox/pkgman.py @@ -144,14 +144,14 @@ class RepoConfig: build_max: Optional[str] = None @staticmethod - def load_repos() -> List['RepoConfig']: + def load_repos(spoof_library_version: Optional[str] = None) -> List['RepoConfig']: """ Load repository configurations from repos.yml """ repos_path = LOCAL_DATA / 'repos.yml' with open(repos_path, 'r') as f: data = load(f, Loader=CLoader) - return [RepoConfig.from_dict(r) for r in data.get('browsers', [])] + return [RepoConfig.from_dict(r, spoof_library_version) for r in data.get('browsers', [])] @staticmethod def get_default_name() -> str: @@ -164,7 +164,7 @@ class RepoConfig: return data.get('default', {}).get('browser', 'Official') @staticmethod - def from_dict(d: Dict) -> 'RepoConfig': + def from_dict(d: Dict, spoof_library_version: Optional[str] = None) -> 'RepoConfig': """ Create RepoConfig from dictionary """ @@ -174,7 +174,7 @@ class RepoConfig: build_min: Optional[str] = None build_max: Optional[str] = None if d.get('versions'): - library_version = _get_library_version() + library_version = spoof_library_version or _get_library_version() browser = _find_version_constraints(d['versions'], library_version) if browser: build_min = browser.get('min')