2022-09-01 14:13:37 +00:00
|
|
|
|
package pikpak
|
2022-08-31 09:32:57 +00:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2024-07-07 08:50:05 +00:00
|
|
|
|
"encoding/json"
|
2022-08-31 09:32:57 +00:00
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
|
|
|
"github.com/alist-org/alist/v3/internal/driver"
|
|
|
|
|
"github.com/alist-org/alist/v3/internal/model"
|
2024-08-21 16:35:52 +00:00
|
|
|
|
"github.com/alist-org/alist/v3/internal/op"
|
2022-08-31 09:32:57 +00:00
|
|
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
2023-09-06 06:46:35 +00:00
|
|
|
|
hash_extend "github.com/alist-org/alist/v3/pkg/utils/hash"
|
2022-08-31 09:32:57 +00:00
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2024-08-21 16:35:52 +00:00
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2022-08-31 09:32:57 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PikPak struct {
|
|
|
|
|
model.Storage
|
|
|
|
|
Addition
|
2024-07-14 13:07:00 +00:00
|
|
|
|
*Common
|
2024-08-15 13:46:55 +00:00
|
|
|
|
RefreshToken string
|
|
|
|
|
AccessToken string
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Config() driver.Config {
|
|
|
|
|
return config
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) GetAddition() driver.Additional {
|
2022-12-13 10:03:30 +00:00
|
|
|
|
return &d.Addition
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-22 15:29:29 +00:00
|
|
|
|
func (d *PikPak) Init(ctx context.Context) (err error) {
|
|
|
|
|
|
2024-07-14 13:07:00 +00:00
|
|
|
|
if d.Common == nil {
|
|
|
|
|
d.Common = &Common{
|
|
|
|
|
client: base.NewRestyClient(),
|
|
|
|
|
CaptchaToken: "",
|
|
|
|
|
UserID: "",
|
|
|
|
|
DeviceID: utils.GetMD5EncodeStr(d.Username + d.Password),
|
2024-08-18 15:26:29 +00:00
|
|
|
|
UserAgent: "",
|
2024-07-14 13:07:00 +00:00
|
|
|
|
RefreshCTokenCk: func(token string) {
|
|
|
|
|
d.Common.CaptchaToken = token
|
|
|
|
|
op.MustSaveDriverStorage(d)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-18 15:26:29 +00:00
|
|
|
|
if d.Platform == "android" {
|
|
|
|
|
d.ClientID = AndroidClientID
|
|
|
|
|
d.ClientSecret = AndroidClientSecret
|
|
|
|
|
d.ClientVersion = AndroidClientVersion
|
|
|
|
|
d.PackageName = AndroidPackageName
|
|
|
|
|
d.Algorithms = AndroidAlgorithms
|
|
|
|
|
d.UserAgent = BuildCustomUserAgent(utils.GetMD5EncodeStr(d.Username+d.Password), AndroidClientID, AndroidPackageName, AndroidSdkVersion, AndroidClientVersion, AndroidPackageName, "")
|
|
|
|
|
} else if d.Platform == "web" {
|
|
|
|
|
d.ClientID = WebClientID
|
|
|
|
|
d.ClientSecret = WebClientSecret
|
|
|
|
|
d.ClientVersion = WebClientVersion
|
|
|
|
|
d.PackageName = WebPackageName
|
|
|
|
|
d.Algorithms = WebAlgorithms
|
|
|
|
|
d.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
|
2024-09-08 02:45:43 +00:00
|
|
|
|
} else if d.Platform == "pc" {
|
|
|
|
|
d.ClientID = PCClientID
|
|
|
|
|
d.ClientSecret = PCClientSecret
|
|
|
|
|
d.ClientVersion = PCClientVersion
|
|
|
|
|
d.PackageName = PCPackageName
|
|
|
|
|
d.Algorithms = PCAlgorithms
|
|
|
|
|
d.UserAgent = "MainWindow Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) PikPak/2.5.6.4831 Chrome/100.0.4896.160 Electron/18.3.15 Safari/537.36"
|
2024-08-18 15:26:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 13:46:55 +00:00
|
|
|
|
if d.Addition.CaptchaToken != "" && d.Addition.RefreshToken == "" {
|
|
|
|
|
d.SetCaptchaToken(d.Addition.CaptchaToken)
|
2024-05-22 15:29:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-18 15:26:29 +00:00
|
|
|
|
if d.Addition.DeviceID != "" {
|
|
|
|
|
d.SetDeviceID(d.Addition.DeviceID)
|
|
|
|
|
} else {
|
|
|
|
|
d.Addition.DeviceID = d.Common.DeviceID
|
|
|
|
|
op.MustSaveDriverStorage(d)
|
|
|
|
|
}
|
|
|
|
|
// 如果已经有RefreshToken,直接获取AccessToken
|
2024-08-15 13:46:55 +00:00
|
|
|
|
if d.Addition.RefreshToken != "" {
|
2024-12-07 09:03:46 +00:00
|
|
|
|
if err = d.refreshToken(d.Addition.RefreshToken); err != nil {
|
|
|
|
|
return err
|
2024-09-01 15:06:51 +00:00
|
|
|
|
}
|
2024-08-15 13:46:55 +00:00
|
|
|
|
} else {
|
2024-08-18 15:26:29 +00:00
|
|
|
|
// 如果没有填写RefreshToken,尝试登录 获取 refreshToken
|
2024-12-07 09:03:46 +00:00
|
|
|
|
if err = d.login(); err != nil {
|
2024-08-15 13:46:55 +00:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-16 08:00:05 +00:00
|
|
|
|
|
|
|
|
|
// 获取CaptchaToken
|
2024-11-17 12:03:04 +00:00
|
|
|
|
err = d.RefreshCaptchaTokenAtLogin(GetAction(http.MethodGet, "https://api-drive.mypikpak.net/drive/v1/files"), d.Common.GetUserID())
|
2024-08-15 13:46:55 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-08-21 16:35:52 +00:00
|
|
|
|
|
2024-07-14 13:07:00 +00:00
|
|
|
|
// 更新UserAgent
|
2024-08-18 15:26:29 +00:00
|
|
|
|
if d.Platform == "android" {
|
|
|
|
|
d.Common.UserAgent = BuildCustomUserAgent(utils.GetMD5EncodeStr(d.Username+d.Password), AndroidClientID, AndroidPackageName, AndroidSdkVersion, AndroidClientVersion, AndroidPackageName, d.Common.UserID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存 有效的 RefreshToken
|
|
|
|
|
d.Addition.RefreshToken = d.RefreshToken
|
|
|
|
|
op.MustSaveDriverStorage(d)
|
2024-09-08 02:45:43 +00:00
|
|
|
|
|
2024-05-22 15:29:29 +00:00
|
|
|
|
return nil
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Drop(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) 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
|
|
|
|
|
}
|
2022-09-04 16:26:04 +00:00
|
|
|
|
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
|
|
|
|
return fileToObj(src), nil
|
|
|
|
|
})
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
|
|
|
var resp File
|
2024-09-08 02:45:43 +00:00
|
|
|
|
var url string
|
2024-08-18 15:26:29 +00:00
|
|
|
|
queryParams := map[string]string{
|
|
|
|
|
"_magic": "2021",
|
|
|
|
|
"usage": "FETCH",
|
|
|
|
|
"thumbnail_size": "SIZE_LARGE",
|
|
|
|
|
}
|
|
|
|
|
if !d.DisableMediaLink {
|
|
|
|
|
queryParams["usage"] = "CACHE"
|
|
|
|
|
}
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request(fmt.Sprintf("https://api-drive.mypikpak.net/drive/v1/files/%s", file.GetID()),
|
2024-08-18 15:26:29 +00:00
|
|
|
|
http.MethodGet, func(req *resty.Request) {
|
|
|
|
|
req.SetQueryParams(queryParams)
|
|
|
|
|
}, &resp)
|
2022-08-31 09:32:57 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-09-08 02:45:43 +00:00
|
|
|
|
url = resp.WebContentLink
|
|
|
|
|
|
2023-07-11 05:40:58 +00:00
|
|
|
|
if !d.DisableMediaLink && len(resp.Medias) > 0 && resp.Medias[0].Link.Url != "" {
|
2022-08-31 09:32:57 +00:00
|
|
|
|
log.Debugln("use media link")
|
2024-09-08 02:45:43 +00:00
|
|
|
|
url = resp.Medias[0].Link.Url
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &model.Link{
|
|
|
|
|
URL: url,
|
|
|
|
|
}, nil
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodPost, func(req *resty.Request) {
|
2022-08-31 09:32:57 +00:00
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"kind": "drive#folder",
|
|
|
|
|
"parent_id": parentDir.GetID(),
|
|
|
|
|
"name": dirName,
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files:batchMove", http.MethodPost, func(req *resty.Request) {
|
2022-08-31 09:32:57 +00:00
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"ids": []string{srcObj.GetID()},
|
|
|
|
|
"to": base.Json{
|
|
|
|
|
"parent_id": dstDir.GetID(),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files/"+srcObj.GetID(), http.MethodPatch, func(req *resty.Request) {
|
2022-08-31 09:32:57 +00:00
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"name": newName,
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files:batchCopy", http.MethodPost, func(req *resty.Request) {
|
2022-08-31 09:32:57 +00:00
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"ids": []string{srcObj.GetID()},
|
|
|
|
|
"to": base.Json{
|
|
|
|
|
"parent_id": dstDir.GetID(),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Remove(ctx context.Context, obj model.Obj) error {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files:batchTrash", http.MethodPost, func(req *resty.Request) {
|
2022-08-31 09:32:57 +00:00
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"ids": []string{obj.GetID()},
|
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
2023-09-06 06:46:35 +00:00
|
|
|
|
hi := stream.GetHash()
|
|
|
|
|
sha1Str := hi.GetHash(hash_extend.GCID)
|
|
|
|
|
if len(sha1Str) < hash_extend.GCID.Width {
|
|
|
|
|
tFile, err := stream.CacheFullInTempFile()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sha1Str, err = utils.HashFile(hash_extend.GCID, tFile, stream.GetSize())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
2023-09-06 06:46:35 +00:00
|
|
|
|
|
2023-07-11 14:19:21 +00:00
|
|
|
|
var resp UploadTaskData
|
2024-11-17 12:03:04 +00:00
|
|
|
|
res, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodPost, func(req *resty.Request) {
|
2023-07-11 14:19:21 +00:00
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"kind": "drive#file",
|
|
|
|
|
"name": stream.GetName(),
|
|
|
|
|
"size": stream.GetSize(),
|
|
|
|
|
"hash": strings.ToUpper(sha1Str),
|
|
|
|
|
"upload_type": "UPLOAD_TYPE_RESUMABLE",
|
|
|
|
|
"objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
|
|
|
|
|
"parent_id": dstDir.GetID(),
|
|
|
|
|
"folder_type": "NORMAL",
|
|
|
|
|
})
|
|
|
|
|
}, &resp)
|
2022-08-31 09:32:57 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-07-11 14:19:21 +00:00
|
|
|
|
|
|
|
|
|
// 秒传成功
|
|
|
|
|
if resp.Resumable == nil {
|
2022-09-10 05:25:52 +00:00
|
|
|
|
log.Debugln(string(res))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2023-07-11 14:19:21 +00:00
|
|
|
|
|
|
|
|
|
params := resp.Resumable.Params
|
2024-08-21 16:35:52 +00:00
|
|
|
|
//endpoint := strings.Join(strings.Split(params.Endpoint, ".")[1:], ".")
|
2024-11-17 12:03:04 +00:00
|
|
|
|
// web 端上传 返回的endpoint 为 `mypikpak.net` | android 端上传 返回的endpoint 为 `vip-lixian-07.mypikpak.net`·
|
2024-08-21 16:35:52 +00:00
|
|
|
|
if d.Addition.Platform == "android" {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
params.Endpoint = "mypikpak.net"
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
2024-08-21 16:35:52 +00:00
|
|
|
|
|
|
|
|
|
if stream.GetSize() <= 10*utils.MB { // 文件大小 小于10MB,改用普通模式上传
|
2025-02-01 09:29:55 +00:00
|
|
|
|
return d.UploadByOSS(ctx, ¶ms, stream, up)
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
2024-08-21 16:35:52 +00:00
|
|
|
|
// 分片上传
|
2025-02-01 09:29:55 +00:00
|
|
|
|
return d.UploadByMultipart(ctx, ¶ms, stream.GetSize(), stream, up)
|
2022-08-31 09:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-07-07 08:50:05 +00:00
|
|
|
|
// 离线下载文件
|
|
|
|
|
func (d *PikPak) OfflineDownload(ctx context.Context, fileUrl string, parentDir model.Obj, fileName string) (*OfflineTask, error) {
|
|
|
|
|
requestBody := base.Json{
|
|
|
|
|
"kind": "drive#file",
|
|
|
|
|
"name": fileName,
|
|
|
|
|
"upload_type": "UPLOAD_TYPE_URL",
|
|
|
|
|
"url": base.Json{
|
|
|
|
|
"url": fileUrl,
|
|
|
|
|
},
|
|
|
|
|
"parent_id": parentDir.GetID(),
|
|
|
|
|
"folder_type": "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resp OfflineDownloadResp
|
2024-11-17 12:03:04 +00:00
|
|
|
|
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodPost, func(req *resty.Request) {
|
2024-07-07 08:50:05 +00:00
|
|
|
|
req.SetBody(requestBody)
|
|
|
|
|
}, &resp)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &resp.Task, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
获取离线下载任务列表
|
|
|
|
|
phase 可能的取值:
|
|
|
|
|
PHASE_TYPE_RUNNING, PHASE_TYPE_ERROR, PHASE_TYPE_COMPLETE, PHASE_TYPE_PENDING
|
|
|
|
|
*/
|
|
|
|
|
func (d *PikPak) OfflineList(ctx context.Context, nextPageToken string, phase []string) ([]OfflineTask, error) {
|
|
|
|
|
res := make([]OfflineTask, 0)
|
2024-11-17 12:03:04 +00:00
|
|
|
|
url := "https://api-drive.mypikpak.net/drive/v1/tasks"
|
2024-07-07 08:50:05 +00:00
|
|
|
|
|
|
|
|
|
if len(phase) == 0 {
|
|
|
|
|
phase = []string{"PHASE_TYPE_RUNNING", "PHASE_TYPE_ERROR", "PHASE_TYPE_COMPLETE", "PHASE_TYPE_PENDING"}
|
|
|
|
|
}
|
|
|
|
|
params := map[string]string{
|
|
|
|
|
"type": "offline",
|
|
|
|
|
"thumbnail_size": "SIZE_SMALL",
|
|
|
|
|
"limit": "10000",
|
|
|
|
|
"page_token": nextPageToken,
|
|
|
|
|
"with": "reference_resource",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理 phase 参数
|
|
|
|
|
if len(phase) > 0 {
|
|
|
|
|
filters := base.Json{
|
|
|
|
|
"phase": map[string]string{
|
|
|
|
|
"in": strings.Join(phase, ","),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
filtersJSON, err := json.Marshal(filters)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to marshal filters: %w", err)
|
|
|
|
|
}
|
|
|
|
|
params["filters"] = string(filtersJSON)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resp OfflineListResp
|
|
|
|
|
_, err := d.request(url, http.MethodGet, func(req *resty.Request) {
|
|
|
|
|
req.SetContext(ctx).
|
|
|
|
|
SetQueryParams(params)
|
|
|
|
|
}, &resp)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to get offline list: %w", err)
|
|
|
|
|
}
|
|
|
|
|
res = append(res, resp.Tasks...)
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *PikPak) DeleteOfflineTasks(ctx context.Context, taskIDs []string, deleteFiles bool) error {
|
2024-11-17 12:03:04 +00:00
|
|
|
|
url := "https://api-drive.mypikpak.net/drive/v1/tasks"
|
2024-07-07 08:50:05 +00:00
|
|
|
|
params := map[string]string{
|
|
|
|
|
"task_ids": strings.Join(taskIDs, ","),
|
|
|
|
|
"delete_files": strconv.FormatBool(deleteFiles),
|
|
|
|
|
}
|
|
|
|
|
_, err := d.request(url, http.MethodDelete, func(req *resty.Request) {
|
|
|
|
|
req.SetContext(ctx).
|
|
|
|
|
SetQueryParams(params)
|
|
|
|
|
}, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to delete tasks %v: %w", taskIDs, err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-31 09:32:57 +00:00
|
|
|
|
var _ driver.Driver = (*PikPak)(nil)
|