package downloader import ( "archive/zip" "fmt" "io" "net/http" "os" "path/filepath" "runtime" "strings" "github.com/fatih/color" ) const ( XrayVersion = "24.12.18" V2RayVersion = "5.22.0" XrayDownloadURL = "https://github.com/XTLS/Xray-core/releases/download/v%s/Xray-windows-64.zip" V2RayDownloadURL = "https://github.com/v2fly/v2ray-core/releases/download/v%s/v2ray-windows-64.zip" ) var ( green = color.New(color.FgGreen).SprintFunc() yellow = color.New(color.FgYellow).SprintFunc() cyan = color.New(color.FgCyan).SprintFunc() ) // DownloadCore загружает Xray или V2Ray func DownloadCore(coreDir, coreName string) error { var downloadURL, version, exeName string if coreName == "xray" { downloadURL = fmt.Sprintf(XrayDownloadURL, XrayVersion) version = XrayVersion exeName = "xray.exe" } else if coreName == "v2ray" { downloadURL = fmt.Sprintf(V2RayDownloadURL, V2RayVersion) version = V2RayVersion exeName = "v2ray.exe" } else { return fmt.Errorf("неизвестный тип ядра: %s", coreName) } // Проверяем, существует ли уже coreExe := filepath.Join(coreDir, exeName) if _, err := os.Stat(coreExe); err == nil { return nil } fmt.Println("\n" + strings.Repeat("=", 60)) fmt.Printf("%s %s не найден. Начинаю загрузку...\n", yellow("⚠"), strings.ToUpper(coreName)) fmt.Println(strings.Repeat("=", 60)) // Создаем директорию if err := os.MkdirAll(coreDir, 0755); err != nil { return fmt.Errorf("ошибка создания директории: %w", err) } fmt.Printf("\nВерсия: %s\n", cyan(version)) fmt.Printf("URL: %s\n\n", downloadURL) // Загружаем файл zipPath := filepath.Join(coreDir, coreName+".zip") if err := downloadFile(zipPath, downloadURL); err != nil { return fmt.Errorf("ошибка загрузки: %w", err) } fmt.Printf("\n%s Загрузка завершена\n", green("✓")) fmt.Printf("%s Распаковка архива...\n", yellow("⚙")) // Распаковываем if err := unzip(zipPath, coreDir); err != nil { os.Remove(zipPath) return fmt.Errorf("ошибка распаковки: %w", err) } os.Remove(zipPath) fmt.Printf("%s %s успешно установлен в %s\n", green("✓"), strings.ToUpper(coreName), coreDir) fmt.Println(strings.Repeat("=", 60)) return nil } // DownloadXray загружает Xray func DownloadXray(xrayDir string) error { return DownloadCore(xrayDir, "xray") } // DownloadV2Ray загружает V2Ray func DownloadV2Ray(v2rayDir string) error { return DownloadCore(v2rayDir, "v2ray") } // downloadFile загружает файл с прогресс-баром func downloadFile(filepath string, url string) error { out, err := os.Create(filepath) if err != nil { return err } defer out.Close() resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("ошибка загрузки: HTTP %d", resp.StatusCode) } size := resp.ContentLength counter := &WriteCounter{Total: size} _, err = io.Copy(out, io.TeeReader(resp.Body, counter)) if err != nil { return err } fmt.Println() return nil } // WriteCounter считает загруженные байты type WriteCounter struct { Total int64 Downloaded int64 } func (wc *WriteCounter) Write(p []byte) (int, error) { n := len(p) wc.Downloaded += int64(n) wc.PrintProgress() return n, nil } func (wc *WriteCounter) PrintProgress() { fmt.Printf("\r%s", strings.Repeat(" ", 60)) percent := float64(wc.Downloaded) / float64(wc.Total) * 100 downloaded := formatBytes(wc.Downloaded) total := formatBytes(wc.Total) fmt.Printf("\rЗагрузка: %s / %s (%.1f%%)", downloaded, total, percent) } func formatBytes(bytes int64) string { const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) } // unzip распаковывает zip архив func unzip(src, dest string) error { r, err := zip.OpenReader(src) if err != nil { return err } defer r.Close() for _, f := range r.File { fpath := filepath.Join(dest, f.Name) if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { return fmt.Errorf("недопустимый путь файла: %s", fpath) } if f.FileInfo().IsDir() { os.MkdirAll(fpath, os.ModePerm) continue } if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { return err } outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } rc, err := f.Open() if err != nil { outFile.Close() return err } _, err = io.Copy(outFile, rc) outFile.Close() rc.Close() if err != nil { return err } } return nil } // CheckAndDownloadXray проверяет и загружает Xray func CheckAndDownloadXray(xrayDir string) error { if runtime.GOOS != "windows" { return nil } xrayExe := filepath.Join(xrayDir, "xray.exe") if _, err := os.Stat(xrayExe); os.IsNotExist(err) { return DownloadXray(xrayDir) } return nil } // CheckAndDownloadV2Ray проверяет и загружает V2Ray func CheckAndDownloadV2Ray(v2rayDir string) error { if runtime.GOOS != "windows" { return nil } v2rayExe := filepath.Join(v2rayDir, "v2ray.exe") if _, err := os.Stat(v2rayExe); os.IsNotExist(err) { return DownloadV2Ray(v2rayDir) } return nil } // CheckAndDownloadBoth проверяет и загружает оба ядра func CheckAndDownloadBoth(xrayDir, v2rayDir string) error { if runtime.GOOS != "windows" { return nil } // Проверяем Xray xrayExe := filepath.Join(xrayDir, "xray.exe") xrayExists := true if _, err := os.Stat(xrayExe); os.IsNotExist(err) { xrayExists = false } // Проверяем V2Ray v2rayExe := filepath.Join(v2rayDir, "v2ray.exe") v2rayExists := true if _, err := os.Stat(v2rayExe); os.IsNotExist(err) { v2rayExists = false } // Если оба есть, ничего не делаем if xrayExists && v2rayExists { return nil } // Загружаем недостающие if !xrayExists { if err := DownloadXray(xrayDir); err != nil { return err } } if !v2rayExists { if err := DownloadV2Ray(v2rayDir); err != nil { return err } } return nil }