Compare commits

...

37 Commits

Author SHA1 Message Date
a22903533e 🐛 set random seed 2022-02-04 16:08:15 +08:00
86cda58b22 🚧 echo password 2022-02-04 14:58:48 +08:00
7804cf9d5c 🔒 random webdav admin password 2022-02-04 14:39:11 +08:00
2bb7036110 🔧 change logo 2022-02-03 21:28:13 +08:00
ba545555cf 📝 update readme 2022-02-03 21:00:08 +08:00
be55ca690c 🔧 change default logo 2022-02-03 20:43:33 +08:00
9013add749 🔒 random initial password 2022-02-03 12:27:50 +08:00
3201b6da76 🐛 fix #376 windows webdav upload 2022-02-02 18:48:34 +08:00
feb42f1f4b 🔥 delete proxy interface 2022-02-02 18:03:54 +08:00
6f14d0eb5c 🎇 baidu disk support 2022-02-02 17:32:11 +08:00
7530d8f5b2 🐛 fix lanzou for download change 2022-02-01 22:33:19 +08:00
e25fe05a53 yandex disk support 2022-02-01 17:15:11 +08:00
8e0ab8f780 🔥 remove onedrive refresh token cron 2022-02-01 15:20:17 +08:00
cb2a3c2b42 🎨 change proxy interface 2022-02-01 14:28:21 +08:00
1b6ec94f33 🐛 fix s3 custom host 2022-02-01 11:34:32 +08:00
cb23edc1fe 🐛 fix #462 check connect while get ftp client 2022-01-31 11:13:29 +08:00
6fd05d7d72 🐛 fix connMap not init 2022-01-30 00:55:12 +08:00
f26ac57569 🐛 fix ftp conn not store 2022-01-30 00:04:31 +08:00
2434ac54d0 🐛 fix webdav 2022-01-29 18:36:22 +08:00
f25b557327 📝 update readme 2022-01-29 15:31:06 +08:00
81a0706d01 189 chunk upload 2022-01-29 11:16:40 +08:00
5f6b576cbf 🔧 switch cdn to jsdelivr 2022-01-28 23:29:38 +08:00
549877f71e 🔥 Delete Text 2022-01-28 23:21:11 +08:00
c6a5ba9b91 🚧 189 chunk upload 2022-01-28 19:51:54 +08:00
1a69d80489 🎨 Pull away Path 2022-01-28 11:04:56 +08:00
b797f4302c 🎇 use tempFile to cal md5 2022-01-27 23:48:29 +08:00
bf9aa5c3d3 🔒 not allowed use relative path of native 2022-01-27 15:10:33 +08:00
7390e19a7a 🔒 not allowed down with relative path 2022-01-27 15:05:17 +08:00
b31a12a0cc 🔒 not allowed access using relative path for native 2022-01-27 14:54:20 +08:00
26ce001782 🎇 add ocr for 189 2022-01-27 12:34:49 +08:00
a2c7ff3262 ✏️ Invalid Token 2022-01-26 16:27:38 +08:00
8fc7c716c0 copy api 2022-01-26 14:07:51 +08:00
c70fc3fc4b finish teambition chunk upload 2022-01-26 14:05:35 +08:00
df513b7dc0 teambition upload (<= 20 MB) 2022-01-23 14:03:04 +08:00
2a9598f4c6 🐛 fix #407 189cloud use local sort 2022-01-21 19:01:33 +08:00
224c20779c 🔧 add webp to image types 2022-01-20 23:01:59 +08:00
5d722298cb 📝 update readme 2022-01-20 22:38:53 +08:00
61 changed files with 2228 additions and 460 deletions

View File

