Skip to content
131 changes: 131 additions & 0 deletions drivers/alidoc/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package alidoc

import (
"context"
"fmt"
"net/http"
"strings"

"github.com/OpenListTeam/OpenList/v4/drivers/base"
"github.com/OpenListTeam/OpenList/v4/internal/driver"
"github.com/OpenListTeam/OpenList/v4/internal/model"
"github.com/go-resty/resty/v2"
)

type AliDoc struct {
model.Storage
Addition

client *resty.Client
}

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

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

func (d *AliDoc) Init(ctx context.Context) error {
d.Cookie = strings.TrimSpace(d.Cookie)
d.RootFolderID = strings.TrimSpace(d.RootFolderID)
if d.Cookie == "" {
return fmt.Errorf("cookie is empty")
}
if d.RootFolderID == "" {
return fmt.Errorf("root folder id is empty")
}
d.client = newClient()
if _, err := d.list(ctx, d.RootFolderID); err != nil {
return err
}
return nil
}

func (d *AliDoc) Drop(ctx context.Context) error {
d.client = nil
return nil
}

func (d *AliDoc) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
parentID := d.RootFolderID
parentPath := "/"
if dir != nil {
if id := strings.TrimSpace(dir.GetID()); id != "" {
parentID = id
}
if p := dir.GetPath(); p != "" {
parentPath = p
}
}

items, err := d.list(ctx, parentID)
if err != nil {
return nil, err
}

objs := make([]model.Obj, 0, len(items))
for _, item := range items {
if strings.TrimSpace(item.DentryUUID) == "" || strings.TrimSpace(item.Name) == "" {
continue
}
objs = append(objs, toObj(parentPath, item))
}
return objs, nil
}

func (d *AliDoc) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
if file == nil || file.IsDir() {
return nil, fmt.Errorf("alidoc does not support directory links")
}
resp, err := d.download(ctx, file.GetID())
if err != nil {
return nil, err
}
url, err := firstDownloadURL(resp)
if err != nil {
return nil, err
}
return &model.Link{
URL: url,
Header: http.Header{
"User-Agent": []string{base.UserAgent},
"Referer": []string{apiBase + "/"},
},
}, nil
}

func (d *AliDoc) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
return nil, readonlyError()
}

func (d *AliDoc) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
return nil, readonlyError()
}

func (d *AliDoc) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
return nil, readonlyError()
}

func (d *AliDoc) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
return nil, readonlyError()
}

func (d *AliDoc) Remove(ctx context.Context, obj model.Obj) error {
return readonlyError()
}

func (d *AliDoc) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
return nil, readonlyError()
}

var (
_ driver.Driver = (*AliDoc)(nil)
_ driver.MkdirResult = (*AliDoc)(nil)
_ driver.MoveResult = (*AliDoc)(nil)
_ driver.RenameResult = (*AliDoc)(nil)
_ driver.CopyResult = (*AliDoc)(nil)
_ driver.Remove = (*AliDoc)(nil)
_ driver.PutResult = (*AliDoc)(nil)
)
24 changes: 24 additions & 0 deletions drivers/alidoc/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package alidoc

import (
"github.com/OpenListTeam/OpenList/v4/internal/driver"
"github.com/OpenListTeam/OpenList/v4/internal/op"
)

type Addition struct {
driver.RootID
Cookie string `json:"cookie" type:"text" required:"true" help:"钉钉文档网页 Cookie"`
}

var config = driver.Config{
Name: "AliDoc",
LocalSort: true,
DefaultRoot: "",
Alert: "info|This driver supports accessing DingDrive through DingTalk Docs and is currently read-only.",
}

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

import "github.com/OpenListTeam/OpenList/v4/internal/model"

type apiResp struct {
Status int `json:"status"`
IsSuccess bool `json:"isSuccess"`
Message string `json:"message"`
Msg string `json:"msg"`
}

func (r apiResp) ErrMessage() string {
if r.Message != "" {
return r.Message
}
if r.Msg != "" {
return r.Msg
}
return ""
}

type listResp struct {
apiResp
Data listData `json:"data"`
}

type listData struct {
Children []dentry `json:"children"`
}

type dentry struct {
DentryType string `json:"dentryType"`
DentryUUID string `json:"dentryUuid"`
Name string `json:"name"`
FileSize int64 `json:"fileSize"`
CreatedTime int64 `json:"createdTime"`
UpdatedTime int64 `json:"updatedTime"`
ContentType string `json:"contentType"`
Extension string `json:"extension"`
DentryStatistic struct {
ChildrenCount int `json:"childrenCount"`
} `json:"dentryStatistic"`
URL struct {
PCChildAppPreviewURL string `json:"pcChildAppPreviewUrl"`
PCChildAppURL string `json:"pcChildAppUrl"`
} `json:"url"`
}

type downloadResp struct {
apiResp
Data downloadData `json:"data"`
}

type downloadData struct {
OSSURLPreSignatureInfo struct {
PreSignURLs []string `json:"preSignUrls"`
} `json:"ossUrlPreSignatureInfo"`
}

