Skip to content

Commit f4623a2

Browse files
committed
chore: formatting
1 parent df64ca4 commit f4623a2

22 files changed

+187
-142
lines changed

CHANGELOG.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
2.0.0
2-
- __breaking change__: more consistent naming all around
3-
- __breaking change__: Stealth object only takes kwargs now
4-
- new methods to hook context: Stealth.use_async and Stealth.use_sync
2+
3+
- __breaking change__: more consistent naming all around
4+
- __breaking change__: Stealth object only takes kwargs now
5+
- new methods to hook context: Stealth.use_async and Stealth.use_sync
56
- this allows us to patch CLI options as well, which are often better at faking than JS
6-
- chore: name options consistently
7-
- fix: scripts will not crash if in headful mode f9f84861
8-
- ft: use replaceProperty util which copies existing property descriptors 2b9b4b39
9-
- fix: remove deprecated pkg_resources usage (to support Python 3.12) (#2)
10-
- fix: navigator_platform typo (#1)
11-
- ft: better type hinted functions
12-
- ft: User-Agent and Sec-CH-UA spoofing
13-
- chore: remove chrome.run_on_unsecure_origins option (didn't see a purpose)
14-
- ft: sane navigator.platform default override
7+
- chore: name options consistently
8+
- fix: scripts will not crash if in headful mode f9f84861
9+
- ft: use replaceProperty util which copies existing property descriptors 2b9b4b39
10+
- fix: remove deprecated pkg_resources usage (to support Python 3.12) (#2)
11+
- fix: navigator_platform typo (#1)
12+
- ft: better type hinted functions
13+
- ft: User-Agent and Sec-CH-UA spoofing
14+
- chore: remove chrome.run_on_unsecure_origins option (didn't see a purpose)
15+
- ft: sane navigator.platform default override

README.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
# playwright_stealth
22

3-
Fork of AtuboDad's port of [puppeteer-extra-plugin-stealth](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth), with some improvements. Don't expect this to bypass anything but the simplest of bot detection methods. Consider this a proof-of-concept starting point.
3+
Fork of AtuboDad's port
4+
of [puppeteer-extra-plugin-stealth](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth),
5+
with some improvements. Don't expect this to bypass anything but the simplest of bot detection methods. Consider this a
6+
proof-of-concept starting point.
47

5-
I've merged some of the outstanding PRs, added some features, and cleaned up the API surface. See the [changelog](./CHANGELOG.md). The latest major version includes breaking changes.
8+
I've merged some of the outstanding PRs, added some features, and cleaned up the API surface. See
9+
the [changelog](./CHANGELOG.md). The latest major version includes breaking changes.
610

711
## Install
812

913
Install from PyPi:
14+
1015
```
1116
$ pip install playwright-stealth
1217
```
1318

1419
## Example Usage
1520

1621
### Recommended Usage
22+
1723
```python
1824
import asyncio
1925
from playwright.async_api import async_playwright
2026
from playwright_stealth import Stealth
2127

28+
2229
async def main():
2330
# This is the recommended usage. All pages created will have stealth applied:
2431
async with Stealth().use_async(async_playwright()) as p:
@@ -27,39 +34,42 @@ async def main():
2734

2835
webdriver_status = await page.evaluate("navigator.webdriver")
2936
print("from new_page: ", webdriver_status)
30-
37+
3138
different_context = await browser.new_context()
3239
page_from_different_context = await different_context.new_page()
3340

3441
different_context_status = await page_from_different_context.evaluate("navigator.webdriver")
3542
print("from new_context: ", different_context_status)
3643

44+
3745
asyncio.run(main())
3846
```
3947

4048
### Specifying config options and applying evasions manually to an entire context
49+
4150
```python
4251
import asyncio
4352
from playwright.async_api import async_playwright
4453
from playwright_stealth import Stealth, ALL_EVASIONS_DISABLED_KWARGS
4554

55+
4656
async def advanced_example():
4757
# Custom configuration with specific languages
4858
custom_languages = ("fr-FR", "fr")
4959
stealth = Stealth(
5060
navigator_languages_override=custom_languages,
5161
init_scripts_only=True
5262
)
53-
63+
5464
async with async_playwright() as p:
5565
browser = await p.chromium.launch()
5666
context = await browser.new_context()
5767
await stealth.apply_stealth_async(context)
58-
68+
5969
# Test stealth on multiple pages
6070
page_1 = await context.new_page()
6171
page_2 = await context.new_page()
62-
72+
6373
# Verify language settings
6474
for i, page in enumerate([page_1, page_2], 1):
6575
is_mocked = await page.evaluate("navigator.languages") == custom_languages
@@ -68,18 +78,21 @@ async def advanced_example():
6878
# Example of selective evasion usage
6979
no_evasions = Stealth(**ALL_EVASIONS_DISABLED_KWARGS)
7080
single_evasion = Stealth(**{**ALL_EVASIONS_DISABLED_KWARGS, "navigator_webdriver": True})
71-
81+
7282
print("Total evasions (none):", len(no_evasions.script_payload))
7383
print("Total evasions (single):", len(single_evasion.script_payload))
7484

85+
7586
asyncio.run(advanced_example())
7687
```
7788

7889
## Todo
79-
- make this work with playwright.launch_persistent_context
80-
- the difficult because sometimes we sniff the UA if an override isn't provided, and this is difficult to do when launch_persistent_context is launched
81-
- sec-platform (we have navigator_platform)
82-
- docs
90+
91+
- make this work with playwright.launch_persistent_context
92+
- the difficult because sometimes we sniff the UA if an override isn't provided, and this is difficult to do when
93+
launch_persistent_context is launched
94+
- sec-platform (we have navigator_platform)
95+
- docs
8396

8497
## A set of Test results
8598

playwright_stealth/context_managers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def __init__(self, stealth: "Stealth", manager: async_api.PlaywrightContextManag
99
self.manager = manager
1010

1111
async def __aenter__(
12-
self,
12+
self,
1313
) -> async_api.Playwright:
1414
context = await self.manager.__aenter__()
1515
self.stealth.hook_playwright_context(context)
@@ -27,7 +27,7 @@ def __init__(self, stealth: "Stealth", manager: sync_api.PlaywrightContextManage
2727
self.manager = manager
2828

2929
def __enter__(
30-
self,
30+
self,
3131
) -> sync_api.Playwright:
3232
context = self.manager.__enter__()
3333
self.stealth.hook_playwright_context(context)

playwright_stealth/js/evasions/chrome.app.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (!window.chrome) {
77
writable: true,
88
enumerable: true,
99
configurable: false, // note!
10-
value: {}, // We'll extend that later
10+
value: {} // We'll extend that later
1111
});
1212
}
1313

@@ -17,7 +17,7 @@ if (!("app" in window.chrome)) {
1717
ErrorInInvocation: (fn) => {
1818
const err = new TypeError(`Error in invocation of app.${fn}()`);
1919
return utils.stripErrorWithAnchor(err, `at ${fn} (eval at <anonymous>`);
20-
},
20+
}
2121
};
2222

2323
const APP_STATIC_DATA = JSON.parse(
@@ -62,7 +62,7 @@ if (!("app" in window.chrome)) {
6262
throw makeError.ErrorInInvocation(`runningState`);
6363
}
6464
return "cannot_run";
65-
},
65+
}
6666
};
6767
utils.patchToStringNested(window.chrome.app);
6868
}

playwright_stealth/js/evasions/chrome.csi.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (!window.chrome) {
77
writable: true,
88
enumerable: true,
99
configurable: false, // note!
10-
value: {}, // We'll extend that later
10+
value: {} // We'll extend that later
1111
});
1212
}
1313

