Metadata-Version: 2.4
Name: cursor-humanizer
Version: 0.1.0
Summary: Realistic, human-like mouse movements for Playwright (Python). Port of CloverLabsAI/human-cursor.
Author: Tih
License: ISC
Project-URL: Homepage, https://github.com/CloverLabsAI/human-cursor
Project-URL: TypeScript source, https://github.com/CloverLabsAI/human-cursor
Project-URL: Original algorithm, https://github.com/riflosnake/HumanCursor
Keywords: playwright,cursor,mouse,automation,bezier,humancursor
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: ISC License (ISCL)
Classifier: Operating System :: OS Independent
Classifier: Framework :: AsyncIO
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: playwright>=1.40
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"

# Human Cursor (Python)

Realistic, human-like mouse movements for **Playwright Python** (async API). Generates natural cursor paths using Bezier curves with randomised parameters, so automated browser interactions are hard to distinguish from real users.

Python port of [CloverLabsAI/human-cursor](https://github.com/CloverLabsAI/human-cursor) (TypeScript), which is itself a port of the original [humancursor](https://github.com/riflosnake/HumanCursor) Python algorithm. This port follows the TypeScript implementation directly — same curve math, same randomisation, same overall API shape.

## Features

- **Human-like movements** — Bezier curves with natural imperfections
- **Randomised parameters** — every movement is unique (control points, knots, distortion, easing)
- **Smart targeting** — clicks random points within elements, not centres
- **Easing functions** — 19 tweens for natural acceleration/deceleration
- **Momentum scrolling** — realistic wheel-scroll behaviour
- **Zero teleporting** — smooth, continuous paths

## Installation

```bash
pip install playwright
pip install -e .  # from this directory
python -m playwright install chromium
```

## Quick start

```python
import asyncio
from playwright.async_api import async_playwright
from human_cursor import create_cursor

async def main():
    async with async_playwright() as pw:
        browser = await pw.chromium.launch(headless=False)
        page = await browser.new_page()
        cursor = create_cursor(page)

        await page.goto("https://example.com")

        # Move to an element and click
        await cursor.click("a")

        # Move to coordinates
        from human_cursor import Vector
        await cursor.move_to(Vector(100, 200))

        # Type with human-like delays (Playwright already does this)
        await cursor.click('input[name="email"]')
        await page.keyboard.type("user@example.com", delay=100)

        # Scroll naturally
        await cursor.scroll({"y": 500})

        await browser.close()

asyncio.run(main())
```

## API

### `create_cursor(page, start=Vector(0,0), perform_random_moves=False, default_options=None, visible=False)`

Returns a `GhostCursor` driving `page`.

| Argument | Type | Default | Notes |
|---|---|---|---|
| `page` | `playwright.async_api.Page` | required | Async Playwright page |
| `start` | `Vector` | `Vector(0, 0)` | Initial cursor location |
| `perform_random_moves` | `bool` | `False` | Idle random-movement loop |
| `default_options` | `dict` | `None` | Per-method defaults (see below) |
| `visible` | `bool` | `False` | Inject a visual debug pointer |

### Cursor methods

All methods are async and take keyword-only options.

```python
await cursor.move("#button",
    padding_percentage=0.1,
    wait_for_selector=5000,
    move_delay=1000,
)

await cursor.move_to(Vector(500, 300), move_delay=500)

await cursor.click("#submit",
    hesitate=200,
    wait_for_click=100,
    move_delay=500,
    button="left",
)

await cursor.scroll({"y": 500}, scroll_speed=50, scroll_delay=200)

await cursor.scroll_to("#footer")
await cursor.scroll_to("top")
await cursor.scroll_to({"y": 1200})

await cursor.scroll_into_view(".main")

cursor.toggle_random_move(True)
await cursor.stop_random_move()
loc = cursor.get_location()  # Vector
```

### Default options

```python
cursor = create_cursor(page, default_options={
    "move":  {"padding_percentage": 0.15, "wait_for_selector": 10000, "move_delay": 2000},
    "click": {"hesitate": 300, "wait_for_click": 150},
    "scroll": {"scroll_speed": 75, "scroll_delay": 300},
    "random_move": {"move_delay": 3000},
})
```

### Visible cursor (debug)

```python
from human_cursor import install_mouse_helper, create_cursor

async with async_playwright() as pw:
    browser = await pw.chromium.launch(headless=False)
    page = await browser.new_page()
    await install_mouse_helper(page)  # call before any navigation
    cursor = create_cursor(page)
```

Or pass `visible=True` to `create_cursor` directly.

## How it works

1. **Bezier curve generation** — smooth paths from randomised control points
2. **Distortion** — natural noise added along the curve
3. **Easing** — one of 19 acceleration/deceleration functions
4. **Interpolation** — curve is resampled to the target point count
5. **Execution** — `page.mouse.move(...)` walks the points

The randomisation matches the reference implementation: weighted boundary offsets, weighted knot counts, weighted target-point ranges. See [`human_cursor/randomize.py`](human_cursor/randomize.py).

## Credits

- Original [ghost-cursor](https://github.com/Xetera/ghost-cursor) by Xetera
- [HumanCursor](https://github.com/riflosnake/HumanCursor) Python implementation by riflosnake
- [humancursor](https://github.com/Sudoeranas/humancursor) algorithm by Sudoeranas
- [CloverLabsAI/human-cursor](https://github.com/CloverLabsAI/human-cursor) TypeScript port

## License

ISC
