feat(terabox): add terabox driver (close #2825 close #2678 #2849)

pull/2867/head
Code2qing 2022-12-31 16:44:20 +08:00 committed by GitHub
parent fbf3fb825b
commit 1c8d895fc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 524 additions and 0 deletions

View File

@ -27,6 +27,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/sftp"
_ "github.com/alist-org/alist/v3/drivers/smb"
_ "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/uss"
_ "github.com/alist-org/alist/v3/drivers/virtual"

214
drivers/terabox/driver.go Normal file
View File

@ -0,0 +1,214 @@
package terbox
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/pkg/utils"
log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"os"
stdpath "path"
"strconv"
"strings"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
)
type Terabox struct {
model.Storage
Addition
}
func (d *Terabox) Config() driver.Config {
return config
}
func (d *Terabox) GetAddition() driver.Additional {
return &d.Addition
}
func (d *Terabox) Init(ctx context.Context) error {
_, err := d.request("https://www.terabox.com/api/check/login", http.MethodGet, nil, nil)
return err
}
func (d *Terabox) Drop(ctx context.Context) error {
return nil
}
func (d *Terabox) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetPath())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
return fileToObj(src), nil
})
}
func (d *Terabox) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
if d.DownloadAPI == "crack" {
return d.linkCrack(file, args)
}
return d.linkOfficial(file, args)
}
func (d *Terabox) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
_, err := d.create(stdpath.Join(parentDir.GetPath(), dirName), 0, 1, "", "")
return err
}
func (d *Terabox) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
data := []base.Json{
{
"path": srcObj.GetPath(),
"dest": dstDir.GetPath(),
"newname": srcObj.GetName(),
},
}
_, err := d.manage("move", data)
return err
}
func (d *Terabox) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
data := []base.Json{
{
"path": srcObj.GetPath(),
"newname": newName,
},
}
_, err := d.manage("rename", data)
return err
}
func (d *Terabox) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
data := []base.Json{
{
"path": srcObj.GetPath(),
"dest": dstDir.GetPath(),
"newname": srcObj.GetName(),
},
}
_, err := d.manage("copy", data)
return err
}
func (d *Terabox) Remove(ctx context.Context, obj model.Obj) error {
data := []string{obj.GetPath()}
_, err := d.manage("delete", data)
return err
}
func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
tempFile, err := utils.CreateTempFile(stream.GetReadCloser())
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
var Default int64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(stream.GetSize()) / float64(Default)))
// cal md5
h1 := md5.New()
h2 := md5.New()
block_list := make([]string, 0)
left := stream.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
rawPath := stdpath.Join(dstDir.GetPath(), stream.GetName())
path := encodeURIComponent(rawPath)
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s",
path, stream.GetSize(),
block_list_str)
params := map[string]string{}
var precreateResp PrecreateResp
_, err = d.post("/api/precreate", params, data, &precreateResp)
if err != nil {
return err
}
log.Debugf("%+v", precreateResp)
if precreateResp.ReturnType == 2 {
return nil
}
params = map[string]string{
"method": "upload",
"path": path,
"uploadid": precreateResp.Uploadid,
"app_id": "250528",
"web": "1",
"channel": "dubox",
"clienttype": "0",
}
left = stream.GetSize()
for i, partseq := range precreateResp.BlockList {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
u := "https://c-jp.terabox.com/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().
SetContext(ctx).
SetQueryParams(params).
SetFileReader("file", stream.GetName(), bytes.NewReader(byteData)).
SetHeader("Cookie", d.Cookie).
Post(u)
if err != nil {
return err
}
log.Debugln(res.String())
if len(precreateResp.BlockList) > 0 {
up(i * 100 / len(precreateResp.BlockList))
}
}
_, err = d.create(rawPath, stream.GetSize(), 0, precreateResp.Uploadid, block_list_str)
return err
}
var _ driver.Driver = (*Terabox)(nil)

25
drivers/terabox/meta.go Normal file
View File

