feat: add pikpak share driver (close #2728 pr #2731)

This commit is contained in:
AkashiCoin 2022-12-16 19:10:19 +08:00 committed by GitHub
parent f9cf29e0b6
commit 3d336b328a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 374 additions and 0 deletions

View File

@ -21,6 +21,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/mega"
_ "github.com/alist-org/alist/v3/drivers/onedrive"
_ "github.com/alist-org/alist/v3/drivers/pikpak"
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
_ "github.com/alist-org/alist/v3/drivers/quark"
_ "github.com/alist-org/alist/v3/drivers/s3"
_ "github.com/alist-org/alist/v3/drivers/sftp"

View File

@ -0,0 +1,112 @@
package pikpak_share
import (
"context"
"net/http"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
type PikPakShare struct {
model.Storage
Addition
RefreshToken string
AccessToken string
PassCodeToken string
}
func (d *PikPakShare) Config() driver.Config {
return config
}
func (d *PikPakShare) GetAddition() driver.Additional {
return &d.Addition
}
func (d *PikPakShare) Init(ctx context.Context) error {
err := d.login()
if err != nil {
return err
}
if d.SharePwd != "" {
err = d.getSharePassToken()
if err != nil {
return err
}
}
return nil
}
func (d *PikPakShare) Drop(ctx context.Context) error {
return nil
}
func (d *PikPakShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetID())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
return fileToObj(src), nil
})
}
func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
var resp ShareResp
query := map[string]string{
"share_id": d.ShareId,
"file_id": file.GetID(),
"pass_code_token": d.PassCodeToken,
}
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/share/file_info", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return nil, err
}
link := model.Link{
URL: resp.FileInfo.WebContentLink,
}
if len(resp.FileInfo.Medias) > 0 && resp.FileInfo.Medias[0].Link.Url != "" {
log.Debugln("use media link")
link.URL = resp.FileInfo.Medias[0].Link.Url
}
return &link, nil
}
func (d *PikPakShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
// TODO create folder
return errs.NotSupport
}
func (d *PikPakShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
// TODO move obj
return errs.NotSupport
}
func (d *PikPakShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
// TODO rename obj
return errs.NotSupport
}
func (d *PikPakShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
// TODO copy obj
return errs.NotSupport
}
func (d *PikPakShare) Remove(ctx context.Context, obj model.Obj) error {
// TODO remove obj
return errs.NotSupport
}
func (d *PikPakShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
// TODO upload file
return errs.NotSupport
}
var _ driver.Driver = (*PikPakShare)(nil)

View File

@ -0,0 +1,27 @@
package pikpak_share
import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/op"
)
type Addition struct {
driver.RootID
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
ShareId string `json:"share_id" required:"true"`
SharePwd string `json:"share_pwd"`
}
var config = driver.Config{
Name: "PikPakShare",
LocalSort: true,
NoUpload: true,
DefaultRoot: "",
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &PikPakShare{}
})
}

View File

@ -0,0 +1,80 @@
package pikpak_share
import (
"strconv"
"time"
"github.com/alist-org/alist/v3/internal/model"
)
type RespErr struct {
ErrorCode int `json:"error_code"`
Error string `json:"error"`
}
type ShareResp struct {
ShareStatus string `json:"share_status"`
ShareStatusText string `json:"share_status_text"`
FileInfo File `json:"file_info"`
Files []File `json:"files"`
NextPageToken string `json:"next_page_token"`
PassCodeToken string `json:"pass_code_token"`
}
type File struct {
Id string `json:"id"`
ShareId string `json:"share_id"`
Kind string `json:"kind"`
Name string `json:"name"`
ModifiedTime time.Time `json:"modified_time"`
Size string `json:"size"`
ThumbnailLink string `json:"thumbnail_link"`
WebContentLink string `json:"web_content_link"`
Medias []Media `json:"medias"`
}
func fileToObj(f File) *model.ObjThumb {
size, _ := strconv.ParseInt(f.Size, 10, 64)
return &model.ObjThumb{
Object: model.Object{
ID: f.Id,
Name: f.Name,
Size: size,
Modified: f.ModifiedTime,
IsFolder: f.Kind == "drive#folder",
},
Thumbnail: model.Thumbnail{
Thumbnail: f.ThumbnailLink,
},
}
}
type Media struct {
MediaId string `json:"media_id"`
MediaName string `json:"media_name"`
Video struct {
Height int `json:"height"`
Width int `json:"width"`
Duration int `json:"duration"`
BitRate int `json:"bit_rate"`
FrameRate int `json:"frame_rate"`
VideoCodec string `json:"video_codec"`
AudioCodec string `json:"audio_codec"`
VideoType string `json:"video_type"`
} `json:"video"`
Link struct {
Url string `json:"url"`
Token string `json:"token"`
Expire time.Time `json:"expire"`
} `json:"link"`
NeedMoreQuota bool `json:"need_more_quota"`
VipTypes []interface{} `json:"vip_types"`
RedirectLink string `json:"redirect_link"`
IconLink string `json:"icon_link"`
IsDefault bool `json:"is_default"`
Priority int `json:"priority"`
IsOrigin bool `json:"is_origin"`
ResolutionName string `json:"resolution_name"`
IsVisible bool `json:"is_visible"`
Category string `json:"category"`
}

