103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
// Package input — humanize.go provides human-like behavior randomization.
|
|
package input
|
|
|
|
import (
|
|
"image"
|
|
"math/rand"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// HumanProfile defines behavior parameters for human-like input.
|
|
type HumanProfile struct {
|
|
ReactionMin time.Duration // min reaction delay
|
|
ReactionMax time.Duration // max reaction delay
|
|
MouseSpeedMin float64 // min pixels per second
|
|
MouseSpeedMax float64 // max pixels per second
|
|
ClickJitter int // pixel jitter on clicks
|
|
HesitationChance float64 // chance of extra pause (0-1)
|
|
HesitationMin time.Duration
|
|
HesitationMax time.Duration
|
|
}
|
|
|
|
// DefaultProfile returns a realistic human behavior profile.
|
|
func DefaultProfile() HumanProfile {
|
|
return HumanProfile{
|
|
ReactionMin: 150 * time.Millisecond,
|
|
ReactionMax: 450 * time.Millisecond,
|
|
MouseSpeedMin: 400,
|
|
MouseSpeedMax: 1200,
|
|
ClickJitter: 3,
|
|
HesitationChance: 0.1,
|
|
HesitationMin: 300 * time.Millisecond,
|
|
HesitationMax: 1200 * time.Millisecond,
|
|
}
|
|
}
|
|
|
|
// Humanizer applies human-like randomization to bot actions.
|
|
type Humanizer struct {
|
|
Profile HumanProfile
|
|
actionCount atomic.Int64
|
|
}
|
|
|
|
// NewHumanizer creates a humanizer with the given profile.
|
|
func NewHumanizer(profile HumanProfile) *Humanizer {
|
|
return &Humanizer{Profile: profile}
|
|
}
|
|
|
|
// ReactionDelay returns a randomized human-like reaction delay.
|
|
func (h *Humanizer) ReactionDelay() time.Duration {
|
|
minMs := h.Profile.ReactionMin.Milliseconds()
|
|
maxMs := h.Profile.ReactionMax.Milliseconds()
|
|
base := time.Duration(minMs+rand.Int63n(maxMs-minMs)) * time.Millisecond
|
|
|
|
// Occasional hesitation
|
|
if rand.Float64() < h.Profile.HesitationChance {
|
|
hesMinMs := h.Profile.HesitationMin.Milliseconds()
|
|
hesMaxMs := h.Profile.HesitationMax.Milliseconds()
|
|
base += time.Duration(hesMinMs+rand.Int63n(hesMaxMs-hesMinMs)) * time.Millisecond
|
|
}
|
|
|
|
// Slight fatigue factor
|
|
actions := float64(h.actionCount.Load())
|
|
fatigue := min(actions/1000.0, 0.3)
|
|
base = time.Duration(float64(base) * (1 + fatigue*rand.Float64()))
|
|
|
|
return base
|
|
}
|
|
|
|
// Wait pauses for a human-like reaction delay.
|
|
func (h *Humanizer) Wait() {
|
|
time.Sleep(h.ReactionDelay())
|
|
h.actionCount.Add(1)
|
|
}
|
|
|
|
// WaitMs pauses for baseMs ± varianceMs with human randomization.
|
|
func (h *Humanizer) WaitMs(baseMs, varianceMs int) {
|
|
actual := baseMs + rand.Intn(2*varianceMs+1) - varianceMs
|
|
if actual < 0 {
|
|
actual = 0
|
|
}
|
|
time.Sleep(time.Duration(actual) * time.Millisecond)
|
|
}
|
|
|
|
// JitterPosition adds random offset to a click position.
|
|
func (h *Humanizer) JitterPosition(pos image.Point) image.Point {
|
|
j := h.Profile.ClickJitter
|
|
return image.Point{
|
|
X: pos.X + rand.Intn(2*j+1) - j,
|
|
Y: pos.Y + rand.Intn(2*j+1) - j,
|
|
}
|
|
}
|
|
|
|
// MouseSpeed returns a randomized mouse movement speed.
|
|
func (h *Humanizer) MouseSpeed() float64 {
|
|
return h.Profile.MouseSpeedMin + rand.Float64()*(h.Profile.MouseSpeedMax-h.Profile.MouseSpeedMin)
|
|
}
|
|
|
|
func min(a, b float64) float64 {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|