From 2d62ef0a6d709f0ae89df5ed2da1812dd4b2af4e Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Tue, 14 Apr 2026 19:22:43 +0100 Subject: [PATCH] Add timezone and locale parameters to generate_context_fingerprint() (#563) Allow callers to inject pre-resolved timezone/locale into the fingerprint before init_script generation. When provided, these take priority over preset data. In launch_options(), use setdefault for geoip timezone/locale keys so that values pre-set via generate_context_fingerprint() aren't overwritten by geoip resolution. Co-authored-by: Claude Opus 4.6 (1M context) --- pythonlib/camoufox/fingerprints.py | 25 +++++++++++++++++++++++++ pythonlib/camoufox/utils.py | 7 ++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pythonlib/camoufox/fingerprints.py b/pythonlib/camoufox/fingerprints.py index a6a95b8..bc2467e 100644 --- a/pythonlib/camoufox/fingerprints.py +++ b/pythonlib/camoufox/fingerprints.py @@ -420,6 +420,8 @@ def generate_context_fingerprint( os: Optional[str] = None, ff_version: Optional[str] = None, webrtc_ip: Optional[str] = None, + timezone: Optional[str] = None, + locale: Optional[str] = None, ) -> Dict[str, Any]: """ Generate fingerprint values for a single per-context identity. @@ -427,6 +429,14 @@ def generate_context_fingerprint( By default, uses BrowserForge for infinite unique synthetic fingerprints. Pass a preset dict to use a real fingerprint preset instead. + + Parameters: + timezone: IANA timezone string (e.g. 'Europe/London'). When provided, + injected into config before init_script generation. Takes priority + over any timezone from the preset. + locale: BCP-47 locale string (e.g. 'en-GB'). When provided, parsed via + normalize_locale() and injected into config. Also sets + context_options['locale'] for Playwright. """ if preset is not None: # Use real fingerprint preset @@ -512,6 +522,18 @@ def generate_context_fingerprint( } preset = {'navigator': nav, 'screen': screen, 'webgl': webgl} + # Inject explicit timezone/locale into config (takes priority over preset) + if timezone: + config['timezone'] = timezone + if locale: + from .locales import normalize_locale + parsed = normalize_locale(locale) + config['locale:language'] = parsed.language + config['locale:region'] = parsed.region + config['navigator.language'] = parsed.as_string + if parsed.script: + config['locale:script'] = parsed.script + # Build the values dict for the init script (works for both paths) init_values: Dict[str, Any] = { 'fontSpacingSeed': config.get('fonts:spacing_seed'), @@ -554,6 +576,9 @@ def generate_context_fingerprint( tz = preset.get('timezone') if tz: context_options['timezone_id'] = tz + nav_lang = config.get('navigator.language') + if nav_lang: + context_options['locale'] = nav_lang return { 'init_script': init_script, diff --git a/pythonlib/camoufox/utils.py b/pythonlib/camoufox/utils.py index 0277516..361e34e 100644 --- a/pythonlib/camoufox/utils.py +++ b/pythonlib/camoufox/utils.py @@ -627,7 +627,12 @@ def launch_options( set_into(config, 'webrtc:ipv6', geoip) geolocation = get_geolocation(geoip, geoip_db=geoip_db) - config.update(geolocation.as_config()) + geo_config = geolocation.as_config() + for key, value in geo_config.items(): + if key in ('timezone', 'locale:language', 'locale:region', 'locale:script'): + config.setdefault(key, value) + else: + config[key] = value # Raise a warning when a proxy is being used without spoofing geolocation. # This is a very bad idea; the warning cannot be ignored with i_know_what_im_doing.