Skip to content
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

feat: add quqi driver #5899

Merged
merged 6 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -37,6 +37,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/pikpak"
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
_ "github.com/alist-org/alist/v3/drivers/quark_uc"
_ "github.com/alist-org/alist/v3/drivers/quqi"
_ "github.com/alist-org/alist/v3/drivers/s3"
_ "github.com/alist-org/alist/v3/drivers/seafile"
_ "github.com/alist-org/alist/v3/drivers/sftp"
Expand Down
236 changes: 236 additions & 0 deletions drivers/quqi/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package quqi

import (
"context"
"net/http"
"strconv"
"strings"
"time"

"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/go-resty/resty/v2"
)

type Quqi struct {
model.Storage
Addition
GroupID string
}

var header = http.Header{}
EchoResponse marked this conversation as resolved.
Show resolved Hide resolved

func (d *Quqi) Config() driver.Config {
return config
}

func (d *Quqi) GetAddition() driver.Additional {
return &d.Addition
}

func (d *Quqi) Init(ctx context.Context) error {
// 登录
if err := d.login(); err != nil {
return err
}

// (暂时仅获取私人云) 获取私人云ID
groupResp := &GroupRes{}
if _, err := d.request("group.quqi.com", "/v1/group/list", resty.MethodGet, groupResp, nil); err != nil {
return err
}
for _, groupInfo := range groupResp.Data {
if groupInfo == nil {
continue
}
if groupInfo.Type == 2 {
d.GroupID = strconv.Itoa(groupInfo.ID)
break
}
}
if d.GroupID == "" {
return errs.StorageNotFound
}

// 设置header
header.Set("Origin", "https://quqi.com")
header.Set("Cookie", d.Cookie)

return nil
}

func (d *Quqi) Drop(ctx context.Context) error {
return nil
}

func (d *Quqi) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
var (
listResp = &ListRes{}
files []model.Obj
)

if _, err := d.request("", "/api/dir/ls", resty.MethodPost, listResp, map[string]string{
"quqi_id": d.GroupID,
"node_id": dir.GetID(),
}); err != nil {
return nil, err
}

if listResp.Data == nil {
return nil, nil
}

// dirs
for _, dirInfo := range listResp.Data.Dir {
if dirInfo == nil {
continue
}
files = append(files, &model.Object{
ID: strconv.FormatInt(dirInfo.NodeID, 10),
Name: dirInfo.Name,
Modified: time.Unix(dirInfo.UpdateTime, 0),
Ctime: time.Unix(dirInfo.AddTime, 0),
IsFolder: true,
})
}

// files
for _, fileInfo := range listResp.Data.File {
if fileInfo == nil {
continue
}
if fileInfo.EXT != "" {
fileInfo.Name = strings.Join([]string{fileInfo.Name, fileInfo.EXT}, ".")
}

files = append(files, &model.Object{
ID: strconv.FormatInt(fileInfo.NodeID, 10),
Name: fileInfo.Name,
Size: fileInfo.Size,
Modified: time.Unix(fileInfo.UpdateTime, 0),
Ctime: time.Unix(fileInfo.AddTime, 0),
})
}

return files, nil
}

func (d *Quqi) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
var getDocResp = &GetDocRes{}

if _, err := d.request("", "/api/doc/getDoc", resty.MethodPost, getDocResp, map[string]string{
"quqi_id": d.GroupID,
"node_id": file.GetID(),
}); err != nil {
return nil, err
}

return &model.Link{
URL: getDocResp.Data.OriginPath,
Header: header,
}, nil
}

func (d *Quqi) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
var (
makeDirRes = &MakeDirRes{}
timeNow = time.Now()
)

if _, err := d.request("", "/api/dir/mkDir", resty.MethodPost, makeDirRes, map[string]string{
"quqi_id": d.GroupID,
"parent_id": parentDir.GetID(),
"name": dirName,
}); err != nil {
return nil, err
}

return &model.Object{
ID: strconv.FormatInt(makeDirRes.Data.NodeID, 10),
Name: dirName,
Modified: timeNow,
Ctime: timeNow,
IsFolder: true,
}, nil
}

func (d *Quqi) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
var moveRes = &MoveRes{}

if _, err := d.request("", "/api/dir/mvDir", resty.MethodPost, moveRes, map[string]string{
"quqi_id": d.GroupID,
"node_id": dstDir.GetID(),
"source_quqi_id": d.GroupID,
"source_node_id": srcObj.GetID(),
}); err != nil {
return nil, err
}

return &model.Object{
ID: strconv.FormatInt(moveRes.Data.NodeID, 10),
Name: moveRes.Data.NodeName,
Size: srcObj.GetSize(),
Modified: time.Now(),
Ctime: srcObj.CreateTime(),
IsFolder: srcObj.IsDir(),
}, nil
}

func (d *Quqi) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
var renameRes = &RenameRes{}

if _, err := d.request("", "/api/dir/renameDir", resty.MethodPost, renameRes, map[string]string{
"quqi_id": d.GroupID,
"node_id": srcObj.GetID(),
"rename": newName,
}); err != nil {
return nil, err
}

