package teambition import ( "context" "errors" "fmt" "io" "net/http" "strconv" "strings" "time" "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/pkg/utils" "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" ) // do others that not defined in Driver interface func (d *Teambition) isInternational() bool { return d.Region == "international" } func (d *Teambition) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { url := "https://www.teambition.com" + pathname if d.isInternational() { url = "https://us.teambition.com" + pathname } req := base.RestyClient.R() req.SetHeader("Cookie", d.Cookie) if callback != nil { callback(req) } if resp != nil { req.SetResult(resp) } var e ErrResp req.SetError(&e) res, err := req.Execute(method, url) if err != nil { return nil, err } if e.Name != "" { return nil, errors.New(e.Message) } return res.Body(), nil } func (d *Teambition) getFiles(parentId string) ([]model.Obj, error) { files := make([]model.Obj, 0) page := 1 for { var collections []Collection _, err := d.request("/api/collections", http.MethodGet, func(req *resty.Request) { req.SetQueryParams(map[string]string{ "_parentId": parentId, "_projectId": d.ProjectID, "order": d.OrderBy + d.OrderDirection, "count": "50", "page": strconv.Itoa(page), }) }, &collections) if err != nil { return nil, err } if len(collections) == 0 { break } page++ for _, collection := range collections { if collection.Title == "" { continue } files = append(files, &model.Object{ ID: collection.ID, Name: collection.Title, IsFolder: true, Modified: collection.Updated, }) } } page = 1 for { var works []Work _, err := d.request("/api/works", http.MethodGet, func(req *resty.Request) { req.SetQueryParams(map[string]string{ "_parentId": parentId, "_projectId": d.ProjectID, "order": d.OrderBy + d.OrderDirection, "count": "50", "page": strconv.Itoa(page), }) }, &works) if err != nil { return nil, err } if len(works) == 0 { break } page++ for _, work := range works { files = append(files, &model.ObjThumbURL{ Object: model.Object{ ID: work.ID, Name: work.FileName, Size: work.FileSize, Modified: work.Updated, }, Thumbnail: model.Thumbnail{Thumbnail: work.Thumbnail}, Url: model.Url{Url: work.DownloadURL}, }) } } return files, nil } func (d *Teambition) upload(ctx context.Context, file model.FileStreamer, token string) (*FileUpload, error) { prefix := "tcs" if d.isInternational() { prefix = "us-tcs" } var newFile FileUpload _, err := base.RestyClient.R(). SetContext(ctx). SetResult(&newFile).SetHeader("Authorization", token). SetMultipartFormData(map[string]string{ "name": file.GetName(), "type": file.GetMimetype(), "size": strconv.FormatInt(file.GetSize(), 10), //"lastModifiedDate": "", }).SetMultipartField("file", file.GetName(), file.GetMimetype(), file). Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix)) if err != nil { return nil, err } return &newFile, nil } func (d *Teambition) chunkUpload(ctx context.Context, file model.FileStreamer, token string, up driver.UpdateProgress) (*FileUpload, error) { prefix := "tcs" referer := "https://www.teambition.com/" if d.isInternational() { prefix = "us-tcs" referer = "https://us.teambition.com/" } var newChunk ChunkUpload _, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token). SetBody(base.Json{ "fileName": file.GetName(), "fileSize": file.GetSize(), "lastUpdated": time.Now(), }).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix)) if err != nil { return nil, err } for i := 0; i < newChunk.Chunks; i++ { if utils.IsCanceled(ctx) { return nil, ctx.Err() } chunkSize := newChunk.ChunkSize if i == newChunk.Chunks-1 { chunkSize = int(file.GetSize()) - i*chunkSize } log.Debugf("%d : %d", i, chunkSize) chunkData := make([]byte, chunkSize) _, err = io.ReadFull(file, chunkData) if err != nil { return nil, err } u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d", prefix, newChunk.FileKey, i+1, newChunk.Chunks) log.Debugf("url: %s", u) _, err := base.RestyClient.R(). SetContext(ctx). SetHeaders(map[string]string{ "Authorization": token, "Content-Type": "application/octet-stream", "Referer": referer, }).SetBody(chunkData).Post(u) if err != nil { return nil, err } if err != nil { return nil, err } up(i * 100 / newChunk.Chunks) } _, err = base.RestyClient.R().SetHeader("Authorization", token).Post( fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s", prefix, newChunk.FileKey)) if err != nil { return nil, err } return &newChunk.FileUpload, nil } func (d *Teambition) finishUpload(file *FileUpload, parentId string) error { file.InvolveMembers = []interface{}{} file.Visible = "members" file.ParentId = parentId _, err := d.request("/api/works", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "works": []FileUpload{*file}, "_parentId": parentId, }) }, nil) return err } func GetBetweenStr(str, start, end string) string { n := strings.Index(str, start) if n == -1 { return "" } n = n + len(start) str = string([]byte(str)[n:]) m := strings.Index(str, end) if m == -1 { return "" } str = string([]byte(str)[:m]) return str }