d7f2c00d7b
- Migration 007: repo_url on apps table + analysis table with FTS5 - Analysis struct, parser, CRUD, validation, hash computation - Selective purge: remote-only apps/analysis preserved across fn index - CLI: fn app list/clone/pull, fn analysis list/clone/pull - search/show/list now include analysis results - Apps removed from git tracking (content lives in Gitea repos) - .gitkeep for apps/ and analysis/ dirs - Bash functions: jupyter analysis pipeline, shell utilities - Browser domain: CDP functions moved from infra to browser Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package browser
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// CdpClick hace click en el primer elemento que coincide con el selector CSS.
|
|
// Obtiene las coordenadas del elemento via Runtime.evaluate y luego despacha
|
|
// eventos mousedown, mouseup y click via Input.dispatchMouseEvent.
|
|
func CdpClick(c *CDPConn, selector string) error {
|
|
if c == nil {
|
|
return fmt.Errorf("cdp click: conexion nula")
|
|
}
|
|
|
|
// Obtener coordenadas del centro del elemento
|
|
js := fmt.Sprintf(`(function() {
|
|
var el = document.querySelector(%q);
|
|
if (!el) return null;
|
|
var r = el.getBoundingClientRect();
|
|
return JSON.stringify({x: r.left + r.width/2, y: r.top + r.height/2});
|
|
})()`, selector)
|
|
|
|
coordStr, err := CdpEvaluate(c, js)
|
|
if err != nil {
|
|
return fmt.Errorf("cdp click: obtener coordenadas de %q: %w", selector, err)
|
|
}
|
|
if coordStr == "" || coordStr == "null" {
|
|
return fmt.Errorf("cdp click: elemento %q no encontrado en el DOM", selector)
|
|
}
|
|
|
|
// Parsear "{x:...,y:...}" — CdpEvaluate ya retorna el JSON como string
|
|
coordStr = strings.Trim(coordStr, `"`)
|
|
x, y, err := parseCoords(coordStr)
|
|
if err != nil {
|
|
return fmt.Errorf("cdp click: parsear coordenadas %q: %w", coordStr, err)
|
|
}
|
|
|
|
// Hacer scroll al elemento para que este visible
|
|
scrollJS := fmt.Sprintf(`document.querySelector(%q).scrollIntoView({block:'center'})`, selector)
|
|
if _, err := CdpEvaluate(c, scrollJS); err != nil {
|
|
// No es fatal si el scroll falla
|
|
_ = err
|
|
}
|
|
|
|
// Despachar mousedown
|
|
mouseParams := map[string]any{
|
|
"type": "mousePressed",
|
|
"x": x,
|
|
"y": y,
|
|
"button": "left",
|
|
"clickCount": 1,
|
|
}
|
|
if _, err := c.sendCDP("Input.dispatchMouseEvent", mouseParams); err != nil {
|
|
return fmt.Errorf("cdp click: mousedown: %w", err)
|
|
}
|
|
|
|
// Despachar mouseup
|
|
mouseParams["type"] = "mouseReleased"
|
|
if _, err := c.sendCDP("Input.dispatchMouseEvent", mouseParams); err != nil {
|
|
return fmt.Errorf("cdp click: mouseup: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseCoords extrae x e y de un string JSON como {"x":100,"y":200}.
|
|
func parseCoords(s string) (float64, float64, error) {
|
|
// Buscar valores x e y manualmente para evitar dependencia de encoding/json
|
|
s = strings.TrimSpace(s)
|
|
s = strings.TrimPrefix(s, "{")
|
|
s = strings.TrimSuffix(s, "}")
|
|
|
|
var x, y float64
|
|
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]), `"`)
|
|
v, err := strconv.ParseFloat(strings.TrimSpace(kv[1]), 64)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("parsear valor %q: %w", kv[1], err)
|
|
}
|
|
switch k {
|
|
case "x":
|
|
x = v
|
|
case "y":
|
|
y = v
|
|
}
|
|
}
|
|
return x, y, nil
|
|
}
|