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 }