{"id":15035813,"url":"https://github.com/xetera/ghost-cursor","last_synced_at":"2025-05-13T20:15:05.085Z","repository":{"id":37722484,"uuid":"215410001","full_name":"Xetera/ghost-cursor","owner":"Xetera","description":"🖱️ Generate human-like mouse movements with puppeteer or on any 2D plane","archived":false,"fork":false,"pushed_at":"2025-03-12T09:55:31.000Z","size":760,"stargazers_count":1254,"open_issues_count":16,"forks_count":131,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-05-13T09:44:13.641Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Xetera.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-15T22:47:00.000Z","updated_at":"2025-05-13T01:38:15.000Z","dependencies_parsed_at":"2023-02-08T03:00:22.429Z","dependency_job_id":"340da00b-4b1f-44b6-8d26-51421dc4d72a","html_url":"https://github.com/Xetera/ghost-cursor","commit_stats":{"total_commits":106,"total_committers":17,"mean_commits":6.235294117647059,"dds":0.4339622641509434,"last_synced_commit":"e3ecd704e2d2a865a0637ca8b22d7e1b98125752"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":"Xetera/typescript-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xetera%2Fghost-cursor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xetera%2Fghost-cursor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xetera%2Fghost-cursor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xetera%2Fghost-cursor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Xetera","download_url":"https://codeload.github.com/Xetera/ghost-cursor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254020638,"owners_count":22000755,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T20:29:32.667Z","updated_at":"2025-05-13T20:15:05.066Z","avatar_url":"https://github.com/Xetera.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ghost Cursor\n\n\u003cimg src=\"https://media2.giphy.com/media/26ufp2LYURTvL5PRS/giphy.gif\" width=\"100\" align=\"right\"\u003e\n\nGenerate realistic, human-like mouse movement data between coordinates or navigate between elements with puppeteer\nlike the definitely-not-robot you are.\n\n\u003e Oh yeah? Could a robot do _**this?**_\n\n## Installation\n\n```sh\nyarn add ghost-cursor\n```\nor with npm\n```sh\nnpm install ghost-cursor\n```\n\n## Usage\nGenerating movement data between 2 coordinates.\n\n```js\nimport { path } from \"ghost-cursor\"\n\nconst from = { x: 100, y: 100 }\nconst to = { x: 600, y: 700 }\n\nconst route = path(from, to)\n\n/**\n * [\n *   { x: 100, y: 100 },\n *   { x: 108.75573501957051, y: 102.83608396351725 },\n *   { x: 117.54686481838543, y: 106.20019239793275 },\n *   { x: 126.3749821408895, y: 110.08364505509256 },\n *   { x: 135.24167973152743, y: 114.47776168684264 }\n *   ... and so on\n * ]\n */\n```\n\nGenerating movement data between 2 coordinates with timestamps.\n```js\nimport { path } from \"ghost-cursor\"\n\nconst from = { x: 100, y: 100 }\nconst to = { x: 600, y: 700 }\n\nconst route = path(from, to, { useTimestamps: true })\n\n/**\n * [\n *   { x: 100, y: 100, timestamp: 1711850430643 },\n *   { x: 114.78071695023473, y: 97.52340709495319, timestamp: 1711850430697 },\n *   { x: 129.1362373468682, y: 96.60141853603243, timestamp: 1711850430749 },\n *   { x: 143.09468422606352, y: 97.18676354029148, timestamp: 1711850430799 },\n *   { x: 156.68418062398405, y: 99.23217132478408, timestamp: 1711850430848 },\n *   ... and so on\n * ]\n */\n```\n\n\nUsage with puppeteer:\n\n```js\nimport { createCursor } from \"ghost-cursor\"\nimport puppeteer from \"puppeteer\"\n\nconst run = async (url) =\u003e {\n  const selector = \"#sign-up button\"\n  const browser = await puppeteer.launch({ headless: false });\n  const page = await browser.newPage()\n  const cursor = createCursor(page)\n  await page.goto(url)\n  await page.waitForSelector(selector)\n  await cursor.click(selector)\n  // shorthand for\n  // await cursor.move(selector)\n  // await cursor.click()\n}\n```\n\n### Puppeteer-specific behavior\n* `cursor.move()` will automatically overshoot or slightly miss and re-adjust for elements that are too far away\nfrom the cursor's starting point.\n* When moving over objects, a random coordinate that's within the element will be selected instead of\nhovering over the exact center of the element.\n* The speed of the mouse will take the distance and the size of the element you're clicking on into account.\n\n\u003cbr\u003e\n\n![ghost-cursor in action](https://cdn.discordapp.com/attachments/418699380833648644/664110683054538772/acc_gen.gif)\n\n\u003e Ghost cursor in action on a form\n\n## Methods\n\n#### `createCursor(page: puppeteer.Page, start?: Vector, performRandomMoves?: boolean, defaultOptions?: DefaultOptions): GhostCursor`\n\nCreates the ghost cursor. Returns cursor action functions.\n\n- **page:** Puppeteer `page`.\n- **start (optional):** Cursor start position. Default is `{ x: 0, y: 0 }`.\n- **performRandomMoves (optional):** Initially perform random movements. Default is `false`.\n- **defaultOptions (optional):** Set custom default options for `click`, `move`, `moveTo`, and `randomMove` functions. Default values are described below.\n\n#### `toggleRandomMove(random: boolean): void`\n\nToggles random mouse movements on or off.\n\n#### `click(selector?: string | ElementHandle, options?: ClickOptions): Promise\u003cvoid\u003e`\n\nSimulates a mouse click at the specified selector or element.\n\n- **selector (optional):** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options for clicking. **Extends the `options` of the `move`, `scrollIntoView`, and `getElement` functions (below)**\n  - `hesitate (number):` Delay before initiating the click action in milliseconds. Default is `0`.\n  - `waitForClick (number):` Delay between mousedown and mouseup in milliseconds. Default is `0`.\n  - `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `2000`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.\n  - `button (MouseButton):` Mouse button to click. Default is `left`.\n  - `clickCount (number):` Number of times to click the button. Default is `1`.\n\n#### `move(selector: string | ElementHandle, options?: MoveOptions): Promise\u003cvoid\u003e`\n\nMoves the mouse to the specified selector or element.\n\n- **selector:** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options for moving. **Extends the `options` of the `scrollIntoView` and `getElement` functions (below)**\n  - `paddingPercentage (number):` Percentage of padding to be added inside the element when determining the target point. Default is `0` (may move to anywhere within the element). `100` will always move to center of element.\n  - `destination (Vector):` Destination to move the cursor to, relative to the top-left corner of the element. If specified, `paddingPercentage` is not used. If not specified (default), destination is random point within the `paddingPercentage`.\n  - `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `0`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.\n  - `randomizeMoveDelay (boolean):` Randomize delay between actions from `0` to `moveDelay`. Default is `true`.\n  - `maxTries (number):` Maximum number of attempts to mouse-over the element. Default is `10`.\n  - `moveSpeed (number):` Speed of mouse movement. Default is random.\n  - `overshootThreshold (number):` Distance from current location to destination that triggers overshoot to occur. (Below this distance, no overshoot will occur). Default is `500`.\n\n#### `moveTo(destination: Vector, options?: MoveToOptions): Promise\u003cvoid\u003e`\n\nMoves the mouse to the specified destination point.\n\n- **destination:** An object with `x` and `y` coordinates representing the target position. For example, `{ x: 500, y: 300 }`.\n- **options (optional):** Additional options for moving.\n  - `moveSpeed (number):` Speed of mouse movement. Default is random.\n  - `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `0`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.\n  - `randomizeMoveDelay (boolean):` Randomize delay between actions from `0` to `moveDelay`. Default is `true`.\n  \n#### `scrollIntoView(selector: string | ElementHandle, options?: ScrollIntoViewOptions) =\u003e Promise\u003cvoid\u003e`\n\nScrolls the element into view. If already in view, no scroll occurs.\n\n- **selector:** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options for scrolling. **Extends the `options` of the `getElement` and `scroll` functions (below)**\n  - `scrollSpeed (number):` Scroll speed (when scrolling occurs). 0 to 100. 100 is instant. Default is `100`.\n  - `scrollDelay (number):` Time to wait after scrolling (when scrolling occurs). Default is `200`.\n  - `inViewportMargin (number):` Margin (in px) to add around the element when ensuring it is in the viewport. Default is `0`.\n\n#### `scrollTo: (destination: Partial\u003cVector\u003e | 'top' | 'bottom' | 'left' | 'right', options?: ScrollOptions) =\u003e Promise\u003cvoid\u003e`\n\nScrolls to the specified destination point.\n\n- **destination:** An object with `x` and `y` coordinates representing the target position. For example, `{ x: 500, y: 300 }`. Can also be `\"top\"` or `\"bottom\"`.\n- **options (optional):** Additional options for scrolling. **Extends the `options` of the `scroll` function (below)**\n\n#### `scroll: (delta: Partial\u003cVector\u003e, options?: ScrollOptions) =\u003e Promise\u003cvoid\u003e`\n\nScrolls the page the distance set by `delta`.\n\n- **delta:** An object with `x` and `y` coordinates representing the distance to scroll from the current position.\n- **options (optional):** Additional options for scrolling.\n  - `scrollSpeed (number):` Scroll speed. 0 to 100. 100 is instant. Default is `100`.\n  - `scrollDelay (number):` Time to wait after scrolling. Default is `200`.\n  \n#### `getElement(selector: string | ElementHandle, options?: GetElementOptions) =\u003e Promise\u003cvoid\u003e`\n\nGets the element via a selector. Can use an XPath.\n\n- **selector:** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options.\n  - `waitForSelector (number):` Time to wait for the selector to appear in milliseconds. Default is to not wait for selector.\n\n#### `getLocation(): Vector`\n\nGet current location of the cursor.\n\n### Other Utility Methods\n\n#### `installMouseHelper(page: Page): Promise\u003cvoid\u003e`\n\nInstalls a mouse helper on the page. Makes pointer visible. Use for debugging only.\n\n#### `getRandomPagePoint(page: Page): Promise\u003cVector\u003e`\n\nGets a random point on the browser window.\n\n#### `path(point: Vector, target: Vector, options?: number | PathOptions): Vector[] | TimedVector[]`\n\nGenerates a set of points for mouse movement between two coordinates.\n\n- **point:** Starting point of the movement.\n- **target:** Ending point of the movement.\n- **options (optional):** Additional options for generating the path. Can also be a number which will set `spreadOverride`.\n  - `spreadOverride (number):` Override the spread of the generated path.\n  - `moveSpeed (number):` Speed of mouse movement. Default is random.\n  - `useTimestamps (boolean):` Generate timestamps for each point based on the trapezoidal rule.\n\n## How does it work\n\nBezier curves do almost all the work here. They let us create an infinite amount of curves between any 2 points we want\nand they look quite human-like. (At least moreso than alternatives like perlin or simplex noise)\n\n![](https://mamamoo.xetera.dev/😽🤵👲🧦👵.png)\n\nThe magic comes from being able to set multiple points for the curve to go through. This is done by picking\n2 coordinates randomly in a limited area above and under the curve. \n\n\u003cimg src=\"https://mamamoo.xetera.dev/🧣👎😠🧟✍.png\" width=\"400\"\u003e\n\nHowever, we don't want wonky looking cubic curves when using this method because nobody really moves their mouse\nthat way, so only one side of the line is picked when generating random points.\n\n\u003cimg src=\"http://simonwallner.at/ext/fitts/shannon.png\" width=\"250\" align=\"right\"\u003e\nWhen calculating how fast the mouse should be moving we use \u003ca href=\"https://en.wikipedia.org/wiki/Fitts%27s_law\"\u003eFitts's Law\u003c/a\u003e\nto determine the amount of points we should be returning relative to the width of the element being clicked on and the distance\nbetween the mouse and the object.\n\n## To turn on logging, please set your DEBUG env variable like so:\n\n- OSX: `DEBUG=\"ghost-cursor:*\"`\n- Linux: `DEBUG=\"ghost-cursor:*\"`\n- Windows CMD: `set DEBUG=ghost-cursor:*`\n- Windows PowerShell: `$env:DEBUG = \"ghost-cursor:*\"`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxetera%2Fghost-cursor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxetera%2Fghost-cursor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxetera%2Fghost-cursor/lists"}