Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
_ "github.com/OpenListTeam/OpenList/v4/drivers/local"
_ "github.com/OpenListTeam/OpenList/v4/drivers/mediatrack"
_ "github.com/OpenListTeam/OpenList/v4/drivers/mega"
_ "github.com/OpenListTeam/OpenList/v4/drivers/micloud"
_ "github.com/OpenListTeam/OpenList/v4/drivers/misskey"
_ "github.com/OpenListTeam/OpenList/v4/drivers/mopan"
_ "github.com/OpenListTeam/OpenList/v4/drivers/netease_music"
Expand Down
224 changes: 224 additions & 0 deletions drivers/micloud/driver.go
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)
34 changes: 34 additions & 0 deletions drivers/micloud/meta.go
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{}
})
}
87 changes: 87 additions & 0 deletions drivers/micloud/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package micloud

// 由于json包在类型定义中没有直接使用,但可能在方法中需要,我们保留它
// 实际上json包在MiCloudClient的实现中使用了,所以保留
Comment on lines +3 to +4
Copy link

Copilot AI Sep 30, 2025

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.

Suggested change
// 由于json包在类型定义中没有直接使用,但可能在方法中需要,我们保留它
// 实际上json包在MiCloudClient的实现中使用了,所以保留

Copilot uses AI. Check for mistakes.

// 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
)
Loading