Files
fn_registry/functions/browser/cdp_click_human.go
T
egutierrez 029dbf57bd feat(core): auto-commit con 10 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 13:20:36 +02:00

95 lines
2.8 KiB
Go

package browser
import (
"fmt"
"math/rand"
"strings"
)
// CdpClickHuman hace click en el elemento identificado por selector CSS con
// movimiento humano: obtiene el bbox, calcula un punto destino ligeramente
// desplazado del centro, mueve el ratón por una trayectoria de Bézier cúbica
// y luego despacha mousePressed/mouseReleased con una micro-pausa entre ellos.
//
// opts controla la trayectoria del movimiento previo al click.
// Para configurar el origen del movimiento usa opts.FromX / opts.FromY.
func CdpClickHuman(c *CDPConn, selector string, opts MouseHumanOpts) error {
if c == nil {
return fmt.Errorf("cdp click human: conexion nula")
}
// Obtener bounding box del selector
js := fmt.Sprintf(`(function() {
var el = document.querySelector(%q);
if (!el) return null;
var r = el.getBoundingClientRect();
return JSON.stringify({x: r.left, y: r.top, w: r.width, h: r.height});
})()`, selector)
bboxStr, err := CdpEvaluate(c, js)
if err != nil {
return fmt.Errorf("cdp click human: obtener bbox de %q: %w", selector, err)
}
if bboxStr == "" || bboxStr == "null" {
return fmt.Errorf("cdp click human: elemento %q no encontrado en el DOM", selector)
}
bboxStr = strings.Trim(bboxStr, `"`)
bx, by, bw, bh, err := parseBbox(bboxStr)
if err != nil {
return fmt.Errorf("cdp click human: parsear bbox %q: %w", bboxStr, err)
}
// Scroll al elemento para que sea visible
scrollJS := fmt.Sprintf(`document.querySelector(%q).scrollIntoView({block:'center'})`, selector)
if _, err := CdpEvaluate(c, scrollJS); err != nil {
_ = err // no fatal
}
// Punto destino: centro + pequeño offset aleatorio (±15% del tamaño)
offX := (rand.Float64()*2 - 1) * bw * 0.15
offY := (rand.Float64()*2 - 1) * bh * 0.15
toX := bx + bw/2 + offX
toY := by + bh/2 + offY
// Delegar en el primitivo compartido: mueve el ratón con trayectoria humana
// y despacha press/release con micro-pausa.
if err := CdpClickXYHuman(c, toX, toY, opts); err != nil {
return fmt.Errorf("cdp click human: %w", err)
}
return nil
}
// parseBbox extrae left, top, width, height de un JSON como {"x":10,"y":20,"w":100,"h":40}.
func parseBbox(s string) (left, top, width, height float64, err error) {
// Reutiliza el mismo parser manual que parseCoords para evitar encoding/json
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "{")
s = strings.TrimSuffix(s, "}")
for part := range strings.SplitSeq(s, ",") {
kv := strings.SplitN(strings.TrimSpace(part), ":", 2)
if len(kv) != 2 {
continue
}
k := strings.Trim(strings.TrimSpace(kv[0]), `"`)
var v float64
if _, e := fmt.Sscanf(strings.TrimSpace(kv[1]), "%f", &v); e != nil {
err = fmt.Errorf("parsear valor %q: %w", kv[1], e)
return
}
switch k {
case "x":
left = v
case "y":
top = v
case "w":
width = v
case "h":
height = v
}
}
return
}