camouchat-browser 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: camouchat-browser
3
+ Version: 0.7.0
4
+ Summary: Plugin for CamouChat providing anti-detection browser management via Camoufox and Playwright.
5
+ Keywords: browser-automation,stealth,camoufox,playwright,anti-detection,fingerprinting,browserforge
6
+ Author: BITS-Rohit
7
+ Author-email: BITS-Rohit <rohitguptaradheradhe123@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Natural Language :: English
19
+ Requires-Dist: browserforge>=1.2.4
20
+ Requires-Dist: camouchat-core
21
+ Requires-Dist: camoufox[geoip]>=0.4.11
22
+ Requires-Dist: platformdirs>=4.9.6
23
+ Requires-Dist: playwright>=1.58.0
24
+ Requires-Dist: pyobjc-framework-quartz ; sys_platform == 'darwin'
25
+ Maintainer: BITS-Rohit
26
+ Maintainer-email: BITS-Rohit <rohitguptaradheradhe123@gmail.com>
27
+ Requires-Python: >=3.14
28
+ Project-URL: Homepage, https://github.com/CamouChat-Team
29
+ Project-URL: Repository, https://github.com/CamouChat-Team/camouchat-browser
30
+ Project-URL: Bug Tracker, https://github.com/CamouChat-Team/camouchat-browser/issues
31
+ Project-URL: Changelog, https://github.com/CamouChat-Team/camouchat-browser/releases
32
+ Description-Content-Type: text/markdown
33
+
34
+ # CamouChat Browser 🦊
35
+
36
+ A high-performance, stealth-oriented browser orchestration layer designed for the CamouChat ecosystem. Built on top of [Camoufox](https://camoufox.com/), it provides advanced anti-detection capabilities, hardware-level fingerprint spoofing, and robust session management.
37
+
38
+ ## Key Features
39
+
40
+ - **Advanced Stealth**: Powered by [Camoufox](https://camoufox.com/) for industry-leading anti-bot bypass.
41
+ - **Fingerprint Spoofing**: Deep integration with `browserforge` for authentic hardware and software headers.
42
+ - **Session Isolation**: Automated profile management with persistent storage for cookies and local data.
43
+ - **GeoIP & Proxy Ready**: Built-in support for residential proxies and automated GeoIP matching.
44
+ - **Humanized Interaction**: Smooth mouse movements and typing patterns to maintain high stealth scores.
45
+
46
+ ## Installation
47
+
48
+ Add to your project using `uv`:
49
+
50
+ ```bash
51
+ uv add camouchat-browser
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ```python
57
+ from camouchat_browser import BrowserConfig, CamoufoxBrowser, ProfileManager, BrowserForge
58
+ from camouchat_core import Platform
59
+
60
+ # 1. Setup Configuration
61
+ config = BrowserConfig.from_dict({
62
+ "platform": Platform.WHATSAPP,
63
+ "headless": False,
64
+ "locale": "en-US",
65
+ "fingerprint_obj": BrowserForge()
66
+ })
67
+
68
+ # 2. Manage Profiles
69
+ pm = ProfileManager(platform=Platform.WHATSAPP)
70
+ profile = pm.get_profile(profile_id="stealth_user_1")
71
+
72
+ # 3. Launch Browser
73
+ browser = CamoufoxBrowser(config=config, profile=profile)
74
+ page = await browser.get_page()
75
+
76
+ await page.goto("https://check.camoufox.com")
77
+ ```
78
+
79
+ ## Documentation
80
+
81
+ - [Browser Configuration](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/browser_config.md) - Finetuning stealth settings.
82
+ - [Browser Engine](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/camoufox_browser.md) - Deep dive into Camoufox integration.
83
+ - [Profile Management](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/profiles.md) - Managing sandboxes and encryption.
84
+ - [Fingerprinting](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/BrowserForge.md) - hardware-level spoofing logic.
85
+
86
+ ## Roadmap
87
+
88
+ - 🐳 **Docker Containerization**: Full headless Docker image with Xvfb, Camoufox binaries, and proxy-routing pre-configured out of the box (Targeting v0.8.0).
89
+
90
+ ## ⚖️ Security & Ethics
91
+
92
+ CamouChat's strict policy regarding acceptable automation, anti-spam, and stealth disclaimers can be found in our central ecosystem hub:
93
+
94
+ 👉 **[SECURITY.md](https://github.com/CamouChat-Team/CamouChat/blob/main/SECURITY.md)**
95
+
96
+ ## License
97
+
98
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,65 @@
1
+ # CamouChat Browser 🦊
2
+
3
+ A high-performance, stealth-oriented browser orchestration layer designed for the CamouChat ecosystem. Built on top of [Camoufox](https://camoufox.com/), it provides advanced anti-detection capabilities, hardware-level fingerprint spoofing, and robust session management.
4
+
5
+ ## Key Features
6
+
7
+ - **Advanced Stealth**: Powered by [Camoufox](https://camoufox.com/) for industry-leading anti-bot bypass.
8
+ - **Fingerprint Spoofing**: Deep integration with `browserforge` for authentic hardware and software headers.
9
+ - **Session Isolation**: Automated profile management with persistent storage for cookies and local data.
10
+ - **GeoIP & Proxy Ready**: Built-in support for residential proxies and automated GeoIP matching.
11
+ - **Humanized Interaction**: Smooth mouse movements and typing patterns to maintain high stealth scores.
12
+
13
+ ## Installation
14
+
15
+ Add to your project using `uv`:
16
+
17
+ ```bash
18
+ uv add camouchat-browser
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from camouchat_browser import BrowserConfig, CamoufoxBrowser, ProfileManager, BrowserForge
25
+ from camouchat_core import Platform
26
+
27
+ # 1. Setup Configuration
28
+ config = BrowserConfig.from_dict({
29
+ "platform": Platform.WHATSAPP,
30
+ "headless": False,
31
+ "locale": "en-US",
32
+ "fingerprint_obj": BrowserForge()
33
+ })
34
+
35
+ # 2. Manage Profiles
36
+ pm = ProfileManager(platform=Platform.WHATSAPP)
37
+ profile = pm.get_profile(profile_id="stealth_user_1")
38
+
39
+ # 3. Launch Browser
40
+ browser = CamoufoxBrowser(config=config, profile=profile)
41
+ page = await browser.get_page()
42
+
43
+ await page.goto("https://check.camoufox.com")
44
+ ```
45
+
46
+ ## Documentation
47
+
48
+ - [Browser Configuration](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/browser_config.md) - Finetuning stealth settings.
49
+ - [Browser Engine](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/camoufox_browser.md) - Deep dive into Camoufox integration.
50
+ - [Profile Management](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/profiles.md) - Managing sandboxes and encryption.
51
+ - [Fingerprinting](https://github.com/CamouChat-Team/camouchat-browser/blob/main/docs/BrowserForge.md) - hardware-level spoofing logic.
52
+
53
+ ## Roadmap
54
+
55
+ - 🐳 **Docker Containerization**: Full headless Docker image with Xvfb, Camoufox binaries, and proxy-routing pre-configured out of the box (Targeting v0.8.0).
56
+
57
+ ## ⚖️ Security & Ethics
58
+
59
+ CamouChat's strict policy regarding acceptable automation, anti-spam, and stealth disclaimers can be found in our central ecosystem hub:
60
+
61
+ 👉 **[SECURITY.md](https://github.com/CamouChat-Team/CamouChat/blob/main/SECURITY.md)**
62
+
63
+ ## License
64
+
65
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "camouchat-browser"
3
+ version = "0.7.0"
4
+ description = "Plugin for CamouChat providing anti-detection browser management via Camoufox and Playwright."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "BITS-Rohit", email = "rohitguptaradheradhe123@gmail.com" }
8
+ ]
9
+ maintainers = [
10
+ { name = "BITS-Rohit", email = "rohitguptaradheradhe123@gmail.com" }
11
+ ]
12
+ requires-python = ">=3.14"
13
+ license = "MIT"
14
+ keywords = [
15
+ "browser-automation",
16
+ "stealth",
17
+ "camoufox",
18
+ "playwright",
19
+ "anti-detection",
20
+ "fingerprinting",
21
+ "browserforge"
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.14",
30
+ "Framework :: AsyncIO",
31
+ "License :: OSI Approved :: MIT License",
32
+ "Operating System :: OS Independent",
33
+ "Natural Language :: English",
34
+ ]
35
+ dependencies = [
36
+ "browserforge>=1.2.4",
37
+ "camouchat-core",
38
+ "camoufox[geoip]>=0.4.11",
39
+ "platformdirs>=4.9.6",
40
+ "playwright>=1.58.0",
41
+ "pyobjc-framework-Quartz; sys_platform == 'darwin'",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/CamouChat-Team"
46
+ Repository = "https://github.com/CamouChat-Team/camouchat-browser"
47
+ "Bug Tracker" = "https://github.com/CamouChat-Team/camouchat-browser/issues"
48
+ Changelog = "https://github.com/CamouChat-Team/camouchat-browser/releases"
49
+
50
+ [tool.deptry]
51
+ package_module_name_map = { "pyobjc-framework-Quartz" = "Quartz" }
52
+ per_rule_ignores = { DEP002 = "pyobjc-framework-Quartz" }
53
+
54
+ [build-system]
55
+ requires = ["uv_build>=0.10.7,<0.11.0"]
56
+ build-backend = "uv_build"
57
+ [tool.uv.sources]
58
+ camouchat-core = { workspace = true }
59
+
60
+ [dependency-groups]
61
+ dev = [
62
+ "black>=26.3.1",
63
+ "deptry>=0.25.1",
64
+ "mypy>=1.20.0",
65
+ "pytest>=9.0.3",
66
+ "pytest-asyncio>=1.3.0",
67
+ "pytest-cov>=7.1.0",
68
+ "pytest-mock>=3.15.1",
69
+ "ruff>=0.15.10",
70
+ "twine>=6.2.0",
71
+ ]
@@ -0,0 +1,27 @@
1
+ """
2
+ CamouChat Browser Plugin.
3
+
4
+ This package provides high-performance, stealth-oriented browser management
5
+ for the CamouChat ecosystem, leveraging Camoufox and BrowserForge for
6
+ advanced anti-detection and session isolation.
7
+ """
8
+
9
+ from .browser_config import BrowserConfig
10
+ from .browserforge import BrowserForge
11
+ from .camoufox_browser import CamoufoxBrowser
12
+ from .directory import DirectoryManager
13
+ from .exceptions import BrowserException
14
+ from .profile_info import ProfileInfo
15
+ from .profile_manager import ProfileManager
16
+
17
+ # Todo , adding logger later
18
+
19
+ __all__ = [
20
+ "BrowserForge",
21
+ "BrowserConfig",
22
+ "DirectoryManager",
23
+ "ProfileInfo",
24
+ "ProfileManager",
25
+ "CamoufoxBrowser",
26
+ "BrowserException",
27
+ ]
@@ -0,0 +1,99 @@
1
+ """
2
+ CamouBrowser Config.
3
+ Pass this config to the CamouBrowser
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import List, Dict, Optional
10
+
11
+ from camouchat_core import Platform
12
+
13
+ from .browserforge import BrowserForge
14
+
15
+
16
+ @dataclass
17
+ class BrowserConfig:
18
+ """
19
+ Config dataclass for browser.
20
+ """
21
+
22
+ platform: Platform
23
+ locale: str
24
+ enable_cache: bool
25
+ headless: bool
26
+ fingerprint_obj: BrowserForge
27
+ geoip: bool = True
28
+ proxy: Optional[Dict[str, str]] = None
29
+ prefs: Optional[Dict[str, bool]] = None
30
+ addons: List[str] = field(default_factory=list)
31
+
32
+ @classmethod
33
+ def from_dict(cls, data: Dict) -> BrowserConfig:
34
+ """
35
+ Creates a BrowserConfig instance from a dictionary.
36
+
37
+ Args:
38
+ data: Dictionary containing configuration parameters.
39
+ - platform: Target platform (e.g., Platform.WHATSAPP)
40
+ - locale: Browser locale (e.g., "en-US")
41
+ - enable_cache: Whether to use browser cache
42
+ - headless: Whether to run in headless mode
43
+ - geoip: Whether to use GeoIP spoofing (default: True)
44
+ - proxy: Proxy configuration dictionary (server, username, password)
45
+ - prefs: Firefox user preferences
46
+ - addons: List of absolute paths to extensions
47
+ - fingerprint_obj: BrowserForgeCapable instance
48
+ """
49
+ return cls(
50
+ platform=data.get("platform", Platform.WHATSAPP),
51
+ locale=data.get("locale", "en-US"),
52
+ enable_cache=data.get("enable_cache", False),
53
+ headless=data.get("headless", False),
54
+ prefs=data.get("prefs", {}),
55
+ addons=data.get("addons", []),
56
+ fingerprint_obj=data["fingerprint_obj"],
57
+ geoip=data.get("geoip", True),
58
+ proxy=data.get("proxy"),
59
+ )
60
+
61
+ def __str__(self):
62
+ return f"""
63
+ Platform: {self.platform}
64
+ Locale: {self.locale}
65
+ EnableCache: {self.enable_cache}
66
+ Headless: {self.headless}
67
+ Fingerprint: {self.fingerprint_obj!r} # Need to check for the fingerprint's __repr__
68
+ geoip: {self.geoip}
69
+ Proxy: {self.proxy}
70
+ Preferences: {self.prefs}
71
+ Addons: {self.addons}
72
+ """
73
+
74
+ def __repr__(self):
75
+ return (
76
+ f"BrowserConfig("
77
+ f"platform={self.platform}, "
78
+ f"locale='{self.locale}', "
79
+ f"headless={self.headless}, "
80
+ f"geoip={self.geoip}, "
81
+ f"fingerprint_obj={self.fingerprint_obj!r}"
82
+ f")"
83
+ )
84
+
85
+ def to_dict(self) -> Dict:
86
+ """
87
+ Serializes BrowserConfig to a dictionary.
88
+ """
89
+ return {
90
+ "platform": self.platform,
91
+ "locale": self.locale,
92
+ "enable_cache": self.enable_cache,
93
+ "headless": self.headless,
94
+ "geoip": self.geoip,
95
+ "proxy": self.proxy,
96
+ "prefs": self.prefs,
97
+ "addons": self.addons,
98
+ "fingerprint": {"provider": "browserforge"},
99
+ }
@@ -0,0 +1,15 @@
1
+ from typing import Optional, Union
2
+ from camouchat_core import LoggerFactory
3
+
4
+
5
+ def get_profile_browser_logger(
6
+ name: str, profile_id: str = "GLOBAL", level: Optional[Union[int, str]] = None
7
+ ):
8
+ """Returns a logger specialized for browser operations with profile context."""
9
+ return LoggerFactory.get_logger(
10
+ name=name, platform="BROWSER", profile_id=profile_id, level=level
11
+ )
12
+
13
+
14
+ # Default logger for the module
15
+ logger = get_profile_browser_logger("browser_init")
@@ -0,0 +1,238 @@
1
+ """
2
+ BrowserForge fingerprint management module.
3
+
4
+ This module provides hardware-level fingerprinting capabilities using the
5
+ browserforge library, ensuring consistent and stealthy browser identities
6
+ that match the host's actual display dimensions.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import pickle
12
+ from logging import Logger, LoggerAdapter
13
+ from pathlib import Path
14
+ from typing import Optional, Tuple, Union
15
+
16
+ from browserforge.fingerprints import Fingerprint, FingerprintGenerator
17
+ from camouchat_core import Platform
18
+
19
+ from .browser_logger import logger
20
+ from .directory import DirectoryManager
21
+ from .exceptions import BrowserException
22
+ from .profile_info import ProfileInfo
23
+
24
+
25
+ class BrowserForge:
26
+ """
27
+ Manages browser fingerprint generation and persistence.
28
+
29
+ Uses the browserforge library to create authentic hardware/software signatures.
30
+ Fingerprints are tied to ProfileInfo objects to ensure identity consistency
31
+ across multiple sessions and avoid detection by screen-size mismatch.
32
+ """
33
+
34
+ log: Union[Logger, LoggerAdapter]
35
+
36
+ def __init__(self, log: Optional[Union[Logger, LoggerAdapter]] = None) -> None:
37
+ self.log = log or logger
38
+
39
+ def get_fg(self, profile: ProfileInfo) -> Fingerprint:
40
+ """
41
+ If old fingerprint exists -> returns that fingerprint.
42
+ Else creates new fingerprint.
43
+ :param profile: ProfileInfo instance
44
+ :return: Fingerprint
45
+ """
46
+ fingerprint_path: Path = profile.fingerprint_path
47
+ if fingerprint_path.exists():
48
+ if os.stat(fingerprint_path).st_size > 0:
49
+ with open(fingerprint_path, "rb") as fh:
50
+ fg = pickle.load(fh)
51
+ else:
52
+ existing_fgs = self._get_all_existing_fingerprints(profile.platform)
53
+ fg = self.__gen_fg__(avoid=existing_fgs)
54
+ if fg is not None:
55
+ with open(fingerprint_path, "wb") as fh:
56
+ pickle.dump(fg, fh)
57
+ return fg
58
+ else:
59
+ raise BrowserException("path given does not exist")
60
+
61
+ def _get_all_existing_fingerprints(self, platform: Platform) -> list[Fingerprint]:
62
+ """
63
+ Scans all platforms and profiles to collect existing fingerprints.
64
+ :return: list of Fingerprint objects
65
+ """
66
+
67
+ dm = DirectoryManager()
68
+ fingerprints: list[Fingerprint] = []
69
+
70
+ platform_dir = dm.get_platform_dir(platform)
71
+ if not platform_dir.exists():
72
+ return fingerprints
73
+
74
+ for profile_dir in platform_dir.iterdir():
75
+ if not profile_dir.is_dir():
76
+ continue
77
+
78
+ fg_path = profile_dir / "fingerprint.pkl"
79
+ if fg_path.exists() and fg_path.stat().st_size > 0:
80
+ try:
81
+ with open(fg_path, "rb") as fh:
82
+ fg = pickle.load(fh)
83
+ fingerprints.append(fg)
84
+ except Exception as e:
85
+ self.log.warning(f"Could not load fingerprint from {fg_path}: {e}")
86
+
87
+ return fingerprints
88
+
89
+ def __gen_fg__(self, avoid: Optional[list[Fingerprint]] = None) -> Fingerprint:
90
+ """
91
+ Generates a new fingerprint.
92
+ :param avoid: Optional list of fingerprints to avoid
93
+ :return: Fingerprint
94
+ """
95
+ gen = FingerprintGenerator()
96
+ real_w, real_h = BrowserForge.get_screen_size()
97
+ tolerance = 0.1
98
+ attempt = 0
99
+ avoid = avoid or []
100
+
101
+ if real_w <= 0 or real_h <= 0:
102
+ raise BrowserException("Invalid real screen dimensions")
103
+
104
+ fg: Fingerprint
105
+ while True:
106
+ fg = gen.generate()
107
+ w, h = fg.screen.width, fg.screen.height
108
+ attempt += 1
109
+
110
+ if (
111
+ abs(w - real_w) / real_w < tolerance
112
+ and abs(h - real_h) / real_h < tolerance
113
+ ):
114
+ if fg in avoid:
115
+ if self.log:
116
+ self.log.warning(
117
+ f"🔁 Generated fingerprint already exists in another profile. Regenerating... (attempt {attempt})"
118
+ )
119
+ if attempt < 30:
120
+ continue
121
+
122
+ if self.log:
123
+ self.log.info(f"✅ Fingerprint screen OK: {w}x{h}")
124
+ break
125
+
126
+ if self.log:
127
+ self.log.warning(
128
+ f"🔁 Invalid fingerprint screen ({w}x{h}) vs real ({real_w}x{real_h}). Regenerating... ({attempt})"
129
+ )
130
+
131
+ if attempt >= 10:
132
+ if self.log:
133
+ self.log.warning(
134
+ "⚠️ Using last generated fingerprint after 10 attempts"
135
+ )
136
+ break
137
+
138
+ return fg
139
+
140
+ @staticmethod
141
+ def get_screen_size() -> Tuple[int, int]:
142
+ """
143
+ Returns the width and height of the primary display in pixels.
144
+ Supports Windows, Linux (X11), and macOS.
145
+ """
146
+ import platform
147
+
148
+ system = platform.system()
149
+
150
+ # ---------------- Windows ----------------
151
+ if system == "Windows":
152
+ try:
153
+ import ctypes
154
+
155
+ user32 = ctypes.windll.user32 # type: ignore[attr-defined]
156
+ try:
157
+ user32.SetProcessDPIAware()
158
+ except Exception:
159
+ pass # older Windows versions
160
+
161
+ return user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
162
+
163
+ except Exception as e:
164
+ raise BrowserException("Windows screen size detection failed") from e
165
+
166
+ # ---------------- Linux ----------------
167
+ elif system == "Linux":
168
+ try:
169
+ import subprocess
170
+
171
+ out = subprocess.check_output(
172
+ ["xdpyinfo"], stderr=subprocess.DEVNULL
173
+ ).decode()
174
+
175
+ for line in out.splitlines():
176
+ if "dimensions:" in line:
177
+ dims = line.split()[1].split("x")
178
+ return int(dims[0]), int(dims[1])
179
+
180
+ raise BrowserException("xdpyinfo did not return screen dimensions")
181
+
182
+ except Exception as e:
183
+ raise BrowserException("Linux screen size detection failed") from e
184
+
185
+ # ---------------- macOS ----------------
186
+ elif system == "Darwin":
187
+ try:
188
+ import Quartz # type: ignore[import-not-found]
189
+ except ImportError as e:
190
+ raise BrowserException("Quartz not available on macOS") from e
191
+
192
+ display = Quartz.CGMainDisplayID()
193
+ return (
194
+ Quartz.CGDisplayPixelsWide(display),
195
+ Quartz.CGDisplayPixelsHigh(display),
196
+ )
197
+
198
+ # ---------------- Unsupported OS ----------------
199
+ else:
200
+ raise BrowserException(
201
+ f"Unsupported OS for screen size detection: {system}"
202
+ )
203
+
204
+ @staticmethod
205
+ def get_fingerprint_as_dict(profile: ProfileInfo) -> dict:
206
+ """
207
+ Auto-Configure path to check & return data .
208
+ :param profile: ProfileInfo
209
+ :return: dict
210
+ """
211
+ saved_fingerprint_path: Path = profile.fingerprint_path
212
+
213
+ if not saved_fingerprint_path.exists():
214
+ raise BrowserException("saved_fingerprint_path does not exist")
215
+
216
+ if not saved_fingerprint_path.is_file():
217
+ raise BrowserException("saved_fingerprint_path is not a file")
218
+
219
+ if os.stat(saved_fingerprint_path).st_size == 0:
220
+ raise BrowserException("saved_fingerprint_path is empty")
221
+
222
+ try:
223
+ with open(saved_fingerprint_path, encoding="utf-8") as f:
224
+ data = json.load(f)
225
+
226
+ if not isinstance(data, dict):
227
+ raise BrowserException("Fingerprint JSON is not a valid dict")
228
+
229
+ return data
230
+
231
+ except json.JSONDecodeError as e:
232
+ raise BrowserException(f"Invalid fingerprint JSON format: {e}")
233
+
234
+ except Exception as e:
235
+ raise BrowserException(f"Failed to load fingerprint JSON: {e}")
236
+
237
+ def __repr__(self):
238
+ return f"BrowserForge(log={type(self.log).__name__})"