wabisabio 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sam Howle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,424 @@
1
+ Metadata-Version: 2.4
2
+ Name: wabisabio
3
+ Version: 0.1.0
4
+ Summary: Human-like input automation framework for Windows
5
+ Author-email: Sam Howle <samhowle@protonmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Sam Howle
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Repository, https://github.com/sam-howle/WabiSabIO
28
+ Classifier: Operating System :: Microsoft :: Windows
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: Topic :: Software Development :: Libraries
31
+ Requires-Python: >=3.9
32
+ Description-Content-Type: text/markdown
33
+ License-File: LICENSE
34
+ Requires-Dist: numpy>=1.20
35
+ Requires-Dist: scanput
36
+ Dynamic: license-file
37
+
38
+ # WabiSabIO
39
+ "Perfect" your input automation through injected imperfections
40
+
41
+ `wabisabio` is an input automation Python library for Windows keyboard and mouse inputs with the specific goal of making inputs appear more human-like. The framework features center-biased coordinate and timing randomization, curved mouse movement, destination overshoot and correction, and idle mouse jitter to model the small imperfections that naturally emerge during human interaction.
42
+
43
+ <img src="demo.gif" width="320">
44
+ <sub><i>*Fire hydrant image recognition module sold separately.</i></sub>
45
+
46
+ ## Introduction
47
+
48
+ Most keyboard and mouse automation libraries optimize for one thing: reliably interacting with a user interface. The resulting inputs are typically fast, precise, and perfectly repeatable, making them easy to distinguish from those of a real user through even relatively simple behavioral analysis.
49
+
50
+ A common response is to introduce randomness by varying delays, mouse speed, cursor landing location, or path generation. While this reduces consistency, it often produces its own unrealistic behavior. Human input is not uniformly random. Users tend to aim near the center of targets, maintain relatively consistent movement characteristics, occasionally overshoot a destination, and naturally alternate between periods of activity and inactivity.
51
+
52
+ This observation led to an interesting question:
53
+
54
+ > **How much more human can synthetic input appear using nothing more than a handful of statistical distributions and simple geometric techniques?**
55
+
56
+ `wabisabio` is an attempt to answer that question.
57
+
58
+ Rather than generating deterministic input and injecting randomness afterward, the library models many of the small imperfections that naturally emerge during human interaction. Mouse movement follows Bézier curves with hand tremor, destination overshoot and correction, timing delays are sampled from configurable statistical distributions, and higher-level primitives provide composable building blocks for constructing more natural interaction patterns while remaining lightweight and easy to understand.
59
+
60
+ ### Design Philosophy
61
+
62
+ Humans are consistent in their inconsistency.
63
+
64
+ When using a UI, people tend to aim near the center of targets, follow recognizable movement patterns, and occasionally overshoot. While the specifics vary person to person, the tendencies do not.
65
+
66
+ A simple example: if a valid click region spans 100×100 pixels, a basic script might pick a random (x, y) uniformly from that space. Technically random, but the implication is that users click the corners just as often as the center. They don't. People aim for the middle of a target and drift from it with decreasing probability the further out you go. It's center-biased, not flat.
67
+
68
+ `wabisabio` treats randomness the same way. Instead of uniform noise, its primitives sample from configurable distributions: shape, spread, and center are all adjustable by the caller.
69
+
70
+ The goal is behavior that looks like a person with habits rather than a script rolling dice.
71
+
72
+ The heatmaps shown below were generated using 10,000 sampled coordinates from each distribution:
73
+
74
+ <figure>
75
+ <img src="DeathBy10000Clicks.png">
76
+ <figcaption>
77
+ <i>Uniform sampling (left), center-biased sampling (center), customized center-biased sampling (right)</i>
78
+ </figcaption>
79
+ </figure>
80
+
81
+ The following examples generate the three distributions shown above:
82
+ ```python
83
+ # Uniform sampling (every valid coordinate is equally likely)
84
+ random_x = random.randint(center_x - radius_x, center_x + radius_x)
85
+ random_y = random.randint(center_y - radius_y, center_y + radius_y)
86
+
87
+ # Center-biased sampling (default)
88
+ random_x, random_y = wabisabio.randomize_coordinate_within_range(
89
+ center_x,
90
+ center_y,
91
+ radius_x=radius_x,
92
+ radius_y=radius_y,
93
+ )
94
+
95
+ # Customized distribution
96
+ random_x, random_y = wabisabio.randomize_coordinate_within_range(
97
+ center_x,
98
+ center_y,
99
+ radius_x=radius_x,
100
+ radius_y=radius_y,
101
+ sigmas_to_edge_x=3.6, # Narrower horizontal spread
102
+ sigmas_to_edge_y=3.6, # Narrower vertical spread
103
+ bias_x=-0.3, # Left-biased target selection
104
+ )
105
+ ```
106
+
107
+ While pixel landing coordinate randomization is one of the easiest behaviors to visualize, the same statistical distributions are used throughout the library and are applied to:
108
+
109
+ * Micro-delays between actions (e.g., moving a mouse before clicking)
110
+ * Action timing delays
111
+ * Mouse click and keystroke hold durations
112
+ * Mouse movement speed (relative to travel distance)
113
+ * Mouse movement curvature
114
+ * Primitive functions for center-biased randomization
115
+
116
+ Together, these primitives provide lightweight, composable building blocks for constructing more natural interaction patterns.
117
+
118
+ ## Installation
119
+ `wabisabio` requiures Python 3.9 or higher.
120
+ ```
121
+ pip install wabisabio
122
+ ```
123
+ Alternatively, clone this repo and build it locally:
124
+ ```
125
+ git clone https://github.com/sam-howle/WabiSabIO.git
126
+ cd wabisabio
127
+ pip install .
128
+ ```
129
+
130
+ ## Usage
131
+
132
+ The following table provides a brief overview of the functions exposed by `wabisabio`:
133
+
134
+ | Function | Description |
135
+ | --- | --- |
136
+ | `move_mouse(dest_x, dest_y)` | Move mouse from current position to the supplied `(x, y)` screen coordinates taking a curved path |
137
+ | `press_key(key)` | Presses the supplied `key` and releases after a short, randomized delay |
138
+ | `left_click()` | Performs a left click and releases after a short, randomized delay |
139
+ | `right_click()` | Performs a right click and releases after a short, randomized delay |
140
+ | `lagged_press_key(key)` | Same as `press_key()`, but with randomized delays before and/or after the event |
141
+ | `lagged_left_click()` | Same as `left_click()`, but with randomized delays before and/or after the event |
142
+ | `lagged_right_click()` | Same as `right_click()`, but with randomized delays before and/or after the event |
143
+ | `modifier_key_press(modifier, key)` | Presses one or more modifier keys (e.g. `"shift"`, `"ctrl"`), then presses `key`, then releases all in reverse order with randomized delays between each event |
144
+ | `modifier_left_click(modifier)` | Same as `left_click()`, but holds one or more modifier keys for the duration of the click |
145
+ | `modifier_right_click(modifier)` | Same as `right_click()`, but holds one or more modifier keys for the duration of the click |
146
+ | `type_string(input_string)` | Types the supplied string character by character with human-like inter-key delays. Handles shift-required characters and special keys (`\n`, `\t`, `\b`) automatically |
147
+ | `toggle_key_preflight_check()` | Ensures toggle keys (CapsLock, ScrollLock, NumLock) are in the desired state before automation begins. Defaults to all off. |
148
+ | `randomize_coordinate_within_range(x, y, radius_x, radius_y)` | Returns a gaussian-randomized `(x, y)` screen coordinate based on a center pixel `(x, y)` and an `x` and `y` radius (total pixels from center on each axis) |
149
+ | `randomize_coordinate_within_square(x, y, radius)` | Same as `randomize_coordinate_within_range()`, but for square-shaped UI elements where `x` and `y` radii are equal |
150
+ | `clamped_gauss_randint(min_int, max_int)` | Returns a gaussian-distributed random integer clamped to `[min_int, max_int]`. Center values are more probable than edge values |
151
+ | `clamped_gauss_randfloat(min_val, max_val)` | Same as `clamped_gauss_randint()`, but returns a float |
152
+ | `start_jitter()` | Causes the mouse cursor to periodically jitter 1-3 pixels, simulating a human hand resting on a mouse. Shares a mutex with `move_mouse()` and will not interfere with it. Resumes automatically after movement completes. Runs indefinitely until `stop_jitter()` is called |
153
+ | `stop_jitter()` | Disables the jitter thread. Call `start_jitter()` again to resume |
154
+ | `rsleep(min_time, max_time)` | Delays execution for a random duration between `min_time` and `max_time` over a clamped gaussian distribution, making center values more common |
155
+ | `rsleep(min_time)` | When called with only `min_time`, the max sleep duration is automatically set to 40% above the supplied value |
156
+
157
+
158
+ ### Mouse Movement
159
+
160
+ Mouse movement is performed using the `move_mouse()` function. It moves the cursor along a procedurally generated curve starting at the cursor's current position:
161
+
162
+ ```python
163
+ move_mouse(dest_x, dest_y, speed_multiplier=1.0, mouse_hz=500, speed_sigmas_to_edge=3, speed_bias=0.0, jitter_intensity=10)
164
+ ```
165
+
166
+ ```python
167
+ # Move mouse to (750, 300)
168
+ move_mouse(750, 300)
169
+ ```
170
+
171
+ #### Optional parameters
172
+ * **`speed_multiplier`** `float` - Scales mouse movement speed. A value of `1.2` is 20% faster, `0.5` is half speed. Note that deviating too far from the default of `1.0` may produce visually unnatural
173
+ movement. You do not need to account for travel distance - the function automatically scales speed relative to distance, as humans naturally move slower for short distances and faster for long ones.
174
+ * **`mouse_hz`** `int` - Simulated mouse polling rate. Affects how many points the cursor visits along the movement curve, not the speed of travel. Stick to common polling rates: `125`, `250`, `500`,
175
+ `1000`. Only supply this if you know what you are doing.
176
+ * **`jitter_intensity`** `int` - Controls the intensity of per-point micro-noise applied to the movement curve, simulating natural hand tremor. Higher values produce more visible noise. The noise is
177
+ angle-aligned to the direction of travel at each point, so it looks physically natural rather than random. Scales automatically with movement distance and speed.
178
+ * **`speed_sigmas_to_edge`** `float` - Controls how tightly the randomized speed clusters around the center of the speed range. Higher values produce less variance. See `clamped_gauss_randfloat()` for a
179
+ detailed explanation.
180
+ * **`speed_bias`** `float` - Biases the randomized speed toward the faster or slower end of the range. Accepts values between `-1.0` (bias toward slow) and `1.0` (bias toward fast).
181
+
182
+ ### Keyboard Input
183
+
184
+ #### Key Press
185
+
186
+ ```python
187
+ press_key(key, sigmas_to_edge=3, bias=0.0)
188
+ ```
189
+
190
+ ```python
191
+ # Press the 'e' key
192
+ press_key('e')
193
+
194
+ # Press the F5 key
195
+ press_key('f5')
196
+ ```
197
+
198
+ #### Optional parameters
199
+
200
+ * **`sigmas_to_edge`** `float` - Controls how tightly the randomized key hold duration clusters around the center of the hold range. Higher values produce less variance. See `clamped_gauss_randfloat()`
201
+ for a detailed explanation.
202
+
203
+ * **`bias`** `float` - Biases the randomized hold duration toward the shorter or longer end of the range. Accepts values between `-1.0` (bias toward short) and `1.0` (bias toward long).
204
+
205
+ ---
206
+
207
+ #### Lagged Key Press
208
+
209
+ Same as `press_key()`, but with randomized delays before and/or after the keypress event. Useful for simulating reaction time before a keypress, or a natural pause after.
210
+
211
+ ```python
212
+ lagged_press_key(key, prelag=0.1, postlag=0.1, sigmas_to_edge=3, bias=0.0, prelag_sigmas_to_edge=3, prelag_bias=0.0, postlag_sigmas_to_edge=3, postlag_bias=0.0)
213
+ ```
214
+
215
+ ```python
216
+ # Press 'e' with default pre and post delays
217
+ lagged_press_key('e')
218
+
219
+ # Press 'e' with a custom pre-delay range of 0.2 to 0.5 seconds
220
+ lagged_press_key('e', prelag=(0.2, 0.5))
221
+
222
+ # Press 'e' with no post-delay
223
+ lagged_press_key('e', postlag=None)
224
+ ```
225
+
226
+ #### Optional parameters
227
+
228
+ * **`prelag`** `float | tuple[float, float] | None` - Delay before the keypress. A single float sets the minimum, with max automatically set 0.1 seconds higher. A tuple sets an explicit `(min, max)`
229
+ range. Pass `None` to disable.
230
+ * **`postlag`** `float | tuple[float, float] | None` - Delay after the keypress. Behaves identically to `prelag`.
231
+ * **`prelag_sigmas_to_edge`** / **`prelag_bias`** - Controls the distribution of the pre-delay. See `clamped_gauss_randfloat()`.
232
+ * **`postlag_sigmas_to_edge`** / **`postlag_bias`** - Controls the distribution of the post-delay. See `clamped_gauss_randfloat()`.
233
+
234
+ ---
235
+
236
+ #### Modifier Key Press
237
+
238
+ Presses one or more modifier keys, then presses the target key, then releases everything in reverse order with randomized delays between each event.
239
+
240
+ ```python
241
+ modifier_key_press(modifier, key, min_time=0.03, max_time=0.08, sigmas_to_edge=3, bias=0.0)
242
+ ```
243
+
244
+ ```python
245
+ # Ctrl+C
246
+ modifier_key_press('ctrl', 'c')
247
+
248
+ # Ctrl+Shift+T
249
+ modifier_key_press(['ctrl', 'shift'], 't')
250
+ ```
251
+
252
+ #### Optional parameters
253
+
254
+ * **`min_time`** / **`max_time`** `float` - The minimum and maximum delay between each modifier down, key press, and modifier up event.
255
+ * **`sigmas_to_edge`** / **`bias`** - Controls the distribution of the inter-event delays. See `clamped_gauss_randfloat()`.
256
+
257
+ ---
258
+
259
+ #### Type String
260
+
261
+ Types a string character by character with human-like inter-key delays. Handles shift-required characters (`!`, `@`, `#`, etc.) and special keys (`\n`, `\t`, `\b`) automatically. CapsLock state is not accounted for. Use `toggle_key_preflight_check()` to ensure it is off before calling if needed.
262
+
263
+ ```python
264
+ type_string(input_string, speed_multiplier=1.0, sleep_sigmas_to_edge=1.5, sleep_bias=-0.3, hold_sigmas_to_edge=3, hold_bias=0.0)
265
+ ```
266
+
267
+ ```python
268
+ type_string("Hello, world!")
269
+ type_string("search query\n")
270
+ ```
271
+
272
+ #### Optional parameters
273
+ * **`speed_multiplier`** `float` - Scales the inter-key delay. A value of `1.2` types 20% faster, `0.5` types at half speed.
274
+ * **`sleep_sigmas_to_edge`** / **`sleep_bias`** - Controls the distribution of the delay between keystrokes.
275
+ * **`hold_sigmas_to_edge`** / **`hold_bias`** - Controls the distribution of the key hold duration.
276
+
277
+ ---
278
+
279
+ #### Toggle Key Preflight Check
280
+
281
+ Ensures toggle keys are in the desired state before automation begins. Useful to call at the start of a script to guarantee a known keyboard state.
282
+
283
+ ```python
284
+ toggle_key_preflight_check(capslock=False, scrolllock=False, numlock=False)
285
+ ```
286
+
287
+ ```python
288
+ # Ensure CapsLock and NumLock are off before starting
289
+ toggle_key_preflight_check(capslock=False, numlock=False)
290
+ ```
291
+
292
+ ### Idle Mouse Behavior
293
+
294
+ When a human hand rests on a mouse, it naturally produces small involuntary movements. `start_jitter()` replicates this behavior by periodically nudging the cursor 1-3 pixels in a random direction while
295
+ idle.
296
+
297
+ ```python
298
+ start_jitter()
299
+ stop_jitter()
300
+ ```
301
+
302
+ ```python
303
+ # Start idle jitter at the beginning of your script
304
+ start_jitter()
305
+
306
+ # ... automation code ...
307
+
308
+ # Stop jitter when done
309
+ stop_jitter()
310
+ ```
311
+
312
+ `start_jitter()` and `move_mouse()` share a mutex, so jitter will never interfere with an in-progress mouse movement and will automatically resume once the cursor is no longer in motion. You do not need
313
+ to call `stop_jitter()` before calling `move_mouse()`.
314
+
315
+ `stop_jitter()` permanently disables the jitter thread until `start_jitter()` is called again.
316
+
317
+ ---
318
+
319
+ ### Coordinate Randomization
320
+
321
+ Humans do not click the exact center of a UI element every time. These functions return a gaussian-randomized coordinate within a defined area, useful for picking a natural click target within a button
322
+ or other UI element.
323
+
324
+ ```python
325
+ randomize_coordinate_within_range(x, y, radius_x, radius_y, sigmas_to_edge_x=3, sigmas_to_edge_y=3, bias_x=0.0, bias_y=0.0)
326
+ randomize_coordinate_within_square(x, y, radius, sigmas_to_edge=3, bias_x=0.0, bias_y=0.0)
327
+ ```
328
+
329
+ ```python
330
+ # Randomize a click target within a 40x20 pixel button centered at (500, 300)
331
+ x, y = randomize_coordinate_within_range(500, 300, 40, 20)
332
+ move_mouse(x, y)
333
+ left_click()
334
+
335
+ # Same, but for a square element
336
+ x, y = randomize_coordinate_within_square(500, 300, 30)
337
+ move_mouse(x, y)
338
+ left_click()
339
+ ```
340
+
341
+ `randomize_coordinate_within_square()` is a convenience wrapper for `randomize_coordinate_within_range()` for square-shaped elements where the `x` and `y` radii are equal.
342
+
343
+ #### Optional parameters
344
+
345
+ * **`sigmas_to_edge_x`** / **`sigmas_to_edge_y`** `float` - Controls how tightly the randomized coordinate clusters around the center on each axis. Higher values produce less variance. See `clamped_gauss_randfloat()` for a detailed explanation.
346
+ * **`bias_x`** / **`bias_y`** `float` - Biases the randomized coordinate toward one side of the area on each axis. Accepts values between `-1.0` and `1.0`.
347
+
348
+ ---
349
+
350
+ ### Timing Utilities
351
+
352
+ #### Random Sleep
353
+
354
+ Delays script execution for a randomized duration over a clamped gaussian distribution, making center values more probable than edge values.
355
+
356
+ ```python
357
+ rsleep(min_time, max_time=None, sigmas_to_edge=3, bias=0.0)
358
+ ```
359
+
360
+ ```python
361
+ # Sleep between 0.5 and 1.5 seconds
362
+ rsleep(0.5, 1.5)
363
+
364
+ # Sleep between 0.5 and 0.7 seconds (max auto-set to 40% above min)
365
+ rsleep(0.5)
366
+ ```
367
+
368
+ When called with only `min_time`, the max duration is automatically set to 40% above the supplied value.
369
+
370
+ #### Optional parameters
371
+ * **`sigmas_to_edge`** `float` - Controls how tightly the randomized sleep duration clusters around the center of the range. Higher values produce less variance. See `clamped_gauss_randfloat()` for a
372
+ detailed explanation.
373
+ * **`bias`** `float` - Biases the randomized duration toward the shorter or longer end of the range. Accepts values between `-1.0` (bias toward short) and `1.0` (bias toward long).
374
+
375
+ ---
376
+
377
+ ### Statistical Primitives
378
+
379
+ These functions underpin all randomization in the library. They return values over a clamped gaussian distribution, meaning results cluster naturally around the center of the supplied range rather than
380
+ being uniformly distributed. Edge values are possible but rare.
381
+
382
+ ```python
383
+ clamped_gauss_randfloat(min_val, max_val, sigmas_to_edge=3, bias=0.0)
384
+ clamped_gauss_randint(min_int, max_int, sigmas_to_edge=3, bias=0.0)
385
+ ```
386
+
387
+ ```python
388
+ # Returns a float between 0.5 and 1.5, center values most likely
389
+ value = clamped_gauss_randfloat(0.5, 1.5)
390
+
391
+ # Returns an integer between 1 and 10, center values most likely
392
+ value = clamped_gauss_randint(1, 10)
393
+ ```
394
+
395
+ #### Optional parameters
396
+ * **`sigmas_to_edge`** `float` - Controls the spread of the distribution. Higher values tighten the distribution around the center, making edge values rarer. Lower values flatten it, making edge values
397
+ more common. Defaults to `3`, meaning the edges of the range sit at 3 standard deviations from the mean.
398
+ * **`bias`** `float` - Shifts the center of the distribution toward one end of the range. Accepts values between `-1.0` (bias toward minimum) and `1.0` (bias toward maximum). Defaults to `0.0` (no bias).
399
+
400
+ ### Lower-level Control
401
+ While `wabisabio` provides higher-level helpers for common interaction patterns, it also re-exports the underlying `_down` and `_up` primitives from [scanput](https://github.com/sam-howle/scanput). This allows more specialized behavior to be constructed without introducing an additional dependency or import.
402
+
403
+ These primitives can be freely composed with the rest of the `wabisabio` API. Functions such as `key_down(key)`, `key_up(key)`, `left_down()`, `left_up()`, `right_down()`, and `right_up()` make it easy to implement interaction patterns that extend beyond the built-in helpers.
404
+
405
+ ```python
406
+ from wabisabio import (
407
+ left_down, # Does not require direct import of scanput
408
+ left_up, # Same as above.
409
+ rsleep,
410
+ randomize_coordinate_within_range,
411
+ move_mouse
412
+ )
413
+
414
+ UI_button_x, UI_button_y = 775, 1010
415
+ UI_radius_x, UI_radius_y = 10, 20
416
+
417
+ # Click & drag to a specific, randomized coordinate
418
+ left_down()
419
+ rsleep(0.25, 1.15)
420
+ x, y = randomize_coordinate_within_range(UI_button_x, UI_button_y, UI_radius_x, UI_radius_y)
421
+ move_mouse(x, y, speed_multiplier=1.2)
422
+ rsleep(0.1, 0.25)
423
+ left_up()
424
+ ```