diff --git a/drivers/123_link/driver.go b/drivers/123_link/driver.go new file mode 100644 index 00000000000..46cdcbae482 --- /dev/null +++ b/drivers/123_link/driver.go @@ -0,0 +1,77 @@ +package _123Link + +import ( + "context" + stdpath "path" + "time" + + "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" +) + +type Pan123Link struct { + model.Storage + Addition + root *Node +} + +func (d *Pan123Link) Config() driver.Config { + return config +} + +func (d *Pan123Link) GetAddition() driver.Additional { + return &d.Addition +} + +func (d *Pan123Link) Init(ctx context.Context) error { + node, err := BuildTree(d.OriginURLs) + if err != nil { + return err + } + node.calSize() + d.root = node + return nil +} + +func (d *Pan123Link) Drop(ctx context.Context) error { + return nil +} + +func (d *Pan123Link) Get(ctx context.Context, path string) (model.Obj, error) { + node := GetNodeFromRootByPath(d.root, path) + return nodeToObj(node, path) +} + +func (d *Pan123Link) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + node := GetNodeFromRootByPath(d.root, dir.GetPath()) + if node == nil { + return nil, errs.ObjectNotFound + } + if node.isFile() { + return nil, errs.NotFolder + } + return utils.SliceConvert(node.Children, func(node *Node) (model.Obj, error) { + return nodeToObj(node, stdpath.Join(dir.GetPath(), node.Name)) + }) +} + +func (d *Pan123Link) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + node := GetNodeFromRootByPath(d.root, file.GetPath()) + if node == nil { + return nil, errs.ObjectNotFound + } + if node.isFile() { + signUrl, err := SignURL(node.Url, d.PrivateKey, d.UID, time.Duration(d.ValidDuration)*time.Minute) + if err != nil { + return nil, err + } + return &model.Link{ + URL: signUrl, + }, nil + } + return nil, errs.NotFile +} + +var _ driver.Driver = (*Pan123Link)(nil) diff --git a/drivers/123_link/meta.go b/drivers/123_link/meta.go new file mode 100644 index 00000000000..9f357628691 --- /dev/null +++ b/drivers/123_link/meta.go @@ -0,0 +1,23 @@ +package _123Link + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + OriginURLs string `json:"origin_urls" type:"text" required:"true" default:"https://vip.123pan.com/29/folder/file.mp3" help:"structure:FolderName:\n [FileSize:][Modified:]Url"` + PrivateKey string `json:"private_key"` + UID uint64 `json:"uid" type:"number"` + ValidDuration int64 `json:"valid_duration" type:"number" default:"30" help:"minutes"` +} + +var config = driver.Config{ + Name: "123PanLink", +} + +func init() { + op.RegisterDriver(func() driver.Driver { + return &Pan123Link{} + }) +} diff --git a/drivers/123_link/parse.go b/drivers/123_link/parse.go new file mode 100644 index 00000000000..8d6c3a13d02 --- /dev/null +++ b/drivers/123_link/parse.go @@ -0,0 +1,152 @@ +package _123Link + +import ( + "fmt" + url2 "net/url" + stdpath "path" + "strconv" + "strings" + "time" +) + +// build tree from text, text structure definition: +/** + * FolderName: + * [FileSize:][Modified:]Url + */ +/** + * For example: + * folder1: + * name1:url1 + * url2 + * folder2: + * url3 + * url4 + * url5 + * folder3: + * url6 + * url7 + * url8 + */ +// if there are no name, use the last segment of url as name +func BuildTree(text string) (*Node, error) { + lines := strings.Split(text, "\n") + var root = &Node{Level: -1, Name: "root"} + stack := []*Node{root} + for _, line := range lines { + // calculate indent + indent := 0 + for i := 0; i < len(line); i++ { + if line[i] != ' ' { + break + } + indent++ + } + // if indent is not a multiple of 2, it is an error + if indent%2 != 0 { + return nil, fmt.Errorf("the line '%s' is not a multiple of 2", line) + } + // calculate level + level := indent / 2 + line = strings.TrimSpace(line[indent:]) + // if the line is empty, skip + if line == "" { + continue + } + // if level isn't greater than the level of the top of the stack + // it is not the child of the top of the stack + for level <= stack[len(stack)-1].Level { + // pop the top of the stack + stack = stack[:len(stack)-1] + } + // if the line is a folder + if isFolder(line) { + // create a new node + node := &Node{ + Level: level, + Name: strings.TrimSuffix(line, ":"), + } + // add the node to the top of the stack + stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node) + // push the node to the stack + stack = append(stack, node) + } else { + // if the line is a file + // create a new node + node, err := parseFileLine(line) + if err != nil { + return nil, err + } + node.Level = level + // add the node to the top of the stack + stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node) + } + } + return root, nil +} + +func isFolder(line string) bool { + return strings.HasSuffix(line, ":") +} + +// line definition: +// [FileSize:][Modified:]Url +func parseFileLine(line string) (*Node, error) { + // if there is no url, it is an error + if !strings.Contains(line, "http://") && !strings.Contains(line, "https://") { + return nil, fmt.Errorf("invalid line: %s, because url is required for file", line) + } + index := strings.Index(line, "http://") + if index == -1 { + index = strings.Index(line, "https://") + } + url := line[index:] + info := line[:index] + node := &Node{ + Url: url, + } + name := stdpath.Base(url) + unescape, err := url2.PathUnescape(name) + if err == nil { + name = unescape + } + node.Name = name + if index > 0 { + if !strings.HasSuffix(info, ":") { + return nil, fmt.Errorf("invalid line: %s, because file info must end with ':'", line) + } + info = info[:len(info)-1] + if info == "" { + return nil, fmt.Errorf("invalid line: %s, because file name can't be empty", line) + } + infoParts := strings.Split(info, ":") + size, err := strconv.ParseInt(infoParts[0], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid line: %s, because file size must be an integer", line) + } + node.Size = size + if len(infoParts) > 1 { + modified, err := strconv.ParseInt(infoParts[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid line: %s, because file modified must be an unix timestamp", line) + } + node.Modified = modified + } else { + node.Modified = time.Now().Unix() + } + } + return node, nil +} + +func splitPath(path string) []string { + if path == "/" { + return []string{"root"} + } + parts := strings.Split(path, "/") + parts[0] = "root" + return parts +} + +func GetNodeFromRootByPath(root *Node, path string) *Node { + return root.getByPath(splitPath(path)) +} diff --git a/drivers/123_link/types.go b/drivers/123_link/types.go new file mode 100644 index 00000000000..3fb040eb831 --- /dev/null +++ b/drivers/123_link/types.go @@ -0,0 +1,66 @@ +package _123Link + +import ( + "time" + + "github.com/alist-org/alist/v3/internal/errs" + "github.com/alist-org/alist/v3/internal/model" +) + +// Node is a node in the folder tree +type Node struct { + Url string + Name string + Level int + Modified int64 + Size int64 + Children []*Node +} + +func (node *Node) getByPath(paths []string) *Node { + if len(paths) == 0 || node == nil { + return nil + } + if node.Name != paths[0] { + return nil + } + if len(paths) == 1 { + return node + } + for _, child := range node.Children { + tmp := child.getByPath(paths[1:]) + if tmp != nil { + return tmp + } + } + return nil +} + +func (node *Node) isFile() bool { + return node.Url != "" +} + +func (node *Node) calSize() int64 { + if node.isFile() { + return node.Size + } + var size int64 = 0 + for _, child := range node.Children { + size += child.calSize() + } + node.Size = size + return size +} + +func nodeToObj(node *Node, path string) (model.Obj, error) { + if node == nil { + return nil, errs.ObjectNotFound + } + return &model.Object{ + Name: node.Name, + Size: node.Size, + Modified: time.Unix(node.Modified, 0), + IsFolder: !node.isFile(), + Path: path, + }, nil +} diff --git a/drivers/123_link/util.go b/drivers/123_link/util.go new file mode 100644 index 00000000000..29c9b54d576 --- /dev/null +++ b/drivers/123_link/util.go @@ -0,0 +1,30 @@ +package _123Link + +import ( + "crypto/md5" + "fmt" + "math/rand" + "net/url" + "time" +) + +func SignURL(originURL, privateKey string, uid uint64, validDuration time.Duration) (newURL string, err error) { + if privateKey == "" { + return originURL, nil + } + var ( + ts = time.Now().Add(validDuration).Unix() // 有效时间戳 + rInt = rand.Int() // 随机正整数 + objURL *url.URL + ) + objURL, err = url.Parse(originURL) + if err != nil { + return "", err + } + authKey := fmt.Sprintf("%d-%d-%d-%x", ts, rInt, uid, md5.Sum([]byte(fmt.Sprintf("%s-%d-%d-%d-%s", + objURL.Path, ts, rInt, uid, privateKey)))) + v := objURL.Query() + v.Add("auth_key", authKey) + objURL.RawQuery = v.Encode() + return objURL.String(), nil +} diff --git a/drivers/all.go b/drivers/all.go index 9bd1733b137..921a467620d 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -3,6 +3,7 @@ package drivers import ( _ "github.com/alist-org/alist/v3/drivers/115" _ "github.com/alist-org/alist/v3/drivers/123" + _ "github.com/alist-org/alist/v3/drivers/123_link" _ "github.com/alist-org/alist/v3/drivers/123_share" _ "github.com/alist-org/alist/v3/drivers/139" _ "github.com/alist-org/alist/v3/drivers/189"