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).
104 lines
2.6 KiB
Go
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
|
|
}
|