Mr.L
feat: add full alist source code for Docker build
7107f0b
package homecloud
import (
"bytes"
"context"
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"mime/multipart"
"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/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/cron"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/utils/random"
)
type HomeCloud struct {
model.Storage
Addition
AccessToken string
UserID string
cron *cron.Cron
Account string
}
func (d *HomeCloud) Config() driver.Config {
return config
}
func (d *HomeCloud) GetAddition() driver.Additional {
return &d.Addition
}
func (d *HomeCloud) Init(ctx context.Context) error {
if d.RefreshToken == "" {
return fmt.Errorf("RefreshToken is empty")
}
if len(d.Addition.RootFolderID) == 0 {
d.RootFolderID = "0"
}
err := d.refreshToken()
if err != nil {
return err
}
d.cron = cron.NewCron(time.Hour * 10)
d.cron.Do(func() {
err := d.refreshToken()
if err != nil {
return
}
})
return nil
}
func (d *HomeCloud) Drop(ctx context.Context) error {
if d.cron != nil {
d.cron.Stop()
}
return nil
}
func (d *HomeCloud) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
return d.familyGetFiles(dir.GetID())
}
func (d *HomeCloud) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
var url string
var err error
url, err = d.getLink(file.GetID())
if err != nil {
return nil, err
}
link := &model.Link{
URL: url,
}
// 创建Header 否则新上传文件无法使用
header := make(http.Header)
header.Add("Cookie", "H_TOKEN="+d.AccessToken)
header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1")
link.Header = header
return link, nil
}
func (d *HomeCloud) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
var err error
data := base.Json{
"parentDirId": parentDir.GetID(),
"dirName": dirName,
"category": 0,
"userId": d.UserID,
"groupId": d.GroupID,
}
pathname := "/storage/addDirectory/v1"
_, err = d.post(pathname, data, nil)
return err
}
func (d *HomeCloud) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
data := base.Json{
"fileIds": []string{srcObj.GetID()},
"targetDirId": dstDir.GetID(),
"userId": d.UserID,
"groupId": d.GroupID,
}
pathname := "/storage/batchMoveFile/v1"
_, err := d.post(pathname, data, nil)
if err != nil {
return nil, err
}
return srcObj, nil
}
func (d *HomeCloud) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
var err error
data := base.Json{
"fileId": srcObj.GetID(),
"fileName": newName,
"userId": d.UserID,
"groupId": d.GroupID,
}
pathname := "/storage/updateFileName/v1"
_, err = d.post(pathname, data, nil)
return err
}
func (d *HomeCloud) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
// 复制会占用空间 所以先屏蔽代码
// data := base.Json{
// "fileIds": []string{srcObj.GetID()},
// "targetDirId": dstDir.GetID(),
// "targetGroupId": d.GroupID,
// "userId": d.UserID,
// "groupId": d.GroupID,
// }
// pathname := "/storage/batchCopyFile/v1"
// _, err := d.post(pathname, data, nil)
// return err
return errs.NotImplement
}
func (d *HomeCloud) Remove(ctx context.Context, obj model.Obj) error {
data := base.Json{
"fileIds": []string{obj.GetID()},
"userId": d.UserID,
"groupId": d.GroupID,
}
pathname := "/storage/batchDeleteFile/v1"
if obj.IsDir() {
data = base.Json{
"fileId": obj.GetID(),
"userId": d.UserID,
"groupId": d.GroupID,
}
pathname = "/storage/deleteDirectory/v1"
}
_, err := d.post(pathname, data, nil)
return err
}
const (
_ = iota //ignore first value by assigning to blank identifier
KB = 1 << (10 * iota)
MB
GB
TB
)
func getPartSize(size int64) int64 {
// 网盘对于分片数量存在上限
if size/GB > 30 {
return 512 * MB
}
return 100 * MB
}
func (d *HomeCloud) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
var err error
h := md5.New()
// need to calculate md5 of the full content
tempFile, err := stream.CacheFullInTempFile()
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
}()
if _, err = io.Copy(h, tempFile); err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
etag := hex.EncodeToString(h.Sum(nil))
// return errs.NotImplement
data := base.Json{
"userId": d.UserID,
"groupId": d.GroupID,
"dirId": dstDir.GetID(),
"fileName": stream.GetName(),
"fileMd5": etag,
"fileSize": stream.GetSize(),
"fileCategory": 99,
}
pathname := "/storage/addFileUploadTask/v1"
var resp PersonalUploadResp
_, err = d.post(pathname, data, &resp)
if err != nil {
return err
}
// Progress
p := driver.NewProgress(stream.GetSize(), up)
var partSize = getPartSize(stream.GetSize())
part := (stream.GetSize() + partSize - 1) / partSize
if part == 0 {
part = 1
}
for i := int64(0); i < part; i++ {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
start := i * partSize
byteSize := stream.GetSize() - start
if byteSize > partSize {
byteSize = partSize
}
limitReader := io.LimitReader(stream, byteSize)
// Update Progress
r := io.TeeReader(limitReader, p)
// Update Progress
//r := io.TeeReader(limitReader, p)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
filePart, err := writer.CreateFormFile("partFile", stream.GetName())
if err != nil {
return err
}
_, err = io.Copy(filePart, r)
if err != nil {
return err
}
isDone := false
if i == (part - 1) {
isDone = true
}
_ = writer.WriteField("uploadId", resp.Data.UploadId)
_ = writer.WriteField("isComplete", strconv.FormatBool(isDone))
_ = writer.WriteField("rangeStart", strconv.Itoa(int(start)))
err = writer.Close()
if err != nil {
return err
}
req, err := http.NewRequest("POST", resp.Data.UploadUrl, body)
if err != nil {
return err
}
requestID := random.String(12)
pbody, err := utils.Json.Marshal(body)
if err != nil {
return err
}
timestamp := fmt.Sprintf("%.3f", float64(time.Now().UnixNano())/1e6)
h := sha1.New()
var sha1Hash string
if pbody == nil {
h.Write([]byte("{}"))
sha1Hash = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
} else {
h.Write(pbody)
sha1Hash = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
}
uppathname := "/upload/upload/uploadFilePart/v1"
encStr := fmt.Sprintf("%s;%s;%s;Bearer %s;%s", uppathname, sha1Hash, requestID, d.AccessToken, timestamp)
signature := strings.ToUpper(fmt.Sprintf("%x", md5.Sum([]byte(encStr))))
req = req.WithContext(ctx)
req.Header.Add("Accept", "*/*")
req.Header.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Add("Authorization", "Bearer "+d.AccessToken)
req.Header.Add("Origin", "https://homecloud.komect.com")
req.Header.Add("Referer", "https://homecloud.komect.com/disk/main/familyspace")
req.Header.Add("Request-Id", requestID)
req.Header.Add("Signature", signature)
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
req.Header.Add("X-Requested-With", "XMLHttpRequest")
req.Header.Add("X-User-Agent", "Web|Chrome 127.0.0.0||OS X|homecloudWebDisk_1.1.1||yunpan 1.1.1|unknown")
req.Header.Add("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\"")
req.Header.Add("sec-ch-ua-mobile", "?0")
req.Header.Add("sec-ch-ua-platform", "\"macOS\"")
req.Header.Add("userId", d.UserID)
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
_ = res.Body.Close()
//log.Debugf("%+v", res)
if res.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
}
// url, err := d.getLink(resp.Data.FileId)
// if err != nil {
// return fmt.Errorf("can not get file donwnload url")
// }
// _, err = base.RestyClient.R().
// SetHeader("Cookie", "H_TOKEN="+d.AccessToken).
// SetHeader("Range", "bytes=0-100").
// Get(url)
// if err != nil {
// return fmt.Errorf("can not active file")
// }
return nil
}
func (d *HomeCloud) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
return nil, errs.NotImplement
}
var _ driver.Driver = (*HomeCloud)(nil)