mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
360 lines
12 KiB
Go
360 lines
12 KiB
Go
package backend
|
|
|
|
import (
|
|
"ant-chrome/backend/internal/browser"
|
|
"ant-chrome/backend/internal/config"
|
|
"ant-chrome/backend/internal/logger"
|
|
"ant-chrome/backend/internal/proxy"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
type BrowserExtension = browser.Extension
|
|
type BrowserExtensionLookupResult = browser.ExtensionLookupResult
|
|
type BrowserProfileExtensionSettings = browser.ProfileExtensionSettings
|
|
|
|
type BrowserExtensionWebStoreRequest struct {
|
|
Query string `json:"query"`
|
|
UseProxy bool `json:"useProxy"`
|
|
ProxyConfig string `json:"proxyConfig"`
|
|
}
|
|
|
|
type BrowserExtensionManualInstallGuide struct {
|
|
ExtensionID string `json:"extensionId"`
|
|
StoreURL string `json:"storeUrl"`
|
|
DownloadURL string `json:"downloadUrl"`
|
|
DownloadDir string `json:"downloadDir"`
|
|
FileName string `json:"fileName"`
|
|
}
|
|
|
|
type BrowserExtensionManualDownloadFile struct {
|
|
FileName string `json:"fileName"`
|
|
FilePath string `json:"filePath"`
|
|
SizeBytes int64 `json:"sizeBytes"`
|
|
UpdatedAt string `json:"updatedAt"`
|
|
}
|
|
|
|
func (a *App) BrowserExtensionList() ([]BrowserExtension, error) {
|
|
if a.browserMgr == nil || a.browserMgr.ExtensionDAO == nil {
|
|
return []BrowserExtension{}, nil
|
|
}
|
|
return a.browserMgr.ExtensionDAO.List()
|
|
}
|
|
|
|
func (a *App) BrowserExtensionLookup(query string) (BrowserExtensionLookupResult, error) {
|
|
return a.BrowserExtensionLookupWithProxy(BrowserExtensionWebStoreRequest{Query: query})
|
|
}
|
|
|
|
func (a *App) BrowserExtensionLookupWithProxy(input BrowserExtensionWebStoreRequest) (BrowserExtensionLookupResult, error) {
|
|
if a.browserMgr == nil {
|
|
return BrowserExtensionLookupResult{}, fmt.Errorf("浏览器管理器未初始化")
|
|
}
|
|
client, err := a.extensionDownloadHTTPClient(input.UseProxy, input.ProxyConfig)
|
|
if err != nil {
|
|
return BrowserExtensionLookupResult{}, fmt.Errorf("下载代理配置错误: %w", err)
|
|
}
|
|
return a.browserMgr.LookupExtensionWithHTTPClient(input.Query, client)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionInstall(query string) (BrowserExtension, error) {
|
|
return a.BrowserExtensionInstallWithProxy(BrowserExtensionWebStoreRequest{Query: query})
|
|
}
|
|
|
|
func (a *App) BrowserExtensionInstallWithProxy(input BrowserExtensionWebStoreRequest) (BrowserExtension, error) {
|
|
a.maintenanceMu.Lock()
|
|
defer a.maintenanceMu.Unlock()
|
|
|
|
if a.ctx == nil {
|
|
return BrowserExtension{}, fmt.Errorf("应用上下文未初始化")
|
|
}
|
|
if a.browserMgr == nil {
|
|
return BrowserExtension{}, fmt.Errorf("浏览器管理器未初始化")
|
|
}
|
|
client, err := a.extensionDownloadHTTPClient(input.UseProxy, input.ProxyConfig)
|
|
if err != nil {
|
|
return BrowserExtension{}, fmt.Errorf("下载代理配置错误: %w", err)
|
|
}
|
|
return a.browserMgr.InstallExtensionFromWebStoreWithHTTPClient(a.ctx, input.Query, client)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionManualInstallGuide(query string) (BrowserExtensionManualInstallGuide, error) {
|
|
extensionID := browser.NormalizeExtensionID(query)
|
|
if extensionID == "" {
|
|
return BrowserExtensionManualInstallGuide{}, fmt.Errorf("请输入 Chrome 插件 ID 或 Chrome Web Store 链接")
|
|
}
|
|
downloadDir := a.extensionManualDownloadDir()
|
|
if err := os.MkdirAll(downloadDir, 0o755); err != nil {
|
|
return BrowserExtensionManualInstallGuide{}, fmt.Errorf("创建手动下载目录失败: %w", err)
|
|
}
|
|
return BrowserExtensionManualInstallGuide{
|
|
ExtensionID: extensionID,
|
|
StoreURL: browser.BuildChromeWebStoreURL(extensionID),
|
|
DownloadURL: browser.BuildChromeExtensionDownloadURL(extensionID),
|
|
DownloadDir: downloadDir,
|
|
FileName: extensionID + ".crx",
|
|
}, nil
|
|
}
|
|
|
|
func (a *App) BrowserExtensionOpenManualDownloadDir() error {
|
|
downloadDir := a.extensionManualDownloadDir()
|
|
if err := os.MkdirAll(downloadDir, 0o755); err != nil {
|
|
return fmt.Errorf("创建手动下载目录失败: %w", err)
|
|
}
|
|
absPath, err := filepath.Abs(downloadDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return openPathInFileManager(absPath)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionListManualDownloadFiles() ([]BrowserExtensionManualDownloadFile, error) {
|
|
downloadDir := a.extensionManualDownloadDir()
|
|
if err := os.MkdirAll(downloadDir, 0o755); err != nil {
|
|
return nil, fmt.Errorf("创建手动下载目录失败: %w", err)
|
|
}
|
|
entries, err := os.ReadDir(downloadDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取手动下载目录失败: %w", err)
|
|
}
|
|
files := make([]BrowserExtensionManualDownloadFile, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
name := strings.TrimSpace(entry.Name())
|
|
lowerName := strings.ToLower(name)
|
|
if !strings.HasSuffix(lowerName, ".crx") && !strings.HasSuffix(lowerName, ".zip") {
|
|
continue
|
|
}
|
|
info, err := entry.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
files = append(files, BrowserExtensionManualDownloadFile{
|
|
FileName: name,
|
|
FilePath: filepath.Join(downloadDir, name),
|
|
SizeBytes: info.Size(),
|
|
UpdatedAt: info.ModTime().Format("2006-01-02 15:04:05"),
|
|
})
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func (a *App) BrowserExtensionInstallManualDownloadFile(fileName string) (BrowserExtension, error) {
|
|
a.maintenanceMu.Lock()
|
|
defer a.maintenanceMu.Unlock()
|
|
|
|
if a.browserMgr == nil {
|
|
return BrowserExtension{}, fmt.Errorf("浏览器管理器未初始化")
|
|
}
|
|
path, err := a.resolveManualDownloadFile(fileName)
|
|
if err != nil {
|
|
return BrowserExtension{}, err
|
|
}
|
|
return a.browserMgr.InstallExtensionPackageFile(path)
|
|
}
|
|
|
|
func (a *App) extensionManualDownloadDir() string {
|
|
return a.resolveAppPath(filepath.ToSlash(filepath.Join("data", "extensions", "manual-downloads")))
|
|
}
|
|
|
|
func (a *App) resolveManualDownloadFile(fileName string) (string, error) {
|
|
fileName = strings.TrimSpace(fileName)
|
|
if fileName == "" {
|
|
return "", fmt.Errorf("请选择要导入的插件包")
|
|
}
|
|
baseDir, err := filepath.Abs(a.extensionManualDownloadDir())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
cleanName := filepath.Clean(filepath.FromSlash(fileName))
|
|
if cleanName == "." || cleanName == "" || filepath.IsAbs(cleanName) || strings.HasPrefix(cleanName, ".."+string(os.PathSeparator)) || cleanName == ".." {
|
|
return "", fmt.Errorf("插件包路径无效")
|
|
}
|
|
lowerName := strings.ToLower(cleanName)
|
|
if !strings.HasSuffix(lowerName, ".crx") && !strings.HasSuffix(lowerName, ".zip") {
|
|
return "", fmt.Errorf("只支持导入 .crx 或 .zip 插件包")
|
|
}
|
|
fullPath, err := filepath.Abs(filepath.Join(baseDir, cleanName))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if fullPath != baseDir && !strings.HasPrefix(fullPath, baseDir+string(os.PathSeparator)) {
|
|
return "", fmt.Errorf("插件包路径越界")
|
|
}
|
|
info, err := os.Stat(fullPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("插件包不存在: %w", err)
|
|
}
|
|
if info.IsDir() {
|
|
return "", fmt.Errorf("请选择插件包文件,不是目录")
|
|
}
|
|
return fullPath, nil
|
|
}
|
|
|
|
func (a *App) extensionDownloadHTTPClient(useProxy bool, proxyConfig string) (*http.Client, error) {
|
|
proxyConfig = strings.TrimSpace(proxyConfig)
|
|
log := logger.New("Extension")
|
|
if useProxy && proxyConfig == "" {
|
|
return nil, fmt.Errorf("已启用下载代理,但代理配置为空,请重新选择代理节点")
|
|
}
|
|
if proxyConfig == "" || strings.EqualFold(proxyConfig, "direct://") {
|
|
log.Info("Chrome 插件下载使用直连")
|
|
client, _, err := proxyCoreHTTPClient(browser.ExtensionDownloadTimeout(), "")
|
|
return client, err
|
|
}
|
|
proxies := a.getLatestProxies()
|
|
connectorType := config.BrowserConnectorXray
|
|
if a != nil && a.config != nil {
|
|
connectorType = a.config.Browser.DefaultConnectorType
|
|
}
|
|
log.Info("Chrome 插件下载使用代理", logger.F("connector", connectorType), logger.F("proxy_prefix", proxyConfigLogPrefix(proxyConfig)))
|
|
return proxy.BuildProxyHTTPClient(proxyConfig, "", proxies, a.xrayMgr, a.singboxMgr, a.clashMgr, connectorType, browser.ExtensionDownloadTimeout())
|
|
}
|
|
|
|
func proxyConfigLogPrefix(proxyConfig string) string {
|
|
proxyConfig = strings.TrimSpace(proxyConfig)
|
|
if len(proxyConfig) <= 24 {
|
|
return proxyConfig
|
|
}
|
|
return proxyConfig[:24]
|
|
}
|
|
|
|
func (a *App) BrowserExtensionInstallLocalFile() (BrowserExtension, error) {
|
|
a.maintenanceMu.Lock()
|
|
defer a.maintenanceMu.Unlock()
|
|
|
|
if a.ctx == nil {
|
|
return BrowserExtension{}, fmt.Errorf("应用上下文未初始化")
|
|
}
|
|
if a.browserMgr == nil {
|
|
return BrowserExtension{}, fmt.Errorf("浏览器管理器未初始化")
|
|
}
|
|
path, err := wailsruntime.OpenFileDialog(a.ctx, wailsruntime.OpenDialogOptions{
|
|
Title: "选择 Chrome 插件包",
|
|
Filters: []wailsruntime.FileFilter{
|
|
{DisplayName: "Chrome 插件包 (*.crx;*.zip)", Pattern: "*.crx;*.zip"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return BrowserExtension{}, fmt.Errorf("打开文件选择框失败: %w", err)
|
|
}
|
|
if strings.TrimSpace(path) == "" {
|
|
return BrowserExtension{}, fmt.Errorf("已取消选择")
|
|
}
|
|
return a.browserMgr.InstallExtensionPackageFile(path)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionInstallLocalDirectory() (BrowserExtension, error) {
|
|
a.maintenanceMu.Lock()
|
|
defer a.maintenanceMu.Unlock()
|
|
|
|
if a.ctx == nil {
|
|
return BrowserExtension{}, fmt.Errorf("应用上下文未初始化")
|
|
}
|
|
if a.browserMgr == nil {
|
|
return BrowserExtension{}, fmt.Errorf("浏览器管理器未初始化")
|
|
}
|
|
path, err := wailsruntime.OpenDirectoryDialog(a.ctx, wailsruntime.OpenDialogOptions{Title: "选择已解压插件目录"})
|
|
if err != nil {
|
|
return BrowserExtension{}, fmt.Errorf("打开目录选择框失败: %w", err)
|
|
}
|
|
if strings.TrimSpace(path) == "" {
|
|
return BrowserExtension{}, fmt.Errorf("已取消选择")
|
|
}
|
|
return a.browserMgr.InstallExtensionDirectory(path)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionSetEnabled(extensionID string, enabled bool) (BrowserExtension, error) {
|
|
if a.browserMgr == nil || a.browserMgr.ExtensionDAO == nil {
|
|
return BrowserExtension{}, fmt.Errorf("插件管理器未初始化")
|
|
}
|
|
extensionID = strings.TrimSpace(extensionID)
|
|
if extensionID == "" {
|
|
return BrowserExtension{}, fmt.Errorf("插件 ID 不能为空")
|
|
}
|
|
if err := a.browserMgr.ExtensionDAO.SetEnabled(extensionID, enabled); err != nil {
|
|
return BrowserExtension{}, err
|
|
}
|
|
return a.browserMgr.ExtensionDAO.Get(extensionID)
|
|
}
|
|
|
|
func (a *App) BrowserExtensionDelete(extensionID string) error {
|
|
a.maintenanceMu.Lock()
|
|
defer a.maintenanceMu.Unlock()
|
|
|
|
if a.browserMgr == nil || a.browserMgr.ExtensionDAO == nil {
|
|
return fmt.Errorf("插件管理器未初始化")
|
|
}
|
|
extensionID = strings.TrimSpace(extensionID)
|
|
if extensionID == "" {
|
|
return fmt.Errorf("插件 ID 不能为空")
|
|
}
|
|
extension, err := a.browserMgr.ExtensionDAO.Get(extensionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := a.resolveBrowserExtensionInstallDir(extension.InstallDir); err != nil {
|
|
return err
|
|
}
|
|
if err := a.browserMgr.ExtensionDAO.Delete(extensionID); err != nil {
|
|
return err
|
|
}
|
|
if err := a.removeBrowserExtensionInstallDir(extension.InstallDir); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) resolveBrowserExtensionInstallDir(installDir string) (string, error) {
|
|
installDir = strings.TrimSpace(installDir)
|
|
if installDir == "" {
|
|
return "", nil
|
|
}
|
|
target, err := filepath.Abs(installDir)
|
|
if err != nil {
|
|
return "", fmt.Errorf("解析插件目录失败: %w", err)
|
|
}
|
|
root, err := filepath.Abs(a.resolveAppPath(filepath.ToSlash(filepath.Join("data", "extensions"))))
|
|
if err != nil {
|
|
return "", fmt.Errorf("解析插件根目录失败: %w", err)
|
|
}
|
|
target = filepath.Clean(target)
|
|
root = filepath.Clean(root)
|
|
rel, err := filepath.Rel(root, target)
|
|
if err != nil || rel == "." || rel == "" || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
return "", fmt.Errorf("拒绝删除插件根目录外的路径: %s", installDir)
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func (a *App) removeBrowserExtensionInstallDir(installDir string) error {
|
|
target, err := a.resolveBrowserExtensionInstallDir(installDir)
|
|
if err != nil || target == "" {
|
|
return err
|
|
}
|
|
if err := os.RemoveAll(target); err != nil {
|
|
return fmt.Errorf("删除插件目录失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) BrowserProfileExtensionGet(profileID string) (BrowserProfileExtensionSettings, error) {
|
|
if a.browserMgr == nil || a.browserMgr.ExtensionDAO == nil {
|
|
return BrowserProfileExtensionSettings{}, fmt.Errorf("插件管理器未初始化")
|
|
}
|
|
return a.browserMgr.ExtensionDAO.GetProfileSettings(profileID)
|
|
}
|
|
|
|
func (a *App) BrowserProfileExtensionSave(profileID string, extensionIDs []string, configured bool) (BrowserProfileExtensionSettings, error) {
|
|
if a.browserMgr == nil || a.browserMgr.ExtensionDAO == nil {
|
|
return BrowserProfileExtensionSettings{}, fmt.Errorf("插件管理器未初始化")
|
|
}
|
|
return a.browserMgr.ExtensionDAO.SetProfileSettings(profileID, extensionIDs, configured)
|
|
}
|