diff options
| author | yingyu5658 <i@yingyu5658.me> | 2025-12-04 11:19:57 +0800 |
|---|---|---|
| committer | yingyu5658 <i@yingyu5658.me> | 2025-12-04 11:19:57 +0800 |
| commit | c52f41545df179b542f106d08e6dd013a70216e7 (patch) | |
| tree | da54d63c0ad311f77fd1ff7d3b3f53109ed99af7 /main.go | |
| parent | 14a941b09749d63871016831b657f6de7a6337fc (diff) | |
| download | bvd-c52f41545df179b542f106d08e6dd013a70216e7.tar.gz bvd-c52f41545df179b542f106d08e6dd013a70216e7.zip | |
refactor: 模块化重构并优化输出
- 将原本的 main.go 拆分为 命令 网络请求 下载三个单独职责的文件
- 优化输出, 添加用户友好的Emoji
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 221 |
1 files changed, 0 insertions, 221 deletions
diff --git a/main.go b/main.go deleted file mode 100644 index 4c296f3..0000000 --- a/main.go +++ /dev/null @@ -1,221 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "github.com/urfave/cli/v2" - "io" - "log" - "net/http" - "os" - "path/filepath" - "time" -) - -const AppVersion = "0.1.0" - -type VideoBaseInfo struct { - Data []struct { - Cid int `json:"cid"` // 每一个视频的 CID - Part string `json:"part"` // 分 P 标题 - Page int `json:"page"` // 分 P 编号 - FirstFrame string `json:"first_frame"` // 封面图 - } `json:"data"` -} - -// getDownloadUrl函数得到的JSON的匹配结构体 -type getDownloadUrlJson struct { - Data struct { - Durl []struct { - Url string `json: "durl"` - } `json:"durl"` - } `json: "data"` -} - -func main() { - app := &cli.App{ - Name: "bvd", - Usage: "快速、高效、易用的下载B站视频 CLI 工具", - Commands: []*cli.Command{ - { - Name: "download", - Usage: "下载指定 BV 号的视频", - Action: downloadAction, - ArgsUsage: "<BVID> 欲下载视频的 BV 号", - }, - }, - - Action: func(c *cli.Context) error { - args := c.Args() - if args.Len() == 0 { - cli.ShowAppHelp(c) - } - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} - -func downloadAction(c *cli.Context) error { - // 从命令行获取 BVID 参数 - bvid := c.Args().First() - - if bvid == "" { - return fmt.Errorf("请提供 BV 号") - } - - return startDownload(bvid) -} - -func startDownload(bvid string) error { - video, err := getVideoBaseInfo(bvid) - if err != nil { - return fmt.Errorf("获取视频信息失败:%w", err) - } - - err = downloadVideo(video, bvid) - if err != nil { - return err - } - - return nil -} - -func getVideoBaseInfo(bvid string) (VideoBaseInfo, error) { - var APIUrl string = "https://api.bilibili.com/x/player/pagelist?bvid=" - APIUrl += bvid - client := &http.Client{ - Timeout: 30 * time.Second, - } - - // 发送 GET 请求 - resp, err := client.Get(APIUrl) - if err != nil { - return VideoBaseInfo{}, fmt.Errorf("发送请求失败: %w", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return VideoBaseInfo{}, fmt.Errorf("读取响应失败: %w", err) - } - - var result VideoBaseInfo - if err := json.Unmarshal(body, &result); err != nil { - return VideoBaseInfo{}, fmt.Errorf("JSON 解析失败: %w", err) - } - - return result, nil -} - -func getDownloadUrl(video VideoBaseInfo, bvid string) ([]string, error) { - cid := make([]int, len(video.Data)) - for i := 0; i < len(video.Data); i++ { - cid[i] = video.Data[i].Cid - } - - var downloadUrls = make([]string, len(video.Data)) - var result getDownloadUrlJson - client := &http.Client{Timeout: 30 * time.Second} - - for j := 0; j < len(cid); j++ { - apiUrl := fmt.Sprintf("https://api.bilibili.com/x/player/playurl?&cid=%d&bvid=%s&qn=80", cid[j], bvid) - - resp, err := client.Get(apiUrl) - if err != nil { - return nil, fmt.Errorf("CID %d 请求失败: %w", cid[j], err) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - resp.Body.Close() - return nil, fmt.Errorf("CID %d 读取响应失败: %w", cid[j], err) - } - - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("CID %d JSON解析失败: %w", cid[j], err) - } - - downloadUrls[j] = result.Data.Durl[0].Url - } - - return downloadUrls, nil -} - -func downloadVideo(video VideoBaseInfo, bvid string) error { - var urls []string - urls, err := getDownloadUrl(video, bvid) - if err != nil { - return fmt.Errorf("获取视频下载链接失败:%w", err) - } - - // 创建下载目录 - downloadDir := "./downloads/" - if err := os.MkdirAll(downloadDir, 0755); err != nil { - return fmt.Errorf("创建下载目录失败:%w", err) - } - - fmt.Printf("成功获取 %d 个下载链接\n", len(urls)) - - for i := 0; i < len(urls); i++ { - fmt.Printf("开始下载第 %d 个文件\n", i+1) - - // 创建支持重定向的 HTTP 客户端 - client := &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return nil - }, - } - - req, err := http.NewRequest("GET", urls[i], nil) - if err != nil { - return err - } - - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") - req.Header.Set("referer", "https://www.bilibili.com") - - // 发送请求 - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("HTTP请求失败:%w", err) - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode) - } - - // 构建文件名 - filename := filepath.Join(downloadDir, video.Data[i].Part+".mp4") - fmt.Printf("保存到: %s\n", filename) - - // 创建文件 - out, err := os.Create(filename) - if err != nil { - return fmt.Errorf("创建文件失败:%w", err) - } - - // 下载文件 - _, err = io.Copy(out, resp.Body) - if err != nil { - out.Close() - return fmt.Errorf("下载失败:%w", err) - } - - // 关闭文件 - if err := out.Close(); err != nil { - return fmt.Errorf("关闭文件失败:%w", err) - } - - fmt.Printf("文件下载成功: %s\n", filename) - - } - - return nil -} |