@ -0,0 +1,25 @@
package terbox
import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/op"
)
type Addition struct {
driver.RootPath
Cookie string `json:"cookie" required:"true"`
DownloadAPI string `json:"download_api" type:"select" options:"official,crack" default:"official"`
OrderBy string `json:"order_by" type:"select" options:"name,time,size" default:"name"`
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
}
var config = driver.Config{
Name: "Terabox",
DefaultRoot: "/",
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &Terabox{}
})
}

92
drivers/terabox/types.go Normal file
View File

@ -0,0 +1,92 @@
package terbox
import (
"github.com/alist-org/alist/v3/internal/model"
"strconv"
"time"
)
type File struct {
//TkbindId int `json:"tkbind_id"`
//OwnerType int `json:"owner_type"`
//Category int `json:"category"`
//RealCategory string `json:"real_category"`
FsId int64 `json:"fs_id"`
ServerMtime int64 `json:"server_mtime"`
//OperId int `json:"oper_id"`
//ServerCtime int `json:"server_ctime"`
Thumbs struct {
//Icon string `json:"icon"`
Url3 string `json:"url3"`
//Url2 string `json:"url2"`
//Url1 string `json:"url1"`
} `json:"thumbs"`
//Wpfile int `json:"wpfile"`
//LocalMtime int `json:"local_mtime"`
Size int64 `json:"size"`
//ExtentTinyint7 int `json:"extent_tinyint7"`
Path string `json:"path"`
//Share int `json:"share"`
//ServerAtime int `json:"server_atime"`
//Pl int `json:"pl"`
//LocalCtime int `json:"local_ctime"`
ServerFilename string `json:"server_filename"`
//Md5 string `json:"md5"`
//OwnerId int `json:"owner_id"`
//Unlist int `json:"unlist"`
Isdir int `json:"isdir"`
}
type ListResp struct {
Errno int `json:"errno"`
GuidInfo string `json:"guid_info"`
List []File `json:"list"`
RequestId int64 `json:"request_id"`
Guid int `json:"guid"`
}
func fileToObj(f File) *model.ObjThumb {
return &model.ObjThumb{
Object: model.Object{
ID: strconv.FormatInt(f.FsId, 10),
Name: f.ServerFilename,
Size: f.Size,
Modified: time.Unix(f.ServerMtime, 0),
IsFolder: f.Isdir == 1,
},
Thumbnail: model.Thumbnail{Thumbnail: f.Thumbs.Url3},
}
}
type DownloadResp struct {
Errno int `json:"errno"`
Dlink []struct {
Dlink string `json:"dlink"`
} `json:"dlink"`
}
type DownloadResp2 struct {
Errno int `json:"errno"`
Info []struct {
Dlink string `json:"dlink"`
} `json:"info"`
RequestID int64 `json:"request_id"`
}
type HomeInfoResp struct {
Errno int `json:"errno"`
Data struct {
Sign1 string `json:"sign1"`
Sign3 string `json:"sign3"`
Timestamp int `json:"timestamp"`
} `json:"data"`
}
type PrecreateResp struct {
Path string `json:"path"`
Uploadid string `json:"uploadid"`
ReturnType int `json:"return_type"`
BlockList []int `json:"block_list"`
Errno int `json:"errno"`
RequestId int64 `json:"request_id"`
}

192
drivers/terabox/util.go Normal file
View File

