Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions drivers/139/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package _139

import (
"context"
"encoding/base64"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"

"github.com/OpenListTeam/OpenList/v4/drivers/base"
Expand All @@ -32,6 +35,72 @@ type Yun139 struct {
RootPath string
}

type shareRef struct {
LinkID string
Password string
NodeID string
}

const multiShareRefPrefix = "shares:"

func encodeShareRef(linkID, password, nodeID string) string {
return url.PathEscape(linkID) + "|" + url.PathEscape(password) + "|" + url.PathEscape(nodeID)
}

func decodeShareRef(id string) (shareRef, bool) {
parts := strings.SplitN(id, "|", 3)
if len(parts) != 3 {
return shareRef{}, false
}
linkID, err1 := url.PathUnescape(parts[0])
password, err2 := url.PathUnescape(parts[1])
nodeID, err3 := url.PathUnescape(parts[2])
if err1 != nil || err2 != nil || err3 != nil || linkID == "" {
return shareRef{}, false
}
return shareRef{LinkID: linkID, Password: password, NodeID: nodeID}, true
}

func encodeShareRefs(refs []shareRef) string {
if len(refs) == 1 {
ref := refs[0]
return encodeShareRef(ref.LinkID, ref.Password, ref.NodeID)
}
data, err := utils.Json.Marshal(refs)
if err != nil {
return ""
}
return multiShareRefPrefix + base64.RawURLEncoding.EncodeToString(data)
}

func decodeShareRefs(id string) ([]shareRef, bool) {
if !strings.HasPrefix(id, multiShareRefPrefix) {
ref, ok := decodeShareRef(id)
if !ok {
return nil, false
}
return []shareRef{ref}, true
}
data, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(id, multiShareRefPrefix))
if err != nil {
return nil, false
}
var refs []shareRef
if err = utils.Json.Unmarshal(data, &refs); err != nil || len(refs) == 0 {
return nil, false
}
return refs, true
}

func (d *Yun139) shareRootEntries() []shareRef {
entries := d.shareEntries()
refs := make([]shareRef, 0, len(entries))
for _, entry := range entries {
refs = append(refs, shareRef{LinkID: entry.LinkID, Password: entry.Password, NodeID: "root"})
}
return refs
}

func (d *Yun139) Config() driver.Config {
return config
}
Expand All @@ -41,6 +110,26 @@ func (d *Yun139) GetAddition() driver.Additional {
}

