Compare commits

..

16 Commits

Author SHA1 Message Date
Andy Hsu 8e2b9c681a fix(ilanzou): upgrade devVersion 2024-05-23 20:05:00 +08:00
foxxorcat 0a8d710e01 fix(mopan): upgrade version (#6500) 2024-05-23 18:56:17 +08:00
Andy Hsu d781f7127a fix: add lark to windows target 2024-05-23 11:52:37 +08:00
WintBit 85d743c5d2 feat: add support for lark driver (#6475)
* feat: lark storage driver

* feat: external view mode

* limit lark targets

* fix: missing package

---------

Co-authored-by: Andy Hsu <i@nn.ci>
2024-05-22 23:31:58 +08:00
j2rong4cn 5f60b51cf8 feat: add proxy_range option for 139Yun Alias AList V3 (#6496) 2024-05-22 23:31:42 +08:00
foxxorcat 7013d1b7b8 fix: pikpak captcha_required (#6497)
* fix: pikpak captcha_required

* fix(pikpak_share):  video download
2024-05-22 23:29:29 +08:00
Kuingsmile 9eec872637 feat(mega): add 2FA support (#6473)
* feat(mega): add support for two-factor authentication in Mega driver  #6226

* feat(mega): remove debug print statement in Mega driver Init function

* feat(mega): add help message for new field
2024-05-22 23:28:14 +08:00
j2rong4cn 037850bbd5 feat(alias): support Rename and Remove (#6478)
* feat(alias): support `Rename` and `Remove`

* fix(alias): `autoFlatten` not updated after editing

* feat(alias): add `protect_same_name` option
2024-05-22 09:27:48 +08:00
YangXu bbe3d4e19f feat: add supports for thunderX driver (#6464) 2024-05-21 23:24:28 +08:00
Sakura-Byte 78a9676c7c feat(alist_v3): Optional pass UA to upstream remote (#6443)
* fix(115): Support 115 302 redirect while getting link under (nested) alist_v3 remote

* chore: simplify logic

* chore: simplify logic

* use internal UA

* add option to set if the user want their ua be passed to upstream
2024-05-12 17:34:36 +08:00
Andy Hsu 8bf93562eb fix(baidu): unknown type for custom upload part size (close #6435) 2024-05-09 14:54:53 +08:00
George Chen b57afd0a98 fix(sftp): reconnect to server when connection was broken (#6416 close #6403)
* fix(sftp): reconnect to server when conn was broken (close #6403)

* fix(sftp): fix typo

---------

Co-authored-by: George Chen <gchen@isimarkets.com>
2024-05-09 14:53:25 +08:00
liuycy f261ef50cc feat: add supports for netease music driver (#6423 close #5364) 2024-05-09 14:29:35 +08:00
itsHenry 7e7b9b9b48 feat(s3): server support generated url request (#6431) 2024-05-09 14:28:59 +08:00
meozky 2313213f59 fix(189pc): FamilyID range overflow (#6427 close #6426) 2024-05-09 14:23:12 +08:00
Moraxyc 5f28532423 fix(test): ensure setupStorages is executed once (#6422)
In TestGetStorageVirtualFilesByPath() and TestGetBalancedStorage(), setupStorages() was being called twice, leading to a "UNIQUE constraint failed" error.
2024-05-09 14:22:19 +08:00
53 changed files with 2782 additions and 246 deletions
+6 -3
View File
@@ -14,12 +14,15 @@ type Addition struct {
}
var config = driver.Config{
Name: "139Yun",
LocalSort: true,
Name: "139Yun",
LocalSort: true,
ProxyRangeOption: true,
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &Yun139{}
d := &Yun139{}
d.ProxyRange = true
return d
})
}
+1 -1
View File
@@ -143,7 +143,7 @@ type FamilyInfoListResp struct {
type FamilyInfoResp struct {
Count int `json:"count"`
CreateTime string `json:"createTime"`
FamilyID int `json:"familyId"`
FamilyID int64 `json:"familyId"`
RemarkName string `json:"remarkName"`
Type int `json:"type"`
UseFlag int `json:"useFlag"`
+26
View File
@@ -7,6 +7,7 @@ import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
)
@@ -45,6 +46,9 @@ func (d *Alias) Init(ctx context.Context) error {
d.oneKey = k
}
d.autoFlatten = true
} else {
d.oneKey = ""
d.autoFlatten = false
}
return nil
}
@@ -111,4 +115,26 @@ func (d *Alias) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
return nil, errs.ObjectNotFound
}
func (d *Alias) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
reqPath, err := d.getReqPath(ctx, srcObj)
if err == nil {
return fs.Rename(ctx, *reqPath, newName)
}
if errs.IsNotImplement(err) {
return errors.New("same-name files cannot be Rename")
}
return err
}
func (d *Alias) Remove(ctx context.Context, obj model.Obj) error {
reqPath, err := d.getReqPath(ctx, obj)
if err == nil {
return fs.Remove(ctx, *reqPath)
}
if errs.IsNotImplement(err) {
return errors.New("same-name files cannot be Delete")
}
return err
}
var _ driver.Driver = (*Alias)(nil)
+13 -7
View File
@@ -9,19 +9,25 @@ type Addition struct {
// Usually one of two
// driver.RootPath
// define other
Paths string `json:"paths" required:"true" type:"text"`
Paths string `json:"paths" required:"true" type:"text"`
ProtectSameName bool `json:"protect_same_name" default:"true" required:"false" help:"Protects same-name files from Delete or Rename"`
}
var config = driver.Config{
Name: "Alias",
LocalSort: true,
NoCache: true,
NoUpload: true,
DefaultRoot: "/",
Name: "Alias",
LocalSort: true,
NoCache: true,
NoUpload: true,
DefaultRoot: "/",
ProxyRangeOption: true,
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &Alias{}
return &Alias{
Addition: Addition{
ProtectSameName: true,
},
}
})
}
+39 -2
View File
@@ -6,6 +6,7 @@ import (
stdpath "path"
"strings"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/sign"
@@ -102,13 +103,49 @@ func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs)
return nil, err
}
if common.ShouldProxy(storage, stdpath.Base(sub)) {
return &model.Link{
link := &model.Link{
URL: fmt.Sprintf("%s/p%s?sign=%s",
common.GetApiUrl(args.HttpReq),
utils.EncodePath(reqPath, true),
sign.Sign(reqPath)),
}, nil
}
if args.HttpReq != nil && d.ProxyRange {
link.RangeReadCloser = common.NoProxyRange
}
return link, nil
}
link, _, err := fs.Link(ctx, reqPath, args)
return link, err
}
func (d *Alias) getReqPath(ctx context.Context, obj model.Obj) (*string, error) {
root, sub := d.getRootAndPath(obj.GetPath())
if sub == "" || sub == "/" {
return nil, errs.NotSupport
}
dsts, ok := d.pathMap[root]
if !ok {
return nil, errs.ObjectNotFound
}
var reqPath string
var err error
for _, dst := range dsts {
reqPath = stdpath.Join(dst, sub)
_, err = fs.Get(ctx, reqPath, &fs.GetArgs{NoLog: true})
if err == nil {
if d.ProtectSameName {
if ok {
ok = false
} else {
return nil, errs.NotImplement
}
} else {
break
}
}
}
if err != nil {
return nil, errs.ObjectNotFound
}
return &reqPath, nil
}
+9 -1
View File
@@ -109,11 +109,19 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs)
func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
var resp common.Resp[FsGetResp]
// if PassUAToUpsteam is true, then pass the user-agent to the upstream
userAgent := base.UserAgent
if d.PassUAToUpsteam {
userAgent = args.Header.Get("user-agent")
if userAgent == "" {
userAgent = base.UserAgent
}
}
_, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) {
req.SetResult(&resp).SetBody(FsGetReq{
Path: file.GetPath(),
Password: d.MetaPassword,
})
}).SetHeader("user-agent", userAgent)
})
if err != nil {
return nil, err
+11 -9
View File
@@ -7,18 +7,20 @@ import (
type Addition struct {
driver.RootPath
Address string `json:"url" required:"true"`
MetaPassword string `json:"meta_password"`
Username string `json:"username"`
Password string `json:"password"`
Token string `json:"token"`
Address string `json:"url" required:"true"`
MetaPassword string `json:"meta_password"`
Username string `json:"username"`
Password string `json:"password"`
Token string `json:"token"`
PassUAToUpsteam bool `json:"pass_ua_to_upsteam" default:"true"`
}
var config = driver.Config{
Name: "AList V3",
LocalSort: true,
DefaultRoot: "/",
CheckStatus: true,
Name: "AList V3",
LocalSort: true,
DefaultRoot: "/",
CheckStatus: true,
ProxyRangeOption: true,
}
func init() {
+2
View File
@@ -32,6 +32,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
_ "github.com/alist-org/alist/v3/drivers/mega"
_ "github.com/alist-org/alist/v3/drivers/mopan"
_ "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_app"
_ "github.com/alist-org/alist/v3/drivers/pikpak"
@@ -45,6 +46,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/teambition"
_ "github.com/alist-org/alist/v3/drivers/terabox"
_ "github.com/alist-org/alist/v3/drivers/thunder"
_ "github.com/alist-org/alist/v3/drivers/thunderx"
_ "github.com/alist-org/alist/v3/drivers/trainbit"
_ "github.com/alist-org/alist/v3/drivers/url_tree"
_ "github.com/alist-org/alist/v3/drivers/uss"
+1 -1
View File
@@ -17,7 +17,7 @@ type Addition struct {
AccessToken string
UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"`
UploadAPI string `json:"upload_api" default:"https://d.pcs.baidu.com"`
CustomUploadPartSize int64 `json:"custom_upload_part_size" default:"0" help:"0 for auto"`
CustomUploadPartSize int64 `json:"custom_upload_part_size" type:"number" default:"0" help:"0 for auto"`
}
var config = driver.Config{
+8 -2
View File
@@ -147,7 +147,8 @@ func (d *ILanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
// get the url after redirect
res, err := base.NoRedirectClient.R().SetHeaders(map[string]string{
//"Origin": d.conf.site,
"Referer": d.conf.site + "/",
"Referer": d.conf.site + "/",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0",
}).Get(realURL)
if err != nil {
return nil, err
@@ -155,7 +156,12 @@ func (d *ILanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
if res.StatusCode() == 302 {
realURL = res.Header().Get("location")
} else {
return nil, fmt.Errorf("redirect failed, status: %d", res.StatusCode())
contentLengthStr := res.Header().Get("Content-Length")
contentLength, err := strconv.Atoi(contentLengthStr)
if err != nil || contentLength == 0 || contentLength > 1024*10 {
return nil, fmt.Errorf("redirect failed, status: %d", res.StatusCode())
}
return nil, fmt.Errorf("redirect failed, content: %s", res.String())
}
link := model.Link{URL: realURL}
return &link, nil
+2 -2
View File
@@ -46,7 +46,7 @@ func init() {
bucket: "wpanstore-lanzou",
unproved: "unproved",
proved: "proved",
devVersion: "122",
devVersion: "125",
site: "https://www.ilanzou.com",
},
}
@@ -72,7 +72,7 @@ func init() {
bucket: "wpanstore",
unproved: "ws",
proved: "app",
devVersion: "121",
devVersion: "125",
site: "https://www.feijipan.com",
},
}
+3 -2
View File
@@ -59,8 +59,9 @@ func (d *ILanZou) request(pathname, method string, callback base.ReqCallback, pr
"extra": "2",
})
req.SetHeaders(map[string]string{
"Origin": d.conf.site,
"Referer": d.conf.site + "/",
"Origin": d.conf.site,
"Referer": d.conf.site + "/",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0",
})
if proved {
req.SetQueryParam("appToken", d.Token)
+8
View File
@@ -0,0 +1,8 @@
// +build linux darwin windows
// +build amd64 arm64
package drivers
import (
_ "github.com/alist-org/alist/v3/drivers/lark"
)
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
package lark
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
AppId string `json:"app_id" type:"text" help:"app id"`
AppSecret string `json:"app_secret" type:"text" help:"app secret"`
ExternalMode bool `json:"external_mode" type:"bool" help:"external mode"`
TenantUrlPrefix string `json:"tenant_url_prefix" type:"text" help:"tenant url prefix"`
}
var config = driver.Config{
Name: "Lark",
LocalSort: false,
OnlyLocal: false,
OnlyProxy: false,
NoCache: false,
NoUpload: false,
NeedMs: false,
DefaultRoot: "/",
CheckStatus: false,
Alert: "",
NoOverwriteUpload: true,
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &Lark{}
})
}
+32
View File
@@ -0,0 +1,32 @@
package lark
import (
"context"
"github.com/Xhofe/go-cache"
"time"
)
type TokenCache struct {
cache.ICache[string]
}
func (t *TokenCache) Set(_ context.Context, key string, value string, expireTime time.Duration) error {
t.ICache.Set(key, value, cache.WithEx[string](expireTime))
return nil
}
func (t *TokenCache) Get(_ context.Context, key string) (string, error) {
v, ok := t.ICache.Get(key)
if ok {
return v, nil
}
return "", nil
}
func newTokenCache() *TokenCache {
c := cache.NewMemCache[string]()
return &TokenCache{c}
}
+66
View File
@@ -0,0 +1,66 @@
package lark
import (
"context"
"github.com/Xhofe/go-cache"
larkdrive "github.com/larksuite/oapi-sdk-go/v3/service/drive/v1"
log "github.com/sirupsen/logrus"
"path"
"time"
)
const objTokenCacheDuration = 5 * time.Minute
const emptyFolderToken = "empty"
var objTokenCache = cache.NewMemCache[string]()
var exOpts = cache.WithEx[string](objTokenCacheDuration)
func (c *Lark) getObjToken(ctx context.Context, folderPath string) (string, bool) {
if token, ok := objTokenCache.Get(folderPath); ok {
return token, true
}
dir, name := path.Split(folderPath)
// strip the last slash of dir if it exists
if len(dir) > 0 && dir[len(dir)-1] == '/' {
dir = dir[:len(dir)-1]
}
if name == "" {
return c.rootFolderToken, true
}
var parentToken string
var found bool
parentToken, found = c.getObjToken(ctx, dir)
if !found {
return emptyFolderToken, false
}
req := larkdrive.NewListFileReqBuilder().FolderToken(parentToken).Build()
resp, err := c.client.Drive.File.ListByIterator(ctx, req)
if err != nil {
log.WithError(err).Error("failed to list files")
return emptyFolderToken, false
}
var file *larkdrive.File
for {
found, file, err = resp.Next()
if !found {
break
}
if err != nil {
log.WithError(err).Error("failed to get next file")
break
}
if *file.Name == name {
objTokenCache.Set(folderPath, *file.Token, exOpts)
return *file.Token, true
}
}
return emptyFolderToken, false
}
+10 -1
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/alist-org/alist/v3/pkg/http_range"
"github.com/pquerna/otp/totp"
"github.com/rclone/rclone/lib/readers"
"github.com/alist-org/alist/v3/internal/driver"
@@ -33,8 +34,16 @@ func (d *Mega) GetAddition() driver.Additional {
}
func (d *Mega) Init(ctx context.Context) error {
var twoFACode = d.TwoFACode
d.c = mega.New()
return d.c.Login(d.Email, d.Password)
if d.TwoFASecret != "" {
code, err := totp.GenerateCode(d.TwoFASecret, time.Now())
if err != nil {
return fmt.Errorf("generate totp code failed: %w", err)
}
twoFACode = code
}
return d.c.MultiFactorLogin(d.Email, d.Password, twoFACode)
}
func (d *Mega) Drop(ctx context.Context) error {
+4 -2
View File
@@ -9,8 +9,10 @@ type Addition struct {
// Usually one of two
//driver.RootPath
//driver.RootID
Email string `json:"email" required:"true"`
Password string `json:"password" required:"true"`
Email string `json:"email" required:"true"`
Password string `json:"password" required:"true"`
TwoFACode string `json:"two_fa_code" required:"false" help:"2FA 6-digit code, filling in the 2FA code alone will not support reloading driver"`
TwoFASecret string `json:"two_fa_secret" required:"false" help:"2FA secret"`
}
var config = driver.Config{
+135
View File
@@ -0,0 +1,135 @@
package netease_music
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"math/big"
"strings"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/utils/random"
)
var (
linuxapiKey = []byte("rFgB&h#%2?^eDg:Q")
eapiKey = []byte("e82ckenh8dichen8")
iv = []byte("0102030405060708")
presetKey = []byte("0CoJUm6Qyw8W8jud")
publicKey = []byte("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----")
stdChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
)
func aesKeyPending(key []byte) []byte {
k := len(key)
count := 0
switch true {
case k <= 16:
count = 16 - k
case k <= 24:
count = 24 - k
case k <= 32:
count = 32 - k
default:
return key[:32]
}
if count == 0 {
return key
}
return append(key, bytes.Repeat([]byte{0}, count)...)
}
func pkcs7Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
func aesCBCEncrypt(src, key, iv []byte) []byte {
block, _ := aes.NewCipher(aesKeyPending(key))
src = pkcs7Padding(src, block.BlockSize())
dst := make([]byte, len(src))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(dst, src)
return dst
}
func aesECBEncrypt(src, key []byte) []byte {
block, _ := aes.NewCipher(aesKeyPending(key))
src = pkcs7Padding(src, block.BlockSize())
dst := make([]byte, len(src))
ecbCryptBlocks(block, dst, src)
return dst
}
func ecbCryptBlocks(block cipher.Block, dst, src []byte) {
bs := block.BlockSize()
for len(src) > 0 {
block.Encrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
}
func rsaEncrypt(buffer, key []byte) []byte {
buffers := make([]byte, 128-16, 128)
buffers = append(buffers, buffer...)
block, _ := pem.Decode(key)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
c := new(big.Int).SetBytes([]byte(buffers))
return c.Exp(c, big.NewInt(int64(pub.E)), pub.N).Bytes()
}
func getSecretKey() ([]byte, []byte) {
key := make([]byte, 16)
reversed := make([]byte, 16)
for i := 0; i < 16; i++ {
result := stdChars[random.RangeInt64(0, 62)]
key[i] = result
reversed[15-i] = result
}
return key, reversed
}
func weapi(data map[string]string) map[string]string {
text, _ := utils.Json.Marshal(data)
secretKey, reversedKey := getSecretKey()
params := []byte(base64.StdEncoding.EncodeToString(aesCBCEncrypt(text, presetKey, iv)))
return map[string]string{
"params": base64.StdEncoding.EncodeToString(aesCBCEncrypt(params, reversedKey, iv)),
"encSecKey": hex.EncodeToString(rsaEncrypt(secretKey, publicKey)),
}
}
func eapi(url string, data map[string]interface{}) map[string]string {
text, _ := utils.Json.Marshal(data)
msg := "nobody" + url + "use" + string(text) + "md5forencrypt"
h := md5.New()
h.Write([]byte(msg))
digest := hex.EncodeToString(h.Sum(nil))
params := []byte(url + "-36cd479b6b5-" + string(text) + "-36cd479b6b5-" + digest)
return map[string]string{
"params": hex.EncodeToString(aesECBEncrypt(params, eapiKey)),
}
}
func linuxapi(data map[string]interface{}) map[string]string {
text, _ := utils.Json.Marshal(data)
return map[string]string{
"eparams": strings.ToUpper(hex.EncodeToString(aesECBEncrypt(text, linuxapiKey))),
}
}

Some files were not shown because too many files have changed in this diff Show More