@ -1,5 +1,5 @@
<div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3>
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
<p><em>🗂Another file list program that supports multiple storage, powered by Gin and React.</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
@ -12,6 +12,7 @@
</div>
---
English | [中文](./README_cn.md)
@ -35,6 +36,8 @@ English | [中文](./README_cn.md)
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
- [x] [Mediatrack](https://www.mediatrack.cn/)
- [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [Baidu Disk](http://pan.baidu.com/)
- [x] Easy to deploy and out-of-the-box
- [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode
@ -50,7 +53,7 @@ English | [中文](./README_cn.md)
- [x] Cloudflare workers proxy
- [x] File/Folder package download
- [x] Support video list playback and subtitles(ass,srt,vtt)
- [x] Web upload(Can allow visitors to upload) and delete
- [x] Web upload(Can allow visitors to upload), delete, mkdir, rename, move and copy
## Discussion

View File

@ -1,5 +1,5 @@
<div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3>
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
@ -11,6 +11,7 @@
</a>
</div>
---
[English](./README.md) | 中文
@ -34,6 +35,8 @@
- [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ )
- [x] [分秒帧](https://www.mediatrack.cn/)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [百度云](http://pan.baidu.com/)
- [x] 部署方便,开箱即用
- [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览
@ -49,7 +52,7 @@
- [x] Cloudflare workers 中转
- [x] 文件/文件夹打包下载
- [x] 支持视频列表播放和字幕(ass,srt,vtt)
- [x] 网页上传(可以允许访客上传),删除
- [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
## 讨论

View File

@ -22,7 +22,7 @@ func Init() bool {
log.Errorf(err.Error())
return false
}
log.Infof("current password: %s", pass.Value)
fmt.Printf("your password: %s\n", pass.Value)
return false
}
server.InitIndex()

View File

@ -19,6 +19,7 @@ func InitAccounts() {
if !ok {
log.Errorf("no [%s] driver", account.Type)
} else {
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
err := driver.Save(&accounts[i], nil)
if err != nil {
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())

View File

@ -5,6 +5,7 @@ import (
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io/ioutil"
"os"
)
// InitConf init config
@ -41,4 +42,8 @@ func InitConf() {
if err != nil {
log.Fatalf("update config struct error: %s", err.Error())
}
err = os.MkdirAll("data/temp", 0700)
if err != nil {
log.Fatalf("create temp dir error: %s", err.Error())
}
}

View File

@ -12,6 +12,9 @@ func InitLog() {
log.SetLevel(log.DebugLevel)
log.SetReportCaller(true)
}
if conf.Password {
log.SetLevel(log.WarnLevel)
}
log.SetFormatter(&log.TextFormatter{
//DisableColors: true,
ForceColors: true,

View File

@ -3,6 +3,7 @@ package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"strings"
@ -27,7 +28,7 @@ func InitSettings() {
},
{
Key: "password",
Value: "alist",
Value: utils.RandomStr(8),
Description: "password",
Type: "string",
Access: model.PRIVATE,
@ -35,7 +36,7 @@ func InitSettings() {
},
{
Key: "logo",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
Description: "logo",
Type: "string",
Access: model.PUBLIC,
@ -43,7 +44,7 @@ func InitSettings() {
},
{
Key: "favicon",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
Description: "favicon",
Type: "string",
Access: model.PUBLIC,
@ -157,7 +158,7 @@ func InitSettings() {
},
{
Key: "WebDAV username",
Value: "alist_admin",
Value: "admin",
Description: "WebDAV username",
Type: "string",
Access: model.PRIVATE,
@ -165,7 +166,7 @@ func InitSettings() {
},
{
Key: "WebDAV password",
Value: "alist_admin",
Value: utils.RandomStr(8),
Description: "WebDAV password",
Type: "string",
Access: model.PRIVATE,
@ -189,7 +190,7 @@ func InitSettings() {
},
{
Key: "Visitor WebDAV username",
Value: "alist_visitor",
Value: "guest",
Description: "Visitor WebDAV username",
Type: "string",
Access: model.PRIVATE,
@ -197,7 +198,7 @@ func InitSettings() {
},
{
Key: "Visitor WebDAV password",
Value: "alist_visitor",
Value: "guest",
Description: "Visitor WebDAV password",
Type: "string",
Access: model.PRIVATE,
@ -219,6 +220,14 @@ func InitSettings() {
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "ocr api",
Value: "https://api.xhofe.top/ocr/file/json",
Description: "Used to identify verification codes",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
}
for i, _ := range settings {
v := settings[i]
@ -227,6 +236,9 @@ func InitSettings() {
if err != nil {
if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v)
if v.Key == "password" {
log.Infof("Initial password: %s", v.Value)
}
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}

View File

@ -36,7 +36,7 @@ func DefaultConfig() *Config {
return &Config{
Address: "0.0.0.0",
Port: 5244,
Assets: "zhimg",
Assets: "jsdelivr",
Database: Database{
Type: "sqlite3",
Port: 0,

View File

@ -36,10 +36,10 @@ var (
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
)
var settingsMap = make(map[string]string, 0)
var settingsMap = make(map[string]string)
func Set(key string, value string) {
settingsMap[key] = value
@ -78,6 +78,7 @@ var (
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
"Visitor WebDAV username", "Visitor WebDAV password",
"default page size", "load type",
"ocr api",
}
)

View File

@ -1,6 +1,8 @@
package _23
import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
@ -10,9 +12,11 @@ import (
"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/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
)
@ -192,9 +196,9 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil
}
func (driver Pan123) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("origin")
}
//func (driver Pan123) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("origin")
//}
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
@ -294,10 +298,37 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
return base.ErrNotFolder
}
parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile("data/temp", "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New()
_, err = io.Copy(h, tempFile)
if err != nil {
return err
}
etag := hex.EncodeToString(h.Sum(nil))
log.Debugln("md5:", etag)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
data := base.Json{
"driveId": 0,
"duplicate": true,
"etag": "836aae6cac845e17fce51919594737d0", //maybe file's md5
"etag": etag,
"fileName": file.GetFileName(),
"parentFileId": parentFileId,
"size": file.GetSize(),
@ -310,6 +341,9 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
if err != nil {
return err
}
if resp.Data.Key == "" {
return nil
}
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
Region: aws.String("123pan"),
@ -324,7 +358,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key,
Body: file,
Body: tempFile,
}
_, err = uploader.Upload(input)
if err != nil {

View File

@ -17,7 +17,7 @@ import (
func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
url := "https://yun.139.com" + pathname
req := base.RestyClient.R()
randStr := randomStr(16)
randStr := utils.RandomStr(16)
ts := time.Now().Format("2006-01-02 15:04:05")
log.Debugf("%+v", data)
body, err := utils.Json.Marshal(data)

View File

@ -7,7 +7,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io"
"math"
@ -163,9 +162,9 @@ func (driver Cloud139) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil
}
func (driver Cloud139) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver Cloud139) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"math/rand"
"net/url"
"sort"
"strconv"
@ -13,16 +12,6 @@ import (
"time"
)
func randomStr(n int) string {
builder := strings.Builder{}
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < n; i++ {
r := rand.Intn(len(t))
builder.WriteString(t[r : r+1])
}
return builder.String()
}
func encodeURIComponent(str string) string {
r := url.QueryEscape(str)
r = strings.Replace(r, "+", "%20", -1)

View File

@ -2,16 +2,9 @@ package _89
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
@ -19,14 +12,11 @@ import (
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"io"
"math"
mathRand "math/rand"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strconv"
@ -35,6 +25,7 @@ import (
)
var client189Map map[string]*resty.Client
var infoMap = make(map[string]Rsa)
func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) {
client, ok := client189Map[account.Name]
@ -153,9 +144,33 @@ func (driver Cloud189) Login(account *model.Account) error {
vCodeRS := ""
if vCodeID != "" {
// need ValidateCode
log.Debugf("try to identify verification codes")
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
imgRes, err := client.R().SetHeaders(map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}).Get(u)
if err != nil {
return err
}
vRes, err := client.R().SetMultipartField(
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
Post(conf.GetStr("ocr api"))
if err != nil {
return err
}
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
}
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", vCodeRS)
}
userRsa := RsaEncode([]byte(account.Username), jRsakey)
passwordRsa := RsaEncode([]byte(account.Password), jRsakey)
userRsa := RsaEncode([]byte(account.Username), jRsakey, true)
passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp
res, err := client.R().
@ -199,39 +214,6 @@ func (driver Cloud189) Login(account *model.Account) error {
return nil
}
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
func (driver Cloud189) isFamily(account *model.Account) bool {
return account.InternalType == "Family"
}
@ -249,8 +231,8 @@ func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud1
"mediaType": "0",
"folderId": fileId,
"iconOption": "5",
"orderBy": account.OrderBy,
"descending": account.OrderDirection,
"orderBy": "lastOpTime", //account.OrderBy
"descending": "true", //account.OrderDirection
}, nil, nil, account)
if err != nil {
return nil, err
@ -321,7 +303,7 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
if err != nil {
return nil, err
}
log.Debug(res.String())
//log.Debug(res.String())
if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account)
@ -338,61 +320,198 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
}
func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) {
//info, ok := infoMap[account.Name]
//if !ok {
// info = Info{}
// infoMap[account.Name] = info
//} else {
// log.Debugf("hit")
//}
//if info.SessionKey != "" {
// return info.SessionKey, nil
//}
resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account)
if err != nil {
return "", err
}
return jsoniter.Get(resp, "sessionKey").ToString(), nil
sessionKey := jsoniter.Get(resp, "sessionKey").ToString()
//info.SessionKey = sessionKey
return sessionKey, nil
}
func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) {
rsa, ok := infoMap[account.Name]
if !ok {
rsa = Rsa{}
infoMap[account.Name] = rsa
}
now := time.Now().UnixMilli()
if rsa.Expire > now {
return rsa.PubKey, rsa.PkId, nil
}
resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account)
if err != nil {
return "", "", err
}
return jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString(), nil
pubKey, pkId := jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString()
rsa.PubKey, rsa.PkId = pubKey, pkId
rsa.Expire = jsoniter.Get(resp, "expire").ToInt64()
return pubKey, pkId, nil
}
func (driver Cloud189) UploadRequest(url string, form map[string]string, account *model.Account) ([]byte, error) {
sessionKey, err := driver.GetSessionKey(account)
if err != nil {
return nil, err
}
//func (driver Cloud189) UploadRequest1(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
// //sessionKey, err := driver.GetSessionKey(account)
// //if err != nil {
// // return nil, err
// //}
// sessionKey := account.DriveId
// pubKey, pkId, err := driver.GetResKey(account)
// log.Debugln(sessionKey, pubKey, pkId)
// if err != nil {
// return nil, err
// }
// xRId := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// pkey := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")[0 : 16+int(16*mathRand.Float32())]
// params := hex.EncodeToString(AesEncrypt([]byte(qs(form)), []byte(pkey[0:16])))
// date := strconv.FormatInt(time.Now().Unix(), 10)
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", date)
// a.Set("params", params)
// signature := hex.EncodeToString(SHA1(EncodeParam(a), pkey))
// encryptionText := RsaEncode([]byte(pkey), pubKey, false)
// headers := map[string]string{
// "signature": signature,
// "sessionKey": sessionKey,
// "encryptionText": encryptionText,
// "pkId": pkId,
// "x-request-id": xRId,
// "x-request-date": date,
// }
// req := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data := res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
//
//func (driver Cloud189) UploadRequest2(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
// c := strconv.FormatInt(time.Now().UnixMilli(), 10)
// r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
// l = l[0 : 16+int(16*mathRand.Float32())]
//
// e := qs(form)
// data := AesEncrypt([]byte(e), []byte(l[0:16]))
// h := hex.EncodeToString(data)
//
// sessionKey := account.DriveId
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", c)
// a.Set("params", h)
// g := SHA1(EncodeParam(a), l)
//
// pubKey, pkId, err := driver.GetResKey(account)
// if err != nil {
// return nil, err
// }
// b := RsaEncode([]byte(l), pubKey, false)
// client, err := driver.getClient(account)
// if err != nil {
// return nil, err
// }
// req := client.R()
// req.Header.Set("accept", "application/json;charset=UTF-8")
// req.Header.Set("SessionKey", sessionKey)
// req.Header.Set("Signature", hex.EncodeToString(g))
// req.Header.Set("X-Request-Date", c)
// req.Header.Set("X-Request-ID", r)
// req.Header.Set("EncryptionText", b)
// req.Header.Set("PkId", pkId)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data = res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
func (driver Cloud189) UploadRequest(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
l = l[0 : 16+int(16*utils.Rand.Float32())]
e := qs(form)
data := AesEncrypt([]byte(e), []byte(l[0:16]))
h := hex.EncodeToString(data)
sessionKey := account.DriveId
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, uri, c, h), l)
pubKey, pkId, err := driver.GetResKey(account)
if err != nil {
return nil, err
}
xRId := uuid.New().String()
pkey := strings.ReplaceAll(xRId, "-", "")[:mathRand.Intn(16)+16]
params := aesEncrypt(qs(form), pkey[:16])
date := strconv.FormatInt(time.Now().Unix(), 10)
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, url, date, params), pkey)
encryptionText := RsaEncode([]byte(pkey), pubKey)
headers := map[string]string{
"signature": signature,
"sessionKey": sessionKey,
"encryptionText": encryptionText,
"pkId": pkId,
"x-request-id": xRId,
"x-request-date": date,
"origin": "https://cloud.189.cn",
"referer": "https://cloud.189.cn/",
}
log.Debugf("%+v\n%s", headers, params)
res, err := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params).Get("https://upload.cloud.189.cn" + url)
b := RsaEncode([]byte(l), pubKey, false)
client, err := driver.getClient(account)
if err != nil {
return nil, err
}
log.Debug(res.String())
data := res.Body()
req := client.R()
req.Header.Set("accept", "application/json;charset=UTF-8")
req.Header.Set("SessionKey", sessionKey)
req.Header.Set("Signature", signature)
req.Header.Set("X-Request-Date", c)
req.Header.Set("X-Request-ID", r)
req.Header.Set("EncryptionText", b)
req.Header.Set("PkId", pkId)
if resp != nil {
req.SetResult(resp)
}
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
if err != nil {
return nil, err
}
//log.Debug(res.String())
data = res.Body()
if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
return nil, errors.New(jsoniter.Get(data, "msg").ToString())
return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
}
return data, nil
}
// Upload Error: decrypt encryptionText failed
// NewUpload Error: signature check false
func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error {
sessionKey, err := driver.GetSessionKey(account)
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
account.DriveId = sessionKey
}
_ = model.SaveAccount(account)
const DEFAULT uint64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
var finish uint64 = 0
@ -409,11 +528,14 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
"fileSize": strconv.FormatInt(int64(file.Size), 10),
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
"lazyCheck": "1",
}, account)
}, account, nil)
if err != nil {
return err
}
uploadFileId := jsoniter.Get(res, "data.uploadFileId").ToString()
uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
//_, err = driver.UploadRequest("/person/getUploadedPartsInfo", map[string]string{
// "uploadFileId": uploadFileId,
//}, account, nil)
var i int64
var byteSize uint64
md5s := make([]string, 0)
@ -423,180 +545,82 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
if DEFAULT < byteSize {
byteSize = DEFAULT
}
log.Debugf("%d,%d", byteSize, finish)
//log.Debugf("%d,%d", byteSize, finish)
byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData)
log.Debug(err, n)
//log.Debug(err, n)
if err != nil {
return err
}
finish += uint64(n)
md5Bytes := getMd5(byteData)
md5Str := hex.EncodeToString(md5Bytes)
md5Hex := hex.EncodeToString(md5Bytes)
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
md5s = append(md5s, md5Str)
md5s = append(md5s, strings.ToUpper(md5Hex))
md5Sum.Write(byteData)
//log.Debugf("md5Bytes: %+v,md5Str:%s,md5Base64:%s", md5Bytes, md5Hex, md5Base64)
var resp UploadUrlsResp
res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
"uploadFileId": uploadFileId,
}, account)
}, account, &resp)
if err != nil {
return err
}
uploadData := jsoniter.Get(res, "uploadUrls.partNumber_"+strconv.FormatInt(i, 10))
headers := strings.Split(uploadData.Get("requestHeader").ToString(), "&")
req, err := http.NewRequest("PUT", uploadData.Get("requestURL").ToString(), bytes.NewBuffer(byteData))
uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
log.Debugf("uploadData: %+v", uploadData)
requestURL := uploadData.RequestURL
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
req, _ := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData))
for _, v := range uploadHeaders {
i := strings.Index(v, "=")
req.Header.Set(v[0:i], v[i+1:])
}
r, err := base.HttpClient.Do(req)
log.Debugf("%+v %+v", r, r.Request.Header)
if err != nil {
return err
}
for _, header := range headers {
kv := strings.Split(header, "=")
req.Header.Set(kv[0], strings.Join(kv[1:], "="))
}
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
log.Debugf("%+v", res)
}
id := md5Sum.Sum(nil)
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
sliceMd5 := fileMd5
if file.GetSize() > DEFAULT {
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
}
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
"uploadFileId": uploadFileId,
"fileMd5": hex.EncodeToString(id),
"sliceMd5": utils.GetMD5Encode(strings.Join(md5s, "\n")),
"fileMd5": fileMd5,
"sliceMd5": sliceMd5,
"lazyCheck": "1",
}, account)
}, account, nil)
account.DriveId, _ = driver.GetSessionKey(account)
return err
}
func random() string {
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000))
}
func RsaEncode(origData []byte, j_rsakey string) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
//return base.ErrNotImplement
client, err := driver.getClient(account)
if err != nil {
log.Errorf("err: %s", err.Error())
return err
}
return b64tohex(base64.StdEncoding.EncodeToString(b))
}
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if e == 1 {
d += int2char(c << 2)
// api refer to PanIndex
res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
}
return d
}
func qs(form map[string]string) string {
strList := make([]string, 0)
for k, v := range form {
strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
return nil
}
return strings.Join(strList, "&")
}
func aesEncrypt(data, key string) string {
encrypted := AesEncryptECB([]byte(data), []byte(key))
//return string(encrypted)
return hex.EncodeToString(encrypted)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
length := (len(origData) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
copy(plain, origData)
pad := byte(len(plain) - len(origData))
for i := len(origData); i < len(plain); i++ {
plain[i] = pad
}
encrypted = make([]byte, len(plain))
// 分组分块加密
for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
}
return encrypted
}
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(encrypted))
//
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}
return decrypted[:trim]
}
func generateKey(key []byte) (genKey []byte) {
genKey = make([]byte, 16)
copy(genKey, key)
for i := 16; i < len(key); {
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
genKey[j] ^= key[i]
}
}
return genKey
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client, 0)
log.Debugf(res.String())
return errors.New(res.String())
}

