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.
- camouchat_browser-0.7.0/PKG-INFO +98 -0
- camouchat_browser-0.7.0/README.md +65 -0
- camouchat_browser-0.7.0/pyproject.toml +71 -0
- camouchat_browser-0.7.0/src/camouchat_browser/__init__.py +27 -0
- camouchat_browser-0.7.0/src/camouchat_browser/browser_config.py +99 -0
- camouchat_browser-0.7.0/src/camouchat_browser/browser_logger.py +15 -0
- camouchat_browser-0.7.0/src/camouchat_browser/browserforge.py +238 -0
- camouchat_browser-0.7.0/src/camouchat_browser/camoufox_browser.py +179 -0
- camouchat_browser-0.7.0/src/camouchat_browser/directory.py +127 -0
- camouchat_browser-0.7.0/src/camouchat_browser/exceptions.py +13 -0
- camouchat_browser-0.7.0/src/camouchat_browser/profile_info.py +74 -0
- camouchat_browser-0.7.0/src/camouchat_browser/profile_manager.py +498 -0
- camouchat_browser-0.7.0/src/camouchat_browser/py.typed +1 -0
|
@@ -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__})"
|