mirror of
https://github.com/AlistGo/alist.git
synced 2025-12-19 02:50:06 +08:00
feat(driver): Added support for Gitee driver (#9368)
* feat(driver): Added support for Gitee driver - Implemented core driver functions including initialization, file listing, and file linking - Added Gitee-specific API interaction and object mapping - Registered Gitee driver in the driver registry * feat(driver): Added cookie-based authentication support for Gitee driver - Extended request handling to include `Cookie` header if provided - Updated metadata to include `cookie` field with appropriate documentation - Adjusted file link generation to propagate `Cookie` headers in requests
This commit is contained in:
parent
b4d9beb49c
commit
0cbc7ebc92
@ -31,6 +31,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/dropbox"
|
_ "github.com/alist-org/alist/v3/drivers/dropbox"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/febbox"
|
_ "github.com/alist-org/alist/v3/drivers/febbox"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/gitee"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/github"
|
_ "github.com/alist-org/alist/v3/drivers/github"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/github_releases"
|
_ "github.com/alist-org/alist/v3/drivers/github_releases"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/gofile"
|
_ "github.com/alist-org/alist/v3/drivers/gofile"
|
||||||
|
|||||||
224
drivers/gitee/driver.go
Normal file
224
drivers/gitee/driver.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package gitee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"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/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gitee struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
client *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) Init(ctx context.Context) error {
|
||||||
|
d.RootFolderPath = utils.FixAndCleanPath(d.RootFolderPath)
|
||||||
|
d.Endpoint = strings.TrimSpace(d.Endpoint)
|
||||||
|
if d.Endpoint == "" {
|
||||||
|
d.Endpoint = "https://gitee.com/api/v5"
|
||||||
|
}
|
||||||
|
d.Endpoint = strings.TrimSuffix(d.Endpoint, "/")
|
||||||
|
d.Owner = strings.TrimSpace(d.Owner)
|
||||||
|
d.Repo = strings.TrimSpace(d.Repo)
|
||||||
|
d.Token = strings.TrimSpace(d.Token)
|
||||||
|
d.DownloadProxy = strings.TrimSpace(d.DownloadProxy)
|
||||||
|
if d.Owner == "" || d.Repo == "" {
|
||||||
|
return errors.New("owner and repo are required")
|
||||||
|
}
|
||||||
|
d.client = base.NewRestyClient().
|
||||||
|
SetBaseURL(d.Endpoint).
|
||||||
|
SetHeader("Accept", "application/json")
|
||||||
|
repo, err := d.getRepo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Ref = strings.TrimSpace(d.Ref)
|
||||||
|
if d.Ref == "" {
|
||||||
|
d.Ref = repo.DefaultBranch
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
relPath := d.relativePath(dir.GetPath())
|
||||||
|
contents, err := d.listContents(relPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs := make([]model.Obj, 0, len(contents))
|
||||||
|
for i := range contents {
|
||||||
|
objs = append(objs, contents[i].toModelObj())
|
||||||
|
}
|
||||||
|
return objs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
var downloadURL string
|
||||||
|
if obj, ok := file.(*Object); ok {
|
||||||
|
downloadURL = obj.DownloadURL
|
||||||
|
if downloadURL == "" {
|
||||||
|
relPath := d.relativePath(file.GetPath())
|
||||||
|
content, err := d.getContent(relPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if content.DownloadURL == "" {
|
||||||
|
return nil, errors.New("empty download url")
|
||||||
|
}
|
||||||
|
obj.DownloadURL = content.DownloadURL
|
||||||
|
downloadURL = content.DownloadURL
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
relPath := d.relativePath(file.GetPath())
|
||||||
|
content, err := d.getContent(relPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if content.DownloadURL == "" {
|
||||||
|
return nil, errors.New("empty download url")
|
||||||
|
}
|
||||||
|
downloadURL = content.DownloadURL
|
||||||
|
}
|
||||||
|
url := d.applyProxy(downloadURL)
|
||||||
|
return &model.Link{
|
||||||
|
URL: url,
|
||||||
|
Header: http.Header{
|
||||||
|
"Cookie": {d.Cookie},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) newRequest() *resty.Request {
|
||||||
|
req := d.client.R()
|
||||||
|
if d.Token != "" {
|
||||||
|
req.SetQueryParam("access_token", d.Token)
|
||||||
|
}
|
||||||
|
if d.Ref != "" {
|
||||||
|
req.SetQueryParam("ref", d.Ref)
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) apiPath(path string) string {
|
||||||
|
escapedOwner := url.PathEscape(d.Owner)
|
||||||
|
escapedRepo := url.PathEscape(d.Repo)
|
||||||
|
if path == "" {
|
||||||
|
return fmt.Sprintf("/repos/%s/%s/contents", escapedOwner, escapedRepo)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/repos/%s/%s/contents/%s", escapedOwner, escapedRepo, encodePath(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) listContents(path string) ([]Content, error) {
|
||||||
|
res, err := d.newRequest().Get(d.apiPath(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.IsError() {
|
||||||
|
return nil, toErr(res)
|
||||||
|
}
|
||||||
|
var contents []Content
|
||||||
|
if err := utils.Json.Unmarshal(res.Body(), &contents); err != nil {
|
||||||
|
var single Content
|
||||||
|
if err2 := utils.Json.Unmarshal(res.Body(), &single); err2 == nil && single.Type != "" {
|
||||||
|
if single.Type != "dir" {
|
||||||
|
return nil, errs.NotFolder
|
||||||
|
}
|
||||||
|
return []Content{}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range contents {
|
||||||
|
contents[i].Path = joinPath(path, contents[i].Name)
|
||||||
|
}
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) getContent(path string) (*Content, error) {
|
||||||
|
res, err := d.newRequest().Get(d.apiPath(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.IsError() {
|
||||||
|
return nil, toErr(res)
|
||||||
|
}
|
||||||
|
var content Content
|
||||||
|
if err := utils.Json.Unmarshal(res.Body(), &content); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if content.Type == "" {
|
||||||
|
return nil, errors.New("invalid response")
|
||||||
|
}
|
||||||
|
if content.Path == "" {
|
||||||
|
content.Path = path
|
||||||
|
}
|
||||||
|
return &content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) relativePath(full string) string {
|
||||||
|
full = utils.FixAndCleanPath(full)
|
||||||
|
root := utils.FixAndCleanPath(d.RootFolderPath)
|
||||||
|
if root == "/" {
|
||||||
|
return strings.TrimPrefix(full, "/")
|
||||||
|
}
|
||||||
|
if utils.PathEqual(full, root) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
prefix := utils.PathAddSeparatorSuffix(root)
|
||||||
|
if strings.HasPrefix(full, prefix) {
|
||||||
|
return strings.TrimPrefix(full, prefix)
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(full, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gitee) applyProxy(raw string) string {
|
||||||
|
if raw == "" || d.DownloadProxy == "" {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
proxy := d.DownloadProxy
|
||||||
|
if !strings.HasSuffix(proxy, "/") {
|
||||||
|
proxy += "/"
|
||||||
|
}
|
||||||
|
return proxy + strings.TrimLeft(raw, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodePath(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parts := strings.Split(p, "/")
|
||||||
|
for i, part := range parts {
|
||||||
|
parts[i] = url.PathEscape(part)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinPath(base, name string) string {
|
||||||
|
if base == "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(stdpath.Join(base, name), "./")
|
||||||
|
}
|
||||||
29
drivers/gitee/meta.go
Normal file
29
drivers/gitee/meta.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package gitee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootPath
|
||||||
|
Endpoint string `json:"endpoint" type:"string" help:"Gitee API endpoint, default https://gitee.com/api/v5"`
|
||||||
|
Token string `json:"token" type:"string"`
|
||||||
|
Owner string `json:"owner" type:"string" required:"true"`
|
||||||
|
Repo string `json:"repo" type:"string" required:"true"`
|
||||||
|
Ref string `json:"ref" type:"string" help:"Branch, tag or commit SHA, defaults to repository default branch"`
|
||||||
|
DownloadProxy string `json:"download_proxy" type:"string" help:"Prefix added before download URLs, e.g. https://mirror.example.com/"`
|
||||||
|
Cookie string `json:"cookie" type:"string" help:"Cookie returned from user info request"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "Gitee",
|
||||||
|
LocalSort: true,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Gitee{}
|
||||||
|
})
|
||||||
|
}
|
||||||
60
drivers/gitee/types.go
Normal file
60
drivers/gitee/types.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package gitee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Links struct {
|
||||||
|
Self string `json:"self"`
|
||||||
|
Html string `json:"html"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Size *int64 `json:"size"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Sha string `json:"sha"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
HtmlURL string `json:"html_url"`
|
||||||
|
DownloadURL string `json:"download_url"`
|
||||||
|
Links Links `json:"_links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Content) toModelObj() model.Obj {
|
||||||
|
size := int64(0)
|
||||||
|
if c.Size != nil {
|
||||||
|
size = *c.Size
|
||||||
|
}
|
||||||
|
return &Object{
|
||||||
|
Object: model.Object{
|
||||||
|
ID: c.Path,
|
||||||
|
Name: c.Name,
|
||||||
|
Size: size,
|
||||||
|
Modified: time.Unix(0, 0),
|
||||||
|
IsFolder: c.Type == "dir",
|
||||||
|
},
|
||||||
|
DownloadURL: c.DownloadURL,
|
||||||
|
HtmlURL: c.HtmlURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object struct {
|
||||||
|
model.Object
|
||||||
|
DownloadURL string
|
||||||
|
HtmlURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Object) URL() string {
|
||||||
|
return o.DownloadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
DefaultBranch string `json:"default_branch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrResp struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
44
drivers/gitee/util.go
Normal file
44
drivers/gitee/util.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package gitee
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Gitee) getRepo() (*Repo, error) {
|
||||||
|
req := d.client.R()
|
||||||
|
if d.Token != "" {
|
||||||
|
req.SetQueryParam("access_token", d.Token)
|
||||||
|
}
|
||||||
|
if d.Cookie != "" {
|
||||||
|
req.SetHeader("Cookie", d.Cookie)
|
||||||
|
}
|
||||||
|
escapedOwner := url.PathEscape(d.Owner)
|
||||||
|
escapedRepo := url.PathEscape(d.Repo)
|
||||||
|
res, err := req.Get(fmt.Sprintf("/repos/%s/%s", escapedOwner, escapedRepo))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.IsError() {
|
||||||
|
return nil, toErr(res)
|
||||||
|
}
|
||||||
|
var repo Repo
|
||||||
|
if err := utils.Json.Unmarshal(res.Body(), &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if repo.DefaultBranch == "" {
|
||||||
|
return nil, fmt.Errorf("failed to fetch default branch")
|
||||||
|
}
|
||||||
|
return &repo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toErr(res *resty.Response) error {
|
||||||
|
var errMsg ErrResp
|
||||||
|
if err := utils.Json.Unmarshal(res.Body(), &errMsg); err == nil && errMsg.Message != "" {
|
||||||
|
return fmt.Errorf("%s: %s", res.Status(), errMsg.Message)
|
||||||
|
}
|
||||||
|
return fmt.Errorf(res.Status())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user