View File

@ -1,14 +1,11 @@
package _89
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
)
@ -17,7 +14,8 @@ type Cloud189 struct{}
func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{
Name: "189Cloud",
Name: "189Cloud",
LocalSort: true,
}
}
@ -55,20 +53,20 @@ func (driver Cloud189) Items() []base.Item {
// Label: "family id",
// Type: base.TypeString,
//},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "name,size,lastOpTime,createdDate",
Required: true,
},
{
Name: "order_direction",
Label: "desc",
Type: base.TypeSelect,
Values: "true,false",
Required: true,
},
//{
// Name: "order_by",
// Label: "order_by",
// Type: base.TypeSelect,
// Values: "name,size,lastOpTime,createdDate",
// Required: true,
//},
//{
// Name: "order_direction",
// Label: "desc",
// Type: base.TypeSelect,
// Values: "true,false",
// Required: true,
//},
}
}
@ -199,9 +197,9 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil
}
func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) {
ctx.Request.Header.Del("Origin")
}
//func (driver Cloud189) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
//}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
@ -349,29 +347,8 @@ func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) er
if file == nil {
return base.ErrEmptyFile
}
client, err := driver.getClient(account)
if err != nil {
return err
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
// api refer to PanIndex
res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
}
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
return nil
}
log.Debugf(res.String())
return errors.New(res.String())
return driver.NewUpload(file, account)
//return driver.OldUpload(file, account)
}
var _ base.Driver = (*Cloud189)(nil)

55
drivers/189/types.go Normal file
View File

@ -0,0 +1,55 @@
package _89
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
type UploadUrlsResp struct {
Code string `json:"code"`
UploadUrls map[string]Part `json:"uploadUrls"`
}
type Part struct {
RequestURL string `json:"requestURL"`
RequestHeader string `json:"requestHeader"`
}
//type Info struct {
// SessionKey string
// Rsa Rsa
//}
type Rsa struct {
Expire int64 `json:"expire"`
PkId string `json:"pkId"`
PubKey string `json:"pubKey"`
}

187
drivers/189/util.go Normal file
View File

@ -0,0 +1,187 @@
package _89
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/url"
"regexp"
"strconv"
"strings"
)
func random() string {
return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
}
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil {
log.Errorf("err: %s", err.Error())
}
res := base64.StdEncoding.EncodeToString(b)
if hex {
return b64tohex(res)
}
return res
}
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
}
if e == 1 {
d += int2char(c << 2)
}
return d
}
func qs(form map[string]string) string {
f := make(url.Values)
for k, v := range form {
f.Set(k, v)
}
return EncodeParam(f)
//strList := make([]string, 0)
//for k, v := range form {
// strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
//}
//return strings.Join(strList, "&")
}
func EncodeParam(v url.Values) string {
if v == nil {
return ""
}
var buf strings.Builder
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
for _, k := range keys {
vs := v[k]
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v)
}
}
return buf.String()
}
func AesEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
if block == nil {
return []byte{}
}
data = PKCS7Padding(data, block.BlockSize())
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], data[bs:be])
}
return decrypted
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client)
}
func decodeURIComponent(str string) string {
r, _ := url.PathUnescape(str)
//r = strings.ReplaceAll(r, " ", "+")
return r
}
func Random(v string) string {
reg := regexp.MustCompilePOSIX("[xy]")
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
var i int64
t := int64(16 * utils.Rand.Float32())
if msg[0] == 120 {
i = t
} else {
i = 3&t | 8
}
return []byte(strconv.FormatInt(i, 16))
})
return string(data)
}
//func SHA1(v, l string) []byte {
// key := []byte(l)
// mac := hmac.New(sha1.New, key)
// mac.Write([]byte(v))
// return mac.Sum(nil)
//}

