package main import ( "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "runtime" "sort" ) // 站点配置 var baseSiteURL = "https://www.sinxmiao.cn/" // 下载路径 const downloadDir = "./downloads/" // 令牌 const apiToken = "a6d1d94a9d49e14a9a85dc1767a91d78c00249e6" // Release 结构体用于解析 API 返回的 JSON 数据 type Release struct { TagName string `json:"tag_name"` CreatedAt string `json:"created_at"` Assets []Asset `json:"assets"` } // Asset 结构体用于解析每个发行版的附件信息 type Asset struct { Name string `json:"name"` BrowserDownloadURL string `json:"browser_download_url"` } // Repo 结构体用于解析仓库信息 type Repo struct { FullName string `json:"full_name"` } func main() { fmt.Printf("GOARCH: %s\n", runtime.GOARCH) fmt.Printf("GOOS: %s\n", runtime.GOOS) // 获取所有公开仓库 repositories, err := fetchRepositories(baseSiteURL) if err != nil { fmt.Printf("获取仓库信息失败: %v\n", err) return } // 选择仓库 repo := chooseRepository(repositories) // 获取该仓库的所有版本信息 apiURL := convertToAPIURL(repo) releases, err := fetchReleases(apiURL) if err != nil { fmt.Printf("获取发行版信息失败: %v\n", err) return } // 打印版本信息并选择版本 release := chooseRelease(releases) // 打印附件信息并选择附件 asset := chooseAsset(release) // 创建下载目录 if err := os.MkdirAll(downloadDir, os.ModePerm); err != nil { fmt.Printf("创建下载目录失败: %v\n", err) return } // 下载选择的附件 fmt.Printf("开始下载: %s\n", asset.Name) if err := downloadFile(asset.BrowserDownloadURL, filepath.Join(downloadDir, asset.Name)); err != nil { fmt.Printf("下载失败: %v\n", err) } else { fmt.Printf("下载完成: %s\n", asset.Name) } } // fetchRepositories 获取站点下的所有仓库 func fetchRepositories(baseURL string) ([]Repo, error) { client := createHTTPClient() page := 1 limit := 200 url := fmt.Sprintf("%sapi/v1/user/repos?page=%d&limit=%d", baseURL, page, limit) resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("请求仓库列表失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API 响应状态码: %d", resp.StatusCode) } var repos []Repo if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil { return nil, fmt.Errorf("解析仓库列表失败: %v", err) } // 筛选有附件的仓库 var validRepos []Repo for _, repo := range repos { apiURL := convertToAPIURL(repo.FullName) releases, err := fetchReleases(apiURL) if err == nil && len(releases) > 0 { validRepos = append(validRepos, repo) } } return validRepos, nil } // chooseRepository 选择仓库 func chooseRepository(repositories []Repo) string { for { fmt.Println("可用的仓库:") for i, repo := range repositories { fmt.Printf("[%d] %s\n", i+1, repo.FullName) } fmt.Print("请选择一个仓库 (输入编号): ") var choice int _, err := fmt.Scan(&choice) if err == nil && choice > 0 && choice <= len(repositories) { return repositories[choice-1].FullName } fmt.Println("无效的选择,请重新输入。") } } // convertToAPIURL 将仓库链接转换为 API 地址 func convertToAPIURL(repo string) string { return baseSiteURL + "api/v1/repos/" + repo + "/releases" } // fetchReleases 获取所有版本信息 func fetchReleases(apiURL string) ([]Release, error) { client := createHTTPClient() resp, err := client.Get(apiURL) if err != nil { return nil, fmt.Errorf("请求 API 失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API 响应状态码: %d", resp.StatusCode) } var releases []Release if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { return nil, fmt.Errorf("解析 JSON 失败: %v", err) } // 按创建时间排序 sort.Slice(releases, func(i, j int) bool { return releases[i].CreatedAt < releases[j].CreatedAt }) return releases, nil } // chooseRelease 打印版本信息并选择版本 func chooseRelease(releases []Release) *Release { for { fmt.Println("可用的版本:") for i, release := range releases { fmt.Printf("[%d] %s (%s)\n", i+1, release.TagName, release.CreatedAt) } fmt.Print("请选择一个版本 (输入编号): ") var choice int _, err := fmt.Scan(&choice) if err == nil && choice > 0 && choice <= len(releases) { return &releases[choice-1] } fmt.Println("无效的选择,请重新输入。") } } // chooseAsset 打印附件信息并选择附件 func chooseAsset(release *Release) *Asset { for { fmt.Println("可用的附件:") for i, asset := range release.Assets { fmt.Printf("[%d] %s\n", i+1, asset.Name) } fmt.Print("请选择一个附件 (输入编号): ") var choice int _, err := fmt.Scan(&choice) if err == nil && choice > 0 && choice <= len(release.Assets) { return &release.Assets[choice-1] } fmt.Println("无效的选择,请重新输入。") } } // downloadFile 下载文件到指定路径 func downloadFile(url, filePath string) error { resp, err := http.Get(url) if err != nil { return fmt.Errorf("请求下载链接失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("下载链接响应状态码: %d", resp.StatusCode) } // 创建文件 out, err := os.Create(filePath) if err != nil { return fmt.Errorf("创建文件失败: %v", err) } defer out.Close() // 写入文件 _, err = io.Copy(out, resp.Body) if err != nil { return fmt.Errorf("保存文件失败: %v", err) } return nil } // createHTTPClient 创建带有 Authorization 头的 HTTP 客户端 func createHTTPClient() *http.Client { client := &http.Client{} return &http.Client{ Transport: &transportWithAuth{ base: client.Transport, }, } } type transportWithAuth struct { base http.RoundTripper } func (t *transportWithAuth) RoundTrip(req *http.Request) (*http.Response, error) { if t.base == nil { t.base = http.DefaultTransport } req.Header.Set("Authorization", "token "+apiToken) return t.base.RoundTrip(req) }