parent
f9cf29e0b6
commit
3d336b328a
@ -21,6 +21,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/mega"
|
_ "github.com/alist-org/alist/v3/drivers/mega"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
_ "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/quark"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/s3"
|
_ "github.com/alist-org/alist/v3/drivers/s3"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/sftp"
|
_ "github.com/alist-org/alist/v3/drivers/sftp"
|
||||||
|
112
drivers/pikpak_share/driver.go
Normal file
112
drivers/pikpak_share/driver.go
Normal 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)
|
27
drivers/pikpak_share/meta.go
Normal file
27
drivers/pikpak_share/meta.go
Normal 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{}
|
||||||
|
})
|
||||||
|
}
|
80
drivers/pikpak_share/types.go
Normal file
80
drivers/pikpak_share/types.go
Normal 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"`
|
||||||
|
}
|
154
drivers/pikpak_share/util.go
Normal file
154
drivers/pikpak_share/util.go
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user