View File

@ -8,7 +8,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"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"
"io"
@ -191,6 +190,12 @@ func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link,
return nil, fmt.Errorf("%s", e.Message)
}
return &base.Link{
Headers: []base.Header{
{
Name: "Referer",
Value: "https://www.aliyundrive.com/",
},
},
Url: resp["url"].(string),
}, nil
}
@ -212,10 +217,10 @@ func (driver AliDrive) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil
}
func (driver AliDrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/")
}
//func (driver AliDrive) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
// r.Header.Set("Referer", "https://www.aliyundrive.com/")
//}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account)

View File

@ -7,7 +7,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath"
"strings"
"time"
@ -148,7 +147,7 @@ func (driver Alist) Path(path string, account *model.Account) (*model.File, []mo
return nil, resp.Data.Files, nil
}
func (driver Alist) Proxy(c *gin.Context, account *model.Account) {}
//func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
var resp PathResp

View File

@ -6,6 +6,7 @@ import (
_ "github.com/Xhofe/alist/drivers/189"
_ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/alist"
_ "github.com/Xhofe/alist/drivers/baidu"
"github.com/Xhofe/alist/drivers/base"
_ "github.com/Xhofe/alist/drivers/ftp"
_ "github.com/Xhofe/alist/drivers/google"
@ -18,6 +19,7 @@ import (
_ "github.com/Xhofe/alist/drivers/shandian"
_ "github.com/Xhofe/alist/drivers/teambition"
_ "github.com/Xhofe/alist/drivers/webdav"
_ "github.com/Xhofe/alist/drivers/yandex"
log "github.com/sirupsen/logrus"
"strings"
)

186
drivers/baidu/baidu.go Normal file
View File

@ -0,0 +1,186 @@
package baidu
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
"path"
"strconv"
)
func (driver Baidu) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
err = driver.refreshToken(account)
}
if err != nil {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Baidu) refreshToken(account *model.Account) error {
u := "https://openapi.baidu.com/oauth/2.0/token"
var resp base.TokenResp
var e TokenErrResp
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
}).Get(u)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
account.Status = "work"
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
return nil
}
func (driver Baidu) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://pan.baidu.com/rest/2.0" + pathname
req := base.RestyClient.R()
req.SetQueryParam("access_token", account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Patch:
res, err = req.Patch(u)
case base.Delete:
res, err = req.Delete(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
errno := jsoniter.Get(res.Body(), "errno").ToInt()
if errno != 0 {
if errno == -6 {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
}
return driver.Request(pathname, method, headers, query, form, data, resp, account)
}
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
}
return res.Body(), nil
}
func (driver Baidu) Get(pathname string, params map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Get, nil, params, nil, nil, resp, account)
}
func (driver Baidu) Post(pathname string, params map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, params, nil, data, resp, account)
}
func (driver Baidu) manage(opera string, filelist interface{}, account *model.Account) ([]byte, error) {
params := map[string]string{
"method": "filemanager",
"opera": opera,
}
marshal, err := utils.Json.Marshal(filelist)
if err != nil {
return nil, err
}
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
return driver.Post("/xpan/file", params, data, nil, account)
}
func (driver Baidu) GetFiles(dir string, account *model.Account) ([]model.File, error) {
dir = utils.Join(account.RootFolder, dir)
start := 0
limit := 200
params := map[string]string{
"method": "list",
"dir": dir,
"web": "web",
}
if account.OrderBy != "" {
params["order"] = account.OrderBy
if account.OrderDirection == "desc" {
params["desc"] = "1"
}
}
res := make([]model.File, 0)
for {
params["start"] = strconv.Itoa(start)
params["limit"] = strconv.Itoa(limit)
start += limit
var resp ListResp
_, err := driver.Get("/xpan/file", params, &resp, account)
if err != nil {
return nil, err
}
if len(resp.List) == 0 {
break
}
for _, f := range resp.List {
file := model.File{
Id: strconv.FormatInt(f.FsId, 10),
Name: f.ServerFilename,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: getTime(f.ServerMtime),
Thumbnail: f.Thumbs.Url3,
}
if f.Isdir == 1 {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(path.Ext(f.ServerFilename))
}
res = append(res, file)
}
}
return res, nil
}
func (driver Baidu) create(path string, size uint64, isdir int, uploadid, block_list string, account *model.Account) ([]byte, error) {
params := map[string]string{
"method": "create",
}
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir)
if uploadid != "" {
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
}
return driver.Post("/xpan/file", params, data, nil, account)
}
func init() {
base.RegisterDriver(&Baidu{})
}

355
drivers/baidu/driver.go Normal file
View File

