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) <noreply@anthropic.com>
This commit is contained in:
Ruben Vereecken
2026-04-14 19:22:43 +01:00
committed by GitHub
parent 873618e454
commit 2d62ef0a6d
2 changed files with 31 additions and 1 deletions
+25
View File
@@ -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,
+6 -1
View File
@@ -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.