mirror of https://github.com/Xhofe/alist
479 lines
12 KiB
Go
479 lines
12 KiB
Go
package baiduphoto
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
"github.com/alist-org/alist/v3/internal/errs"
|
|
"github.com/alist-org/alist/v3/internal/model"
|
|
"github.com/alist-org/alist/v3/internal/op"
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
"github.com/go-resty/resty/v2"
|
|
)
|
|
|
|
const (
|
|
API_URL = "https://photo.baidu.com/youai"
|
|
USER_API_URL = API_URL + "/user/v1"
|
|
ALBUM_API_URL = API_URL + "/album/v1"
|
|
FILE_API_URL_V1 = API_URL + "/file/v1"
|
|
FILE_API_URL_V2 = API_URL + "/file/v2"
|
|
)
|
|
|
|
func (d *BaiduPhoto) Request(client *resty.Client, furl string, method string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
|
|
req := client.R().
|
|
SetQueryParam("access_token", d.AccessToken)
|
|
if callback != nil {
|
|
callback(req)
|
|
}
|
|
if resp != nil {
|
|
req.SetResult(resp)
|
|
}
|
|
res, err := req.Execute(method, furl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
erron := utils.Json.Get(res.Body(), "errno").ToInt()
|
|
switch erron {
|
|
case 0:
|
|
break
|
|
case 50805:
|
|
return nil, fmt.Errorf("you have joined album")
|
|
case 50820:
|
|
return nil, fmt.Errorf("no shared albums found")
|
|
case 50100:
|
|
return nil, fmt.Errorf("illegal title, only supports 50 characters")
|
|
case -6:
|
|
if err = d.refreshToken(); err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("errno: %d, refer to https://photo.baidu.com/union/doc", erron)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
//func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
|
// res, err := d.request(furl, method, callback, resp)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// return res.Body(), nil
|
|
//}
|
|
|
|
func (d *BaiduPhoto) refreshToken() error {
|
|
u := "https://openapi.baidu.com/oauth/2.0/token"
|
|
var resp base.TokenResp
|
|
var e TokenErrResp
|
|
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
|
|
"grant_type": "refresh_token",
|
|
"refresh_token": d.RefreshToken,
|
|
"client_id": d.ClientID,
|
|
"client_secret": d.ClientSecret,
|
|
}).Get(u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.ErrorMsg != "" {
|
|
return &e
|
|
}
|
|
if resp.RefreshToken == "" {
|
|
return errs.EmptyToken
|
|
}
|
|
d.AccessToken, d.RefreshToken = resp.AccessToken, resp.RefreshToken
|
|
op.MustSaveDriverStorage(d)
|
|
return nil
|
|
}
|
|
|
|
func (d *BaiduPhoto) Get(furl string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
|
|
return d.Request(base.RestyClient, furl, http.MethodGet, callback, resp)
|
|
}
|
|
|
|
func (d *BaiduPhoto) Post(furl string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
|
|
return d.Request(base.RestyClient, furl, http.MethodPost, callback, resp)
|
|
}
|
|
|
|
// 获取所有文件
|
|
func (d *BaiduPhoto) GetAllFile(ctx context.Context) (files []File, err error) {
|
|
var cursor string
|
|
for {
|
|
var resp FileListResp
|
|
_, err = d.Get(FILE_API_URL_V1+"/list", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetQueryParams(map[string]string{
|
|
"need_thumbnail": "1",
|
|
"need_filter_hidden": "0",
|
|
"cursor": cursor,
|
|
})
|
|
}, &resp)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
files = append(files, resp.List...)
|
|
if !resp.HasNextPage() {
|
|
return
|
|
}
|
|
cursor = resp.Cursor
|
|
}
|
|
}
|
|
|
|
// 删除根文件
|
|
func (d *BaiduPhoto) DeleteFile(ctx context.Context, file *File) error {
|
|
_, err := d.Get(FILE_API_URL_V1+"/delete", func(req *resty.Request) {
|
|
req.SetContext(ctx)
|
|
req.SetQueryParams(map[string]string{
|
|
"fsid_list": fmt.Sprintf("[%d]", file.Fsid),
|
|
})
|
|
}, nil)
|
|
return err
|
|
}
|
|
|
|
// 获取所有相册
|
|
func (d *BaiduPhoto) GetAllAlbum(ctx context.Context) (albums []Album, err error) {
|
|
var cursor string
|
|
for {
|
|
var resp AlbumListResp
|
|
_, err = d.Get(ALBUM_API_URL+"/list", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetQueryParams(map[string]string{
|
|
"need_amount": "1",
|
|
"limit": "100",
|
|
"cursor": cursor,
|
|
})
|
|
}, &resp)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if albums == nil {
|
|
albums = make([]Album, 0, resp.TotalCount)
|
|
}
|
|
|
|
cursor = resp.Cursor
|
|
albums = append(albums, resp.List...)
|
|
|
|
if !resp.HasNextPage() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 获取相册中所有文件
|
|
func (d *BaiduPhoto) GetAllAlbumFile(ctx context.Context, album *Album, passwd string) (files []AlbumFile, err error) {
|
|
var cursor string
|
|
for {
|
|
var resp AlbumFileListResp
|
|
_, err = d.Get(ALBUM_API_URL+"/listfile", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetQueryParams(map[string]string{
|
|
"album_id": album.AlbumID,
|
|
"need_amount": "1",
|
|
"limit": "1000",
|
|
"passwd": passwd,
|
|
"cursor": cursor,
|
|
})
|
|
}, &resp)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if files == nil {
|
|
files = make([]AlbumFile, 0, resp.TotalCount)
|
|
}
|
|
|
|
cursor = resp.Cursor
|
|
files = append(files, resp.List...)
|
|
|
|
if !resp.HasNextPage() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 创建相册
|
|
func (d *BaiduPhoto) CreateAlbum(ctx context.Context, name string) (*Album, error) {
|
|
var resp JoinOrCreateAlbumResp
|
|
_, err := d.Post(ALBUM_API_URL+"/create", func(r *resty.Request) {
|
|
r.SetContext(ctx).SetResult(&resp)
|
|
r.SetQueryParams(map[string]string{
|
|
"title": name,
|
|
"tid": getTid(),
|
|
"source": "0",
|
|
})
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return d.GetAlbumDetail(ctx, resp.AlbumID)
|
|
}
|
|
|
|
// 相册改名
|
|
func (d *BaiduPhoto) SetAlbumName(ctx context.Context, album *Album, name string) (*Album, error) {
|
|
_, err := d.Post(ALBUM_API_URL+"/settitle", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetFormData(map[string]string{
|
|
"title": name,
|
|
"album_id": album.AlbumID,
|
|
"tid": fmt.Sprint(album.Tid),
|
|
})
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return renameAlbum(album, name), nil
|
|
}
|
|
|
|
// 删除相册
|
|
func (d *BaiduPhoto) DeleteAlbum(ctx context.Context, album *Album) error {
|
|
_, err := d.Post(ALBUM_API_URL+"/delete", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetFormData(map[string]string{
|
|
"album_id": album.AlbumID,
|
|
"tid": fmt.Sprint(album.Tid),
|
|
"delete_origin_image": BoolToIntStr(d.DeleteOrigin), // 是否删除原图 0 不删除 1 删除
|
|
})
|
|
}, nil)
|
|
return err
|
|
}
|
|
|
|
// 删除相册文件
|
|
func (d *BaiduPhoto) DeleteAlbumFile(ctx context.Context, file *AlbumFile) error {
|
|
_, err := d.Post(ALBUM_API_URL+"/delfile", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetFormData(map[string]string{
|
|
"album_id": fmt.Sprint(file.AlbumID),
|
|
"tid": fmt.Sprint(file.Tid),
|
|
"list": fmt.Sprintf(`[{"fsid":%d,"uk":%d}]`, file.Fsid, file.Uk),
|
|
"del_origin": BoolToIntStr(d.DeleteOrigin), // 是否删除原图 0 不删除 1 删除
|
|
})
|
|
}, nil)
|
|
return err
|
|
}
|
|
|
|
// 增加相册文件
|
|
func (d *BaiduPhoto) AddAlbumFile(ctx context.Context, album *Album, file *File) (*AlbumFile, error) {
|
|
_, err := d.Get(ALBUM_API_URL+"/addfile", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetQueryParams(map[string]string{
|
|
"album_id": fmt.Sprint(album.AlbumID),
|
|
"tid": fmt.Sprint(album.Tid),
|
|
"list": fsidsFormatNotUk(file.Fsid),
|
|
})
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return moveFileToAlbumFile(file, album, d.Uk), nil
|
|
}
|
|
|
|
// 保存相册文件为根文件
|
|
func (d *BaiduPhoto) CopyAlbumFile(ctx context.Context, file *AlbumFile) (*File, error) {
|
|
var resp CopyFileResp
|
|
_, err := d.Post(ALBUM_API_URL+"/copyfile", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetFormData(map[string]string{
|
|
"album_id": file.AlbumID,
|
|
"tid": fmt.Sprint(file.Tid),
|
|
"uk": fmt.Sprint(file.Uk),
|
|
"list": fsidsFormatNotUk(file.Fsid),
|
|
})
|
|
r.SetResult(&resp)
|
|
}, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return copyFile(file, &resp.List[0]), nil
|
|
}
|
|
|
|
// 加入相册
|
|
func (d *BaiduPhoto) JoinAlbum(ctx context.Context, code string) (*Album, error) {
|
|
var resp InviteResp
|
|
_, err := d.Get(ALBUM_API_URL+"/querypcode", func(req *resty.Request) {
|
|
req.SetContext(ctx)
|
|
req.SetQueryParams(map[string]string{
|
|
"pcode": code,
|
|
"web": "1",
|
|
})
|
|
}, &resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var resp2 JoinOrCreateAlbumResp
|
|
_, err = d.Get(ALBUM_API_URL+"/join", func(req *resty.Request) {
|
|
req.SetContext(ctx)
|
|
req.SetQueryParams(map[string]string{
|
|
"invite_code": resp.Pdata.InviteCode,
|
|
})
|
|
}, &resp2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return d.GetAlbumDetail(ctx, resp2.AlbumID)
|
|
}
|
|
|
|
// 获取相册详细信息
|
|
func (d *BaiduPhoto) GetAlbumDetail(ctx context.Context, albumID string) (*Album, error) {
|
|
var album Album
|
|
_, err := d.Get(ALBUM_API_URL+"/detail", func(req *resty.Request) {
|
|
req.SetContext(ctx).SetResult(&album)
|
|
req.SetQueryParams(map[string]string{
|
|
"album_id": albumID,
|
|
})
|
|
}, &album)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &album, nil
|
|
}
|
|
|
|
func (d *BaiduPhoto) linkAlbum(ctx context.Context, file *AlbumFile, args model.LinkArgs) (*model.Link, error) {
|
|
headers := map[string]string{
|
|
"User-Agent": base.UserAgent,
|
|
}
|
|
if args.Header.Get("User-Agent") != "" {
|
|
headers["User-Agent"] = args.Header.Get("User-Agent")
|
|
}
|
|
if !utils.IsLocalIPAddr(args.IP) {
|
|
headers["X-Forwarded-For"] = args.IP
|
|
}
|
|
|
|
resp, err := d.Request(base.NoRedirectClient, ALBUM_API_URL+"/download", http.MethodHead, func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetHeaders(headers)
|
|
r.SetQueryParams(map[string]string{
|
|
"fsid": fmt.Sprint(file.Fsid),
|
|
"album_id": file.AlbumID,
|
|
"tid": fmt.Sprint(file.Tid),
|
|
"uk": fmt.Sprint(file.Uk),
|
|
})
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode() != 302 {
|
|
return nil, fmt.Errorf("not found 302 redirect")
|
|
}
|
|
|
|
location := resp.Header().Get("Location")
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
link := &model.Link{
|
|
URL: location,
|
|
Header: http.Header{
|
|
"User-Agent": []string{headers["User-Agent"]},
|
|
"Referer": []string{"https://photo.baidu.com/"},
|
|
},
|
|
}
|
|
return link, nil
|
|
}
|
|
|
|
func (d *BaiduPhoto) linkFile(ctx context.Context, file *File, args model.LinkArgs) (*model.Link, error) {
|
|
headers := map[string]string{
|
|
"User-Agent": base.UserAgent,
|
|
}
|
|
if args.Header.Get("User-Agent") != "" {
|
|
headers["User-Agent"] = args.Header.Get("User-Agent")
|
|
}
|
|
if !utils.IsLocalIPAddr(args.IP) {
|
|
headers["X-Forwarded-For"] = args.IP
|
|
}
|
|
|
|
// var downloadUrl struct {
|
|
// Dlink string `json:"dlink"`
|
|
// }
|
|
// _, err := d.Get(FILE_API_URL_V1+"/download", func(r *resty.Request) {
|
|
// r.SetContext(ctx)
|
|
// r.SetHeaders(headers)
|
|
// r.SetQueryParams(map[string]string{
|
|
// "fsid": fmt.Sprint(file.Fsid),
|
|
// })
|
|
// }, &downloadUrl)
|
|
|
|
resp, err := d.Request(base.NoRedirectClient, FILE_API_URL_V1+"/download", http.MethodHead, func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetHeaders(headers)
|
|
r.SetQueryParams(map[string]string{
|
|
"fsid": fmt.Sprint(file.Fsid),
|
|
})
|
|
}, nil)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode() != 302 {
|
|
return nil, fmt.Errorf("not found 302 redirect")
|
|
}
|
|
|
|
location := resp.Header().Get("Location")
|
|
link := &model.Link{
|
|
URL: location,
|
|
Header: http.Header{
|
|
"User-Agent": []string{headers["User-Agent"]},
|
|
"Referer": []string{"https://photo.baidu.com/"},
|
|
},
|
|
}
|
|
return link, nil
|
|
}
|
|
|
|
/*func (d *BaiduPhoto) linkStreamAlbum(ctx context.Context, file *AlbumFile) (*model.Link, error) {
|
|
return &model.Link{
|
|
Header: http.Header{},
|
|
Writer: func(w io.Writer) error {
|
|
res, err := d.Get(ALBUM_API_URL+"/streaming", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetQueryParams(map[string]string{
|
|
"fsid": fmt.Sprint(file.Fsid),
|
|
"album_id": file.AlbumID,
|
|
"tid": fmt.Sprint(file.Tid),
|
|
"uk": fmt.Sprint(file.Uk),
|
|
}).SetDoNotParseResponse(true)
|
|
}, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.RawBody().Close()
|
|
_, err = io.Copy(w, res.RawBody())
|
|
return err
|
|
},
|
|
}, nil
|
|
}*/
|
|
|
|
/*func (d *BaiduPhoto) linkStream(ctx context.Context, file *File) (*model.Link, error) {
|
|
return &model.Link{
|
|
Header: http.Header{},
|
|
Writer: func(w io.Writer) error {
|
|
res, err := d.Get(FILE_API_URL_V1+"/streaming", func(r *resty.Request) {
|
|
r.SetContext(ctx)
|
|
r.SetQueryParams(map[string]string{
|
|
"fsid": fmt.Sprint(file.Fsid),
|
|
}).SetDoNotParseResponse(true)
|
|
}, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.RawBody().Close()
|
|
_, err = io.Copy(w, res.RawBody())
|
|
return err
|
|
},
|
|
}, nil
|
|
}*/
|
|
|
|
// 获取uk
|
|
func (d *BaiduPhoto) uInfo() (*UInfo, error) {
|
|
var info UInfo
|
|
_, err := d.Get(USER_API_URL+"/getuinfo", func(req *resty.Request) {
|
|
|
|
}, &info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &info, nil
|
|
}
|