@@ -17,12 +17,12 @@ if (!("csi" in window.chrome) && window.performance?.timing) {
1717
const { csi_timing } = window.performance;
1818

1919
log("loading chrome.csi.js");
20-
window.chrome.csi = function () {
20+
window.chrome.csi = function() {
2121
return {
2222
onloadT: csi_timing?.domContentLoadedEventEnd,
2323
startE: csi_timing?.navigationStart,
2424
pageT: Date.now() - csi_timing?.navigationStart,
25-
tran: 15, // transition? seems constant
25+
tran: 15 // transition? seems constant
2626
};
2727
};
2828
utils.patchToString(window.chrome.csi);
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
log("loading chrome.hairline.js");
22
// inspired by: https://intoli.com/blog/making-chrome-headless-undetectable/
3-
const elementDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight");
3+
const elementDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype,
4+
"offsetHeight");
45

56
utils.replaceProperty(HTMLDivElement.prototype, "offsetHeight", {
6-
get: function () {
7+
get: function() {
78
// hmmm not sure about this
89
if (this.id === "modernizr") {
910
return 1;
1011
}
1112
return elementDescriptor.get.apply(this);
12-
},
13+
}
1314
});

playwright_stealth/js/evasions/chrome.load.times.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (!window.chrome) {
77
writable: true,
88
enumerable: true,
99
configurable: false, // note!
10-
value: {}, // We'll extend that later
10+
value: {} // We'll extend that later
1111
});
1212
}
1313

