-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(drivers): add Xiaomi Cloud Dirve #1391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
antecanis8
wants to merge
1
commit into
OpenListTeam:main
Choose a base branch
from
antecanis8:xiaomicloud
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| package micloud | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/OpenListTeam/OpenList/v4/internal/driver" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/errs" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/op" | ||
| ) | ||
|
|
||
| type MiCloud struct { | ||
| model.Storage | ||
| Addition | ||
| client *MiCloudClient // 小米云盘客户端 | ||
| } | ||
|
|
||
| func (d *MiCloud) Config() driver.Config { | ||
| return config | ||
| } | ||
|
|
||
| func (d *MiCloud) GetAddition() driver.Additional { | ||
| return &d.Addition | ||
| } | ||
|
|
||
| func (d *MiCloud) Init(ctx context.Context) error { | ||
| // 初始化小米云盘客户端 | ||
| client, err := NewMiCloudClient(d.UserId, d.ServiceToken, d.DeviceId) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| d.client = client | ||
| // 当 cookie(含 serviceToken)刷新时,写回 Addition 并持久化 | ||
| d.client.SetOnCookieUpdate(func(userId, serviceToken, deviceId string) { | ||
| // 更新 Addition | ||
| d.UserId = userId | ||
| d.ServiceToken = serviceToken | ||
| d.DeviceId = deviceId | ||
| // 持久化到数据库 | ||
| op.MustSaveDriverStorage(d) | ||
| }) | ||
|
|
||
| // 登录或刷新token | ||
| if err := d.client.Login(); err != nil { | ||
| return fmt.Errorf("failed to login to MiCloud: %w", err) | ||
| } | ||
| // 启动后台自动续期 serviceToken | ||
| d.client.StartAutoRenewal() | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func (d *MiCloud) Drop(ctx context.Context) error { | ||
| // 停止自动续期 | ||
| if d.client != nil { | ||
| d.client.StopAutoRenewal() | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (d *MiCloud) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 获取目录ID | ||
| folderId := d.client.rootId | ||
| if dir.GetID() != "" { | ||
| folderId = dir.GetID() | ||
| } | ||
|
|
||
| // 获取目录列表 | ||
| files, err := d.client.GetFolder(folderId) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| var objects []model.Obj | ||
| for _, file := range files { | ||
| obj := ConvertFileToObj(file) | ||
| objects = append(objects, obj) | ||
| } | ||
|
|
||
| return objects, nil | ||
| } | ||
|
|
||
| func (d *MiCloud) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
| // 直接获取直链 | ||
| url, err := d.client.GetFileDownLoadUrl(file.GetID()) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return &model.Link{URL: url}, nil | ||
| } | ||
|
|
||
| func (d *MiCloud) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 创建目录 | ||
| folderId := d.client.rootId | ||
| if parentDir.GetID() != "" { | ||
| folderId = parentDir.GetID() | ||
| } | ||
|
|
||
| newDirId, err := d.client.CreateFolder(dirName, folderId) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // 创建目录对象 | ||
| obj := &model.Object{ | ||
| ID: newDirId, | ||
| Name: dirName, | ||
| Size: 0, | ||
| Modified: time.Now(), | ||
| IsFolder: true, | ||
| } | ||
|
|
||
| return obj, nil | ||
| } | ||
|
|
||
| func (d *MiCloud) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 移动文件/目录 | ||
| movedObj, err := d.client.Move(srcObj.GetID(), dstDir.GetID()) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| obj := ConvertFileToObj(*movedObj) | ||
|
|
||
| return obj, nil | ||
| } | ||
|
|
||
| func (d *MiCloud) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 重命名文件/目录 | ||
| renamedObj, err := d.client.Rename(srcObj.GetID(), newName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| obj := ConvertFileToObj(*renamedObj) | ||
|
|
||
| return obj, nil | ||
| } | ||
|
|
||
| func (d *MiCloud) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { | ||
| return nil, errs.NotSupport | ||
| } | ||
|
|
||
| func (d *MiCloud) Remove(ctx context.Context, obj model.Obj) error { | ||
| if d.client == nil { | ||
| return fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 删除文件/目录 | ||
| return d.client.Delete(obj.GetID()) | ||
| } | ||
|
|
||
| func (d *MiCloud) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 上传文件 | ||
| uploadedObj, err := d.client.Upload(dstDir.GetID(), file, up) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| obj := ConvertFileToObj(*uploadedObj) | ||
|
|
||
| return obj, nil | ||
| } | ||
|
|
||
| func (d *MiCloud) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *MiCloud) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *MiCloud) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *MiCloud) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *MiCloud) GetDetails(ctx context.Context) (*model.StorageDetails, error) { | ||
| if d.client == nil { | ||
| return nil, fmt.Errorf("MiCloud client not initialized") | ||
| } | ||
|
|
||
| // 获取存储详情 | ||
| details, err := d.client.GetStorageDetails() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return details, nil | ||
| } | ||
|
|
||
| //func (d *MiCloud) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { | ||
| // return nil, errs.NotSupport | ||
| //} | ||
|
|
||
| var _ driver.Driver = (*MiCloud)(nil) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package micloud | ||
|
|
||
| import ( | ||
| "github.com/OpenListTeam/OpenList/v4/internal/driver" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/op" | ||
| ) | ||
|
|
||
| type Addition struct { | ||
| driver.RootID // 使用RootID作为根目录标识 | ||
| UserId string `json:"user_id" required:"true" help:"小米云盘用户ID"` | ||
| ServiceToken string `json:"service_token" required:"true" help:"小米云盘服务令牌"` | ||
| DeviceId string `json:"device_id" required:"true" help:"设备ID"` | ||
| } | ||
|
|
||
| var config = driver.Config{ | ||
| Name: "MiCloud", | ||
| LocalSort: true, // 本地排序 | ||
| OnlyLinkMFile: false, | ||
| OnlyProxy: false, // 允许直链 | ||
| NoCache: false, | ||
| NoUpload: false, // 支持上传 | ||
| NeedMs: false, | ||
| DefaultRoot: "0", // 根目录ID为0 | ||
| CheckStatus: true, // 检查状态 | ||
| //Alert: "注意:需要提供正确的用户ID、服务令牌和设备ID", | ||
| NoOverwriteUpload: false, | ||
| NoLinkURL: false, // Link 返回直链 URL | ||
| } | ||
|
|
||
| func init() { | ||
| op.RegisterDriver(func() driver.Driver { | ||
| return &MiCloud{} | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package micloud | ||
|
|
||
| // 由于json包在类型定义中没有直接使用,但可能在方法中需要,我们保留它 | ||
| // 实际上json包在MiCloudClient的实现中使用了,所以保留 | ||
|
|
||
| // File 小米云盘文件结构 | ||
| type File struct { | ||
| Sha1 string `json:"sha1"` | ||
| ModifyTime uint `json:"modifyTime"` | ||
| Size int64 `json:"size"` | ||
| CreateTime uint `json:"createTime"` | ||
| Name string `json:"name"` | ||
| Id string `json:"id"` | ||
| Type string `json:"type"` // "file" or "folder" | ||
| Revision string `json:"revision"` | ||
| IsActive bool `json:"isActive"` | ||
| } | ||
|
|
||
| // API响应结构 | ||
| type Msg struct { | ||
| Result string `json:"result"` | ||
| Retryable bool `json:"retryable"` | ||
| Code int `json:"code"` | ||
| Data struct { | ||
| HasMore bool `json:"has_more"` | ||
| List []File `json:"list"` | ||
| } `json:"data"` | ||
| } | ||
|
|
||
| // 上传相关结构 | ||
| type UploadJson struct { | ||
| Content UploadContent `json:"content"` | ||
| } | ||
|
|
||
| type UploadContent struct { | ||
| Name string `json:"name"` | ||
| ParentId string `json:"parentId"` | ||
| Storage interface{} `json:"storage"` | ||
| } | ||
|
|
||
| // Detailed storage payloads used by MiCloud KSS flow | ||
| type UploadStorage struct { | ||
| Size int64 `json:"size"` | ||
| Sha1 string `json:"sha1"` | ||
| Kss interface{} `json:"kss"` | ||
| UploadId string `json:"uploadId"` | ||
| Exists bool `json:"exists"` | ||
| } | ||
|
|
||
| type UploadExistedStorage struct { | ||
| UploadId string `json:"uploadId"` | ||
| Exists bool `json:"exists"` | ||
| } | ||
|
|
||
| type UploadKss struct { | ||
| BlockInfos []BlockInfo `json:"block_infos"` | ||
| } | ||
|
|
||
| type Kss struct { | ||
| Stat string `json:"stat"` | ||
| NodeUrls interface{} `json:"node_urls"` | ||
| SecureKey string `json:"secure_key"` | ||
| ContentCacheKey string `json:"contentCacheKey"` | ||
| FileMeta string `json:"file_meta"` | ||
| CommitMetas []map[string]string `json:"commit_metas"` | ||
| } | ||
|
|
||
| type BlockInfo struct { | ||
| Blob struct{} `json:"blob"` | ||
| Sha1 string `json:"sha1"` | ||
| Md5 string `json:"md5"` | ||
| Size int64 `json:"size"` | ||
| } | ||
|
|
||
| // 常量定义 | ||
| const ( | ||
| BaseUri = "https://i.mi.com" | ||
| GetFiles = BaseUri + "/drive/user/files/%s?jsonpCallback=callback" | ||
| GetFolders = BaseUri + "/drive/user/folders/%s/children" | ||
| GetDirectDL = BaseUri + "/drive/v2/user/files/download" | ||
| AutoRenewal = BaseUri + "/status/setting?type=AutoRenewal&inactiveTime=10&_dc=%d" | ||
| CreateFile = BaseUri + "/drive/user/files/create" | ||
| UploadFile = BaseUri + "/drive/user/files" | ||
| DeleteFiles = BaseUri + "/drive/v2/user/records/filemanager" | ||
| CreateFolder = BaseUri + "/drive/v2/user/folders/create" | ||
| ChunkSize = 4194304 // 4MB | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These comments are outdated and confusing since no json package is imported in this file. The comments should be removed or clarified.