mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-04-11 09:02:03 -07:00
feat: Implement specified version installation feature
This commit is contained in:
parent
2a250720a6
commit
d471ce607b
2 changed files with 157 additions and 35 deletions
|
|
@ -27,11 +27,11 @@ class CamoufoxUpdate(CamoufoxFetcher):
|
||||||
Checks & updates Camoufox
|
Checks & updates Camoufox
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, specified_version: Optional[str] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Initializes the CamoufoxUpdate class
|
Initializes the CamoufoxUpdate class
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(specified_version=specified_version)
|
||||||
self.current_verstr: Optional[str]
|
self.current_verstr: Optional[str]
|
||||||
try:
|
try:
|
||||||
self.current_verstr = installed_verstr()
|
self.current_verstr = installed_verstr()
|
||||||
|
|
@ -53,7 +53,10 @@ class CamoufoxUpdate(CamoufoxFetcher):
|
||||||
"""
|
"""
|
||||||
# Check if the version is the same as the latest available version
|
# Check if the version is the same as the latest available version
|
||||||
if not self.is_updated_needed():
|
if not self.is_updated_needed():
|
||||||
rprint("Camoufox binaries up to date!", fg="green")
|
if not self.specified_version:
|
||||||
|
rprint("Camoufox binaries up to date!", fg="green")
|
||||||
|
else:
|
||||||
|
rprint("Target Camoufox binaries already installed!", fg="green")
|
||||||
rprint(f"Current version: v{self.current_verstr}", fg="green")
|
rprint(f"Current version: v{self.current_verstr}", fg="green")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -77,13 +80,18 @@ def cli() -> None:
|
||||||
|
|
||||||
@cli.command(name='fetch')
|
@cli.command(name='fetch')
|
||||||
@click.option(
|
@click.option(
|
||||||
'--browserforge', is_flag=True, help='Update browserforge\'s header and fingerprint definitions'
|
'--browserforge', is_flag=True,
|
||||||
|
help='Update browserforge\'s header and fingerprint definitions'
|
||||||
)
|
)
|
||||||
def fetch(browserforge=False) -> None:
|
@click.option(
|
||||||
|
'--version', type=str, default=None,
|
||||||
|
help='Download a specific release version instead of the latest'
|
||||||
|
)
|
||||||
|
def fetch(browserforge: bool, version: Optional[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Fetch the latest version of Camoufox and optionally update Browserforge's database
|
Fetch the latest or specified version of Camoufox.
|
||||||
"""
|
"""
|
||||||
CamoufoxUpdate().update()
|
CamoufoxUpdate(specified_version=version).update()
|
||||||
# Fetch the GeoIP database
|
# Fetch the GeoIP database
|
||||||
if ALLOW_GEOIP:
|
if ALLOW_GEOIP:
|
||||||
download_mmdb()
|
download_mmdb()
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from dataclasses import dataclass
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from io import BufferedWriter, BytesIO
|
from io import BufferedWriter, BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union, TypedDict
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
@ -137,6 +137,44 @@ class Version:
|
||||||
VERSION_MIN, VERSION_MAX = Version.build_minmax()
|
VERSION_MIN, VERSION_MAX = Version.build_minmax()
|
||||||
|
|
||||||
|
|
||||||
|
class GithubRelease(TypedDict):
|
||||||
|
id: int
|
||||||
|
noded_id: str
|
||||||
|
name: str
|
||||||
|
tag_name: str
|
||||||
|
author: Dict
|
||||||
|
target_commitish: str
|
||||||
|
draft: bool
|
||||||
|
prerelease: bool
|
||||||
|
created_at: str
|
||||||
|
published_at: str
|
||||||
|
assets: List[Dict]
|
||||||
|
url: str
|
||||||
|
assets_url: str
|
||||||
|
upload_url: str
|
||||||
|
html_url: str
|
||||||
|
tarball_url: str
|
||||||
|
zipball_url: str
|
||||||
|
body: str
|
||||||
|
reactions: Dict
|
||||||
|
|
||||||
|
|
||||||
|
class GithubAsset(TypedDict):
|
||||||
|
id: int
|
||||||
|
node_id: str
|
||||||
|
name: str
|
||||||
|
label: str
|
||||||
|
uploader: dict
|
||||||
|
browser_download_url: str
|
||||||
|
content_type: str
|
||||||
|
state: str
|
||||||
|
size: int
|
||||||
|
download_count: int
|
||||||
|
created_at: str
|
||||||
|
updated_at: str
|
||||||
|
browser_download_url: str
|
||||||
|
|
||||||
|
|
||||||
class GitHubDownloader:
|
class GitHubDownloader:
|
||||||
"""
|
"""
|
||||||
Manages fetching and installing GitHub releases.
|
Manages fetching and installing GitHub releases.
|
||||||
|
|
@ -146,17 +184,40 @@ class GitHubDownloader:
|
||||||
self.github_repo = github_repo
|
self.github_repo = github_repo
|
||||||
self.api_url = f"https://api.github.com/repos/{github_repo}/releases"
|
self.api_url = f"https://api.github.com/repos/{github_repo}/releases"
|
||||||
|
|
||||||
def check_asset(self, asset: Dict) -> Any:
|
def fetch_all_releases(self, per_page: int = 100) -> List[GithubRelease]:
|
||||||
|
"""
|
||||||
|
Internal function to iterate through GitHub release pages.
|
||||||
|
"""
|
||||||
|
releases_all = []
|
||||||
|
page = 1
|
||||||
|
while True:
|
||||||
|
url = f"{self.api_url}?page={page}&per_page={per_page}"
|
||||||
|
resp = requests.get(url, timeout=20)
|
||||||
|
resp.raise_for_status()
|
||||||
|
releases_page = resp.json()
|
||||||
|
if not releases_page:
|
||||||
|
break
|
||||||
|
releases_all.extend(releases_page)
|
||||||
|
page += 1
|
||||||
|
return releases_all
|
||||||
|
|
||||||
|
def _default_predicate(self, asset: GithubAsset) -> str:
|
||||||
|
return asset.get('browser_download_url')
|
||||||
|
|
||||||
|
def check_asset(
|
||||||
|
self,
|
||||||
|
asset: Dict,
|
||||||
|
predicate: Optional[Callable[[GithubAsset], Optional[Tuple[Version, str]]]] = None
|
||||||
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Compare the asset to determine if it's the desired asset.
|
Compare the asset to determine if it's the desired asset.
|
||||||
|
If predicate is provided, it is applied to the asset; otherwise,
|
||||||
Args:
|
the default predicate is used.
|
||||||
asset: Asset information from GitHub API
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Any: Data to be returned if this is the desired asset, or None/False if not
|
|
||||||
"""
|
"""
|
||||||
return asset.get('browser_download_url')
|
|
||||||
|
if predicate is None:
|
||||||
|
predicate = self._default_predicate
|
||||||
|
return predicate(asset)
|
||||||
|
|
||||||
def missing_asset_error(self) -> None:
|
def missing_asset_error(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -164,19 +225,24 @@ class GitHubDownloader:
|
||||||
"""
|
"""
|
||||||
raise MissingRelease(f"Could not find a release asset in {self.github_repo}.")
|
raise MissingRelease(f"Could not find a release asset in {self.github_repo}.")
|
||||||
|
|
||||||
def get_asset(self) -> Any:
|
def get_asset(
|
||||||
|
self,
|
||||||
|
predicate: Optional[Callable[[GithubAsset], Optional[str]]] = None
|
||||||
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Fetch the latest release from the GitHub API.
|
Fetch the latest release from the GitHub API.
|
||||||
Gets the first asset that returns a truthy value from check_asset.
|
Iterates over all pages and returns the first asset for which
|
||||||
|
check_asset (with the predicate) returns a truthy value.
|
||||||
"""
|
"""
|
||||||
resp = requests.get(self.api_url, timeout=20)
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
releases = resp.json()
|
if predicate is None:
|
||||||
|
predicate = self._default_predicate
|
||||||
|
|
||||||
|
# Search through releases for the first supported version
|
||||||
|
releases = self.fetch_all_releases()
|
||||||
for release in releases:
|
for release in releases:
|
||||||
for asset in release['assets']:
|
for asset in release.get('assets', []):
|
||||||
if data := self.check_asset(asset):
|
if data := self.check_asset(asset, predicate=predicate):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
self.missing_asset_error()
|
self.missing_asset_error()
|
||||||
|
|
@ -187,26 +253,21 @@ class CamoufoxFetcher(GitHubDownloader):
|
||||||
Handles fetching and installing the latest version of Camoufox.
|
Handles fetching and installing the latest version of Camoufox.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, specified_version: Optional[str] = None) -> None:
|
||||||
super().__init__("daijro/camoufox")
|
super().__init__("daijro/camoufox")
|
||||||
|
self.specified_version = specified_version
|
||||||
self.arch = self.get_platform_arch()
|
self.arch = self.get_platform_arch()
|
||||||
self._version_obj: Optional[Version] = None
|
self._version_obj: Optional[Version] = None
|
||||||
self.pattern: re.Pattern = re.compile(
|
self.pattern: re.Pattern = re.compile(
|
||||||
rf'camoufox-(?P<version>.+)-(?P<release>.+)-{OS_NAME}\.{self.arch}\.zip'
|
rf'camoufox-(?P<version>.+)-(?P<release>.+)-{OS_NAME}\.{self.arch}\.zip'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fetch_latest()
|
if self.specified_version:
|
||||||
|
self.fetch_specific(self.specified_version)
|
||||||
|
else:
|
||||||
|
self.fetch_latest()
|
||||||
|
|
||||||
def check_asset(self, asset: Dict) -> Optional[Tuple[Version, str]]:
|
def _default_predicate(self, asset: Dict) -> Optional[Tuple[Version, str]]:
|
||||||
"""
|
|
||||||
Finds the latest release from a GitHub releases API response that
|
|
||||||
supports the Camoufox version constraints, the OS, and architecture.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional[Tuple[Version, str]]: The version and URL of a release
|
|
||||||
"""
|
|
||||||
# Search through releases for the first supported version
|
|
||||||
match = self.pattern.match(asset['name'])
|
match = self.pattern.match(asset['name'])
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
|
|
@ -219,6 +280,22 @@ class CamoufoxFetcher(GitHubDownloader):
|
||||||
# Asset was found. Return data
|
# Asset was found. Return data
|
||||||
return version, asset['browser_download_url']
|
return version, asset['browser_download_url']
|
||||||
|
|
||||||
|
def check_asset(
|
||||||
|
self,
|
||||||
|
asset: Dict,
|
||||||
|
predicate: Optional[Callable[[Dict], Optional[Tuple[Version, str]]]] = None
|
||||||
|
) -> Optional[Tuple[Version, str]]:
|
||||||
|
"""
|
||||||
|
Finds the latest or specified release from a GitHub releases API response that
|
||||||
|
supports the Camoufox version constraints, the OS, and architecture.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Tuple[Version, str]]: The version and URL of a release
|
||||||
|
"""
|
||||||
|
|
||||||
|
if checked_result := super().check_asset(asset, predicate):
|
||||||
|
return checked_result
|
||||||
|
|
||||||
def missing_asset_error(self) -> None:
|
def missing_asset_error(self) -> None:
|
||||||
"""
|
"""
|
||||||
Raise a MissingRelease exception if no release is found.
|
Raise a MissingRelease exception if no release is found.
|
||||||
|
|
@ -254,6 +331,15 @@ class CamoufoxFetcher(GitHubDownloader):
|
||||||
|
|
||||||
return arch
|
return arch
|
||||||
|
|
||||||
|
def convert_asset_to_version(self, asset: GithubAsset) -> Version:
|
||||||
|
"""
|
||||||
|
Convert an github release asset info to a Version object.
|
||||||
|
"""
|
||||||
|
match = self.pattern.match(asset['name'])
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f"Invalid asset name: {asset['name']}")
|
||||||
|
return Version(release=match['release'], version=match['version'])
|
||||||
|
|
||||||
def fetch_latest(self) -> None:
|
def fetch_latest(self) -> None:
|
||||||
"""
|
"""
|
||||||
Fetch the URL of the latest camoufox release for the current platform.
|
Fetch the URL of the latest camoufox release for the current platform.
|
||||||
|
|
@ -268,6 +354,34 @@ class CamoufoxFetcher(GitHubDownloader):
|
||||||
# Set the version and URL
|
# Set the version and URL
|
||||||
self._version_obj, self._url = release_data
|
self._version_obj, self._url = release_data
|
||||||
|
|
||||||
|
def fetch_specific(self, version: str) -> None:
|
||||||
|
"""
|
||||||
|
Fetch the URL of a specific camoufox release for the current platform.
|
||||||
|
Sets the version, release, and url properties.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version (str): The version to fetch
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
requests.RequestException: If there's an error fetching release data
|
||||||
|
ValueError: If no matching release is found for the current platform
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _find_specific_version_predicate(asset: Dict) -> Optional[tuple[Version, str]]:
|
||||||
|
try:
|
||||||
|
candidate_version = self.convert_asset_to_version(asset)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if candidate_version.full_string == version:
|
||||||
|
return candidate_version, asset['browser_download_url']
|
||||||
|
return None
|
||||||
|
|
||||||
|
# get_asset will raise a MissingRelease exception if no release is found
|
||||||
|
specific_version, download_url = self.get_asset(_find_specific_version_predicate)
|
||||||
|
self._version_obj, self._url = specific_version, download_url
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def download_file(file: DownloadBuffer, url: str) -> DownloadBuffer:
|
def download_file(file: DownloadBuffer, url: str) -> DownloadBuffer:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue