├── go.mod ├── gomouse_test.go ├── README.md └── gomouse.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/obito/gomouse 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /gomouse_test.go: -------------------------------------------------------------------------------- 1 | package gomouse 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestGeneratePoints(t *testing.T) { 10 | settings := MouseSettings{ 11 | StartX: math.Ceil(RandomNumberFloat() * 1920), 12 | StartY: math.Ceil(RandomNumberFloat() * 1080), 13 | EndX: math.Ceil(RandomNumberFloat() * 1920), 14 | EndY: math.Ceil(RandomNumberFloat() * 1080), 15 | Gravity: math.Ceil(RandomNumberFloat() * 10), 16 | Wind: math.Ceil(RandomNumberFloat() * 10), 17 | MinWait: 2.0, 18 | MaxWait: math.Ceil(RandomNumberFloat() * 5), 19 | MaxStep: math.Ceil(RandomNumberFloat() * 3), 20 | TargetArea: math.Ceil(RandomNumberFloat() * 10), 21 | } 22 | 23 | points := GeneratePoints(settings) 24 | 25 | log.Print(points) 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoMouse 2 | 3 | Library to generate human-like mouse mouvement. 4 | 5 | # Usage 6 | 7 | ```go 8 | func TestGeneratePoints(t *testing.T) { 9 | settings := MouseSettings{ 10 | StartX: math.Ceil(RandomNumberFloat() * 1920), 11 | StartY: math.Ceil(RandomNumberFloat() * 1080), 12 | EndX: math.Ceil(RandomNumberFloat() * 1920), 13 | EndY: math.Ceil(RandomNumberFloat() * 1080), 14 | Gravity: math.Ceil(RandomNumberFloat() * 10), 15 | Wind: math.Ceil(RandomNumberFloat() * 10), 16 | MinWait: 2.0, 17 | MaxWait: math.Ceil(RandomNumberFloat() * 5), 18 | MaxStep: math.Ceil(RandomNumberFloat() * 3), 19 | TargetArea: math.Ceil(RandomNumberFloat() * 10), 20 | } 21 | 22 | points := GeneratePoints(settings) 23 | 24 | log.Print(points) 25 | } 26 | ``` 27 | 28 | # Credits 29 | 30 | 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. 31 | 32 | # Visualizer 33 | 34 | 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. -------------------------------------------------------------------------------- /gomouse.go: -------------------------------------------------------------------------------- 1 | package gomouse 2 | 3 | import ( 4 | cryptorand "crypto/rand" 5 | "encoding/binary" 6 | "math" 7 | "math/rand" 8 | ) 9 | 10 | // MouseSettings initiate the mouse settings 11 | type MouseSettings struct { 12 | StartX float64 13 | StartY float64 14 | EndX float64 15 | EndY float64 16 | Gravity float64 17 | Wind float64 18 | MinWait float64 19 | MaxWait float64 20 | MaxStep float64 21 | TargetArea float64 22 | } 23 | 24 | func RandomNumberFloat() float64 { 25 | // avoid pitfalls of clock based seed value 26 | var b [8]byte 27 | _, err := cryptorand.Read(b[:]) 28 | if err != nil { 29 | panic("cannot seed math/rand package with cryptographically secure random number generator") 30 | } 31 | 32 | r := rand.New(rand.NewSource(int64(binary.LittleEndian.Uint64(b[:])))) 33 | return r.Float64() 34 | } 35 | 36 | func hypot(dx, dy float64) float64 { 37 | return math.Sqrt(dx*dx + dy*dy) 38 | } 39 | 40 | func GeneratePoints(settings MouseSettings) [][]float64 { 41 | if settings.Gravity < 1 { 42 | settings.Gravity = 1 43 | } 44 | 45 | if settings.MaxStep == 0 { 46 | settings.MaxStep = 0.01 47 | } 48 | 49 | windX := math.Floor(RandomNumberFloat() * 10) 50 | windY := math.Floor(RandomNumberFloat() * 10) 51 | 52 | var oldX float64 53 | var oldY float64 54 | newX := math.Floor(settings.StartX) 55 | newY := math.Floor(settings.StartY) 56 | 57 | waitDiff := settings.MaxWait - settings.MinWait 58 | 59 | // Hardcore instead of doing math.sqrt, maybe saving us some computiong time 60 | sqrt2 := 1.4142135623730951 61 | sqrt3 := 1.7320508075688772 62 | sqrt5 := 2.23606797749979 63 | 64 | var randomDist float64 65 | var velocityX float64 = 0 66 | var velocityY float64 = 0 67 | var dist float64 68 | var veloMag float64 69 | var step float64 70 | 71 | var points [][]float64 72 | var currentWait float64 = 0 73 | 74 | dist = hypot(settings.EndX-settings.StartX, settings.EndY-settings.StartY) 75 | 76 | for dist > 1.0 { 77 | settings.Wind = math.Min(settings.Wind, dist) 78 | 79 | if dist >= settings.TargetArea { 80 | w := math.Floor(RandomNumberFloat()*math.Round(settings.Wind)*2 + 1) 81 | 82 | windX = windX/sqrt3 + (w-settings.Wind)/sqrt5 83 | windY = windY/sqrt3 + (w-settings.Wind)/sqrt5 84 | } else { 85 | windX = windX / sqrt2 86 | windY = windY / sqrt2 87 | 88 | if settings.MaxStep < 3 { 89 | settings.MaxStep = math.Floor(RandomNumberFloat()*3) + 3.0 90 | } else { 91 | settings.MaxStep = settings.MaxStep / sqrt5 92 | } 93 | } 94 | 95 | velocityX += windX 96 | velocityY += windY 97 | velocityX = velocityX + (settings.Gravity*(settings.EndX-settings.StartX))/dist 98 | velocityY = velocityY + (settings.Gravity*(settings.EndY-settings.StartY))/dist 99 | 100 | if hypot(velocityX, velocityY) > settings.MaxStep { 101 | randomDist = settings.MaxStep/2.0 + math.Floor((RandomNumberFloat()*math.Round(settings.MaxStep))/2) 102 | veloMag = hypot(velocityX, velocityY) 103 | velocityX = (velocityX / veloMag) * randomDist 104 | velocityY = (velocityY / veloMag) * randomDist 105 | } 106 | 107 | oldX = math.Round(settings.StartX) 108 | oldY = math.Round(settings.StartY) 109 | 110 | settings.StartX += velocityX 111 | settings.StartY += velocityY 112 | 113 | dist = hypot(settings.EndX-settings.StartX, settings.EndY-settings.StartY) 114 | 115 | newX = math.Round(settings.StartX) 116 | newY = math.Round(settings.StartY) 117 | 118 | step = hypot(settings.StartX-oldX, settings.StartY-oldY) 119 | wait := math.Round(waitDiff*(step/settings.MaxStep) + settings.MinWait) 120 | 121 | currentWait += wait 122 | 123 | if oldX != newY || oldY != newY { 124 | points = append(points, []float64{ 125 | newX, 126 | newY, 127 | currentWait, 128 | }) 129 | } 130 | } 131 | 132 | return points 133 | } 134 | --------------------------------------------------------------------------------