Files
BiliDown/internal/bilibili/client.go
T
yw1573 dd8eaef97d refactor: restructure project to standard Go layout
- Reorganize code into cmd/bilidown and internal/ packages
- Rename client/ to web/ for frontend source
- Remove systray dependency for headless web service
- Embed static files into binary using go:embed
- Update import paths to use internal/ prefix
- Update .gitignore with common patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 17:21:49 +08:00

178 lines
4.5 KiB
Go

package bilibili
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
"net/url"
"regexp"
"bilidown/internal/util"
)
type BiliClient struct {
SESSDATA string
}
// SimpleGET 简单的 GET 请求
func (client *BiliClient) SimpleGET(_url string, params map[string]string) (*http.Response, error) {
values := url.Values{}
for k, v := range params {
values.Set(k, v)
}
_client := http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(nil),
},
}
request, err := http.NewRequest("GET", _url+"?"+values.Encode(), nil)
if err != nil {
return nil, err
}
request.Header = client.MakeHeader()
return _client.Do(request)
}
// MakeHeader 生成请求头
func (client *BiliClient) MakeHeader() http.Header {
header := http.Header{}
header.Set("Cookie", "SESSDATA="+client.SESSDATA)
header.Set("User-Agent", "Mozilla/5.0")
header.Set("Referer", "https://www.bilibili.com")
return header
}
// CheckLogin 检查是否已经登录
func (client *BiliClient) CheckLogin() (bool, error) {
response, err := client.SimpleGET("https://api.bilibili.com/x/space/myinfo", nil)
if err != nil {
return false, err
}
defer response.Body.Close()
body := BaseResV2{}
err = json.NewDecoder(response.Body).Decode(&body)
if err != nil {
return false, err
}
if body.Code != 0 {
return false, errors.New(body.Message)
}
return body.Success(), nil
}
// NewQRInfo 获取登录二维码信息
func (client *BiliClient) NewQRInfo() (*QRInfo, error) {
response, err := client.SimpleGET("https://passport.bilibili.com/x/passport-login/web/qrcode/generate", nil)
if err != nil {
return nil, err
}
defer response.Body.Close()
body := BaseResV2{}
err = json.NewDecoder(response.Body).Decode(&body)
if err != nil {
return nil, err
}
if body.Code != 0 {
return nil, errors.New(body.Message)
}
qrInfo := QRInfo{}
err = json.Unmarshal(body.Data, &qrInfo)
if err != nil {
return nil, err
}
return &qrInfo, nil
}
func (client *BiliClient) getWbiKeyRemote() (wbiKey string, err error) {
if client.SESSDATA == "" {
return "", errors.New("SESSDATA 不能为空")
}
response, err := client.SimpleGET("https://api.bilibili.com/x/web-interface/nav", nil)
if err != nil {
return "", err
}
defer response.Body.Close()
body := BaseResV2{}
if err = json.NewDecoder(response.Body).Decode(&body); err != nil {
return "", err
}
var data struct {
WbiImg struct {
ImgURL string `json:"img_url"`
SubURL string `json:"sub_url"`
} `json:"wbi_img"`
}
if err = json.Unmarshal(body.Data, &data); err != nil {
return "", err
}
match := regexp.MustCompile(`/bfs/wbi/([a-z0-9]+)\.`)
imgKey := match.FindStringSubmatch(data.WbiImg.ImgURL)[1]
subKey := match.FindStringSubmatch(data.WbiImg.SubURL)[1]
if imgKey == "" || subKey == "" {
return "", errors.New("regexp.MustCompile(`/bfs/wbi/([a-z0-9])\\.`)")
}
return imgKey + subKey, nil
}
// GetQRStatus 获取二维码状态
func (client *BiliClient) GetQRStatus(qrKey string) (qrStatus *QRStatus, sessdata string, err error) {
params := map[string]string{
"qrcode_key": qrKey,
}
response, err := client.SimpleGET("https://passport.bilibili.com/x/passport-login/web/qrcode/poll", params)
if err != nil {
return nil, "", err
}
defer response.Body.Close()
body := BaseResV2{}
err = json.NewDecoder(response.Body).Decode(&body)
if err != nil {
return nil, "", err
}
if body.Code != 0 {
return nil, "", errors.New(body.Message)
}
qrStatus = &QRStatus{}
err = json.Unmarshal(body.Data, &qrStatus)
if err != nil {
return nil, "", err
}
if qrStatus.Code != 0 {
return qrStatus, "", nil
}
sessdata, err = GetCookieValue(response.Cookies(), "SESSDATA")
if err != nil {
return nil, "", err
}
return qrStatus, sessdata, nil
}
// GetCookieValue 获取指定 Name 的 Cookie 值
func GetCookieValue(cookies []*http.Cookie, name string) (string, error) {
for _, cookie := range cookies {
if cookie.Name == name {
return cookie.Value, nil
}
}
return "", errors.New("cookie with name " + name + " not found")
}
// SaveSessdata 保存 SESSDATA
func SaveSessdata(db *sql.DB, sessdata string) error {
util.SqliteLock.Lock()
_, err := db.Exec(`INSERT OR REPLACE INTO "field" ("name", "value") VALUES ("sessdata", ?)`, sessdata)
util.SqliteLock.Unlock()
return err
}
// GetSessdata 获取 SESSDATA
func GetSessdata(db *sql.DB) (string, error) {
util.SqliteLock.Lock()
row := db.QueryRow(`SELECT "value" FROM "field" WHERE "name" = "sessdata"`)
util.SqliteLock.Unlock()
var sessdata string
err := row.Scan(&sessdata)
return sessdata, err
}