Skip to content

Commit aa534a9

Browse files
zjhcxcodexj2rong4cn
authored
feat(drivers/alidoc): add DingTalk Docs driver (#2543)
* feat(drivers/alidoc): add DingTalk Docs driver Generated with OpenAI Codex * chore(drivers/alidoc): update metadata copy Generated with OpenAI Codex * chore(drivers/alidoc): update read-only alert copy Generated with OpenAI Codex * chore(gitignore): remove alidoc ignore rule Generated with OpenAI Codex * feat(alidoc): support upload move and recycle delete Generated with OpenAI Codex * fix(alidoc): commit uploaded files after oss transfer Generated with OpenAI Codex * feat(alidoc): support mkdir copy and rename Generated with OpenAI Codex * chore(alidoc): remove unused readonly helper * chore(alidoc): remove driver alert copy * refactor(alidoc): simplify id-based driver behavior Generated with OpenAI Codex * refactor(alidoc): stream multipart uploads directly Generated with OpenAI Codex * refactor(alidoc): use retry helper for multipart uploads Generated with OpenAI Codex * Update drivers/alidoc/driver.go Co-authored-by: j2rong4cn <36783515+j2rong4cn@users.noreply.github.com> Signed-off-by: Unity_exe <zjhcx12@gmail.com> * refactor(alidoc): simplify write operation responses Generated with OpenAI Codex * fix(alidoc): update multipart upload progress Generated with OpenAI Codex * docs(readme): sync DingTalk Docs support across translations * refactor(alidoc): simplify file operations and remove redundant checks * fix(wps): update login state handling and improve error messages * fix(alidoc): validate cookie with mine info endpoint Generated with OpenAI Codex * fix(alidoc): skip root folder listing during init --------- Signed-off-by: Unity_exe <zjhcx12@gmail.com> Co-authored-by: Codex <267193182+codex@users.noreply.github.com> Co-authored-by: j2rong4cn <36783515+j2rong4cn@users.noreply.github.com> Co-authored-by: j2rong4cn <j2rong@qq.com>
1 parent d403f75 commit aa534a9

12 files changed

Lines changed: 668 additions & 10 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Thank you for your support and understanding of the OpenList project.
9595
- [x] [OpenList](https://github.com/OpenListTeam/OpenList)
9696
- [x] [Teldrive](https://github.com/tgdrive/teldrive)
9797
- [x] [Weiyun](https://www.weiyun.com)
98+
- [x] [DingTalk Docs](https://alidocs.dingtalk.com/)
9899
- [x] Easy to deploy and out-of-the-box
99100
- [x] File preview (PDF, markdown, code, plain text, ...)
100101
- [x] Image preview in gallery mode

‎README_cn.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ OpenList 是一个由 OpenList 团队独立维护的开源项目,遵循 AGPL-3
9595
- [x] [OpenList](https://github.com/OpenListTeam/OpenList)
9696
- [x] [Teldrive](https://github.com/tgdrive/teldrive)
9797
- [x] [微云](https://www.weiyun.com)
98+
- [x] [钉钉文档](https://alidocs.dingtalk.com/)
9899
- [x] 部署方便,开箱即用
99100
- [x] 文件预览(PDF、markdown、代码、纯文本等)
100101
- [x] 画廊模式下的图片预览

‎README_ja.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ OpenListプロジェクトへのご支援とご理解をありがとうござい
9494
- [x] [OpenList](https://github.com/OpenListTeam/OpenList)
9595
- [x] [Teldrive](https://github.com/tgdrive/teldrive)
9696
- [x] [Weiyun](https://www.weiyun.com)
97+
- [x] [DingTalk ドキュメント](https://alidocs.dingtalk.com/)
9798
- [x] [MediaFire](https://www.mediafire.com)
9899
- [x] 簡単にデプロイでき、すぐに使える
99100
- [x] ファイルプレビュー(PDF、markdown、コード、テキストなど)

‎README_nl.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Dank u voor uw ondersteuning en begrip
9595
- [x] [OpenList](https://github.com/OpenListTeam/OpenList)
9696
- [x] [Teldrive](https://github.com/tgdrive/teldrive)
9797
- [x] [Weiyun](https://www.weiyun.com)
98+
- [x] [DingTalk-documenten](https://alidocs.dingtalk.com/)
9899
- [x] Eenvoudig te implementeren en direct te gebruiken
99100
- [x] Bestandsvoorbeeld (PDF, markdown, code, platte tekst, ...)
100101
- [x] Afbeeldingsvoorbeeld in galerijweergave

‎drivers/alidoc/driver.go‎

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package alidoc
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/OpenListTeam/OpenList/v4/drivers/base"
10+
"github.com/OpenListTeam/OpenList/v4/internal/driver"
11+
"github.com/OpenListTeam/OpenList/v4/internal/model"
12+
"github.com/go-resty/resty/v2"
13+
)
14+
15+
type AliDoc struct {
16+
model.Storage
17+
Addition
18+
19+
client *resty.Client
20+
}
21+
22+
func (d *AliDoc) Config() driver.Config {
23+
return config
24+
}
25+
26+
func (d *AliDoc) GetAddition() driver.Additional {
27+
return &d.Addition
28+
}
29+
30+
func (d *AliDoc) Init(ctx context.Context) error {
31+
d.Cookie = strings.TrimSpace(d.Cookie)
32+
d.RootFolderID = strings.TrimSpace(d.RootFolderID)
33+
if d.Cookie == "" {
34+
return fmt.Errorf("cookie is empty")
35+
}
36+
if d.RootFolderID == "" {
37+
return fmt.Errorf("root folder id is empty")
38+
}
39+
d.client = newClient()
40+
if err := d.checkCookie(ctx); err != nil {
41+
return err
42+
}
43+
return nil
44+
}
45+
46+
func (d *AliDoc) Drop(ctx context.Context) error {
47+
d.client = nil
48+
return nil
49+
}
50+
51+
func (d *AliDoc) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
52+
items, err := d.list(ctx, dir.GetID())
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
objs := make([]model.Obj, 0, len(items))
58+
for _, item := range items {
59+
if strings.TrimSpace(item.DentryUUID) == "" || strings.TrimSpace(item.Name) == "" {
60+
continue
61+
}
62+
objs = append(objs, toObj(item))
63+
}
64+
return objs, nil
65+
}
66+
67+
func (d *AliDoc) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
68+
if file.IsDir() {
69+
return nil, fmt.Errorf("alidoc does not support directory links")
70+
}
71+
resp, err := d.download(ctx, file.GetID())
72+
if err != nil {
73+
return nil, err
74+
}
75+
url, err := firstDownloadURL(resp)
76+
if err != nil {
77+
return nil, err
78+
}
79+
return &model.Link{
80+
URL: url,
81+
Header: http.Header{
82+
"User-Agent": []string{base.UserAgent},
83+
"Referer": []string{apiBase + "/"},
84+
},
85+
}, nil
86+
}
87+
88+
func (d *AliDoc) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
89+
err := d.post(ctx, "/box/api/v2/dentry/createfolder", map[string]string{
90+
"dentryType": "folder",
91+
"name": dirName,
92+
"parentDentryUuid": parentDir.GetID(),
93+
"conflictHandleStrategy": "auto_rename",
94+
})
95+
if err != nil {
96+
return nil, err
97+
}
98+
return nil, nil
99+
}
100+
101+
func (d *AliDoc) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
102+
err := d.post(ctx, "/box/api/v2/dentry/move", map[string]interface{}{
103+
"targetParentDentryUuid": dstDir.GetID(),
104+
"sourceDentryUuid": srcObj.GetID(),
105+
"operateFrom": 1,
106+
})
107+
if err != nil {
108+
return nil, err
109+
}
110+
return srcObj, nil
111+
}
112+
113+
func (d *AliDoc) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
114+
err := d.post(ctx, "/box/api/v2/dentry/rename", map[string]string{
115+
"dentryUuid": srcObj.GetID(),
116+
"name": newName,
117+
})
118+
if err != nil {
119+
return nil, err
120+
}
121+
srcObj.(*model.Object).Name = newName
122+
return srcObj, nil
123+
}
124+
125+
func (d *AliDoc) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
126+
return d.post(ctx, "/box/api/v2/dentry/copy", map[string]interface{}{
127+
"sourceDentryUuid": srcObj.GetID(),
128+
"targetParentDentryUuid": dstDir.GetID(),
129+
"operateFrom": 1,
130+
"onlyCopyMeta": false,
131+
})
132+
}
133+
134+
func (d *AliDoc) Remove(ctx context.Context, obj model.Obj) error {
135+
return d.post(ctx, "/box/api/v1/dentry/recycle", map[string]string{
136+
"dentryUuid": obj.GetID(),
137+
})
138+
}
139+
140+
var (
141+
_ driver.Driver = (*AliDoc)(nil)
142+
_ driver.MkdirResult = (*AliDoc)(nil)
143+
_ driver.MoveResult = (*AliDoc)(nil)
144+
_ driver.RenameResult = (*AliDoc)(nil)
145+
_ driver.Copy = (*AliDoc)(nil)
146+
_ driver.Remove = (*AliDoc)(nil)
147+
)

‎drivers/alidoc/meta.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package alidoc
2+
3+
import (
4+
"github.com/OpenListTeam/OpenList/v4/internal/driver"
5+
"github.com/OpenListTeam/OpenList/v4/internal/op"
6+
)
7+
8+
type Addition struct {
9+
driver.RootID
10+
Cookie string `json:"cookie" type:"text" required:"true" help:"钉钉文档网页 Cookie"`
11+
}
12+
13+
var config = driver.Config{
14+
Name: "AliDoc",
15+
LocalSort: true,
16+
DefaultRoot: "",
17+
}
18+
19+
func init() {
20+
op.RegisterDriver(func() driver.Driver {
21+
return &AliDoc{}
22+
})
23+
}

‎drivers/alidoc/types.go‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package alidoc
2+
3+
type apiResp struct {
4+
Status int `json:"status"`
5+
IsSuccess bool `json:"isSuccess"`
6+
Message string `json:"message"`
7+
Msg string `json:"msg"`
8+
}
9+
10+
func (r apiResp) ErrMessage() string {
11+
if r.Message != "" {
12+
return r.Message
13+
}
14+
if r.Msg != "" {
15+
return r.Msg
16+
}
17+
return ""
18+
}
19+
20+
type listResp struct {
21+
apiResp
22+
Data listData `json:"data"`
23+
}
24+
25+
type listData struct {
26+
Children []dentry `json:"children"`
27+
}
28+
29+
type dentry struct {
30+
DentryType string `json:"dentryType"`
31+
DentryUUID string `json:"dentryUuid"`
32+
ParentDentryUUID string `json:"parentDentryUuid"`
33+
Name string `json:"name"`
34+
Path string `json:"path"`
35+
FileSize int64 `json:"fileSize"`
36+
CreatedTime int64 `json:"createdTime"`
37+
UpdatedTime int64 `json:"updatedTime"`
38+
ContentType string `json:"contentType"`
39+
Extension string `json:"extension"`
40+
DentryStatistic struct {
41+
ChildrenCount int `json:"childrenCount"`
42+
} `json:"dentryStatistic"`
43+
URL struct {
44+
PCChildAppPreviewURL string `json:"pcChildAppPreviewUrl"`
45+
PCChildAppURL string `json:"pcChildAppUrl"`
46+
} `json:"url"`
47+
}
48+
49+
type downloadResp struct {
50+
apiResp
51+
Data downloadData `json:"data"`
52+
}
53+
54+
type downloadData struct {
55+
OSSURLPreSignatureInfo struct {
56+
PreSignURLs []string `json:"preSignUrls"`
57+
} `json:"ossUrlPreSignatureInfo"`
58+
}
59+
60+
type uploadInfoResp struct {
61+
apiResp
62+
Data uploadInfoData `json:"data"`
63+
}
64+
65+
type uploadInfoData struct {
66+
CurrentTimestamp int64 `json:"currentTimestamp"`
67+
FileUploadProtocolConfig uploadProtocolConfig `json:"fileUploadProtocolConfig"`
68+
STSSignatureInfo uploadSTSSignatureInfo `json:"stsSignatureInfo"`
69+
UploadKey string `json:"uploadKey"`
70+
UploadType string `json:"uploadType"`
71+
}
72+
73+
type uploadProtocolConfig struct {
74+
MinPartSize int64 `json:"minPartSize"`
75+
}
76+
77+
type uploadSTSSignatureInfo struct {
78+
AccelerateCname string `json:"accelerateCname"`
79+
AccessKeyID string `json:"accessKeyId"`
80+
AccessKeySecret string `json:"accessKeySecret"`
81+
AccessToken string `json:"accessToken"`
82+
AccessTokenExpiration int64 `json:"accessTokenExpiration"`
83+
Bucket string `json:"bucket"`
84+
Cname string `json:"cname"`
85+
EndPoint string `json:"endPoint"`
86+
ObjectKey string `json:"objectKey"`
87+
}

0 commit comments

Comments
 (0)