support onedrive

pull/548/head
微凉 2021-11-02 23:53:05 +08:00
parent 510292c8b0
commit 04e89754f7
4 changed files with 357 additions and 30 deletions

View File

@ -112,7 +112,7 @@ type AliFile struct {
Url string `json:"url"`
}
func AliToFile(file AliFile) *model.File {
func (a AliDrive) FormatFile(file *AliFile) *model.File {
f := &model.File{
Name: file.Name,
Size: file.Size,
@ -148,7 +148,7 @@ func (a AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFile, er
SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(JsonStr(Json{
SetBody(Json{
"drive_id": account.DriveId,
"fields": "*",
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
@ -160,11 +160,20 @@ func (a AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFile, er
"parent_file_id": fileId,
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
"url_expire_sec": 14400,
})).Post("https://api.aliyundrive.com/v2/file/list")
}).Post("https://api.aliyundrive.com/v2/file/list")
if err != nil {
return nil, err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = a.RefreshToken(account)
if err != nil {
return nil, err
} else {
_ = model.SaveAccount(*account)
return a.GetFiles(fileId, account)
}
}
return nil, fmt.Errorf("%s", e.Message)
}
marker = resp.NextMarker
@ -202,12 +211,12 @@ func (a AliDrive) Path(path string, account *model.Account) (*model.File, []*mod
if err == nil {
file, ok := cache.(AliFile)
if ok {
return AliToFile(file), nil, nil
return a.FormatFile(&file), nil, nil
} else {
files, _ := cache.([]AliFile)
res := make([]*model.File, 0)
for _, file = range files {
res = append(res, AliToFile(file))
res = append(res, a.FormatFile(&file))
}
return nil, res, nil
}
@ -227,12 +236,12 @@ func (a AliDrive) Path(path string, account *model.Account) (*model.File, []*mod
if file.Name == name {
found = true
if file.Type == "file" {
url,err := a.Link(path,account)
url, err := a.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = url
return AliToFile(file), nil, nil
return a.FormatFile(&file), nil, nil
} else {
fileId = file.FileId
break
@ -250,7 +259,7 @@ func (a AliDrive) Path(path string, account *model.Account) (*model.File, []*mod
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil)
res := make([]*model.File, 0)
for _, file := range files {
res = append(res, AliToFile(file))
res = append(res, a.FormatFile(&file))
}
return nil, res, nil
}
@ -275,34 +284,39 @@ func (a AliDrive) Link(path string, account *model.Account) (string, error) {
return "", err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = a.RefreshToken(account)
if err != nil {
return "", err
} else {
_ = model.SaveAccount(*account)
return a.Link(path, account)
}
}
return "", fmt.Errorf("%s", e.Message)
}
return resp["url"].(string), nil
}
type AliTokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func AliRefreshToken(refresh string) (string, string, error) {
func (a AliDrive) RefreshToken(account *model.Account) error {
url := "https://auth.aliyundrive.com/v2/account/token"
var resp AliTokenResp
var resp TokenResp
var e AliRespError
_, err := aliClient.R().
//ForceContentType("application/json").
SetBody(JsonStr(Json{"refresh_token": refresh, "grant_type": "refresh_token"})).
SetBody(Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
SetResult(&resp).
SetError(&e).
Post(url)
if err != nil {
return "", "", err
return err
}
log.Debugf("%+v,%+v", resp, e)
if e.Code != "" {
return "", "", fmt.Errorf("failed to refresh token: %s", e.Message)
return fmt.Errorf("failed to refresh token: %s", e.Message)
}
return resp.RefreshToken, resp.AccessToken, nil
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
func (a AliDrive) Save(account *model.Account, old *model.Account) error {
@ -315,25 +329,24 @@ func (a AliDrive) Save(account *model.Account, old *model.Account) error {
if account.Limit == 0 {
account.Limit = 200
}
refresh, access, err := AliRefreshToken(account.RefreshToken)
err := a.RefreshToken(account)
if err != nil {
return err
}
var resp Json
_, _ = aliClient.R().SetResult(&resp).
SetBody("{}").
SetHeader("authorization", "Bearer\t"+access).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
Post("https://api.aliyundrive.com/v2/user/get")
log.Debugf("user info: %+v", resp)
account.DriveId = resp["default_drive_id"].(string)
account.RefreshToken, account.AccessToken = refresh, access
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
name := account.Name
newAccount, ok := model.GetAccount(name)
if !ok {
return
}
newAccount.RefreshToken, newAccount.AccessToken, err = AliRefreshToken(newAccount.RefreshToken)
err = a.RefreshToken(&newAccount)
if err != nil {
newAccount.Status = err.Error()
}

View File

@ -1,7 +1,6 @@
package drivers
import (
"encoding/json"
"github.com/Xhofe/alist/model"
"github.com/gofiber/fiber/v2"
)
@ -12,7 +11,7 @@ type Driver interface {
Link(path string, account *model.Account) (string, error)
Save(account *model.Account, old *model.Account) error
Proxy(ctx *fiber.Ctx)
Preview(path string, account *model.Account) (interface{},error)
Preview(path string, account *model.Account) (interface{}, error)
// TODO
//MakeDir(path string, account *model.Account) error
//Move(src string, des string, account *model.Account) error
@ -24,10 +23,16 @@ type Item struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"`
Values string `json:"values"`
Required bool `json:"required"`
Description string `json:"description"`
}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var driversMap = map[string]Driver{}
func RegisterDriver(name string, driver Driver) {
@ -48,8 +53,3 @@ func GetDrivers() map[string][]Item {
}
type Json map[string]interface{}
func JsonStr(j Json) string {
data, _ := json.Marshal(j)
return string(data)
}

307
drivers/onedrive.go Normal file
View File

@ -0,0 +1,307 @@
package drivers
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"github.com/gofiber/fiber/v2"
"github.com/robfig/cron/v3"
"path/filepath"
"time"
)
type Onedrive struct{}
var oneClient = resty.New()
type OnedriveHost struct {
Oauth string
Api string
}
var onedriveHostMap = map[string]OnedriveHost{
"global": {
Oauth: "https://login.microsoftonline.com",
Api: "https://graph.microsoft.com",
},
"cn": {
Oauth: "https://login.chinacloudapi.cn",
Api: "https://microsoftgraph.chinacloudapi.cn",
},
"us": {
Oauth: "https://login.microsoftonline.us",
Api: "https://graph.microsoft.us",
},
"de": {
Oauth: "https://login.microsoftonline.de",
Api: "https://graph.microsoft.de",
},
}
func init() {
RegisterDriver("Onedrive", &Onedrive{})
oneClient.SetRetryCount(3)
}
func (o Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
path = filepath.Join(account.RootFolder, path)
host, _ := onedriveHostMap[account.Zone]
if auth {
return host.Oauth
}
switch account.OnedriveType {
case "onedrive":
{
if path == "/" {
return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
} else {
return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
}
}
case "sharepoint":
{
if path == "/" {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
} else {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
}
}
default:
return ""
}
}
func (o Onedrive) Items() []Item {
return []Item{
{
Name: "zone",
Label: "zone",
Type: "select",
Required: true,
Values: "global,cn,us,de",
Description: "",
},
{
Name: "onedrive_type",
Label: "onedrive type",
Type: "select",
Required: true,
Values: "onedrive,sharepoint",
},
{
Name: "client_id",
Label: "client id",
Type: "string",
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Type: "string",
Required: true,
},
{
Name: "redirect_uri",
Label: "redirect uri",
Type: "string",
Required: true,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Required: true,
},
{
Name: "site_url",
Label: "site url",
Type: "string",
Required: false,
},
{
Name: "root_folder",
Label: "root folder path",
Type: "string",
Required: false,
},
}
}
type OneTokenErr struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (o Onedrive) RefreshToken(account *model.Account) error {
url := o.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp TokenResp
var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
"redirect_uri": account.RedirectUri,
"refresh_token": account.RefreshToken,
}).Post(url)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf("%s", e.ErrorDescription)
}
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
type OneFile struct {
Name string `json:"name"`
Size int64 `json:"size"`
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
Url string `json:"@microsoft.graph.downloadUrl"`
File struct {
MimeType string `json:"mimeType"`
} `json:"file"`
}
type OneFiles struct {
Value []OneFile `json:"value"`
}
type OneRespErr struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (o Onedrive) FormatFile(file *OneFile) *model.File {
f := &model.File{
Name: file.Name,
Size: file.Size,
UpdatedAt: file.LastModifiedDateTime,
Driver: "OneDrive",
Url: file.Url,
}
if file.File.MimeType == "" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
func (o Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
var files OneFiles
var e OneRespErr
_, err := oneClient.R().SetResult(&files).SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(o.GetMetaUrl(account, false, path) + "/children")
if err != nil {
return nil, err
}
if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message)
}
return files.Value, nil
}
func (o Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
var file OneFile
var e OneRespErr
_, err := oneClient.R().SetResult(&file).SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(o.GetMetaUrl(account, false, path))
if err != nil {
return nil, err
}
if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message)
}
return &file, nil
}
func (o Onedrive) Path(path string, account *model.Account) (*model.File, []*model.File, error) {
path = utils.ParsePath(path)
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
if err == nil {
files, _ := cache.([]*model.File)
return nil, files, nil
}
file, err := o.GetFile(account, path)
if err != nil {
return nil, nil, err
}
if file.File.MimeType != "" {
return o.FormatFile(file), nil, nil
} else {
files, err := o.GetFiles(account, path)
if err != nil {
return nil, nil, err
}
res := make([]*model.File, 0)
for _, file := range files {
res = append(res, o.FormatFile(&file))
}
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), res, nil)
return nil, res, nil
}
}
func (o Onedrive) Link(path string, account *model.Account) (string, error) {
file, err := o.GetFile(account, path)
if err != nil {
return "", err
}
if file.File.MimeType == "" {
return "", fmt.Errorf("can't down folder")
}
return file.Url, nil
}
func (o Onedrive) Save(account *model.Account, old *model.Account) error {
_, ok := onedriveHostMap[account.Zone]
if !ok {
return fmt.Errorf("no [%s] zone", account.Zone)
}
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
account.RootFolder = utils.ParsePath(account.RootFolder)
err := o.RefreshToken(account)
if err != nil {
return err
}
cronId, err := conf.Cron.AddFunc("@every 1h", func() {
name := account.Name
newAccount, ok := model.GetAccount(name)
if !ok {
return
}
err = o.RefreshToken(&newAccount)
if err != nil {
newAccount.Status = err.Error()
}
_ = model.SaveAccount(newAccount)
})
if err != nil {
return err
}
account.CronId = int(cronId)
err = model.SaveAccount(*account)
if err != nil {
return err
}
return nil
}
func (o Onedrive) Proxy(ctx *fiber.Ctx) {
}
func (o Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
}
var _ Driver = (*Onedrive)(nil)

View File

@ -22,6 +22,13 @@ type Account struct {
Proxy bool `json:"proxy"`
UpdatedAt *time.Time `json:"updated_at"`
Search bool `json:"search"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Zone string `json:"zone"`
RedirectUri string `json:"redirect_uri"`
SiteUrl string `json:"site_url"`
SiteId string
OnedriveType string `json:"onedrive_type"`
}
var accountsMap = map[string]Account{}