diff --git a/drivers/all.go b/drivers/all.go index ffb5a534..fdbe60b5 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -3,6 +3,7 @@ package drivers import ( _ "github.com/alist-org/alist/v3/drivers/local" _ "github.com/alist-org/alist/v3/drivers/onedrive" + _ "github.com/alist-org/alist/v3/drivers/pikpak" _ "github.com/alist-org/alist/v3/drivers/virtual" ) diff --git a/drivers/pikpak/driver.go b/drivers/pikpak/driver.go new file mode 100644 index 00000000..2d2e94b5 --- /dev/null +++ b/drivers/pikpak/driver.go @@ -0,0 +1,220 @@ +package local + +import ( + "context" + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/internal/conf" + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/errs" + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/go-resty/resty/v2" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +type PikPak struct { + model.Storage + Addition + RefreshToken string + AccessToken string +} + +func (d *PikPak) Config() driver.Config { + return config +} + +func (d *PikPak) GetAddition() driver.Additional { + return d.Addition +} + +func (d *PikPak) Init(ctx context.Context, storage model.Storage) error { + d.Storage = storage + err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition) + if err != nil { + return errors.Wrap(err, "error while unmarshal addition") + } + return d.login() +} + +func (d *PikPak) Drop(ctx context.Context) error { + return nil +} + +func (d *PikPak) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + files, err := d.getFiles(dir.GetID()) + if err != nil { + return nil, err + } + objs := make([]model.Obj, len(files)) + for i := 0; i < len(files); i++ { + objs[i] = fileToObj(files[i]) + } + return objs, nil +} + +func (d *PikPak) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + var resp File + _, err := d.request(fmt.Sprintf("https://api-drive.mypikpak.com/drive/v1/files/%s?_magic=2021&thumbnail_size=SIZE_LARGE", file.GetID()), + http.MethodGet, nil, &resp) + if err != nil { + return nil, err + } + link := model.Link{ + URL: resp.WebContentLink, + } + if len(resp.Medias) > 0 && resp.Medias[0].Link.Url != "" { + log.Debugln("use media link") + link.URL = resp.Medias[0].Link.Url + } + return &link, nil +} + +func (d *PikPak) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + _, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "kind": "drive#folder", + "parent_id": parentDir.GetID(), + "name": dirName, + }) + }, nil) + return err +} + +func (d *PikPak) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + _, err := d.request("https://api-drive.mypikpak.com/drive/v1/files:batchMove", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "ids": []string{srcObj.GetID()}, + "to": base.Json{ + "parent_id": dstDir.GetID(), + }, + }) + }, nil) + return err +} + +func (d *PikPak) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + _, err := d.request("https://api-drive.mypikpak.com/drive/v1/files/"+srcObj.GetID(), http.MethodPatch, func(req *resty.Request) { + req.SetBody(base.Json{ + "name": newName, + }) + }, nil) + return err +} + +func (d *PikPak) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + _, err := d.request("https://api-drive.mypikpak.com/drive/v1/files:batchCopy", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "ids": []string{srcObj.GetID()}, + "to": base.Json{ + "parent_id": dstDir.GetID(), + }, + }) + }, nil) + return err +} + +func (d *PikPak) Remove(ctx context.Context, obj model.Obj) error { + _, err := d.request("https://api-drive.mypikpak.com/drive/v1/files:batchTrash", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "ids": []string{obj.GetID()}, + }) + }, nil) + return err +} + +func (d *PikPak) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { + var tempFile *os.File + var err error + if f, ok := stream.GetReadCloser().(*os.File); ok { + tempFile = f + } else { + tempFile, err = os.CreateTemp(conf.Conf.TempDir, "file-*") + if err != nil { + return err + } + defer func() { + _ = tempFile.Close() + _ = os.Remove(tempFile.Name()) + }() + _, err = io.Copy(tempFile, stream) + if err != nil { + return err + } + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + } + // cal sha1 + s := sha1.New() + _, err = io.Copy(s, tempFile) + if err != nil { + return err + } + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + sha1Str := hex.EncodeToString(s.Sum(nil)) + data := base.Json{ + "kind": "drive#file", + "name": stream.GetName(), + "size": stream.GetSize(), + "hash": strings.ToUpper(sha1Str), + "upload_type": "UPLOAD_TYPE_RESUMABLE", + "objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"}, + "parent_id": dstDir.GetID(), + } + res, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + if err != nil { + return err + } + params := jsoniter.Get(res, "resumable").Get("params") + endpoint := params.Get("endpoint").ToString() + endpointS := strings.Split(endpoint, ".") + endpoint = strings.Join(endpointS[1:], ".") + accessKeyId := params.Get("access_key_id").ToString() + accessKeySecret := params.Get("access_key_secret").ToString() + securityToken := params.Get("security_token").ToString() + key := params.Get("key").ToString() + bucket := params.Get("bucket").ToString() + cfg := &aws.Config{ + Credentials: credentials.NewStaticCredentials(accessKeyId, accessKeySecret, securityToken), + Region: aws.String("pikpak"), + Endpoint: &endpoint, + } + ss, err := session.NewSession(cfg) + if err != nil { + return err + } + uploader := s3manager.NewUploader(ss) + input := &s3manager.UploadInput{ + Bucket: &bucket, + Key: &key, + Body: tempFile, + } + _, err = uploader.Upload(input) + return err +} + +func (d *PikPak) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { + return nil, errs.NotSupport +} + +var _ driver.Driver = (*PikPak)(nil) diff --git a/drivers/pikpak/meta.go b/drivers/pikpak/meta.go new file mode 100644 index 00000000..6d568e64 --- /dev/null +++ b/drivers/pikpak/meta.go @@ -0,0 +1,26 @@ +package local + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/operations" +) + +type Addition struct { + driver.RootFolderId + Username string `json:"username" required:"true"` + Password string `json:"password" required:"true"` +} + +var config = driver.Config{ + Name: "PikPak", + LocalSort: true, + DefaultRoot: "", +} + +func New() driver.Driver { + return &PikPak{} +} + +func init() { + operations.RegisterDriver(config, New) +} diff --git a/drivers/pikpak/types.go b/drivers/pikpak/types.go new file mode 100644 index 00000000..276d063d --- /dev/null +++ b/drivers/pikpak/types.go @@ -0,0 +1,75 @@ +package local + +import ( + "strconv" + "time" + + "github.com/alist-org/alist/v3/internal/model" +) + +type RespErr struct { + ErrorCode int `json:"error_code"` + Error string `json:"error"` +} + +type Files struct { + Files []File `json:"files"` + NextPageToken string `json:"next_page_token"` +} + +type File struct { + Id string `json:"id"` + Kind string `json:"kind"` + Name string `json:"name"` + ModifiedTime time.Time `json:"modified_time"` + Size string `json:"size"` + ThumbnailLink string `json:"thumbnail_link"` + WebContentLink string `json:"web_content_link"` + Medias []Media `json:"medias"` +} + +func fileToObj(f File) model.ObjectThumbnail { + size, _ := strconv.ParseInt(f.Size, 10, 64) + return model.ObjectThumbnail{ + Object: model.Object{ + ID: f.Id, + Name: f.Name, + Size: size, + Modified: f.ModifiedTime, + IsFolder: f.Kind == "drive#folder", + }, + Thumbnail: model.Thumbnail{ + Thumbnail: f.ThumbnailLink, + }, + } +} + +type Media struct { + MediaId string `json:"media_id"` + MediaName string `json:"media_name"` + Video struct { + Height int `json:"height"` + Width int `json:"width"` + Duration int `json:"duration"` + BitRate int `json:"bit_rate"` + FrameRate int `json:"frame_rate"` + VideoCodec string `json:"video_codec"` + AudioCodec string `json:"audio_codec"` + VideoType string `json:"video_type"` + } `json:"video"` + Link struct { + Url string `json:"url"` + Token string `json:"token"` + Expire time.Time `json:"expire"` + } `json:"link"` + NeedMoreQuota bool `json:"need_more_quota"` + VipTypes []interface{} `json:"vip_types"` + RedirectLink string `json:"redirect_link"` + IconLink string `json:"icon_link"` + IsDefault bool `json:"is_default"` + Priority int `json:"priority"` + IsOrigin bool `json:"is_origin"` + ResolutionName string `json:"resolution_name"` + IsVisible bool `json:"is_visible"` + Category string `json:"category"` +} diff --git a/drivers/pikpak/util.go b/drivers/pikpak/util.go new file mode 100644 index 00000000..391f1bc1 --- /dev/null +++ b/drivers/pikpak/util.go @@ -0,0 +1,125 @@ +package local + +import ( + "net/http" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/internal/operations" + "github.com/go-resty/resty/v2" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" +) + +// do others that not defined in Driver interface + +func (d *PikPak) login() error { + url := "https://user.mypikpak.com/v1/auth/signin" + var e RespErr + res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{ + "captcha_token": "", + "client_id": "YNxT9w7GMdWvEOKa", + "client_secret": "dbw2OtmVEeuUvIptb1Coyg", + "username": d.Username, + "password": d.Password, + }).Post(url) + if err != nil { + return err + } + if e.ErrorCode != 0 { + return errors.New(e.Error) + } + data := res.Body() + d.RefreshToken = jsoniter.Get(data, "refresh_token").ToString() + d.AccessToken = jsoniter.Get(data, "access_token").ToString() + return nil +} + +func (d *PikPak) refreshToken() error { + url := "https://user.mypikpak.com/v1/auth/token" + var e RespErr + res, err := base.RestyClient.R().SetError(&e). + SetHeader("user-agent", "").SetBody(base.Json{ + "client_id": "YNxT9w7GMdWvEOKa", + "client_secret": "dbw2OtmVEeuUvIptb1Coyg", + "grant_type": "refresh_token", + "refresh_token": d.RefreshToken, + }).Post(url) + if err != nil { + d.Status = err.Error() + operations.MustSaveDriverStorage(d) + return err + } + if e.ErrorCode != 0 { + if e.ErrorCode == 4126 { + // refresh_token invalid, re-login + return d.login() + } + d.Status = e.Error + operations.MustSaveDriverStorage(d) + return errors.New(e.Error) + } + data := res.Body() + d.Status = "work" + d.RefreshToken = jsoniter.Get(data, "refresh_token").ToString() + d.AccessToken = jsoniter.Get(data, "access_token").ToString() + operations.MustSaveDriverStorage(d) + return nil +} + +func (d *PikPak) request(url string, method string, callback func(*resty.Request), resp interface{}) ([]byte, error) { + req := base.RestyClient.R() + req.SetHeader("Authorization", "Bearer "+d.AccessToken) + if callback != nil { + callback(req) + } + if resp != nil { + req.SetResult(resp) + } + var e RespErr + req.SetError(&e) + res, err := req.Execute(method, url) + if err != nil { + return nil, err + } + if e.ErrorCode != 0 { + if e.ErrorCode == 16 { + // login / refresh token + err = d.refreshToken() + if err != nil { + return nil, err + } + return d.request(url, method, callback, resp) + } else { + return nil, errors.New(e.Error) + } + } + return res.Body(), nil +} + +func (d *PikPak) getFiles(id string) ([]File, error) { + res := make([]File, 0) + pageToken := "first" + for pageToken != "" { + if pageToken == "first" { + pageToken = "" + } + query := map[string]string{ + "parent_id": id, + "thumbnail_size": "SIZE_LARGE", + "with_audit": "true", + "limit": "100", + "filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`, + "page_token": pageToken, + } + var resp Files + _, err := d.request("https://api-drive.mypikpak.com/drive/v1/files", http.MethodGet, func(req *resty.Request) { + req.SetQueryParams(query) + }, &resp) + if err != nil { + return nil, err + } + pageToken = resp.NextPageToken + res = append(res, resp.Files...) + } + return res, nil +} diff --git a/go.mod b/go.mod index ee1431c0..a5be458d 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( ) require ( + github.com/aws/aws-sdk-go v1.44.88 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect @@ -44,6 +45,7 @@ require ( github.com/jackc/pgx/v4 v4.16.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-sqlite3 v1.14.13 // indirect diff --git a/go.sum b/go.sum index a7cb39b3..6f82a10e 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030I github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE= github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04= +github.com/aws/aws-sdk-go v1.44.88 h1:9jhiZsTx9koQQsM29RTgwI0g4mfyphCdc3bkUcKrdwA= +github.com/aws/aws-sdk-go v1.44.88/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/caarlos0/env/v6 v6.9.3 h1:Tyg69hoVXDnpO5Qvpsu8EoquarbPyQb+YwExWHP8wWU= @@ -114,6 +116,9 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -242,6 +247,7 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= @@ -262,12 +268,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -304,6 +312,7 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go index f065c72d..d73504c8 100644 --- a/internal/bootstrap/data/setting.go +++ b/internal/bootstrap/data/setting.go @@ -90,11 +90,11 @@ func InitialSettings() []model.SettingItem { {Key: "external_previews", Value: `{}`, Type: conf.TypeText, Group: model.PREVIEW}, {Key: "iframe_previews", Value: `{ "doc,docx,xls,xlsx,ppt,pptx": { - "Microsoft":"https://view.officeapps.live.com/op/view.aspx?src=$url", - "Google":"https://docs.google.com/gview?url=$url&embedded=true" + "Microsoft":"https://view.officeapps.live.com/op/view.aspx?src=$e_url", + "Google":"https://docs.google.com/gview?url=$e_url&embedded=true" }, "pdf": { - "PDF.js":"https://alist-org.github.io/pdf.js/web/viewer.html?file=$url" + "PDF.js":"https://alist-org.github.io/pdf.js/web/viewer.html?file=$e_url" } }`, Type: conf.TypeText, Group: model.PREVIEW}, // {Key: conf.OfficeViewers, Value: `{