func (d *Yun139) Init(ctx context.Context) error {
if d.Addition.Type == MetaShare {
if len(d.Addition.RootFolderID) == 0 {
d.RootFolderID = "root"
}
d.LinkID = d.Addition.LinkID
if d.ref == nil && (d.Authorization != "" || (d.Username != "" && d.Password != "")) {
if len(d.Authorization) == 0 {
log.Infof("139yun: authorization is empty, trying to login with password.")
newAuth, err := d.loginWithPassword()
log.Debugf("newAuth: Ok: %s", newAuth)
if err != nil {
return fmt.Errorf("login with password failed: %w", err)
}
}
if err := d.refreshToken(); err != nil {
return err
}
}
return nil
}
if d.ref == nil {
if len(d.Authorization) == 0 {
if d.Username != "" && d.Password != "" {
Expand Down Expand Up @@ -107,6 +196,13 @@ func (d *Yun139) Init(ctx context.Context) error {
if err != nil {
return err
}
case MetaShare:
if len(d.Addition.RootFolderID) == 0 {
d.RootFolderID = "root"
}
if len(d.shareEntries()) == 0 {
return fmt.Errorf("link_id is empty")
}
case MetaFamily:
if len(d.Addition.RootFolderID) == 0 {
// Attempt to obtain data.path as the root via a query and persist it.
Expand Down Expand Up @@ -134,6 +230,45 @@ func (d *Yun139) InitReference(storage driver.Driver) error {
return errs.NotSupport
}

func (d *Yun139) Get(ctx context.Context, path string) (model.Obj, error) {
if d.Addition.Type != MetaShare {
return nil, errs.NotImplement
}
if path == "/" {
return &model.Object{ID: "root", Name: "root", IsFolder: true, Path: "/"}, nil
}
if obj, err := d.shareGetObj(path); err == nil {
return obj, nil
}
return nil, errs.ObjectNotFound
}

func (d *Yun139) shareEntries() []struct{ LinkID, Password string } {
raw := strings.TrimSpace(d.LinkID)
if raw == "" {
return nil
}
parts := strings.FieldsFunc(raw, func(r rune) bool {
return r == '\n' || r == '\r' || r == ',' || r == ';'
})
entries := make([]struct{ LinkID, Password string }, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
entry := struct{ LinkID, Password string }{LinkID: part}
if linkID, password, ok := strings.Cut(part, "#"); ok {
entry.LinkID = strings.TrimSpace(linkID)
entry.Password = strings.TrimSpace(password)
}
if entry.LinkID != "" {
entries = append(entries, entry)
}
}
return entries
}

func (d *Yun139) Drop(ctx context.Context) error {
if d.cron != nil {
d.cron.Stop()
Expand All @@ -152,12 +287,23 @@ func (d *Yun139) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
return d.familyGetFiles(dir.GetID())
case MetaGroup:
return d.groupGetFiles(dir.GetID())
case MetaShare:
if dir.GetID() == "root" {
return d.shareGetMergedFiles(d.shareRootEntries())
}
if refs, ok := decodeShareRefs(dir.GetID()); ok {
return d.shareGetMergedFiles(refs)
}
return d.shareGetFilesWithRef(shareRef{LinkID: d.LinkID, NodeID: dir.GetID()}, dir.GetID())
default:
return nil, errs.NotImplement
}
}

func (d *Yun139) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
if file.IsDir() {
return nil, errs.NotFile
}
var url string
var err error
switch d.Addition.Type {
Expand All @@ -169,6 +315,11 @@ func (d *Yun139) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
url, err = d.familyGetLink(file.GetID(), file.GetPath())
case MetaGroup:
url, err = d.groupGetLink(file.GetID(), file.GetPath())
case MetaShare:
if refs, ok := decodeShareRefs(file.GetID()); ok && len(refs) > 0 {
return d.shareGetLinkWithRef(refs[0], refs[0].NodeID, args.Type)
}
return d.shareGetLinkWithRef(shareRef{LinkID: d.LinkID, NodeID: "root"}, file.GetID(), args.Type)
default:
return nil, errs.NotImplement
}
Expand Down
3 changes: 2 additions & 1 deletion drivers/139/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ type Addition struct {
Password string `json:"password" required:"true" secret:"true"`
MailCookies string `json:"mail_cookies" required:"true" type:"text" help:"Cookies from mail.139.com used for login authentication."`
driver.RootID
Type string `json:"type" type:"select" options:"personal_new,family,group,personal" default:"personal_new"`
Type string `json:"type" type:"select" options:"personal_new,family,group,personal,share" default:"personal_new"`
LinkID string `json:"link_id" type:"text" help:"Multiple shares are separated by commas or new lines. Use link_id#password for password-protected shares."`
CloudID string `json:"cloud_id"`
UserDomainID string `json:"user_domain_id" help:"ud_id in Cookie, fill in to show disk usage"`
CustomUploadPartSize int64 `json:"custom_upload_part_size" type:"number" default:"0" help:"0 for auto"`
Expand Down
35 changes: 35 additions & 0 deletions drivers/139/share_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package _139

import (
"testing"
)

func TestShareEntriesAndRefEncoding(t *testing.T) {
d := &Yun139{Addition: Addition{
Type: MetaShare,
LinkID: "2w2KLTrz2Y8bd,2uR1zFho3YNcj,2w2KLzpSwt57p#f95e",
}}

entries := d.shareEntries()
if len(entries) != 3 {
t.Fatalf("expected 3 share entries, got %d", len(entries))
}
if entries[2].LinkID != "2w2KLzpSwt57p" || entries[2].Password != "f95e" {
t.Fatalf("unexpected password share entry: %+v", entries[2])
}

refs := []shareRef{
{LinkID: entries[0].LinkID, Password: entries[0].Password, NodeID: "root-a"},
{LinkID: entries[2].LinkID, Password: entries[2].Password, NodeID: "root-b"},
}
encoded := encodeShareRefs(refs)
decoded, ok := decodeShareRefs(encoded)
if !ok || len(decoded) != len(refs) {
t.Fatalf("failed to decode merged share refs: %q", encoded)
}
for i := range refs {
if decoded[i] != refs[i] {
t.Fatalf("unexpected decoded ref at %d: %+v", i, decoded[i])
}
}
}
51 changes: 51 additions & 0 deletions drivers/139/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
MetaFamily string = "family"
MetaGroup string = "group"
MetaPersonalNew string = "personal_new"
MetaShare string = "share"
)

type BaseResp struct {
Expand Down Expand Up @@ -285,6 +286,56 @@ type PersonalUploadUrlResp struct {
}
}

type ShareCatalog struct {
CaID string `json:"caId"`
CaName string `json:"caName"`
UdTime string `json:"udTime"`
}

type ShareContent struct {
CoID string `json:"coId"`
CoName string `json:"coName"`
CoSize int64 `json:"coSize"`
CoType int `json:"coType"`
UdTime string `json:"udTime"`
CoPath string `json:"coPath"`
PresentURL string `json:"presentURL"`
DownloadURL string `json:"downloadURL"`
}

type ShareListResp struct {
BaseResp
Data struct {
LKName string `json:"lkName"`
Passwd string `json:"password"`
CaLst []ShareCatalog `json:"caLst"`
CoLst []ShareContent `json:"coLst"`
} `json:"data"`
}

type ShareContentInfo struct {
PresentURL string `json:"presentURL"`
DownloadURL string `json:"cdnDownLoadUrl"`
}

type ShareDownloadResp struct {
BaseResp
Data struct {
DownloadURL string `json:"downloadURL"`
RedrURL string `json:"redrUrl"`
ExtInfo struct {
CDNDownloadURL string `json:"cdnDownloadUrl"`
} `json:"extInfo"`
} `json:"data"`
}

type ShareContentInfoResp struct {
BaseResp
Data struct {
ContentInfo ShareContentInfo `json:"contentInfo"`
} `json:"data"`
}

type QueryRoutePolicyResp struct {
Success bool `json:"success"`
Code string `json:"code"`
Expand Down
Loading
Loading