package geminiweb import ( "context" "encoding/json" "fmt" "os" "path/filepath" "time" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" "github.com/go-rod/rod/lib/proto" ) type Browser struct { browser *rod.Browser visible bool } func NewBrowser(visible bool) (*Browser, error) { l := launcher.New() if visible { l = l.Headless(false) } else { l = l.Headless(true) } url, err := l.Launch() if err != nil { return nil, fmt.Errorf("failed to launch browser: %w", err) } b := rod.New().ControlURL(url) if err := b.Connect(); err != nil { return nil, fmt.Errorf("failed to connect browser: %w", err) } return &Browser{browser: b, visible: visible}, nil } func (b *Browser) Close() error { if b.browser != nil { return b.browser.Close() } return nil } func (b *Browser) NewPage() (*rod.Page, error) { return b.browser.Page(proto.TargetCreateTarget{URL: "about:blank"}) } type Cookie struct { Name string `json:"name"` Value string `json:"value"` Domain string `json:"domain"` Path string `json:"path"` Expires float64 `json:"expires"` HTTPOnly bool `json:"httpOnly"` Secure bool `json:"secure"` } func LoadCookiesFromFile(cookieFile string) ([]Cookie, error) { data, err := os.ReadFile(cookieFile) if err != nil { return nil, fmt.Errorf("failed to read cookies: %w", err) } var cookies []Cookie if err := json.Unmarshal(data, &cookies); err != nil { return nil, fmt.Errorf("failed to parse cookies: %w", err) } return cookies, nil } func SaveCookiesToFile(cookies []Cookie, cookieFile string) error { data, err := json.MarshalIndent(cookies, "", " ") if err != nil { return fmt.Errorf("failed to marshal cookies: %w", err) } dir := filepath.Dir(cookieFile) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create cookie dir: %w", err) } if err := os.WriteFile(cookieFile, data, 0644); err != nil { return fmt.Errorf("failed to write cookies: %w", err) } return nil } func SetCookiesOnPage(page *rod.Page, cookies []Cookie) error { var protoCookies []*proto.NetworkCookieParam for _, c := range cookies { p := &proto.NetworkCookieParam{ Name: c.Name, Value: c.Value, Domain: c.Domain, Path: c.Path, HTTPOnly: c.HTTPOnly, Secure: c.Secure, } if c.Expires > 0 { exp := proto.TimeSinceEpoch(c.Expires) p.Expires = exp } protoCookies = append(protoCookies, p) } return page.SetCookies(protoCookies) } func WaitForElement(page *rod.Page, selector string, timeout time.Duration) (*rod.Element, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() return page.Context(ctx).Element(selector) } func WaitForElements(page *rod.Page, selector string, timeout time.Duration) (rod.Elements, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() return page.Context(ctx).Elements(selector) }