package browser import ( "fmt" "math" "math/rand" "time" ) // MouseHumanOpts configura el movimiento humano del ratón. type MouseHumanOpts struct { // Steps es el número de puntos intermedios de la curva (default 25). Steps int // DurationMs es la duración total aproximada del movimiento en milisegundos. // Si es 0, se elige aleatoriamente entre 350 y 800 ms. DurationMs int // JitterPx es la desviación perpendicular máxima por punto en píxeles (default 2.0). JitterPx float64 // FromX es la coordenada X de origen. Si < 0, se usa (0, 0) como origen. FromX float64 // FromY es la coordenada Y de origen. Si < 0, se usa (0, 0) como origen. FromY float64 } // mouseHumanDefaults aplica valores por defecto a opts. func mouseHumanDefaults(opts MouseHumanOpts) MouseHumanOpts { if opts.Steps <= 0 { opts.Steps = 25 } if opts.DurationMs <= 0 { opts.DurationMs = 350 + rand.Intn(451) // 350..800 } if opts.JitterPx <= 0 { opts.JitterPx = 2.0 } if opts.FromX < 0 { opts.FromX = 0 } if opts.FromY < 0 { opts.FromY = 0 } return opts } // smoothstep aplica easing suave (ease-in-out) al parámetro t ∈ [0,1]. // Produce aceleración inicial y desaceleración final, imitando movimiento humano. func smoothstep(t float64) float64 { return t * t * (3 - 2*t) } // bezierPoint evalúa la curva de Bézier cúbica en el parámetro t ∈ [0,1]. // p0 = origen, p1/p2 = puntos de control, p3 = destino. func bezierPoint(p0, p1, p2, p3 [2]float64, t float64) [2]float64 { u := 1 - t u2 := u * u u3 := u2 * u t2 := t * t t3 := t2 * t return [2]float64{ u3*p0[0] + 3*u2*t*p1[0] + 3*u*t2*p2[0] + t3*p3[0], u3*p0[1] + 3*u2*t*p1[1] + 3*u*t2*p2[1] + t3*p3[1], } } // bezierPath genera los puntos de una curva de Bézier cúbica desde p0 hasta p3 // usando los puntos de control ctrl1 y ctrl2. Retorna steps+1 puntos // (incluye origen y destino). Esta función es pura y testeable sin Chrome. func bezierPath(p0, p3, ctrl1, ctrl2 [2]float64, steps int) [][2]float64 { if steps < 1 { steps = 1 } pts := make([][2]float64, steps+1) for i := 0; i <= steps; i++ { t := smoothstep(float64(i) / float64(steps)) pts[i] = bezierPoint(p0, ctrl1, ctrl2, p3, t) } return pts } // randomControlPoints genera dos puntos de control aleatorios desplazados // lateralmente del segmento recto p0→p3, produciendo el arco curvo humano. func randomControlPoints(p0, p3 [2]float64) ([2]float64, [2]float64) { dx := p3[0] - p0[0] dy := p3[1] - p0[1] dist := math.Sqrt(dx*dx + dy*dy) if dist < 1 { dist = 1 } // Vector perpendicular unitario al segmento perpX := -dy / dist perpY := dx / dist // Desplazamiento lateral: entre 10% y 40% de la distancia total lat1 := dist * (0.1 + rand.Float64()*0.3) * (1 - 2*float64(rand.Intn(2))) lat2 := dist * (0.1 + rand.Float64()*0.3) * (1 - 2*float64(rand.Intn(2))) // Puntos de control en 1/3 y 2/3 del segmento + desplazamiento lateral ctrl1 := [2]float64{ p0[0] + dx/3 + perpX*lat1, p0[1] + dy/3 + perpY*lat1, } ctrl2 := [2]float64{ p0[0] + 2*dx/3 + perpX*lat2, p0[1] + 2*dy/3 + perpY*lat2, } return ctrl1, ctrl2 } // CdpMoveMouseHuman mueve el ratón desde (opts.FromX, opts.FromY) hasta (toX, toY) // siguiendo una trayectoria de Bézier cúbica con easing suave y micro-jitter, // imitando el movimiento humano para reducir la detección de automatización. // // Despacha Input.dispatchMouseEvent {type:"mouseMoved"} en cada punto de la curva // con pausas proporcionales a DurationMs/Steps (±20% de variación aleatoria). func CdpMoveMouseHuman(c *CDPConn, toX, toY float64, opts MouseHumanOpts) error { if c == nil { return fmt.Errorf("cdp move mouse human: conexion nula") } opts = mouseHumanDefaults(opts) p0 := [2]float64{opts.FromX, opts.FromY} p3 := [2]float64{toX, toY} ctrl1, ctrl2 := randomControlPoints(p0, p3) pts := bezierPath(p0, p3, ctrl1, ctrl2, opts.Steps) // Pausa base por paso en microsegundos baseStepUs := int64(opts.DurationMs) * 1000 / int64(opts.Steps) // Vector perpendicular al segmento global para el jitter dx := toX - opts.FromX dy := toY - opts.FromY dist := math.Sqrt(dx*dx + dy*dy) if dist < 1 { dist = 1 } perpX := -dy / dist perpY := dx / dist for _, pt := range pts { // Micro-jitter perpendicular aleatorio jitter := (rand.Float64()*2 - 1) * opts.JitterPx x := pt[0] + perpX*jitter y := pt[1] + perpY*jitter if _, err := c.sendCDP("Input.dispatchMouseEvent", map[string]any{ "type": "mouseMoved", "x": x, "y": y, }); err != nil { return fmt.Errorf("cdp move mouse human: mouseMoved: %w", err) } // Pausa con variación ±20% variation := int64(float64(baseStepUs) * (0.8 + rand.Float64()*0.4)) time.Sleep(time.Duration(variation) * time.Microsecond) } return nil }