Compare commits
11 Commits
v3.0.0-rc.
...
v3.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
2185839236 | |||
24d58f278a | |||
f80be96cf9 | |||
6c89c6c8ae | |||
b74b55fa4a | |||
09564102e7 | |||
d436a6e676 | |||
bec3a327a7 | |||
d329df70f3 | |||
1af9f4061e | |||
0d012f85cb |
@ -234,7 +234,10 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
r, _ := new(big.Int).SetString(utils.GetMD5Encode(d.AccessToken)[:16], 16)
|
r, _ := new(big.Int).SetString(utils.GetMD5Encode(d.AccessToken)[:16], 16)
|
||||||
i := new(big.Int).SetInt64(file.GetSize())
|
i := new(big.Int).SetInt64(file.GetSize())
|
||||||
o := r.Mod(r, i)
|
o := new(big.Int).SetInt64(0)
|
||||||
|
if file.GetSize() > 0 {
|
||||||
|
o = r.Mod(r, i)
|
||||||
|
}
|
||||||
n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
||||||
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||||
|
|
||||||
|
@ -130,13 +130,10 @@ func (d *S3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *S3) Remove(ctx context.Context, obj model.Obj) error {
|
func (d *S3) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
key := getKey(obj.GetPath(), obj.IsDir())
|
if obj.IsDir() {
|
||||||
input := &s3.DeleteObjectInput{
|
return d.removeDir(ctx, obj.GetPath())
|
||||||
Bucket: &d.Bucket,
|
|
||||||
Key: &key,
|
|
||||||
}
|
}
|
||||||
_, err := d.client.DeleteObject(input)
|
return d.removeFile(obj.GetPath())
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
@ -52,12 +52,11 @@ func getKey(path string, dir bool) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultPlaceholderName = ".placeholder"
|
// var defaultPlaceholderName = ".placeholder"
|
||||||
|
|
||||||
func getPlaceholderName(placeholder string) string {
|
func getPlaceholderName(placeholder string) string {
|
||||||
if placeholder == "" {
|
//if placeholder == "" {
|
||||||
return defaultPlaceholderName
|
// return defaultPlaceholderName
|
||||||
}
|
//}
|
||||||
return placeholder
|
return placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,3 +204,33 @@ func (d *S3) copyDir(ctx context.Context, src string, dst string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *S3) removeDir(ctx context.Context, src string) error {
|
||||||
|
objs, err := op.List(ctx, d, src, model.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, obj := range objs {
|
||||||
|
cSrc := path.Join(src, obj.GetName())
|
||||||
|
if obj.IsDir() {
|
||||||
|
err = d.removeDir(ctx, cSrc)
|
||||||
|
} else {
|
||||||
|
err = d.removeFile(cSrc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *S3) removeFile(src string) error {
|
||||||
|
key := getKey(src, true)
|
||||||
|
input := &s3.DeleteObjectInput{
|
||||||
|
Bucket: &d.Bucket,
|
||||||
|
Key: &key,
|
||||||
|
}
|
||||||
|
_, err := d.client.DeleteObject(input)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -146,6 +146,7 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
|
|||||||
PackageName: x.PackageName,
|
PackageName: x.PackageName,
|
||||||
UserAgent: x.UserAgent,
|
UserAgent: x.UserAgent,
|
||||||
DownloadUserAgent: x.DownloadUserAgent,
|
DownloadUserAgent: x.DownloadUserAgent,
|
||||||
|
UseVideoUrl: x.UseVideoUrl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +210,7 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
|
|||||||
}
|
}
|
||||||
x.XunLeiCommon.UserAgent = x.UserAgent
|
x.XunLeiCommon.UserAgent = x.UserAgent
|
||||||
x.XunLeiCommon.DownloadUserAgent = x.DownloadUserAgent
|
x.XunLeiCommon.DownloadUserAgent = x.DownloadUserAgent
|
||||||
|
x.XunLeiCommon.UseVideoUrl = x.UseVideoUrl
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -252,6 +254,15 @@ func (xc *XunLeiCommon) Link(ctx context.Context, file model.Obj, args model.Lin
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if xc.UseVideoUrl {
|
||||||
|
for _, media := range lFile.Medias {
|
||||||
|
if media.Link.URL != "" {
|
||||||
|
link.URL = media.Link.URL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
strs := regexp.MustCompile(`e=([0-9]*)`).FindStringSubmatch(lFile.WebContentLink)
|
strs := regexp.MustCompile(`e=([0-9]*)`).FindStringSubmatch(lFile.WebContentLink)
|
||||||
if len(strs) == 2 {
|
if len(strs) == 2 {
|
||||||
|
@ -41,6 +41,9 @@ type ExpertAddition struct {
|
|||||||
//不影响登录,影响下载速度
|
//不影响登录,影响下载速度
|
||||||
UserAgent string `json:"user_agent" required:"true" default:"ANDROID-com.xunlei.downloadprovider/7.51.0.8196 netWorkType/4G appid/40 deviceName/Xiaomi_M2004j7ac deviceModel/M2004J7AC OSVersion/12 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_14_186-perf-gdcf98eab238b) (JAVA 0)"`
|
UserAgent string `json:"user_agent" required:"true" default:"ANDROID-com.xunlei.downloadprovider/7.51.0.8196 netWorkType/4G appid/40 deviceName/Xiaomi_M2004j7ac deviceModel/M2004J7AC OSVersion/12 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_14_186-perf-gdcf98eab238b) (JAVA 0)"`
|
||||||
DownloadUserAgent string `json:"download_user_agent" required:"true" default:"Dalvik/2.1.0 (Linux; U; Android 12; M2004J7AC Build/SP1A.210812.016)"`
|
DownloadUserAgent string `json:"download_user_agent" required:"true" default:"Dalvik/2.1.0 (Linux; U; Android 12; M2004J7AC Build/SP1A.210812.016)"`
|
||||||
|
|
||||||
|
//优先使用视频链接代替下载链接
|
||||||
|
UseVideoUrl bool `json:"use_video_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录特征,用于判断是否重新登录
|
// 登录特征,用于判断是否重新登录
|
||||||
|
@ -77,6 +77,13 @@ type FileList struct {
|
|||||||
VersionOutdated bool `json:"version_outdated"`
|
VersionOutdated bool `json:"version_outdated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Expire time.Time `json:"expire"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
type Files struct {
|
type Files struct {
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@ -95,26 +102,26 @@ type Files struct {
|
|||||||
ThumbnailLink string `json:"thumbnail_link"`
|
ThumbnailLink string `json:"thumbnail_link"`
|
||||||
//Md5Checksum string `json:"md5_checksum"`
|
//Md5Checksum string `json:"md5_checksum"`
|
||||||
//Hash string `json:"hash"`
|
//Hash string `json:"hash"`
|
||||||
//Links struct{} `json:"links"`
|
Links map[string]Link `json:"links"`
|
||||||
Phase string `json:"phase"`
|
Phase string `json:"phase"`
|
||||||
Audit struct {
|
Audit struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
} `json:"audit"`
|
} `json:"audit"`
|
||||||
/* Medias []struct {
|
Medias []struct {
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
IconLink string `json:"icon_link"`
|
IconLink string `json:"icon_link"`
|
||||||
IsDefault bool `json:"is_default"`
|
IsDefault bool `json:"is_default"`
|
||||||
IsOrigin bool `json:"is_origin"`
|
IsOrigin bool `json:"is_origin"`
|
||||||
IsVisible bool `json:"is_visible"`
|
IsVisible bool `json:"is_visible"`
|
||||||
//Link interface{} `json:"link"`
|
Link Link `json:"link"`
|
||||||
MediaID string `json:"media_id"`
|
MediaID string `json:"media_id"`
|
||||||
MediaName string `json:"media_name"`
|
MediaName string `json:"media_name"`
|
||||||
NeedMoreQuota bool `json:"need_more_quota"`
|
NeedMoreQuota bool `json:"need_more_quota"`
|
||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
RedirectLink string `json:"redirect_link"`
|
RedirectLink string `json:"redirect_link"`
|
||||||
ResolutionName string `json:"resolution_name"`
|
ResolutionName string `json:"resolution_name"`
|
||||||
Video struct {
|
Video struct {
|
||||||
AudioCodec string `json:"audio_codec"`
|
AudioCodec string `json:"audio_codec"`
|
||||||
BitRate int `json:"bit_rate"`
|
BitRate int `json:"bit_rate"`
|
||||||
@ -126,7 +133,7 @@ type Files struct {
|
|||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
} `json:"video"`
|
} `json:"video"`
|
||||||
VipTypes []string `json:"vip_types"`
|
VipTypes []string `json:"vip_types"`
|
||||||
} `json:"medias"` */
|
} `json:"medias"`
|
||||||
Trashed bool `json:"trashed"`
|
Trashed bool `json:"trashed"`
|
||||||
DeleteTime string `json:"delete_time"`
|
DeleteTime string `json:"delete_time"`
|
||||||
OriginalURL string `json:"original_url"`
|
OriginalURL string `json:"original_url"`
|
||||||
|
@ -52,6 +52,7 @@ type Common struct {
|
|||||||
PackageName string
|
PackageName string
|
||||||
UserAgent string
|
UserAgent string
|
||||||
DownloadUserAgent string
|
DownloadUserAgent string
|
||||||
|
UseVideoUrl bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) SetCaptchaToken(captchaToken string) {
|
func (c *Common) SetCaptchaToken(captchaToken string) {
|
||||||
|
@ -76,10 +76,15 @@ func (m *Monitor) Update() (bool, error) {
|
|||||||
}
|
}
|
||||||
m.retried = 0
|
m.retried = 0
|
||||||
if len(info.FollowedBy) != 0 {
|
if len(info.FollowedBy) != 0 {
|
||||||
|
log.Debugf("followen by: %+v", info.FollowedBy)
|
||||||
gid := info.FollowedBy[0]
|
gid := info.FollowedBy[0]
|
||||||
notify.Signals.Delete(m.tsk.ID)
|
notify.Signals.Delete(m.tsk.ID)
|
||||||
|
oldId := m.tsk.ID
|
||||||
m.tsk.ID = gid
|
m.tsk.ID = gid
|
||||||
|
DownTaskManager.RawTasks().Delete(oldId)
|
||||||
|
DownTaskManager.RawTasks().Store(m.tsk.ID, m.tsk)
|
||||||
notify.Signals.Store(gid, m.c)
|
notify.Signals.Store(gid, m.c)
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
// update download status
|
// update download status
|
||||||
total, err := strconv.ParseUint(info.TotalLength, 10, 64)
|
total, err := strconv.ParseUint(info.TotalLength, 10, 64)
|
||||||
@ -120,6 +125,7 @@ func (m *Monitor) Complete() error {
|
|||||||
}
|
}
|
||||||
// get files
|
// get files
|
||||||
files, err := client.GetFiles(m.tsk.ID)
|
files, err := client.GetFiles(m.tsk.ID)
|
||||||
|
log.Debugf("files len: %d", len(files))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
|
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
|
||||||
}
|
}
|
||||||
@ -134,7 +140,8 @@ func (m *Monitor) Complete() error {
|
|||||||
log.Errorf("failed to remove aria2 temp dir: %+v", err.Error())
|
log.Errorf("failed to remove aria2 temp dir: %+v", err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for _, file := range files {
|
for i, _ := range files {
|
||||||
|
file := files[i]
|
||||||
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||||
Name: fmt.Sprintf("transfer %s to [%s](%s)", file.Path, storage.GetStorage().MountPath, dstDirActualPath),
|
Name: fmt.Sprintf("transfer %s to [%s](%s)", file.Path, storage.GetStorage().MountPath, dstDirActualPath),
|
||||||
Func: func(tsk *task.Task[uint64]) error {
|
Func: func(tsk *task.Task[uint64]) error {
|
||||||
|
@ -53,9 +53,6 @@ func InitDB() {
|
|||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
||||||
database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode)
|
database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode)
|
||||||
dB, err = gorm.Open(mysql.Open(dsn), gormConfig)
|
dB, err = gorm.Open(mysql.Open(dsn), gormConfig)
|
||||||
if err == nil {
|
|
||||||
dB = dB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "postgres":
|
case "postgres":
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -11,7 +12,12 @@ var db *gorm.DB
|
|||||||
|
|
||||||
func Init(d *gorm.DB) {
|
func Init(d *gorm.DB) {
|
||||||
db = d
|
db = d
|
||||||
err := db.AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
|
var err error
|
||||||
|
if conf.Conf.Database.Type == "mysql" {
|
||||||
|
err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
|
||||||
|
} else {
|
||||||
|
err = db.AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed migrate database: %s", err.Error())
|
log.Fatalf("failed migrate database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -16,37 +16,43 @@ import (
|
|||||||
func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
||||||
meta := ctx.Value("meta").(*model.Meta)
|
meta := ctx.Value("meta").(*model.Meta)
|
||||||
user := ctx.Value("user").(*model.User)
|
user := ctx.Value("user").(*model.User)
|
||||||
|
var objs []model.Obj
|
||||||
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
||||||
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(virtualFiles) != 0 {
|
if len(virtualFiles) == 0 {
|
||||||
return virtualFiles, nil
|
return nil, errors.WithMessage(err, "failed get storage")
|
||||||
}
|
}
|
||||||
return nil, errors.WithMessage(err, "failed get storage")
|
} else {
|
||||||
}
|
objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
|
||||||
objs, err := op.List(ctx, storage, actualPath, model.ListArgs{
|
ReqPath: path,
|
||||||
ReqPath: path,
|
}, refresh...)
|
||||||
}, refresh...)
|
if err != nil {
|
||||||
if err != nil {
|
log.Errorf("%+v", err)
|
||||||
log.Errorf("%+v", err)
|
if len(virtualFiles) == 0 {
|
||||||
if len(virtualFiles) != 0 {
|
return nil, errors.WithMessage(err, "failed get objs")
|
||||||
return virtualFiles, nil
|
}
|
||||||
}
|
}
|
||||||
return nil, errors.WithMessage(err, "failed get objs")
|
|
||||||
}
|
}
|
||||||
for _, storageFile := range virtualFiles {
|
if objs == nil {
|
||||||
if !containsByName(objs, storageFile) {
|
objs = virtualFiles
|
||||||
objs = append(objs, storageFile)
|
} else {
|
||||||
|
for _, storageFile := range virtualFiles {
|
||||||
|
if !containsByName(objs, storageFile) {
|
||||||
|
objs = append(objs, storageFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if whetherHide(user, meta, path) {
|
if whetherHide(user, meta, path) {
|
||||||
objs = hide(objs, meta)
|
objs = hide(objs, meta)
|
||||||
}
|
}
|
||||||
// sort objs
|
// sort objs
|
||||||
if storage.Config().LocalSort {
|
if storage != nil {
|
||||||
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
|
if storage.Config().LocalSort {
|
||||||
|
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
|
||||||
|
}
|
||||||
|
model.ExtractFolder(objs, storage.GetStorage().ExtractFolder)
|
||||||
}
|
}
|
||||||
model.ExtractFolder(objs, storage.GetStorage().ExtractFolder)
|
|
||||||
return objs, nil
|
return objs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
|
|||||||
}
|
}
|
||||||
key := Key(storage, path)
|
key := Key(storage, path)
|
||||||
if len(refresh) == 0 || !refresh[0] {
|
if len(refresh) == 0 || !refresh[0] {
|
||||||
if files, ok := listCache.Get(key); ok {
|
if files, ok := listCache.Get(key); ok && len(files) > 0 {
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,10 @@ func (tm *Manager[K]) ClearDone() {
|
|||||||
tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED)
|
tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tm *Manager[K]) RawTasks() *generic_sync.MapOf[K, *Task[K]] {
|
||||||
|
return &tm.tasks
|
||||||
|
}
|
||||||
|
|
||||||
func NewTaskManager[K comparable](maxWorker int, updateID ...func(*K)) *Manager[K] {
|
func NewTaskManager[K comparable](maxWorker int, updateID ...func(*K)) *Manager[K] {
|
||||||
tm := &Manager[K]{
|
tm := &Manager[K]{
|
||||||
tasks: generic_sync.MapOf[K, *Task[K]]{},
|
tasks: generic_sync.MapOf[K, *Task[K]]{},
|
||||||
|
@ -3,7 +3,9 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetSHA1Encode(data string) string {
|
func GetSHA1Encode(data string) string {
|
||||||
@ -17,3 +19,20 @@ func GetMD5Encode(data string) string {
|
|||||||
h.Write([]byte(data))
|
h.Write([]byte(data))
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DEC = map[string]string{
|
||||||
|
"-": "+",
|
||||||
|
"_": "/",
|
||||||
|
".": "=",
|
||||||
|
}
|
||||||
|
|
||||||
|
func SafeAtob(data string) (string, error) {
|
||||||
|
for k, v := range DEC {
|
||||||
|
data = strings.ReplaceAll(data, k, v)
|
||||||
|
}
|
||||||
|
bytes, err := base64.StdEncoding.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Favicon(c *gin.Context) {
|
func Favicon(c *gin.Context) {
|
||||||
@ -18,20 +18,33 @@ func Favicon(c *gin.Context) {
|
|||||||
|
|
||||||
func Plist(c *gin.Context) {
|
func Plist(c *gin.Context) {
|
||||||
link := c.Param("link")
|
link := c.Param("link")
|
||||||
u, err := url.PathUnescape(link)
|
u, err := utils.SafeAtob(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uUrl, err := url.Parse(u)
|
uUrl, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := c.Param("name")
|
fullName := c.Param("name")
|
||||||
log.Debug("name", name)
|
Url := uUrl.String()
|
||||||
u = uUrl.String()
|
fullName = strings.TrimSuffix(fullName, ".plist")
|
||||||
name = strings.TrimSuffix(name, ".plist")
|
fullName, err = utils.SafeAtob(fullName)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := fullName
|
||||||
|
identifier := fmt.Sprintf("ci.nn.%s", url.PathEscape(fullName))
|
||||||
|
sep := "@"
|
||||||
|
if strings.Contains(fullName, sep) {
|
||||||
|
ss := strings.Split(fullName, sep)
|
||||||
|
name = strings.Join(ss[:len(ss)-1], sep)
|
||||||
|
identifier = ss[len(ss)-1]
|
||||||
|
}
|
||||||
|
|
||||||
name = strings.ReplaceAll(name, "<", "[")
|
name = strings.ReplaceAll(name, "<", "[")
|
||||||
name = strings.ReplaceAll(name, ">", "]")
|
name = strings.ReplaceAll(name, ">", "]")
|
||||||
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
@ -46,13 +59,13 @@ func Plist(c *gin.Context) {
|
|||||||
<key>kind</key>
|
<key>kind</key>
|
||||||
<string>software-package</string>
|
<string>software-package</string>
|
||||||
<key>url</key>
|
<key>url</key>
|
||||||
<string>%s</string>
|
<string><![CDATA[%s]]></string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>metadata</key>
|
<key>metadata</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>bundle-identifier</key>
|
<key>bundle-identifier</key>
|
||||||
<string>ci.nn.%s</string>
|
<string>%s</string>
|
||||||
<key>bundle-version</key>
|
<key>bundle-version</key>
|
||||||
<string>4.4</string>
|
<string>4.4</string>
|
||||||
<key>kind</key>
|
<key>kind</key>
|
||||||
@ -63,7 +76,7 @@ func Plist(c *gin.Context) {
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`, u, url.PathEscape(name), name)
|
</plist>`, Url, identifier, name)
|
||||||
c.Header("Content-Type", "application/xml;charset=utf-8")
|
c.Header("Content-Type", "application/xml;charset=utf-8")
|
||||||
c.Status(200)
|
c.Status(200)
|
||||||
_, _ = c.Writer.WriteString(plist)
|
_, _ = c.Writer.WriteString(plist)
|
||||||
|
Reference in New Issue
Block a user