pi-lean-dimension 0.1.0 → 0.2.1
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.
- package/README.md +96 -36
- package/node_modules/pi-lean-portal/AGENTS.md +146 -4
- package/node_modules/pi-lean-portal/README.md +112 -50
- package/node_modules/pi-lean-portal/__tests__/browser-data.test.ts +124 -0
- package/node_modules/pi-lean-portal/__tests__/browser-inspect.test.ts +192 -16
- package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +3 -3
- package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +49 -35
- package/node_modules/pi-lean-portal/__tests__/chromium-py-persistence.test.ts +21 -195
- package/node_modules/pi-lean-portal/__tests__/chromium-py.test.ts +17 -81
- package/node_modules/pi-lean-portal/__tests__/chromium.test.ts +25 -0
- package/node_modules/pi-lean-portal/__tests__/contributed/invisible-py/invisible-py.test.ts +299 -0
- package/node_modules/pi-lean-portal/__tests__/cookie-persistence.test.ts +22 -182
- package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +1 -1
- package/node_modules/pi-lean-portal/__tests__/firefox-py-persistence.test.ts +21 -184
- package/node_modules/pi-lean-portal/__tests__/firefox-py.test.ts +17 -101
- package/node_modules/pi-lean-portal/__tests__/firefox.test.ts +2 -18
- package/node_modules/pi-lean-portal/__tests__/helpers/__pycache__/mock-python-bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/create-py-backend-harness.ts +105 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/load-plugin-config-from-file.ts +53 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/mock-plugin.ts +11 -7
- package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +4 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/persistence-suite.ts +218 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/plugin-contract.ts +198 -318
- package/node_modules/pi-lean-portal/__tests__/helpers/probe-user-backend.ts +198 -0
- package/node_modules/pi-lean-portal/__tests__/helpers/test-server.ts +14 -0
- package/node_modules/pi-lean-portal/__tests__/plugin-config-browser.test.ts +150 -15
- package/node_modules/pi-lean-portal/__tests__/plugin-loading.test.ts +120 -18
- package/node_modules/pi-lean-portal/__tests__/plugin-registry.test.ts +6 -67
- package/node_modules/pi-lean-portal/__tests__/probe-user-backend.test.ts +236 -0
- package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +401 -11
- package/node_modules/pi-lean-portal/__tests__/router-session.test.ts +4 -1
- package/node_modules/pi-lean-portal/__tests__/run-contributed-suites.test.ts +318 -0
- package/node_modules/pi-lean-portal/__tests__/session-manager.test.ts +50 -0
- package/node_modules/pi-lean-portal/__tests__/snapshot-cache.test.ts +2 -2
- package/node_modules/pi-lean-portal/__tests__/url-safety.test.ts +1 -1
- package/node_modules/pi-lean-portal/backends/chromium/index.ts +5 -13
- package/node_modules/pi-lean-portal/backends/chromium-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/chromium-py/bridge.py +0 -2
- package/node_modules/pi-lean-portal/backends/firefox/index.ts +6 -5
- package/node_modules/pi-lean-portal/backends/firefox-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/firefox-py/bridge.py +7 -6
- package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +241 -398
- package/node_modules/pi-lean-portal/backends/python-adapter.ts +182 -83
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__init__.py +1 -42
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/accessibility.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/accessibility.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bot_detection.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bot_detection.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/browser_data.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/browser_data.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/transport.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/transport.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/accessibility.py +12 -147
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bot_detection.py +12 -37
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bridge.py +249 -322
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/browser_data.py +92 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/patch_playwright.py +321 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +511 -299
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_accessibility.cpython-313-pytest-9.1.0.pyc → test_accessibility.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_chromium_py_bridge.cpython-313-pytest-9.1.0.pyc → test_chromium_py_bridge.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_firefox_py_bridge.cpython-313-pytest-9.1.0.pyc → test_firefox_py_bridge.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_py_bridges.cpython-313-pytest-9.1.1.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/{test_transport.cpython-313-pytest-9.1.0.pyc → test_transport.cpython-313-pytest-9.1.1.pyc} +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/conftest.py +95 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_accessibility.py +8 -132
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_bot_detection.py +8 -147
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_browser_data.py +131 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +768 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_py_bridges.py +198 -0
- package/node_modules/pi-lean-portal/browser-toggle.ts +33 -69
- package/node_modules/pi-lean-portal/contributed/CHOOSING.md +126 -0
- package/node_modules/pi-lean-portal/contributed/README.md +304 -0
- package/node_modules/pi-lean-portal/contributed/camoufox-py/bridge.py +216 -0
- package/node_modules/pi-lean-portal/contributed/invisible-py/__pycache__/bridge.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/contributed/invisible-py/bridge.py +434 -0
- package/node_modules/pi-lean-portal/core/fetch-backend.ts +0 -5
- package/node_modules/pi-lean-portal/core/plugin-api.ts +6 -33
- package/node_modules/pi-lean-portal/core/plugin-config.ts +75 -58
- package/node_modules/pi-lean-portal/core/plugin-registry.ts +11 -49
- package/node_modules/pi-lean-portal/core/router.ts +59 -98
- package/node_modules/pi-lean-portal/core/shared/accessibility-tree.ts +10 -143
- package/node_modules/pi-lean-portal/core/shared/bot-detection.ts +31 -77
- package/node_modules/pi-lean-portal/core/shared/browser-data.json +183 -0
- package/node_modules/pi-lean-portal/core/shared/browser-data.ts +50 -0
- package/node_modules/pi-lean-portal/core/shared/browser-events.ts +6 -6
- package/node_modules/pi-lean-portal/core/shared/dom-extractor.ts +97 -36
- package/node_modules/pi-lean-portal/core/shared/nav-settle.ts +12 -15
- package/node_modules/pi-lean-portal/core/shared/paths.ts +3 -0
- package/node_modules/pi-lean-portal/core/shared/session-manager.ts +7 -21
- package/node_modules/pi-lean-portal/{verify-ship-manifest.ts → core/shared/ship-manifest.ts} +34 -9
- package/node_modules/pi-lean-portal/core/shared/snapshot-cache.ts +7 -6
- package/node_modules/pi-lean-portal/core/shared/storage-state.ts +40 -9
- package/node_modules/pi-lean-portal/index.ts +42 -7
- package/node_modules/pi-lean-portal/package.json +8 -3
- package/node_modules/pi-lean-portal/ship-manifest.test.ts +8 -3
- package/node_modules/pi-lean-portal/tools/browser-inspect.ts +2 -6
- package/node_modules/pi-lean-portal/tools/browser-navigate.ts +7 -5
- package/node_modules/pi-lean-portal/tools/browser-snapshot.ts +2 -5
- package/node_modules/pi-lean-portal/tools/utils.ts +22 -4
- package/node_modules/pi-lean-portal/tools/web-fetch.ts +3 -4
- package/node_modules/pi-lean-search/README.md +6 -2
- package/node_modules/pi-lean-search/__tests__/web-search.test.ts +11 -0
- package/node_modules/pi-lean-search/index.ts +4 -21
- package/node_modules/pi-lean-search/package.json +2 -2
- package/node_modules/pi-lean-search/verify-ship-manifest.ts +6 -92
- package/node_modules/pi-lean-search/web-search-tool.ts +19 -13
- package/package.json +4 -4
- package/node_modules/pi-lean-portal/__tests__/helpers/reddit-fixture.ts +0 -264
- package/node_modules/pi-lean-portal/__tests__/helpers/toggle-test-utils.ts +0 -31
- package/node_modules/pi-lean-portal/__tests__/reddit-dialog.test.ts +0 -302
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/occlusion.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.0.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_chromium_py_bridge.py +0 -281
- package/node_modules/pi-lean-portal/backends/python-base/tests/test_firefox_py_bridge.py +0 -212
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
* Parses Playwright's page.ariaSnapshot() YAML-like output into an
|
|
5
5
|
* LLM-friendly text format with @e1, @e2 element references. Caches
|
|
6
6
|
* parsed nodes so interactions (click, type) can map back via getByRole().
|
|
7
|
+
*
|
|
8
|
+
* Role sets and icons are loaded from the shared ``browser-data.json``
|
|
9
|
+
* — the same file used by the Python bridge so the two sides never drift.
|
|
7
10
|
*/
|
|
8
11
|
|
|
12
|
+
import { ACCESSIBILITY } from "./browser-data.js";
|
|
13
|
+
|
|
9
14
|
/** A single parsed node from the aria snapshot, cached for interaction */
|
|
10
15
|
export interface AriaCachedNode {
|
|
11
16
|
ref: string;
|
|
@@ -31,84 +36,16 @@ export interface AriaParseResult {
|
|
|
31
36
|
|
|
32
37
|
/**
|
|
33
38
|
* Roles that get @e refs and can be used for interaction.
|
|
39
|
+
* Loaded from shared browser-data.json.
|
|
34
40
|
*/
|
|
35
|
-
|
|
36
|
-
"button",
|
|
37
|
-
"link",
|
|
38
|
-
"textbox",
|
|
39
|
-
"searchbox",
|
|
40
|
-
"combobox",
|
|
41
|
-
"checkbox",
|
|
42
|
-
"radio",
|
|
43
|
-
"heading",
|
|
44
|
-
"listbox",
|
|
45
|
-
"option",
|
|
46
|
-
"menuitem",
|
|
47
|
-
"menuitemcheckbox",
|
|
48
|
-
"menuitemradio",
|
|
49
|
-
"tab",
|
|
50
|
-
"treeitem",
|
|
51
|
-
"switch",
|
|
52
|
-
"slider",
|
|
53
|
-
"spinbutton",
|
|
54
|
-
"progressbar",
|
|
55
|
-
"meter",
|
|
56
|
-
"scrollbar",
|
|
57
|
-
"gridcell",
|
|
58
|
-
"cell",
|
|
59
|
-
"columnheader",
|
|
60
|
-
"rowheader",
|
|
61
|
-
"tabpanel",
|
|
62
|
-
"img",
|
|
63
|
-
"figure",
|
|
64
|
-
"listitem",
|
|
65
|
-
"dialog",
|
|
66
|
-
"alertdialog",
|
|
67
|
-
"tooltip",
|
|
68
|
-
"navigation",
|
|
69
|
-
"banner",
|
|
70
|
-
"form",
|
|
71
|
-
"search",
|
|
72
|
-
"toolbar",
|
|
73
|
-
"menu",
|
|
74
|
-
"menubar",
|
|
75
|
-
"note",
|
|
76
|
-
"alert",
|
|
77
|
-
"status",
|
|
78
|
-
"list",
|
|
79
|
-
"table",
|
|
80
|
-
"grid",
|
|
81
|
-
"treegrid",
|
|
82
|
-
"article",
|
|
83
|
-
"section",
|
|
84
|
-
"blockquote",
|
|
85
|
-
"code",
|
|
86
|
-
]);
|
|
41
|
+
const INTERACTIVE_ROLES = new Set(ACCESSIBILITY.interactiveRoles);
|
|
87
42
|
|
|
88
43
|
/**
|
|
89
44
|
* Roles that are shown in the tree but DON'T get @e refs
|
|
90
45
|
* (informational only, not useful click targets).
|
|
46
|
+
* Loaded from shared browser-data.json.
|
|
91
47
|
*/
|
|
92
|
-
|
|
93
|
-
"paragraph",
|
|
94
|
-
"text",
|
|
95
|
-
"group",
|
|
96
|
-
"region",
|
|
97
|
-
"main",
|
|
98
|
-
"complementary",
|
|
99
|
-
"contentinfo",
|
|
100
|
-
"definition",
|
|
101
|
-
"term",
|
|
102
|
-
"math",
|
|
103
|
-
"marquee",
|
|
104
|
-
"timer",
|
|
105
|
-
"log",
|
|
106
|
-
"deletion",
|
|
107
|
-
"insertion",
|
|
108
|
-
"mark",
|
|
109
|
-
"suggestion",
|
|
110
|
-
"comment",
|
|
111
|
-
]);
|
|
48
|
+
const INFORMATIONAL_ROLES = new Set(ACCESSIBILITY.informationalRoles);
|
|
112
49
|
|
|
113
50
|
/**
|
|
114
51
|
* Parse the YAML-like output of page.ariaSnapshot().
|
|
@@ -306,77 +243,7 @@ function countLeadingSpaces(s: string): number {
|
|
|
306
243
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
307
244
|
|
|
308
245
|
export function roleIcon(role: string): string {
|
|
309
|
-
|
|
310
|
-
alert: "🔔 ",
|
|
311
|
-
alertdialog: "⚠ ",
|
|
312
|
-
article: "📰 ",
|
|
313
|
-
banner: "📰 ",
|
|
314
|
-
blockquote: "💬 ",
|
|
315
|
-
button: "🔘 ",
|
|
316
|
-
cell: "▫ ",
|
|
317
|
-
checkbox: "☑ ",
|
|
318
|
-
code: "💻 ",
|
|
319
|
-
columnheader: "📊 ",
|
|
320
|
-
combobox: "📋 ",
|
|
321
|
-
comment: "💬 ",
|
|
322
|
-
complementary: "📎 ",
|
|
323
|
-
contentinfo: "ℹ ",
|
|
324
|
-
definition: "📖 ",
|
|
325
|
-
deletion: "❌ ",
|
|
326
|
-
dialog: "💬 ",
|
|
327
|
-
figure: "🖼 ",
|
|
328
|
-
form: "📝 ",
|
|
329
|
-
grid: "📊 ",
|
|
330
|
-
gridcell: "▫ ",
|
|
331
|
-
group: "📦 ",
|
|
332
|
-
heading: "📌 ",
|
|
333
|
-
img: "🖼 ",
|
|
334
|
-
insertion: "➕ ",
|
|
335
|
-
link: "🔗 ",
|
|
336
|
-
list: "📋 ",
|
|
337
|
-
listbox: "📋 ",
|
|
338
|
-
listitem: "• ",
|
|
339
|
-
log: "📋 ",
|
|
340
|
-
main: "📄 ",
|
|
341
|
-
mark: "🖍️ ",
|
|
342
|
-
marquee: "📜 ",
|
|
343
|
-
math: "🧮 ",
|
|
344
|
-
menu: "📋 ",
|
|
345
|
-
menubar: "📋 ",
|
|
346
|
-
menuitem: "📋 ",
|
|
347
|
-
menuitemcheckbox: "☑ ",
|
|
348
|
-
menuitemradio: "○ ",
|
|
349
|
-
meter: "📊 ",
|
|
350
|
-
navigation: "🧭 ",
|
|
351
|
-
note: "📝 ",
|
|
352
|
-
option: "• ",
|
|
353
|
-
paragraph: "📃 ",
|
|
354
|
-
progressbar: "⏳ ",
|
|
355
|
-
radio: "○ ",
|
|
356
|
-
region: "📦 ",
|
|
357
|
-
rowheader: "📊 ",
|
|
358
|
-
search: "🔍 ",
|
|
359
|
-
searchbox: "🔍 ",
|
|
360
|
-
scrollbar: "📜 ",
|
|
361
|
-
section: "📄 ",
|
|
362
|
-
slider: "🔧 ",
|
|
363
|
-
spinbutton: "🔢 ",
|
|
364
|
-
status: "📊 ",
|
|
365
|
-
suggestion: "💡 ",
|
|
366
|
-
switch: "🔀 ",
|
|
367
|
-
tab: "📑 ",
|
|
368
|
-
table: "📊 ",
|
|
369
|
-
tabpanel: "📑 ",
|
|
370
|
-
term: "📖 ",
|
|
371
|
-
text: "📝 ",
|
|
372
|
-
textbox: "📝 ",
|
|
373
|
-
timer: "⏱️ ",
|
|
374
|
-
toolbar: "🔧 ",
|
|
375
|
-
tooltip: "💡 ",
|
|
376
|
-
treegrid: "📊 ",
|
|
377
|
-
treeitem: "• ",
|
|
378
|
-
};
|
|
379
|
-
return icons[role] || "";
|
|
246
|
+
return ACCESSIBILITY.roleIcons[role] ?? "";
|
|
380
247
|
}
|
|
381
248
|
|
|
382
249
|
function truncate(s: string, max: number): string {
|
|
@@ -6,93 +6,54 @@
|
|
|
6
6
|
* the router flags the navigation as bot-blocked so the agent
|
|
7
7
|
* can decide how to proceed — try web-fetch, try a different URL,
|
|
8
8
|
* or switch to a stealth browser backend if one is configured.
|
|
9
|
+
*
|
|
10
|
+
* Signal lists are loaded from the shared ``browser-data.json`` —
|
|
11
|
+
* the same file used by the Python bridge so the two sides never drift.
|
|
9
12
|
*/
|
|
10
13
|
|
|
14
|
+
import { BOT_SIGNALS } from "./browser-data.js";
|
|
15
|
+
|
|
11
16
|
export interface BotDetectionResult {
|
|
12
17
|
/** True if the page appears to be a bot block/challenge page */
|
|
13
18
|
isBlocked: boolean;
|
|
14
|
-
/** Confidence score 0-1 */
|
|
15
|
-
confidence: number;
|
|
16
|
-
/** Signal that triggered the detection */
|
|
17
|
-
signal?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Content patterns that indicate a bot block.
|
|
22
|
-
*
|
|
23
|
-
* Only specific challenge phrases are included — generic single words
|
|
24
|
-
* like "cloudflare", "captcha", "recaptcha", "hcaptcha", "enable javascript"
|
|
25
|
-
* etc. are excluded because they cause false positives on legitimate
|
|
26
|
-
* pages (Cloudflare's own site, web scraping articles, CAPTCHA service pages).
|
|
27
|
-
* Real challenge pages always use these exact phrases.
|
|
22
|
+
* Content patterns that indicate a bot block — loaded from shared data.
|
|
28
23
|
*/
|
|
29
|
-
const BLOCK_SIGNALS =
|
|
30
|
-
"please verify you are human",
|
|
31
|
-
"attention required!",
|
|
32
|
-
"just a moment...",
|
|
33
|
-
"checking your browser",
|
|
34
|
-
"you have been blocked",
|
|
35
|
-
"sorry, you have been blocked",
|
|
36
|
-
"verify you are human",
|
|
37
|
-
"your request has been blocked",
|
|
38
|
-
"we are checking your browser",
|
|
39
|
-
"cf-challenge",
|
|
40
|
-
"_cf_chl_opt",
|
|
41
|
-
"cdn-cgi/challenge",
|
|
42
|
-
];
|
|
24
|
+
const BLOCK_SIGNALS = BOT_SIGNALS.blockSignals;
|
|
43
25
|
|
|
44
26
|
/**
|
|
45
|
-
* Body-only string patterns —
|
|
46
|
-
* avoid false matches on legitimate content. Catches CDN-specific
|
|
47
|
-
* block pages (Akamai "Access Denied", generic 403s).
|
|
27
|
+
* Body-only string patterns — loaded from shared data.
|
|
48
28
|
*/
|
|
49
|
-
const BODY_ONLY_SIGNALS =
|
|
50
|
-
"errors.edgesuite.net",
|
|
51
|
-
"you don't have permission to access",
|
|
52
|
-
];
|
|
29
|
+
const BODY_ONLY_SIGNALS = BOT_SIGNALS.bodyOnlySignals;
|
|
53
30
|
|
|
54
31
|
/**
|
|
55
|
-
* Body-only regex patterns —
|
|
56
|
-
* These match the exact format of CDN error reference codes to
|
|
57
|
-
* avoid false positives from generic "reference #123" in normal content.
|
|
32
|
+
* Body-only regex patterns — compiled from shared data at module load.
|
|
58
33
|
*/
|
|
59
|
-
const BODY_ONLY_PATTERNS: RegExp[] =
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
const BODY_ONLY_PATTERNS: RegExp[] = BOT_SIGNALS.bodyOnlyPatterns.map(
|
|
35
|
+
(src) => new RegExp(src, "i"),
|
|
36
|
+
);
|
|
62
37
|
|
|
63
38
|
/**
|
|
64
|
-
* HTML-level signals —
|
|
65
|
-
* rather than visible body text. These look for CAPTCHA widget embed codes
|
|
66
|
-
* in the raw HTML source, which is a stronger signal than visible text
|
|
67
|
-
* (generic mentions of "captcha" in body copy are excluded to avoid false
|
|
68
|
-
* positives, but embed codes in HTML attributes are unambiguous).
|
|
39
|
+
* HTML-level signals — loaded from shared data.
|
|
69
40
|
*/
|
|
70
|
-
const HTML_SIGNALS =
|
|
71
|
-
"recaptcha",
|
|
72
|
-
"hcaptcha",
|
|
73
|
-
"turnstile",
|
|
74
|
-
"g-recaptcha",
|
|
75
|
-
"data-sitekey",
|
|
76
|
-
];
|
|
41
|
+
const HTML_SIGNALS = BOT_SIGNALS.htmlSignals;
|
|
77
42
|
|
|
78
43
|
/**
|
|
79
44
|
* Check if page text content suggests a bot block.
|
|
80
45
|
*/
|
|
81
|
-
|
|
82
|
-
if (!bodyText) return { isBlocked: false
|
|
46
|
+
function checkBodyText(bodyText: string): BotDetectionResult {
|
|
47
|
+
if (!bodyText) return { isBlocked: false };
|
|
83
48
|
|
|
84
49
|
const lower = bodyText.toLowerCase();
|
|
85
50
|
for (const signal of BLOCK_SIGNALS) {
|
|
86
51
|
if (lower.includes(signal)) {
|
|
87
|
-
return {
|
|
88
|
-
isBlocked: true,
|
|
89
|
-
confidence: signal.length > 20 ? 0.9 : 0.7,
|
|
90
|
-
signal,
|
|
91
|
-
};
|
|
52
|
+
return { isBlocked: true };
|
|
92
53
|
}
|
|
93
54
|
}
|
|
94
55
|
|
|
95
|
-
return { isBlocked: false
|
|
56
|
+
return { isBlocked: false };
|
|
96
57
|
}
|
|
97
58
|
|
|
98
59
|
/**
|
|
@@ -102,14 +63,14 @@ export function checkBodyText(bodyText: string): BotDetectionResult {
|
|
|
102
63
|
* (for Akamai reference codes, etc.).
|
|
103
64
|
*/
|
|
104
65
|
function checkBodyOnlyText(bodyText: string): BotDetectionResult {
|
|
105
|
-
if (!bodyText) return { isBlocked: false
|
|
66
|
+
if (!bodyText) return { isBlocked: false };
|
|
106
67
|
|
|
107
68
|
const lower = bodyText.toLowerCase();
|
|
108
69
|
|
|
109
70
|
// Check string signals first
|
|
110
71
|
for (const signal of BODY_ONLY_SIGNALS) {
|
|
111
72
|
if (lower.includes(signal)) {
|
|
112
|
-
return { isBlocked: true
|
|
73
|
+
return { isBlocked: true };
|
|
113
74
|
}
|
|
114
75
|
}
|
|
115
76
|
|
|
@@ -117,39 +78,32 @@ function checkBodyOnlyText(bodyText: string): BotDetectionResult {
|
|
|
117
78
|
for (const pattern of BODY_ONLY_PATTERNS) {
|
|
118
79
|
const match = bodyText.match(pattern);
|
|
119
80
|
if (match) {
|
|
120
|
-
return {
|
|
121
|
-
isBlocked: true,
|
|
122
|
-
confidence: 0.9,
|
|
123
|
-
signal: `regex: ${match[0].slice(0, 50)}`,
|
|
124
|
-
};
|
|
81
|
+
return { isBlocked: true };
|
|
125
82
|
}
|
|
126
83
|
}
|
|
127
84
|
|
|
128
|
-
return { isBlocked: false
|
|
85
|
+
return { isBlocked: false };
|
|
129
86
|
}
|
|
130
87
|
|
|
131
88
|
/**
|
|
132
89
|
* Check raw HTML content for CAPTCHA widget embed codes.
|
|
133
90
|
*
|
|
134
91
|
* Looks for specific widget identifiers in the HTML source (not visible text),
|
|
135
|
-
* so false positive risk from generic mentions is low.
|
|
136
|
-
*
|
|
92
|
+
* so false positive risk from generic mentions is low. The signal list is
|
|
93
|
+
* loaded from the shared ``browser-data.json`` so TypeScript and Python
|
|
94
|
+
* bridge implementations share a single source of truth.
|
|
137
95
|
*/
|
|
138
|
-
|
|
139
|
-
if (!html) return { isBlocked: false
|
|
96
|
+
function checkHtmlContent(html: string): BotDetectionResult {
|
|
97
|
+
if (!html) return { isBlocked: false };
|
|
140
98
|
|
|
141
99
|
const lower = html.toLowerCase();
|
|
142
100
|
for (const signal of HTML_SIGNALS) {
|
|
143
101
|
if (lower.includes(signal)) {
|
|
144
|
-
return {
|
|
145
|
-
isBlocked: true,
|
|
146
|
-
confidence: 0.85,
|
|
147
|
-
signal: `html:${signal}`,
|
|
148
|
-
};
|
|
102
|
+
return { isBlocked: true };
|
|
149
103
|
}
|
|
150
104
|
}
|
|
151
105
|
|
|
152
|
-
return { isBlocked: false
|
|
106
|
+
return { isBlocked: false };
|
|
153
107
|
}
|
|
154
108
|
|
|
155
109
|
/**
|
|
@@ -183,5 +137,5 @@ export function checkPage(
|
|
|
183
137
|
if (htmlResult.isBlocked) return htmlResult;
|
|
184
138
|
}
|
|
185
139
|
|
|
186
|
-
return { isBlocked: false
|
|
140
|
+
return { isBlocked: false };
|
|
187
141
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"botSignals": {
|
|
4
|
+
"blockSignals": [
|
|
5
|
+
"please verify you are human",
|
|
6
|
+
"attention required!",
|
|
7
|
+
"just a moment...",
|
|
8
|
+
"checking your browser",
|
|
9
|
+
"you have been blocked",
|
|
10
|
+
"sorry, you have been blocked",
|
|
11
|
+
"verify you are human",
|
|
12
|
+
"your request has been blocked",
|
|
13
|
+
"we are checking your browser",
|
|
14
|
+
"cf-challenge",
|
|
15
|
+
"_cf_chl_opt",
|
|
16
|
+
"cdn-cgi/challenge"
|
|
17
|
+
],
|
|
18
|
+
"bodyOnlySignals": [
|
|
19
|
+
"errors.edgesuite.net",
|
|
20
|
+
"you don't have permission to access"
|
|
21
|
+
],
|
|
22
|
+
"bodyOnlyPatterns": [
|
|
23
|
+
"reference\\s*#[a-f0-9]+(?:\\.[a-f0-9]+)+"
|
|
24
|
+
],
|
|
25
|
+
"htmlSignals": [
|
|
26
|
+
"recaptcha",
|
|
27
|
+
"hcaptcha",
|
|
28
|
+
"turnstile",
|
|
29
|
+
"g-recaptcha",
|
|
30
|
+
"data-sitekey"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"accessibility": {
|
|
34
|
+
"interactiveRoles": [
|
|
35
|
+
"button",
|
|
36
|
+
"link",
|
|
37
|
+
"textbox",
|
|
38
|
+
"searchbox",
|
|
39
|
+
"combobox",
|
|
40
|
+
"checkbox",
|
|
41
|
+
"radio",
|
|
42
|
+
"heading",
|
|
43
|
+
"listbox",
|
|
44
|
+
"option",
|
|
45
|
+
"menuitem",
|
|
46
|
+
"menuitemcheckbox",
|
|
47
|
+
"menuitemradio",
|
|
48
|
+
"tab",
|
|
49
|
+
"treeitem",
|
|
50
|
+
"switch",
|
|
51
|
+
"slider",
|
|
52
|
+
"spinbutton",
|
|
53
|
+
"progressbar",
|
|
54
|
+
"meter",
|
|
55
|
+
"scrollbar",
|
|
56
|
+
"gridcell",
|
|
57
|
+
"cell",
|
|
58
|
+
"columnheader",
|
|
59
|
+
"rowheader",
|
|
60
|
+
"tabpanel",
|
|
61
|
+
"img",
|
|
62
|
+
"figure",
|
|
63
|
+
"listitem",
|
|
64
|
+
"dialog",
|
|
65
|
+
"alertdialog",
|
|
66
|
+
"tooltip",
|
|
67
|
+
"navigation",
|
|
68
|
+
"banner",
|
|
69
|
+
"form",
|
|
70
|
+
"search",
|
|
71
|
+
"toolbar",
|
|
72
|
+
"menu",
|
|
73
|
+
"menubar",
|
|
74
|
+
"note",
|
|
75
|
+
"alert",
|
|
76
|
+
"status",
|
|
77
|
+
"list",
|
|
78
|
+
"table",
|
|
79
|
+
"grid",
|
|
80
|
+
"treegrid",
|
|
81
|
+
"article",
|
|
82
|
+
"section",
|
|
83
|
+
"blockquote",
|
|
84
|
+
"code"
|
|
85
|
+
],
|
|
86
|
+
"informationalRoles": [
|
|
87
|
+
"paragraph",
|
|
88
|
+
"text",
|
|
89
|
+
"group",
|
|
90
|
+
"region",
|
|
91
|
+
"main",
|
|
92
|
+
"complementary",
|
|
93
|
+
"contentinfo",
|
|
94
|
+
"definition",
|
|
95
|
+
"term",
|
|
96
|
+
"math",
|
|
97
|
+
"marquee",
|
|
98
|
+
"timer",
|
|
99
|
+
"log",
|
|
100
|
+
"deletion",
|
|
101
|
+
"insertion",
|
|
102
|
+
"mark",
|
|
103
|
+
"suggestion",
|
|
104
|
+
"comment"
|
|
105
|
+
],
|
|
106
|
+
"roleIcons": {
|
|
107
|
+
"alert": "🔔 ",
|
|
108
|
+
"alertdialog": "⚠ ",
|
|
109
|
+
"article": "📰 ",
|
|
110
|
+
"banner": "📰 ",
|
|
111
|
+
"blockquote": "💬 ",
|
|
112
|
+
"button": "🔘 ",
|
|
113
|
+
"cell": "▫ ",
|
|
114
|
+
"checkbox": "☑ ",
|
|
115
|
+
"code": "💻 ",
|
|
116
|
+
"columnheader": "📊 ",
|
|
117
|
+
"combobox": "📋 ",
|
|
118
|
+
"comment": "💬 ",
|
|
119
|
+
"complementary": "📎 ",
|
|
120
|
+
"contentinfo": "ℹ ",
|
|
121
|
+
"definition": "📖 ",
|
|
122
|
+
"deletion": "❌ ",
|
|
123
|
+
"dialog": "💬 ",
|
|
124
|
+
"figure": "🖼 ",
|
|
125
|
+
"form": "📝 ",
|
|
126
|
+
"grid": "📊 ",
|
|
127
|
+
"gridcell": "▫ ",
|
|
128
|
+
"group": "📦 ",
|
|
129
|
+
"heading": "📌 ",
|
|
130
|
+
"img": "🖼 ",
|
|
131
|
+
"insertion": "➕ ",
|
|
132
|
+
"link": "🔗 ",
|
|
133
|
+
"list": "📋 ",
|
|
134
|
+
"listbox": "📋 ",
|
|
135
|
+
"listitem": "• ",
|
|
136
|
+
"log": "📋 ",
|
|
137
|
+
"main": "📄 ",
|
|
138
|
+
"mark": "🖍️ ",
|
|
139
|
+
"marquee": "📜 ",
|
|
140
|
+
"math": "🧮 ",
|
|
141
|
+
"menu": "📋 ",
|
|
142
|
+
"menubar": "📋 ",
|
|
143
|
+
"menuitem": "📋 ",
|
|
144
|
+
"menuitemcheckbox": "☑ ",
|
|
145
|
+
"menuitemradio": "○ ",
|
|
146
|
+
"meter": "📊 ",
|
|
147
|
+
"navigation": "🧭 ",
|
|
148
|
+
"note": "📝 ",
|
|
149
|
+
"option": "• ",
|
|
150
|
+
"paragraph": "📃 ",
|
|
151
|
+
"progressbar": "⏳ ",
|
|
152
|
+
"radio": "○ ",
|
|
153
|
+
"region": "📦 ",
|
|
154
|
+
"rowheader": "📊 ",
|
|
155
|
+
"search": "🔍 ",
|
|
156
|
+
"searchbox": "🔍 ",
|
|
157
|
+
"scrollbar": "📜 ",
|
|
158
|
+
"section": "📄 ",
|
|
159
|
+
"slider": "🔧 ",
|
|
160
|
+
"spinbutton": "🔢 ",
|
|
161
|
+
"status": "📊 ",
|
|
162
|
+
"suggestion": "💡 ",
|
|
163
|
+
"switch": "🔀 ",
|
|
164
|
+
"tab": "📑 ",
|
|
165
|
+
"table": "📊 ",
|
|
166
|
+
"tabpanel": "📑 ",
|
|
167
|
+
"term": "📖 ",
|
|
168
|
+
"text": "📝 ",
|
|
169
|
+
"textbox": "📝 ",
|
|
170
|
+
"timer": "⏱️ ",
|
|
171
|
+
"toolbar": "🔧 ",
|
|
172
|
+
"tooltip": "💡 ",
|
|
173
|
+
"treegrid": "📊 ",
|
|
174
|
+
"treeitem": "• "
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"navSettle": {
|
|
178
|
+
"navTimeoutMs": 5000,
|
|
179
|
+
"settleTimeoutMs": 400,
|
|
180
|
+
"settleRaceMs": 150,
|
|
181
|
+
"domStabilizationJs": "new Promise((resolve) => {\n const count = document.querySelectorAll(\"*\").length;\n setTimeout(() => {\n resolve(document.querySelectorAll(\"*\").length === count || count > 5000);\n }, 400);\n})"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed loader for shared browser data (bot-detection signals,
|
|
3
|
+
* accessibility roles/icons, nav-settle constants).
|
|
4
|
+
*
|
|
5
|
+
* Imports from the sibling JSON file so the same source of truth is
|
|
6
|
+
* available to both TypeScript and Python consumers. The import
|
|
7
|
+
* assertion ensures the JSON is loaded directly — no runtime path
|
|
8
|
+
* resolution needed, which keeps vitest and the pi runtime happy.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import _raw from "./browser-data.json" with { type: "json" };
|
|
14
|
+
|
|
15
|
+
// ─── Types ─────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export interface BotSignals {
|
|
18
|
+
blockSignals: string[];
|
|
19
|
+
bodyOnlySignals: string[];
|
|
20
|
+
bodyOnlyPatterns: string[];
|
|
21
|
+
htmlSignals: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AccessibilityData {
|
|
25
|
+
interactiveRoles: string[];
|
|
26
|
+
informationalRoles: string[];
|
|
27
|
+
roleIcons: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface NavSettleData {
|
|
31
|
+
navTimeoutMs: number;
|
|
32
|
+
settleTimeoutMs: number;
|
|
33
|
+
settleRaceMs: number;
|
|
34
|
+
domStabilizationJs: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BrowserDataFile {
|
|
38
|
+
version: number;
|
|
39
|
+
botSignals: BotSignals;
|
|
40
|
+
accessibility: AccessibilityData;
|
|
41
|
+
navSettle: NavSettleData;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const _data = _raw as BrowserDataFile;
|
|
45
|
+
|
|
46
|
+
// ─── Exports ───────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export const BOT_SIGNALS: BotSignals = _data.botSignals;
|
|
49
|
+
export const ACCESSIBILITY: AccessibilityData = _data.accessibility;
|
|
50
|
+
export const NAV_SETTLE: NavSettleData = _data.navSettle;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Page } from "playwright";
|
|
12
12
|
|
|
13
|
-
export interface
|
|
13
|
+
export interface DialogLogEntry {
|
|
14
14
|
type: "alert" | "confirm" | "prompt" | "beforeunload";
|
|
15
15
|
message: string;
|
|
16
16
|
/** Default value for prompt dialogs */
|
|
@@ -35,13 +35,13 @@ export interface ConsoleEvent {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Dialogs logged per task, for reporting to user */
|
|
38
|
-
const _dialogLog = new Map<string,
|
|
38
|
+
const _dialogLog = new Map<string, DialogLogEntry[]>();
|
|
39
39
|
|
|
40
40
|
/** Console messages logged per task */
|
|
41
41
|
const _consoleLog = new Map<string, ConsoleEvent[]>();
|
|
42
42
|
|
|
43
43
|
/** Get logged dialogs for a task */
|
|
44
|
-
export function getDialogLog(taskId: string):
|
|
44
|
+
export function getDialogLog(taskId: string): DialogLogEntry[] {
|
|
45
45
|
return _dialogLog.get(taskId) ?? [];
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -61,7 +61,7 @@ export function clearConsoleLog(taskId: string): void {
|
|
|
61
61
|
* Captures console messages (log, warn, error, info, debug) for retrieval.
|
|
62
62
|
*/
|
|
63
63
|
export function installDialogHandlers(taskId: string, page: Page): void {
|
|
64
|
-
const dialogLog:
|
|
64
|
+
const dialogLog: DialogLogEntry[] = [];
|
|
65
65
|
_dialogLog.set(taskId, dialogLog);
|
|
66
66
|
|
|
67
67
|
const consoleLog: ConsoleEvent[] = [];
|
|
@@ -69,8 +69,8 @@ export function installDialogHandlers(taskId: string, page: Page): void {
|
|
|
69
69
|
|
|
70
70
|
// Auto-accept JavaScript dialogs
|
|
71
71
|
page.on("dialog", async (dialog) => {
|
|
72
|
-
const entry:
|
|
73
|
-
type: dialog.type() as
|
|
72
|
+
const entry: DialogLogEntry = {
|
|
73
|
+
type: dialog.type() as DialogLogEntry["type"],
|
|
74
74
|
message: dialog.message(),
|
|
75
75
|
defaultValue: dialog.defaultValue(),
|
|
76
76
|
handledAs: "accepted",
|