Repository: AlistGo/alist (language: go)

Issue:
v3版本增加添加其他Alist支持
### Please make sure of the following things

- [x] I have read the [documentation](https://alist.nn.ci).
- [x] I'm sure there are no duplicate issues or discussions.
- [x] I'm sure this feature is not implemented.
- [x] I'm sure it's a reasonable and popular requirement.

### Description of the feature / 需求描述

v2版本（2.6.4）是支持添加另一个alist地址的，不过v3似乎找不到添加其他alist的选项，希望可以添加回来。

### Suggested solution / 实现思路

_No response_

### Additional context / 附件

_No response_


Discussion / hints:
Thanks for opening your first issue here! Be sure to follow the issue template!

也许可以考虑通过WebDAV挂载别的alist？
> 也许可以考虑通过WebDAV挂载别的alist？
只要对方支持就可以挂载，不过挂载后不稳定。

Fix patch:
diff --git a/drivers/alist_v3/driver.go b/drivers/alist_v3/driver.go
new file mode 100644
index 00000000000..bb61172c181
--- /dev/null
+++ b/drivers/alist_v3/driver.go
@@ -0,0 +1,128 @@
+package alist_v3
+
+import (
+	"context"
+
+	"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/alist-org/alist/v3/server/common"
+	"github.com/alist-org/alist/v3/server/handles"
+)
+
+type AListV3 struct {
+	model.Storage
+	Addition
+}
+
+func (d *AListV3) Config() driver.Config {
+	return config
+}
+
+func (d *AListV3) GetAddition() driver.Additional {
+	return d.Addition
+}
+
+func (d *AListV3) Init(ctx context.Context, storage model.Storage) error {
+	d.Storage = storage
+	err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition)
+	if err != nil {
+		return err
+	}
+	// TODO login / refresh token
+	//op.MustSaveDriverStorage(d)
+	return err
+}
+
+func (d *AListV3) Drop(ctx context.Context) error {
+	return nil
+}
+
+func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
+	url := d.Address + "/api/fs/list"
+	var resp common.Resp[handles.FsListResp]
+	_, err := base.RestyClient.R().
+		SetResult(&resp).
+		SetHeader("Authorization", d.AccessToken).
+		SetBody(handles.ListReq{
+			PageReq: common.PageReq{
+				Page:    1,
+				PerPage: 0,
+			},
+			Path:     dir.GetPath(),
+			Password: d.Password,
+			Refresh:  false,
+		}).Post(url)
+	if err != nil {
+		return nil, err
+	}
+	var files []model.Obj
+	for _, f := range resp.Data.Content {
+		file := model.ObjThumb{
+			Object: model.Object{
+				Name:     f.Name,
+				Modified: f.Modified,
+				Size:     f.Size,
+				IsFolder: f.IsDir,
+			},
+			Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
+		}
+		files = append(files, &file)
+	}
+	return files, nil
+}
+
+//func (d *AList) Get(ctx context.Context, path string) (model.Obj, error) {
+//	// this is optional
+//	return nil, errs.NotImplement
+//}
+
+func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
+	url := d.Address + "/api/fs/get"
+	var resp common.Resp[handles.FsGetResp]
+	_, err := base.RestyClient.R().
+		SetResult(&resp).
+		SetHeader("Authorization", d.AccessToken).
+		SetBody(handles.FsGetReq{
+			Path:     file.GetPath(),
+			Password: d.Password,
+		}).Post(url)
+	if err != nil {
+		return nil, err
+	}
+	return &model.Link{
+		URL: resp.Data.RawURL,
+	}, nil
+}
+
+func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
+	return errs.NotImplement
+}
+
+func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
+	return errs.NotImplement
+}
+
+func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
+	return errs.NotImplement
+}
+
+func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
+	return errs.NotImplement
+}
+
+func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
+	return errs.NotImplement
+}
+
+func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
+	return errs.NotImplement
+}
+
+//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
+//	return nil, errs.NotSupport
+//}
+
+var _ driver.Driver = (*AListV3)(nil)
diff --git a/drivers/alist_v3/meta.go b/drivers/alist_v3/meta.go
new file mode 100644
index 00000000000..d9ae7412a7a
--- /dev/null
+++ b/drivers/alist_v3/meta.go
@@ -0,0 +1,26 @@
+package alist_v3
+
+import (
+	"github.com/alist-org/alist/v3/internal/driver"
+	"github.com/alist-org/alist/v3/internal/op"
+)
+
+type Addition struct {
+	driver.RootPath
+	Address     string `json:"url" required:"true"`
+	Password    string `json:"password"`
+	AccessToken string `json:"access_token"`
+}
+
+var config = driver.Config{
+	Name:        "AList V3",
+	LocalSort:   true,
+	NoUpload:    true,
+	DefaultRoot: "/",
+}
+
+func init() {
+	op.RegisterDriver(config, func() driver.Driver {
+		return &AListV3{}
+	})
+}
diff --git a/drivers/alist_v3/types.go b/drivers/alist_v3/types.go
new file mode 100644
index 00000000000..d86e5347cad
--- /dev/null
+++ b/drivers/alist_v3/types.go
@@ -0,0 +1,1 @@
+package alist_v3
diff --git a/drivers/alist_v3/util.go b/drivers/alist_v3/util.go
new file mode 100644
index 00000000000..d86e5347cad
--- /dev/null
+++ b/drivers/alist_v3/util.go
@@ -0,0 +1,1 @@
+package alist_v3
diff --git a/drivers/all.go b/drivers/all.go
index bbfbf537b35..60ea6b70bc7 100644
--- a/drivers/all.go
+++ b/drivers/all.go
@@ -5,6 +5,7 @@ import (
 	_ "github.com/alist-org/alist/v3/drivers/139"
 	_ "github.com/alist-org/alist/v3/drivers/189"
 	_ "github.com/alist-org/alist/v3/drivers/189pc"
+	_ "github.com/alist-org/alist/v3/drivers/alist_v3"
 	_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
 	_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
 	_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
diff --git a/server/common/common.go b/server/common/common.go
index b78466fad72..56b0a701352 100644
--- a/server/common/common.go
+++ b/server/common/common.go
@@ -28,7 +28,7 @@ func ErrorResp(c *gin.Context, err error, code int, l ...bool) {
 			log.Errorf("%v", err)
 		}
 	}
