mirror of
https://github.com/AlistGo/alist.git
synced 2025-12-18 18:30:10 +08:00
add wps driver
This commit is contained in:
parent
3cddb6b7ed
commit
e37a8e9f93
@ -77,6 +77,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||
_ "github.com/alist-org/alist/v3/drivers/weiyun"
|
||||
_ "github.com/alist-org/alist/v3/drivers/wopan"
|
||||
_ "github.com/alist-org/alist/v3/drivers/wps"
|
||||
_ "github.com/alist-org/alist/v3/drivers/yandex_disk"
|
||||
)
|
||||
|
||||
|
||||
139
drivers/wps/driver.go
Normal file
139
drivers/wps/driver.go
Normal file
@ -0,0 +1,139 @@
|
||||
package wps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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 Wps struct {
|
||||
model.Storage
|
||||
Addition
|
||||
companyID string
|
||||
}
|
||||
|
||||
func (d *Wps) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Wps) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Wps) Init(ctx context.Context) error {
|
||||
if d.Cookie == "" {
|
||||
return fmt.Errorf("cookie is empty")
|
||||
}
|
||||
return d.ensureCompanyID(ctx)
|
||||
}
|
||||
|
||||
func (d *Wps) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Wps) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
basePath := "/"
|
||||
if dir != nil {
|
||||
if p := dir.GetPath(); p != "" {
|
||||
basePath = p
|
||||
}
|
||||
}
|
||||
node, err := d.resolvePath(ctx, basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node.kind == "root" {
|
||||
groups, err := d.getGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]model.Obj, 0, len(groups))
|
||||
for _, g := range groups {
|
||||
path := joinPath(basePath, g.Name)
|
||||
obj := &Obj{
|
||||
id: path,
|
||||
name: g.Name,
|
||||
ctime: parseTime(0),
|
||||
mtime: parseTime(0),
|
||||
isDir: true,
|
||||
path: path,
|
||||
}
|
||||
res = append(res, obj)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
if node.kind != "group" && node.kind != "folder" {
|
||||
return nil, nil
|
||||
}
|
||||
parentID := int64(0)
|
||||
if node.file != nil && node.kind == "folder" {
|
||||
parentID = node.file.ID
|
||||
}
|
||||
files, err := d.getFiles(ctx, node.group.GroupID, parentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]model.Obj, 0, len(files))
|
||||
for _, f := range files {
|
||||
res = append(res, fileToObj(basePath, f))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Wps) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
path := file.GetPath()
|
||||
node, err := d.resolvePath(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node.kind != "file" || node.file == nil {
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
if node.file.FilePerms.Download == 0 {
|
||||
return nil, fmt.Errorf("no download permission")
|
||||
}
|
||||
url := fmt.Sprintf("%s/3rd/drive/api/v5/groups/%d/files/%d/download?support_checksums=sha1", endpoint, node.group.GroupID, node.file.ID)
|
||||
var resp downloadResp
|
||||
_, err = d.request(ctx).SetResult(&resp).Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.URL == "" {
|
||||
return nil, fmt.Errorf("empty download url")
|
||||
}
|
||||
link := &model.Link{
|
||||
URL: resp.URL,
|
||||
Header: http.Header{},
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (d *Wps) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Wps) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Wps) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Wps) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Wps) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Wps) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Wps)(nil)
|
||||
30
drivers/wps/meta.go
Normal file
30
drivers/wps/meta.go
Normal file
@ -0,0 +1,30 @@
|
||||
package wps
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" required:"true" help:"kso_sid=xxxxx"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Wps",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Wps{}
|
||||
})
|
||||
}
|
||||
94
drivers/wps/types.go
Normal file
94
drivers/wps/types.go
Normal file
@ -0,0 +1,94 @@
|
||||
package wps
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
type workspaceResp struct {
|
||||
Companies []struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"companies"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
CompanyID int64 `json:"company_id"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type groupsResp struct {
|
||||
Groups []Group `json:"groups"`
|
||||
}
|
||||
|
||||
type filePerms struct {
|
||||
Download int `json:"download"`
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
GroupID int64 `json:"groupid"`
|
||||
ParentID int64 `json:"parentid"`
|
||||
Name string `json:"fname"`
|
||||
Size int64 `json:"fsize"`
|
||||
Type string `json:"ftype"`
|
||||
Ctime int64 `json:"ctime"`
|
||||
Mtime int64 `json:"mtime"`
|
||||
ID int64 `json:"id"`
|
||||
Deleted bool `json:"deleted"`
|
||||
FilePerms filePerms `json:"file_perms_acl"`
|
||||
}
|
||||
|
||||
type filesResp struct {
|
||||
Files []FileInfo `json:"files"`
|
||||
}
|
||||
|
||||
type downloadResp struct {
|
||||
URL string `json:"url"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
type Obj struct {
|
||||
id string
|
||||
name string
|
||||
size int64
|
||||
ctime time.Time
|
||||
mtime time.Time
|
||||
isDir bool
|
||||
hash utils.HashInfo
|
||||
path string
|
||||
canDownload bool
|
||||
}
|
||||
|
||||
func (o *Obj) GetSize() int64 {
|
||||
return o.size
|
||||
}
|
||||
|
||||
func (o *Obj) GetName() string {
|
||||
return o.name
|
||||
}
|
||||
|
||||
func (o *Obj) ModTime() time.Time {
|
||||
return o.mtime
|
||||
}
|
||||
|
||||
func (o *Obj) CreateTime() time.Time {
|
||||
return o.ctime
|
||||
}
|
||||
|
||||
func (o *Obj) IsDir() bool {
|
||||
return o.isDir
|
||||
}
|
||||
|
||||
func (o *Obj) GetHash() utils.HashInfo {
|
||||
return o.hash
|
||||
}
|
||||
|
||||
func (o *Obj) GetID() string {
|
||||
return o.id
|
||||
}
|
||||
|
||||
func (o *Obj) GetPath() string {
|
||||
return o.path
|
||||
}
|
||||
149
drivers/wps/util.go
Normal file
149
drivers/wps/util.go
Normal file
@ -0,0 +1,149 @@
|
||||
package wps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
const endpoint = "https://365.kdocs.cn"
|
||||
|
||||
type resolvedNode struct {
|
||||
kind string
|
||||
group Group
|
||||
file *FileInfo
|
||||
}
|
||||
|
||||
func (d *Wps) request(ctx context.Context) *resty.Request {
|
||||
return base.RestyClient.R().
|
||||
SetHeader("Cookie", d.Cookie).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetContext(ctx)
|
||||
}
|
||||
|
||||
func (d *Wps) ensureCompanyID(ctx context.Context) error {
|
||||
if d.companyID != "" {
|
||||
return nil
|
||||
}
|
||||
var resp workspaceResp
|
||||
_, err := d.request(ctx).SetResult(&resp).Get(endpoint + "/3rd/plussvr/compose/v1/users/self/workspaces?fields=name&comp_status=active")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.Companies) == 0 {
|
||||
return fmt.Errorf("no company id")
|
||||
}
|
||||
d.companyID = strconv.FormatInt(resp.Companies[0].ID, 10)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Wps) getGroups(ctx context.Context) ([]Group, error) {
|
||||
if err := d.ensureCompanyID(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp groupsResp
|
||||
url := fmt.Sprintf("%s/3rd/plus/groups/v1/companies/%s/users/self/groups/private", endpoint, d.companyID)
|
||||
_, err := d.request(ctx).SetResult(&resp).Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Groups, nil
|
||||
}
|
||||
|
||||
func (d *Wps) getFiles(ctx context.Context, groupID, parentID int64) ([]FileInfo, error) {
|
||||
var resp filesResp
|
||||
url := fmt.Sprintf("%s/3rd/drive/api/v5/groups/%d/files", endpoint, groupID)
|
||||
_, err := d.request(ctx).SetQueryParam("parentid", strconv.FormatInt(parentID, 10)).SetResult(&resp).Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Files, nil
|
||||
}
|
||||
|
||||
func parseTime(v int64) time.Time {
|
||||
if v <= 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Unix(v, 0)
|
||||
}
|
||||
|
||||
func joinPath(basePath, name string) string {
|
||||
if basePath == "" || basePath == "/" {
|
||||
return "/" + name
|
||||
}
|
||||
return strings.TrimRight(basePath, "/") + "/" + name
|
||||
}
|
||||
|
||||
func (d *Wps) resolvePath(ctx context.Context, path string) (*resolvedNode, error) {
|
||||
clean := strings.TrimSpace(path)
|
||||
if clean == "" {
|
||||
clean = "/"
|
||||
}
|
||||
clean = strings.Trim(clean, "/")
|
||||
if clean == "" {
|
||||
return &resolvedNode{kind: "root"}, nil
|
||||
}
|
||||
segs := strings.Split(clean, "/")
|
||||
groups, err := d.getGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var grp *Group
|
||||
for i := range groups {
|
||||
if groups[i].Name == segs[0] {
|
||||
grp = &groups[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if grp == nil {
|
||||
return nil, fmt.Errorf("group not found")
|
||||
}
|
||||
if len(segs) == 1 {
|
||||
return &resolvedNode{kind: "group", group: *grp}, nil
|
||||
}
|
||||
parentID := int64(0)
|
||||
var last FileInfo
|
||||
for i := 1; i < len(segs); i++ {
|
||||
files, err := d.getFiles(ctx, grp.GroupID, parentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var found *FileInfo
|
||||
for j := range files {
|
||||
if files[j].Name == segs[i] {
|
||||
found = &files[j]
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
return nil, fmt.Errorf("path not found")
|
||||
}
|
||||
last = *found
|
||||
parentID = found.ID
|
||||
}
|
||||
kind := "file"
|
||||
if last.Type == "folder" {
|
||||
kind = "folder"
|
||||
}
|
||||
return &resolvedNode{kind: kind, group: *grp, file: &last}, nil
|
||||
}
|
||||
|
||||
func fileToObj(basePath string, f FileInfo) *Obj {
|
||||
name := f.Name
|
||||
path := joinPath(basePath, name)
|
||||
return &Obj{
|
||||
id: path,
|
||||
name: name,
|
||||
size: f.Size,
|
||||
ctime: parseTime(f.Ctime),
|
||||
mtime: parseTime(f.Mtime),
|
||||
isDir: f.Type == "folder",
|
||||
path: path,
|
||||
canDownload: f.FilePerms.Download != 0,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user