captchakraken 2.9.0 → 3.0.0
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/AGENTS.md +112 -0
- package/NOTICE +36 -0
- package/dist/cli-invocation.d.ts +7 -36
- package/dist/cli-invocation.js +8 -49
- package/dist/errors.d.ts +7 -27
- package/dist/errors.js +6 -25
- package/dist/humanize.d.ts +56 -240
- package/dist/humanize.js +159 -415
- package/dist/index.d.ts +5 -129
- package/dist/index.js +4 -98
- package/dist/kinds.d.ts +135 -0
- package/dist/kinds.js +70 -0
- package/dist/limits.d.ts +0 -48
- package/dist/limits.js +2 -48
- package/dist/model-name.d.ts +4 -19
- package/dist/model-name.js +32 -40
- package/dist/playwright-types.d.ts +25 -95
- package/dist/playwright-types.js +0 -23
- package/dist/puppeteer-adapter.d.ts +4 -43
- package/dist/puppeteer-adapter.js +22 -58
- package/dist/python-command.d.ts +0 -41
- package/dist/python-command.js +0 -37
- package/dist/selectors.d.ts +54 -0
- package/dist/selectors.js +117 -0
- package/dist/slide-geometry.d.ts +2 -29
- package/dist/slide-geometry.js +18 -44
- package/dist/solver.d.ts +94 -712
- package/dist/solver.js +923 -3362
- package/dist/timing.d.ts +7 -40
- package/dist/timing.js +4 -36
- package/dist/token-usage.d.ts +1 -7
- package/dist/token-usage.js +10 -95
- package/dist/types.d.ts +75 -493
- package/dist/watcher.d.ts +5 -78
- package/dist/watcher.js +0 -56
- package/package.json +11 -4
- package/python/AGENTS.md +122 -0
- package/python/NOTICE +36 -0
- package/python/README.md +1 -2
- package/python/examples/README.md +10 -0
- package/python/examples/_harness.py +4 -73
- package/python/examples/demoHcaptcha.py +0 -13
- package/python/examples/demoRecaptcha.py +0 -10
- package/python/examples/watch_playwright.py +1 -26
- package/python/examples/with_local_gguf.py +28 -0
- package/python/examples/with_playwright.py +0 -19
- package/python/pyproject.toml +68 -19
- package/python/src/captchakraken/__init__.py +37 -45
- package/python/src/captchakraken/action_types.py +14 -36
- package/python/src/captchakraken/cli.py +262 -883
- package/python/src/captchakraken/config.py +60 -146
- package/python/src/captchakraken/errors.py +17 -95
- package/python/src/captchakraken/humanize.py +174 -597
- package/python/src/captchakraken/image_processor.py +16 -235
- package/python/src/captchakraken/keyframes.py +46 -484
- package/python/src/captchakraken/kinds.py +152 -0
- package/python/src/captchakraken/models.json +36 -4
- package/python/src/captchakraken/overlay.py +65 -498
- package/python/src/captchakraken/page_solver.py +1052 -3098
- package/python/src/captchakraken/planner.py +50 -487
- package/python/src/captchakraken/prompts.py +45 -261
- package/python/src/captchakraken/selectors.py +184 -0
- package/python/src/captchakraken/server_manager.py +7 -58
- package/python/src/captchakraken/solver.py +128 -522
- package/python/src/captchakraken/timing.py +20 -74
- package/python/src/captchakraken/tool_calls/board_painted.py +47 -0
- package/python/src/captchakraken/tool_calls/find_checkbox.py +20 -30
- package/python/src/captchakraken/tool_calls/find_grid.py +186 -1339
- package/python/src/captchakraken/tool_calls/track_piece.py +81 -57
- package/python/src/captchakraken/updater.py +7 -76
- package/python/src/captchakraken/watcher.py +12 -101
- package/scripts/copy-python.mjs +10 -10
- package/scripts/setup-python.js +1 -21
- package/dist/trajectory.d.ts +0 -58
- package/dist/trajectory.js +0 -266
- package/python/src/captchakraken/tool_calls/move_indicator.py +0 -431
- package/python/src/captchakraken/trajectory.py +0 -284
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from captchakraken import solve_captcha
|
|
5
|
+
|
|
6
|
+
TEXT_CAPTCHAS = {"--text"}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> int:
|
|
10
|
+
args = [a for a in sys.argv[1:] if a not in TEXT_CAPTCHAS]
|
|
11
|
+
if not args:
|
|
12
|
+
print(__doc__)
|
|
13
|
+
return 2
|
|
14
|
+
image = args[0]
|
|
15
|
+
text_mode = bool(TEXT_CAPTCHAS & set(sys.argv[1:]))
|
|
16
|
+
|
|
17
|
+
print(f"endpoint : {os.getenv('VLLM_BASE_URL', '(default)')}")
|
|
18
|
+
print(f"model : {os.getenv('CAPTCHA_LORA_NAME', '(registry default)')}")
|
|
19
|
+
print(f"prompts : {os.getenv('CAPTCHA_LORA_ADAPTER', '(latest)')}")
|
|
20
|
+
|
|
21
|
+
result = solve_captcha(image, text_mode=text_mode)
|
|
22
|
+
|
|
23
|
+
print(f"answer : {result}")
|
|
24
|
+
return 0 if result else 1
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
raise SystemExit(main())
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Vanilla Playwright + CaptchaKraken.
|
|
3
|
-
|
|
4
|
-
No adapter, and no browser dependency in the package: the driver duck-types the
|
|
5
|
-
Playwright surface, so a real `sync_playwright` page satisfies it as-is — as do
|
|
6
|
-
patchright's and camoufox's.
|
|
7
|
-
|
|
8
|
-
pip install captchakraken playwright && playwright install chromium
|
|
9
|
-
export VLLM_BASE_URL=https://api.captchakraken.com/v1
|
|
10
|
-
export CAPTCHA_KRAKEN_API_KEY=ck_live_...
|
|
11
|
-
python examples/with_playwright.py [url]
|
|
12
|
-
|
|
13
|
-
SYNC, NOT ASYNC. `PageSolver` mirrors the synchronous Playwright API. A sync
|
|
14
|
-
handle cannot be driven from inside an event loop, so `async_playwright` pages
|
|
15
|
-
cannot be solved — use `sync_playwright`, as here.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
1
|
import sys
|
|
19
2
|
|
|
20
3
|
from playwright.sync_api import sync_playwright
|
|
@@ -30,9 +13,7 @@ def main() -> int:
|
|
|
30
13
|
page = browser.new_page()
|
|
31
14
|
page.goto(URL)
|
|
32
15
|
|
|
33
|
-
# ── the whole integration: construct, solve ──────────────────────────
|
|
34
16
|
result = PageSolver().solve(page)
|
|
35
|
-
# ─────────────────────────────────────────────────────────────────────
|
|
36
17
|
|
|
37
18
|
print("✅ solved" if result.is_solved else "❌ not solved")
|
|
38
19
|
browser.close()
|
package/python/pyproject.toml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling==1.32.0"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "captchakraken"
|
|
7
|
-
version = "
|
|
7
|
+
version = "3.0.0"
|
|
8
8
|
description = "Captcha-solving engine and CLI: OpenCV grid detection plus a fine-tuned Qwen3.5-9B vision model, for image grids, click and drag puzzles, sliders, distorted text and animated challenges. Hosted API or self-hosted on vLLM."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
# Source-available, NOT GPL. The CaptchaKraken Source-Available License permits
|
|
12
12
|
# commercial use as an internal component but prohibits selling the solve itself,
|
|
13
13
|
# thin-wrapper resale, and bundling it into a stealth browser someone else ships
|
|
@@ -23,7 +23,6 @@ classifiers = [
|
|
|
23
23
|
"Development Status :: 5 - Production/Stable",
|
|
24
24
|
"Intended Audience :: Developers",
|
|
25
25
|
"Programming Language :: Python :: 3",
|
|
26
|
-
"Programming Language :: Python :: 3.10",
|
|
27
26
|
"Programming Language :: Python :: 3.11",
|
|
28
27
|
"Programming Language :: Python :: 3.12",
|
|
29
28
|
"Programming Language :: Python :: 3.13",
|
|
@@ -40,29 +39,52 @@ classifiers = [
|
|
|
40
39
|
# talks to a vLLM server (local or remote). This is all you need to SOLVE
|
|
41
40
|
# captchas against an existing endpoint. The heavy serving stack (vllm/torch)
|
|
42
41
|
# is deliberately NOT here — see the `serve` extra.
|
|
42
|
+
# Pinned exactly, never ranged: a dependency changes when we decide it does.
|
|
43
|
+
#
|
|
44
|
+
# EVERY PIN MUST INSTALL ON `requires-python`'s FLOOR, not on the newest
|
|
45
|
+
# interpreter that happens to be on your machine. numpy 2.5.3 was pinned here
|
|
46
|
+
# from a 3.14 box and broke the then-3.10 leg outright — it declares
|
|
47
|
+
# `Requires-Python >=3.12`, so pip could not find it at all.
|
|
48
|
+
#
|
|
49
|
+
# The floor is 3.11 as of 3.0.0, and it moved because the MOUSE moved: `cursory`
|
|
50
|
+
# declares `Requires-Python >=3.11` and drags numpy 2.3.x with it, which declares
|
|
51
|
+
# the same. Neither is a pin we could have chosen differently — 2.2.6 was the
|
|
52
|
+
# newest numpy covering 3.10, and cursory does not run there at all. Resolve
|
|
53
|
+
# against the floor before changing any of these, and see TRIBAL_KNOWLEDGE.md.
|
|
43
54
|
dependencies = [
|
|
44
|
-
"pydantic
|
|
45
|
-
"pillow
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
55
|
+
"pydantic==2.13.5",
|
|
56
|
+
"pillow==12.3.0",
|
|
57
|
+
# 2.3.x rather than 2.2.6 because `cursory` requires `numpy~=2.3.3` and the
|
|
58
|
+
# old pin was ours, not opencv's — opencv-python-headless asks only for
|
|
59
|
+
# `numpy>=2`. Moved deliberately as part of the major bump.
|
|
60
|
+
"numpy==2.3.5",
|
|
61
|
+
"opencv-python-headless==5.0.0.93",
|
|
62
|
+
"requests==2.34.2",
|
|
63
|
+
"python-dotenv==1.2.3",
|
|
64
|
+
# The mouse. LGPL-3.0-or-later, used as an ordinary installed dependency and
|
|
65
|
+
# never vendored or bundled, which is the arrangement the licence is written
|
|
66
|
+
# for — see NOTICE. Its TypeScript port `cursory-js` is the JS driver's
|
|
67
|
+
# dependency, and the two agree bit for bit on a seed.
|
|
68
|
+
"cursory==2.0.0",
|
|
50
69
|
]
|
|
51
70
|
|
|
52
71
|
[project.optional-dependencies]
|
|
53
72
|
# Self-hosting: install this to RUN a local vLLM server (the setup script does
|
|
54
73
|
# `pip install "captchakraken[serve]"`). Not needed to call a remote server.
|
|
74
|
+
# torch is the version vllm 0.29.0 itself pins; any other value makes this
|
|
75
|
+
# extra unresolvable rather than merely different.
|
|
55
76
|
serve = [
|
|
56
|
-
"vllm
|
|
57
|
-
"torch
|
|
58
|
-
"transformers
|
|
59
|
-
"accelerate
|
|
60
|
-
"huggingface_hub
|
|
77
|
+
"vllm==0.29.0",
|
|
78
|
+
"torch==2.13.0",
|
|
79
|
+
"transformers==5.17.0",
|
|
80
|
+
"accelerate==1.15.0",
|
|
81
|
+
"huggingface_hub==1.30.0",
|
|
61
82
|
]
|
|
62
83
|
dev = [
|
|
63
|
-
"pytest
|
|
64
|
-
"
|
|
65
|
-
"
|
|
84
|
+
"pytest==9.1.1",
|
|
85
|
+
"pytest-cov==7.1.0",
|
|
86
|
+
"mypy==2.3.1",
|
|
87
|
+
"ruff==0.16.6",
|
|
66
88
|
]
|
|
67
89
|
|
|
68
90
|
[project.scripts]
|
|
@@ -78,12 +100,39 @@ Changelog = "https://github.com/JWriter20/CaptchaKraken/blob/main/CHANGELOG.md"
|
|
|
78
100
|
[tool.hatch.build.targets.wheel]
|
|
79
101
|
packages = ["src/captchakraken"]
|
|
80
102
|
|
|
103
|
+
# AGENTS.md lives beside pyproject.toml, not inside the package, so the wheel
|
|
104
|
+
# would not carry it. An agent that only ever sees `site-packages/captchakraken`
|
|
105
|
+
# is exactly the reader it is written for, so it is copied in.
|
|
106
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
107
|
+
"AGENTS.md" = "captchakraken/AGENTS.md"
|
|
108
|
+
|
|
81
109
|
[tool.pytest.ini_options]
|
|
82
110
|
testpaths = ["tests"]
|
|
83
111
|
pythonpath = ["src"]
|
|
84
112
|
|
|
113
|
+
# Coverage is NOT in addopts on purpose: the rule here is to run the failing
|
|
114
|
+
# test rather than the suite, and a threshold measured over one test would fail
|
|
115
|
+
# every time. CI asks for it explicitly.
|
|
116
|
+
[tool.coverage.run]
|
|
117
|
+
source = ["captchakraken"]
|
|
118
|
+
branch = true
|
|
119
|
+
|
|
120
|
+
[tool.coverage.report]
|
|
121
|
+
precision = 2
|
|
122
|
+
# The floor is the value measured over the suite as it stands, rounded down to a
|
|
123
|
+
# whole percent — not the 90% the spec asks for. A gate that is red the day it
|
|
124
|
+
# lands blocks every pull request and teaches people to route around it; the
|
|
125
|
+
# real distance to 90% is recorded in TRIBAL_KNOWLEDGE.md so it cannot be
|
|
126
|
+
# forgotten. Raise this as coverage rises, never lower it.
|
|
127
|
+
#
|
|
128
|
+
# 53 -> 58: dead code removed (the denominator shrank) plus behaviour tests on
|
|
129
|
+
# the overlay, the checkbox detector, the wait-gate metric, the CLI wire format
|
|
130
|
+
# and the worker protocol. Measured 58.33% on 3.11 and 58.31% on 3.12; the floor
|
|
131
|
+
# is the lower, rounded down.
|
|
132
|
+
fail_under = 69
|
|
133
|
+
|
|
85
134
|
[tool.mypy]
|
|
86
|
-
python_version = "3.
|
|
135
|
+
python_version = "3.11"
|
|
87
136
|
warn_return_any = false
|
|
88
137
|
warn_unused_configs = true
|
|
89
138
|
disallow_untyped_defs = false
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
"""
|
|
2
|
-
CaptchaKraken — OpenCV grid detection + a fine-tuned Qwen3.5-9B vision LoRA
|
|
3
|
-
served on vLLM.
|
|
4
|
-
|
|
5
|
-
Usage:
|
|
6
|
-
from captchakraken import CaptchaSolver
|
|
7
|
-
solver = CaptchaSolver() # auto-starts / connects to a local vLLM server
|
|
8
|
-
actions = solver.solve("captcha.png")
|
|
9
|
-
|
|
10
|
-
Model/endpoint defaults live in `captchakraken.config` and are fully
|
|
11
|
-
env-overridable (VLLM_BASE_URL, CAPTCHA_LORA_ADAPTER, …); the solver itself is
|
|
12
|
-
model-agnostic.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
1
|
from pathlib import Path
|
|
16
2
|
|
|
17
|
-
try:
|
|
3
|
+
try:
|
|
18
4
|
from dotenv import load_dotenv
|
|
19
5
|
|
|
20
6
|
project_root = Path(__file__).resolve().parent.parent
|
|
@@ -32,16 +18,20 @@ from .action_types import (
|
|
|
32
18
|
from .image_processor import ImageProcessor
|
|
33
19
|
from .overlay import add_overlays_to_image
|
|
34
20
|
|
|
35
|
-
# The hosted API's refusal type. Outside the try below on purpose: `errors`
|
|
36
|
-
# imports nothing but `typing`, and docs/hosted-api.md tells every caller to
|
|
37
|
-
# branch on its `.code` — a recipe that has to work in both ports, and in a
|
|
38
|
-
# minimal install that has no serving stack.
|
|
39
21
|
from .errors import CaptchaKrakenAPIError
|
|
22
|
+
from .kinds import (
|
|
23
|
+
ActionKind,
|
|
24
|
+
Availability,
|
|
25
|
+
ErrorCode,
|
|
26
|
+
HumanizationMode,
|
|
27
|
+
KeyframeMode,
|
|
28
|
+
Outcome,
|
|
29
|
+
PauseKind,
|
|
30
|
+
PromptFamily,
|
|
31
|
+
RetryMode,
|
|
32
|
+
Vendor,
|
|
33
|
+
)
|
|
40
34
|
|
|
41
|
-
# How gestures are PERFORMED — mouse, touch, or nothing. Deliberately outside
|
|
42
|
-
# the try below: `humanize` imports only `trajectory`, so it has no dependency
|
|
43
|
-
# floor at all, and a caller writing a custom Humanizer must be able to import
|
|
44
|
-
# the base class without the serving stack installed.
|
|
45
35
|
from .humanize import (
|
|
46
36
|
AppiumTouchBackend,
|
|
47
37
|
CdpTouchBackend,
|
|
@@ -53,33 +43,30 @@ from .humanize import (
|
|
|
53
43
|
TouchscreenTouchBackend,
|
|
54
44
|
)
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
# serving stack. Keep them optional so leaf modules — e.g. tool_calls.find_grid,
|
|
58
|
-
# which needs only cv2 + numpy + pillow — can be imported in a minimal env (CI's
|
|
59
|
-
# hermetic grid-detection test) without the full GPU dependency set installed.
|
|
60
|
-
try: # pragma: no cover - exercised only when the serving stack is installed
|
|
46
|
+
try:
|
|
61
47
|
from .planner import ActionPlanner
|
|
62
48
|
from .solver import CaptchaSolver, solve_captcha
|
|
63
49
|
|
|
64
|
-
# The page driver sits behind the same guard: it imports `solver`, so it has
|
|
65
|
-
# the same dependency floor. It needs NO browser package of its own — the
|
|
66
|
-
# caller supplies the Playwright-compatible page (see page_solver's module
|
|
67
|
-
# docstring on why we duck-type rather than import one).
|
|
68
50
|
from .page_solver import PageSolver, SolveResult, solve_captcha_on_page
|
|
51
|
+
from .selectors import SELECTORS, VendorSelectors
|
|
69
52
|
from .watcher import CaptchaWatcher
|
|
70
53
|
except ModuleNotFoundError:
|
|
71
|
-
ActionPlanner = None
|
|
72
|
-
CaptchaSolver = None
|
|
73
|
-
solve_captcha = None
|
|
74
|
-
PageSolver = None
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
ActionPlanner = None
|
|
55
|
+
CaptchaSolver = None
|
|
56
|
+
solve_captcha = None
|
|
57
|
+
PageSolver = None
|
|
58
|
+
SELECTORS = None
|
|
59
|
+
VendorSelectors = None
|
|
60
|
+
CaptchaWatcher = None
|
|
61
|
+
SolveResult = None
|
|
62
|
+
solve_captcha_on_page = None
|
|
78
63
|
|
|
79
64
|
__all__ = [
|
|
80
65
|
"CaptchaSolver",
|
|
81
66
|
"solve_captcha",
|
|
82
67
|
"PageSolver",
|
|
68
|
+
"SELECTORS",
|
|
69
|
+
"VendorSelectors",
|
|
83
70
|
"CaptchaWatcher",
|
|
84
71
|
"SolveResult",
|
|
85
72
|
"solve_captcha_on_page",
|
|
@@ -100,11 +87,16 @@ __all__ = [
|
|
|
100
87
|
"CdpTouchBackend",
|
|
101
88
|
"AppiumTouchBackend",
|
|
102
89
|
"TouchscreenTouchBackend",
|
|
90
|
+
"ActionKind",
|
|
91
|
+
"Availability",
|
|
92
|
+
"ErrorCode",
|
|
93
|
+
"HumanizationMode",
|
|
94
|
+
"KeyframeMode",
|
|
95
|
+
"Outcome",
|
|
96
|
+
"PauseKind",
|
|
97
|
+
"PromptFamily",
|
|
98
|
+
"RetryMode",
|
|
99
|
+
"Vendor",
|
|
103
100
|
]
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
# ship together (rule 1c) and this is the only one importable code can read.
|
|
107
|
-
# It said 2.6.0 while the wheel on PyPI said 2.6.1, so anyone gating on the
|
|
108
|
-
# runtime attribute saw a version that had not been current for weeks.
|
|
109
|
-
# tests/test_public_contract.py compares all three.
|
|
110
|
-
__version__ = "2.9.0"
|
|
102
|
+
__version__ = "3.0.0"
|
|
@@ -2,12 +2,10 @@ from typing import List, Literal, Optional, Union, Tuple
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, RootModel
|
|
4
4
|
|
|
5
|
+
from .kinds import ActionKind
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class BoundingBox(RootModel):
|
|
7
|
-
"""
|
|
8
|
-
Strongly typed bounding box: [x1, y1, x2, y2] in percentages (0.0 to 1.0).
|
|
9
|
-
Acts like a list for convenience but ensures exactly 4 float elements.
|
|
10
|
-
"""
|
|
11
9
|
root: Tuple[float, float, float, float]
|
|
12
10
|
|
|
13
11
|
def __iter__(self):
|
|
@@ -21,57 +19,37 @@ class BoundingBox(RootModel):
|
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
class Action(BaseModel):
|
|
24
|
-
action:
|
|
25
|
-
# ANIMATED CHALLENGES ONLY. Path to the keyframe the model chose to act on, and
|
|
26
|
-
# its 1-based number in the set that was sent.
|
|
27
|
-
#
|
|
28
|
-
# Why an action carries a picture: on an animated puzzle the target is only
|
|
29
|
-
# present part of the time, so these coordinates are correct only while the
|
|
30
|
-
# widget looks the way it did in that keyframe. The driver therefore holds the
|
|
31
|
-
# mouse until the live neighbourhood around the click point matches the same
|
|
32
|
-
# neighbourhood of this file, then clicks. Without it the click fires on
|
|
33
|
-
# whatever happens to be on screen, which for a fading sprite is usually
|
|
34
|
-
# background.
|
|
35
|
-
#
|
|
36
|
-
# Absent on every still puzzle, where there is no moment to wait for. Both are
|
|
37
|
-
# set together or not at all — a number with no image cannot be waited on, and
|
|
38
|
-
# an image with no number cannot be reported.
|
|
22
|
+
action: ActionKind
|
|
39
23
|
await_keyframe: Optional[str] = None
|
|
40
24
|
frame: Optional[int] = None
|
|
41
25
|
|
|
42
26
|
|
|
43
27
|
class ClickAction(Action):
|
|
44
|
-
action: Literal[
|
|
45
|
-
target_bounding_boxes: List[BoundingBox]
|
|
28
|
+
action: Literal[ActionKind.CLICK]
|
|
29
|
+
target_bounding_boxes: List[BoundingBox]
|
|
46
30
|
|
|
47
31
|
|
|
48
32
|
class DragAction(Action):
|
|
49
|
-
action: Literal[
|
|
50
|
-
# Optional
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# every slide puzzle, at the moment of acting on a correct answer.
|
|
55
|
-
source_bounding_box: Optional[BoundingBox] = None # [x1, y1, x2, y2] in percentages
|
|
56
|
-
target_bounding_box: Optional[BoundingBox] = None # [x1, y1, x2, y2] in percentages
|
|
33
|
+
action: Literal[ActionKind.DRAG]
|
|
34
|
+
# `Optional[...] = None`, never a bare `BoundingBox = None`: pydantic validates a passed value even when it
|
|
35
|
+
# equals the default, and the bare form made every slide (a drag with no source) raise ValidationError.
|
|
36
|
+
source_bounding_box: Optional[BoundingBox] = None
|
|
37
|
+
target_bounding_box: Optional[BoundingBox] = None
|
|
57
38
|
|
|
58
39
|
|
|
59
40
|
class TypeAction(Action):
|
|
60
|
-
|
|
61
|
-
action: Literal["type"]
|
|
41
|
+
action: Literal[ActionKind.TYPE]
|
|
62
42
|
text: str
|
|
63
|
-
target_bounding_box: Optional[BoundingBox] = None
|
|
43
|
+
target_bounding_box: Optional[BoundingBox] = None
|
|
64
44
|
|
|
65
45
|
|
|
66
46
|
class WaitAction(Action):
|
|
67
|
-
|
|
68
|
-
action: Literal["wait"]
|
|
47
|
+
action: Literal[ActionKind.WAIT]
|
|
69
48
|
duration_ms: int
|
|
70
49
|
|
|
71
50
|
|
|
72
51
|
class DoneAction(Action):
|
|
73
|
-
|
|
74
|
-
action: Literal["done"]
|
|
52
|
+
action: Literal[ActionKind.DONE]
|
|
75
53
|
|
|
76
54
|
|
|
77
55
|
CaptchaAction = Union[ClickAction, DragAction, TypeAction, WaitAction, DoneAction]
|