mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-09-19 04:06:18 +08:00

- Implement complete MediaFire storage driver - Add authentication via session_token and cookie - Support all core operations: List, Get, Link, Put, Copy, Move, Remove, Rename, MakeDir - Include thumbnail generation for media files - Handle MediaFire's resumable upload API with multi-unit transfers - Add proper error handling and progress reporting Co-authored-by: Da3zKi7 <da3zki7@duck.com>
428 lines
10 KiB
Go
428 lines
10 KiB
Go
package mediafire
|
|
|
|
/*
|
|
Package mediafire
|
|
Author: Da3zKi7<da3zki7@duck.com>
|
|
Date: 2025-09-11
|
|
|
|
D@' 3z K!7 - The King Of Cracking
|
|
*/
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
|
)
|
|
|
|
type Mediafire struct {
|
|
model.Storage
|
|
Addition
|
|
|
|
actionToken string
|
|
|
|
appBase string
|
|
apiBase string
|
|
hostBase string
|
|
maxRetries int
|
|
|
|
secChUa string
|
|
secChUaPlatform string
|
|
userAgent string
|
|
}
|
|
|
|
func (d *Mediafire) Config() driver.Config {
|
|
return config
|
|
}
|
|
|
|
func (d *Mediafire) GetAddition() driver.Additional {
|
|
return &d.Addition
|
|
}
|
|
|
|
func (d *Mediafire) Init(ctx context.Context) error {
|
|
if d.SessionToken == "" {
|
|
return fmt.Errorf("Init :: [MediaFire] {critical} missing sessionToken")
|
|
}
|
|
|
|
if d.Cookie == "" {
|
|
return fmt.Errorf("Init :: [MediaFire] {critical} missing Cookie")
|
|
}
|
|
|
|
if _, err := d.getSessionToken(ctx); err != nil {
|
|
|
|
//fmt.Printf("Init :: Obtain Session Token \n\n")
|
|
|
|
if err := d.renewToken(ctx); err != nil {
|
|
|
|
//fmt.Printf("Init :: Renew Session Token \n\n")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Mediafire) Drop(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (d *Mediafire) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
files, err := d.getFiles(ctx, dir.GetID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
|
return d.fileToObj(src), nil
|
|
})
|
|
}
|
|
|
|
func (d *Mediafire) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
|
|
downloadUrl, err := d.getDirectDownloadLink(ctx, file.GetID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := base.NoRedirectClient.R().SetDoNotParseResponse(true).SetContext(ctx).Get(downloadUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
_ = res.RawBody().Close()
|
|
}()
|
|
|
|
if res.StatusCode() == 302 {
|
|
downloadUrl = res.Header().Get("location")
|
|
}
|
|
|
|
return &model.Link{
|
|
URL: downloadUrl,
|
|
Header: http.Header{
|
|
"Origin": []string{d.appBase},
|
|
"Referer": []string{d.appBase + "/"},
|
|
"sec-ch-ua": []string{d.secChUa},
|
|
"sec-ch-ua-platform": []string{d.secChUaPlatform},
|
|
"User-Agent": []string{d.userAgent},
|
|
//"User-Agent": []string{base.UserAgent},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (d *Mediafire) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
|
data := map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"parent_key": parentDir.GetID(),
|
|
"foldername": dirName,
|
|
}
|
|
|
|
var resp MediafireFolderCreateResponse
|
|
_, err := d.postForm("/folder/create.php", data, &resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.Response.Result != "Success" {
|
|
return nil, fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
|
|
}
|
|
|
|
created, _ := time.Parse("2006-01-02T15:04:05Z", resp.Response.CreatedUTC)
|
|
|
|
return &model.ObjThumb{
|
|
Object: model.Object{
|
|
ID: resp.Response.FolderKey,
|
|
Name: resp.Response.Name,
|
|
Size: 0,
|
|
Modified: created,
|
|
Ctime: created,
|
|
IsFolder: true,
|
|
},
|
|
Thumbnail: model.Thumbnail{},
|
|
}, nil
|
|
}
|
|
|
|
func (d *Mediafire) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
|
var data map[string]string
|
|
var endpoint string
|
|
|
|
if srcObj.IsDir() {
|
|
|
|
endpoint = "/folder/move.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"folder_key_src": srcObj.GetID(),
|
|
"folder_key_dst": dstDir.GetID(),
|
|
}
|
|
} else {
|
|
|
|
endpoint = "/file/move.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"quick_key": srcObj.GetID(),
|
|
"folder_key": dstDir.GetID(),
|
|
}
|
|
}
|
|
|
|
var resp MediafireMoveResponse
|
|
_, err := d.postForm(endpoint, data, &resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.Response.Result != "Success" {
|
|
return nil, fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
|
|
}
|
|
|
|
return srcObj, nil
|
|
}
|
|
|
|
func (d *Mediafire) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
|
var data map[string]string
|
|
var endpoint string
|
|
|
|
if srcObj.IsDir() {
|
|
|
|
endpoint = "/folder/update.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"folder_key": srcObj.GetID(),
|
|
"foldername": newName,
|
|
}
|
|
} else {
|
|
|
|
endpoint = "/file/update.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"quick_key": srcObj.GetID(),
|
|
"filename": newName,
|
|
}
|
|
}
|
|
|
|
var resp MediafireRenameResponse
|
|
_, err := d.postForm(endpoint, data, &resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.Response.Result != "Success" {
|
|
return nil, fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
|
|
}
|
|
|
|
return &model.ObjThumb{
|
|
Object: model.Object{
|
|
ID: srcObj.GetID(),
|
|
Name: newName,
|
|
Size: srcObj.GetSize(),
|
|
Modified: srcObj.ModTime(),
|
|
Ctime: srcObj.CreateTime(),
|
|
IsFolder: srcObj.IsDir(),
|
|
},
|
|
Thumbnail: model.Thumbnail{},
|
|
}, nil
|
|
}
|
|
|
|
func (d *Mediafire) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
|
var data map[string]string
|
|
var endpoint string
|
|
|
|
if srcObj.IsDir() {
|
|
|
|
endpoint = "/folder/copy.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"folder_key_src": srcObj.GetID(),
|
|
"folder_key_dst": dstDir.GetID(),
|
|
}
|
|
} else {
|
|
|
|
endpoint = "/file/copy.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"quick_key": srcObj.GetID(),
|
|
"folder_key": dstDir.GetID(),
|
|
}
|
|
}
|
|
|
|
var resp MediafireCopyResponse
|
|
_, err := d.postForm(endpoint, data, &resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.Response.Result != "Success" {
|
|
return nil, fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
|
|
}
|
|
|
|
var newID string
|
|
if srcObj.IsDir() {
|
|
if len(resp.Response.NewFolderKeys) > 0 {
|
|
newID = resp.Response.NewFolderKeys[0]
|
|
}
|
|
} else {
|
|
if len(resp.Response.NewQuickKeys) > 0 {
|
|
newID = resp.Response.NewQuickKeys[0]
|
|
}
|
|
}
|
|
|
|
return &model.ObjThumb{
|
|
Object: model.Object{
|
|
ID: newID,
|
|
Name: srcObj.GetName(),
|
|
Size: srcObj.GetSize(),
|
|
Modified: srcObj.ModTime(),
|
|
Ctime: srcObj.CreateTime(),
|
|
IsFolder: srcObj.IsDir(),
|
|
},
|
|
Thumbnail: model.Thumbnail{},
|
|
}, nil
|
|
}
|
|
|
|
func (d *Mediafire) Remove(ctx context.Context, obj model.Obj) error {
|
|
var data map[string]string
|
|
var endpoint string
|
|
|
|
if obj.IsDir() {
|
|
|
|
endpoint = "/folder/delete.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"folder_key": obj.GetID(),
|
|
}
|
|
} else {
|
|
|
|
endpoint = "/file/delete.php"
|
|
data = map[string]string{
|
|
"session_token": d.SessionToken,
|
|
"response_format": "json",
|
|
"quick_key": obj.GetID(),
|
|
}
|
|
}
|
|
|
|
var resp MediafireRemoveResponse
|
|
_, err := d.postForm(endpoint, data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.Response.Result != "Success" {
|
|
return fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Mediafire) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
|
|
_, err := d.PutResult(ctx, dstDir, file, up)
|
|
return err
|
|
}
|
|
|
|
func (d *Mediafire) PutResult(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
|
|
|
tempFile, err := file.CacheFullInTempFile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tempFile.Close()
|
|
|
|
osFile, ok := tempFile.(*os.File)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected *os.File, got %T", tempFile)
|
|
}
|
|
|
|
fileHash, err := d.calculateSHA256(osFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
checkResp, err := d.uploadCheck(ctx, file.GetName(), file.GetSize(), fileHash, dstDir.GetID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if checkResp.Response.ResumableUpload.AllUnitsReady == "yes" {
|
|
up(100.0)
|
|
}
|
|
|
|
if checkResp.Response.HashExists == "yes" && checkResp.Response.InAccount == "yes" {
|
|
up(100.0)
|
|
existingFile, err := d.getExistingFileInfo(ctx, fileHash, file.GetName(), dstDir.GetID())
|
|
if err == nil {
|
|
return existingFile, nil
|
|
}
|
|
}
|
|
|
|
var pollKey string
|
|
|
|
if checkResp.Response.ResumableUpload.AllUnitsReady != "yes" {
|
|
|
|
var err error
|
|
|
|
pollKey, err = d.uploadUnits(ctx, osFile, checkResp, file.GetName(), fileHash, dstDir.GetID(), up)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
|
|
pollKey = checkResp.Response.ResumableUpload.UploadKey
|
|
}
|
|
|
|
//fmt.Printf("pollKey: %+v\n", pollKey)
|
|
|
|
pollResp, err := d.pollUpload(ctx, pollKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quickKey := pollResp.Response.Doupload.QuickKey
|
|
|
|
return &model.ObjThumb{
|
|
Object: model.Object{
|
|
ID: quickKey,
|
|
Name: file.GetName(),
|
|
Size: file.GetSize(),
|
|
},
|
|
Thumbnail: model.Thumbnail{},
|
|
}, nil
|
|
}
|
|
|
|
func (d *Mediafire) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
|
// TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
func (d *Mediafire) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
|
// TODO list args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
func (d *Mediafire) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
|
|
// TODO return link of file args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
func (d *Mediafire) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
|
|
// TODO extract args.InnerPath path in the archive srcObj to the dstDir location, optional
|
|
// a folder with the same name as the archive file needs to be created to store the extracted results if args.PutIntoNewDir
|
|
// return errs.NotImplement to use an internal archive tool
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
//func (d *Mediafire) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
|
// return nil, errs.NotSupport
|
|
//}
|
|
|
|
var _ driver.Driver = (*Mediafire)(nil)
|