diff --git a/bootstrap/drivers.go b/bootstrap/drivers.go index 826200ce..63a2ea7c 100644 --- a/bootstrap/drivers.go +++ b/bootstrap/drivers.go @@ -6,4 +6,5 @@ import ( _ "github.com/Xhofe/alist/drivers/alidrive" _ "github.com/Xhofe/alist/drivers/googledrive" _ "github.com/Xhofe/alist/drivers/native" + _ "github.com/Xhofe/alist/drivers/onedrive" ) \ No newline at end of file diff --git a/drivers/189cloud/189.go b/drivers/189cloud/189.go index f1fa9ba8..3d770ea0 100644 --- a/drivers/189cloud/189.go +++ b/drivers/189cloud/189.go @@ -76,11 +76,6 @@ type Cloud189Down struct { FileDownloadUrl string `json:"fileDownloadUrl"` } -func init() { - drivers.RegisterDriver("189Cloud", &Cloud189{}) - client189Map = make(map[string]*resty.Client, 0) -} - type LoginResp struct { Msg string `json:"msg"` Result int `json:"result"` @@ -316,3 +311,8 @@ func b64tohex(a string) string { } return d } + +func init() { + drivers.RegisterDriver(driverName, &Cloud189{}) + client189Map = make(map[string]*resty.Client, 0) +} \ No newline at end of file diff --git a/drivers/native/native.go b/drivers/native/native.go index e67a87d1..d85a7001 100644 --- a/drivers/native/native.go +++ b/drivers/native/native.go @@ -5,5 +5,5 @@ import ( ) func init() { - drivers.RegisterDriver("Native", &Native{}) + drivers.RegisterDriver(driverName, &Native{}) } diff --git a/drivers/onedrive.go b/drivers/onedrive.go deleted file mode 100644 index a7c83d63..00000000 --- a/drivers/onedrive.go +++ /dev/null @@ -1,354 +0,0 @@ -package drivers - -import ( - "fmt" - "github.com/Xhofe/alist/conf" - "github.com/Xhofe/alist/model" - "github.com/Xhofe/alist/utils" - "github.com/gin-gonic/gin" - "github.com/go-resty/resty/v2" - "github.com/robfig/cron/v3" - log "github.com/sirupsen/logrus" - "path/filepath" - "time" -) - -type Onedrive struct{} - -func (o Onedrive) File(path string, account *model.Account) (*model.File, error) { - panic("implement me") -} - -func (o Onedrive) Files(path string, account *model.Account) ([]model.File, error) { - panic("implement me") -} - -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) - log.Debugf(path) - host, _ := onedriveHostMap[account.Zone] - if auth { - return host.Oauth - } - switch account.OnedriveType { - case "onedrive": - { - if path == "/" || 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: "proxy", - Label: "proxy", - Type: "bool", - Required: true, - Description: "allow proxy", - }, - { - 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_id", - Label: "site id", - Type: "string", - Required: false, - }, - { - Name: "root_folder", - Label: "root folder path", - Type: "string", - Required: false, - }, - { - Name: "order_by", - Label: "order_by", - Type: "select", - Values: "name,size,lastModifiedDateTime", - Required: false, - }, - { - Name: "order_direction", - Label: "order_direction", - Type: "select", - Values: "asc,desc", - 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 { - account.Status = err.Error() - return err - } - if e.Error != "" { - account.Status = e.ErrorDescription - return fmt.Errorf("%s", e.ErrorDescription) - } else { - account.Status = "work" - } - 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"` - NextLink string `json:"@odata.nextLink"` -} - -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 res []OneFile - nextLink := o.GetMetaUrl(account, false, path) + "/children" - if account.OrderBy != "" { - nextLink += fmt.Sprintf("?orderby=%s", account.OrderBy) - if account.OrderDirection != "" { - nextLink += fmt.Sprintf(" %s", account.OrderDirection) - } - } - for nextLink != "" { - var files OneFiles - var e OneRespErr - _, err := oneClient.R().SetResult(&files).SetError(&e). - SetHeader("Authorization", "Bearer "+account.AccessToken). - Get(nextLink) - if err != nil { - return nil, err - } - if e.Error.Code != "" { - return nil, fmt.Errorf("%s", e.Error.Message) - } - res = append(res, files.Value...) - nextLink = files.NextLink - } - return res, 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 - log.Debugf("onedrive account name: %s", name) - newAccount, ok := model.GetAccount(name) - log.Debugf("onedrive account: %+v", newAccount) - if !ok { - return - } - err = o.RefreshToken(&newAccount) - _ = 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(c *gin.Context, account *model.Account) { - c.Request.Header.Del("Origin") -} - -func (o Onedrive) Preview(path string, account *model.Account) (interface{}, error) { - return nil, nil -} - -var _ Driver = (*Onedrive)(nil) diff --git a/drivers/onedrive/driver.go b/drivers/onedrive/driver.go new file mode 100644 index 00000000..f93bbe45 --- /dev/null +++ b/drivers/onedrive/driver.go @@ -0,0 +1,193 @@ +package onedrive + +import ( + "fmt" + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/drivers" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/gin-gonic/gin" + "github.com/robfig/cron/v3" + log "github.com/sirupsen/logrus" +) + +type Onedrive struct{} + +var driverName = "Onedrive" + +func (driver Onedrive) Items() []drivers.Item { + return []drivers.Item{ + { + Name: "proxy", + Label: "proxy", + Type: "bool", + Required: true, + Description: "allow proxy", + }, + { + 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_id", + Label: "site id", + Type: "string", + Required: false, + }, + { + Name: "root_folder", + Label: "root folder path", + Type: "string", + Required: false, + }, + { + Name: "order_by", + Label: "order_by", + Type: "select", + Values: "name,size,lastModifiedDateTime", + Required: false, + }, + { + Name: "order_direction", + Label: "order_direction", + Type: "select", + Values: "asc,desc", + Required: false, + }, + } +} + +func (driver 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 := driver.RefreshToken(account) + if err != nil { + return err + } + cronId, err := conf.Cron.AddFunc("@every 1h", func() { + name := account.Name + log.Debugf("onedrive account name: %s", name) + newAccount, ok := model.GetAccount(name) + log.Debugf("onedrive account: %+v", newAccount) + if !ok { + return + } + err = driver.RefreshToken(&newAccount) + _ = model.SaveAccount(&newAccount) + }) + if err != nil { + return err + } + account.CronId = int(cronId) + err = model.SaveAccount(account) + if err != nil { + return err + } + return nil +} + +func (driver Onedrive) File(path string, account *model.Account) (*model.File, error) { + path = utils.ParsePath(path) + rawFile, err := driver.GetFile(account, path) + if err != nil { + return nil, err + } + file := driver.FormatFile(rawFile) + return file, nil +} + +func (driver Onedrive) Files(path string, account *model.Account) ([]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 files, nil + } + rawFiles, err := driver.GetFiles(account, path) + if err != nil { + return nil, err + } + files := make([]model.File, 0) + for _, file := range rawFiles { + files = append(files, *driver.FormatFile(&file)) + } + _ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil) + return files, nil +} + +func (driver Onedrive) Link(path string, account *model.Account) (string, error) { + file, err := driver.GetFile(account, path) + if err != nil { + return "", err + } + if file.File.MimeType == "" { + return "", fmt.Errorf("can't down folder") + } + return file.Url, nil +} + +func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) { + log.Debugf("onedrive path: %s", path) + file, err := driver.File(path, account) + if err != nil { + return nil, nil, err + } + if file.Type != conf.FOLDER { + //file.Url, _ = driver.Link(path, account) + return file, nil, nil + } + files, err := driver.Files(path, account) + if err != nil { + return nil, nil, err + } + return nil, files, nil +} + +func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) { + c.Request.Header.Del("Origin") +} + +func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) { + return nil, nil +} diff --git a/drivers/onedrive/onedrive.go b/drivers/onedrive/onedrive.go new file mode 100644 index 00000000..a537b563 --- /dev/null +++ b/drivers/onedrive/onedrive.go @@ -0,0 +1,185 @@ +package onedrive + +import ( + "fmt" + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/drivers" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" + "path/filepath" + "time" +) + +var oneClient = resty.New() + +type Host struct { + Oauth string + Api string +} + +var onedriveHostMap = map[string]Host{ + "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 (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string { + path = filepath.Join(account.RootFolder, path) + log.Debugf(path) + host, _ := onedriveHostMap[account.Zone] + if auth { + return host.Oauth + } + switch account.OnedriveType { + case "onedrive": + { + if path == "/" || 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 "" + } +} + +type OneTokenErr struct { + Error string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +func (driver Onedrive) RefreshToken(account *model.Account) error { + url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token" + var resp drivers.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 { + account.Status = err.Error() + return err + } + if e.Error != "" { + account.Status = e.ErrorDescription + return fmt.Errorf("%s", e.ErrorDescription) + } else { + account.Status = "work" + } + 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"` + NextLink string `json:"@odata.nextLink"` +} + +type OneRespErr struct { + Error struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"error"` +} + +func (driver Onedrive) FormatFile(file *OneFile) *model.File { + f := &model.File{ + Name: file.Name, + Size: file.Size, + UpdatedAt: file.LastModifiedDateTime, + Driver: driverName, + Url: file.Url, + } + if file.File.MimeType == "" { + f.Type = conf.FOLDER + } else { + f.Type = utils.GetFileType(filepath.Ext(file.Name)) + } + return f +} + +func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) { + var res []OneFile + nextLink := driver.GetMetaUrl(account, false, path) + "/children" + if account.OrderBy != "" { + nextLink += fmt.Sprintf("?orderby=%s", account.OrderBy) + if account.OrderDirection != "" { + nextLink += fmt.Sprintf(" %s", account.OrderDirection) + } + } + for nextLink != "" { + var files OneFiles + var e OneRespErr + _, err := oneClient.R().SetResult(&files).SetError(&e). + SetHeader("Authorization", "Bearer "+account.AccessToken). + Get(nextLink) + if err != nil { + return nil, err + } + if e.Error.Code != "" { + return nil, fmt.Errorf("%s", e.Error.Message) + } + res = append(res, files.Value...) + nextLink = files.NextLink + } + return res, nil +} + +func (driver 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(driver.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 +} + +var _ drivers.Driver = (*Onedrive)(nil) + +func init() { + drivers.RegisterDriver(driverName, &Onedrive{}) + oneClient.SetRetryCount(3) +} \ No newline at end of file