@ -0,0 +1,355 @@
package baidu
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"strconv"
"strings"
)
type Baidu struct{}
func (driver Baidu) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Baidu.Disk",
}
}
func (driver Baidu) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Default: "/",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Default: "name",
Values: "name,time,size",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Default: "asc",
Required: false,
},
{
Name: "client_id",
Label: "client id",
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
return driver.RefreshToken(account)
}
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Baidu) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
files, err := driver.GetFiles(path, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
if file.IsDir() {
return nil, base.ErrNotFile
}
var resp DownloadResp
params := map[string]string{
"method": "filemetas",
"fsids": fmt.Sprintf("[%s]", file.Id),
"dlink": "1",
}
_, err = driver.Get("/xpan/multimedia", params, &resp, account)
if err != nil {
return nil, err
}
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken)
res, err := base.NoRedirectClient.R().Head(u)
if err != nil {
return nil, err
}
if res.StatusCode() == 302 {
u = res.Header().Get("location")
}
return &base.Link{
Url: u,
Headers: []base.Header{
{Name: "User-Agent", Value: "pan.baidu.com"},
}}, nil
}
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Baidu) Proxy(r *http.Request, account *model.Account) {
// r.Header.Set("User-Agent", "pan.baidu.com")
//}
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Baidu) MakeDir(path string, account *model.Account) error {
_, err := driver.create(utils.Join(account.RootFolder, path), 0, 1, "", "", account)
return err
}
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("move", data, account)
return err
}
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
newname := utils.Base(dst)
data := []base.Json{
{
"path": path,
"newname": newname,
},
}
_, err := driver.manage("rename", data, account)
return err
}
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("copy", data, account)
return err
}
func (driver Baidu) Delete(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
data := []string{path}
_, err := driver.manage("delete", data, account)
return err
}
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
tempFile, err := ioutil.TempFile("data/temp", "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
var Default uint64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
var SliceSize uint64 = 256 * 1024
// cal md5
h1 := md5.New()
h2 := md5.New()
block_list := make([]string, 0)
content_md5 := ""
slice_md5 := ""
left := file.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
content_md5 = hex.EncodeToString(h1.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
if file.GetSize() <= SliceSize {
slice_md5 = content_md5
} else {
sliceData := make([]byte, SliceSize)
_, err = io.ReadFull(tempFile, sliceData)
if err != nil {
return err
}
h2.Write(sliceData)
slice_md5 = hex.EncodeToString(h2.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
}
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
path, file.GetSize(),
block_list_str,
content_md5, slice_md5)
params := map[string]string{
"method": "precreate",
}
var precreateResp PrecreateResp
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
if err != nil {
return err
}
log.Debugf("%+v", precreateResp)
if precreateResp.ReturnType == 2 {
return nil
}
params = map[string]string{
"method": "upload",
"access_token": account.AccessToken,
"type": "tmpfile",
"path": path,
"uploadid": precreateResp.Uploadid,
}
left = file.GetSize()
for _, partseq := range precreateResp.BlockList {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
if err != nil {
return err
}
log.Debugln(res.String())
}
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
return err
}
var _ base.Driver = (*Baidu)(nil)

84
drivers/baidu/types.go Normal file
View File

@ -0,0 +1,84 @@
package baidu
type TokenErrResp struct {
ErrorDescription string `json:"error_description"`
Error string `json:"error"`
}
type File struct {
//TkbindId int `json:"tkbind_id"`
//OwnerType int `json:"owner_type"`
//Category int `json:"category"`
//RealCategory string `json:"real_category"`
FsId int64 `json:"fs_id"`
ServerMtime int64 `json:"server_mtime"`
//OperId int `json:"oper_id"`
//ServerCtime int `json:"server_ctime"`
Thumbs struct {
//Icon string `json:"icon"`
Url3 string `json:"url3"`
//Url2 string `json:"url2"`
//Url1 string `json:"url1"`
} `json:"thumbs"`
//Wpfile int `json:"wpfile"`
//LocalMtime int `json:"local_mtime"`
Size int64 `json:"size"`
//ExtentTinyint7 int `json:"extent_tinyint7"`
Path string `json:"path"`
//Share int `json:"share"`
//ServerAtime int `json:"server_atime"`
//Pl int `json:"pl"`
//LocalCtime int `json:"local_ctime"`
ServerFilename string `json:"server_filename"`
//Md5 string `json:"md5"`
//OwnerId int `json:"owner_id"`
//Unlist int `json:"unlist"`
Isdir int `json:"isdir"`
}
type ListResp struct {
Errno int `json:"errno"`
GuidInfo string `json:"guid_info"`
List []File `json:"list"`
RequestId int64 `json:"request_id"`
Guid int `json:"guid"`
}
type DownloadResp struct {
Errmsg string `json:"errmsg"`
Errno int `json:"errno"`
List []struct {
//Category int `json:"category"`
//DateTaken int `json:"date_taken,omitempty"`
Dlink string `json:"dlink"`
//Filename string `json:"filename"`
//FsId int64 `json:"fs_id"`
//Height int `json:"height,omitempty"`
//Isdir int `json:"isdir"`
//Md5 string `json:"md5"`
//OperId int `json:"oper_id"`
//Path string `json:"path"`
//ServerCtime int `json:"server_ctime"`
//ServerMtime int `json:"server_mtime"`
//Size int `json:"size"`
//Thumbs struct {
// Icon string `json:"icon,omitempty"`
// Url1 string `json:"url1,omitempty"`
// Url2 string `json:"url2,omitempty"`
// Url3 string `json:"url3,omitempty"`
//} `json:"thumbs"`
//Width int `json:"width,omitempty"`
} `json:"list"`
//Names struct {
//} `json:"names"`
RequestId string `json:"request_id"`
}
type PrecreateResp struct {
Path string `json:"path"`
Uploadid string `json:"uploadid"`
ReturnType int `json:"return_type"`
BlockList []int `json:"block_list"`
Errno int `json:"errno"`
RequestId int64 `json:"request_id"`
}

18
drivers/baidu/util.go Normal file
View File

@ -0,0 +1,18 @@
package baidu
import (
"net/url"
"strings"
"time"
)
func getTime(t int64) *time.Time {
tm := time.Unix(t, 0)
return &tm
}
func encodeURIComponent(str string) string {
r := url.QueryEscape(str)
r = strings.ReplaceAll(r, "+", "%20")
return r
}

View File

@ -2,7 +2,6 @@ package base
import (
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
@ -38,8 +37,8 @@ type Driver interface {
Link(args Args, account *model.Account) (*Link, error)
// Path 取路径(文件或文件夹)
Path(path string, account *model.Account) (*model.File, []model.File, error)
// Proxy 代理处理
Proxy(c *gin.Context, account *model.Account)
// Deprecated Proxy 代理处理
//Proxy(r *http.Request, account *model.Account)
// Preview 预览
Preview(path string, account *model.Account) (interface{}, error)
// MakeDir 创建文件夹
@ -62,6 +61,7 @@ type Item struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"`
Default string `json:"default"`
Values string `json:"values"`
Required bool `json:"required"`
Description string `json:"description"`
@ -84,7 +84,7 @@ func GetDriversMap() map[string]Driver {
}
func GetDrivers() map[string][]Item {
res := make(map[string][]Item, 0)
res := make(map[string][]Item)
for k, v := range driversMap {
if v.Config().OnlyProxy {
res[k] = v.Items()

View File

@ -12,6 +12,8 @@ var (
ErrNotSupport = errors.New("not support")
ErrNotFolder = errors.New("not a folder")
ErrEmptyFile = errors.New("empty file")
ErrRelativePath = errors.New("access using relative path is not allowed")
ErrEmptyToken = errors.New("empty token")
)
const (

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/jlaffaye/ftp"
log "github.com/sirupsen/logrus"
"path/filepath"
@ -186,9 +185,9 @@ func (driver FTP) Path(path string, account *model.Account) (*model.File, []mode
return nil, files, nil
}
func (driver FTP) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver FTP) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -11,7 +11,12 @@ var connMap map[string]*ftp.ServerConn
func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
conn, ok := connMap[account.Name]
if ok {
return conn, nil
_, err := conn.CurrentDir()
if err == nil {
return conn, nil
} else {
delete(connMap, account.Name)
}
}
conn, err := ftp.Connect(account.SiteUrl)
if err != nil {
@ -21,9 +26,11 @@ func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
if err != nil {
return nil, err
}
connMap[account.Name] = conn
return conn, nil
}
func init() {
base.RegisterDriver(&FTP{})
connMap = make(map[string]*ftp.ServerConn)
}

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io/ioutil"
"path/filepath"
@ -178,9 +177,9 @@ func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File
return nil, files, nil
}
func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Add("Authorization", "Bearer "+account.AccessToken)
}
//func (driver GoogleDrive) Proxy(r *http.Request, account *model.Account) {
// r.Header.Add("Authorization", "Bearer "+account.AccessToken)
//}
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
@ -159,9 +158,9 @@ func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil
}
func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
}
//func (driver Lanzou) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
//}
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -209,19 +209,24 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
if len(iframe) == 0 {
return "", fmt.Errorf("get down empty page")
}
iframeUrl := "https://wwa.lanzouo.com" + iframe[1]
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
res, err = lanzouClient.R().Get(iframeUrl)
if err != nil {
return "", err
}
log.Debugln(res.String())
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
if len(ajaxdata) == 0 {
return "", fmt.Errorf("get iframe empty page")
}
signs := ajaxdata[1]
sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
//sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
//websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
//websign := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())[1]
websign := ""
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
var resp LanzouLinkResp
form := map[string]string{
"action": "downprocess",

View File

@ -1,7 +1,6 @@
package mediatrack
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
@ -13,11 +12,12 @@ import (
"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/gin-gonic/gin"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
)
@ -147,9 +147,9 @@ func (driver MediaTrack) Path(path string, account *model.Account) (*model.File,
return nil, files, nil
}
func (driver MediaTrack) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver MediaTrack) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotImplement
@ -265,21 +265,39 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
if err != nil {
return err
}
var buf bytes.Buffer
read := io.TeeReader(file, &buf)
tempFile, err := ioutil.TempFile("data/temp", "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Object,
Body: read,
Body: tempFile,
}
_, err = uploader.Upload(input)
if err != nil {
return err
}
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New()
_, err = io.Copy(h, &buf)
_, err = io.Copy(h, tempFile)
if err != nil {
return err
}

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
@ -58,6 +57,9 @@ func (driver Native) Save(account *model.Account, old *model.Account) error {
}
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound
@ -82,6 +84,9 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
}
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound
@ -114,6 +119,10 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
}
func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) {
_, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
fullPath := filepath.Join(account.RootFolder, args.Path)
s, err := os.Stat(fullPath)
if err != nil {
@ -146,21 +155,27 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil
}
func (driver Native) Proxy(c *gin.Context, account *model.Account) {
// unnecessary
}
//func (driver Native) Proxy(r *http.Request, account *model.Account) {
// // unnecessary
//}
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Native) MakeDir(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
err := os.MkdirAll(fullPath, 0700)
return err
}
func (driver Native) Move(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst)
return os.Rename(fullSrc, fullDst)
@ -171,6 +186,9 @@ func (driver Native) Rename(src string, dst string, account *model.Account) erro
}
func (driver Native) Copy(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst)
srcFile, err := driver.File(src, account)
@ -190,6 +208,9 @@ func (driver Native) Copy(src string, dst string, account *model.Account) error
}
func (driver Native) Delete(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
file, err := driver.File(path, account)
if err != nil {
@ -205,6 +226,9 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
if file == nil {
return base.ErrEmptyFile
}
if utils.IsContain(strings.Split(file.ParentPath, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
if err == nil {
@ -224,6 +248,16 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
defer func() {
_ = out.Close()
}()
//var buf bytes.Buffer
//reader := io.TeeReader(file, &buf)
//h := md5.New()
//_, err = io.Copy(h, reader)
//if err != nil {
// return err
//}
//hash := hex.EncodeToString(h.Sum(nil))
//log.Debugln("md5:", hash)
//_, err = io.Copy(out, &buf)
_, err = io.Copy(out, file)
return err
}

View File

@ -6,8 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"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"
"path/filepath"
)
@ -92,9 +90,9 @@ func (driver Onedrive) Items() []base.Item {
}
func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
//if old != nil {
// conf.Cron.Remove(cron.EntryID(old.CronId))
//}
if account == nil {
return nil
}
@ -104,28 +102,25 @@ func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
}
account.RootFolder = utils.ParsePath(account.RootFolder)
err := driver.RefreshToken(account)
_ = model.SaveAccount(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
}
//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)
return nil
}
@ -205,9 +200,9 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil
}
func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
}
//func (driver Onedrive) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
//}
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -19,8 +19,6 @@ import (
"time"
)
var oneClient = resty.New()
type Host struct {
Oauth string
Api string
@ -81,7 +79,7 @@ type OneTokenErr struct {
func (driver Onedrive) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err.Error() == "empty refresh_token" {
if err != nil && err == base.ErrEmptyToken {
return driver.refreshToken(account)
}
return err
@ -91,7 +89,7 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp
var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
@ -109,8 +107,8 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
account.Status = "work"
}
if resp.RefreshToken == "" {
account.Status = "empty refresh_token"
return errors.New("empty refresh_token")
account.Status = base.ErrEmptyToken.Error()
return base.ErrEmptyToken
}
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
@ -178,16 +176,17 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
}
for nextLink != "" {
var files OneFiles
var e OneRespErr
_, err := oneClient.R().SetResult(&files).SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(nextLink)
_, err := driver.Request(nextLink, base.Get, nil, nil, nil, nil, &files, account)
//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)
}
//if e.Error.Code != "" {
// return nil, fmt.Errorf("%s", e.Error.Message)
//}
res = append(res, files.Value...)
nextLink = files.NextLink
}
@ -196,16 +195,18 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
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))
//var e OneRespErr
u := driver.GetMetaUrl(account, false, path)
_, err := driver.Request(u, base.Get, nil, nil, nil, nil, &file, account)
//_, 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)
}
//if e.Error.Code != "" {
// return nil, fmt.Errorf("%s", e.Error.Message)
//}
return &file, nil
}
@ -314,5 +315,4 @@ func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account)
func init() {
base.RegisterDriver(&Onedrive{})
oneClient.SetRetryCount(3)
}

