Compare commits

...

18 Commits

Author SHA1 Message Date
94915b2148 fix(baidu_netdisk): update fileToObj to use ServerCtime and ServerMtime (#7535) 2024-11-21 22:41:23 +08:00
2dec756f23 fix(pikpak&pikpak_share): captcha_sign error (#7530 close #7481 close #7482) 2024-11-21 22:40:39 +08:00
4c0cffd29b fix(net): close of closed channel (#7529) 2024-11-21 22:39:14 +08:00
25c5e075a9 fix(local): Preserve file owner when copying (#7528) 2024-11-21 22:38:41 +08:00
Mmx
398c04386a feat(sso): generate and verify OAuth state with go-cache (#7527) 2024-11-21 22:38:04 +08:00
Mmx
12b429584e feat(security): generating random string with crypto rand (#7525) 2024-11-21 22:37:19 +08:00
Mmx
150dcc2147 fix(sso): OIDC compatibility mode (#7524) 2024-11-21 22:36:41 +08:00
0ba754fd40 fix(release): missing installation of zig 2024-11-17 23:11:03 +08:00
28d2367a87 fix(ci): no space left on device 2024-11-17 22:24:06 +08:00
a4ad98ee3e fix(pikpak): domain block and change to NET (#7350) 2024-11-17 20:03:04 +08:00
1c01dc6839 fix(storage): delete storage fails if a panic occurred during initialization (#7501)
* fix(storage): store storages map when init storage panic

* fix(drivers): add nil check to drop method
2024-11-16 13:20:49 +08:00
c3c5843dce fix(terabox): panic due to slice out of range (#7499 close #7487) 2024-11-16 13:19:59 +08:00
6c38c5972d fix(terabox): big file upload issue (#7498 close #7490) 2024-11-16 13:18:49 +08:00
0a46979c51 feat(115): enhance cache (#7479) 2024-11-08 22:08:50 +08:00
67c93eed2b feat(baidu_netdisk,baidu_photo): add and fix hashinfo (#7469) 2024-11-08 22:08:25 +08:00
f58de9923a refactor(aliyunopen,config): Modify default properties (#7476) 2024-11-08 22:07:35 +08:00
2671c876f1 revert: "fix(115): enforce 20GB file size limit on uploadev"
This reverts commit 216e3909f3.
2024-11-02 21:08:19 +08:00
e707fa38f1 ci: remove specific tag for freebsd action 2024-11-02 17:05:00 +08:00
27 changed files with 530 additions and 504 deletions

View File

@ -13,6 +13,23 @@ jobs:
name: Release
runs-on: ${{ matrix.platform }}
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Prerelease
uses: irongut/EditRelease@v1.2.0
with:

View File

@ -31,5 +31,4 @@ jobs:
- name: Upload assets
uses: softprops/action-gh-release@v2
with:
tag_name: dev
files: build/compress/*

View File

@ -2,7 +2,6 @@ package _115
import (
"context"
"fmt"
"strings"
"sync"
@ -80,28 +79,60 @@ func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
return link, nil
}
func (d *Pan115) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
func (d *Pan115) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
if err := d.WaitLimit(ctx); err != nil {
return err
return nil, err
}
if _, err := d.client.Mkdir(parentDir.GetID(), dirName); err != nil {
return err
result := driver115.MkdirResp{}
form := map[string]string{
"pid": parentDir.GetID(),
"cname": dirName,
}
return nil
req := d.client.NewRequest().
SetFormData(form).
SetResult(&result).
ForceContentType("application/json;charset=UTF-8")
resp, err := req.Post(driver115.ApiDirAdd)
err = driver115.CheckErr(err, &result, resp)
if err != nil {
return nil, err
}
f, err := d.getNewFile(result.FileID)
if err != nil {
return nil, nil
}
return f, nil
}
func (d *Pan115) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
func (d *Pan115) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
if err := d.WaitLimit(ctx); err != nil {
return err
return nil, err
}
return d.client.Move(dstDir.GetID(), srcObj.GetID())
if err := d.client.Move(dstDir.GetID(), srcObj.GetID()); err != nil {
return nil, err
}
f, err := d.getNewFile(srcObj.GetID())
if err != nil {
return nil, nil
}
return f, nil
}
func (d *Pan115) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
func (d *Pan115) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
if err := d.WaitLimit(ctx); err != nil {
return err
return nil, err
}
return d.client.Rename(srcObj.GetID(), newName)
if err := d.client.Rename(srcObj.GetID(), newName); err != nil {
return nil, err
}
f, err := d.getNewFile((srcObj.GetID()))
if err != nil {
return nil, nil
}
return f, nil
}
func (d *Pan115) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
@ -118,24 +149,21 @@ func (d *Pan115) Remove(ctx context.Context, obj model.Obj) error {
return d.client.Delete(obj.GetID())
}
func (d *Pan115) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
func (d *Pan115) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
if err := d.WaitLimit(ctx); err != nil {
return err
return nil, err
}
if stream.GetSize() > utils.GB*20 { // TODO 由于官方分片上传接口失效所以使用普通上传小于20GB的文件
return fmt.Errorf("unsupported file size: 20GB limit exceeded")
}
// 分片上传
var (
fastInfo *driver115.UploadInitResp
dirID = dstDir.GetID()
)
if ok, err := d.client.UploadAvailable(); err != nil || !ok {
return err
return nil, err
}
if stream.GetSize() > d.client.UploadMetaInfo.SizeLimit {
return driver115.ErrUploadTooLarge
return nil, driver115.ErrUploadTooLarge
}
//if digest, err = d.client.GetDigestResult(stream); err != nil {
// return err
@ -148,22 +176,22 @@ func (d *Pan115) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
}
reader, err := stream.RangeRead(http_range.Range{Start: 0, Length: hashSize})
if err != nil {
return err
return nil, err
}
preHash, err := utils.HashReader(utils.SHA1, reader)
if err != nil {
return err
return nil, err
}
preHash = strings.ToUpper(preHash)
fullHash := stream.GetHash().GetHash(utils.SHA1)
if len(fullHash) <= 0 {
tmpF, err := stream.CacheFullInTempFile()
if err != nil {
return err
return nil, err
}
fullHash, err = utils.HashFile(utils.SHA1, tmpF)
if err != nil {
return err
return nil, err
}
}
fullHash = strings.ToUpper(fullHash)
@ -172,22 +200,36 @@ func (d *Pan115) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
// note that 115 add timeout for rapid-upload,
// and "sig invalid" err is thrown even when the hash is correct after timeout.
if fastInfo, err = d.rapidUpload(stream.GetSize(), stream.GetName(), dirID, preHash, fullHash, stream); err != nil {
return err
return nil, err
}
if matched, err := fastInfo.Ok(); err != nil {
return err
return nil, err
} else if matched {
return nil
f, err := d.getNewFileByPickCode(fastInfo.PickCode)
if err != nil {
return nil, nil
}
return f, nil
}
var uploadResult *UploadResult
// 闪传失败,上传
// if stream.GetSize() <= utils.KB{ // 文件大小小于1KB改用普通模式上传
if stream.GetSize() <= utils.GB*20 { // TODO 由于官方分片上传接口失效所以使用普通上传小于20GB的文件
return d.client.UploadByOSS(&fastInfo.UploadOSSParams, stream, dirID)
if stream.GetSize() <= 10*utils.MB { // 文件大小小于10MB改用普通模式上传
if uploadResult, err = d.UploadByOSS(&fastInfo.UploadOSSParams, stream, dirID); err != nil {
return nil, err
}
} else {
// 分片上传
if uploadResult, err = d.UploadByMultipart(&fastInfo.UploadOSSParams, stream.GetSize(), stream, dirID); err != nil {
return nil, err
}
}
return driver115.ErrUnexpected
// 分片上传
// return d.UploadByMultipart(&fastInfo.UploadOSSParams, stream.GetSize(), stream, dirID)
file, err := d.getNewFile(uploadResult.Data.FileID)
if err != nil {
return nil, nil
}
return file, nil
}
func (d *Pan115) OfflineList(ctx context.Context) ([]*driver115.OfflineTask, error) {

View File

@ -10,7 +10,7 @@ type Addition struct {
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,tv,alipaymini,wechatmini,qandroid" default:"linux" help:"select the QR code device, default linux"`
PageSize int64 `json:"page_size" type:"number" default:"1000" help:"list api per page size of 115 driver"`
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate ([limit]r/1s)"`
driver.RootID
}

View File

@ -74,6 +74,34 @@ func (d *Pan115) getFiles(fileId string) ([]FileObj, error) {
return res, nil
}
func (d *Pan115) getNewFile(fileId string) (*FileObj, error) {
file, err := d.client.GetFile(fileId)
if err != nil {
return nil, err
}
return &FileObj{*file}, nil
}
func (d *Pan115) getNewFileByPickCode(pickCode string) (*FileObj, error) {
result := driver115.GetFileInfoResponse{}
req := d.client.NewRequest().
SetQueryParam("pick_code", pickCode).
ForceContentType("application/json;charset=UTF-8").
SetResult(&result)
resp, err := req.Get(driver115.ApiFileInfo)
if err := driver115.CheckErr(err, &result, resp); err != nil {
return nil, err
}
if len(result.Files) == 0 {
return nil, errors.New("not get file info")
}
fileInfo := result.Files[0]
f := &FileObj{}
f.From(fileInfo)
return f, nil
}
func (d *Pan115) getUA() string {
return fmt.Sprintf("Mozilla/5.0 115Browser/%s", appVer)
}
@ -244,8 +272,38 @@ func UploadDigestRange(stream model.FileStreamer, rangeSpec string) (result stri
return
}
// UploadByOSS use aliyun sdk to upload
func (c *Pan115) UploadByOSS(params *driver115.UploadOSSParams, r io.Reader, dirID string) (*UploadResult, error) {
ossToken, err := c.client.GetOSSToken()
if err != nil {
return nil, err
}
ossClient, err := oss.New(driver115.OSSEndpoint, ossToken.AccessKeyID, ossToken.AccessKeySecret)
if err != nil {
return nil, err
}
bucket, err := ossClient.Bucket(params.Bucket)
if err != nil {
return nil, err
}
var bodyBytes []byte
if err = bucket.PutObject(params.Object, r, append(
driver115.OssOption(params, ossToken),
oss.CallbackResult(&bodyBytes),
)...); err != nil {
return nil, err
}
var uploadResult UploadResult
if err = json.Unmarshal(bodyBytes, &uploadResult); err != nil {
return nil, err
}
return &uploadResult, uploadResult.Err(string(bodyBytes))
}
// UploadByMultipart upload by mutipart blocks
func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize int64, stream model.FileStreamer, dirID string, opts ...driver115.UploadMultipartOption) error {
func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize int64, stream model.FileStreamer, dirID string, opts ...driver115.UploadMultipartOption) (*UploadResult, error) {
var (
chunks []oss.FileChunk
parts []oss.UploadPart
@ -259,7 +317,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
tmpF, err := stream.CacheFullInTempFile()
if err != nil {
return err
return nil, err
}
options := driver115.DefalutUploadMultipartOptions()
@ -272,15 +330,15 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
options.ThreadsNum = 1
if ossToken, err = d.client.GetOSSToken(); err != nil {
return err
return nil, err
}
if ossClient, err = oss.New(driver115.OSSEndpoint, ossToken.AccessKeyID, ossToken.AccessKeySecret, oss.EnableMD5(true), oss.EnableCRC(true)); err != nil {
return err
return nil, err
}
if bucket, err = ossClient.Bucket(params.Bucket); err != nil {
return err
return nil, err
}
// ossToken一小时后就会失效所以每50分钟重新获取一次
@ -290,7 +348,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
timeout := time.NewTimer(options.Timeout)
if chunks, err = SplitFile(fileSize); err != nil {
return err
return nil, err
}
if imur, err = bucket.InitiateMultipartUpload(params.Object,
@ -298,7 +356,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
oss.UserAgentHeader(driver115.OSSUserAgent),
oss.EnableSha1(), oss.Sequential(),
); err != nil {
return err
return nil, err
}
wg := sync.WaitGroup{}
@ -364,14 +422,14 @@ LOOP:
case <-ticker.C:
// 到时重新获取ossToken
if ossToken, err = d.client.GetOSSToken(); err != nil {
return err
return nil, err
}
case <-quit:
break LOOP
case <-errCh:
return err
return nil, err
case <-timeout.C:
return fmt.Errorf("time out")
return nil, fmt.Errorf("time out")
}
}
@ -381,14 +439,14 @@ LOOP:
driver115.OssOption(params, ossToken),
oss.CallbackResult(&bodyBytes),
)...); err != nil {
return err
return nil, err
}
var uploadResult UploadResult
if err = json.Unmarshal(bodyBytes, &uploadResult); err != nil {
return err
return nil, err
}
return uploadResult.Err(string(bodyBytes))
return &uploadResult, uploadResult.Err(string(bodyBytes))
}
func chunksProducer(ch chan oss.FileChunk, chunks []oss.FileChunk) {

View File

@ -6,7 +6,7 @@ import (
)
type Addition struct {
DriveType string `json:"drive_type" type:"select" options:"default,resource,backup" default:"default"`
DriveType string `json:"drive_type" type:"select" options:"default,resource,backup" default:"resource"`
driver.RootID
RefreshToken string `json:"refresh_token" required:"true"`
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
)
type TokenErrResp struct {
@ -55,11 +56,11 @@ func fileToObj(f File) *model.ObjThumb {
if f.ServerFilename == "" {
f.ServerFilename = path.Base(f.Path)
}
if f.LocalCtime == 0 {
f.LocalCtime = f.Ctime
if f.ServerCtime == 0 {
f.ServerCtime = f.Ctime
}
if f.LocalMtime == 0 {
f.LocalMtime = f.Mtime
if f.ServerMtime == 0 {
f.ServerMtime = f.Mtime
}
return &model.ObjThumb{
Object: model.Object{
@ -67,12 +68,12 @@ func fileToObj(f File) *model.ObjThumb {
Path: f.Path,
Name: f.ServerFilename,
Size: f.Size,
Modified: time.Unix(f.LocalMtime, 0),
Ctime: time.Unix(f.LocalCtime, 0),
Modified: time.Unix(f.ServerMtime, 0),
Ctime: time.Unix(f.ServerCtime, 0),
IsFolder: f.Isdir == 1,
// 直接获取的MD5是错误的
// HashInfo: utils.NewHashInfo(utils.MD5, f.Md5),
HashInfo: utils.NewHashInfo(utils.MD5, DecryptMd5(f.Md5)),
},
Thumbnail: model.Thumbnail{Thumbnail: f.Thumbs.Url3},
}

View File

@ -1,11 +1,14 @@
package baidu_netdisk
import (
"encoding/hex"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"unicode"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/errs"
@ -153,8 +156,6 @@ func (d *BaiduNetdisk) linkOfficial(file model.Obj, args model.LinkArgs) (*model
u = res.Header().Get("location")
//}
updateObjMd5(file, "pan.baidu.com", u)
return &model.Link{
URL: u,
Header: http.Header{
@ -178,8 +179,6 @@ func (d *BaiduNetdisk) linkCrack(file model.Obj, args model.LinkArgs) (*model.Li
return nil, err
}
updateObjMd5(file, d.CustomCrackUA, resp.Info[0].Dlink)
return &model.Link{
URL: resp.Info[0].Dlink,
Header: http.Header{
@ -229,19 +228,6 @@ func joinTime(form map[string]string, ctime, mtime int64) {
form["local_ctime"] = strconv.FormatInt(ctime, 10)
}
func updateObjMd5(obj model.Obj, userAgent, u string) {
object := model.GetRawObject(obj)
if object != nil {
req, _ := http.NewRequest(http.MethodHead, u, nil)
req.Header.Add("User-Agent", userAgent)
resp, _ := base.HttpClient.Do(req)
if resp != nil {
contentMd5 := resp.Header.Get("Content-Md5")
object.HashInfo = utils.NewHashInfo(utils.MD5, contentMd5)
}
}
}
const (
DefaultSliceSize int64 = 4 * utils.MB
VipSliceSize = 16 * utils.MB
@ -267,3 +253,40 @@ func (d *BaiduNetdisk) getSliceSize() int64 {
// r = strings.ReplaceAll(r, "+", "%20")
// return r
// }
func DecryptMd5(encryptMd5 string) string {
if _, err := hex.DecodeString(encryptMd5); err == nil {
return encryptMd5
}
var out strings.Builder
out.Grow(len(encryptMd5))
for i, n := 0, int64(0); i < len(encryptMd5); i++ {
if i == 9 {
n = int64(unicode.ToLower(rune(encryptMd5[i])) - 'g')
} else {
n, _ = strconv.ParseInt(encryptMd5[i:i+1], 16, 64)
}
out.WriteString(strconv.FormatInt(n^int64(15&i), 16))
}
encryptMd5 = out.String()
return encryptMd5[8:16] + encryptMd5[:8] + encryptMd5[24:32] + encryptMd5[16:24]
}
func EncryptMd5(originalMd5 string) string {
reversed := originalMd5[8:16] + originalMd5[:8] + originalMd5[24:32] + originalMd5[16:24]
var out strings.Builder
out.Grow(len(reversed))
for i, n := 0, int64(0); i < len(reversed); i++ {
n, _ = strconv.ParseInt(reversed[i:i+1], 16, 64)
n ^= int64(15 & i)
if i == 9 {
out.WriteRune(rune(n) + 'g')
} else {
out.WriteString(strconv.FormatInt(n, 16))
}
}
return out.String()
}

View File

@ -72,7 +72,7 @@ func (c *File) Thumb() string {
}
func (c *File) GetHash() utils.HashInfo {
return utils.NewHashInfo(utils.MD5, c.Md5)
return utils.NewHashInfo(utils.MD5, DecryptMd5(c.Md5))
}
/*相册部分*/

View File

@ -2,8 +2,12 @@ package baiduphoto
import (
"context"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"strings"
"unicode"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/errs"
@ -476,3 +480,40 @@ func (d *BaiduPhoto) uInfo() (*UInfo, error) {
}
return &info, nil
}
func DecryptMd5(encryptMd5 string) string {
if _, err := hex.DecodeString(encryptMd5); err == nil {
return encryptMd5
}
var out strings.Builder
out.Grow(len(encryptMd5))
for i, n := 0, int64(0); i < len(encryptMd5); i++ {
if i == 9 {
n = int64(unicode.ToLower(rune(encryptMd5[i])) - 'g')
} else {
n, _ = strconv.ParseInt(encryptMd5[i:i+1], 16, 64)
}
out.WriteString(strconv.FormatInt(n^int64(15&i), 16))
}
encryptMd5 = out.String()
return encryptMd5[8:16] + encryptMd5[:8] + encryptMd5[24:32] + encryptMd5[16:24]
}
func EncryptMd5(originalMd5 string) string {
reversed := originalMd5[8:16] + originalMd5[:8] + originalMd5[24:32] + originalMd5[16:24]
var out strings.Builder
out.Grow(len(reversed))
for i, n := 0, int64(0); i < len(reversed); i++ {
n, _ = strconv.ParseInt(reversed[i:i+1], 16, 64)
n ^= int64(15 & i)
if i == 9 {
out.WriteRune(rune(n) + 'g')
} else {
out.WriteString(strconv.FormatInt(n, 16))
}
}
return out.String()
}

View File

@ -67,7 +67,9 @@ func (d *ChaoXing) Init(ctx context.Context) error {
}
func (d *ChaoXing) Drop(ctx context.Context) error {
d.cron.Stop()
if d.cron != nil {
d.cron.Stop()
}
return nil
}

View File

@ -280,7 +280,7 @@ func (d *Local) Copy(_ context.Context, srcObj, dstDir model.Obj) error {
return cp.Copy(srcPath, dstPath, cp.Options{
Sync: true, // Sync file to disk after copy, may have performance penalty in filesystem such as ZFS
PreserveTimes: true,
NumOfWorkers: 0, // Serialized copy without using goroutine
PreserveOwner: true,
})
}

View File

@ -14,7 +14,6 @@ import (
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"net/http"
"regexp"
"strconv"
"strings"
)
@ -49,7 +48,6 @@ func (d *PikPak) Init(ctx context.Context) (err error) {
d.Common.CaptchaToken = token
op.MustSaveDriverStorage(d)
},
LowLatencyAddr: "",
}
}
@ -91,8 +89,8 @@ func (d *PikPak) Init(ctx context.Context) (err error) {
ClientID: d.ClientID,
ClientSecret: d.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: "https://user.mypikpak.com/v1/auth/signin",
TokenURL: "https://user.mypikpak.com/v1/auth/token",
AuthURL: "https://user.mypikpak.net/v1/auth/signin",
TokenURL: "https://user.mypikpak.net/v1/auth/token",
AuthStyle: oauth2.AuthStyleInParams,
},
}
@ -124,7 +122,7 @@ func (d *PikPak) Init(ctx context.Context) (err error) {
}
// 获取CaptchaToken
err = d.RefreshCaptchaTokenAtLogin(GetAction(http.MethodGet, "https://api-drive.mypikpak.com/drive/v1/files"), d.Common.GetUserID())
err = d.RefreshCaptchaTokenAtLogin(GetAction(http.MethodGet, "https://api-drive.mypikpak.net/drive/v1/files"), d.Common.GetUserID())
if err != nil {
return err
}
@ -138,14 +136,6 @@ func (d *PikPak) Init(ctx context.Context) (err error) {
d.Addition.RefreshToken = d.RefreshToken
op.MustSaveDriverStorage(d)
if d.UseLowLatencyAddress && d.Addition.CustomLowLatencyAddress != "" {
d.Common.LowLatencyAddr = d.Addition.CustomLowLatencyAddress
} else if d.UseLowLatencyAddress {
d.Common.LowLatencyAddr = findLowestLatencyAddress(DlAddr)
d.Addition.CustomLowLatencyAddress = d.Common.LowLatencyAddr
op.MustSaveDriverStorage(d)
}
return nil
}
@ -174,7 +164,7 @@ func (d *PikPak) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
if !d.DisableMediaLink {
queryParams["usage"] = "CACHE"
}
_, err := d.request(fmt.Sprintf("https://api-drive.mypikpak.com/drive/v1/files/%s", file.GetID()),
_, err := d.request(fmt.Sprintf("https://api-drive.mypikpak.net/drive/v1/files/%s", file.GetID()),
http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(queryParams)
}, &resp)
@ -188,19 +178,13 @@ func (d *PikPak) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
url = resp.Medias[0].Link.Url
}
if d.UseLowLatencyAddress && d.Common.LowLatencyAddr != "" {
// 替换为加速链接
re := regexp.MustCompile(`https://[^/]+/download/`)
url = re.ReplaceAllString(url, "https://"+d.Common.LowLatencyAddr+"/download/")
}
return &model.Link{
URL: url,
}, nil
}
func (d *PikPak) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"kind": "drive#folder",
"parent_id": parentDir.GetID(),
@ -211,7 +195,7 @@ func (d *PikPak) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
}
func (d *PikPak) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files:batchMove", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files:batchMove", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"ids": []string{srcObj.GetID()},
"to": base.Json{
@ -223,7 +207,7 @@ func (d *PikPak) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
}
func (d *PikPak) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files/"+srcObj.GetID(), http.MethodPatch, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files/"+srcObj.GetID(), http.MethodPatch, func(req *resty.Request) {
req.SetBody(base.Json{
"name": newName,
})
@ -232,7 +216,7 @@ func (d *PikPak) Rename(ctx context.Context, srcObj model.Obj, newName string) e
}
func (d *PikPak) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files:batchCopy", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files:batchCopy", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"ids": []string{srcObj.GetID()},
"to": base.Json{
@ -244,7 +228,7 @@ func (d *PikPak) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
}
func (d *PikPak) Remove(ctx context.Context, obj model.Obj) error {
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files:batchTrash", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files:batchTrash", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"ids": []string{obj.GetID()},
})
@ -268,7 +252,7 @@ func (d *PikPak) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
}
var resp UploadTaskData
res, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodPost, func(req *resty.Request) {
res, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"kind": "drive#file",
"name": stream.GetName(),
@ -292,9 +276,9 @@ func (d *PikPak) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
params := resp.Resumable.Params
//endpoint := strings.Join(strings.Split(params.Endpoint, ".")[1:], ".")
// web 端上传 返回的endpoint 为 `mypikpak.com` | android 端上传 返回的endpoint 为 `vip-lixian-07.mypikpak.com
// web 端上传 返回的endpoint 为 `mypikpak.net` | android 端上传 返回的endpoint 为 `vip-lixian-07.mypikpak.net
if d.Addition.Platform == "android" {
params.Endpoint = "mypikpak.com"
params.Endpoint = "mypikpak.net"
}
if stream.GetSize() <= 10*utils.MB { // 文件大小 小于10MB改用普通模式上传
@ -318,7 +302,7 @@ func (d *PikPak) OfflineDownload(ctx context.Context, fileUrl string, parentDir
}
var resp OfflineDownloadResp
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodPost, func(req *resty.Request) {
req.SetBody(requestBody)
}, &resp)
@ -336,7 +320,7 @@ 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)
url := "https://api-drive.mypikpak.com/drive/v1/tasks"
url := "https://api-drive.mypikpak.net/drive/v1/tasks"
if len(phase) == 0 {
phase = []string{"PHASE_TYPE_RUNNING", "PHASE_TYPE_ERROR", "PHASE_TYPE_COMPLETE", "PHASE_TYPE_PENDING"}
@ -377,7 +361,7 @@ func (d *PikPak) OfflineList(ctx context.Context, nextPageToken string, phase []
}
func (d *PikPak) DeleteOfflineTasks(ctx context.Context, taskIDs []string, deleteFiles bool) error {
url := "https://api-drive.mypikpak.com/drive/v1/tasks"
url := "https://api-drive.mypikpak.net/drive/v1/tasks"
params := map[string]string{
"task_ids": strings.Join(taskIDs, ","),
"delete_files": strconv.FormatBool(deleteFiles),

View File

@ -7,16 +7,14 @@ import (
type Addition struct {
driver.RootID
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
Platform string `json:"platform" required:"true" type:"select" options:"android,web,pc"`
RefreshToken string `json:"refresh_token" required:"true" default:""`
RefreshTokenMethod string `json:"refresh_token_method" required:"true" type:"select" options:"oauth2,http"`
CaptchaToken string `json:"captcha_token" default:""`
DeviceID string `json:"device_id" required:"false" default:""`
DisableMediaLink bool `json:"disable_media_link" default:"true"`
UseLowLatencyAddress bool `json:"use_low_latency_address" default:"false"`
CustomLowLatencyAddress string `json:"custom_low_latency_address" default:""`
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
Platform string `json:"platform" required:"true" default:"web" type:"select" options:"android,web,pc"`
RefreshToken string `json:"refresh_token" required:"true" default:""`
RefreshTokenMethod string `json:"refresh_token_method" required:"true" type:"select" options:"oauth2,http"`
CaptchaToken string `json:"captcha_token" default:""`
DeviceID string `json:"device_id" required:"false" default:""`
DisableMediaLink bool `json:"disable_media_link" default:"true"`
}
var config = driver.Config{

View File

@ -30,32 +30,34 @@ import (
// do others that not defined in Driver interface
var AndroidAlgorithms = []string{
"aDhgaSE3MsjROCmpmsWqP1sJdFJ",
"+oaVkqdd8MJuKT+uMr2AYKcd9tdWge3XPEPR2hcePUknd",
"u/sd2GgT2fTytRcKzGicHodhvIltMntA3xKw2SRv7S48OdnaQIS5mn",
"2WZiae2QuqTOxBKaaqCNHCW3olu2UImelkDzBn",
"/vJ3upic39lgmrkX855Qx",
"yNc9ruCVMV7pGV7XvFeuLMOcy1",
"4FPq8mT3JQ1jzcVxMVfwFftLQm33M7i",
"xozoy5e3Ea",
"7xOq4Z8s",
"QE9/9+IQco",
"WdX5J9CPLZp",
"NmQ5qFAXqH3w984cYhMeC5TJR8j",
"cc44M+l7GDhav",
"KxGjo/wHB+Yx8Lf7kMP+/m9I+",
"wla81BUVSmDkctHDpUT",
"c6wMr1sm1WxiR3i8LDAm3W",
"hRLrEQCFNYi0PFPV",
"o1J41zIraDtJPNuhBu7Ifb/q3",
"U",
"RrbZvV0CTu3gaZJ56PVKki4IeP",
"NNuRbLckJqUp1Do0YlrKCUP",
"UUwnBbipMTvInA0U0E9",
"VzGc",
}
var WebAlgorithms = []string{
"C9qPpZLN8ucRTaTiUMWYS9cQvWOE",
"+r6CQVxjzJV6LCV",
"F",
"pFJRC",
"9WXYIDGrwTCz2OiVlgZa90qpECPD6olt",
"/750aCr4lm/Sly/c",
"RB+DT/gZCrbV",
"",
"CyLsf7hdkIRxRm215hl",
"7xHvLi2tOYP0Y92b",
"ZGTXXxu8E/MIWaEDB+Sm/",
"1UI3",
"E7fP5Pfijd+7K+t6Tg/NhuLq0eEUVChpJSkrKxpO",
"ihtqpG6FMt65+Xk+tWUH2",
"NhXXU9rg4XXdzo7u5o",
"fyZ4+p77W1U4zcWBUwefAIFhFxvADWtT1wzolCxhg9q7etmGUjXr",
"uSUX02HYJ1IkyLdhINEFcCf7l2",
"iWt97bqD/qvjIaPXB2Ja5rsBWtQtBZZmaHH2rMR41",
"3binT1s/5a1pu3fGsN",
"8YCCU+AIr7pg+yd7CkQEY16lDMwi8Rh4WNp5",
"DYS3StqnAEKdGddRP8CJrxUSFh",
"crquW+4",
"ryKqvW9B9hly+JAymXCIfag5Z",
"Hr08T/NDTX1oSJfHk90c",
"i",
}
var PCAlgorithms = []string{
@ -80,13 +82,13 @@ const (
const (
AndroidClientID = "YNxT9w7GMdWvEOKa"
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
AndroidClientVersion = "1.48.3"
AndroidClientVersion = "1.49.3"
AndroidPackageName = "com.pikcloud.pikpak"
AndroidSdkVersion = "2.0.4.204101"
WebClientID = "YUMx5nI8ZU8Ap8pm"
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
WebClientVersion = "2.0.0"
WebPackageName = "mypikpak.com"
WebClientVersion = "undefined"
WebPackageName = "drive.mypikpak.com"
WebSdkVersion = "8.0.3"
PCClientID = "YvtoWO6GNHiuCl7x"
PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
@ -95,51 +97,13 @@ const (
PCSdkVersion = "8.0.3"
)
var DlAddr = []string{
"dl-a10b-0621.mypikpak.com",
"dl-a10b-0622.mypikpak.com",
"dl-a10b-0623.mypikpak.com",
"dl-a10b-0624.mypikpak.com",
"dl-a10b-0625.mypikpak.com",
"dl-a10b-0858.mypikpak.com",
"dl-a10b-0859.mypikpak.com",
"dl-a10b-0860.mypikpak.com",
"dl-a10b-0861.mypikpak.com",
"dl-a10b-0862.mypikpak.com",
"dl-a10b-0863.mypikpak.com",
"dl-a10b-0864.mypikpak.com",
"dl-a10b-0865.mypikpak.com",
"dl-a10b-0866.mypikpak.com",
"dl-a10b-0867.mypikpak.com",
"dl-a10b-0868.mypikpak.com",
"dl-a10b-0869.mypikpak.com",
"dl-a10b-0870.mypikpak.com",
"dl-a10b-0871.mypikpak.com",
"dl-a10b-0872.mypikpak.com",
"dl-a10b-0873.mypikpak.com",
"dl-a10b-0874.mypikpak.com",
"dl-a10b-0875.mypikpak.com",
"dl-a10b-0876.mypikpak.com",
"dl-a10b-0877.mypikpak.com",
"dl-a10b-0878.mypikpak.com",
"dl-a10b-0879.mypikpak.com",
"dl-a10b-0880.mypikpak.com",
"dl-a10b-0881.mypikpak.com",
"dl-a10b-0882.mypikpak.com",
"dl-a10b-0883.mypikpak.com",
"dl-a10b-0884.mypikpak.com",
"dl-a10b-0885.mypikpak.com",
"dl-a10b-0886.mypikpak.com",
"dl-a10b-0887.mypikpak.com",
}
func (d *PikPak) login() error {
// 检查用户名和密码是否为空
if d.Addition.Username == "" || d.Addition.Password == "" {
return errors.New("username or password is empty")
}
url := "https://user.mypikpak.com/v1/auth/signin"
url := "https://user.mypikpak.net/v1/auth/signin"
// 使用 用户填写的 CaptchaToken —————— (验证后的captcha_token)
if d.GetCaptchaToken() == "" {
if err := d.RefreshCaptchaTokenInLogin(GetAction(http.MethodPost, url), d.Username); err != nil {
@ -169,7 +133,7 @@ func (d *PikPak) login() error {
}
func (d *PikPak) refreshToken(refreshToken string) error {
url := "https://user.mypikpak.com/v1/auth/token"
url := "https://user.mypikpak.net/v1/auth/token"
var e ErrResp
res, err := base.RestyClient.SetRetryCount(1).R().SetError(&e).
SetHeader("user-agent", "").SetBody(base.Json{
@ -307,7 +271,7 @@ func (d *PikPak) getFiles(id string) ([]File, error) {
"page_token": pageToken,
}
var resp Files
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodGet, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/files", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
@ -338,7 +302,6 @@ type Common struct {
UserAgent string
// 验证码token刷新成功回调
RefreshCTokenCk func(token string)
LowLatencyAddr string
}
func generateDeviceSign(deviceID, packageName string) string {
@ -473,7 +436,7 @@ func (d *PikPak) refreshCaptchaToken(action string, metas map[string]string) err
}
var e ErrResp
var resp CaptchaTokenResponse
_, err := d.request("https://user.mypikpak.com/v1/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://user.mypikpak.net/v1/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
req.SetError(&e).SetBody(param).SetQueryParam("client_id", d.ClientID)
}, &resp)
@ -729,46 +692,3 @@ func OssOption(params *S3Params) []oss.Option {
}
return options
}
type AddressLatency struct {
Address string
Latency time.Duration
}
func checkLatency(address string, wg *sync.WaitGroup, ch chan<- AddressLatency) {
defer wg.Done()
start := time.Now()
resp, err := http.Get("https://" + address + "/generate_204")
if err != nil {
ch <- AddressLatency{Address: address, Latency: time.Hour} // Set high latency on error
return
}
defer resp.Body.Close()
latency := time.Since(start)
ch <- AddressLatency{Address: address, Latency: latency}
}
func findLowestLatencyAddress(addresses []string) string {
var wg sync.WaitGroup
ch := make(chan AddressLatency, len(addresses))
for _, address := range addresses {
wg.Add(1)
go checkLatency(address, &wg, ch)
}
wg.Wait()
close(ch)
var lowestLatencyAddress string
lowestLatency := time.Hour
for result := range ch {
if result.Latency < lowestLatency {
lowestLatency = result.Latency
lowestLatencyAddress = result.Address
}
}
return lowestLatencyAddress
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/alist-org/alist/v3/internal/op"
"net/http"
"regexp"
"time"
"github.com/alist-org/alist/v3/internal/driver"
@ -37,7 +36,6 @@ func (d *PikPakShare) Init(ctx context.Context) error {
d.Common.CaptchaToken = token
op.MustSaveDriverStorage(d)
},
LowLatencyAddr: "",
}
}
@ -71,16 +69,8 @@ func (d *PikPakShare) Init(ctx context.Context) error {
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"
}
if d.UseLowLatencyAddress && d.Addition.CustomLowLatencyAddress != "" {
d.Common.LowLatencyAddr = d.Addition.CustomLowLatencyAddress
} else if d.UseLowLatencyAddress {
d.Common.LowLatencyAddr = findLowestLatencyAddress(DlAddr)
d.Addition.CustomLowLatencyAddress = d.Common.LowLatencyAddr
op.MustSaveDriverStorage(d)
}
// 获取CaptchaToken
err := d.RefreshCaptchaToken(GetAction(http.MethodGet, "https://api-drive.mypikpak.com/drive/v1/share:batch_file_info"), "")
err := d.RefreshCaptchaToken(GetAction(http.MethodGet, "https://api-drive.mypikpak.net/drive/v1/share:batch_file_info"), "")
if err != nil {
return err
}
@ -113,7 +103,7 @@ func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkA
"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) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/share/file_info", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
@ -131,12 +121,6 @@ func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkA
}
if d.UseLowLatencyAddress && d.Common.LowLatencyAddr != "" {
// 替换为加速链接
re := regexp.MustCompile(`https://[^/]+/download/`)
downloadUrl = re.ReplaceAllString(downloadUrl, "https://"+d.Common.LowLatencyAddr+"/download/")
}
return &model.Link{
URL: downloadUrl,
}, nil

View File

@ -7,13 +7,11 @@ import (
type Addition struct {
driver.RootID
ShareId string `json:"share_id" required:"true"`
SharePwd string `json:"share_pwd"`
Platform string `json:"platform" required:"true" type:"select" options:"android,web,pc"`
DeviceID string `json:"device_id" required:"false" default:""`
UseTransCodingAddress bool `json:"use_transcoding_address" required:"true" default:"false"`
UseLowLatencyAddress bool `json:"use_low_latency_address" default:"false"`
CustomLowLatencyAddress string `json:"custom_low_latency_address" default:""`
ShareId string `json:"share_id" required:"true"`
SharePwd string `json:"share_pwd"`
Platform string `json:"platform" default:"web" required:"true" type:"select" options:"android,web,pc"`
DeviceID string `json:"device_id" required:"false" default:""`
UseTransCodingAddress bool `json:"use_transcoding_address" required:"true" default:"false"`
}
var config = driver.Config{

View File

@ -10,7 +10,6 @@ import (
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/alist-org/alist/v3/drivers/base"
@ -18,32 +17,34 @@ import (
)
var AndroidAlgorithms = []string{
"aDhgaSE3MsjROCmpmsWqP1sJdFJ",
"+oaVkqdd8MJuKT+uMr2AYKcd9tdWge3XPEPR2hcePUknd",
"u/sd2GgT2fTytRcKzGicHodhvIltMntA3xKw2SRv7S48OdnaQIS5mn",
"2WZiae2QuqTOxBKaaqCNHCW3olu2UImelkDzBn",
"/vJ3upic39lgmrkX855Qx",
"yNc9ruCVMV7pGV7XvFeuLMOcy1",
"4FPq8mT3JQ1jzcVxMVfwFftLQm33M7i",
"xozoy5e3Ea",
"7xOq4Z8s",
"QE9/9+IQco",
"WdX5J9CPLZp",
"NmQ5qFAXqH3w984cYhMeC5TJR8j",
"cc44M+l7GDhav",
"KxGjo/wHB+Yx8Lf7kMP+/m9I+",
"wla81BUVSmDkctHDpUT",
"c6wMr1sm1WxiR3i8LDAm3W",
"hRLrEQCFNYi0PFPV",
"o1J41zIraDtJPNuhBu7Ifb/q3",
"U",
"RrbZvV0CTu3gaZJ56PVKki4IeP",
"NNuRbLckJqUp1Do0YlrKCUP",
"UUwnBbipMTvInA0U0E9",
"VzGc",
}
var WebAlgorithms = []string{
"C9qPpZLN8ucRTaTiUMWYS9cQvWOE",
"+r6CQVxjzJV6LCV",
"F",
"pFJRC",
"9WXYIDGrwTCz2OiVlgZa90qpECPD6olt",
"/750aCr4lm/Sly/c",
"RB+DT/gZCrbV",
"",
"CyLsf7hdkIRxRm215hl",
"7xHvLi2tOYP0Y92b",
"ZGTXXxu8E/MIWaEDB+Sm/",
"1UI3",
"E7fP5Pfijd+7K+t6Tg/NhuLq0eEUVChpJSkrKxpO",
"ihtqpG6FMt65+Xk+tWUH2",
"NhXXU9rg4XXdzo7u5o",
"fyZ4+p77W1U4zcWBUwefAIFhFxvADWtT1wzolCxhg9q7etmGUjXr",
"uSUX02HYJ1IkyLdhINEFcCf7l2",
"iWt97bqD/qvjIaPXB2Ja5rsBWtQtBZZmaHH2rMR41",
"3binT1s/5a1pu3fGsN",
"8YCCU+AIr7pg+yd7CkQEY16lDMwi8Rh4WNp5",
"DYS3StqnAEKdGddRP8CJrxUSFh",
"crquW+4",
"ryKqvW9B9hly+JAymXCIfag5Z",
"Hr08T/NDTX1oSJfHk90c",
"i",
}
var PCAlgorithms = []string{
@ -62,13 +63,13 @@ var PCAlgorithms = []string{
const (
AndroidClientID = "YNxT9w7GMdWvEOKa"
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
AndroidClientVersion = "1.48.3"
AndroidClientVersion = "1.49.3"
AndroidPackageName = "com.pikcloud.pikpak"
AndroidSdkVersion = "2.0.4.204101"
WebClientID = "YUMx5nI8ZU8Ap8pm"
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
WebClientVersion = "2.0.0"
WebPackageName = "mypikpak.com"
WebClientVersion = "undefined"
WebPackageName = "drive.mypikpak.com"
WebSdkVersion = "8.0.3"
PCClientID = "YvtoWO6GNHiuCl7x"
PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
@ -77,44 +78,6 @@ const (
PCSdkVersion = "8.0.3"
)
var DlAddr = []string{
"dl-a10b-0621.mypikpak.com",
"dl-a10b-0622.mypikpak.com",
"dl-a10b-0623.mypikpak.com",
"dl-a10b-0624.mypikpak.com",
"dl-a10b-0625.mypikpak.com",
"dl-a10b-0858.mypikpak.com",
"dl-a10b-0859.mypikpak.com",
"dl-a10b-0860.mypikpak.com",
"dl-a10b-0861.mypikpak.com",
"dl-a10b-0862.mypikpak.com",
"dl-a10b-0863.mypikpak.com",
"dl-a10b-0864.mypikpak.com",
"dl-a10b-0865.mypikpak.com",
"dl-a10b-0866.mypikpak.com",
"dl-a10b-0867.mypikpak.com",
"dl-a10b-0868.mypikpak.com",
"dl-a10b-0869.mypikpak.com",
"dl-a10b-0870.mypikpak.com",
"dl-a10b-0871.mypikpak.com",
"dl-a10b-0872.mypikpak.com",
"dl-a10b-0873.mypikpak.com",
"dl-a10b-0874.mypikpak.com",
"dl-a10b-0875.mypikpak.com",
"dl-a10b-0876.mypikpak.com",
"dl-a10b-0877.mypikpak.com",
"dl-a10b-0878.mypikpak.com",
"dl-a10b-0879.mypikpak.com",
"dl-a10b-0880.mypikpak.com",
"dl-a10b-0881.mypikpak.com",
"dl-a10b-0882.mypikpak.com",
"dl-a10b-0883.mypikpak.com",
"dl-a10b-0884.mypikpak.com",
"dl-a10b-0885.mypikpak.com",
"dl-a10b-0886.mypikpak.com",
"dl-a10b-0887.mypikpak.com",
}
func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
@ -159,7 +122,7 @@ func (d *PikPakShare) getSharePassToken() error {
"limit": "100",
}
var resp ShareResp
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/share", http.MethodGet, func(req *resty.Request) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/share", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
@ -187,7 +150,7 @@ func (d *PikPakShare) getFiles(id string) ([]File, error) {
"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) {
_, err := d.request("https://api-drive.mypikpak.net/drive/v1/share/detail", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
@ -227,7 +190,6 @@ type Common struct {
UserAgent string
// 验证码token刷新成功回调
RefreshCTokenCk func(token string)
LowLatencyAddr string
}
func (c *Common) SetUserAgent(userAgent string) {
@ -345,7 +307,7 @@ func (d *PikPakShare) refreshCaptchaToken(action string, metas map[string]string
}
var e ErrResp
var resp CaptchaTokenResponse
_, err := d.request("https://user.mypikpak.com/v1/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
_, err := d.request("https://user.mypikpak.net/v1/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
req.SetError(&e).SetBody(param)
}, &resp)
@ -367,46 +329,3 @@ func (d *PikPakShare) refreshCaptchaToken(action string, metas map[string]string
d.Common.SetCaptchaToken(resp.CaptchaToken)
return nil
}
type AddressLatency struct {
Address string
Latency time.Duration
}
func checkLatency(address string, wg *sync.WaitGroup, ch chan<- AddressLatency) {
defer wg.Done()
start := time.Now()
resp, err := http.Get("https://" + address + "/generate_204")
if err != nil {
ch <- AddressLatency{Address: address, Latency: time.Hour} // Set high latency on error
return
}
defer resp.Body.Close()
latency := time.Since(start)
ch <- AddressLatency{Address: address, Latency: latency}
}
func findLowestLatencyAddress(addresses []string) string {
var wg sync.WaitGroup
ch := make(chan AddressLatency, len(addresses))
for _, address := range addresses {
wg.Add(1)
go checkLatency(address, &wg, ch)
}
wg.Wait()
close(ch)
var lowestLatencyAddress string
lowestLatency := time.Hour
for result := range ch {
if result.Latency < lowestLatency {
lowestLatency = result.Latency
lowestLatencyAddress = result.Address
}
}
return lowestLatencyAddress
}

View File

@ -10,8 +10,6 @@ import (
"math"
stdpath "path"
"strconv"
"strings"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/pkg/utils"
@ -24,9 +22,9 @@ import (
type Terabox struct {
model.Storage
Addition
JsToken string
JsToken string
url_domain_prefix string
base_url string
base_url string
}
func (d *Terabox) Config() driver.Config {
@ -145,52 +143,24 @@ func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
}
log.Debugln(locateupload_resp)
tempFile, err := stream.CacheFullInTempFile()
if err != nil {
return err
}
var Default int64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(stream.GetSize()) / float64(Default)))
// cal md5
h1 := md5.New()
h2 := md5.New()
block_list := make([]string, 0)
left := stream.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
// precreate file
rawPath := stdpath.Join(dstDir.GetPath(), stream.GetName())
path := encodeURIComponent(rawPath)
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
var precreateBlockListStr string
if stream.GetSize() > initialChunkSize {
precreateBlockListStr = `["5910a591dd8fc18c32a8f3df4fdc1761","a5fc157d78e6ad1c7e114b056c92821e"]`
} else {
precreateBlockListStr = `["5910a591dd8fc18c32a8f3df4fdc1761"]`
}
data := map[string]string{
"path": rawPath,
"autoinit": "1",
"target_path": dstDir.GetPath(),
"block_list": block_list_str,
"local_mtime": strconv.FormatInt(time.Now().Unix(), 10),
"path": rawPath,
"autoinit": "1",
"target_path": dstDir.GetPath(),
"block_list": precreateBlockListStr,
"local_mtime": strconv.FormatInt(stream.ModTime().Unix(), 10),
"file_limit_switch_v34": "true",
}
var precreateResp PrecreateResp
log.Debugln(data)
@ -206,6 +176,13 @@ func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
if precreateResp.ReturnType == 2 {
return nil
}
// upload chunks
tempFile, err := stream.CacheFullInTempFile()
if err != nil {
return err
}
params := map[string]string{
"method": "upload",
"path": path,
@ -215,24 +192,37 @@ func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
"channel": "dubox",
"clienttype": "0",
}
left = stream.GetSize()
for i, partseq := range precreateResp.BlockList {
streamSize := stream.GetSize()
chunkSize := calculateChunkSize(streamSize)
chunkByteData := make([]byte, chunkSize)
count := int(math.Ceil(float64(streamSize) / float64(chunkSize)))
left := streamSize
uploadBlockList := make([]string, 0, count)
h := md5.New()
for partseq := 0; partseq < count; partseq++ {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
byteSize := Default
byteSize := chunkSize
var byteData []byte
if left < Default {
if left >= chunkSize {
byteData = chunkByteData
} else {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
// calculate md5
h.Write(byteData)
uploadBlockList = append(uploadBlockList, hex.EncodeToString(h.Sum(nil)))
h.Reset()
u := "https://" + locateupload_resp.Host + "/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().
@ -245,25 +235,39 @@ func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
return err
}
log.Debugln(res.String())
if len(precreateResp.BlockList) > 0 {
up(float64(i) * 100 / float64(len(precreateResp.BlockList)))
if count > 0 {
up(float64(partseq) * 100 / float64(count))
}
}
// create file
params = map[string]string{
"isdir": "0",
"rtype": "1",
}
uploadBlockListStr, err := utils.Json.MarshalToString(uploadBlockList)
if err != nil {
return err
}
data = map[string]string{
"path": rawPath,
"size": strconv.FormatInt(stream.GetSize(), 10),
"uploadid": precreateResp.Uploadid,
"target_path": dstDir.GetPath(),
"block_list": block_list_str,
"local_mtime": strconv.FormatInt(time.Now().Unix(), 10),
"block_list": uploadBlockListStr,
"local_mtime": strconv.FormatInt(stream.ModTime().Unix(), 10),
}
res, err = d.post_form("/api/create", params, data, nil)
var createResp CreateResp
res, err = d.post_form("/api/create", params, data, &createResp)
log.Debugln(string(res))
return err
if err != nil {
return err
}
if createResp.Errno != 0 {
return fmt.Errorf("[terabox] failed to create file, errno: %d", createResp.Errno)
}
return nil
}
var _ driver.Driver = (*Terabox)(nil)

View File

@ -99,3 +99,7 @@ type CheckLoginResp struct {
type LocateUploadResp struct {
Host string `json:"host"`
}
type CreateResp struct {
Errno int `json:"errno"`
}

View File

@ -17,6 +17,11 @@ import (
log "github.com/sirupsen/logrus"
)
const (
initialChunkSize int64 = 4 << 20 // 4MB
initialSizeThreshold int64 = 4 << 30 // 4GB
)
func getStrBetween(raw, start, end string) string {
regexPattern := fmt.Sprintf(`%s(.*?)%s`, regexp.QuoteMeta(start), regexp.QuoteMeta(end))
regex := regexp.MustCompile(regexPattern)
@ -86,11 +91,15 @@ func (d *Terabox) request(rurl string, method string, callback base.ReqCallback,
return d.request(rurl, method, callback, resp, true)
}
} else if errno == -6 {
log.Debugln(res.Header())
d.url_domain_prefix = res.Header()["Url-Domain-Prefix"][0]
d.base_url = "https://" + d.url_domain_prefix + ".terabox.com"
log.Debugln("Redirect base_url to", d.base_url)
return d.request(rurl, method, callback, resp, noRetry...)
header := res.Header()
log.Debugln(header)
urlDomainPrefix := header.Get("Url-Domain-Prefix")
if len(urlDomainPrefix) > 0 {
d.url_domain_prefix = urlDomainPrefix
d.base_url = "https://" + d.url_domain_prefix + ".terabox.com"
log.Debugln("Redirect base_url to", d.base_url)
return d.request(rurl, method, callback, resp, noRetry...)
}
}
return res.Body(), nil
}
@ -258,3 +267,19 @@ func encodeURIComponent(str string) string {
r = strings.ReplaceAll(r, "+", "%20")
return r
}
func calculateChunkSize(streamSize int64) int64 {
chunkSize := initialChunkSize
sizeThreshold := initialSizeThreshold
if streamSize < chunkSize {
return streamSize
}
for streamSize > sizeThreshold {
chunkSize <<= 1
sizeThreshold <<= 1
}
return chunkSize
}

View File

@ -55,7 +55,9 @@ func (d *Vtencent) Init(ctx context.Context) error {
}
func (d *Vtencent) Drop(ctx context.Context) error {
d.cron.Stop()
if d.cron != nil {
d.cron.Stop()
}
return nil
}

View File

@ -131,22 +131,22 @@ func DefaultConfig() *Config {
TlsInsecureSkipVerify: true,
Tasks: TasksConfig{
Download: TaskConfig{
Workers: 5,
MaxRetry: 1,
TaskPersistant: true,
Workers: 5,
MaxRetry: 1,
// TaskPersistant: true,
},
Transfer: TaskConfig{
Workers: 5,
MaxRetry: 2,
TaskPersistant: true,
Workers: 5,
MaxRetry: 2,
// TaskPersistant: true,
},
Upload: TaskConfig{
Workers: 5,
},
Copy: TaskConfig{
Workers: 5,
MaxRetry: 2,
TaskPersistant: true,
Workers: 5,
MaxRetry: 2,
// TaskPersistant: true,
},
},
Cors: Cors{

View File

@ -4,7 +4,6 @@ import (
"bytes"
"context"
"fmt"
"github.com/alist-org/alist/v3/pkg/utils"
"io"
"math"
"net/http"
@ -13,6 +12,8 @@ import (
"sync"
"time"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/http_range"
"github.com/aws/aws-sdk-go/aws/awsutil"
log "github.com/sirupsen/logrus"
@ -168,6 +169,9 @@ func (d *downloader) sendChunkTask() *chunk {
// when the final reader Close, we interrupt
func (d *downloader) interrupt() error {
if d.chunkChannel == nil {
return nil
}
d.cancel()
if d.written != d.params.Range.Length {
log.Debugf("Downloader interrupt before finish")
@ -177,6 +181,7 @@ func (d *downloader) interrupt() error {
}
defer func() {
close(d.chunkChannel)
d.chunkChannel = nil
for _, buf := range d.bufs {
buf.Close()
}

View File

@ -101,7 +101,7 @@ func initStorage(ctx context.Context, storage model.Storage, storageDriver drive
log.Errorf("panic init storage: %s", errInfo)
driverStorage.SetStatus(errInfo)
MustSaveDriverStorage(storageDriver)
storagesMap.Delete(driverStorage.MountPath)
storagesMap.Store(driverStorage.MountPath, storageDriver)
}
}()
// Unmarshal Addition

View File

@ -1,20 +1,27 @@
package random
import (
"math/rand"
"crypto/rand"
"math/big"
mathRand "math/rand"
"time"
"github.com/google/uuid"
)
var Rand *rand.Rand
var Rand *mathRand.Rand
const letterBytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
func String(n int) string {
b := make([]byte, n)
letterLen := big.NewInt(int64(len(letterBytes)))
for i := range b {
b[i] = letterBytes[Rand.Intn(len(letterBytes))]
idx, err := rand.Int(rand.Reader, letterLen)
if err != nil {
panic(err)
}
b[i] = letterBytes[idx.Int64()]
}
return string(b)
}
@ -24,10 +31,10 @@ func Token() string {
}
func RangeInt64(left, right int64) int64 {
return rand.Int63n(left+right) - left
return mathRand.Int63n(left+right) - left
}
func init() {
s := rand.NewSource(time.Now().UnixNano())
Rand = rand.New(s)
s := mathRand.NewSource(time.Now().UnixNano())
Rand = mathRand.New(s)
}

View File

@ -1,10 +1,10 @@
package handles
import (
"encoding/base32"
"encoding/base64"
"errors"
"fmt"
"github.com/Xhofe/go-cache"
"net/http"
"net/url"
"path"
@ -21,29 +21,45 @@ import (
"github.com/coreos/go-oidc"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"golang.org/x/oauth2"
"gorm.io/gorm"
)
var opts = totp.ValidateOpts{
// state verify won't expire in 30 secs, which is quite enough for the callback
Period: 30,
Skew: 1,
// in some OIDC providers(such as Authelia), state parameter must be at least 8 characters
Digits: otp.DigitsEight,
Algorithm: otp.AlgorithmSHA1,
const stateLength = 16
const stateExpire = time.Minute * 5
var stateCache = cache.NewMemCache[string](cache.WithShards[string](stateLength))
func _keyState(clientID, state string) string {
return fmt.Sprintf("%s_%s", clientID, state)
}
func generateState(clientID, ip string) string {
state := random.String(stateLength)
stateCache.Set(_keyState(clientID, state), ip, cache.WithEx[string](stateExpire))
return state
}
func verifyState(clientID, ip, state string) bool {
value, ok := stateCache.Get(_keyState(clientID, state))
return ok && value == ip
}
func ssoRedirectUri(c *gin.Context, useCompatibility bool, method string) string {
if useCompatibility {
return common.GetApiUrl(c.Request) + "/api/auth/" + method
} else {
return common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + method
}
}
func SSOLoginRedirect(c *gin.Context) {
method := c.Query("method")
usecompatibility := setting.GetBool(conf.SSOCompatibilityMode)
useCompatibility := setting.GetBool(conf.SSOCompatibilityMode)
enabled := setting.GetBool(conf.SSOLoginEnabled)
clientId := setting.GetStr(conf.SSOClientId)
platform := setting.GetStr(conf.SSOLoginPlatform)
var r_url string
var redirect_uri string
var rUrl string
if !enabled {
common.ErrorStrResp(c, "Single sign-on is not enabled", 403)
return
@ -53,69 +69,52 @@ func SSOLoginRedirect(c *gin.Context) {
common.ErrorStrResp(c, "no method provided", 400)
return
}
if usecompatibility {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/" + method
} else {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + method
}
redirectUri := ssoRedirectUri(c, useCompatibility, method)
urlValues.Add("response_type", "code")
urlValues.Add("redirect_uri", redirect_uri)
urlValues.Add("redirect_uri", redirectUri)
urlValues.Add("client_id", clientId)
switch platform {
case "Github":
r_url = "https://github.com/login/oauth/authorize?"
rUrl = "https://github.com/login/oauth/authorize?"
urlValues.Add("scope", "read:user")
case "Microsoft":
r_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?"
rUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?"
urlValues.Add("scope", "user.read")
urlValues.Add("response_mode", "query")
case "Google":
r_url = "https://accounts.google.com/o/oauth2/v2/auth?"
rUrl = "https://accounts.google.com/o/oauth2/v2/auth?"
urlValues.Add("scope", "https://www.googleapis.com/auth/userinfo.profile")
case "Dingtalk":
r_url = "https://login.dingtalk.com/oauth2/auth?"
rUrl = "https://login.dingtalk.com/oauth2/auth?"
urlValues.Add("scope", "openid")
urlValues.Add("prompt", "consent")
urlValues.Add("response_type", "code")
case "Casdoor":
endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/")
r_url = endpoint + "/login/oauth/authorize?"
rUrl = endpoint + "/login/oauth/authorize?"
urlValues.Add("scope", "profile")
urlValues.Add("state", endpoint)
case "OIDC":
oauth2Config, err := GetOIDCClient(c)
if err != nil {
common.ErrorStrResp(c, err.Error(), 400)
return
}
// generate state parameter
state, err := totp.GenerateCodeCustom(base32.StdEncoding.EncodeToString([]byte(oauth2Config.ClientSecret)), time.Now(), opts)
oauth2Config, err := GetOIDCClient(c, useCompatibility, redirectUri, method)
if err != nil {
common.ErrorStrResp(c, err.Error(), 400)
return
}
state := generateState(clientId, c.ClientIP())
c.Redirect(http.StatusFound, oauth2Config.AuthCodeURL(state))
return
default:
common.ErrorStrResp(c, "invalid platform", 400)
return
}
c.Redirect(302, r_url+urlValues.Encode())
c.Redirect(302, rUrl+urlValues.Encode())
}
var ssoClient = resty.New().SetRetryCount(3)
func GetOIDCClient(c *gin.Context) (*oauth2.Config, error) {
var redirect_uri string
usecompatibility := setting.GetBool(conf.SSOCompatibilityMode)
argument := c.Query("method")
if usecompatibility {
argument = path.Base(c.Request.URL.Path)
}
if usecompatibility {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/" + argument
} else {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + argument
func GetOIDCClient(c *gin.Context, useCompatibility bool, redirectUri, method string) (*oauth2.Config, error) {
if redirectUri == "" {
redirectUri = ssoRedirectUri(c, useCompatibility, method)
}
endpoint := setting.GetStr(conf.SSOEndpointName)
provider, err := oidc.NewProvider(c, endpoint)
@ -127,7 +126,7 @@ func GetOIDCClient(c *gin.Context) (*oauth2.Config, error) {
return &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirect_uri,
RedirectURL: redirectUri,
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
@ -181,9 +180,9 @@ func parseJWT(p string) ([]byte, error) {
func OIDCLoginCallback(c *gin.Context) {
useCompatibility := setting.GetBool(conf.SSOCompatibilityMode)
argument := c.Query("method")
method := c.Query("method")
if useCompatibility {
argument = path.Base(c.Request.URL.Path)
method = path.Base(c.Request.URL.Path)
}
clientId := setting.GetStr(conf.SSOClientId)
endpoint := setting.GetStr(conf.SSOEndpointName)
@ -192,18 +191,12 @@ func OIDCLoginCallback(c *gin.Context) {
common.ErrorResp(c, err, 400)
return
}
oauth2Config, err := GetOIDCClient(c)
oauth2Config, err := GetOIDCClient(c, useCompatibility, "", method)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
// add state verify process
stateVerification, err := totp.ValidateCustom(c.Query("state"), base32.StdEncoding.EncodeToString([]byte(oauth2Config.ClientSecret)), time.Now(), opts)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
if !stateVerification {
if !verifyState(clientId, c.ClientIP(), c.Query("state")) {
common.ErrorStrResp(c, "incorrect or expired state parameter", 400)
return
}
@ -236,7 +229,7 @@ func OIDCLoginCallback(c *gin.Context) {
common.ErrorStrResp(c, "cannot get username from OIDC provider", 400)
return
}
if argument == "get_sso_id" {
if method == "get_sso_id" {
if useCompatibility {
c.Redirect(302, common.GetApiUrl(c.Request)+"/@manage?sso_id="+userID)
return
@ -252,7 +245,7 @@ func OIDCLoginCallback(c *gin.Context) {
c.Data(200, "text/html; charset=utf-8", []byte(html))
return
}
if argument == "sso_get_token" {
if method == "sso_get_token" {
user, err := db.GetUserBySSOID(userID)
if err != nil {
user, err = autoRegister(userID, userID, err)