return &model.Object{
ID: strconv.FormatInt(renameRes.Data.NodeID, 10),
Name: renameRes.Data.Rename,
Size: srcObj.GetSize(),
Modified: time.Unix(renameRes.Data.UpdateTime, 0),
Ctime: srcObj.CreateTime(),
IsFolder: srcObj.IsDir(),
}, nil
}

func (d *Quqi) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
// 无法从曲奇接口响应中直接获取复制后的文件信息
if _, err := d.request("", "/api/node/copy", resty.MethodPost, nil, map[string]string{
"quqi_id": d.GroupID,
"node_id": dstDir.GetID(),
"source_quqi_id": d.GroupID,
"source_node_id": srcObj.GetID(),
}); err != nil {
return nil, err
}

return nil, nil
}

func (d *Quqi) Remove(ctx context.Context, obj model.Obj) error {
// 暂时不做直接删除,默认都放到回收站。直接删除方法:先调用删除接口放入回收站,在通过回收站接口删除文件
if _, err := d.request("", "/api/node/del", resty.MethodPost, nil, map[string]string{
"quqi_id": d.GroupID,
"node_id": obj.GetID(),
}); err != nil {
return err
}

return nil
}

func (d *Quqi) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
// TODO upload file, optional
return nil, errs.NotImplement
}

//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
// return nil, errs.NotSupport
//}

var _ driver.Driver = (*Quqi)(nil)
26 changes: 26 additions & 0 deletions drivers/quqi/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package quqi

import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/op"
)

type Addition struct {
driver.RootID
Phone string `json:"phone"`
Password string `json:"password"`
Cookie string `json:"cookie" help:"Cookie can be used on multiple clients at the same time"`
}

var config = driver.Config{
Name: "曲奇网盘",
OnlyLocal: true,
NoUpload: true,
DefaultRoot: "0",
}

func init() {
op.RegisterDriver(func() driver.Driver {
return &Quqi{}
})
}
125 changes: 125 additions & 0 deletions drivers/quqi/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package quqi

type BaseReqQuery struct {
ID string `json:"quqiid"`
}

type BaseReq struct {
GroupID string `json:"quqi_id"`
}

type BaseRes struct {
//Data interface{} `json:"data"`
Code int `json:"err"`
Message string `json:"msg"`
}

type GroupRes struct {
BaseRes
Data []*Group `json:"data"`
}

type ListRes struct {
BaseRes
Data *List `json:"data"`
}

type GetDocRes struct {
BaseRes
Data struct {
OriginPath string `json:"origin_path"`
} `json:"data"`
}

type MakeDirRes struct {
BaseRes
Data struct {
IsRoot bool `json:"is_root"`
NodeID int64 `json:"node_id"`
ParentID int64 `json:"parent_id"`
} `json:"data"`
}

type MoveRes struct {
BaseRes
Data struct {
NodeChildNum int64 `json:"node_child_num"`
NodeID int64 `json:"node_id"`
NodeName string `json:"node_name"`
ParentID int64 `json:"parent_id"`
GroupID int64 `json:"quqi_id"`
TreeID int64 `json:"tree_id"`
} `json:"data"`
}

type RenameRes struct {
BaseRes
Data struct {
NodeID int64 `json:"node_id"`
GroupID int64 `json:"quqi_id"`
Rename string `json:"rename"`
TreeID int64 `json:"tree_id"`
UpdateTime int64 `json:"updatetime"`
} `json:"data"`
}

type CopyRes struct {
BaseRes
}

type RemoveRes struct {
BaseRes
}

type Group struct {
ID int `json:"quqi_id"`
Type int `json:"type"`
Name string `json:"name"`
IsAdministrator int `json:"is_administrator"`
Role int `json:"role"`
Avatar string `json:"avatar_url"`
IsStick int `json:"is_stick"`
Nickname string `json:"nickname"`
Status int `json:"status"`
}

type List struct {
ListDir
Dir []*ListDir `json:"dir"`
File []*ListFile `json:"file"`
}

type ListItem struct {
AddTime int64 `json:"add_time"`
IsDir int `json:"is_dir"`
IsExpand int `json:"is_expand"`
IsFinalize int `json:"is_finalize"`
LastEditorName string `json:"last_editor_name"`
Name string `json:"name"`
NodeID int64 `json:"nid"`
ParentID int64 `json:"parent_id"`
Permission int `json:"permission"`
TreeID int64 `json:"tid"`
UpdateCNT int64 `json:"update_cnt"`
UpdateTime int64 `json:"update_time"`
}

type ListDir struct {
ListItem
ChildDocNum int64 `json:"child_doc_num"`
DirDetail string `json:"dir_detail"`
DirType int `json:"dir_type"`
}

type ListFile struct {
ListItem
BroadDocType string `json:"broad_doc_type"`
CanDisplay bool `json:"can_display"`
Detail string `json:"detail"`
EXT string `json:"ext"`
Filetype string `json:"filetype"`
HasMobileThumbnail bool `json:"has_mobile_thumbnail"`
HasThumbnail bool `json:"has_thumbnail"`
Size int64 `json:"size"`
Version int `json:"version"`
}
Loading