-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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 6 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,228 @@ | ||
| package cloudflare_imgbed | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/OpenListTeam/OpenList/v4/drivers/base" | ||
| "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/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 | ||
| } | ||
|
|
||
| // Init 使用 base 包提供的工厂方法初始化 HTTP 客户端, | ||
| // 并设置 API 基础地址和鉴权请求头。 | ||
| func (d *CFImgBed) Init(ctx context.Context) error { | ||
| d.client = base.NewRestyClient() | ||
| d.client.SetBaseURL(strings.TrimRight(d.Address, "/")). | ||
| SetHeader("Authorization", "Bearer "+d.Token). | ||
| SetDebug(false) | ||
| return nil | ||
| } | ||
|
|
||
| func (d *CFImgBed) Drop(ctx context.Context) error { | ||
| return nil | ||
| } | ||
|
|
||
| // apiError 表示 CFImgBed API 返回的通用错误响应结构。 | ||
| type apiError struct { | ||
| Error string `json:"error"` | ||
| Message string `json:"message"` | ||
| } | ||
|
|
||
| // buildReqPath 根据挂载根路径和当前浏览目录,拼接出发送给 API 的请求路径。 | ||
| // | ||
| // OpenList 可能在两种场景下调用 List: | ||
| // 1. List(nil) — 首次加载挂载点根目录 | ||
| // 2. List(obj) — 用户点击进入某个子目录,obj 由上一次 List 返回 | ||
| // | ||
| // 当设置了 RootPath(如 "/telegram")时,OpenList 首次调用的 dir 对象 | ||
| // 的 GetPath() 可能已经等于 rootPath 本身,此时不应重复拼接前缀。 | ||
| func buildReqPath(rootPath, dirPath string) string { | ||
| rootPath = strings.Trim(rootPath, "/") | ||
| dirPath = strings.Trim(dirPath, "/") | ||
|
|
||
| if dirPath == "" || dirPath == rootPath { | ||
| // 正在浏览根目录,或 OpenList 传入了虚拟根目录对象 | ||
| return rootPath | ||
| } | ||
| if rootPath == "" { | ||
| // 未设置挂载前缀,直接使用目录路径 | ||
| return dirPath | ||
| } | ||
| // 正常子目录:在目录路径前补上挂载根路径 | ||
| return rootPath + "/" + dirPath | ||
| } | ||
|
|
||
| // List 获取指定目录下的文件和子目录列表。 | ||
| // | ||
| // 采用内部分页循环拉取,以防止单目录文件过多导致 API 响应超时或内存异常。 | ||
| // 每次请求 listPageSize 条记录,直到返回数量不足一页时退出循环, | ||
| // 最终将所有分页结果汇总后一次性返回给 OpenList。 | ||
| func (d *CFImgBed) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { | ||
| rootPath := strings.Trim(d.GetRootPath(), "/") | ||
|
|
||
| var dirPath string | ||
| if dir != nil { | ||
| dirPath = strings.Trim(dir.GetPath(), "/") | ||
| } | ||
| reqPath := buildReqPath(rootPath, dirPath) | ||
|
|
||
| // 用于去重:API 在分页时每个页面都可能重复返回相同的目录列表, | ||
| // 使用 map 确保同一个目录对象只被添加一次。 | ||
| dirSeen := make(map[string]bool) | ||
| objs := make([]model.Obj, 0) | ||
|
|
||
| // 分页拉取循环 | ||
| start := 0 | ||
| for { | ||
| var resp ListResponse | ||
| var errResp apiError | ||
| res, err := d.client.R(). | ||
| SetQueryParam("dir", reqPath). | ||
| SetQueryParam("start", fmt.Sprintf("%d", start)). | ||
| SetQueryParam("count", fmt.Sprintf("%d", listPageSize)). | ||
| SetResult(&resp). | ||
| SetError(&errResp). | ||
| Get("/api/manage/list") | ||
|
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if res.IsError() { | ||
| if errResp.Message != "" { | ||
| return nil, fmt.Errorf("CFImgBed API error: %s", errResp.Message) | ||
| } | ||
| return nil, fmt.Errorf("CFImgBed API returned status %d", res.StatusCode()) | ||
| } | ||
|
|
||
| // 裁剪 API 返回路径中的挂载根前缀, | ||
| // 使 GetPath() 返回的是相对于 OpenList 挂载点的路径,而非图床的绝对路径。 | ||
| for _, rawDir := range resp.Directories { | ||
| cleanDir := strings.TrimRight(rawDir, "/") | ||
| p := stripRootPrefix(cleanDir, rootPath) | ||
| // 目录去重:分页场景下不同页面可能返回相同的目录条目 | ||
| if !dirSeen[p] { | ||
| dirSeen[p] = true | ||
| objs = append(objs, parseDir(p)) | ||
| } | ||
| } | ||
|
|
||
| for _, item := range resp.Files { | ||
| p := stripRootPrefix(item.Name, rootPath) | ||
| objs = append(objs, parseFile(FileItem{ | ||
| Name: p, | ||
| Metadata: item.Metadata, | ||
| })) | ||
| } | ||
|
|
||
| // 判断是否已到最后一页:当返回的文件和目录总数小于请求的每页数量时, | ||
| // 说明本页已经是最后一页,无需继续请求。 | ||
| fetched := len(resp.Files) + len(resp.Directories) | ||
| if fetched < listPageSize { | ||
| break | ||
| } | ||
|
|
||
| start += listPageSize | ||
| } | ||
|
|
||
| return objs, nil | ||
| } | ||
|
|
||
| // stripRootPrefix 移除 API 返回路径中的挂载根前缀。 | ||
| // 如果未设置 rootPath 或路径不以 rootPath/ 开头,则原样返回。 | ||
| func stripRootPrefix(p, rootPath string) string { | ||
| if rootPath == "" { | ||
| return p | ||
| } | ||
| prefix := rootPath + "/" | ||
| if strings.HasPrefix(p, prefix) { | ||
| return strings.TrimPrefix(p, prefix) | ||
| } | ||
| return p | ||
| } | ||
|
|
||
| // Link 拼装文件的直接下载/访问链接。 | ||
| // 路径中可能包含空格、中文、#、+ 等特殊字符,必须进行安全编码以生成有效 URL。 | ||
| func (d *CFImgBed) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { | ||
| rootPath := strings.Trim(d.GetRootPath(), "/") | ||
| filePath := strings.Trim(file.GetPath(), "/") | ||
|
|
||
| // 拼接完整路径,避免出现双斜杠 | ||
| var fullPath string | ||
| if rootPath != "" && filePath != "" { | ||
| fullPath = rootPath + "/" + filePath | ||
| } else if rootPath != "" { | ||
| fullPath = rootPath | ||
| } else { | ||
| fullPath = filePath | ||
| } | ||
|
|
||
| // 对路径进行安全编码,处理空格、特殊字符等可能导致链接失效的情况 | ||
| link := strings.TrimRight(d.Address, "/") + "/file/" + utils.EncodePath(fullPath) | ||
| return &model.Link{URL: link}, nil | ||
| } | ||
|
|
||
| func (d *CFImgBed) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) Remove(ctx context.Context, obj model.Obj) error { | ||
| return errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| func (d *CFImgBed) GetDetails(ctx context.Context) (*model.StorageDetails, error) { | ||
| return nil, errs.NotImplement | ||
| } | ||
|
|
||
| // 编译时检查 CFImgBed 是否完整实现 driver.Driver 接口。 | ||
| 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,33 @@ | ||
| package cloudflare_imgbed | ||
|
|
||
| import ( | ||
| "github.com/OpenListTeam/OpenList/v4/internal/driver" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/op" | ||
| ) | ||
|
|
||
| // Addition 定义驱动在 OpenList 前端管理界面中显示的表单配置项。 | ||
| type Addition struct { | ||
| driver.RootPath | ||
| Address string `json:"address" type:"text" required:"true" default:"" help:"API domain, https://img.example.com"` | ||
| Token string `json:"token" type:"text" required:"true" default:"" help:"API authentication token"` | ||
| } | ||
|
|
||
| var config = driver.Config{ | ||
| Name: "cloudflare_imgbed", | ||
| LocalSort: false, | ||
| OnlyProxy: false, | ||
| NoCache: false, | ||
| NoUpload: true, | ||
| NeedMs: false, | ||
| DefaultRoot: "/", | ||
| CheckStatus: false, | ||
| Alert: "", | ||
| NoOverwriteUpload: false, | ||
| NoLinkURL: false, | ||
| } | ||
|
|
||
| func init() { | ||
| op.RegisterDriver(func() driver.Driver { | ||
| return &CFImgBed{} | ||
| }) | ||
| } |
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.