package main import ( "crypto/md5" "crypto/subtle" "encoding/json" "fmt" "html/template" "io" "log" "math/rand" "net/http" "os" "path/filepath" "sort" "strconv" "strings" "time" ) const ( downloadDir = "./chrome_versions" checkInterval = 24 * time.Hour maxRetries = 3 versionAPI = "https://versionhistory.googleapis.com/v1/chrome/platforms/win64/channels/stable/versions" chromeURL = "https://dl.google.com/tag/s/appguid%3D%7B8A69D345-D564-463C-AFF1-A69D9E530F96%7D%26iid%3D%7B00000000-0000-0000-0000-000000000000%7D%26lang%3Dzh-CN%26browser%3D4%26usagestats%3D0%26appname%3DGoogle%2520Chrome%26needsadmin%3Dprefers%26ap%3Dx64-stable-statsdef_1%26installdataindex%3Dempty/chrome/install/ChromeStandaloneSetup64.exe" ) var ( authToken string authEnabled bool port string keepVersions int ) type Version struct { Filename string Size int64 Time time.Time } type ChromeVersion struct { Version string `json:"version"` } type ChromeVersionResponse struct { Versions []ChromeVersion `json:"versions"` } func init() { authToken = getEnv("AUTH_TOKEN", "") authEnabled = getEnv("AUTH_ENABLED", "false") == "true" || authToken != "" port = getEnv("PORT", "8080") keepVersions, _ = strconv.Atoi(getEnv("KEEP_VERSIONS", "3")) } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func main() { os.MkdirAll(downloadDir, 0755) log.Printf("Auth enabled: %v, Port: %s, Keep versions: %d", authEnabled, port, keepVersions) log.Println("Performing initial check...") checkAndDownload() go monitor() http.HandleFunc("/", authMiddleware(serveIndex)) http.Handle("/download/", authMiddleware(http.StripPrefix("/download/", http.FileServer(http.Dir(downloadDir))).ServeHTTP)) log.Printf("Server started on :%s", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } func monitor() { for { delay := time.Duration(rand.Intn(3600)) * time.Second time.Sleep(delay) checkAndDownload() time.Sleep(checkInterval) } } func getLatestVersion() (string, error) { resp, err := http.Get(versionAPI) if err != nil { return "", err } defer resp.Body.Close() var versionResp ChromeVersionResponse if err := json.NewDecoder(resp.Body).Decode(&versionResp); err != nil { return "", err } if len(versionResp.Versions) == 0 { return "", fmt.Errorf("no versions found") } return versionResp.Versions[0].Version, nil } func checkAndDownload() { version, err := getLatestVersion() if err != nil { log.Printf("Failed to get version: %v", err) return } log.Printf("Latest Chrome version: %s", version) // 检查是否已存在该版本 files, _ := os.ReadDir(downloadDir) for _, f := range files { if strings.HasPrefix(f.Name(), "chrome_"+version+"_") { log.Printf("Version %s already exists, skipping download", version) return } } var data []byte client := &http.Client{} for i := 0; i < maxRetries; i++ { resp, err := client.Get(chromeURL) if err != nil { log.Printf("Attempt %d failed: %v", i+1, err) time.Sleep(time.Duration(i+1) * 10 * time.Second) continue } data, err = io.ReadAll(resp.Body) resp.Body.Close() if err == nil && len(data) > 10000000 { break } log.Printf("Attempt %d: invalid response (size: %d bytes)", i+1, len(data)) time.Sleep(time.Duration(i+1) * 10 * time.Second) } if len(data) < 10000000 { log.Println("All download attempts failed") return } hash := fmt.Sprintf("%x", md5.Sum(data)) filename := fmt.Sprintf("chrome_%s_%s.exe", version, hash[:8]) filepath := filepath.Join(downloadDir, filename) if err := os.WriteFile(filepath, data, 0644); err != nil { log.Printf("Error saving file: %v", err) return } log.Printf("Downloaded: %s (%.2f MB)", filename, float64(len(data))/1024/1024) cleanupOldVersions() } func cleanupOldVersions() { files, _ := os.ReadDir(downloadDir) if len(files) <= keepVersions { return } var versions []Version for _, f := range files { if strings.HasSuffix(f.Name(), ".exe") { info, _ := f.Info() versions = append(versions, Version{ Filename: f.Name(), Time: info.ModTime(), Size: info.Size(), }) } } sort.Slice(versions, func(i, j int) bool { return versions[i].Time.After(versions[j].Time) }) for i := keepVersions; i < len(versions); i++ { os.Remove(filepath.Join(downloadDir, versions[i].Filename)) log.Printf("Removed old version: %s", versions[i].Filename) } } func authMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if authEnabled { token := r.URL.Query().Get("token") if subtle.ConstantTimeCompare([]byte(token), []byte(authToken)) != 1 { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } } next(w, r) } } func serveIndex(w http.ResponseWriter, r *http.Request) { files, _ := os.ReadDir(downloadDir) var versions []Version for _, f := range files { if strings.HasSuffix(f.Name(), ".exe") { info, _ := f.Info() versions = append(versions, Version{ Filename: f.Name(), Time: info.ModTime(), Size: info.Size(), }) } } sort.Slice(versions, func(i, j int) bool { return versions[i].Time.After(versions[j].Time) }) token := r.URL.Query().Get("token") tmpl := template.Must(template.New("index").Parse(`