Files
fn_registry/functions/browser/list_chrome_profiles.go
Egutierrez ccfa5bc78b feat(browser): funciones anti-deteccion + perfiles para web_scraping
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).
2026-06-05 16:25:11 +02:00

104 lines
2.6 KiB
Go

package browser
import (
"encoding/json"
"os"
"path/filepath"
"sort"
)
// ChromeProfile holds metadata about a single Chrome/Chromium profile directory.
type ChromeProfile struct {
Dir string // directory name (value for --profile-directory), e.g. "Default"
Name string // human-readable name from Local State info_cache, e.g. "Personal"
Extensions int // number of installed extension dirs under <dir>/Extensions (excluding "Temp")
HasPreferences bool // true if <dir>/Preferences file exists
}
// localState mirrors the parts of Local State we need.
type localState struct {
Profile struct {
InfoCache map[string]struct {
Name string `json:"name"`
} `json:"info_cache"`
} `json:"profile"`
}
// ListChromeProfiles scans userDataDir and returns one ChromeProfile per
// subdirectory that contains a Preferences file (excluding "System Profile").
// If userDataDir is empty it defaults to ~/.config/chromium.
// Names are resolved from Local State; if that file is missing or unparseable
// the profile Name field equals Dir.
func ListChromeProfiles(userDataDir string) ([]ChromeProfile, error) {
if userDataDir == "" {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
userDataDir = filepath.Join(home, ".config", "chromium")
}
// Parse Local State for human-readable names. Failure is non-fatal.
names := make(map[string]string)
lsPath := filepath.Join(userDataDir, "Local State")
if data, err := os.ReadFile(lsPath); err == nil {
var ls localState
if json.Unmarshal(data, &ls) == nil {
for dir, info := range ls.Profile.InfoCache {
names[dir] = info.Name
}
}
}
entries, err := os.ReadDir(userDataDir)
if err != nil {
return nil, err
}
var profiles []ChromeProfile
for _, e := range entries {
if !e.IsDir() {
continue
}
dir := e.Name()
if dir == "System Profile" {
continue
}
prefPath := filepath.Join(userDataDir, dir, "Preferences")
info, err := os.Stat(prefPath)
if err != nil || info.IsDir() {
continue
}
// Count extension directories (excluding "Temp").
extCount := 0
extDir := filepath.Join(userDataDir, dir, "Extensions")
if exts, err := os.ReadDir(extDir); err == nil {
for _, ext := range exts {
if ext.IsDir() && ext.Name() != "Temp" {
extCount++
}
}
}
name := names[dir]
if name == "" {
name = dir
}
profiles = append(profiles, ChromeProfile{
Dir: dir,
Name: name,
Extensions: extCount,
HasPreferences: true,
})
}
sort.Slice(profiles, func(i, j int) bool {
return profiles[i].Dir < profiles[j].Dir
})
return profiles, nil
}