-
Notifications
You must be signed in to change notification settings - Fork 52
/
stealth.py
145 lines (131 loc) · 5.45 KB
/
stealth.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# -*- coding: utf-8 -*-
import json
from dataclasses import dataclass
from typing import Tuple, Optional, Dict
import pkg_resources
from playwright.async_api import Page as AsyncPage
from playwright.sync_api import Page as SyncPage
def from_file(name):
"""Read script from ./js directory"""
return pkg_resources.resource_string('playwright_stealth', f'js/{name}').decode()
SCRIPTS: Dict[str, str] = {
'chrome_csi': from_file('chrome.csi.js'),
'chrome_app': from_file('chrome.app.js'),
'chrome_runtime': from_file('chrome.runtime.js'),
'chrome_load_times': from_file('chrome.load.times.js'),
'chrome_hairline': from_file('chrome.hairline.js'),
'generate_magic_arrays': from_file('generate.magic.arrays.js'),
'iframe_content_window': from_file('iframe.contentWindow.js'),
'media_codecs': from_file('media.codecs.js'),
'navigator_vendor': from_file('navigator.vendor.js'),
'navigator_plugins': from_file('navigator.plugins.js'),
'navigator_permissions': from_file('navigator.permissions.js'),
'navigator_languages': from_file('navigator.languages.js'),
'navigator_platform': from_file('navigator.platform.js'),
'navigator_user_agent': from_file('navigator.userAgent.js'),
'navigator_hardware_concurrency': from_file('navigator.hardwareConcurrency.js'),
'outerdimensions': from_file('window.outerdimensions.js'),
'utils': from_file('utils.js'),
'webdriver': from_file('navigator.webdriver.js'),
'webgl_vendor': from_file('webgl.vendor.js'),
}
@dataclass
class StealthConfig:
"""
Playwright stealth configuration that applies stealth strategies to playwright page objects.
The stealth strategies are contained in ./js package and are basic javascript scripts that are executed
on every page.goto() called.
Note:
All init scripts are combined by playwright into one script and then executed this means
the scripts should not have conflicting constants/variables etc. !
This also means scripts can be extended by overriding enabled_scripts generator:
```
@property
def enabled_scripts():
yield 'console.log("first script")'
yield from super().enabled_scripts()
yield 'console.log("last script")'
```
"""
# load script options
webdriver: bool = True
webgl_vendor: bool = True
chrome_app: bool = True
chrome_csi: bool = True
chrome_load_times: bool = True
chrome_runtime: bool = True
iframe_content_window: bool = True
media_codecs: bool = True
navigator_hardware_concurrency: int = 4
navigator_languages: bool = True
navigator_permissions: bool = True
navigator_platform: bool = True
navigator_plugins: bool = True
navigator_user_agent: bool = True
navigator_vendor: bool = True
outerdimensions: bool = True
hairline: bool = True
# options
vendor: str = 'Intel Inc.'
renderer: str = 'Intel Iris OpenGL Engine'
nav_vendor: str = 'Google Inc.'
nav_user_agent: str = None
nav_platform: str = None
languages: Tuple[str] = ('en-US', 'en')
runOnInsecureOrigins: Optional[bool] = None
@property
def enabled_scripts(self):
opts = json.dumps({
'webgl_vendor': self.vendor,
'webgl_renderer': self.renderer,
'navigator_vendor': self.nav_vendor,
'navigator_platform': self.nav_platform,
'navigator_user_agent': self.nav_user_agent,
'languages': list(self.languages),
'runOnInsecureOrigins': self.runOnInsecureOrigins,
})
# defined options constant
yield f'const opts = {opts}'
# init utils and generate_magic_arrays helper
yield SCRIPTS['utils']
yield SCRIPTS['generate_magic_arrays']
if self.chrome_app:
yield SCRIPTS['chrome_app']
if self.chrome_csi:
yield SCRIPTS['chrome_csi']
if self.hairline:
yield SCRIPTS['chrome_hairline']
if self.chrome_load_times:
yield SCRIPTS['chrome_load_times']
if self.chrome_runtime:
yield SCRIPTS['chrome_runtime']
if self.iframe_content_window:
yield SCRIPTS['iframe_content_window']
if self.media_codecs:
yield SCRIPTS['media_codecs']
if self.navigator_languages:
yield SCRIPTS['navigator_languages']
if self.navigator_permissions:
yield SCRIPTS['navigator_permissions']
if self.navigator_platform:
yield SCRIPTS['navigator_platform']
if self.navigator_plugins:
yield SCRIPTS['navigator_plugins']
if self.navigator_user_agent:
yield SCRIPTS['navigator_user_agent']
if self.navigator_vendor:
yield SCRIPTS['navigator_vendor']
if self.webdriver:
yield SCRIPTS['webdriver']
if self.outerdimensions:
yield SCRIPTS['outerdimensions']
if self.webgl_vendor:
yield SCRIPTS['webgl_vendor']
def stealth_sync(page: SyncPage, config: StealthConfig = None):
"""teaches synchronous playwright Page to be stealthy like a ninja!"""
for script in (config or StealthConfig()).enabled_scripts:
page.add_init_script(script)
async def stealth_async(page: AsyncPage, config: StealthConfig = None):
"""teaches asynchronous playwright Page to be stealthy like a ninja!"""
for script in (config or StealthConfig()).enabled_scripts:
await page.add_init_script(script)