* add chaoxing and vtencent * add vtencent put file * add sha1 to transfer files instantly * simplified upload file code * setting onlyproxy * fix get files modifyDate bug
This commit is contained in:
203
drivers/vtencent/drive.go
Normal file
203
drivers/vtencent/drive.go
Normal file
@ -0,0 +1,203 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"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/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/cron"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Vtencent struct {
|
||||
model.Storage
|
||||
Addition
|
||||
cron *cron.Cron
|
||||
config driver.Config
|
||||
conf Conf
|
||||
}
|
||||
|
||||
func (d *Vtencent) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *Vtencent) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Vtencent) Init(ctx context.Context) error {
|
||||
tfUid, err := d.LoadUser()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
d.Addition.TfUid = tfUid
|
||||
op.MustSaveDriverStorage(d)
|
||||
d.cron = cron.NewCron(time.Hour * 12)
|
||||
d.cron.Do(func() {
|
||||
_, err := d.LoadUser()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) Drop(ctx context.Context) error {
|
||||
d.cron.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.GetFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Vtencent) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
form := fmt.Sprintf(`{"MaterialIds":["%s"]}`, file.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resps RspDown
|
||||
api := "https://api.vs.tencent.com/SaaS/Material/DescribeMaterialDownloadUrl"
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(rsp, &resps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resps.Data.DownloadURLInfoSet) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
u := resps.Data.DownloadURLInfoSet[0].DownloadURL
|
||||
return &model.Link{
|
||||
URL: u,
|
||||
Header: http.Header{
|
||||
"Referer": []string{d.conf.referer},
|
||||
"User-Agent": []string{d.conf.ua},
|
||||
},
|
||||
Concurrency: 2,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
classId, err := strconv.Atoi(parentDir.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request("https://api.vs.tencent.com/PaaS/Material/CreateClass", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"Owner": base.Json{
|
||||
"Type": "PERSON",
|
||||
"Id": d.TfUid,
|
||||
},
|
||||
"ParentClassId": classId,
|
||||
"Name": dirName,
|
||||
"VerifySign": ""})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
srcType := "MATERIAL"
|
||||
if srcObj.IsDir() {
|
||||
srcType = "CLASS"
|
||||
}
|
||||
form := fmt.Sprintf(`{"SourceInfos":[
|
||||
{"Owner":{"Id":"%s","Type":"PERSON"},
|
||||
"Resource":{"Type":"%s","Id":"%s"}}
|
||||
],
|
||||
"Destination":{"Owner":{"Id":"%s","Type":"PERSON"},
|
||||
"Resource":{"Type":"CLASS","Id":"%s"}}
|
||||
}`, d.TfUid, srcType, srcObj.GetID(), d.TfUid, dstDir.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request("https://api.vs.tencent.com/PaaS/Material/MoveResource", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/ModifyMaterial"
|
||||
form := fmt.Sprintf(`{
|
||||
"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"MaterialId":"%s","Name":"%s"}`, d.TfUid, srcObj.GetID(), newName)
|
||||
if srcObj.IsDir() {
|
||||
classId, err := strconv.Atoi(srcObj.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api = "https://api.vs.tencent.com/PaaS/Material/ModifyClass"
|
||||
form = fmt.Sprintf(`{"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"ClassId":%d,"Name":"%s"}`, d.TfUid, classId, newName)
|
||||
}
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Vtencent) Remove(ctx context.Context, obj model.Obj) error {
|
||||
srcType := "MATERIAL"
|
||||
if obj.IsDir() {
|
||||
srcType = "CLASS"
|
||||
}
|
||||
form := fmt.Sprintf(`{
|
||||
"SourceInfos":[
|
||||
{"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"Resource":{"Type":"%s","Id":"%s"}}
|
||||
]
|
||||
}`, d.TfUid, srcType, obj.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request("https://api.vs.tencent.com/PaaS/Material/DeleteResource", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
err := d.FileUpload(ctx, dstDir, stream, up)
|
||||
return err
|
||||
}
|
||||
|
||||
//func (d *Vtencent) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Vtencent)(nil)
|
39
drivers/vtencent/meta.go
Normal file
39
drivers/vtencent/meta.go
Normal file
@ -0,0 +1,39 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
Cookie string `json:"cookie" required:"true"`
|
||||
TfUid string `json:"tf_uid"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"Name,Size,UpdateTime,CreatTime"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"Asc,Desc"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
ua string
|
||||
referer string
|
||||
origin string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Vtencent{
|
||||
config: driver.Config{
|
||||
Name: "腾讯智能创作平台",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: "9",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://app.v.tencent.com/",
|
||||
origin: "https://app.v.tencent.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
33
drivers/vtencent/signature.go
Normal file
33
drivers/vtencent/signature.go
Normal file
@ -0,0 +1,33 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func QSignatureKey(timeKey string, signPath string, key string) string {
|
||||
signKey := hmac.New(sha1.New, []byte(key))
|
||||
signKey.Write([]byte(timeKey))
|
||||
signKeyBytes := signKey.Sum(nil)
|
||||
signKeyHex := hex.EncodeToString(signKeyBytes)
|
||||
sha := sha1.New()
|
||||
sha.Write([]byte(signPath))
|
||||
shaBytes := sha.Sum(nil)
|
||||
shaHex := hex.EncodeToString(shaBytes)
|
||||
|
||||
O := "sha1\n" + timeKey + "\n" + shaHex + "\n"
|
||||
dataSignKey := hmac.New(sha1.New, []byte(signKeyHex))
|
||||
dataSignKey.Write([]byte(O))
|
||||
dataSignKeyBytes := dataSignKey.Sum(nil)
|
||||
dataSignKeyHex := hex.EncodeToString(dataSignKeyBytes)
|
||||
return dataSignKeyHex
|
||||
}
|
||||
|
||||
func QTwoSignatureKey(timeKey string, key string) string {
|
||||
signKey := hmac.New(sha1.New, []byte(key))
|
||||
signKey.Write([]byte(timeKey))
|
||||
signKeyBytes := signKey.Sum(nil)
|
||||
signKeyHex := hex.EncodeToString(signKeyBytes)
|
||||
return signKeyHex
|
||||
}
|
252
drivers/vtencent/types.go
Normal file
252
drivers/vtencent/types.go
Normal file
@ -0,0 +1,252 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type RespErr struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
type Reqfiles struct {
|
||||
ScrollToken string `json:"ScrollToken"`
|
||||
Text string `json:"Text"`
|
||||
Offset int `json:"Offset"`
|
||||
Limit int `json:"Limit"`
|
||||
Sort struct {
|
||||
Field string `json:"Field"`
|
||||
Order string `json:"Order"`
|
||||
} `json:"Sort"`
|
||||
CreateTimeRanges []any `json:"CreateTimeRanges"`
|
||||
MaterialTypes []any `json:"MaterialTypes"`
|
||||
ReviewStatuses []any `json:"ReviewStatuses"`
|
||||
Tags []any `json:"Tags"`
|
||||
SearchScopes []struct {
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
ClassID int `json:"ClassId"`
|
||||
SearchOneDepth bool `json:"SearchOneDepth"`
|
||||
} `json:"SearchScopes"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Type string `json:"Type"`
|
||||
ClassInfo struct {
|
||||
ClassID int `json:"ClassId"`
|
||||
Name string `json:"Name"`
|
||||
UpdateTime time.Time `json:"UpdateTime"`
|
||||
CreateTime time.Time `json:"CreateTime"`
|
||||
FileInboxID string `json:"FileInboxId"`
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
ClassPath string `json:"ClassPath"`
|
||||
ParentClassID int `json:"ParentClassId"`
|
||||
AttachmentInfo struct {
|
||||
SubClassCount int `json:"SubClassCount"`
|
||||
MaterialCount int `json:"MaterialCount"`
|
||||
Size int64 `json:"Size"`
|
||||
} `json:"AttachmentInfo"`
|
||||
ClassPreviewURLSet []string `json:"ClassPreviewUrlSet"`
|
||||
} `json:"ClassInfo"`
|
||||
MaterialInfo struct {
|
||||
BasicInfo struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
MaterialType string `json:"MaterialType"`
|
||||
Name string `json:"Name"`
|
||||
CreateTime time.Time `json:"CreateTime"`
|
||||
UpdateTime time.Time `json:"UpdateTime"`
|
||||
ClassPath string `json:"ClassPath"`
|
||||
ClassID int `json:"ClassId"`
|
||||
TagInfoSet []any `json:"TagInfoSet"`
|
||||
TagSet []any `json:"TagSet"`
|
||||
PreviewURL string `json:"PreviewUrl"`
|
||||
MediaURL string `json:"MediaUrl"`
|
||||
UnifiedMediaPreviewURL string `json:"UnifiedMediaPreviewUrl"`
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
PermissionSet any `json:"PermissionSet"`
|
||||
PermissionInfoSet []any `json:"PermissionInfoSet"`
|
||||
TfUID string `json:"TfUid"`
|
||||
GroupID string `json:"GroupId"`
|
||||
VersionMaterialIDSet []any `json:"VersionMaterialIdSet"`
|
||||
FileType string `json:"FileType"`
|
||||
CmeMaterialPlayList []any `json:"CmeMaterialPlayList"`
|
||||
Status string `json:"Status"`
|
||||
DownloadSwitch string `json:"DownloadSwitch"`
|
||||
} `json:"BasicInfo"`
|
||||
MediaInfo struct {
|
||||
Width int `json:"Width"`
|
||||
Height int `json:"Height"`
|
||||
Size int `json:"Size"`
|
||||
Duration float64 `json:"Duration"`
|
||||
Fps int `json:"Fps"`
|
||||
BitRate int `json:"BitRate"`
|
||||
Codec string `json:"Codec"`
|
||||
MediaType string `json:"MediaType"`
|
||||
FavoriteStatus string `json:"FavoriteStatus"`
|
||||
} `json:"MediaInfo"`
|
||||
MaterialStatus struct {
|
||||
ContentReviewStatus string `json:"ContentReviewStatus"`
|
||||
EditorUsableStatus string `json:"EditorUsableStatus"`
|
||||
UnifiedPreviewStatus string `json:"UnifiedPreviewStatus"`
|
||||
EditPreviewImageSpiritStatus string `json:"EditPreviewImageSpiritStatus"`
|
||||
TranscodeStatus string `json:"TranscodeStatus"`
|
||||
AdaptiveStreamingStatus string `json:"AdaptiveStreamingStatus"`
|
||||
StreamConnectable string `json:"StreamConnectable"`
|
||||
AiAnalysisStatus string `json:"AiAnalysisStatus"`
|
||||
AiRecognitionStatus string `json:"AiRecognitionStatus"`
|
||||
} `json:"MaterialStatus"`
|
||||
ImageMaterial struct {
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Size int `json:"Size"`
|
||||
MaterialURL string `json:"MaterialUrl"`
|
||||
Resolution string `json:"Resolution"`
|
||||
VodFileID string `json:"VodFileId"`
|
||||
OriginalURL string `json:"OriginalUrl"`
|
||||
} `json:"ImageMaterial"`
|
||||
VideoMaterial struct {
|
||||
MetaData struct {
|
||||
Size int `json:"Size"`
|
||||
Container string `json:"Container"`
|
||||
Bitrate int `json:"Bitrate"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Duration float64 `json:"Duration"`
|
||||
Rotate int `json:"Rotate"`
|
||||
VideoStreamInfoSet []struct {
|
||||
Bitrate int `json:"Bitrate"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Codec string `json:"Codec"`
|
||||
Fps int `json:"Fps"`
|
||||
} `json:"VideoStreamInfoSet"`
|
||||
AudioStreamInfoSet []struct {
|
||||
Bitrate int `json:"Bitrate"`
|
||||
SamplingRate int `json:"SamplingRate"`
|
||||
Codec string `json:"Codec"`
|
||||
} `json:"AudioStreamInfoSet"`
|
||||
} `json:"MetaData"`
|
||||
ImageSpriteInfo any `json:"ImageSpriteInfo"`
|
||||
MaterialURL string `json:"MaterialUrl"`
|
||||
CoverURL string `json:"CoverUrl"`
|
||||
Resolution string `json:"Resolution"`
|
||||
VodFileID string `json:"VodFileId"`
|
||||
OriginalURL string `json:"OriginalUrl"`
|
||||
AudioWaveformURL string `json:"AudioWaveformUrl"`
|
||||
SubtitleURL string `json:"SubtitleUrl"`
|
||||
TranscodeInfoSet []any `json:"TranscodeInfoSet"`
|
||||
ImageSpriteInfoSet []any `json:"ImageSpriteInfoSet"`
|
||||
} `json:"VideoMaterial"`
|
||||
} `json:"MaterialInfo"`
|
||||
}
|
||||
|
||||
type RspFiles struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
TotalCount int `json:"TotalCount"`
|
||||
ResourceInfoSet []File `json:"ResourceInfoSet"`
|
||||
ScrollToken string `json:"ScrollToken"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspDown struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
DownloadURLInfoSet []struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
DownloadURL string `json:"DownloadUrl"`
|
||||
} `json:"DownloadUrlInfoSet"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspCreatrMaterial struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
UploadContext string `json:"UploadContext"`
|
||||
VodUploadSign string `json:"VodUploadSign"`
|
||||
QuickUpload bool `json:"QuickUpload"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspApplyUploadUGC struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Video struct {
|
||||
StorageSignature string `json:"storageSignature"`
|
||||
StoragePath string `json:"storagePath"`
|
||||
} `json:"video"`
|
||||
StorageAppID int `json:"storageAppId"`
|
||||
StorageBucket string `json:"storageBucket"`
|
||||
StorageRegion string `json:"storageRegion"`
|
||||
StorageRegionV5 string `json:"storageRegionV5"`
|
||||
Domain string `json:"domain"`
|
||||
VodSessionKey string `json:"vodSessionKey"`
|
||||
TempCertificate struct {
|
||||
SecretID string `json:"secretId"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Token string `json:"token"`
|
||||
ExpiredTime int `json:"expiredTime"`
|
||||
} `json:"tempCertificate"`
|
||||
AppID int `json:"appId"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
StorageRegionV50 string `json:"StorageRegionV5"`
|
||||
MiniProgramAccelerateHost string `json:"MiniProgramAccelerateHost"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RspCommitUploadUGC struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Video struct {
|
||||
URL string `json:"url"`
|
||||
VerifyContent string `json:"verify_content"`
|
||||
} `json:"video"`
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RspFinishUpload struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
func fileToObj(f File) *model.Object {
|
||||
obj := &model.Object{}
|
||||
if f.Type == "CLASS" {
|
||||
obj.Name = f.ClassInfo.Name
|
||||
obj.ID = strconv.Itoa(f.ClassInfo.ClassID)
|
||||
obj.IsFolder = true
|
||||
obj.Modified = f.ClassInfo.CreateTime
|
||||
obj.Size = 0
|
||||
} else if f.Type == "MATERIAL" {
|
||||
obj.Name = f.MaterialInfo.BasicInfo.Name
|
||||
obj.ID = f.MaterialInfo.BasicInfo.MaterialID
|
||||
obj.IsFolder = false
|
||||
obj.Modified = f.MaterialInfo.BasicInfo.CreateTime
|
||||
obj.Size = int64(f.MaterialInfo.MediaInfo.Size)
|
||||
}
|
||||
return obj
|
||||
}
|
289
drivers/vtencent/util.go
Normal file
289
drivers/vtencent/util.go
Normal file
@ -0,0 +1,289 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/http_range"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (d *Vtencent) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"cookie": d.Cookie,
|
||||
"content-type": "application/json",
|
||||
"origin": d.conf.origin,
|
||||
"referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
} else {
|
||||
req.SetBody("{}")
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "Code").ToString()
|
||||
if code != "Success" {
|
||||
switch code {
|
||||
case "AuthFailure.SessionInvalid":
|
||||
if err != nil {
|
||||
return nil, errors.New(code)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New(code)
|
||||
}
|
||||
return d.request(url, method, callback, resp)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) ugcRequest(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"cookie": d.Cookie,
|
||||
"content-type": "application/json",
|
||||
"origin": d.conf.origin,
|
||||
"referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
} else {
|
||||
req.SetBody("{}")
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "Code").ToInt()
|
||||
if code != 0 {
|
||||
message := utils.Json.Get(res.Body(), "message").ToString()
|
||||
if len(message) == 0 {
|
||||
message = utils.Json.Get(res.Body(), "msg").ToString()
|
||||
}
|
||||
return nil, errors.New(message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) LoadUser() (string, error) {
|
||||
api := "https://api.vs.tencent.com/SaaS/Account/DescribeAccount"
|
||||
res, err := d.request(api, http.MethodPost, func(req *resty.Request) {}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.Json.Get(res, "Data", "TfUid").ToString(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) GetFiles(dirId string) ([]File, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/SearchResource"
|
||||
form := fmt.Sprintf(`{
|
||||
"Text":"",
|
||||
"Text":"",
|
||||
"Offset":0,
|
||||
"Limit":20000,
|
||||
"Sort":{"Field":"%s","Order":"%s"},
|
||||
"CreateTimeRanges":[],
|
||||
"MaterialTypes":[],
|
||||
"ReviewStatuses":[],
|
||||
"Tags":[],
|
||||
"SearchScopes":[{"Owner":{"Type":"PERSON","Id":"%s"},"ClassId":%s,"SearchOneDepth":true}]
|
||||
}`, d.Addition.OrderBy, d.Addition.OrderDirection, d.TfUid, dirId)
|
||||
var resps RspFiles
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return []File{}, err
|
||||
}
|
||||
return resps.Data.ResourceInfoSet, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) CreateUploadMaterial(classId int, fileName string, UploadSummaryKey string) (RspCreatrMaterial, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/CreateUploadMaterial"
|
||||
form := base.Json{"Owner": base.Json{"Type": "PERSON", "Id": d.TfUid},
|
||||
"MaterialType": "VIDEO", "Name": fileName, "ClassId": classId,
|
||||
"UploadSummaryKey": UploadSummaryKey}
|
||||
var resps RspCreatrMaterial
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspCreatrMaterial{}, err
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) ApplyUploadUGC(signature string, stream model.FileStreamer) (RspApplyUploadUGC, error) {
|
||||
api := "https://vod2.qcloud.com/v3/index.php?Action=ApplyUploadUGC"
|
||||
form := base.Json{
|
||||
"signature": signature,
|
||||
"videoName": stream.GetName(),
|
||||
"videoType": strings.ReplaceAll(path.Ext(stream.GetName()), ".", ""),
|
||||
"videoSize": stream.GetSize(),
|
||||
}
|
||||
var resps RspApplyUploadUGC
|
||||
_, err := d.ugcRequest(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspApplyUploadUGC{}, err
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) CommitUploadUGC(signature string, vodSessionKey string) (RspCommitUploadUGC, error) {
|
||||
api := "https://vod2.qcloud.com/v3/index.php?Action=CommitUploadUGC"
|
||||
form := base.Json{
|
||||
"signature": signature,
|
||||
"vodSessionKey": vodSessionKey,
|
||||
}
|
||||
var resps RspCommitUploadUGC
|
||||
rsp, err := d.ugcRequest(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspCommitUploadUGC{}, err
|
||||
}
|
||||
if len(resps.Data.Video.URL) == 0 {
|
||||
return RspCommitUploadUGC{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FinishUploadMaterial(SummaryKey string, VodVerifyKey string, UploadContext, VodFileId string) (RspFinishUpload, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/FinishUploadMaterial"
|
||||
form := base.Json{
|
||||
"UploadContext": UploadContext,
|
||||
"VodVerifyKey": VodVerifyKey,
|
||||
"VodFileId": VodFileId,
|
||||
"UploadFullKey": SummaryKey}
|
||||
var resps RspFinishUpload
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspFinishUpload{}, err
|
||||
}
|
||||
if len(resps.Data.MaterialID) == 0 {
|
||||
return RspFinishUpload{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FinishHashUploadMaterial(SummaryKey string, UploadContext string) (RspFinishUpload, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/FinishUploadMaterial"
|
||||
var resps RspFinishUpload
|
||||
form := base.Json{
|
||||
"UploadContext": UploadContext,
|
||||
"UploadFullKey": SummaryKey}
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspFinishUpload{}, err
|
||||
}
|
||||
if len(resps.Data.MaterialID) == 0 {
|
||||
return RspFinishUpload{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FileUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
classId, err := strconv.Atoi(dstDir.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const chunkLength int64 = 1024 * 1024 * 10
|
||||
reader, err := stream.RangeRead(http_range.Range{Start: 0, Length: chunkLength})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunkHash, err := utils.HashReader(utils.SHA1, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rspCreatrMaterial, err := d.CreateUploadMaterial(classId, stream.GetName(), chunkHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rspCreatrMaterial.Data.QuickUpload {
|
||||
SummaryKey := stream.GetHash().GetHash(utils.SHA1)
|
||||
if len(SummaryKey) < utils.SHA1.Width {
|
||||
if SummaryKey, err = utils.HashReader(utils.SHA1, stream); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
UploadContext := rspCreatrMaterial.Data.UploadContext
|
||||
_, err = d.FinishHashUploadMaterial(SummaryKey, UploadContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
hash := sha1.New()
|
||||
rspUGC, err := d.ApplyUploadUGC(rspCreatrMaterial.Data.VodUploadSign, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := rspUGC.Data
|
||||
certificate := params.TempCertificate
|
||||
cfg := &aws.Config{
|
||||
HTTPClient: base.HttpClient,
|
||||
// S3ForcePathStyle: aws.Bool(true),
|
||||
Credentials: credentials.NewStaticCredentials(certificate.SecretID, certificate.SecretKey, certificate.Token),
|
||||
Region: aws.String(params.StorageRegionV5),
|
||||
Endpoint: aws.String(fmt.Sprintf("cos.%s.myqcloud.com", params.StorageRegionV5)),
|
||||
}
|
||||
ss, err := session.NewSession(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(ss)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: aws.String(fmt.Sprintf("%s-%d", params.StorageBucket, params.StorageAppID)),
|
||||
Key: ¶ms.Video.StoragePath,
|
||||
Body: io.TeeReader(stream, io.MultiWriter(hash, driver.NewProgress(stream.GetSize(), up))),
|
||||
}
|
||||
_, err = uploader.UploadWithContext(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rspCommitUGC, err := d.CommitUploadUGC(rspCreatrMaterial.Data.VodUploadSign, rspUGC.Data.VodSessionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
VodVerifyKey := rspCommitUGC.Data.Video.VerifyContent
|
||||
VodFileId := rspCommitUGC.Data.FileID
|
||||
UploadContext := rspCreatrMaterial.Data.UploadContext
|
||||
SummaryKey := hex.EncodeToString(hash.Sum(nil))
|
||||
_, err = d.FinishUploadMaterial(SummaryKey, VodVerifyKey, UploadContext, VodFileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user