@ -0,0 +1,192 @@
package terbox
import (
"encoding/base64"
"fmt"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
func (d *Terabox) request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"Cookie": d.Cookie,
"Accept": "application/json, text/plain, */*",
"Referer": "https://www.terabox.com/",
"User-Agent": base.UserAgent,
})
req.SetQueryParam("app_id", "250528")
req.SetQueryParam("web", "1")
req.SetQueryParam("channel", "dubox")
req.SetQueryParam("clienttype", "0")
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
res, err := req.Execute(method, furl)
if err != nil {
return nil, err
}
return res.Body(), nil
}
func (d *Terabox) get(pathname string, params map[string]string, resp interface{}) ([]byte, error) {
return d.request("https://www.terabox.com"+pathname, http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(params)
}, resp)
}
func (d *Terabox) post(pathname string, params map[string]string, data interface{}, resp interface{}) ([]byte, error) {
return d.request("https://www.terabox.com"+pathname, http.MethodPost, func(req *resty.Request) {
req.SetQueryParams(params)
req.SetBody(data)
}, resp)
}
func (d *Terabox) getFiles(dir string) ([]File, error) {
page := 1
num := 100
params := map[string]string{
"dir": dir,
}
if d.OrderBy != "" {
params["order"] = d.OrderBy
if d.OrderDirection == "desc" {
params["desc"] = "1"
}
}
res := make([]File, 0)
for {
params["page"] = strconv.Itoa(page)
params["num"] = strconv.Itoa(num)
var resp ListResp
_, err := d.get("/api/list", params, &resp)
if err != nil {
return nil, err
}
if len(resp.List) == 0 {
break
}
res = append(res, resp.List...)
page++
}
return res, nil
}
func sign(s1, s2 string) string {
var a = make([]int, 256)
var p = make([]int, 256)
var o []byte
var v = len(s1)
for q := 0; q < 256; q++ {
a[q] = int(s1[(q % v) : (q%v)+1][0])
p[q] = q
}
for u, q := 0, 0; q < 256; q++ {
u = (u + p[q] + a[q]) % 256
p[q], p[u] = p[u], p[q]
}
for i, u, q := 0, 0, 0; q < len(s2); q++ {
i = (i + 1) % 256
u = (u + p[i]) % 256
p[i], p[u] = p[u], p[i]
k := p[((p[i] + p[u]) % 256)]
o = append(o, byte(int(s2[q])^k))
}
return base64.StdEncoding.EncodeToString(o)
}
func (d *Terabox) genSign() (string, error) {
var resp HomeInfoResp
_, err := d.get("/api/home/info", map[string]string{}, &resp)
if err != nil {
return "", err
}
return sign(resp.Data.Sign3, resp.Data.Sign1), nil
}
func (d *Terabox) linkOfficial(file model.Obj, args model.LinkArgs) (*model.Link, error) {
var resp DownloadResp
signString, err := d.genSign()
if err != nil {
return nil, err
}
params := map[string]string{
"type": "dlink",
"fidlist": fmt.Sprintf("[%s]", file.GetID()),
"sign": signString,
"vip": "2",
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
}
_, err = d.get("/api/download", params, &resp)
if err != nil {
return nil, err
}
res, err := base.NoRedirectClient.R().SetHeader("Cookie", d.Cookie).SetHeader("User-Agent", base.UserAgent).Get(resp.Dlink[0].Dlink)
if err != nil {
return nil, err
}
u := res.Header().Get("location")
return &model.Link{
URL: u,
Header: http.Header{
"User-Agent": []string{base.UserAgent},
},
}, nil
}
func (d *Terabox) linkCrack(file model.Obj, args model.LinkArgs) (*model.Link, error) {
var resp DownloadResp2
param := map[string]string{
"target": fmt.Sprintf("[\"%s\"]", file.GetPath()),
"dlink": "1",
"origin": "dlna",
}
_, err := d.get("/api/filemetas", param, &resp)
if err != nil {
return nil, err
}
return &model.Link{
URL: resp.Info[0].Dlink,
Header: http.Header{
"User-Agent": []string{base.UserAgent},
},
}, nil
}
func (d *Terabox) manage(opera string, filelist interface{}) ([]byte, error) {
params := map[string]string{
"onnest": "fail",
"opera": opera,
}
marshal, err := utils.Json.Marshal(filelist)
if err != nil {
return nil, err
}
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
return d.post("/api/filemanager", params, data, nil)
}
func (d *Terabox) create(path string, size int64, isdir int, uploadid, block_list string) ([]byte, error) {
params := map[string]string{}
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", encodeURIComponent(path), size, isdir)
if uploadid != "" {
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
}
return d.post("/api/create", params, data, nil)
}
func encodeURIComponent(str string) string {
r := url.QueryEscape(str)
r = strings.ReplaceAll(r, "+", "%20")
return r
}