-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(driver): add Cloudflare Image Bed support #2427
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
Open
ZZ0YY
wants to merge
16
commits into
OpenListTeam:main
Choose a base branch
from
ZZ0YY:feat/cfimgbed
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.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
202f77d
feat(driver): add Cloudflare Image Bed support
ZZ0YY 9bdaac8
fix: restore accidentally deleted file and name
ZZ0YY f828fc5
refactor: rename driver to cloudflare_imgbed and fix module structure
ZZ0YY 7858f49
fix: use base.NewRestyClient() and use e.g
ZZ0YY 569dedf
fix:go fmt
ZZ0YY 36aecbf
feat(driver/cloudflare-imgbed): enhance cloudflare_imgbed API integra…
ZZ0YY dc74222
refactor
j2rong4cn a54f30b
feat(cloudflare_imgbed): implement upload functionality and optimize …
ZZ0YY 719de68
Merge branch 'main' into feat/cfimgbed
ZZ0YY 1fdf6a3
refactor: simplify path handling logic
ZZ0YY 6013825
Merge branch 'feat/cfimgbed' of https://github.com/ZZ0YY/OpenList int…
ZZ0YY 865c19f
refactor(cloudflare_imgbed): streamline API endpoint constants and im…
j2rong4cn 7b5259b
refactor(cloudflare_imgbed): clean up upload logic and remove unused …
j2rong4cn 85f1189
docs: update help descriptions to English in cloudflare_imgbed
ZZ0YY 64b9947
Merge branch 'OpenListTeam:main' into ZZ0YY-patch-1
ZZ0YY 19a1dcb
docs: update help descriptions to English in cloudflare_imgbed
ZZ0YY 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,128 @@ | ||
| package cloudflare_imgbed | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "net/http" | ||
| "path" | ||
| "strings" | ||
|
|
||
| "github.com/OpenListTeam/OpenList/v4/drivers/base" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/driver" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||
| "github.com/OpenListTeam/OpenList/v4/pkg/utils" | ||
| "github.com/go-resty/resty/v2" | ||
| ) | ||
|
|
||
| type CFImgBed struct { | ||
| model.Storage | ||
| Addition | ||
| client *resty.Client | ||
| } | ||
|
|
||
| func (d *CFImgBed) Config() driver.Config { return config } | ||
| func (d *CFImgBed) GetAddition() driver.Additional { return &d.Addition } | ||
|
|
||
| func (d *CFImgBed) Init(ctx context.Context) error { | ||
| d.UploadThread = min(d.UploadThread, 32) | ||
| if d.UploadThread < 1 { | ||
| d.UploadThread = 3 | ||
| } | ||
| d.Address = strings.TrimRight(d.Address, "/") | ||
|
|
||
| d.client = base.NewRestyClient(). | ||
| SetBaseURL(d.Address). | ||
| SetHeader("Authorization", "Bearer "+d.Token). | ||
| SetDebug(false) | ||
|
|
||
| // 连通性测试:尝试获取根目录单条数据 | ||
| _, err := d.doRequest(http.MethodGet, listApi, func(req *resty.Request) { | ||
| req.SetQueryParams(map[string]string{ | ||
| "start": "0", | ||
| "count": "1", | ||
| "dir": "/", | ||
| }) | ||
| }, nil) | ||
| if err != nil { | ||
| return fmt.Errorf("init verification failed: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (d *CFImgBed) Drop(ctx context.Context) error { return nil } | ||
|
|
||
| func (d *CFImgBed) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { | ||
| reqPath := dir.GetPath() | ||
|
|
||
| dirSeen := make(map[string]bool) | ||
| fileSeen := make(map[string]bool) | ||
| objs := make([]model.Obj, 0) | ||
|
|
||
| start := 0 | ||
| for { | ||
| var resp ListResponse | ||
| _, err := d.doRequest(http.MethodGet, listApi, func(req *resty.Request) { | ||
| req.SetQueryParams(map[string]string{ | ||
| "dir": reqPath, | ||
| "start": fmt.Sprintf("%d", start), | ||
| "count": fmt.Sprintf("%d", listPageSize), | ||
| }) | ||
| }, &resp) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| for _, rawDir := range resp.Directories { | ||
| cleanDir := strings.TrimRight(rawDir, "/") | ||
| if !dirSeen[cleanDir] { | ||
| dirSeen[cleanDir] = true | ||
| objs = append(objs, &model.Object{ | ||
| Path: cleanDir, | ||
| Name: path.Base(cleanDir), | ||
| Modified: d.Modified, | ||
| IsFolder: true, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| for _, item := range resp.Files { | ||
| if !fileSeen[item.Name] { | ||
| fileSeen[item.Name] = true | ||
| objs = append(objs, parseFile(item)) | ||
| } | ||
| } | ||
|
|
||
| // 如果当前获取的数量少于分页大小,说明已加载完毕 | ||
| if len(resp.Files)+len(resp.Directories) < listPageSize { | ||
| break | ||
| } | ||
| start += listPageSize | ||
| } | ||
| return objs, nil | ||
| } | ||
|
|
||
| func (d *CFImgBed) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { | ||
| return &model.Link{URL: d.Address + "/file/" + utils.EncodePath(file.GetPath())}, nil | ||
| } | ||
|
|
||
| // MakeDir 在图床中通常是虚拟的,此处返回虚拟目录对象以支持上传时的路径展示 | ||
| func (d *CFImgBed) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) { | ||
| fullPath := path.Join(parentDir.GetPath(), dirName) | ||
| return &model.Object{ | ||
| Path: fullPath, | ||
| Name: dirName, | ||
| IsFolder: true, | ||
| }, nil | ||
| } | ||
|
|
||
| func (d *CFImgBed) Remove(ctx context.Context, obj model.Obj) error { | ||
| reqPath := obj.GetPath() | ||
| _, err := d.doRequest(http.MethodPost, deleteApi, func(req *resty.Request) { | ||
| req.SetBody(map[string]string{ | ||
| "path": reqPath, | ||
| }).SetQueryParam("folder", fmt.Sprintf("%t", obj.IsDir())) | ||
| }, nil) | ||
| return err | ||
| } | ||
|
|
||
| var _ driver.Driver = (*CFImgBed)(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,27 @@ | ||
| package cloudflare_imgbed | ||
|
|
||
| import ( | ||
| "github.com/OpenListTeam/OpenList/v4/internal/driver" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/op" | ||
| ) | ||
|
|
||
| type Addition struct { | ||
| driver.RootPath | ||
| Address string `json:"address" type:"text" required:"true" help:"Backend API address of the image hosting service, e.g., https://img.example.com"` | ||
| Token string `json:"token" type:"text" required:"true" help:"Authentication Token"` | ||
| SmallChannelName string `json:"smallChannelName" type:"text" help:"Channel name for regular files (typically <20MB)"` | ||
| LargeChannelName string `json:"largeChannelName" type:"text" help:"Channel name for large files"` | ||
| LargeChannelType string `json:"largeChannelType" type:"select" options:",huggingface" help:"Special type for large file channels (select 'huggingface' for direct upload to HuggingFace)"` | ||
| UploadThread int `json:"uploadThread" type:"number" default:"3" help:"Concurrent thread count for HuggingFace chunked direct upload"` | ||
| } | ||
|
|
||
| var config = driver.Config{ | ||
| Name: "cloudflare_imgbed", | ||
| LocalSort: true, | ||
| NoUpload: false, | ||
| DefaultRoot: "/", | ||
| } | ||
|
|
||
| func init() { | ||
| op.RegisterDriver(func() driver.Driver { return &CFImgBed{} }) | ||
| } |
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,114 @@ | ||
| package cloudflare_imgbed | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "path" | ||
| "strconv" | ||
| "time" | ||
|
|
||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||
| ) | ||
|
|
||
| const listPageSize = 1000 | ||
|
|
||
| // ListResponse 列表接口响应 | ||
| type ListResponse struct { | ||
| Files []FileItem `json:"files"` | ||
| Directories []string `json:"directories"` | ||
| } | ||
|
|
||
| type FileItem struct { | ||
| Name string `json:"name"` | ||
| Metadata map[string]interface{} `json:"metadata"` // 存储文件大小、哈希、时间戳等 | ||
| } | ||
|
|
||
| type apiError struct { | ||
| Error string `json:"error"` | ||
| Message string `json:"message"` | ||
| } | ||
|
|
||
| // standardUploadResp 标准上传成功返回的数组 | ||
| type standardUploadResp []struct { | ||
| Src string `json:"src"` | ||
| } | ||
|
|
||
| // hfGetUrlResp 获取 HF 直传授权地址的响应 | ||
| type hfGetUrlResp struct { | ||
| Success bool `json:"success"` | ||
| FullID string `json:"fullId"` | ||
| FilePath string `json:"filePath"` | ||
| ChannelName string `json:"channelName"` | ||
| Repo string `json:"repo"` | ||
| NeedsLfs bool `json:"needsLfs"` // 是否需要进行 LFS 物理上传 | ||
| AlreadyExists bool `json:"alreadyExists"` // 是否秒传成功 | ||
| Oid string `json:"oid"` // Git LFS 对象 ID (SHA256) | ||
| UploadAction *UploadAction `json:"uploadAction"` | ||
| } | ||
|
|
||
| type UploadAction struct { | ||
| Href string `json:"href"` | ||
| Header map[string]string `json:"header"` | ||
| } | ||
|
|
||
| type hfCommitResp struct { | ||
| Success bool `json:"success"` | ||
| Src string `json:"src"` | ||
| FileUrl string `json:"fileUrl"` | ||
| FullID string `json:"fullId"` | ||
| } | ||
|
|
||
| // 辅助函数:从 map 中安全提取字符串/数值 | ||
| func getString(m map[string]interface{}, keys ...string) string { | ||
| for _, k := range keys { | ||
| if v, ok := m[k]; ok { | ||
| switch val := v.(type) { | ||
| case string: | ||
| return val | ||
| case float64: | ||
| return strconv.FormatInt(int64(val), 10) | ||
| default: | ||
| return fmt.Sprintf("%v", val) | ||
| } | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| func getInt64(m map[string]interface{}, keys ...string) int64 { | ||
| for _, k := range keys { | ||
| if v, ok := m[k]; ok { | ||
| switch val := v.(type) { | ||
| case string: | ||
| n, _ := strconv.ParseInt(val, 10, 64) | ||
| return n | ||
| case float64: | ||
| return int64(val) | ||
| case int64: | ||
| return val | ||
| } | ||
| } | ||
| } | ||
| return 0 | ||
| } | ||
|
|
||
| func parseFile(item FileItem) *model.Object { | ||
| name := path.Base(item.Name) | ||
| var size int64 | ||
| var modTime time.Time | ||
|
|
||
| if item.Metadata != nil { | ||
| size = getInt64(item.Metadata, "FileSizeBytes", "File-Size") | ||
| ts := getInt64(item.Metadata, "TimeStamp") | ||
| if ts > 0 { | ||
| modTime = time.UnixMilli(ts) | ||
| } | ||
| } | ||
|
|
||
| return &model.Object{ | ||
| Path: item.Name, | ||
| Name: name, | ||
| Size: size, | ||
| Modified: modTime, | ||
| IsFolder: false, | ||
| } | ||
| } |
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.
Uh oh!
There was an error while loading. Please reload this page.