ccfa5bc78b
Funciones nuevas del dominio browser (grupo navegator): - cdp_move_mouse_human / cdp_click_human: movimiento de raton con curva de Bezier cubica, easing y micro-jitter para imitar comportamiento humano y reducir deteccion de automatizacion. - cdp_wait_idle: espera network-idle contando requests en vuelo via eventos CDP Network.*; inmune a extensiones que mutan el DOM (Dark Reader, uBlock) y a animaciones JS. - list_chrome_profiles: lista perfiles de un user-data-dir (extensiones, nombre legible, preferencias). - prepare_chrome_profile (bash): clona un user-data-dir conservando solo una whitelist de extensiones (default uBlock Origin Lite). Modificadas: - chrome_launch: Linux-first (chromium/google-chrome/brave antes que chrome.exe), KeepExtensions y Setpgid para matar el arbol con cdp_close. - cdp_close: kill por grupo de proceso. Todas con tests verdes (go test ./functions/browser ok).
162 lines
4.7 KiB
Go
162 lines
4.7 KiB
Go
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
|
|
}
|