type Object struct {
model.Object
DentryType string
ContentType string
Extension string
PreviewURL string
EditURL string
}
135 changes: 135 additions & 0 deletions drivers/alidoc/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package alidoc

import (
"context"
"fmt"
"strings"
"time"

"github.com/OpenListTeam/OpenList/v4/drivers/base"
"github.com/OpenListTeam/OpenList/v4/internal/errs"
"github.com/OpenListTeam/OpenList/v4/internal/model"
"github.com/go-resty/resty/v2"
)

const apiBase = "https://alidocs.dingtalk.com"

func (d *AliDoc) request(ctx context.Context) *resty.Request {
return d.client.R().
SetContext(ctx).
SetHeader("Cookie", d.Cookie).
SetHeader("Accept", "application/json, text/plain, */*").
SetHeader("Referer", apiBase+"/").
SetHeader("Origin", apiBase)
}

func joinPath(basePath, name string) string {
if basePath == "" || basePath == "/" {
return "/" + name
}
return strings.TrimRight(basePath, "/") + "/" + name
}

func msToTime(v int64) time.Time {
if v <= 0 {
return time.Time{}
}
return time.UnixMilli(v)
}

func checkResp(resp *resty.Response, result apiResp) error {
if resp != nil && resp.IsError() {
if msg := result.ErrMessage(); msg != "" {
return fmt.Errorf("%s", msg)
}
return fmt.Errorf("http error: %d", resp.StatusCode())
}
if !result.IsSuccess || result.Status != 200 {
msg := result.ErrMessage()
if msg == "" {
msg = "request failed"
}
return fmt.Errorf("%s", msg)
}
return nil
}

func toObj(parentPath string, item dentry) model.Obj {
obj := &Object{
Object: model.Object{
ID: item.DentryUUID,
Path: joinPath(parentPath, item.Name),
Name: item.Name,
Size: item.FileSize,
Modified: msToTime(item.UpdatedTime),
Ctime: msToTime(item.CreatedTime),
IsFolder: item.DentryType == "folder",
},
DentryType: item.DentryType,
ContentType: item.ContentType,
Extension: item.Extension,
PreviewURL: item.URL.PCChildAppPreviewURL,
EditURL: item.URL.PCChildAppURL,
}
if obj.IsDir() && item.DentryStatistic.ChildrenCount > 0 && obj.Size == 0 {
// Keep size as 0 for folders; childrenCount is intentionally ignored.
}
return obj
}

func readonlyError() error {
return fmt.Errorf("alidoc driver is read-only: %w", errs.NotSupport)
}

func firstDownloadURL(resp downloadResp) (string, error) {
if len(resp.Data.OSSURLPreSignatureInfo.PreSignURLs) == 0 {
return "", fmt.Errorf("empty download url")
}
return resp.Data.OSSURLPreSignatureInfo.PreSignURLs[0], nil
}

func newClient() *resty.Client {
client := base.NewRestyClient()
client.SetHeader("User-Agent", base.UserAgent)
return client
}

func (d *AliDoc) list(ctx context.Context, dentryUUID string) ([]dentry, error) {
var result listResp
resp, err := d.request(ctx).
SetQueryParam("dentryUuid", dentryUUID).
SetQueryParam("withParentAncestors", "true").
SetQueryParam("orderType", "SORT_KEY").
SetQueryParam("sortType", "desc").
SetQueryParam("listDentrySource", "2").
SetQueryParam("pageSize", "1000").
SetResult(&result).
SetError(&result).
Get(apiBase + "/box/api/v2/dentry/list")
if err != nil {
return nil, err
}
if err := checkResp(resp, result.apiResp); err != nil {
return nil, err
}
return result.Data.Children, nil
}

func (d *AliDoc) download(ctx context.Context, dentryUUID string) (downloadResp, error) {
var result downloadResp
resp, err := d.request(ctx).
SetQueryParam("dentryUuid", dentryUUID).
SetQueryParam("version", "1").
SetQueryParam("supportDownloadTypes", "URL_PRE_SIGNATURE,HTTP_TO_CENTER").
SetQueryParam("downloadType", "URL_PRE_SIGNATURE").
SetResult(&result).
SetError(&result).
Get(apiBase + "/box/api/v2/file/download")
if err != nil {
return result, err
}
if err := checkResp(resp, result.apiResp); err != nil {
return result, err
}
return result, nil
}
1 change: 1 addition & 0 deletions drivers/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
_ "github.com/OpenListTeam/OpenList/v4/drivers/189_tv"
_ "github.com/OpenListTeam/OpenList/v4/drivers/189pc"
_ "github.com/OpenListTeam/OpenList/v4/drivers/alias"
_ "github.com/OpenListTeam/OpenList/v4/drivers/alidoc"
_ "github.com/OpenListTeam/OpenList/v4/drivers/alist_v3"
_ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive"
_ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive_open"
Expand Down
Loading