feat: add chaoxing and vtencent driver (#5526 close #3347)

* 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:
zhangxiang
2023-11-23 21:40:16 +08:00
committed by GitHub
parent d7f66138eb
commit 1280070438
12 changed files with 1605 additions and 3 deletions

203
drivers/vtencent/drive.go Normal file
View 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
View 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",
},
}
})
}

View 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
View 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
View 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: &params.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
}