View File

@ -5,8 +5,28 @@ import (
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"runtime/debug"
)
func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
return driver.Path(path, account)
}
func Files(driver base.Driver, account *model.Account, path string) ([]model.File, error) {
_, files, err := Path(driver, account, path)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrNotFolder
}
return files, nil
}
func File(driver base.Driver, account *model.Account, path string) (*model.File, error) {
return driver.File(path, account)
}
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
log.Debugf("mkdir: %s", path)
err := driver.MakeDir(path, account)
@ -78,5 +98,6 @@ func Upload(driver base.Driver, account *model.Account, file *model.FileStream,
if err != nil {
log.Errorf("upload error: %s", err.Error())
}
debug.FreeOSMemory()
return err
}

View File

@ -10,7 +10,6 @@ import (
"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/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
@ -142,9 +141,9 @@ func (driver PikPak) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil
}
func (driver PikPak) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver PikPak) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -8,7 +8,6 @@ import (
"github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"net/url"
"path/filepath"
@ -77,7 +76,7 @@ func (driver S3) Items() []base.Item {
{
Name: "zone",
Label: "placeholder filename",
Type: base.TypeNumber,
Type: base.TypeString,
Description: "default empty string",
},
}
@ -142,7 +141,7 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
}
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
client, err := driver.GetClient(account)
client, err := driver.GetClient(account, true)
if err != nil {
return nil, err
}
@ -189,9 +188,9 @@ func (driver S3) Path(path string, account *model.Account) (*model.File, []model
return nil, files, nil
}
func (driver S3) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver S3) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
@ -215,7 +214,7 @@ func (driver S3) Rename(src string, dst string, account *model.Account) error {
}
func (driver S3) Copy(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account)
client, err := driver.GetClient(account, false)
if err != nil {
return err
}
@ -235,7 +234,7 @@ func (driver S3) Copy(src string, dst string, account *model.Account) error {
}
func (driver S3) Delete(path string, account *model.Account) error {
client, err := driver.GetClient(account)
client, err := driver.GetClient(account, false)
if err != nil {
return err
}

View File

@ -29,13 +29,13 @@ func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
return session.NewSession(cfg)
}
func (driver S3) GetClient(account *model.Account) (*s3.S3, error) {
func (driver S3) GetClient(account *model.Account, link bool) (*s3.S3, error) {
s, ok := sessionsMap[account.Name]
if !ok {
return nil, fmt.Errorf("can't find [%s] session", account.Name)
}
client := s3.New(s)
if account.CustomHost != "" {
if link && account.CustomHost != "" {
cURL, err := url.Parse(account.CustomHost)
if err != nil {
return nil, err
@ -54,7 +54,7 @@ func (driver S3) GetClient(account *model.Account) (*s3.S3, error) {
func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account)
client, err := driver.GetClient(account, false)
if err != nil {
return nil, err
}
@ -118,6 +118,6 @@ func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
}
func init() {
sessionsMap = make(map[string]*session.Session, 0)
sessionsMap = make(map[string]*session.Session)
base.RegisterDriver(&S3{})
}

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
@ -154,9 +153,9 @@ func (driver Shandian) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil
}
func (driver Shandian) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver Shandian) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
@ -149,9 +148,9 @@ func (driver Teambition) Path(path string, account *model.Account) (*model.File,
return nil, files, nil
}
func (driver Teambition) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver Teambition) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
@ -252,7 +251,34 @@ func (driver Teambition) Delete(path string, account *model.Account) error {
}
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if !parentFile.IsDir() {
return base.ErrNotFolder
}
if err != nil {
return err
}
res, err := driver.Request("/projects", base.Get, nil, nil, nil, nil, nil, account)
if err != nil {
return err
}
token := GetBetweenStr(string(res), "strikerAuth&quot;:&quot;", "&quot;,&quot;phoneForLogin")
var newFile *FileUpload
if file.Size <= 20971520 {
// post upload
newFile, err = driver.upload(file, token, account)
} else {
// chunk upload
//err = base.ErrNotImplement
newFile, err = driver.chunkUpload(file, token, account)
}
if err != nil {
return err
}
return driver.finishUpload(newFile, parentFile.Id, account)
}
var _ base.Driver = (*Teambition)(nil)

View File

@ -2,11 +2,14 @@ package teambition
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"io"
"path"
"strconv"
"time"
@ -66,25 +69,6 @@ func (driver Teambition) Request(pathname string, method int, headers, query, fo
return res.Body(), nil
}
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0)
page := 1
@ -151,6 +135,101 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
return files, nil
}
func (driver Teambition) upload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
if account.InternalType == "International" {
prefix = "us-tcs"
}
var newFile FileUpload
_, err := base.RestyClient.R().SetResult(&newFile).SetHeader("Authorization", token).
SetMultipartFormData(map[string]string{
"name": file.GetFileName(),
"type": file.GetMIMEType(),
"size": strconv.FormatUint(file.GetSize(), 10),
//"lastModifiedDate": "",
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
if err != nil {
return nil, err
}
return &newFile, nil
}
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
if account.InternalType == "International" {
prefix = "us-tcs"
}
var newChunk ChunkUpload
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
SetBody(base.Json{
"fileName": file.GetFileName(),
"fileSize": file.GetSize(),
"lastUpdated": time.Now(),
}).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix))
if err != nil {
return nil, err
}
for i := 0; i < newChunk.Chunks; i++ {
chunkSize := newChunk.ChunkSize
if i == newChunk.Chunks-1 {
chunkSize = int(file.GetSize()) - i*chunkSize
}
log.Debugf("%d : %d", i, chunkSize)
chunkData := make([]byte, chunkSize)
_, err = io.ReadFull(file, chunkData)
if err != nil {
return nil, err
}
u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d",
prefix, newChunk.FileKey, i+1, newChunk.Chunks)
log.Debugf("url: %s", u)
res, err := base.RestyClient.R().SetHeaders(map[string]string{
"Authorization": token,
"Content-Type": "application/octet-stream",
"Referer": "https://www.teambition.com/",
}).SetBody(chunkData).Post(u)
if err != nil {
return nil, err
}
log.Debug(res.Status(), res.String())
//req, err := http.NewRequest("POST",
// u,
// bytes.NewBuffer(chunkData))
//if err != nil {
// return nil, err
//}
//req.Header.Set("Authorization", token)
//req.Header.Set("Content-Type", "application/octet-stream")
//req.Header.Set("Referer", "https://www.teambition.com/")
//resp, err := base.HttpClient.Do(req)
//res, _ := ioutil.ReadAll(resp.Body)
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
if err != nil {
return nil, err
}
}
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
prefix, newChunk.FileKey))
log.Debug(res.Status(), res.String())
if err != nil {
return nil, err
}
return &newChunk.FileUpload, nil
}
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
file.InvolveMembers = []interface{}{}
file.Visible = "members"
file.ParentId = parentId
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
"works": []FileUpload{*file},
"_parentId": parentId,
}, nil, account)
return err
}
func init() {
base.RegisterDriver(&Teambition{})
}

