174 lines
3.3 KiB
Go
174 lines
3.3 KiB
Go
|
|
package geminiweb
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/go-rod/rod"
|
||
|
|
"github.com/go-rod/rod/lib/launcher"
|
||
|
|
"github.com/go-rod/rod/lib/proto"
|
||
|
|
)
|
||
|
|
|
||
|
|
// BrowserManager 管理瀏覽器實例的生命週期
|
||
|
|
type BrowserManager struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
browser *rod.Browser
|
||
|
|
userDataDir string
|
||
|
|
page *rod.Page
|
||
|
|
visible bool
|
||
|
|
isRunning bool
|
||
|
|
currentModel string
|
||
|
|
}
|
||
|
|
|
||
|
|
var (
|
||
|
|
globalManager *BrowserManager
|
||
|
|
globalMu sync.Mutex
|
||
|
|
)
|
||
|
|
|
||
|
|
// GetBrowserManager 獲取全域瀏覽器管理器(單例)
|
||
|
|
func GetBrowserManager(userDataDir string, visible bool) (*BrowserManager, error) {
|
||
|
|
globalMu.Lock()
|
||
|
|
defer globalMu.Unlock()
|
||
|
|
|
||
|
|
if globalManager != nil {
|
||
|
|
return globalManager, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
manager, err := NewBrowserManager(userDataDir, visible)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
globalManager = manager
|
||
|
|
return globalManager, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewBrowserManager 建立新的瀏覽器管理器
|
||
|
|
func NewBrowserManager(userDataDir string, visible bool) (*BrowserManager, error) {
|
||
|
|
cleanLockFiles(userDataDir)
|
||
|
|
|
||
|
|
if err := os.MkdirAll(userDataDir, 0755); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to create user data dir: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return &BrowserManager{
|
||
|
|
userDataDir: userDataDir,
|
||
|
|
visible: visible,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// cleanLockFiles 清理 Chrome 的殘留鎖檔案
|
||
|
|
func cleanLockFiles(userDataDir string) {
|
||
|
|
lockFiles := []string{
|
||
|
|
"SingletonLock",
|
||
|
|
"SingletonCookie",
|
||
|
|
"SingletonSocket",
|
||
|
|
"Default/SingletonLock",
|
||
|
|
"Default/SingletonCookie",
|
||
|
|
"Default/SingletonSocket",
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, file := range lockFiles {
|
||
|
|
path := filepath.Join(userDataDir, file)
|
||
|
|
os.Remove(path)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Launch 啟動瀏覽器(如果尚未啟動)
|
||
|
|
func (m *BrowserManager) Launch() error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
|
||
|
|
if m.isRunning && m.browser != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
l := launcher.New()
|
||
|
|
|
||
|
|
if m.visible {
|
||
|
|
l = l.Headless(false)
|
||
|
|
} else {
|
||
|
|
l = l.Headless(true)
|
||
|
|
}
|
||
|
|
|
||
|
|
l = l.UserDataDir(m.userDataDir)
|
||
|
|
|
||
|
|
url, err := l.Launch()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to launch browser: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
b := rod.New().ControlURL(url)
|
||
|
|
if err := b.Connect(); err != nil {
|
||
|
|
return fmt.Errorf("failed to connect browser: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
m.browser = b
|
||
|
|
|
||
|
|
page, err := b.Page(proto.TargetCreateTarget{URL: "about:blank"})
|
||
|
|
if err != nil {
|
||
|
|
_ = b.Close()
|
||
|
|
return fmt.Errorf("failed to create page: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
m.page = page
|
||
|
|
m.isRunning = true
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetPage 獲取頁面
|
||
|
|
func (m *BrowserManager) GetPage() (*rod.Page, error) {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
|
||
|
|
if !m.isRunning || m.browser == nil {
|
||
|
|
return nil, fmt.Errorf("browser not running")
|
||
|
|
}
|
||
|
|
|
||
|
|
return m.page, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close 關閉瀏覽器
|
||
|
|
func (m *BrowserManager) Close() error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
|
||
|
|
if !m.isRunning {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var err error
|
||
|
|
if m.browser != nil {
|
||
|
|
err = m.browser.Close()
|
||
|
|
m.browser = nil
|
||
|
|
}
|
||
|
|
|
||
|
|
m.page = nil
|
||
|
|
m.isRunning = false
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsRunning 檢查瀏覽器是否正在運行
|
||
|
|
func (m *BrowserManager) IsRunning() bool {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
return m.isRunning
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetCurrentModel 設定當前模型
|
||
|
|
func (m *BrowserManager) SetCurrentModel(model string) {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
m.currentModel = model
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetCurrentModel 獲取當前模型
|
||
|
|
func (m *BrowserManager) GetCurrentModel() string {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
return m.currentModel
|
||
|
|
}
|