captchakraken 2.8.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 +9 -40
- package/dist/cli-invocation.js +16 -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 +112 -539
- package/dist/solver.js +1088 -2950
- 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 +77 -483
- package/dist/watcher.d.ts +5 -78
- package/dist/watcher.js +0 -56
- package/package.json +12 -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 -879
- 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 +69 -434
- package/python/src/captchakraken/kinds.py +152 -0
- package/python/src/captchakraken/models.json +79 -21
- package/python/src/captchakraken/overlay.py +65 -498
- package/python/src/captchakraken/page_solver.py +1123 -2564
- package/python/src/captchakraken/planner.py +70 -428
- 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
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Human-like mouse trajectories for the page driver.
|
|
3
|
-
|
|
4
|
-
The Python side of `js/src/trajectory.ts`; the two are one implementation in
|
|
5
|
-
two languages, pinned by `test_trajectory_parity.py`.
|
|
6
|
-
|
|
7
|
-
The CONTRACT is identical, which is what matters for keeping the two drivers in
|
|
8
|
-
step: given a start point, an end point and a sampling frequency, return
|
|
9
|
-
`(points, timings)` where `timings[i]` is the cumulative milliseconds from the
|
|
10
|
-
start of the gesture at which `points[i]` should be delivered. The caller
|
|
11
|
-
(`page_solver`) sleeps against those cumulative timings exactly as the TS driver
|
|
12
|
-
does, so movement pacing is driver-independent.
|
|
13
|
-
|
|
14
|
-
What makes the output human rather than a lerp:
|
|
15
|
-
- a Bezier arc, not a straight line (hand motion bows)
|
|
16
|
-
- Fitts's-law duration: distance and precision set the time, not a constant
|
|
17
|
-
- an ease-in-out velocity profile, so the cursor accelerates then brakes
|
|
18
|
-
- sub-pixel jitter that scales with speed (fast motion is sloppier)
|
|
19
|
-
- a small overshoot-and-correct on longer moves, which is the single most
|
|
20
|
-
recognisable human tell and the one a linear interpolation never produces
|
|
21
|
-
|
|
22
|
-
`generate_swipe` is the SAME contract for a FINGER, and it is a different model
|
|
23
|
-
rather than the mouse model with different constants — see its docstring.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
from __future__ import annotations
|
|
27
|
-
|
|
28
|
-
import math
|
|
29
|
-
import random
|
|
30
|
-
from typing import List, Sequence, Tuple
|
|
31
|
-
|
|
32
|
-
Point = Tuple[float, float]
|
|
33
|
-
|
|
34
|
-
# Fitts's law: MT = a + b * log2(distance / width + 1). The constants are the
|
|
35
|
-
# usual empirical range for a mouse; `_WIDTH` stands in for target precision.
|
|
36
|
-
_FITTS_A_MS = 90.0
|
|
37
|
-
_FITTS_B_MS = 105.0
|
|
38
|
-
_WIDTH_PX = 28.0
|
|
39
|
-
|
|
40
|
-
# Below this, a move is a nudge inside one element: no overshoot, no arc.
|
|
41
|
-
_SHORT_MOVE_PX = 24.0
|
|
42
|
-
# Overshooting a 30px hop looks like a twitch, not a human.
|
|
43
|
-
_OVERSHOOT_MIN_DISTANCE_PX = 220.0
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _ease_in_out(t: float) -> float:
|
|
47
|
-
"""Cubic ease. Real pointer motion is roughly bell-shaped in velocity."""
|
|
48
|
-
if t < 0.5:
|
|
49
|
-
return 4.0 * t * t * t
|
|
50
|
-
return 1.0 - ((-2.0 * t + 2.0) ** 3) / 2.0
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _cubic_bezier(p0: Point, p1: Point, p2: Point, p3: Point, t: float) -> Point:
|
|
54
|
-
u = 1.0 - t
|
|
55
|
-
x = u * u * u * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t * t * t * p3[0]
|
|
56
|
-
y = u * u * u * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t * t * t * p3[1]
|
|
57
|
-
return (x, y)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _control_points(start: Point, end: Point, distance: float) -> Tuple[Point, Point]:
|
|
61
|
-
"""
|
|
62
|
-
Two control points offset PERPENDICULAR to the travel direction, so the path
|
|
63
|
-
bows to one side. The sign is random per gesture: always bowing the same way
|
|
64
|
-
would itself be a fingerprint.
|
|
65
|
-
"""
|
|
66
|
-
dx, dy = end[0] - start[0], end[1] - start[1]
|
|
67
|
-
if distance == 0:
|
|
68
|
-
return start, end
|
|
69
|
-
# Unit normal to the direction of travel.
|
|
70
|
-
nx, ny = -dy / distance, dx / distance
|
|
71
|
-
# Bow proportional to distance, but capped — long drags don't arc absurdly.
|
|
72
|
-
bow = min(distance * random.uniform(0.06, 0.16), 140.0) * random.choice((-1.0, 1.0))
|
|
73
|
-
# Control points at roughly 1/3 and 2/3 along, each pushed off-axis.
|
|
74
|
-
c1 = (
|
|
75
|
-
start[0] + dx * random.uniform(0.20, 0.40) + nx * bow,
|
|
76
|
-
start[1] + dy * random.uniform(0.20, 0.40) + ny * bow,
|
|
77
|
-
)
|
|
78
|
-
c2 = (
|
|
79
|
-
start[0] + dx * random.uniform(0.60, 0.80) + nx * bow * random.uniform(0.4, 0.9),
|
|
80
|
-
start[1] + dy * random.uniform(0.60, 0.80) + ny * bow * random.uniform(0.4, 0.9),
|
|
81
|
-
)
|
|
82
|
-
return c1, c2
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def generate_trajectory(
|
|
86
|
-
target_start: Sequence[float],
|
|
87
|
-
target_end: Sequence[float],
|
|
88
|
-
frequency: int = 60,
|
|
89
|
-
frequency_randomizer: float = 0.12,
|
|
90
|
-
) -> Tuple[List[Point], List[float]]:
|
|
91
|
-
"""
|
|
92
|
-
Returns `(points, timings)`. `timings` is CUMULATIVE milliseconds from the
|
|
93
|
-
gesture start — not per-step deltas — because that is what the TS driver's
|
|
94
|
-
`tracePath` sleeps against, and the two must pace identically.
|
|
95
|
-
|
|
96
|
-
`frequency` is samples per second (the TS driver passes 60).
|
|
97
|
-
`frequency_randomizer` jitters each frame's timestamp so the sample interval
|
|
98
|
-
is not a metronome; a perfectly periodic mousemove stream is trivially
|
|
99
|
-
detectable.
|
|
100
|
-
"""
|
|
101
|
-
start: Point = (float(target_start[0]), float(target_start[1]))
|
|
102
|
-
end: Point = (float(target_end[0]), float(target_end[1]))
|
|
103
|
-
|
|
104
|
-
dx, dy = end[0] - start[0], end[1] - start[1]
|
|
105
|
-
distance = math.hypot(dx, dy)
|
|
106
|
-
|
|
107
|
-
if distance < 1e-6:
|
|
108
|
-
return [end], [0.0]
|
|
109
|
-
|
|
110
|
-
duration_ms = _FITTS_A_MS + _FITTS_B_MS * math.log2(distance / _WIDTH_PX + 1.0)
|
|
111
|
-
duration_ms *= random.uniform(0.85, 1.20)
|
|
112
|
-
|
|
113
|
-
steps = max(2, int(round(duration_ms / 1000.0 * max(1, frequency))))
|
|
114
|
-
|
|
115
|
-
# A short hop is a straight ease with jitter — bowing a 15px move looks worse
|
|
116
|
-
# than not bowing it.
|
|
117
|
-
if distance < _SHORT_MOVE_PX:
|
|
118
|
-
c1, c2 = start, end
|
|
119
|
-
else:
|
|
120
|
-
c1, c2 = _control_points(start, end, distance)
|
|
121
|
-
|
|
122
|
-
# Overshoot: on a long move the hand arrives past the target and corrects.
|
|
123
|
-
# Modelled as a longer primary gesture to a point beyond the target, then a
|
|
124
|
-
# short settle back — the settle is appended as extra samples below.
|
|
125
|
-
overshoot = distance >= _OVERSHOOT_MIN_DISTANCE_PX and random.random() < 0.55
|
|
126
|
-
aim: Point = end
|
|
127
|
-
if overshoot:
|
|
128
|
-
over = random.uniform(0.01, 0.035) * distance
|
|
129
|
-
aim = (end[0] + dx / distance * over, end[1] + dy / distance * over)
|
|
130
|
-
|
|
131
|
-
points: List[Point] = []
|
|
132
|
-
timings: List[float] = []
|
|
133
|
-
elapsed = 0.0
|
|
134
|
-
per_step = duration_ms / steps
|
|
135
|
-
|
|
136
|
-
for i in range(steps + 1):
|
|
137
|
-
t = _ease_in_out(i / steps)
|
|
138
|
-
px, py = _cubic_bezier(start, c1, c2, aim, t)
|
|
139
|
-
|
|
140
|
-
# Jitter scales with instantaneous speed: the faster the cursor is
|
|
141
|
-
# moving, the less precisely a human tracks the intended path.
|
|
142
|
-
speed = abs(_ease_in_out(min(1.0, (i + 1) / steps)) - _ease_in_out(i / steps))
|
|
143
|
-
jitter = min(1.6, 0.25 + speed * steps * 0.5)
|
|
144
|
-
px += random.gauss(0.0, jitter * 0.5)
|
|
145
|
-
py += random.gauss(0.0, jitter * 0.5)
|
|
146
|
-
|
|
147
|
-
points.append((px, py))
|
|
148
|
-
timings.append(elapsed)
|
|
149
|
-
elapsed += per_step * random.uniform(1.0 - frequency_randomizer, 1.0 + frequency_randomizer)
|
|
150
|
-
|
|
151
|
-
if overshoot:
|
|
152
|
-
# Correction: a couple of small samples back onto the true target, at the
|
|
153
|
-
# slower pace of a deliberate fine adjustment.
|
|
154
|
-
settle_steps = random.randint(2, 4)
|
|
155
|
-
origin = points[-1]
|
|
156
|
-
for i in range(1, settle_steps + 1):
|
|
157
|
-
t = i / settle_steps
|
|
158
|
-
points.append(
|
|
159
|
-
(
|
|
160
|
-
origin[0] + (end[0] - origin[0]) * t + random.gauss(0.0, 0.35),
|
|
161
|
-
origin[1] + (end[1] - origin[1]) * t + random.gauss(0.0, 0.35),
|
|
162
|
-
)
|
|
163
|
-
)
|
|
164
|
-
timings.append(elapsed)
|
|
165
|
-
elapsed += random.uniform(14.0, 30.0)
|
|
166
|
-
|
|
167
|
-
# Land exactly on the requested pixel. Everything above is texture; the final
|
|
168
|
-
# sample must be the point the caller asked for or clicks drift off-target.
|
|
169
|
-
points[-1] = end
|
|
170
|
-
return points, timings
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
# ── Touch ───────────────────────────────────────────────────────────────────
|
|
174
|
-
#
|
|
175
|
-
# Fitts's law holds for direct touch too, but with a slower intercept and a much
|
|
176
|
-
# wider effective target: a fingertip contact patch is ~9mm across, which is the
|
|
177
|
-
# 44pt / 48dp minimum both platform guidelines are built around. So the same
|
|
178
|
-
# distance takes longer AND is aimed less precisely than with a mouse.
|
|
179
|
-
_TOUCH_FITTS_A_MS = 160.0
|
|
180
|
-
_TOUCH_FITTS_B_MS = 135.0
|
|
181
|
-
_TOUCH_WIDTH_PX = 44.0
|
|
182
|
-
|
|
183
|
-
# How flick-like the launch is. 0 would be the mouse's symmetric bell; 1 would
|
|
184
|
-
# be an instantaneous jump at t=0. See `_ease_touch`.
|
|
185
|
-
_TOUCH_LAUNCH = 0.7
|
|
186
|
-
|
|
187
|
-
# A finger drags across glass on a short wrist/thumb pivot, so it bows far less
|
|
188
|
-
# than a hand moving a mouse across a desk.
|
|
189
|
-
_TOUCH_BOW_CAP_PX = 40.0
|
|
190
|
-
|
|
191
|
-
# The reported contact point wanders, because it is the CENTROID of a soft patch
|
|
192
|
-
# rolling under pressure rather than a rigid sensor. `_TOUCH_WOBBLE_DECAY` is the
|
|
193
|
-
# AR(1) coefficient that makes that wander low-frequency: white per-sample noise
|
|
194
|
-
# would show up in a spectrum as nothing a finger produces.
|
|
195
|
-
_TOUCH_WOBBLE_PX = 0.55
|
|
196
|
-
_TOUCH_WOBBLE_DECAY = 0.82
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def _ease_touch(t: float) -> float:
|
|
200
|
-
"""Asymmetric ease: a finger leaves fast and brakes late.
|
|
201
|
-
|
|
202
|
-
A mouse hand is symmetric (`_ease_in_out` — accelerate, decelerate, equally).
|
|
203
|
-
A finger is not: the launch is a flick off the contact point and the arrival
|
|
204
|
-
is a brake. Blending the two rather than using a pure ease-out is what keeps
|
|
205
|
-
the launch from being an instantaneous jump at t=0, which no digitizer would
|
|
206
|
-
ever report.
|
|
207
|
-
"""
|
|
208
|
-
return (1.0 - _TOUCH_LAUNCH) * _ease_in_out(t) + _TOUCH_LAUNCH * (1.0 - (1.0 - t) ** 2.4)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def generate_swipe(
|
|
212
|
-
target_start: Sequence[float],
|
|
213
|
-
target_end: Sequence[float],
|
|
214
|
-
frequency: int = 90,
|
|
215
|
-
frequency_randomizer: float = 0.10,
|
|
216
|
-
) -> Tuple[List[Point], List[float]]:
|
|
217
|
-
"""A FINGER travelling from `target_start` to `target_end`, same contract as
|
|
218
|
-
`generate_trajectory`: `(points, timings)`, timings cumulative from 0, last
|
|
219
|
-
point exactly `target_end`.
|
|
220
|
-
|
|
221
|
-
A separate model rather than `generate_trajectory` with other constants,
|
|
222
|
-
because the three things that make the mouse output human are each WRONG
|
|
223
|
-
here:
|
|
224
|
-
|
|
225
|
-
- **No overshoot-and-correct.** That tell is a hand arriving past a target
|
|
226
|
-
it cannot see under the cursor. A finger occludes its own target and
|
|
227
|
-
commits; the correction, when there is one, is a second gesture.
|
|
228
|
-
- **A different velocity profile.** `_ease_touch`, not `_ease_in_out` —
|
|
229
|
-
see above.
|
|
230
|
-
- **Different jitter.** The mouse model adds white noise scaled by speed
|
|
231
|
-
(a hand tracking a path imprecisely). A digitizer instead reports a
|
|
232
|
-
wandering centroid, which is low-frequency and roughly speed
|
|
233
|
-
INDEPENDENT, so this is a smoothed random walk.
|
|
234
|
-
|
|
235
|
-
`frequency` defaults to 90: digitizers sample at 120Hz+, but touchmove is
|
|
236
|
-
coalesced to the compositor, so more samples than this buys nothing and
|
|
237
|
-
costs a dispatch round-trip each.
|
|
238
|
-
"""
|
|
239
|
-
start: Point = (float(target_start[0]), float(target_start[1]))
|
|
240
|
-
end: Point = (float(target_end[0]), float(target_end[1]))
|
|
241
|
-
|
|
242
|
-
dx, dy = end[0] - start[0], end[1] - start[1]
|
|
243
|
-
distance = math.hypot(dx, dy)
|
|
244
|
-
|
|
245
|
-
if distance < 1e-6:
|
|
246
|
-
return [end], [0.0]
|
|
247
|
-
|
|
248
|
-
duration_ms = _TOUCH_FITTS_A_MS + _TOUCH_FITTS_B_MS * math.log2(
|
|
249
|
-
distance / _TOUCH_WIDTH_PX + 1.0
|
|
250
|
-
)
|
|
251
|
-
duration_ms *= random.uniform(0.85, 1.20)
|
|
252
|
-
|
|
253
|
-
steps = max(2, int(round(duration_ms / 1000.0 * max(1, frequency))))
|
|
254
|
-
|
|
255
|
-
if distance < _SHORT_MOVE_PX:
|
|
256
|
-
c1, c2 = start, end
|
|
257
|
-
else:
|
|
258
|
-
c1, c2 = _control_points(start, end, distance)
|
|
259
|
-
# Pull the mouse model's bow in: `_control_points` is shared, and its
|
|
260
|
-
# arc is sized for a hand crossing a desk.
|
|
261
|
-
shrink = min(1.0, _TOUCH_BOW_CAP_PX / max(_TOUCH_BOW_CAP_PX, distance * 0.16))
|
|
262
|
-
c1 = (start[0] + (c1[0] - start[0]) * shrink, start[1] + (c1[1] - start[1]) * shrink)
|
|
263
|
-
c2 = (start[0] + (c2[0] - start[0]) * shrink, start[1] + (c2[1] - start[1]) * shrink)
|
|
264
|
-
|
|
265
|
-
points: List[Point] = []
|
|
266
|
-
timings: List[float] = []
|
|
267
|
-
elapsed = 0.0
|
|
268
|
-
per_step = duration_ms / steps
|
|
269
|
-
wob_x = wob_y = 0.0
|
|
270
|
-
|
|
271
|
-
for i in range(steps + 1):
|
|
272
|
-
t = _ease_touch(i / steps)
|
|
273
|
-
px, py = _cubic_bezier(start, c1, c2, end, t)
|
|
274
|
-
|
|
275
|
-
wob_x = wob_x * _TOUCH_WOBBLE_DECAY + random.gauss(0.0, _TOUCH_WOBBLE_PX)
|
|
276
|
-
wob_y = wob_y * _TOUCH_WOBBLE_DECAY + random.gauss(0.0, _TOUCH_WOBBLE_PX)
|
|
277
|
-
|
|
278
|
-
points.append((px + wob_x, py + wob_y))
|
|
279
|
-
timings.append(elapsed)
|
|
280
|
-
elapsed += per_step * random.uniform(1.0 - frequency_randomizer, 1.0 + frequency_randomizer)
|
|
281
|
-
|
|
282
|
-
# Land exactly where the caller asked, same reason as the mouse model.
|
|
283
|
-
points[-1] = end
|
|
284
|
-
return points, timings
|