View File

@ -0,0 +1,63 @@
package teambition
import "time"
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
type FileUpload struct {
FileKey string `json:"fileKey"`
FileName string `json:"fileName"`
FileType string `json:"fileType"`
FileSize int `json:"fileSize"`
FileCategory string `json:"fileCategory"`
ImageWidth int `json:"imageWidth"`
ImageHeight int `json:"imageHeight"`
InvolveMembers []interface{} `json:"involveMembers"`
Source string `json:"source"`
Visible string `json:"visible"`
ParentId string `json:"_parentId"`
}
type ChunkUpload struct {
FileUpload
Storage string `json:"storage"`
MimeType string `json:"mimeType"`
Chunks int `json:"chunks"`
ChunkSize int `json:"chunkSize"`
Created time.Time `json:"created"`
FileMD5 string `json:"fileMD5"`
LastUpdated time.Time `json:"lastUpdated"`
UploadedChunks []interface{} `json:"uploadedChunks"`
Token struct {
AppID string `json:"AppID"`
OrganizationID string `json:"OrganizationID"`
UserID string `json:"UserID"`
Exp time.Time `json:"Exp"`
Storage string `json:"Storage"`
Resource string `json:"Resource"`
Speed int `json:"Speed"`
} `json:"token"`
DownloadUrl string `json:"downloadUrl"`
ThumbnailUrl string `json:"thumbnailUrl"`
PreviewUrl string `json:"previewUrl"`
ImmPreviewUrl string `json:"immPreviewUrl"`
PreviewExt string `json:"previewExt"`
LastUploadTime interface{} `json:"lastUploadTime"`
}

View File

@ -0,0 +1,18 @@
package teambition
import "strings"
func GetBetweenStr(str, start, end string) string {
n := strings.Index(str, start)
if n == -1 {
return ""
}
n = n + len(start)
str = string([]byte(str)[n:])
m := strings.Index(str, end)
if m == -1 {
return ""
}
str = string([]byte(str)[:m])
return str
}

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath"
)
@ -137,9 +136,9 @@ func (driver WebDav) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil
}
func (driver WebDav) Proxy(c *gin.Context, account *model.Account) {
}
//func (driver WebDav) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport

224
drivers/yandex/driver.go Normal file
View File