@@ -24,23 +24,28 @@ if (window.performance?.timing || window.PerformancePaintTiming) {
2424
// let's harden the code to not fail then:
2525
const ntEntryFallback = {
2626
nextHopProtocol: "h2",
27-
type: "other",
27+
type: "other"
2828
};
2929

3030
// The API exposes some funky info regarding the connection
3131
const protocolInfo = {
3232
get connectionInfo() {
33-
const ntEntry = performance.getEntriesByType("navigation")[0] || ntEntryFallback;
33+
const ntEntry = performance.getEntriesByType("navigation")[0] ||
34+
ntEntryFallback;
3435
return ntEntry.nextHopProtocol;
3536
},
3637
get npnNegotiatedProtocol() {
3738
// NPN is deprecated in favor of ALPN, but this implementation returns the
3839
// HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.
39-
const ntEntry = performance.getEntriesByType("navigation")[0] || ntEntryFallback;
40-
return ["h2", "hq"].includes(ntEntry.nextHopProtocol) ? ntEntry.nextHopProtocol : "unknown";
40+
const ntEntry = performance.getEntriesByType("navigation")[0] ||
41+
ntEntryFallback;
42+
return ["h2", "hq"].includes(ntEntry.nextHopProtocol) ?
43+
ntEntry.nextHopProtocol :
44+
"unknown";
4145
},
4246
get navigationType() {
43-
const ntEntry = performance.getEntriesByType("navigation")[0] || ntEntryFallback;
47+
const ntEntry = performance.getEntriesByType("navigation")[0] ||
48+
ntEntryFallback;
4449
return ntEntry.type;
4550
},
4651
get wasAlternateProtocolAvailable() {
@@ -52,15 +57,17 @@ if (window.performance?.timing || window.PerformancePaintTiming) {
5257
get wasFetchedViaSpdy() {
5358
// SPDY is deprecated in favor of HTTP/2, but this implementation returns
5459
// true for HTTP/2 or HTTP2+QUIC/39 as well.
55-
const ntEntry = performance.getEntriesByType("navigation")[0] || ntEntryFallback;
60+
const ntEntry = performance.getEntriesByType("navigation")[0] ||
61+
ntEntryFallback;
5662
return ["h2", "hq"].includes(ntEntry.nextHopProtocol);
5763
},
5864
get wasNpnNegotiated() {
5965
// NPN is deprecated in favor of ALPN, but this implementation returns true
6066
// for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.
61-
const ntEntry = performance.getEntriesByType("navigation")[0] || ntEntryFallback;
67+
const ntEntry = performance.getEntriesByType("navigation")[0] ||
68+
ntEntryFallback;
6269
return ["h2", "hq"].includes(ntEntry.nextHopProtocol);
63-
},
70+
}
6471
};
6572

6673
const { timing } = window.performance;
@@ -93,16 +100,16 @@ if (window.performance?.timing || window.PerformancePaintTiming) {
93100
},
94101
get firstPaintTime() {
95102
const fpEntry = performance.getEntriesByType("paint")[0] || {
96-
startTime: timing.loadEventEnd / 1000, // Fallback if no navigation occured (`about:blank`)
103+
startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)
97104
};
98105
return toFixed((fpEntry.startTime + performance.timeOrigin) / 1000, 3);
99-
},
106+
}
100107
};
101108

102-
window.chrome.loadTimes = function () {
109+
window.chrome.loadTimes = function() {
103110
return {
104111
...protocolInfo,
105-
...timingInfo,
112+
...timingInfo
106113
};
107114
};
108115
utils.patchToString(window.chrome.loadTimes);

0 commit comments

Comments
 (0)