From 57940d77f78d2e25292bab45aefaac28d5507336 Mon Sep 17 00:00:00 2001 From: HIM049 Date: Sat, 24 Jan 2026 23:58:32 +0800 Subject: [PATCH 01/15] refactor: request lib --- bilibili/Audio.go | 88 ------ bilibili/Video.go | 122 -------- bilibili/api.go | 456 ++++++++++++++++++++++++++++ bilibili/collect.go | 205 +------------ bilibili/compilation.go | 66 +--- bilibili/login.go | 139 +-------- bilibili/profile.go | 61 +--- bilibili/requests.go | 147 +++++++++ bilibili/wbi.go | 33 -- frontend/wailsjs/runtime/runtime.js | 4 + go.mod | 7 +- go.sum | 14 +- 12 files changed, 626 insertions(+), 716 deletions(-) create mode 100644 bilibili/api.go create mode 100644 bilibili/requests.go diff --git a/bilibili/Audio.go b/bilibili/Audio.go index 7690d93..1b20cd6 100644 --- a/bilibili/Audio.go +++ b/bilibili/Audio.go @@ -1,15 +1,5 @@ package bilibili -import ( - "errors" - "io" - "net/http" - "net/url" - "strconv" - - "github.com/tidwall/gjson" -) - // // 用于获取 AUID 音频流信息 // type audio struct { // Code int `json:"code"` @@ -53,81 +43,3 @@ type Audio struct { StreamLink string `json:"stream_link"` // 音频流列表 } } - -func (audio *Audio) Query(auid string) error { - - // 设置 URL 并发送 GET 请求 - params := url.Values{} - Url, _ := url.Parse("https://www.bilibili.com/audio/music-service-c/web/song/info") - - // 设置 URL 参数 - params.Set("sid", auid) - - Url.RawQuery = params.Encode() - urlPath := Url.String() - resp, err := http.Get(urlPath) - if err != nil { - return err - } - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyJson := string(body) - defer resp.Body.Close() - - audio.Auid = auid - audio.Meta.Title = gjson.Get(bodyJson, "data.title").String() - audio.Meta.Cover = gjson.Get(bodyJson, "data.cover").String() - audio.Meta.Lyric = gjson.Get(bodyJson, "data.lyric").String() - audio.Up.Author = gjson.Get(bodyJson, "data.author").String() - - return nil -} - -func (audio *Audio) GetStream(sessdata string) error { - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/audio/music-service-c/url", nil) - if err != nil { - return err - } - - // 添加 Cookie 到请求头 - if sessdata != "" { - req.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 设置 URL 参数 - q := req.URL.Query() - q.Add("songid", audio.Auid) - q.Add("quality", "2") - q.Add("privilege", "2") - q.Add("mid", "2") - q.Add("platform", "web") - req.URL.RawQuery = q.Encode() - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyJson := string(body) - - // 错误检查 - if CheckObj(int(gjson.Get(bodyJson, "code").Int())) { - return errors.New(gjson.Get(bodyJson, "message").String()) - } - - audio.Stream.Type = int(gjson.Get(bodyJson, "data.type").Int()) - audio.Stream.StreamLink = gjson.Get(bodyJson, "data.cdns.0").String() - - return nil -} diff --git a/bilibili/Video.go b/bilibili/Video.go index e0ec679..e52228a 100644 --- a/bilibili/Video.go +++ b/bilibili/Video.go @@ -1,14 +1,5 @@ package bilibili -import ( - "errors" - "io" - "net/http" - "strconv" - - "github.com/tidwall/gjson" -) - type Video struct { Bvid string `json:"bvid"` Meta struct { @@ -31,116 +22,3 @@ type Videos struct { SongName string `json:"song_name"` // 歌名 } } - -// 请求视频详细信息 -// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/info.md -// TODO:重新添加字幕信息 -func (v *Video) Query(sessdata, bvid string) error { - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/web-interface/view", nil) - if err != nil { - return err - } - - // 添加 Cookie 到请求头 - if sessdata != "" { - req.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 设置 URL 参数 - q := req.URL.Query() - q.Add("bvid", bvid) - req.URL.RawQuery = q.Encode() - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - json := string(body) - - // 将信息写入结构体 - v.Bvid = bvid - v.Meta.Title = gjson.Get(json, "data.title").String() // 视频标题 - v.Meta.Cover = gjson.Get(json, "data.pic").String() // 视频封面 - v.Meta.LyricsPath = gjson.Get(json, "data.subtitle.0.subtitle_url").String() // 字幕获取(临时) - v.Up.Mid = int(gjson.Get(json, "data.owner.mid").Int()) // UP MID - v.Up.Name = gjson.Get(json, "data.owner.name").String() // UP 昵称 - v.Up.Avatar = gjson.Get(json, "data.owner.face").String() // UP 头像 - - // 根据分 P 数量写入对应信息 - for i := 0; i < int(gjson.Get(json, "data.videos").Int()); i++ { - - // 单个分集视频信息 - videos := Videos{ - Cid: int(gjson.Get(json, "data.pages."+strconv.Itoa(i)+".cid").Int()), - Part: gjson.Get(json, "data.pages."+strconv.Itoa(i)+".part").String(), - } - v.Videos = append(v.Videos, videos) - } - - return nil -} - -// 获取视频流 -// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md#%E8%8E%B7%E5%8F%96%E8%A7%86%E9%A2%91%E6%B5%81%E5%9C%B0%E5%9D%80_web%E7%AB%AF -func GetVideoStream(bvid, cid, sessdata string) (string, error) { - // 创建请求 - request, err := http.NewRequest("GET", "https://api.bilibili.com/x/player/wbi/playurl", nil) - if err != nil { - return "", err - } - - // 设置 URL 参数 - q := request.URL.Query() - q.Add("bvid", bvid) - q.Add("cid", cid) - q.Add("fnval", "16") - request.URL.RawQuery = q.Encode() - - signedUrl, err := WbiSignURLParams(request.URL.String()) - if err != nil { - return "", errors.New("Wbi Sign Error: " + err.Error()) - } - - signedRequest, err := http.NewRequest("GET", signedUrl, nil) - if err != nil { - return "", errors.New("New Signed Request Error: " + err.Error()) - } - - signedRequest.Header.Set("referer", "https://www.bilibili.com") - signedRequest.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0") - - // 添加 Cookie 到请求头 - if sessdata != "" { - signedRequest.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(signedRequest) - if err != nil { - return "", err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return "", errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - return bodyString, nil -} diff --git a/bilibili/api.go b/bilibili/api.go new file mode 100644 index 0000000..121d7d6 --- /dev/null +++ b/bilibili/api.go @@ -0,0 +1,456 @@ +package bilibili + +import ( + "errors" + "io" + "net/http" + "strconv" + "strings" + + "github.com/tidwall/gjson" +) + +// Audio.go API + +// Query query audio streams +func (audio *Audio) Query(auid string) error { + // 设置 URL 并发送 GET 请求 + urlPath := "https://www.bilibili.com/audio/music-service-c/web/song/info" + + body, err := Request(urlPath, WithParams(map[string]string{"sid": auid})) + if err != nil { + return err + } + bodyJson := string(body) + + audio.Auid = auid + audio.Meta.Title = gjson.Get(bodyJson, "data.title").String() + audio.Meta.Cover = gjson.Get(bodyJson, "data.cover").String() + audio.Meta.Lyric = gjson.Get(bodyJson, "data.lyric").String() + audio.Up.Author = gjson.Get(bodyJson, "data.author").String() + + return nil +} + +// GetStream get audio stream url +func (audio *Audio) GetStream(sessdata string) error { + // 创建请求 + urlStr := "https://api.bilibili.com/audio/music-service-c/url" + + // 设置 URL 参数 + params := map[string]string{ + "songid": audio.Auid, + "quality": "2", + "privilege": "2", + "mid": "2", + "platform": "web", + } + + body, err := Request(urlStr, WithSESSDATA(sessdata), WithParams(params)) + if err != nil { + return err + } + bodyJson := string(body) + + // 错误检查 + if CheckObj(int(gjson.Get(bodyJson, "code").Int())) { + return errors.New(gjson.Get(bodyJson, "message").String()) + } + + audio.Stream.Type = int(gjson.Get(bodyJson, "data.type").Int()) + audio.Stream.StreamLink = gjson.Get(bodyJson, "data.cdns.0").String() + + return nil +} + +// Video.go API + +// 请求视频详细信息 +// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/info.md +// TODO:重新添加字幕信息 +func (v *Video) Query(sessdata, bvid string) error { + // 创建请求 + urlStr := "https://api.bilibili.com/x/web-interface/view" + + body, err := Request(urlStr, WithSESSDATA(sessdata), WithParams(map[string]string{"bvid": bvid})) + if err != nil { + return err + } + + json := string(body) + + // 将信息写入结构体 + v.Bvid = bvid + v.Meta.Title = gjson.Get(json, "data.title").String() // 视频标题 + v.Meta.Cover = gjson.Get(json, "data.pic").String() // 视频封面 + v.Meta.LyricsPath = gjson.Get(json, "data.subtitle.0.subtitle_url").String() // 字幕获取(临时) + v.Up.Mid = int(gjson.Get(json, "data.owner.mid").Int()) // UP MID + v.Up.Name = gjson.Get(json, "data.owner.name").String() // UP 昵称 + v.Up.Avatar = gjson.Get(json, "data.owner.face").String() // UP 头像 + + // 根据分 P 数量写入对应信息 + for i := 0; i < int(gjson.Get(json, "data.videos").Int()); i++ { + + // 单个分集视频信息 + videos := Videos{ + Cid: int(gjson.Get(json, "data.pages."+strconv.Itoa(i)+".cid").Int()), + Part: gjson.Get(json, "data.pages."+strconv.Itoa(i)+".part").String(), + } + v.Videos = append(v.Videos, videos) + } + + return nil +} + +// 获取视频流 +// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md#%E8%8E%B7%E5%8F%96%E8%A7%86%E9%A2%91%E6%B5%81%E5%9C%B0%E5%9D%80_web%E7%AB%AF +func GetVideoStream(bvid, cid, sessdata string) (string, error) { + // 创建请求 + urlStr := "https://api.bilibili.com/x/player/wbi/playurl" + + // 设置 URL 参数 + params := map[string]string{ + "bvid": bvid, + "cid": cid, + "fnval": "16", + } + + // This particular API uses WbiSignURLParams which was called manually in the original code. + // Since we use the Wbi option, it should be handled by requests.go logic if we use WithWbi(). + // However, Request() with WithWbi() will sign the params. + // Original code: + // signedUrl, err := WbiSignURLParams(request.URL.String()) + // Then creates new request with signedUrl. + + body, err := Request(urlStr, WithParams(params), WithWbi(), WithSESSDATA(sessdata)) + if err != nil { + return "", err + } + return string(body), nil +} + +// login.go API + +// 获取登录密钥 +func GetLoginKey() (string, string, error) { + var obj GetLoginKeyReturn + body, err := getLoginKey() + if err != nil { + return "", "", err + } + err = decodeJson(body, &obj) + if err != nil { + return "", "", err + } + // 错误检查 + if CheckObj(obj.Code) { + return "", "", errors.New(obj.Message) + } + return obj.Data.Url, obj.Data.Qrcode_key, nil +} + +// 请求登录密钥 +func getLoginKey() (string, error) { + body, err := Request("https://passport.bilibili.com/x/passport-login/web/qrcode/generate") + if err != nil { + return "", err + } + return string(body), nil +} + +// 检查扫码状态 +func checkLoginStatus(qrcode_key string) (string, *[]*http.Cookie, error) { + // Original code manually handles cookies from response. + // My Request function returns body, error. It does not return the response object to get cookies. + // We need to modify Request to return (body, cookies, error) or exposing response? + // Or handle this case specifically. + // The requirement "unify request methods" suggests using the standard way. + // But getting cookies from response is a specific requirement here. + + // Let's implement checkLoginStatus manually here using http.Client to preserve functionality, + // OR modify Request to return *http.Response (and let user read body)? + // Or maybe a specialized request in requests.go? + + // Since this is a specific case (polling login status and getting Set-Cookie), + // I will keep the http.Client usage but try to make it cleaner or just adapt it slightly. + // Actually, the user asked to "classify request methods and unify to SAME call method (closure call)". + // If I can't fit it into `Request`, I should extend `Request` or add `RequestWithResponse`. + + // Let's add `RequestWithCookies` to requests.go? + // Or just use the original logic but inside this file? + // The prompt implies "unify" -> so I should probably make `Request` capable of returning cookies or headers. + // But `Request` currently returns `[]byte`. + + // For now, I will manually implement this using `http.Client` here but structure it similarly, + // OR I will modify `requests.go` to return `(*http.Response, []byte, error)`? That seems too heavy for simple requests. + + // I'll stick to manual implementation for this one but minimalize it, OR add a `RequestRaw` to `requests.go`. + // Let's stick to standard `http.Client` here for now to avoid over-engineering `requests.go` for one edge case, + // unless `requests.go` can be easily modified. + // Actually, `checkLoginStatus` returns `*[]*http.Cookie`. + + client := &http.Client{} + req, err := http.NewRequest("GET", "https://passport.bilibili.com/x/passport-login/web/qrcode/poll", nil) + if err != nil { + return "", nil, err + } + + q := req.URL.Query() + q.Add("qrcode_key", qrcode_key) + req.URL.RawQuery = q.Encode() + + resp, err := client.Do(req) + if err != nil { + return "", nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", nil, errors.New("Error:" + strconv.Itoa(resp.StatusCode)) + } + + cookies := resp.Cookies() + body, _ := io.ReadAll(resp.Body) // Simplify error handling as original code ignored it? No, original: _ + bodyString := string(body) + return bodyString, &cookies, nil +} + +func CheckLoginStatus(qrcode_key string) (*checkLoginReturn, *[]*http.Cookie, error) { + var obj checkLoginReturn + body, cookies, err := checkLoginStatus(qrcode_key) + if err != nil { + return nil, nil, err + } + err = decodeJson(body, &obj) + if err != nil { + return nil, nil, err + } + // 错误检查 + if CheckObj(obj.Code) { + return nil, nil, errors.New(obj.Message) + } + + return &obj, cookies, nil +} + +// 获取用户信息 +// https://socialsisteryi.github.io/bilibili-API-collect/docs/login/login_info.html +func (accountInf *AccountInformation) GetUserInf(sessdata string) error { + body, err := Request("https://api.bilibili.com/x/web-interface/nav", WithSESSDATA(sessdata)) + if err != nil { + return err + } + bodyJson := string(body) + + // 错误检查 + if CheckObj(int(gjson.Get(bodyJson, "code").Int())) { + return errors.New(gjson.Get(bodyJson, "message").String()) + } + + accountInf.Avatar = gjson.Get(bodyJson, "data.face").String() + accountInf.Name = gjson.Get(bodyJson, "data.uname").String() + + return nil +} + +// collect.go API + +func getFavList(id, ps, pn, sessdata string) (string, error) { + params := map[string]string{ + "media_id": id, + "ps": ps, + "pn": pn, + "platform": "web", + } + body, err := Request("https://api.bilibili.com/x/v3/fav/resource/list", WithSESSDATA(sessdata), WithParams(params)) + if err != nil { + return "", err + } + return string(body), nil +} + +func GetFavListObj(id, sessdata string, ps, pn int) (*FavList, error) { + var obj FavList + body, err := getFavList(id, strconv.Itoa(ps), strconv.Itoa(pn), sessdata) + if err != nil { + return nil, err + } + err = decodeJson(body, &obj) + if err != nil { + return nil, err + } + // 错误检查 + if CheckObj(obj.Code) { + return nil, errors.New(obj.Message) + } + return &obj, nil +} + +// 获取用户收藏的收藏夹 +func (collects *Collects) GetFavCollect(sessdata string, ps, pn int) error { + json, err := getUserfavoritesCollect(sessdata, strconv.Itoa(collects.UserMid), strconv.Itoa(ps), strconv.Itoa(pn)) + if err != nil { + return err + } + + // 错误检查 + if CheckObj(int(gjson.Get(json, "code").Int())) { + return errors.New(gjson.Get(json, "message").String()) + } + + collects.Count = int(gjson.Get(json, "data.count").Int()) + pageCount := collects.Count + + if collects.Count/20 >= pn { + pageCount = 20 + } else { + pageCount = collects.Count % 20 + } + + for i := 0; i < pageCount; i++ { + meta := new(meta) + meta.Id = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".id").Int()) + meta.Mid = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".mid").Int()) + meta.Attr = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".attr").Int()) + meta.Title = gjson.Get(json, "data.list."+strconv.Itoa(i)+".title").String() + meta.Cover = gjson.Get(json, "data.list."+strconv.Itoa(i)+".cover").String() + meta.MediaCount = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".media_count").Int()) + collects.List = append(collects.List, *meta) + } + + return nil +} + +// 获取用户收藏的收藏夹 +func getUserfavoritesCollect(sessdata, mid, pageSize, pageNumber string) (string, error) { + params := map[string]string{ + "ps": pageSize, + "pn": pageNumber, + "up_mid": mid, + "platform": "web", + } + body, err := Request("https://api.bilibili.com/x/v3/fav/folder/collected/list", WithSESSDATA(sessdata), WithParams(params)) + if err != nil { + return "", err + } + return string(body), nil +} + +// 获取用户创建的收藏夹 +func (collects *Collects) GetUsersCollect(sessdata string) error { + json, err := getUsersCollect(sessdata, strconv.Itoa(collects.UserMid)) + if err != nil { + return err + } + + // 错误检查 + if CheckObj(int(gjson.Get(json, "code").Int())) { + return errors.New(gjson.Get(json, "message").String()) + } + + collects.Count = int(gjson.Get(json, "data.count").Int()) + for i := 0; i < collects.Count; i++ { + meta := new(meta) + meta.Id = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".id").Int()) + meta.Mid = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".mid").Int()) + meta.Attr = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".attr").Int()) + meta.Title = gjson.Get(json, "data.list."+strconv.Itoa(i)+".title").String() + meta.MediaCount = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".media_count").Int()) + collects.List = append(collects.List, *meta) + } + + return nil +} + +// 获取用户创建的收藏夹 +func getUsersCollect(sessdata, mid string) (string, error) { + params := map[string]string{ + "up_mid": mid, + "platform": "web", + } + body, err := Request("https://api.bilibili.com/x/v3/fav/folder/created/list-all", WithSESSDATA(sessdata), WithParams(params)) + if err != nil { + return "", err + } + return string(body), nil +} + +// wbi.go API + +func getWbiKeys() (string, string) { + // REPLACEMENT calling Request: + body, err := Request("https://api.bilibili.com/x/web-interface/nav") + if err != nil { + return "", "" + } + + json := string(body) + imgURL := gjson.Get(json, "data.wbi_img.img_url").String() + subURL := gjson.Get(json, "data.wbi_img.sub_url").String() + // Check if imgURL/subURL are empty to avoid panic on Split? Original code didn't check. + if imgURL == "" || subURL == "" { + return "", "" + } + + return parseWbiKeys(imgURL, subURL) +} + +func parseWbiKeys(imgURL, subURL string) (string, string) { + imgKey := strings.Split(strings.Split(imgURL, "/")[len(strings.Split(imgURL, "/"))-1], ".")[0] + subKey := strings.Split(strings.Split(subURL, "/")[len(strings.Split(subURL, "/"))-1], ".")[0] + return imgKey, subKey +} + +// compilation.go API + +func getCompliation(mid, sid, ps, pn string) (string, error) { + params := map[string]string{ + "mid": mid, + "season_id": sid, + "page_size": ps, + "page_num": pn, + } + // Original code sets referer manually, Request() does it too. + body, err := Request("https://api.bilibili.com/x/polymer/web-space/seasons_archives_list", WithParams(params)) + if err != nil { + return "", err + } + return string(body), nil +} + +func GetCompliationObj(mid, sid, ps, pn int) (*CompliationInformation, error) { + var obj CompliationInformation + body, err := getCompliation(strconv.Itoa(mid), strconv.Itoa(sid), strconv.Itoa(ps), strconv.Itoa(pn)) + if err != nil { + return nil, err + } + err = decodeJson(body, &obj) + if err != nil { + return nil, err + } + + // 错误检查 + if CheckObj(obj.Code) { + return nil, errors.New(obj.Message) + } + return &obj, nil +} + +// profile.go API + +// 获取用户投稿列表 +// https://socialsisteryi.github.io/bilibili-API-collect/docs/user/space.html#%E6%9F%A5%E8%AF%A2%E7%94%A8%E6%88%B7%E6%8A%95%E7%A8%BF%E8%A7%86%E9%A2%91%E6%98%8E%E7%BB%86 +func GetProfileVideo(mid, pn, ps, sessdata string) (string, error) { + params := map[string]string{ + "mid": mid, + "order": "pubdate", + "pn": pn, + "ps": ps, + } + // This uses Wbi signing! + body, err := Request("https://api.bilibili.com/x/space/wbi/arc/search", WithParams(params), WithWbi(), WithSESSDATA(sessdata)) + if err != nil { + return "", err + } + return string(body), nil +} diff --git a/bilibili/collect.go b/bilibili/collect.go index db72fda..367e4b6 100644 --- a/bilibili/collect.go +++ b/bilibili/collect.go @@ -1,14 +1,5 @@ package bilibili -import ( - "errors" - "io" - "net/http" - "strconv" - - "github.com/tidwall/gjson" -) - // 用于获取收藏夹基本信息的函数 // 传入收藏夹 ID ,ps 单页大小, pn 页码 // 获得如下结构体 @@ -36,62 +27,7 @@ type FavList struct { } } -func getFavList(id, ps, pn, sessdata string) (string, error) { - - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/v3/fav/resource/list", nil) - if err != nil { - return "", err - } - - // 添加 Cookie 到请求头 - if sessdata != "" { - req.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 设置 URL 参数 - q := req.URL.Query() - q.Add("media_id", id) // 每页项数 - q.Add("ps", ps) // 页码 - q.Add("pn", pn) // 页码+ - q.Add("platform", "web") // 平台 - req.URL.RawQuery = q.Encode() - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return "", errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - return bodyString, nil -} - -func GetFavListObj(id, sessdata string, ps, pn int) (*FavList, error) { - var obj FavList - body, err := getFavList(id, strconv.Itoa(ps), strconv.Itoa(pn), sessdata) - if err != nil { - return nil, err - } - err = decodeJson(body, &obj) - if err != nil { - return nil, err - } - // 错误检查 - if CheckObj(obj.Code) { - return nil, errors.New(obj.Message) - } - return &obj, nil -} +// Used to be getFavList here. Moved to api.go // 获取用户创建的收藏夹 type Collects struct { @@ -108,141 +44,4 @@ type meta struct { MediaCount int `json:"media_count"` } -// 获取用户收藏的收藏夹 -func (collects *Collects) GetFavCollect(sessdata string, ps, pn int) error { - json, err := getUserfavoritesCollect(sessdata, strconv.Itoa(collects.UserMid), strconv.Itoa(ps), strconv.Itoa(pn)) - if err != nil { - return err - } - - // 错误检查 - if CheckObj(int(gjson.Get(json, "code").Int())) { - return errors.New(gjson.Get(json, "message").String()) - } - - collects.Count = int(gjson.Get(json, "data.count").Int()) - pageCount := collects.Count - - if collects.Count/20 >= pn { - pageCount = 20 - } else { - pageCount = collects.Count % 20 - } - - for i := 0; i < pageCount; i++ { - meta := new(meta) - meta.Id = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".id").Int()) - meta.Mid = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".mid").Int()) - meta.Attr = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".attr").Int()) - meta.Title = gjson.Get(json, "data.list."+strconv.Itoa(i)+".title").String() - meta.Cover = gjson.Get(json, "data.list."+strconv.Itoa(i)+".cover").String() - meta.MediaCount = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".media_count").Int()) - collects.List = append(collects.List, *meta) - } - - return nil -} - -// 获取用户收藏的收藏夹 -func getUserfavoritesCollect(sessdata, mid, pageSize, pageNumber string) (string, error) { - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/v3/fav/folder/collected/list", nil) - if err != nil { - return "", err - } - - // 添加 Cookie 到请求头 - if sessdata != "" { - req.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 设置 URL 参数 - q := req.URL.Query() - q.Add("ps", pageSize) // 每页项数 - q.Add("pn", pageNumber) // 页码 - q.Add("up_mid", mid) // 用户 mid - q.Add("platform", "web") // 平台 - req.URL.RawQuery = q.Encode() - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return "", errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - return bodyString, nil -} - -// 获取用户创建的收藏夹 -func (collects *Collects) GetUsersCollect(sessdata string) error { - json, err := getUsersCollect(sessdata, strconv.Itoa(collects.UserMid)) - if err != nil { - return err - } - - // 错误检查 - if CheckObj(int(gjson.Get(json, "code").Int())) { - return errors.New(gjson.Get(json, "message").String()) - } - - collects.Count = int(gjson.Get(json, "data.count").Int()) - for i := 0; i < collects.Count; i++ { - meta := new(meta) - meta.Id = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".id").Int()) - meta.Mid = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".mid").Int()) - meta.Attr = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".attr").Int()) - meta.Title = gjson.Get(json, "data.list."+strconv.Itoa(i)+".title").String() - meta.MediaCount = int(gjson.Get(json, "data.list."+strconv.Itoa(i)+".media_count").Int()) - collects.List = append(collects.List, *meta) - } - - return nil -} - -// 获取用户创建的收藏夹 -func getUsersCollect(sessdata, mid string) (string, error) { - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/v3/fav/folder/created/list-all", nil) - if err != nil { - return "", err - } - - // 添加 Cookie 到请求头 - if sessdata != "" { - req.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 设置 URL 参数 - q := req.URL.Query() - q.Add("up_mid", mid) // 用户 mid - q.Add("platform", "web") // 平台 - req.URL.RawQuery = q.Encode() - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return "", errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - return bodyString, nil -} +// Used to be GetFavCollect, getUserfavoritesCollect, GetUsersCollect, getUsersCollect here. Moved to api.go diff --git a/bilibili/compilation.go b/bilibili/compilation.go index 7b83d69..211942f 100644 --- a/bilibili/compilation.go +++ b/bilibili/compilation.go @@ -1,12 +1,5 @@ package bilibili -import ( - "errors" - "io" - "net/http" - "strconv" -) - // 用于获取收藏夹基本信息的函数 // 传入收藏夹 ID ,ps 单页大小, pn 页码 type CompliationInformation struct { @@ -27,61 +20,4 @@ type CompliationInformation struct { } } -func getCompliation(mid, sid, ps, pn string) (string, error) { - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/polymer/web-space/seasons_archives_list", nil) - if err != nil { - return "", err - } - - // // 添加 Cookie 到请求头 - // if sessdata != "" { - // req.Header.Add("Cookie", "SESSDATA="+sessdata) - // } - req.Header.Set("referer", "https://www.bilibili.com") - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0") - - // 设置 URL 参数 - q := req.URL.Query() - q.Add("mid", mid) - q.Add("season_id", sid) - q.Add("page_size", ps) - q.Add("page_num", pn) - req.URL.RawQuery = q.Encode() - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return "", errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - return bodyString, nil -} - -func GetCompliationObj(mid, sid, ps, pn int) (*CompliationInformation, error) { - var obj CompliationInformation - body, err := getCompliation(strconv.Itoa(mid), strconv.Itoa(sid), strconv.Itoa(ps), strconv.Itoa(pn)) - if err != nil { - return nil, err - } - err = decodeJson(body, &obj) - if err != nil { - return nil, err - } - - // 错误检查 - if CheckObj(obj.Code) { - return nil, errors.New(obj.Message) - } - return &obj, nil -} +// Used to be getCompliation and GetCompliationObj here. Moved to api.go diff --git a/bilibili/login.go b/bilibili/login.go index 59a08f8..b6ce1de 100644 --- a/bilibili/login.go +++ b/bilibili/login.go @@ -1,14 +1,5 @@ package bilibili -import ( - "errors" - "io" - "net/http" - "strconv" - - "github.com/tidwall/gjson" -) - // 登录密钥请求返回内容 type GetLoginKeyReturn struct { Code int `json:"code"` @@ -19,36 +10,7 @@ type GetLoginKeyReturn struct { } } -// 获取登录密钥 -func GetLoginKey() (string, string, error) { - var obj GetLoginKeyReturn - body, err := getLoginKey() - if err != nil { - return "", "", err - } - err = decodeJson(body, &obj) - if err != nil { - return "", "", err - } - // 错误检查 - if CheckObj(obj.Code) { - return "", "", errors.New(obj.Message) - } - return obj.Data.Url, obj.Data.Qrcode_key, nil -} - -// 请求登录密钥 -func getLoginKey() (string, error) { - resp, err := http.Get("https://passport.bilibili.com/x/passport-login/web/qrcode/generate") - if err != nil { - return "", err - } - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - defer resp.Body.Close() - return bodyString, nil -} +// Used to be GetLoginKey and getLoginKey here. Moved to api.go // 用于检查扫码状态和获取 cookie 的函数 type checkLoginReturn struct { @@ -63,61 +25,7 @@ type checkLoginReturn struct { } } -// 检查扫码状态 -func checkLoginStatus(qrcode_key string) (string, *[]*http.Cookie, error) { - // 创建一个 HTTP 客户端 - client := &http.Client{} - - // 创建一个 GET 请求 - req, err := http.NewRequest("GET", "https://passport.bilibili.com/x/passport-login/web/qrcode/poll", nil) - if err != nil { - return "", nil, err - } - - // 添加参数到请求的查询字符串 - q := req.URL.Query() - q.Add("qrcode_key", qrcode_key) - req.URL.RawQuery = q.Encode() - - // 发送请求并获取响应 - resp, err := client.Do(req) - if err != nil { - return "", nil, err - } - defer resp.Body.Close() - - // 检查响应状态码 - if resp.StatusCode != http.StatusOK { - return "", nil, errors.New("Error:" + strconv.Itoa(resp.StatusCode)) - } - - // 读取 Set-Cookie 头部信息 - cookies := resp.Cookies() - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - defer resp.Body.Close() - return bodyString, &cookies, nil -} - -func CheckLoginStatus(qrcode_key string) (*checkLoginReturn, *[]*http.Cookie, error) { - var obj checkLoginReturn - body, cookies, err := checkLoginStatus(qrcode_key) - if err != nil { - return nil, nil, err - } - err = decodeJson(body, &obj) - if err != nil { - return nil, nil, err - } - // 错误检查 - if CheckObj(obj.Code) { - return nil, nil, errors.New(obj.Message) - } - - return &obj, cookies, nil -} +// Used to be checkLoginStatus here. Moved to api.go // TODO: 与登录部分整合结构体 type AccountInformation struct { @@ -125,45 +33,4 @@ type AccountInformation struct { Name string `json:"name"` } -// 获取用户信息 -// https://socialsisteryi.github.io/bilibili-API-collect/docs/login/login_info.html -func (accountInf *AccountInformation) GetUserInf(sessdata string) error { - - // 创建请求 - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/web-interface/nav", nil) - if err != nil { - return err - } - - // 添加 Cookie 到请求头 - if sessdata != "" { - req.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyJson := string(body) - - // 错误检查 - if CheckObj(int(gjson.Get(bodyJson, "code").Int())) { - return errors.New(gjson.Get(bodyJson, "message").String()) - } - - accountInf.Avatar = gjson.Get(bodyJson, "data.face").String() - accountInf.Name = gjson.Get(bodyJson, "data.uname").String() - - return nil -} +// Used to be GetUserInf here. Moved to api.go diff --git a/bilibili/profile.go b/bilibili/profile.go index 079ff18..b4a05b0 100644 --- a/bilibili/profile.go +++ b/bilibili/profile.go @@ -1,62 +1,3 @@ package bilibili -import ( - "errors" - "io" - "net/http" - "strconv" -) - -// 获取用户投稿列表 -// https://socialsisteryi.github.io/bilibili-API-collect/docs/user/space.html#%E6%9F%A5%E8%AF%A2%E7%94%A8%E6%88%B7%E6%8A%95%E7%A8%BF%E8%A7%86%E9%A2%91%E6%98%8E%E7%BB%86 -func GetProfileVideo(mid, pn, ps, sessdata string) (string, error) { - // 创建请求 - request, err := http.NewRequest("GET", "https://api.bilibili.com/x/space/wbi/arc/search", nil) - if err != nil { - return "", err - } - - // 设置 URL 参数 - q := request.URL.Query() - q.Add("mid", mid) - q.Add("order", "pubdate") - q.Add("pn", pn) - q.Add("ps", ps) - request.URL.RawQuery = q.Encode() - - signedUrl, err := WbiSignURLParams(request.URL.String()) - if err != nil { - return "", errors.New("Wbi Sign Error: " + err.Error()) - } - - signedRequest, err := http.NewRequest("GET", signedUrl, nil) - if err != nil { - return "", errors.New("New Signed Request Error: " + err.Error()) - } - - signedRequest.Header.Set("referer", "https://www.bilibili.com") - signedRequest.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0") - - // 添加 Cookie 到请求头 - if sessdata != "" { - signedRequest.Header.Add("Cookie", "SESSDATA="+sessdata) - } - - // 创建 HTTP 客户端并发送请求 - client := &http.Client{} - resp, err := client.Do(signedRequest) - if err != nil { - return "", err - } - defer resp.Body.Close() - - // 检查响应状态 - if resp.StatusCode != http.StatusOK { - return "", errors.New("Error: " + strconv.Itoa(resp.StatusCode)) - } - - // 将 body 转为字符串并返回 - body, _ := io.ReadAll(resp.Body) - bodyString := string(body) - return bodyString, nil -} +// Used to be GetProfileVideo here. Moved to api.go diff --git a/bilibili/requests.go b/bilibili/requests.go new file mode 100644 index 0000000..00f54b4 --- /dev/null +++ b/bilibili/requests.go @@ -0,0 +1,147 @@ +package bilibili + +import ( + "errors" + "io" + "net/http" + "net/url" + "strconv" + "time" +) + +type requestOption func(*requestConfig) + +type requestConfig struct { + method string + url string + params map[string]string + headers map[string]string + cookies map[string]string + useWbi bool +} + +func defaultRequestConfig(rawURL string) *requestConfig { + return &requestConfig{ + method: "GET", + url: rawURL, + params: make(map[string]string), + headers: make(map[string]string), + cookies: make(map[string]string), + useWbi: false, + } +} + +// WithMethod sets the HTTP method +func WithMethod(method string) requestOption { + return func(c *requestConfig) { + c.method = method + } +} + +// WithParams adds query parameters +func WithParams(params map[string]string) requestOption { + return func(c *requestConfig) { + for k, v := range params { + c.params[k] = v + } + } +} + +// WithHeaders adds HTTP headers +func WithHeaders(headers map[string]string) requestOption { + return func(c *requestConfig) { + for k, v := range headers { + c.headers[k] = v + } + } +} + +// WithCookies adds cookies +func WithCookies(cookies map[string]string) requestOption { + return func(c *requestConfig) { + for k, v := range cookies { + c.cookies[k] = v + } + } +} + +// WithSESSDATA is a helper for SESSDATA cookie +func WithSESSDATA(sessdata string) requestOption { + return func(c *requestConfig) { + if sessdata != "" { + c.cookies["SESSDATA"] = sessdata + } + } +} + +// WithWbi enables Wbi signing +func WithWbi() requestOption { + return func(c *requestConfig) { + c.useWbi = true + } +} + +// Request sends an HTTP request with the given options +func Request(rawURL string, options ...requestOption) ([]byte, error) { + config := defaultRequestConfig(rawURL) + for _, option := range options { + option(config) + } + + reqURL, err := url.Parse(config.url) + if err != nil { + return nil, err + } + + q := reqURL.Query() + for k, v := range config.params { + q.Add(k, v) + } + reqURL.RawQuery = q.Encode() + + finalURL := reqURL.String() + if config.useWbi { + signedUrl, err := WbiSignURLParams(finalURL) + if err != nil { + return nil, errors.New("Wbi Sign Error: " + err.Error()) + } + finalURL = signedUrl + } + + req, err := http.NewRequest(config.method, finalURL, nil) + if err != nil { + return nil, err + } + + // Default Headers + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") + req.Header.Set("Referer", "https://www.bilibili.com") + + for k, v := range config.headers { + req.Header.Set(k, v) + } + + for k, v := range config.cookies { + req.AddCookie(&http.Cookie{Name: k, Value: v}) + } + + client := &http.Client{ + Timeout: 30 * time.Second, + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New("Error: " + strconv.Itoa(resp.StatusCode)) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return body, nil +} diff --git a/bilibili/wbi.go b/bilibili/wbi.go index 86d03d9..c8b38f0 100644 --- a/bilibili/wbi.go +++ b/bilibili/wbi.go @@ -3,17 +3,12 @@ package bilibili import ( "crypto/md5" "encoding/hex" - "fmt" - "io" - "net/http" "net/url" "sort" "strconv" "strings" "sync" "time" - - "github.com/tidwall/gjson" ) var ( @@ -113,31 +108,3 @@ func getWbiKeysCached() (string, string) { subKeyI, _ := cache.Load("subKey") return imgKeyI.(string), subKeyI.(string) } - -func getWbiKeys() (string, string) { - client := &http.Client{} - req, err := http.NewRequest("GET", "https://api.bilibili.com/x/web-interface/nav", nil) - if err != nil { - fmt.Printf("Error creating request: %s", err) - return "", "" - } - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") - req.Header.Set("Referer", "https://www.bilibili.com/") - resp, err := client.Do(req) - if err != nil { - fmt.Printf("Error sending request: %s", err) - return "", "" - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Printf("Error reading response: %s", err) - return "", "" - } - json := string(body) - imgURL := gjson.Get(json, "data.wbi_img.img_url").String() - subURL := gjson.Get(json, "data.wbi_img.sub_url").String() - imgKey := strings.Split(strings.Split(imgURL, "/")[len(strings.Split(imgURL, "/"))-1], ".")[0] - subKey := strings.Split(strings.Split(subURL, "/")[len(strings.Split(subURL, "/"))-1], ".")[0] - return imgKey, subKey -} diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js index 623397b..7cb89d7 100644 --- a/frontend/wailsjs/runtime/runtime.js +++ b/frontend/wailsjs/runtime/runtime.js @@ -48,6 +48,10 @@ export function EventsOff(eventName, ...additionalEventNames) { return window.runtime.EventsOff(eventName, ...additionalEventNames); } +export function EventsOffAll() { + return window.runtime.EventsOffAll(); +} + export function EventsOnce(eventName, callback) { return EventsOnMultiple(eventName, callback, 1); } diff --git a/go.mod b/go.mod index af82283..95201de 100644 --- a/go.mod +++ b/go.mod @@ -8,16 +8,17 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/viper v1.20.1 github.com/tidwall/gjson v1.18.0 - github.com/wailsapp/wails/v2 v2.10.1 + github.com/wailsapp/wails/v2 v2.11.0 ) require ( github.com/bep/debounce v1.2.1 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/labstack/echo/v4 v4.13.3 // indirect github.com/labstack/gommon v0.4.2 // indirect @@ -43,7 +44,7 @@ require ( github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/wailsapp/go-webview2 v1.0.21 // indirect + github.com/wailsapp/go-webview2 v1.0.22 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect diff --git a/go.sum b/go.sum index ea6c048..d7ef945 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= @@ -17,6 +17,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -94,12 +96,12 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA= -github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= +github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns= -github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY= +github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ= +github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= From 9137d290e1a56cc46325e3f25f6b646cce3aaa2e Mon Sep 17 00:00:00 2001 From: HIM049 Date: Sun, 25 Jan 2026 01:11:08 +0800 Subject: [PATCH 02/15] fix: file output --- backend/adapter/bilibili/audio.go | 2 +- backend/adapter/bilibili/video.go | 3 ++- backend/config/config.go | 5 +++-- backend/ffmpeg/metadata_writer.go | 8 +++++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/adapter/bilibili/audio.go b/backend/adapter/bilibili/audio.go index 87e9a17..89be991 100644 --- a/backend/adapter/bilibili/audio.go +++ b/backend/adapter/bilibili/audio.go @@ -116,7 +116,7 @@ func (a *Audio) ConventFormat() error { } func (a *Audio) WriteMetadata() error { - newPath := fmt.Sprintf("%s.meta", a.path.StreamPath) + newPath := fmt.Sprintf("%s_meta%s", a.path.StreamPath, a.path.OutputFormat) err := ffmpeg.WriteMetadata(a.path.CurrentPath, newPath, a.path.CoverPath, a.metaData.SongName, a.metaData.Author, a.path.OutputFormat) if err != nil { return err diff --git a/backend/adapter/bilibili/video.go b/backend/adapter/bilibili/video.go index 93bfebe..533dc70 100644 --- a/backend/adapter/bilibili/video.go +++ b/backend/adapter/bilibili/video.go @@ -9,9 +9,10 @@ import ( "bili-audio-downloader/bilibili" "errors" "fmt" - "github.com/tidwall/gjson" "strconv" "sync" + + "github.com/tidwall/gjson" ) type Video struct { diff --git a/backend/config/config.go b/backend/config/config.go index 3c7a684..c431b67 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -5,11 +5,12 @@ import ( "context" "errors" "fmt" - wails "github.com/wailsapp/wails/v2/pkg/runtime" "log" "os" "path/filepath" + wails "github.com/wailsapp/wails/v2/pkg/runtime" + "github.com/spf13/viper" ) @@ -172,7 +173,7 @@ func DefaultConfig() *Config { }, FileConfig: FileConfig{ ConvertFormat: false, // TODO - FileNameTemplate: "{{.ID}}_{{.Title}}({{.Subtitle}})_{{.Quality}}.{{.Format}}", + FileNameTemplate: "{{.ID}}_{{.Title}}({{.Subtitle}})_{{.Quality}}{{.Format}}", DownloadPath: "./Download", CachePath: "./Cache", VideoListPath: "./Cache/video_list.json", diff --git a/backend/ffmpeg/metadata_writer.go b/backend/ffmpeg/metadata_writer.go index 97ed20f..7d0bbb9 100644 --- a/backend/ffmpeg/metadata_writer.go +++ b/backend/ffmpeg/metadata_writer.go @@ -62,7 +62,13 @@ func WriteMetadata(input, output, coverPath, songName, songAuthor string, format args = append(args, "-id3v2_version", "3") } - args = append(args, "-f", format[1:], output) + // 确定 ffmpeg 的 -f 参数 + formatFlag := format[1:] + if format == constants.AudioType.M4a { + formatFlag = "mp4" + } + + args = append(args, "-f", formatFlag, output) log, err := utils.RunCommand("ffmpeg", args...) if err != nil { From 1067b6581c126b7002df07ecd1fe8f54b1bc905b Mon Sep 17 00:00:00 2001 From: HIM049 Date: Sun, 25 Jan 2026 02:00:53 +0800 Subject: [PATCH 03/15] perf: added tailwindcss & fix settings --- frontend/package.json | 38 ++++++++++--------- frontend/package.json.md5 | 2 +- frontend/src/App.css | 10 +++-- .../src/components/modules/frame_page.vue | 10 ++--- frontend/src/components/modules/head_bar.vue | 2 +- frontend/src/components/setting_page.vue | 34 ++++++++--------- frontend/vite.config.js | 3 +- 7 files changed, 51 insertions(+), 48 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 543fe75..82ccc02 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,19 +1,21 @@ { - "name": "frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "vue": "^3.2.37", - "@varlet/ui": "^3.6.1" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^3.0.3", - "vite": "^3.0.7" - } -} \ No newline at end of file + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.18", + "@varlet/ui": "^3.13.1", + "tailwindcss": "^4.1.18", + "vue": "^3.5.27" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.3", + "vite": "^7.3.1" + } +} diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 263dc19..ccc2399 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -4a755aa8d82c1f44ce8a545fab0d3126 \ No newline at end of file +06a5320a63796d4b8011e678f6dfc6eb \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css index f65cca1..6818440 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,3 +1,7 @@ -body, html, #root { - font-family: "Microsoft YaHei UI", "PingFang SC", "Segoe UI", "Helvetica Neue", Arial, sans-serif; -} +@import "tailwindcss"; + +body, +html, +#root { + font-family: "Microsoft YaHei UI", "PingFang SC", "Segoe UI", "Helvetica Neue", Arial, sans-serif; +} \ No newline at end of file diff --git a/frontend/src/components/modules/frame_page.vue b/frontend/src/components/modules/frame_page.vue index 8ae7d1c..11aea44 100644 --- a/frontend/src/components/modules/frame_page.vue +++ b/frontend/src/components/modules/frame_page.vue @@ -1,8 +1,8 @@