diff --git a/main.go b/main.go index d2d6e2e..5ca9c52 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "crypto/md5" "crypto/subtle" + "encoding/json" "fmt" "html/template" "io" @@ -21,14 +22,14 @@ const ( downloadDir = "./chrome_versions" checkInterval = 24 * time.Hour maxRetries = 3 + versionAPI = "https://versionhistory.googleapis.com/v1/chrome/platforms/win64/channels/stable/versions" ) var ( - authToken string - authEnabled bool - port string - keepVersions int - chromeURL string + authToken string + authEnabled bool + port string + keepVersions int ) type Version struct { @@ -37,12 +38,20 @@ type Version struct { Time time.Time } +type ChromeVersion struct { + Name string `json:"name"` + 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")) - chromeURL = getEnv("CHROME_URL", "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") } func getEnv(key, defaultValue string) string { @@ -56,7 +65,6 @@ 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() @@ -79,34 +87,86 @@ func monitor() { } } -func checkAndDownload() { - var data []byte - var err error +func getLatestVersion() (string, error) { + resp, err := http.Get(versionAPI) + if err != nil { + return "", err + } + defer resp.Body.Close() - for i := 0; i < maxRetries; i++ { - resp, err := http.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 { - break - } - log.Printf("Attempt %d read failed: %v", i+1, err) + 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.Println("All retries failed") + log.Printf("Failed to get latest version: %v", err) return } + log.Printf("Latest Chrome version: %s", version) + + // 尝试多个可能的下载 URL 模式 + urls := []string{ + fmt.Sprintf("https://dl.google.com/release2/chrome/%%s_%s/%s_chrome_installer.exe", version, version), + fmt.Sprintf("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 data []byte + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + log.Printf("Redirecting to: %s", req.URL.String()) + return nil + }, + } + + for _, urlPattern := range urls { + for i := 0; i < maxRetries; i++ { + url := urlPattern + if strings.Contains(url, "%s") { + // 跳过需要 hash 的 URL + continue + } + + log.Printf("Trying URL: %s", url) + resp, err := client.Get(url) + if err != nil { + log.Printf("Attempt %d failed: %v", i+1, err) + time.Sleep(time.Duration(i+1) * 10 * time.Second) + continue + } + + contentType := resp.Header.Get("Content-Type") + contentLength := resp.Header.Get("Content-Length") + log.Printf("Content-Type: %s, Content-Length: %s", contentType, contentLength) + + data, err = io.ReadAll(resp.Body) + resp.Body.Close() + + if err == nil && len(data) > 10000000 && !strings.Contains(contentType, "text/html") { + goto success + } + + log.Printf("Invalid response (size: %d bytes)", len(data)) + time.Sleep(time.Duration(i+1) * 10 * time.Second) + } + } + + log.Println("All download attempts failed") + return + +success: hash := fmt.Sprintf("%x", md5.Sum(data)) - filename := fmt.Sprintf("chrome_%s_%s.exe", time.Now().Format("20060102"), hash[:8]) + filename := fmt.Sprintf("chrome_%s_%s.exe", version, hash[:8]) filepath := filepath.Join(downloadDir, filename) if _, err := os.Stat(filepath); err == nil { @@ -192,14 +252,30 @@ func serveIndex(w http.ResponseWriter, r *http.Request) {

Chrome Offline Versions

`)) + + type VersionDisplay struct { + Filename string + SizeMB float64 + Time time.Time + } + + var displayVersions []VersionDisplay + for _, v := range versions { + displayVersions = append(displayVersions, VersionDisplay{ + Filename: v.Filename, + SizeMB: float64(v.Size) / 1024 / 1024, + Time: v.Time, + }) + } + tmpl.Execute(w, struct { - Versions []Version + Versions []VersionDisplay Token string - }{versions, token}) + }{displayVersions, token}) }