diff --git a/drivers/123pan.go b/drivers/123pan.go new file mode 100644 index 00000000..a3fa8426 --- /dev/null +++ b/drivers/123pan.go @@ -0,0 +1,310 @@ +package drivers + +import ( + "fmt" + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/gin-gonic/gin" + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" + "path/filepath" + "strconv" + "time" +) + +type Pan123 struct { +} + +var pan123Client = resty.New() + +func (p Pan123) Items() []Item { + return []Item{ + { + Name: "proxy", + Label: "proxy", + Type: "bool", + Required: true, + Description: "allow proxy", + }, + { + Name: "username", + Label: "username", + Type: "string", + Required: true, + Description: "account username/phone number", + }, + { + Name: "password", + Label: "password", + Type: "string", + Required: true, + Description: "account password", + }, + { + Name: "root_folder", + Label: "root folder file_id", + Type: "string", + Required: false, + }, + { + Name: "order_by", + Label: "order_by", + Type: "select", + Values: "name,fileId,updateAt,createAt", + Required: true, + }, + { + Name: "order_direction", + Label: "order_direction", + Type: "select", + Values: "asc,desc", + Required: true, + }, + } +} + +type Pan123TokenResp struct { + Code int `json:"code"` + Data struct { + Token string `json:"token"` + } `json:"data"` + Message string `json:"message"` +} + +func (p Pan123) Login(account *model.Account) error { + var resp Pan123TokenResp + _, err := pan123Client.R(). + SetResult(&resp). + SetBody(Json{ + "passport": account.Username, + "password": account.Password, + }).Post("https://www.123pan.com/api/user/sign_in") + if err != nil { + return err + } + if resp.Code != 200 { + err = fmt.Errorf(resp.Message) + account.Status = resp.Message + } else { + account.Status = "work" + account.AccessToken = resp.Data.Token + } + _ = model.SaveAccount(account) + return err +} + +func (p Pan123) Save(account *model.Account, old *model.Account) error { + if account.RootFolder == "" { + account.RootFolder = "0" + } + err := p.Login(account) + return err +} + +type Pan123File struct { + FileName string `json:"FileName"` + Size int64 `json:"Size"` + UpdateAt *time.Time `json:"UpdateAt"` + FileId int64 `json:"FileId"` + Type int `json:"Type"` + Etag string `json:"Etag"` + S3KeyFlag string `json:"S3KeyFlag"` +} + +func (p Pan123) FormatFile(file *Pan123File) *model.File { + f := &model.File{ + Name: file.FileName, + Size: file.Size, + Driver: "123Pan", + UpdatedAt: file.UpdateAt, + } + if file.Type == 1 { + f.Type = conf.FOLDER + } else { + f.Type = utils.GetFileType(filepath.Ext(file.FileName)) + } + return f +} + +type Pan123Files struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + InfoList []Pan123File `json:"InfoList"` + Next string `json:"Next"` + } `json:"data"` +} + +func (p Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123File, error) { + next := "0" + res := make([]Pan123File, 0) + for next != "-1" { + var resp Pan123Files + _, err := pan123Client.R().SetResult(&resp). + SetHeader("authorization", "Bearer "+account.AccessToken). + SetQueryParams(map[string]string{ + "driveId": "0", + "limit": "100", + "next": next, + "orderBy": account.OrderBy, + "orderDirection": account.OrderDirection, + "parentFileId": parentId, + "trashed": "false", + }).Get("https://www.123pan.com/api/file/list") + if err != nil { + return nil, err + } + log.Debugf("%+v", resp) + if resp.Code != 0 { + if resp.Code == 401 { + err := p.Login(account) + if err != nil { + return nil, err + } + return p.GetFiles(parentId, account) + } + return nil, fmt.Errorf(resp.Message) + } + next = resp.Data.Next + res = append(res, resp.Data.InfoList...) + } + return res, nil +} + +func (p Pan123) Path(path string, account *model.Account) (*model.File, []*model.File, error) { + path = utils.ParsePath(path) + log.Debugf("pan123 path: %s", path) + cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path)) + if err == nil { + files, _ := cache.([]Pan123File) + if len(files) != 0 { + res := make([]*model.File, 0) + for _, file := range files { + res = append(res, p.FormatFile(&file)) + } + return nil, res, nil + } + } + // no cache or len(files) == 0 + fileId := account.RootFolder + if path != "/" { + dir, name := filepath.Split(path) + dir = utils.ParsePath(dir) + _, _, err = p.Path(dir, account) + if err != nil { + return nil, nil, err + } + parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir)) + parentFiles, _ := parentFiles_.([]Pan123File) + found := false + for _, file := range parentFiles { + if file.FileName == name { + found = true + if file.Type != 1 { + url, err := p.Link(path, account) + if err != nil { + return nil, nil, err + } + + f := p.FormatFile(&file) + f.Url = url + return f, nil, nil + } else { + fileId = strconv.FormatInt(file.FileId, 10) + break + } + } + } + if !found { + return nil, nil, fmt.Errorf("path not found") + } + } + files, err := p.GetFiles(fileId, account) + if err != nil { + return nil, nil, err + } + log.Debugf("%+v", files) + _ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil) + res := make([]*model.File, 0) + for _, file := range files { + res = append(res, p.FormatFile(&file)) + } + return nil, res, nil +} + +func (p Pan123) GetFile(path string, account *model.Account) (*Pan123File, error) { + dir, name := filepath.Split(path) + dir = utils.ParsePath(dir) + _, _, err := p.Path(dir, account) + if err != nil { + return nil, err + } + parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir)) + parentFiles, _ := parentFiles_.([]Pan123File) + for _, file := range parentFiles { + if file.FileName == name { + if file.Type != 1 { + return &file, err + } else { + return nil, fmt.Errorf("not file") + } + } + } + return nil, fmt.Errorf("path not found") +} + +type Pan123DownResp struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + DownloadUrl string `json:"DownloadUrl"` + } `json:"data"` +} + +func (p Pan123) Link(path string, account *model.Account) (string, error) { + file, err := p.GetFile(utils.ParsePath(path), account) + if err != nil { + return "", err + } + var resp Pan123DownResp + _, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken). + SetBody(Json{ + "driveId": 0, + "etag": file.Etag, + "fileId": file.FileId, + "fileName": file.FileName, + "s3keyFlag": file.S3KeyFlag, + "size": file.Size, + "type": file.Type, + }).Post("https://www.123pan.com/api/file/download_info") + if err != nil { + return "", err + } + if resp.Code != 0 { + if resp.Code == 401 { + err := p.Login(account) + if err != nil { + return "", err + } + return p.Link(path, account) + } + return "", fmt.Errorf(resp.Message) + } + return resp.Data.DownloadUrl, nil +} + +func (p Pan123) Proxy(c *gin.Context, account *model.Account) { + c.Request.Header.Del("origin") +} + +func (p Pan123) Preview(path string, account *model.Account) (interface{}, error) { + return nil, nil +} + +var _ Driver = (*Pan123)(nil) + +func init() { + RegisterDriver("123Pan", &Pan123{}) + pan123Client.SetRetryCount(3) +}