fix(139): update APIs (#7591 close #7603)

* fix(139): update family cloud API

* fix(139): update API of familyGetLink

* feat(139): support group (close #7603)

* docs: add `139 group` to Readme

* feat(139): support multipart upload (close: #7444)

* feat(139): add custom upload part size option

* fix: missing right big quote

---------

Co-authored-by: Andy Hsu <i@nn.ci>
pull/7154/merge
MadDogOwner 2024-12-09 23:54:21 +08:00 committed by GitHub
parent 2a035302b2
commit a3908fd9a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 268 additions and 26 deletions

View File

@ -58,7 +58,7 @@ English | [中文](./README_cn.md)| [日本語](./README_ja.md) | [Contributing]
- [x] WebDav(Support OneDrive/SharePoint without API)
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
- [x] [Mediatrack](https://www.mediatrack.cn/)
- [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [139yun](https://yun.139.com/) (Personal, Family, Group)
- [x] [YandexDisk](https://disk.yandex.com/)
- [x] [BaiduNetdisk](http://pan.baidu.com/)
- [x] [Terabox](https://www.terabox.com/main)

View File

@ -58,7 +58,7 @@
- [x] WebDav(支持无API的OneDrive/SharePoint)
- [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ )
- [x] [分秒帧](https://www.mediatrack.cn/)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云,共享群组)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [百度网盘](http://pan.baidu.com/)
- [x] [UC网盘](https://drive.uc.cn)

View File

@ -58,7 +58,7 @@
- [x] WebDav(Support OneDrive/SharePoint without API)
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
- [x] [Mediatrack](https://www.mediatrack.cn/)
- [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [139yun](https://yun.139.com/) (Personal, Family, Group)
- [x] [YandexDisk](https://disk.yandex.com/)
- [x] [BaiduNetdisk](http://pan.baidu.com/)
- [x] [Terabox](https://www.terabox.com/main)

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"path"
"strconv"
"strings"
"time"
@ -14,15 +15,16 @@ import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/cron"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/utils/random"
log "github.com/sirupsen/logrus"
)
type Yun139 struct {
model.Storage
Addition
cron *cron.Cron
cron *cron.Cron
Account string
}
@ -56,6 +58,11 @@ func (d *Yun139) Init(ctx context.Context) error {
d.RootFolderID = "root"
}
fallthrough
case MetaGroup:
if len(d.Addition.RootFolderID) == 0 {
d.RootFolderID = d.CloudID
}
fallthrough
case MetaFamily:
decode, err := base64.StdEncoding.DecodeString(d.Authorization)
if err != nil {
@ -96,6 +103,8 @@ func (d *Yun139) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
return d.getFiles(dir.GetID())
case MetaFamily:
return d.familyGetFiles(dir.GetID())
case MetaGroup:
return d.groupGetFiles(dir.GetID())
default:
return nil, errs.NotImplement
}
@ -108,9 +117,11 @@ func (d *Yun139) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
case MetaPersonalNew:
url, err = d.personalGetLink(file.GetID())
case MetaPersonal:
fallthrough
case MetaFamily:
url, err = d.getLink(file.GetID())
case MetaFamily:
url, err = d.familyGetLink(file.GetID(), file.GetPath())
case MetaGroup:
url, err = d.groupGetLink(file.GetID(), file.GetPath())
default:
return nil, errs.NotImplement
}
@ -154,8 +165,22 @@ func (d *Yun139) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
"accountType": 1,
},
"docLibName": dirName,
"path": path.Join(parentDir.GetPath(), parentDir.GetID()),
}
pathname := "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
pathname := "/orchestration/familyCloud-rebuild/cloudCatalog/v1.0/createCloudDoc"
_, err = d.post(pathname, data, nil)
case MetaGroup:
data := base.Json{
"catalogName": dirName,
"commonAccountInfo": base.Json{
"account": d.Account,
"accountType": 1,
},
"groupID": d.CloudID,
"parentFileId": parentDir.GetID(),
"path": path.Join(parentDir.GetPath(), parentDir.GetID()),
}
pathname := "/orchestration/group-rebuild/catalog/v1.0/createGroupCatalog"
_, err = d.post(pathname, data, nil)
default:
err = errs.NotImplement
@ -176,6 +201,34 @@ func (d *Yun139) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj,
return nil, err
}
return srcObj, nil
case MetaGroup:
var contentList []string
var catalogList []string
if srcObj.IsDir() {
catalogList = append(catalogList, srcObj.GetID())
} else {
contentList = append(contentList, srcObj.GetID())
}
data := base.Json{
"taskType": 3,
"srcType": 2,
"srcGroupID": d.CloudID,
"destType": 2,
"destGroupID": d.CloudID,
"destPath": dstDir.GetPath(),
"contentList": contentList,
"catalogList": catalogList,
"commonAccountInfo": base.Json{
"account": d.Account,
"accountType": 1,
},
}
pathname := "/orchestration/group-rebuild/task/v1.0/createBatchOprTask"
_, err := d.post(pathname, data, nil)
if err != nil {
return nil, err
}
return srcObj, nil
case MetaPersonal:
var contentInfoList []string
var catalogInfoList []string
@ -246,6 +299,65 @@ func (d *Yun139) Rename(ctx context.Context, srcObj model.Obj, newName string) e
pathname = "/orchestration/personalCloud/content/v1.0/updateContentInfo"
}
_, err = d.post(pathname, data, nil)
case MetaGroup:
var data base.Json
var pathname string
if srcObj.IsDir() {
data = base.Json{
"groupID": d.CloudID,
"modifyCatalogID": srcObj.GetID(),
"modifyCatalogName": newName,
"path": srcObj.GetPath(),
"commonAccountInfo": base.Json{
"account": d.Account,
"accountType": 1,
},
}
pathname = "/orchestration/group-rebuild/catalog/v1.0/modifyGroupCatalog"
} else {
data = base.Json{
"groupID": d.CloudID,
"contentID": srcObj.GetID(),
"contentName": newName,
"path": srcObj.GetPath(),
"commonAccountInfo": base.Json{
"account": d.Account,
"accountType": 1,
},
}
pathname = "/orchestration/group-rebuild/content/v1.0/modifyGroupContent"
}
_, err = d.post(pathname, data, nil)
case MetaFamily:
var data base.Json
var pathname string
if srcObj.IsDir() {
// 网页接口不支持重命名家庭云文件夹
// data = base.Json{
// "catalogType": 3,
// "catalogID": srcObj.GetID(),
// "catalogName": newName,
// "commonAccountInfo": base.Json{
// "account": d.Account,
// "accountType": 1,
// },
// "path": srcObj.GetPath(),
// }
// pathname = "/orchestration/familyCloud-rebuild/photoContent/v1.0/modifyCatalogInfo"
return errs.NotImplement
} else {
data = base.Json{
"contentID": srcObj.GetID(),
"contentName": newName,
"commonAccountInfo": base.Json{
"account": d.Account,
"accountType": 1,
},
"path": srcObj.GetPath(),
}
pathname = "/orchestration/familyCloud-rebuild/photoContent/v1.0/modifyContentInfo"
}
_, err = d.post(pathname, data, nil)
default:
err = errs.NotImplement
}
@ -303,6 +415,28 @@ func (d *Yun139) Remove(ctx context.Context, obj model.Obj) error {
pathname := "/hcy/recyclebin/batchTrash"
_, err := d.personalPost(pathname, data, nil)
return err
case MetaGroup:
var contentList []string
var catalogList []string
// 必须使用完整路径删除
if obj.IsDir() {
catalogList = append(catalogList, obj.GetPath())
} else {
contentList = append(contentList, path.Join(obj.GetPath(), obj.GetID()))
}
data := base.Json{
"taskType": 2,
"srcGroupID": d.CloudID,
"contentList": contentList,
"catalogList": catalogList,
"commonAccountInfo": base.Json{
"account": d.Account,
"accountType": 1,
},
}
pathname := "/orchestration/group-rebuild/task/v1.0/createBatchOprTask"
_, err := d.post(pathname, data, nil)
return err
case MetaPersonal:
fallthrough
case MetaFamily:
@ -337,10 +471,12 @@ func (d *Yun139) Remove(ctx context.Context, obj model.Obj) error {
"account": d.Account,
"accountType": 1,
},
"sourceCloudID": d.CloudID,
"sourceCatalogType": 1002,
"taskType": 2,
"path": obj.GetPath(),
}
pathname = "/orchestration/familyCloud/batchOprTask/v1.0/createBatchOprTask"
pathname = "/orchestration/familyCloud-rebuild/batchOprTask/v1.0/createBatchOprTask"
}
_, err := d.post(pathname, data, nil)
return err
@ -536,21 +672,20 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
}
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
if d.isFamily() {
// data = d.newJson(base.Json{
// "fileCount": 1,
// "manualRename": 2,
// "operation": 0,
// "path": "",
// "seqNo": "",
// "totalSize": 0,
// "uploadContentList": []base.Json{{
// "contentName": stream.GetName(),
// "contentSize": 0,
// // "digest": "5a3231986ce7a6b46e408612d385bafa"
// }},
// })
// pathname = "/orchestration/familyCloud/content/v1.0/getFileUploadURL"
return errs.NotImplement
data = d.newJson(base.Json{
"fileCount": 1,
"manualRename": 2,
"operation": 0,
"path": path.Join(dstDir.GetPath(), dstDir.GetID()),
"seqNo": random.String(32), //序列号不能为空
"totalSize": 0,
"uploadContentList": []base.Json{{
"contentName": stream.GetName(),
"contentSize": 0,
// "digest": "5a3231986ce7a6b46e408612d385bafa"
}},
})
pathname = "/orchestration/familyCloud-rebuild/content/v1.0/getFileUploadURL"
}
var resp UploadResp
_, err := d.post(pathname, data, &resp)

View File

@ -7,6 +7,7 @@ import (
const (
MetaPersonal string = "personal"
MetaFamily string = "family"
MetaGroup string = "group"
MetaPersonalNew string = "personal_new"
)
@ -54,6 +55,7 @@ type Content struct {
//ContentDesc string `json:"contentDesc"`
//ContentType int `json:"contentType"`
//ContentOrigin int `json:"contentOrigin"`
CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
//CommentCount int `json:"commentCount"`
ThumbnailURL string `json:"thumbnailURL"`
@ -196,6 +198,27 @@ type QueryContentListResp struct {
} `json:"data"`
}
type QueryGroupContentListResp struct {
BaseResp
Data struct {
Result struct {
ResultCode string `json:"resultCode"`
ResultDesc string `json:"resultDesc"`
} `json:"result"`
GetGroupContentResult struct {
ParentCatalogID string `json:"parentCatalogID"` // 根目录是"0"
CatalogList []struct {
Catalog
Path string `json:"path"`
} `json:"catalogList"`
ContentList []Content `json:"contentList"`
NodeCount int `json:"nodeCount"` // 文件+文件夹数量
CtlgCnt int `json:"ctlgCnt"` // 文件夹数量
ContCnt int `json:"contCnt"` // 文件数量
} `json:"getGroupContentResult"`
} `json:"data"`
}
type ParallelHashCtx struct {
PartOffset int64 `json:"partOffset"`
}

View File

@ -13,9 +13,9 @@ import (
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/utils/random"
"github.com/alist-org/alist/v3/internal/op"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
@ -220,10 +220,11 @@ func (d *Yun139) familyGetFiles(catalogID string) ([]model.Obj, error) {
"sortDirection": 1,
})
var resp QueryContentListResp
_, err := d.post("/orchestration/familyCloud/content/v1.0/queryContentList", data, &resp)
_, err := d.post("/orchestration/familyCloud-rebuild/content/v1.2/queryContentList", data, &resp)
if err != nil {
return nil, err
}
path := resp.Data.Path
for _, catalog := range resp.Data.CloudCatalogList {
f := model.Object{
ID: catalog.CatalogID,
@ -232,6 +233,7 @@ func (d *Yun139) familyGetFiles(catalogID string) ([]model.Obj, error) {
IsFolder: true,
Modified: getTime(catalog.LastUpdateTime),
Ctime: getTime(catalog.CreateTime),
Path: path, // 文件夹上一级的Path
}
files = append(files, &f)
}
@ -243,6 +245,7 @@ func (d *Yun139) familyGetFiles(catalogID string) ([]model.Obj, error) {
Size: content.ContentSize,
Modified: getTime(content.LastUpdateTime),
Ctime: getTime(content.CreateTime),
Path: path, // 文件所在目录的Path
},
Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
//Thumbnail: content.BigthumbnailURL,
@ -257,6 +260,61 @@ func (d *Yun139) familyGetFiles(catalogID string) ([]model.Obj, error) {
return files, nil
}
func (d *Yun139) groupGetFiles(catalogID string) ([]model.Obj, error) {
pageNum := 1
files := make([]model.Obj, 0)
for {
data := d.newJson(base.Json{
"groupID": d.CloudID,
"catalogID": catalogID,
"contentSortType": 0,
"sortDirection": 1,
"startNumber": pageNum,
"endNumber": pageNum + 99,
"path": catalogID,
})
var resp QueryGroupContentListResp
_, err := d.post("/orchestration/group-rebuild/content/v1.0/queryGroupContentList", data, &resp)
if err != nil {
return nil, err
}
path := resp.Data.GetGroupContentResult.ParentCatalogID
for _, catalog := range resp.Data.GetGroupContentResult.CatalogList {
f := model.Object{
ID: catalog.CatalogID,
Name: catalog.CatalogName,
Size: 0,
IsFolder: true,
Modified: getTime(catalog.UpdateTime),
Ctime: getTime(catalog.CreateTime),
Path: catalog.Path, // 文件夹的真实Path root:/开头
}
files = append(files, &f)
}
for _, content := range resp.Data.GetGroupContentResult.ContentList {
f := model.ObjThumb{
Object: model.Object{
ID: content.ContentID,
Name: content.ContentName,
Size: content.ContentSize,
Modified: getTime(content.UpdateTime),
Ctime: getTime(content.CreateTime),
Path: path, // 文件所在目录的Path
},
Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
//Thumbnail: content.BigthumbnailURL,
}
files = append(files, &f)
}
if pageNum > resp.Data.GetGroupContentResult.NodeCount {
break
}
pageNum = pageNum + 100
}
return files, nil
}
func (d *Yun139) getLink(contentId string) (string, error) {
data := base.Json{
"appName": "",
@ -273,6 +331,32 @@ func (d *Yun139) getLink(contentId string) (string, error) {
}
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
}
func (d *Yun139) familyGetLink(contentId string, path string) (string, error) {
data := d.newJson(base.Json{
"contentID": contentId,
"path": path,
})
res, err := d.post("/orchestration/familyCloud-rebuild/content/v1.0/getFileDownLoadURL",
data, nil)
if err != nil {
return "", err
}
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
}
func (d *Yun139) groupGetLink(contentId string, path string) (string, error) {
data := d.newJson(base.Json{
"contentID": contentId,
"groupID": d.CloudID,
"path": path,
})
res, err := d.post("/orchestration/group-rebuild/groupManage/v1.0/getGroupFileDownLoadURL",
data, nil)
if err != nil {
return "", err
}
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
}
func unicode(str string) string {
textQuoted := strconv.QuoteToASCII(str)