UNPKG

bezier-mouse-js

Version:

Lightweight javascript library to mirror human-like mouse movements with Bézier curves.

128 lines (112 loc) 4.05 kB
/** * @module BezierMouse */ const { Bezier } = require("bezier-js"); const { Point, mouse, Button, Region, randomPointIn, } = require("@nut-tree/nut-js"); const Utils = require("./Utils.js"); const { abs, ceil } = Math; /** * BezierMouse class generates natural mouse movement using bezier curves */ class BezierMouse { /** * constructor * @param {number} mouseSpeed - mouse movement speed in pixels per second */ constructor(mouseSpeed) { mouse.config.mouseSpeed = mouseSpeed || 100; } /** * moveAndClick: moves mouse on a bezier curve and clicks * @param {Point} initPos * @param {Point} finPos * @param {String} clickType - enum: LEFT, MIDDLE, RIGHT * @param {Options} opts */ async moveAndClick(initPos, finPos, clickType = "LEFT", opts = {}) { await this.move(initPos, finPos, opts); await mouse.click(Button[clickType]); } /** * moveAndDoubleClick: moves mouse on a bezier curve and double clicks * @param {Point} initPos * @param {Point} finPos * @param {String} clickType - enum: LEFT, MIDDLE, RIGHT * @param {Options} opts */ async moveAndDoubleClick(initPos, finPos, clickType = "LEFT", opts = {}) { await this.move(initPos, finPos, opts); await mouse.doubleClick(Button[clickType]); } /** * move: moves mouse on a bezier curve * @param {Point} initPos * @param {Point} finPos * @param {Options} opts */ async move(initPos, finPos, opts = {}) { let finishPosition = finPos; if (!opts.preciseClick) { const deviation = 5; const randDeviation = () => Utils.randint(deviation / 2, deviation); const region = new Region( finPos.x, finPos.y, randDeviation(), randDeviation() ); finishPosition = await randomPointIn(region); } await mouse.move(this.bezierCurveTo(initPos, finishPosition, opts)); } // async click(finPos, clickType, natural = true) { // // TODO: // // - factor in a random (not very likely) total misclick, where the region is large // // - if this misclick=true, then do a recovery click after to ensure click is made // // await mouse.click(Button[clickType]); // } async bezierCurveTo(initPos, finPos, opts = {}) { const curve = this.cubicBezierCurve(initPos, finPos, opts); const LUT = curve.getLUT(opts.steps || 100); return LUT.map((point) => new Point(point.x, point.y)); } cubicBezierCurve(initPos, finPos, opts = {}) { const cntlPt1 = this.getBezierControlPoint(initPos, finPos, opts); const cntlPt2 = this.getBezierControlPoint(initPos, finPos, opts); const cubicPoints = [initPos, cntlPt1, cntlPt2, finPos]; return new Bezier(cubicPoints); } getBezierControlPoint(initPos, finPos, opts = {}) { const { deviation = 20, flip = false } = opts; const deltaX = abs(ceil(finPos.x) - ceil(initPos.x)); const deltaY = abs(ceil(finPos.y) - ceil(initPos.y)); const randDeviation = () => Utils.randint(deviation / 2, deviation); const refPointX = flip ? initPos.x : finPos.x; const refPointY = flip ? initPos.y : finPos.y; return { x: refPointX + Utils.choice([-1, 1]) * deltaX * 0.01 * randDeviation(), y: refPointY + Utils.choice([-1, 1]) * deltaY * 0.01 * randDeviation(), }; } } /* typedefs */ /** * @typedef {Object} Point * @property {number} x - The x-coordinate of the point. * @property {number} y - The y-coordinate of the point. */ /** * @typedef {Object} Options * @property {number} deviation - deviation scale (larger = more curve). default=20. * @property {number} flip - controls where the control points are anchored from, false: curves closer to finish position, true: curves closer to init position. default=false. * @property {number} steps - number of steps (points) when moving on the bezier curve (t=0 to t=1 at interval 1/steps). default=100. * @property {boolean} preciseClick - controls if click action should be on exact point. default=false. */ module.exports = BezierMouse;