View File

@ -0,0 +1,154 @@
package pikpak_share
import (
"errors"
"net/http"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/op"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
)
// do others that not defined in Driver interface
func (d *PikPakShare) login() error {
url := "https://user.mypikpak.com/v1/auth/signin"
var e RespErr
res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{
"captcha_token": "",
"client_id": "YNxT9w7GMdWvEOKa",
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
"username": d.Username,
"password": d.Password,
}).Post(url)
if err != nil {
return err
}
if e.ErrorCode != 0 {
return errors.New(e.Error)
}
data := res.Body()
d.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
d.AccessToken = jsoniter.Get(data, "access_token").ToString()
return nil
}
func (d *PikPakShare) refreshToken() error {
url := "https://user.mypikpak.com/v1/auth/token"
var e RespErr
res, err := base.RestyClient.R().SetError(&e).
SetHeader("user-agent", "").SetBody(base.Json{
"client_id": "YNxT9w7GMdWvEOKa",
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
"grant_type": "refresh_token",
"refresh_token": d.RefreshToken,
}).Post(url)
if err != nil {
d.Status = err.Error()
op.MustSaveDriverStorage(d)
return err
}
if e.ErrorCode != 0 {
if e.ErrorCode == 4126 {
// refresh_token invalid, re-login
return d.login()
}
d.Status = e.Error
op.MustSaveDriverStorage(d)
return errors.New(e.Error)
}
data := res.Body()
d.Status = "work"
d.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
d.AccessToken = jsoniter.Get(data, "access_token").ToString()
op.MustSaveDriverStorage(d)
return nil
}
func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e RespErr
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
if e.ErrorCode != 0 {
if e.ErrorCode == 16 {
// login / refresh token
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.request(url, method, callback, resp)
}
return nil, errors.New(e.Error)
}
return res.Body(), nil
}
func (d *PikPakShare) getSharePassToken() error {
query := map[string]string{
"share_id": d.ShareId,
"pass_code": d.SharePwd,
"thumbnail_size": "SIZE_LARGE",
"limit": "100",
}
var resp ShareResp
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/share", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return err
}
d.PassCodeToken = resp.PassCodeToken
return nil
}
func (d *PikPakShare) getFiles(id string) ([]File, error) {
res := make([]File, 0)
pageToken := "first"
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
query := map[string]string{
"parent_id": id,
"share_id": d.ShareId,
"thumbnail_size": "SIZE_LARGE",
"with_audit": "true",
"limit": "100",
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
"page_token": pageToken,
"pass_code_token": d.PassCodeToken,
}
var resp ShareResp
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/share/detail", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return nil, err
}
if resp.ShareStatus != "OK" {
if resp.ShareStatus == "PASS_CODE_EMPTY" || resp.ShareStatus == "PASS_CODE_ERROR" {
err = d.getSharePassToken()
if err != nil {
return nil, err
}
return d.getFiles(id)
}
return nil, errors.New(resp.ShareStatusText)
}
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}