mirror of https://github.com/Xhofe/alist
feat(misskey): add misskey driver (#7864)
parent
bdd9774aa7
commit
fd51f34efa
|
@ -37,6 +37,7 @@ import (
|
||||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/mega"
|
_ "github.com/alist-org/alist/v3/drivers/mega"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/misskey"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/mopan"
|
_ "github.com/alist-org/alist/v3/drivers/mopan"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/netease_music"
|
_ "github.com/alist-org/alist/v3/drivers/netease_music"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package misskey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Misskey struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Init(ctx context.Context) error {
|
||||||
|
d.Endpoint = strings.TrimSuffix(d.Endpoint, "/")
|
||||||
|
if d.Endpoint == "" || d.AccessToken == "" {
|
||||||
|
return errs.EmptyToken
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
return d.list(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
return d.link(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||||
|
return d.makeDir(parentDir, dirName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
return d.move(srcObj, dstDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
||||||
|
return d.rename(srcObj, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
return d.copy(srcObj, dstDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
return d.remove(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||||
|
return d.put(dstDir, stream, up)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
// return nil, errs.NotSupport
|
||||||
|
//}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Misskey)(nil)
|
|
@ -0,0 +1,35 @@
|
||||||
|
package misskey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
// Usually one of two
|
||||||
|
driver.RootPath
|
||||||
|
// define other
|
||||||
|
// Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"`
|
||||||
|
Endpoint string `json:"endpoint" required:"true" default:"https://misskey.io"`
|
||||||
|
AccessToken string `json:"access_token" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "Misskey",
|
||||||
|
LocalSort: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: false,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
CheckStatus: false,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Misskey{}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package misskey
|
||||||
|
|
||||||
|
type Resp struct {
|
||||||
|
Code int
|
||||||
|
Raw []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Properties struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MFile struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
MD5 string `json:"md5"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
IsSensitive bool `json:"isSensitive"`
|
||||||
|
Blurhash string `json:"blurhash"`
|
||||||
|
Properties Properties `json:"properties"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
ThumbnailURL string `json:"thumbnailUrl"`
|
||||||
|
Comment *string `json:"comment"`
|
||||||
|
FolderID *string `json:"folderId"`
|
||||||
|
Folder MFolder `json:"folder"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MFolder struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ParentID *string `json:"parentId"`
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
package misskey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base layer methods
|
||||||
|
|
||||||
|
func (d *Misskey) request(path, method string, callback base.ReqCallback, resp interface{}) error {
|
||||||
|
url := d.Endpoint + "/api/drive" + path
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
|
||||||
|
req.SetAuthToken(d.AccessToken).SetHeader("Content-Type", "application/json")
|
||||||
|
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
} else {
|
||||||
|
req.SetBody("{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetResult(resp)
|
||||||
|
|
||||||
|
// 启用调试模式
|
||||||
|
req.EnableTrace()
|
||||||
|
|
||||||
|
response, err := req.Execute(method, url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.IsSuccess() {
|
||||||
|
return errors.New(response.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) getThumb(ctx context.Context, obj model.Obj) (io.Reader, error) {
|
||||||
|
// TODO return the thumb of obj, optional
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBody(body interface{}) base.ReqCallback {
|
||||||
|
return func(req *resty.Request) {
|
||||||
|
req.SetBody(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFolderId(dir model.Obj) interface{} {
|
||||||
|
if dir.GetID() == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dir.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// API layer methods
|
||||||
|
|
||||||
|
func (d *Misskey) getFiles(dir model.Obj) ([]model.Obj, error) {
|
||||||
|
var files []MFile
|
||||||
|
var body map[string]string
|
||||||
|
if dir.GetPath() != "/" {
|
||||||
|
body = map[string]string{"folderId": dir.GetID()}
|
||||||
|
} else {
|
||||||
|
body = map[string]string{}
|
||||||
|
}
|
||||||
|
err := d.request("/files", "POST", setBody(body), &files)
|
||||||
|
if err != nil {
|
||||||
|
return []model.Obj{}, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(files, func(src MFile) (model.Obj, error) {
|
||||||
|
return mFile2Object(src), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) getFolders(dir model.Obj) ([]model.Obj, error) {
|
||||||
|
var folders []MFolder
|
||||||
|
var body map[string]string
|
||||||
|
if dir.GetPath() != "/" {
|
||||||
|
body = map[string]string{"folderId": dir.GetID()}
|
||||||
|
} else {
|
||||||
|
body = map[string]string{}
|
||||||
|
}
|
||||||
|
err := d.request("/folders", "POST", setBody(body), &folders)
|
||||||
|
if err != nil {
|
||||||
|
return []model.Obj{}, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(folders, func(src MFolder) (model.Obj, error) {
|
||||||
|
return mFolder2Object(src), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) list(dir model.Obj) ([]model.Obj, error) {
|
||||||
|
files, _ := d.getFiles(dir)
|
||||||
|
folders, _ := d.getFolders(dir)
|
||||||
|
return append(files, folders...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) link(file model.Obj) (*model.Link, error) {
|
||||||
|
var mFile MFile
|
||||||
|
err := d.request("/files/show", "POST", setBody(map[string]string{"fileId": file.GetID()}), &mFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.Link{
|
||||||
|
URL: mFile.URL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) makeDir(parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||||
|
var folder MFolder
|
||||||
|
err := d.request("/folders/create", "POST", setBody(map[string]interface{}{"parentId": handleFolderId(parentDir), "name": dirName}), &folder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mFolder2Object(folder), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) move(srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
if srcObj.IsDir() {
|
||||||
|
var folder MFolder
|
||||||
|
err := d.request("/folders/update", "POST", setBody(map[string]interface{}{"folderId": srcObj.GetID(), "parentId": handleFolderId(dstDir)}), &folder)
|
||||||
|
return mFolder2Object(folder), err
|
||||||
|
} else {
|
||||||
|
var file MFile
|
||||||
|
err := d.request("/files/update", "POST", setBody(map[string]interface{}{"fileId": srcObj.GetID(), "folderId": handleFolderId(dstDir)}), &file)
|
||||||
|
return mFile2Object(file), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) rename(srcObj model.Obj, newName string) (model.Obj, error) {
|
||||||
|
if srcObj.IsDir() {
|
||||||
|
var folder MFolder
|
||||||
|
err := d.request("/folders/update", "POST", setBody(map[string]string{"folderId": srcObj.GetID(), "name": newName}), &folder)
|
||||||
|
return mFolder2Object(folder), err
|
||||||
|
} else {
|
||||||
|
var file MFile
|
||||||
|
err := d.request("/files/update", "POST", setBody(map[string]string{"fileId": srcObj.GetID(), "name": newName}), &file)
|
||||||
|
return mFile2Object(file), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) copy(srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
if srcObj.IsDir() {
|
||||||
|
folder, err := d.makeDir(dstDir, srcObj.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err := d.list(srcObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, obj := range list {
|
||||||
|
_, err := d.copy(obj, folder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return folder, nil
|
||||||
|
} else {
|
||||||
|
var file MFile
|
||||||
|
url, err := d.link(srcObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = d.request("/files/upload-from-url", "POST", setBody(map[string]interface{}{"url": url.URL, "folderId": handleFolderId(dstDir)}), &file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mFile2Object(file), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) remove(obj model.Obj) error {
|
||||||
|
if obj.IsDir() {
|
||||||
|
err := d.request("/folders/delete", "POST", setBody(map[string]string{"folderId": obj.GetID()}), nil)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
err := d.request("/files/delete", "POST", setBody(map[string]string{"fileId": obj.GetID()}), nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Misskey) put(dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||||
|
var file MFile
|
||||||
|
|
||||||
|
fileContent, err := io.ReadAll(stream)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := base.RestyClient.R().
|
||||||
|
SetFileReader("file", stream.GetName(), io.NopCloser(bytes.NewReader(fileContent))).
|
||||||
|
SetFormData(map[string]string{
|
||||||
|
"folderId": handleFolderId(dstDir).(string),
|
||||||
|
"name": stream.GetName(),
|
||||||
|
"comment": "",
|
||||||
|
"isSensitive": "false",
|
||||||
|
"force": "false",
|
||||||
|
}).
|
||||||
|
SetResult(&file).SetAuthToken(d.AccessToken)
|
||||||
|
|
||||||
|
resp, err := req.Post(d.Endpoint + "/api/drive/files/create")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !resp.IsSuccess() {
|
||||||
|
return nil, errors.New(resp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFile2Object(file), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mFile2Object(file MFile) *model.ObjThumbURL {
|
||||||
|
ctime, err := time.Parse(time.RFC3339, file.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
ctime = time.Time{}
|
||||||
|
}
|
||||||
|
return &model.ObjThumbURL{
|
||||||
|
Object: model.Object{
|
||||||
|
ID: file.ID,
|
||||||
|
Name: file.Name,
|
||||||
|
Ctime: ctime,
|
||||||
|
IsFolder: false,
|
||||||
|
Size: file.Size,
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{
|
||||||
|
Thumbnail: file.ThumbnailURL,
|
||||||
|
},
|
||||||
|
Url: model.Url{
|
||||||
|
Url: file.URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mFolder2Object(folder MFolder) *model.Object {
|
||||||
|
ctime, err := time.Parse(time.RFC3339, folder.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
ctime = time.Time{}
|
||||||
|
}
|
||||||
|
return &model.Object{
|
||||||
|
ID: folder.ID,
|
||||||
|
Name: folder.Name,
|
||||||
|
Ctime: ctime,
|
||||||
|
IsFolder: true,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue