3253828fef
Add complete navegator system for stealthy browser automation: - CDP client with WebSocket communication - Browser API with navigation, storage, network, runtime - Stealth flags and anti-detection scripts - Persistent profile support - Examples and comprehensive documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
348 lines
8.8 KiB
Go
348 lines
8.8 KiB
Go
package browser
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Cookie representa una cookie.
|
|
type Cookie struct {
|
|
Name string `json:"name"`
|
|
Value string `json:"value"`
|
|
Domain string `json:"domain,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
Expires float64 `json:"expires,omitempty"` // Unix timestamp
|
|
HTTPOnly bool `json:"httpOnly,omitempty"`
|
|
Secure bool `json:"secure,omitempty"`
|
|
SameSite string `json:"sameSite,omitempty"` // "Strict", "Lax", "None"
|
|
}
|
|
|
|
// GetCookies obtiene todas las cookies o las de un dominio específico.
|
|
func (b *Browser) GetCookies(ctx context.Context, urls ...string) ([]*Cookie, error) {
|
|
params := make(map[string]interface{})
|
|
if len(urls) > 0 {
|
|
params["urls"] = urls
|
|
}
|
|
|
|
var result struct {
|
|
Cookies []*Cookie `json:"cookies"`
|
|
}
|
|
|
|
if err := b.cdpClient.Execute(ctx, "Network.getCookies", params, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to get cookies: %w", err)
|
|
}
|
|
|
|
return result.Cookies, nil
|
|
}
|
|
|
|
// SetCookie establece una cookie.
|
|
func (b *Browser) SetCookie(ctx context.Context, cookie *Cookie) error {
|
|
params := map[string]interface{}{
|
|
"name": cookie.Name,
|
|
"value": cookie.Value,
|
|
}
|
|
|
|
if cookie.Domain != "" {
|
|
params["domain"] = cookie.Domain
|
|
}
|
|
if cookie.Path != "" {
|
|
params["path"] = cookie.Path
|
|
}
|
|
if cookie.Expires > 0 {
|
|
params["expires"] = cookie.Expires
|
|
}
|
|
if cookie.HTTPOnly {
|
|
params["httpOnly"] = true
|
|
}
|
|
if cookie.Secure {
|
|
params["secure"] = true
|
|
}
|
|
if cookie.SameSite != "" {
|
|
params["sameSite"] = cookie.SameSite
|
|
}
|
|
|
|
var result struct {
|
|
Success bool `json:"success"`
|
|
}
|
|
|
|
if err := b.cdpClient.Execute(ctx, "Network.setCookie", params, &result); err != nil {
|
|
return fmt.Errorf("failed to set cookie: %w", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
return fmt.Errorf("failed to set cookie: %s", cookie.Name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetCookies establece múltiples cookies.
|
|
func (b *Browser) SetCookies(ctx context.Context, cookies []*Cookie) error {
|
|
for _, cookie := range cookies {
|
|
if err := b.SetCookie(ctx, cookie); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteCookie elimina una cookie específica.
|
|
func (b *Browser) DeleteCookie(ctx context.Context, name string, domain string) error {
|
|
params := map[string]interface{}{
|
|
"name": name,
|
|
}
|
|
|
|
if domain != "" {
|
|
params["domain"] = domain
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Network.deleteCookies", params, nil)
|
|
}
|
|
|
|
// ClearCookies elimina todas las cookies.
|
|
func (b *Browser) ClearCookies(ctx context.Context) error {
|
|
return b.cdpClient.Execute(ctx, "Network.clearBrowserCookies", nil, nil)
|
|
}
|
|
|
|
// LocalStorageItem representa un item de localStorage.
|
|
type LocalStorageItem struct {
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
// GetLocalStorage obtiene todos los items del localStorage.
|
|
func (b *Browser) GetLocalStorage(ctx context.Context) ([]*LocalStorageItem, error) {
|
|
script := `
|
|
(() => {
|
|
const items = [];
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
items.push({ key: key, value: localStorage.getItem(key) });
|
|
}
|
|
return items;
|
|
})()
|
|
`
|
|
|
|
var result struct {
|
|
Result struct {
|
|
Value []map[string]interface{} `json:"value"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"expression": script,
|
|
"returnByValue": true,
|
|
}
|
|
|
|
if err := b.cdpClient.Execute(ctx, "Runtime.evaluate", params, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to get localStorage: %w", err)
|
|
}
|
|
|
|
items := make([]*LocalStorageItem, 0, len(result.Result.Value))
|
|
for _, item := range result.Result.Value {
|
|
items = append(items, &LocalStorageItem{
|
|
Key: item["key"].(string),
|
|
Value: item["value"].(string),
|
|
})
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// SetLocalStorage establece un item en localStorage.
|
|
func (b *Browser) SetLocalStorage(ctx context.Context, key, value string) error {
|
|
script := fmt.Sprintf(`localStorage.setItem(%q, %q)`, key, value)
|
|
|
|
params := map[string]interface{}{
|
|
"expression": script,
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Runtime.evaluate", params, nil)
|
|
}
|
|
|
|
// RemoveLocalStorage elimina un item de localStorage.
|
|
func (b *Browser) RemoveLocalStorage(ctx context.Context, key string) error {
|
|
script := fmt.Sprintf(`localStorage.removeItem(%q)`, key)
|
|
|
|
params := map[string]interface{}{
|
|
"expression": script,
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Runtime.evaluate", params, nil)
|
|
}
|
|
|
|
// ClearLocalStorage limpia todo el localStorage.
|
|
func (b *Browser) ClearLocalStorage(ctx context.Context) error {
|
|
params := map[string]interface{}{
|
|
"expression": "localStorage.clear()",
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Runtime.evaluate", params, nil)
|
|
}
|
|
|
|
// GetSessionStorage obtiene todos los items del sessionStorage.
|
|
func (b *Browser) GetSessionStorage(ctx context.Context) ([]*LocalStorageItem, error) {
|
|
script := `
|
|
(() => {
|
|
const items = [];
|
|
for (let i = 0; i < sessionStorage.length; i++) {
|
|
const key = sessionStorage.key(i);
|
|
items.push({ key: key, value: sessionStorage.getItem(key) });
|
|
}
|
|
return items;
|
|
})()
|
|
`
|
|
|
|
var result struct {
|
|
Result struct {
|
|
Value []map[string]interface{} `json:"value"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"expression": script,
|
|
"returnByValue": true,
|
|
}
|
|
|
|
if err := b.cdpClient.Execute(ctx, "Runtime.evaluate", params, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to get sessionStorage: %w", err)
|
|
}
|
|
|
|
items := make([]*LocalStorageItem, 0, len(result.Result.Value))
|
|
for _, item := range result.Result.Value {
|
|
items = append(items, &LocalStorageItem{
|
|
Key: item["key"].(string),
|
|
Value: item["value"].(string),
|
|
})
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// SetSessionStorage establece un item en sessionStorage.
|
|
func (b *Browser) SetSessionStorage(ctx context.Context, key, value string) error {
|
|
script := fmt.Sprintf(`sessionStorage.setItem(%q, %q)`, key, value)
|
|
|
|
params := map[string]interface{}{
|
|
"expression": script,
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Runtime.evaluate", params, nil)
|
|
}
|
|
|
|
// RemoveSessionStorage elimina un item de sessionStorage.
|
|
func (b *Browser) RemoveSessionStorage(ctx context.Context, key string) error {
|
|
script := fmt.Sprintf(`sessionStorage.removeItem(%q)`, key)
|
|
|
|
params := map[string]interface{}{
|
|
"expression": script,
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Runtime.evaluate", params, nil)
|
|
}
|
|
|
|
// ClearSessionStorage limpia todo el sessionStorage.
|
|
func (b *Browser) ClearSessionStorage(ctx context.Context) error {
|
|
params := map[string]interface{}{
|
|
"expression": "sessionStorage.clear()",
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Runtime.evaluate", params, nil)
|
|
}
|
|
|
|
// StorageType tipo de storage.
|
|
type StorageType string
|
|
|
|
const (
|
|
StorageTypeCookies StorageType = "cookies"
|
|
StorageTypeLocalStorage StorageType = "local_storage"
|
|
StorageTypeSessionStorage StorageType = "session_storage"
|
|
StorageTypeIndexedDB StorageType = "indexeddb"
|
|
StorageTypeWebSQL StorageType = "websql"
|
|
StorageTypeCacheStorage StorageType = "cache_storage"
|
|
)
|
|
|
|
// ClearDataForOrigin limpia datos de un origen específico.
|
|
func (b *Browser) ClearDataForOrigin(ctx context.Context, origin string, storageTypes ...StorageType) error {
|
|
if len(storageTypes) == 0 {
|
|
// Por defecto, limpiar todo
|
|
storageTypes = []StorageType{
|
|
StorageTypeCookies,
|
|
StorageTypeLocalStorage,
|
|
StorageTypeSessionStorage,
|
|
StorageTypeIndexedDB,
|
|
StorageTypeWebSQL,
|
|
StorageTypeCacheStorage,
|
|
}
|
|
}
|
|
|
|
// Convertir a string separado por comas
|
|
types := ""
|
|
for i, st := range storageTypes {
|
|
if i > 0 {
|
|
types += ","
|
|
}
|
|
types += string(st)
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"origin": origin,
|
|
"storageTypes": types,
|
|
}
|
|
|
|
return b.cdpClient.Execute(ctx, "Storage.clearDataForOrigin", params, nil)
|
|
}
|
|
|
|
// ExportCookies exporta las cookies del perfil a un archivo JSON.
|
|
func (b *Browser) ExportCookies(ctx context.Context) ([]*Cookie, error) {
|
|
return b.GetCookies(ctx)
|
|
}
|
|
|
|
// ImportCookies importa cookies desde un slice.
|
|
func (b *Browser) ImportCookies(ctx context.Context, cookies []*Cookie) error {
|
|
return b.SetCookies(ctx, cookies)
|
|
}
|
|
|
|
// CreateCookie crea una cookie helper.
|
|
func CreateCookie(name, value, domain string) *Cookie {
|
|
return &Cookie{
|
|
Name: name,
|
|
Value: value,
|
|
Domain: domain,
|
|
Path: "/",
|
|
// Expira en 1 año
|
|
Expires: float64(time.Now().Add(365 * 24 * time.Hour).Unix()),
|
|
HTTPOnly: false,
|
|
Secure: false,
|
|
SameSite: "Lax",
|
|
}
|
|
}
|
|
|
|
// CreateSessionCookie crea una cookie de sesión (sin expiración).
|
|
func CreateSessionCookie(name, value, domain string) *Cookie {
|
|
return &Cookie{
|
|
Name: name,
|
|
Value: value,
|
|
Domain: domain,
|
|
Path: "/",
|
|
HTTPOnly: false,
|
|
Secure: false,
|
|
SameSite: "Lax",
|
|
}
|
|
}
|
|
|
|
// CreateSecureCookie crea una cookie segura (HTTPS only).
|
|
func CreateSecureCookie(name, value, domain string) *Cookie {
|
|
return &Cookie{
|
|
Name: name,
|
|
Value: value,
|
|
Domain: domain,
|
|
Path: "/",
|
|
Expires: float64(time.Now().Add(365 * 24 * time.Hour).Unix()),
|
|
HTTPOnly: true,
|
|
Secure: true,
|
|
SameSite: "Strict",
|
|
}
|
|
}
|