From 28d5fd9476459581177578368eeeb672df820e84 Mon Sep 17 00:00:00 2001 From: taynpg Date: Wed, 22 Jan 2025 10:22:58 +0800 Subject: [PATCH] add: transm --- .gitignore | 3 + .vscode/launch.json | 15 ++++ .vscode/settings.json | 6 ++ REAME.md | 3 + go.mod | 3 + main.go | 178 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 208 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 REAME.md create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aeb7268 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +downloads +*.exe +.DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..664b24b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Program with External cmd", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}", + "env": {}, // 可以在这里设置环境变量 + "args": [], // 传递给程序的参数 + "console": "externalTerminal" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..30e3d75 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.autoSave": "onFocusChange", + "editor.fontSize": 14, + "editor.fontFamily": "'Source Code Pro', 'Source Code Pro', monospace", + "terminal.integrated.fontFamily": "Source Code Pro", +} \ No newline at end of file diff --git a/REAME.md b/REAME.md new file mode 100644 index 0000000..4efb493 --- /dev/null +++ b/REAME.md @@ -0,0 +1,3 @@ +# 简介 + +`sinxmiao`站点下的工具快速下载器。 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ac9e389 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module tooldown + +go 1.23.5 diff --git a/main.go b/main.go new file mode 100644 index 0000000..0bc84f9 --- /dev/null +++ b/main.go @@ -0,0 +1,178 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "sort" + "strings" +) + +// 仓库配置 +var repositories = []string{ + "https://www.sinxmiao.cn/taynpg/transm", +} + +const downloadDir = "./downloads/" // 下载路径 + +// 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"` +} + +func main() { + // 选择仓库 + 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) + } +} + +// chooseRepository 选择仓库 +func chooseRepository(repositories []string) string { + for { + fmt.Println("可用的仓库:") + for i, repo := range repositories { + fmt.Printf("[%d] %s\n", i+1, repo[strings.LastIndex(repo, "/")+1:]) + } + fmt.Print("请选择一个仓库 (输入编号): ") + + var choice int + _, err := fmt.Scan(&choice) + if err == nil && choice > 0 && choice <= len(repositories) { + return repositories[choice-1] + } + fmt.Println("无效的选择,请重新输入。") + } +} + +// convertToAPIURL 将仓库链接转换为 API 地址 +func convertToAPIURL(repo string) string { + // 将仓库链接转换为 Gitea API 地址 + return strings.Replace(repo, "https://www.sinxmiao.cn", "https://www.sinxmiao.cn/api/v1/repos", 1) + "/releases" +} + +// fetchReleases 获取所有版本信息 +func fetchReleases(apiURL string) ([]Release, error) { + resp, err := http.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 +}