From 9610f043148ba242cd79bf6b0468651f9c12ee9b Mon Sep 17 00:00:00 2001 From: obito <64253722+obito@users.noreply.github.com> Date: Mon, 16 Nov 2020 19:59:50 +0100 Subject: [PATCH] init --- README.md | 34 +++++++++++++ go.mod | 3 ++ gomouse.go | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ gomouse_test.go | 26 ++++++++++ 4 files changed, 196 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 gomouse.go create mode 100644 gomouse_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..871f0fe --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# GoMouse + +Library to generate human-like mouse mouvement. + +# Usage + +```go +func TestGeneratePoints(t *testing.T) { + settings := MouseSettings{ + StartX: math.Ceil(RandomNumberFloat() * 1920), + StartY: math.Ceil(RandomNumberFloat() * 1080), + EndX: math.Ceil(RandomNumberFloat() * 1920), + EndY: math.Ceil(RandomNumberFloat() * 1080), + Gravity: math.Ceil(RandomNumberFloat() * 10), + Wind: math.Ceil(RandomNumberFloat() * 10), + MinWait: 2.0, + MaxWait: math.Ceil(RandomNumberFloat() * 5), + MaxStep: math.Ceil(RandomNumberFloat() * 3), + TargetArea: math.Ceil(RandomNumberFloat() * 10), + } + + points := GeneratePoints(settings) + + log.Print(points) +} +``` + +# Credits + +Thanks to [@BenLand100](https://github.com/BenLand100) for the [original WindMouse library in Java](https://github.com/BenLand100/SMART/blob/157e50691b4b63a0950fac06deccac26aae31f88/src/EventNazi.java#L201). All I did is porting it to Go. + +# Visualizer + +You can use [Mouse Data Visualizer](https://github.com/arevi/mouse-data-visualizer) made by [@arevi](https://github.com/arevi) to tune your mouse settings. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..59ce4a6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/obito/gomouse + +go 1.15 diff --git a/gomouse.go b/gomouse.go new file mode 100644 index 0000000..7aec31f --- /dev/null +++ b/gomouse.go @@ -0,0 +1,133 @@ +package gomouse + +import ( + cryptorand "crypto/rand" + "encoding/binary" + "math" + "math/rand" +) + +// MouseSettings initiate the mouse settings +type MouseSettings struct { + StartX float64 + StartY float64 + EndX float64 + EndY float64 + Gravity float64 + Wind float64 + MinWait float64 + MaxWait float64 + MaxStep float64 + TargetArea float64 +} + +func RandomNumberFloat() float64 { + // avoid pitfalls of clock based seed value + var b [8]byte + _, err := cryptorand.Read(b[:]) + if err != nil { + panic("cannot seed math/rand package with cryptographically secure random number generator") + } + + r := rand.New(rand.NewSource(int64(binary.LittleEndian.Uint64(b[:])))) + return r.Float64() +} + +func hypot(dx, dy float64) float64 { + return math.Sqrt(dx*dx + dy*dy) +} + +func GeneratePoints(settings MouseSettings) [][]float64 { + if settings.Gravity < 1 { + settings.Gravity = 1 + } + + if settings.MaxStep == 0 { + settings.MaxStep = 0.01 + } + + windX := math.Floor(RandomNumberFloat() * 10) + windY := math.Floor(RandomNumberFloat() * 10) + + var oldX float64 + var oldY float64 + newX := math.Floor(settings.StartX) + newY := math.Floor(settings.StartY) + + waitDiff := settings.MaxWait - settings.MinWait + + // Hardcore instead of doing math.sqrt, maybe saving us some computiong time + sqrt2 := 1.4142135623730951 + sqrt3 := 1.7320508075688772 + sqrt5 := 2.23606797749979 + + var randomDist float64 + var velocityX float64 = 0 + var velocityY float64 = 0 + var dist float64 + var veloMag float64 + var step float64 + + var points [][]float64 + var currentWait float64 = 0 + + dist = hypot(settings.EndX-settings.StartX, settings.EndY-settings.StartY) + + for dist > 1.0 { + settings.Wind = math.Min(settings.Wind, dist) + + if dist >= settings.TargetArea { + w := math.Floor(RandomNumberFloat()*math.Round(settings.Wind)*2 + 1) + + windX = windX/sqrt3 + (w-settings.Wind)/sqrt5 + windY = windY/sqrt3 + (w-settings.Wind)/sqrt5 + } else { + windX = windX / sqrt2 + windY = windY / sqrt2 + + if settings.MaxStep < 3 { + settings.MaxStep = math.Floor(RandomNumberFloat()*3) + 3.0 + } else { + settings.MaxStep = settings.MaxStep / sqrt5 + } + } + + velocityX += windX + velocityY += windY + velocityX = velocityX + (settings.Gravity*(settings.EndX-settings.StartX))/dist + velocityY = velocityY + (settings.Gravity*(settings.EndY-settings.StartY))/dist + + if hypot(velocityX, velocityY) > settings.MaxStep { + randomDist = settings.MaxStep/2.0 + math.Floor((RandomNumberFloat()*math.Round(settings.MaxStep))/2) + veloMag = hypot(velocityX, velocityY) + velocityX = (velocityX / veloMag) * randomDist + velocityY = (velocityY / veloMag) * randomDist + } + + oldX = math.Round(settings.StartX) + oldY = math.Round(settings.StartY) + + settings.StartX += velocityX + settings.StartY += velocityY + + dist = hypot(settings.EndX-settings.StartX, settings.EndY-settings.StartY) + + newX = math.Round(settings.StartX) + newY = math.Round(settings.StartY) + + step = hypot(settings.StartX-oldX, settings.StartY-oldY) + wait := math.Round(waitDiff*(step/settings.MaxStep) + settings.MinWait) + + currentWait += wait + + if oldX != newY || oldY != newY { + points = append(points, []float64{ + newX, + newY, + currentWait, + }) + } + } + + return points +} diff --git a/gomouse_test.go b/gomouse_test.go new file mode 100644 index 0000000..61f17fa --- /dev/null +++ b/gomouse_test.go @@ -0,0 +1,26 @@ +package gomouse + +import ( + "log" + "math" + "testing" +) + +func TestGeneratePoints(t *testing.T) { + settings := MouseSettings{ + StartX: math.Ceil(RandomNumberFloat() * 1920), + StartY: math.Ceil(RandomNumberFloat() * 1080), + EndX: math.Ceil(RandomNumberFloat() * 1920), + EndY: math.Ceil(RandomNumberFloat() * 1080), + Gravity: math.Ceil(RandomNumberFloat() * 10), + Wind: math.Ceil(RandomNumberFloat() * 10), + MinWait: 2.0, + MaxWait: math.Ceil(RandomNumberFloat() * 5), + MaxStep: math.Ceil(RandomNumberFloat() * 3), + TargetArea: math.Ceil(RandomNumberFloat() * 10), + } + + points := GeneratePoints(settings) + + log.Print(points) +}