@ -0,0 +1,224 @@
package yandex
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"net/http"
"path/filepath"
"strconv"
)
type Yandex struct{}
func (driver Yandex) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Yandex.Disk",
}
}
func (driver Yandex) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Default: "/",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Default: "name",
Values: "name,path,created,modified,size",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Default: "asc",
Required: false,
},
{
Name: "client_id",
Label: "client id",
Default: "a78d5a69054042fa936f6c77f9a0ae8b",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "9c119bbb04b346d2a52aa64401936b2b",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Yandex) Save(account *model.Account, old *model.Account) error {
return driver.RefreshToken(account)
}
func (driver Yandex) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Yandex) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
files, err := driver.GetFiles(path, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Yandex) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := utils.Join(account.RootFolder, args.Path)
log.Debugln("down path:", path)
var resp DownResp
_, err := driver.Request("/download", base.Get, nil, map[string]string{
"path": path,
}, nil, nil, &resp, account)
if err != nil {
return nil, err
}
link := base.Link{
Url: resp.Href,
}
return &link, nil
}
func (driver Yandex) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Yandex) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Yandex) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Yandex) MakeDir(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
_, err := driver.Request("", base.Put, nil, map[string]string{
"path": path,
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Move(src string, dst string, account *model.Account) error {
from := utils.Join(account.RootFolder, src)
path := utils.Join(account.RootFolder, dst)
_, err := driver.Request("/move", base.Post, nil, map[string]string{
"from": from,
"path": path,
"overwrite": "true",
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver Yandex) Copy(src string, dst string, account *model.Account) error {
from := utils.Join(account.RootFolder, src)
path := utils.Join(account.RootFolder, dst)
_, err := driver.Request("/copy", base.Post, nil, map[string]string{
"from": from,
"path": path,
"overwrite": "true",
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Delete(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
_, err := driver.Request("", base.Delete, nil, map[string]string{
"path": path,
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
path := utils.Join(account.RootFolder, file.ParentPath, file.Name)
var resp UploadResp
_, err := driver.Request("/upload", base.Get, nil, map[string]string{
"path": path,
"overwrite": "true",
}, nil, nil, &resp, account)
if err != nil {
return err
}
req, err := http.NewRequest(resp.Method, resp.Href, file)
if err != nil {
return err
}
req.Header.Set("Content-Length", strconv.FormatUint(file.Size, 10))
req.Header.Set("Content-Type", "application/octet-stream")
_, err = base.HttpClient.Do(req)
//res, err := base.RestyClient.R().
// SetHeader("Content-Length", strconv.FormatUint(file.Size, 10)).
// SetBody(file).Put(resp.Href)
//log.Debugln(res.Status(), res.String())
return err
}
var _ base.Driver = (*Yandex)(nil)

74
drivers/yandex/types.go Normal file
View File

@ -0,0 +1,74 @@
package yandex
import "time"
type TokenErrResp struct {
ErrorDescription string `json:"error_description"`
Error string `json:"error"`
}
type ErrResp struct {
Message string `json:"message"`
Description string `json:"description"`
Error string `json:"error"`
}
type File struct {
//AntivirusStatus string `json:"antivirus_status"`
Size int64 `json:"size"`
//CommentIds struct {
// PrivateResource string `json:"private_resource"`
// PublicResource string `json:"public_resource"`
//} `json:"comment_ids"`
Name string `json:"name"`
//Exif struct {
// DateTime time.Time `json:"date_time"`
//} `json:"exif"`
//Created time.Time `json:"created"`
//ResourceId string `json:"resource_id"`
Modified time.Time `json:"modified"`
//MimeType string `json:"mime_type"`
File string `json:"file"`
//MediaType string `json:"media_type"`
Preview string `json:"preview"`
Path string `json:"path"`
//Sha256 string `json:"sha256"`
Type string `json:"type"`
//Md5 string `json:"md5"`
//Revision int64 `json:"revision"`
}
type FilesResp struct {
Embedded struct {
Sort string `json:"sort"`
Items []File `json:"items"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Path string `json:"path"`
Total int `json:"total"`
} `json:"_embedded"`
Name string `json:"name"`
Exif struct {
} `json:"exif"`
ResourceId string `json:"resource_id"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
Path string `json:"path"`
CommentIds struct {
} `json:"comment_ids"`
Type string `json:"type"`
Revision int64 `json:"revision"`
}
type DownResp struct {
Href string `json:"href"`
Method string `json:"method"`
Templated bool `json:"templated"`
}
type UploadResp struct {
OperationId string `json:"operation_id"`
Href string `json:"href"`
Method string `json:"method"`
Templated bool `json:"templated"`
}

1
drivers/yandex/util.go Normal file
View File

@ -0,0 +1 @@
package yandex

154
drivers/yandex/yandex.go Normal file
View File

@ -0,0 +1,154 @@
package yandex
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"path"
"strconv"
)
func (driver Yandex) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
err = driver.refreshToken(account)
}
if err != nil {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Yandex) refreshToken(account *model.Account) error {
u := "https://oauth.yandex.com/token"
var resp base.TokenResp
var e TokenErrResp
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
}).Post(u)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
account.Status = "work"
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
return nil
}
func (driver Yandex) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://cloud-api.yandex.net/v1/disk/resources" + pathname
req := base.RestyClient.R()
req.SetHeader("Authorization", "OAuth "+account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
var e ErrResp
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Patch:
res, err = req.Patch(u)
case base.Delete:
res, err = req.Delete(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
if e.Error != "" {
if e.Error == "UnauthorizedError" {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
}
return driver.Request(pathname, method, headers, query, form, data, resp, account)
}
return nil, errors.New(e.Description)
}
return res.Body(), nil
}
func (driver Yandex) GetFiles(rawPath string, account *model.Account) ([]model.File, error) {
path_ := utils.Join(account.RootFolder, rawPath)
limit := 100
page := 1
res := make([]model.File, 0)
for {
offset := (page - 1) * limit
query := map[string]string{
"path": path_,
"limit": strconv.Itoa(limit),
"offset": strconv.Itoa(offset),
}
if account.OrderBy != "" {
if account.OrderDirection == "desc" {
query["sort"] = "-" + account.OrderBy
} else {
query["sort"] = account.OrderBy
}
}
var resp FilesResp
_, err := driver.Request("", base.Get, nil, query, nil, nil, &resp, account)
if err != nil {
return nil, err
}
for _, file := range resp.Embedded.Items {
f := model.File{
Name: file.Name,
Size: file.Size,
Driver: driver.Config().Name,
UpdatedAt: &file.Modified,
Thumbnail: file.Preview,
Url: file.File,
}
if file.Type == "dir" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(path.Ext(file.Name))
}
res = append(res, f)
}
if resp.Embedded.Total <= offset+limit {
break
}
}
return res, nil
}
func init() {
base.RegisterDriver(&Yandex{})
}

View File

@ -0,0 +1,61 @@
package file
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
func Copy(c *gin.Context) {
var req MoveCopyReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
if len(req.Names) == 0 {
common.ErrorStrResp(c, "Empty file names", 400)
return
}
if model.AccountsCount() > 1 && (req.SrcDir == "/" || req.DstDir == "/") {
common.ErrorStrResp(c, "Can't operate root folder", 400)
return
}
srcAccount, srcPath, srcDriver, err := common.ParsePath(utils.Join(req.SrcDir, req.Names[0]))
if err != nil {
common.ErrorResp(c, err, 500)
return
}
dstAccount, dstPath, _, err := common.ParsePath(utils.Join(req.DstDir, req.Names[0]))
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if srcAccount.Name != dstAccount.Name {
common.ErrorStrResp(c, "Can't copy files between two accounts", 400)
return
}
if srcPath == "/" || dstPath == "/" {
common.ErrorStrResp(c, "Can't copy root folder", 400)
return
}
srcDir, dstDir := utils.Dir(srcPath), utils.Dir(dstPath)
for i, name := range req.Names {
clearCache := false
if i == len(req.Names)-1 {
clearCache = true
}
err := operate.Copy(srcDriver, srcAccount, utils.Join(srcDir, name), utils.Join(dstDir, name), clearCache)
if err != nil {
if i == 0 {
_ = base.DeleteCache(srcDir, srcAccount)
_ = base.DeleteCache(dstDir, dstAccount)
}
common.ErrorResp(c, err, 500)
return
}
}
common.SuccessResp(c)
}

View File

@ -1,6 +1,7 @@
package file
import (
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
@ -30,7 +31,7 @@ func Folder(c *gin.Context) {
common.ErrorResp(c, err, 500)
return
}
file, rawFiles, err := driver.Path(path, account)
file, rawFiles, err := operate.Path(driver, account, path)
if err != nil {
common.ErrorResp(c, err, 500)
return

View File

@ -9,14 +9,14 @@ import (
"github.com/gin-gonic/gin"
)
type MoveReq struct {
type MoveCopyReq struct {
SrcDir string `json:"src_dir"`
DstDir string `json:"dst_dir"`
Names []string `json:"names"`
}
func Move(c *gin.Context) {
var req MoveReq
var req MoveCopyReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return

View File

@ -24,7 +24,7 @@ func Rename(c *gin.Context) {
return
}
if path_ == "/" {
common.ErrorStrResp(c, "Can't account name here", 400)
common.ErrorStrResp(c, "Can't edit account name here", 400)
return
}
err = operate.Move(driver, account, path_, utils.Join(utils.Dir(path_), req.Name), true)

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
@ -109,7 +110,7 @@ func Path(c *gin.Context) {
common.ErrorResp(c, err, 500)
return
}
file, files, err := driver.Path(path, account)
file, files, err := operate.Path(driver, account, path)
if err != nil {
common.ErrorResp(c, err, 500)
return

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
@ -50,7 +51,7 @@ func Proxy(c *gin.Context) {
return
}
// 检查文件
file, err := driver.File(path, account)
file, err := operate.File(driver, account, path)
if err != nil {
common.ErrorResp(c, err, 500)
return
@ -100,11 +101,10 @@ func Proxy(c *gin.Context) {
c.File(link.Url)
return
} else {
if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
Text(c, link)
return
}
driver.Proxy(c, account)
//if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
// Text(c, link)
// return
//}
r := c.Request
w := c.Writer
//target, err := url.Parse(link.Url)
@ -120,6 +120,9 @@ func Proxy(c *gin.Context) {
for h, val := range r.Header {
req.Header[h] = val
}
for _, header := range link.Headers {
req.Header.Set(header.Name, header.Value)
}
res, err := HttpClient.Do(req)
if err != nil {
common.ErrorResp(c, err, 500)

View File

@ -19,7 +19,7 @@ func Auth(c *gin.Context) {
//}
//if token != utils.GetMD5Encode(password.Value) {
if token != conf.Token {
common.ErrorStrResp(c, "Wrong password", 401)
common.ErrorStrResp(c, "Invalid token", 401)
return
}
c.Next()

View File

@ -54,6 +54,7 @@ func InitApiRouter(r *gin.Engine) {
admin.POST("/mkdir", file.Mkdir)
admin.POST("/rename", file.Rename)
admin.POST("/move", file.Move)
admin.POST("/copy", file.Copy)
admin.POST("/folder", file.Folder)
}
WebDav(r)

View File

@ -24,8 +24,13 @@ import (
type FileSystem struct{}
var upFileMap = make(map[string]*model.File)
func (fs *FileSystem) File(rawPath string) (*model.File, error) {
rawPath = utils.ParsePath(rawPath)
if f, ok := upFileMap[rawPath]; ok {
return f, nil
}
if model.AccountsCount() > 1 && rawPath == "/" {
now := time.Now()
return &model.File{
@ -40,7 +45,7 @@ func (fs *FileSystem) File(rawPath string) (*model.File, error) {
if err != nil {
return nil, err
}
return driver.File(path_, account)
return operate.File(driver, account, path_)
}
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
@ -56,7 +61,7 @@ func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File,
if err != nil {
return nil, err
}
files, err := driver.Files(path_, account)
files, err := operate.Files(driver, account, path_)
if err != nil {
return nil, err
}
@ -154,6 +159,17 @@ func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath strin
// return err
//}
filePath, fileName := filepath.Split(path_)
now := time.Now()
if fileSize == 0 {
upFileMap[rawPath] = &model.File{
Name: fileName,
Size: 0,
UpdatedAt: &now,
}
return nil
} else {
delete(upFileMap, rawPath)
}
fileData := model.FileStream{
MIMEType: r.Header.Get("Content-Type"),
File: r.Body,

View File

@ -46,7 +46,7 @@ func (h *Handler) stripPrefix(p string) (string, int, error) {
func isPathExist(ctx context.Context, fs *FileSystem, path string) (bool, FileInfo) {
file, err := fs.File(path)
if err != nil {
log.Debug(path, err)
log.Debugln(path, err)
return false, nil
}
return true, file

View File

@ -123,3 +123,7 @@ func Join(elem ...string) string {
}
return res
}
func Split(p string) (string, string) {
return path.Split(p)
}

25
utils/random.go Normal file
View File

@ -0,0 +1,25 @@
package utils
import (
"math/rand"
"strings"
"time"
)
var Rand *rand.Rand
func init() {
s := rand.NewSource(time.Now().UnixNano())
Rand = rand.New(s)
}
func RandomStr(n int) string {
builder := strings.Builder{}
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < n; i++ {
r := Rand.Intn(len(t))
builder.WriteString(t[r : r+1])
}
return builder.String()
}