diff --git a/pythonlib/camoufox/__version__.py b/pythonlib/camoufox/__version__.py index 71689e1..9f4915f 100644 --- a/pythonlib/camoufox/__version__.py +++ b/pythonlib/camoufox/__version__.py @@ -8,7 +8,7 @@ class CONSTRAINTS: The minimum and maximum supported versions of the Camoufox browser. """ - MIN_VERSION = 'beta.12' + MIN_VERSION = 'beta.15' MAX_VERSION = '1' @staticmethod diff --git a/pythonlib/camoufox/exceptions.py b/pythonlib/camoufox/exceptions.py index cc05f7d..4f3df43 100644 --- a/pythonlib/camoufox/exceptions.py +++ b/pythonlib/camoufox/exceptions.py @@ -178,3 +178,11 @@ class VirtualDisplayNotSupported(VirtualDisplayError): """ ... + + +class CamoufoxNotInstalled(FileNotFoundError): + """ + Raised when camoufox is not installed. + """ + + ... diff --git a/pythonlib/camoufox/pkgman.py b/pythonlib/camoufox/pkgman.py index 46ba77a..9210ebd 100644 --- a/pythonlib/camoufox/pkgman.py +++ b/pythonlib/camoufox/pkgman.py @@ -21,7 +21,12 @@ from typing_extensions import TypeAlias from yaml import CLoader, load from .__version__ import CONSTRAINTS -from .exceptions import UnsupportedArchitecture, UnsupportedOS, UnsupportedVersion +from .exceptions import ( + CamoufoxNotInstalled, + UnsupportedArchitecture, + UnsupportedOS, + UnsupportedVersion, +) DownloadBuffer: TypeAlias = Union[BytesIO, tempfile._TemporaryFileWrapper, BufferedWriter] @@ -55,6 +60,13 @@ OS_ARCH_MATRIX: Dict[str, List[str]] = { 'lin': ['x86_64', 'arm64', 'i686'], } +# The relative path to the camoufox executable +LAUNCH_FILE = { + 'win': 'camoufox.exe', + 'lin': 'camoufox-bin', + 'mac': '../MacOS/camoufox', +} + def rprint(*a, **k): click.secho(*a, **k, bold=True) @@ -102,7 +114,7 @@ class Version: if not os.path.exists(version_path): raise FileNotFoundError( f"Version information not found at {version_path}. " - "You are likely using an unsupported version of Camoufox." + "Please run `camoufox fetch` to install." ) with open(version_path, 'rb') as f: version_data = orjson.loads(f.read()) @@ -358,14 +370,16 @@ def camoufox_path(download_if_missing: bool = True) -> Path: """ Full path to the camoufox folder. """ - if os.path.exists(INSTALL_DIR) and Version.from_path().is_supported(): - return INSTALL_DIR - # Ensure the directory exists - if not os.path.exists(INSTALL_DIR): + # Ensure the directory exists and is not empty + if not os.path.exists(INSTALL_DIR) or not os.listdir(INSTALL_DIR): if not download_if_missing: raise FileNotFoundError(f"Camoufox executable not found at {INSTALL_DIR}") + # Camoufox exists and the the version is supported + elif os.path.exists(INSTALL_DIR) and Version.from_path().is_supported(): + return INSTALL_DIR + # Ensure the version is supported else: if not download_if_missing: @@ -385,6 +399,19 @@ def get_path(file: str) -> str: return str(camoufox_path() / file) +def launch_path() -> str: + """ + Get the path to the camoufox executable. + """ + launch_path = get_path(LAUNCH_FILE[OS_NAME]) + if not os.path.exists(launch_path): + # Not installed error + raise CamoufoxNotInstalled( + f"Camoufox is not installed at {camoufox_path()}. Please run `camoufox fetch` to install." + ) + return launch_path + + def webdl( url: str, desc: Optional[str] = None, diff --git a/pythonlib/camoufox/utils.py b/pythonlib/camoufox/utils.py index 91a297b..e4e3936 100644 --- a/pythonlib/camoufox/utils.py +++ b/pythonlib/camoufox/utils.py @@ -4,7 +4,7 @@ from os import environ from os.path import abspath from pathlib import Path from pprint import pprint -from random import randrange +from random import randint, randrange from typing import Any, Dict, List, Literal, Optional, Tuple, Union, cast import numpy as np @@ -29,19 +29,22 @@ from .exceptions import ( from .fingerprints import from_browserforge, generate_fingerprint from .ip import Proxy, public_ip, valid_ipv4, valid_ipv6 from .locale import geoip_allowed, get_geolocation, handle_locales -from .pkgman import OS_NAME, get_path, installed_verstr +from .pkgman import OS_NAME, get_path, installed_verstr, launch_path from .virtdisplay import VirtualDisplay from .warnings import LeakWarning from .xpi_dl import add_default_addons -LAUNCH_FILE = { - 'win': 'camoufox.exe', - 'lin': 'camoufox-bin', - 'mac': '../MacOS/camoufox', -} - ListOrString: TypeAlias = Union[Tuple[str, ...], List[str], str] +# Camoufox preferences to cache previous pages and requests +CACHE_PREFS = { + 'browser.sessionhistory.max_entries': 10, + 'browser.sessionhistory.max_total_viewers': -1, + 'browser.cache.memory.enable': True, + 'browser.cache.disk_cache_ssl': True, + 'browser.cache.disk.smart_size.enabled': True, +} + def get_env_vars( config_map: Dict[str, str], user_agent_os: str @@ -354,9 +357,10 @@ def launch_options( fingerprint: Optional[Fingerprint] = None, ff_version: Optional[int] = None, headless: Optional[bool] = None, - executable_path: Optional[str] = None, + executable_path: Optional[Union[str, Path]] = None, firefox_user_prefs: Optional[Dict[str, Any]] = None, proxy: Optional[Dict[str, str]] = None, + enable_cache: Optional[bool] = None, args: Optional[List[str]] = None, env: Optional[Dict[str, Union[str, float, bool]]] = None, i_know_what_im_doing: Optional[bool] = None, @@ -413,13 +417,15 @@ def launch_options( Whether to run the browser in headless mode. Defaults to False. Note: If you are running linux, passing headless='virtual' to Camoufox & AsyncCamoufox will use Xvfb. - executable_path (Optional[str]): + executable_path (Optional[Union[str, Path]]): Custom Camoufox browser executable path. firefox_user_prefs (Optional[Dict[str, Any]]): Firefox user preferences to set. proxy (Optional[Dict[str, str]]): Proxy to use for the browser. Note: If geoip is True, a request will be sent through this proxy to find the target IP. + enable_cache (Optional[bool]): + Cache previous pages, requests, etc (uses more memory). args (Optional[List[str]]): Arguments to pass to the browser. env (Optional[Dict[str, Union[str, float, bool]]]): @@ -449,6 +455,7 @@ def launch_options( if env is None: env = cast(Dict[str, Union[str, float, bool]], environ) if isinstance(executable_path, str): + # Convert executable path to a Path object executable_path = Path(abspath(executable_path)) # Handle virtual display @@ -504,6 +511,8 @@ def launch_options( if fonts: config['fonts'] = fonts update_fonts(config, target_os) + # Set a fixed font spacing seed + set_into(config, 'fonts:spacing_seed', randint(0, 2147483647)) # nosec # Set geolocation if geoip: @@ -563,6 +572,10 @@ def launch_options( LeakWarning.warn('allow_webgl', i_know_what_im_doing) firefox_user_prefs['webgl.disabled'] = False + # Cache previous pages, requests, etc (uses more memory) + if enable_cache: + firefox_user_prefs.update(CACHE_PREFS) + # Load the addons threaded_try_load_addons(get_debug_port(args), addons) @@ -571,8 +584,14 @@ def launch_options( **get_env_vars(config, target_os), **env, } + # Prepare the executable path + if executable_path: + executable_path = str(executable_path) + else: + executable_path = launch_path() + return { - "executable_path": executable_path or get_path(LAUNCH_FILE[OS_NAME]), + "executable_path": executable_path, "args": args, "env": env_vars, "firefox_user_prefs": firefox_user_prefs, diff --git a/pythonlib/pyproject.toml b/pythonlib/pyproject.toml index cb9f27d..5db599b 100644 --- a/pythonlib/pyproject.toml +++ b/pythonlib/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "camoufox" -version = "0.3.8" +version = "0.3.9" description = "Wrapper around Playwright to help launch Camoufox" authors = ["daijro "] license = "MIT"