-	c.JSON(200, Resp{
+	c.JSON(200, Resp[interface{}]{
 		Code:    code,
 		Message: hidePrivacy(err.Error()),
 		Data:    nil,
@@ -40,7 +40,7 @@ func ErrorStrResp(c *gin.Context, str string, code int, l ...bool) {
 	if len(l) != 0 && l[0] {
 		log.Error(str)
 	}
-	c.JSON(200, Resp{
+	c.JSON(200, Resp[interface{}]{
 		Code:    code,
 		Message: hidePrivacy(str),
 		Data:    nil,
@@ -50,14 +50,14 @@ func ErrorStrResp(c *gin.Context, str string, code int, l ...bool) {
 
 func SuccessResp(c *gin.Context, data ...interface{}) {
 	if len(data) == 0 {
-		c.JSON(200, Resp{
+		c.JSON(200, Resp[interface{}]{
 			Code:    200,
 			Message: "success",
 			Data:    nil,
 		})
 		return
 	}
-	c.JSON(200, Resp{
+	c.JSON(200, Resp[interface{}]{
 		Code:    200,
 		Message: "success",
 		Data:    data[0],
diff --git a/server/common/resp.go b/server/common/resp.go
index df35778307b..53308339aa6 100644
--- a/server/common/resp.go
+++ b/server/common/resp.go
@@ -1,9 +1,9 @@
 package common
 
-type Resp struct {
-	Code    int         `json:"code"`
-	Message string      `json:"message"`
-	Data    interface{} `json:"data"`
+type Resp[T any] struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+	Data    T      `json:"data"`
 }
 
 type PageResp struct {


Test patch: