mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-06-09 09:29:57 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
721c9666eb | ||
|
|
6f35401181 | ||
|
|
72ae2ce079 | ||
|
|
495ad3ea5c | ||
|
|
3def9df4b4 | ||
|
|
790a32d297 | ||
|
|
f7779224ef |
12
README_EN.md
12
README_EN.md
@@ -10,21 +10,13 @@ Save Telegram files to various storage endpoints.
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Demo Video:
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
[SaveAny-Bot Demo Video.webm](https://github.com/user-attachments/assets/a0de2453-a4d1-4a12-81fb-9d84856dce09)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Deploy from Binary
|
### Deploy from Binary
|
||||||
|
|
||||||
Download the binary file for your platform from the [Release](https://github.com/krau/SaveAny-Bot/releases) page.
|
Download the binary file for your platform from the [Release](https://github.com/krau/SaveAny-Bot/releases) page.
|
||||||
|
|
||||||
Create a `config.toml` file in the extracted directory, refer to [config.toml.example](https://github.com/krau/SaveAny-Bot/blob/main/config.example.toml) for configuration.
|
Create a `config.toml` file in the extracted directory, refer to [config.example.toml](https://github.com/krau/SaveAny-Bot/blob/main/config.example.toml) for configuration.
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
@@ -62,7 +54,7 @@ systemctl enable --now saveany-bot
|
|||||||
|
|
||||||
#### Docker Compose
|
#### Docker Compose
|
||||||
|
|
||||||
Download [docker-compose.yml](https://github.com/krau/SaveAny-Bot/blob/main/docker-compose.yml) file and create a `config.toml` file in the same directory, refer to [config.toml.example](https://github.com/krau/SaveAny-Bot/blob/main/config.example.toml) for configuration.
|
Download [docker-compose.yml](https://github.com/krau/SaveAny-Bot/blob/main/docker-compose.yml) file and create a `config.toml` file in the same directory, refer to [config.example.toml](https://github.com/krau/SaveAny-Bot/blob/main/config.example.toml) for configuration.
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ url = "socks5://127.0.0.1:7890"
|
|||||||
[[storages]]
|
[[storages]]
|
||||||
# 标识名, 需要唯一
|
# 标识名, 需要唯一
|
||||||
name = "本机1"
|
name = "本机1"
|
||||||
# 存储类型, 目前可用: local , alist , webdav
|
# 存储类型, 目前可用: local, alist, webdav, minio
|
||||||
type = "local"
|
type = "local"
|
||||||
# 启用存储
|
# 启用存储
|
||||||
enable = true
|
enable = true
|
||||||
@@ -59,6 +59,16 @@ url = 'https://example.com/dav'
|
|||||||
username = 'username'
|
username = 'username'
|
||||||
password = 'password'
|
password = 'password'
|
||||||
|
|
||||||
|
[[storages]]
|
||||||
|
name = "MyMinio"
|
||||||
|
type = "minio"
|
||||||
|
enable = true
|
||||||
|
endpoint = 'play.min.io'
|
||||||
|
use_ssl = true
|
||||||
|
access_key_id = 'Q3AM3UQ867SPQQA43P2F'
|
||||||
|
secret_access_key = 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG'
|
||||||
|
bucket_name = 'saveanybot'
|
||||||
|
base_path = '/path/telegram'
|
||||||
|
|
||||||
# 用户列表
|
# 用户列表
|
||||||
[[users]]
|
[[users]]
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// storage_config.go
|
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -18,24 +16,49 @@ type StorageConfig interface {
|
|||||||
|
|
||||||
// Base storage config
|
// Base storage config
|
||||||
type NewStorageConfig struct {
|
type NewStorageConfig struct {
|
||||||
Name string `toml:"name" mapstructure:"name" json:"name"`
|
Name string `toml:"name" mapstructure:"name" json:"name"`
|
||||||
Type string `toml:"type" mapstructure:"type" json:"type"`
|
Type string `toml:"type" mapstructure:"type" json:"type"`
|
||||||
Enable bool `toml:"enable" mapstructure:"enable" json:"enable"`
|
Enable bool `toml:"enable" mapstructure:"enable" json:"enable"`
|
||||||
RawConfig map[string]interface{} `toml:"-" mapstructure:",remain"`
|
RawConfig map[string]any `toml:"-" mapstructure:",remain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StorageConfigFactory func(cfg *NewStorageConfig) (StorageConfig, error)
|
var storageFactories = map[types.StorageType]func(cfg *NewStorageConfig) (StorageConfig, error){
|
||||||
|
types.StorageTypeLocal: newLocalStorageConfig,
|
||||||
var storageFactories = make(map[string]StorageConfigFactory)
|
types.StorageTypeAlist: newAlistStorageConfig,
|
||||||
|
types.StorageTypeWebdav: newWebdavStorageConfig,
|
||||||
func RegisterStorageFactory(storageType string, factory StorageConfigFactory) {
|
types.StorageTypeMinio: newMinioStorageConfig,
|
||||||
storageFactories[storageType] = factory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func LoadStorageConfigs(v *viper.Viper) ([]StorageConfig, error) {
|
||||||
RegisterStorageFactory(string(types.StorageTypeLocal), newLocalStorageConfig)
|
var baseConfigs []NewStorageConfig
|
||||||
RegisterStorageFactory(string(types.StorageTypeAlist), newAlistStorageConfig)
|
if err := v.UnmarshalKey("storages", &baseConfigs); err != nil {
|
||||||
RegisterStorageFactory(string(types.StorageTypeWebdav), newWebdavStorageConfig)
|
return nil, fmt.Errorf("failed to unmarshal storage configs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var configs []StorageConfig
|
||||||
|
for _, baseCfg := range baseConfigs {
|
||||||
|
if !baseCfg.Enable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
factory, ok := storageFactories[types.StorageType(baseCfg.Type)]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unsupported storage type: %s", baseCfg.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := factory(&baseCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create storage config for %s: %w", baseCfg.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid storage config for %s: %w", baseCfg.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configs = append(configs, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalStorageConfig(cfg *NewStorageConfig) (StorageConfig, error) {
|
func newLocalStorageConfig(cfg *NewStorageConfig) (StorageConfig, error) {
|
||||||
@@ -71,34 +94,11 @@ func newWebdavStorageConfig(cfg *NewStorageConfig) (StorageConfig, error) {
|
|||||||
return &webdavCfg, nil
|
return &webdavCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadStorageConfigs(v *viper.Viper) ([]StorageConfig, error) {
|
func newMinioStorageConfig(cfg *NewStorageConfig) (StorageConfig, error) {
|
||||||
var baseConfigs []NewStorageConfig
|
var minioCfg MinioStorageConfig
|
||||||
if err := v.UnmarshalKey("storages", &baseConfigs); err != nil {
|
minioCfg.NewStorageConfig = *cfg
|
||||||
return nil, fmt.Errorf("failed to unmarshal storage configs: %w", err)
|
if err := mapstructure.Decode(cfg.RawConfig, &minioCfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode minio storage config: %w", err)
|
||||||
}
|
}
|
||||||
|
return &minioCfg, nil
|
||||||
var configs []StorageConfig
|
|
||||||
for _, baseCfg := range baseConfigs {
|
|
||||||
if !baseCfg.Enable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
factory, ok := storageFactories[baseCfg.Type]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unsupported storage type: %s", baseCfg.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := factory(&baseCfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create storage config for %s: %w", baseCfg.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cfg.Validate(); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid storage config for %s: %w", baseCfg.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
configs = append(configs, cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return configs, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,3 +104,37 @@ func (w *WebdavStorageConfig) GetType() types.StorageType {
|
|||||||
func (w *WebdavStorageConfig) GetName() string {
|
func (w *WebdavStorageConfig) GetName() string {
|
||||||
return w.Name
|
return w.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MinioStorageConfig struct {
|
||||||
|
NewStorageConfig
|
||||||
|
Endpoint string `toml:"endpoint" mapstructure:"endpoint" json:"endpoint"`
|
||||||
|
AccessKeyID string `toml:"access_key_id" mapstructure:"access_key_id" json:"access_key_id"`
|
||||||
|
SecretAccessKey string `toml:"secret_access_key" mapstructure:"secret_access_key" json:"secret_access_key"`
|
||||||
|
BucketName string `toml:"bucket_name" mapstructure:"bucket_name" json:"bucket_name"`
|
||||||
|
UseSSL bool `toml:"use_ssl" mapstructure:"use_ssl" json:"use_ssl"`
|
||||||
|
BasePath string `toml:"base_path" mapstructure:"base_path" json:"base_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MinioStorageConfig) Validate() error {
|
||||||
|
if m.Endpoint == "" {
|
||||||
|
return fmt.Errorf("endpoint is required for minio storage")
|
||||||
|
}
|
||||||
|
if m.AccessKeyID == "" || m.SecretAccessKey == "" {
|
||||||
|
return fmt.Errorf("access_key_id and secret_access_key are required for minio storage")
|
||||||
|
}
|
||||||
|
if m.BucketName == "" {
|
||||||
|
return fmt.Errorf("bucket_name is required for minio storage")
|
||||||
|
}
|
||||||
|
if m.BasePath == "" {
|
||||||
|
return fmt.Errorf("base_path is required for minio storage")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MinioStorageConfig) GetType() types.StorageType {
|
||||||
|
return types.StorageTypeMinio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MinioStorageConfig) GetName() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
在 [Release](https://github.com/krau/SaveAny-Bot/releases) 页面下载对应平台的二进制文件.
|
在 [Release](https://github.com/krau/SaveAny-Bot/releases) 页面下载对应平台的二进制文件.
|
||||||
|
|
||||||
在解压后目录新建 `config.toml` 文件, 参考 [config.example.toml](./config.example.toml) 编辑配置文件.
|
在解压后目录新建 `config.toml` 文件, 参考 [config.example.toml](https://github.com/krau/SaveAny-Bot/blob/main/config.example.toml) 编辑配置文件.
|
||||||
|
|
||||||
运行:
|
运行:
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ systemctl enable --now saveany-bot
|
|||||||
|
|
||||||
### 为OpenWrt及衍生系统添加开机自启动服务
|
### 为OpenWrt及衍生系统添加开机自启动服务
|
||||||
|
|
||||||
创建文件 ` /etc/init.d/saveanybot` ,参考[saveanybot](./docs/saveanybot)自行修改.
|
创建文件 ` /etc/init.d/saveanybot` ,参考[saveanybot](https://github.com/krau/SaveAny-Bot/blob/main/docs/saveanybot)自行修改.
|
||||||
|
|
||||||
`chmod +x /etc/init.d/saveanybot`
|
`chmod +x /etc/init.d/saveanybot`
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ systemctl enable --now saveany-bot
|
|||||||
|
|
||||||
### 为OpenWrt及衍生系统添加快捷指令
|
### 为OpenWrt及衍生系统添加快捷指令
|
||||||
|
|
||||||
创建文件` /usr/bin/sabot` ,参考[sabot](./docs/sabot)自行配置修改,注意此处文件编码仅支持 ANSI 936 .
|
创建文件` /usr/bin/sabot` ,参考[sabot](https://github.com/krau/SaveAny-Bot/blob/main/docs/sabot)自行配置修改,注意此处文件编码仅支持 ANSI 936 .
|
||||||
|
|
||||||
`chmod +x /usr/bin/sabot`
|
`chmod +x /usr/bin/sabot`
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ systemctl enable --now saveany-bot
|
|||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
|
|
||||||
下载 [docker-compose.yml](./docker-compose.yml) 文件, 在同目录下新建 `config.toml` 文件, 参考 [config.example.toml](./config.example.toml) 编辑配置文件.
|
下载 [docker-compose.yml](https://github.com/krau/SaveAny-Bot/blob/main/docker-compose.yml) 文件, 在同目录下新建 `config.toml` 文件, 参考 [config.example.toml](https://github.com/krau/SaveAny-Bot/blob/main/config.example.toml) 编辑配置文件.
|
||||||
|
|
||||||
启动:
|
启动:
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,11 @@
|
|||||||
|
|
||||||
## 上传 alist 失败也会显示成功
|
## 上传 alist 失败也会显示成功
|
||||||
|
|
||||||
这是 alist 的上传实现导致的问题, 上传到 alist 的文件实际上会被 alist 暂存在本地, 在客户端上传结束后 alist 就返回成功, 然后 alist 会在后台将文件上传到对应的存储.
|
|
||||||
|
|
||||||
目前 bot 是根据 alist 的返回判断是否成功, 无法获知 alist 的后台上传任务是否成功.
|
|
||||||
|
|
||||||
在 alist 管理页面适当调整上传分片大小, 为 alist 使用更稳定的网络环境部署, 都可以减少这种情况的发生.
|
在 alist 管理页面适当调整上传分片大小, 为 alist 使用更稳定的网络环境部署, 都可以减少这种情况的发生.
|
||||||
|
|
||||||
## Bot 提示下载成功但是 alist 未显示
|
## Bot 提示下载成功但是 alist 未显示
|
||||||
|
|
||||||
检查 alist 后台 > 任务 > 上传 中对应的上传任务的状态, 如果任务状态为成功但目录中不显示, 是由于 alist 缓存了目录结构, 参考文档可以调整缓存时间
|
alist 缓存了目录结构, 参考文档可以调整缓存时间
|
||||||
|
|
||||||
https://alist.nn.ci/zh/guide/drivers/common.html#缓存过期
|
https://alist.nn.ci/zh/guide/drivers/common.html#缓存过期
|
||||||
|
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/gookit/slog v0.5.7
|
github.com/gookit/slog v0.5.7
|
||||||
github.com/gotd/contrib v0.21.0
|
github.com/gotd/contrib v0.21.0
|
||||||
github.com/gotd/td v0.120.0
|
github.com/gotd/td v0.120.0
|
||||||
|
github.com/minio/minio-go/v7 v7.0.81
|
||||||
github.com/rhysd/go-github-selfupdate v1.2.3
|
github.com/rhysd/go-github-selfupdate v1.2.3
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
@@ -31,7 +32,9 @@ require (
|
|||||||
github.com/go-faster/jx v1.1.0 // indirect
|
github.com/go-faster/jx v1.1.0 // indirect
|
||||||
github.com/go-faster/xor v1.0.0 // indirect
|
github.com/go-faster/xor v1.0.0 // indirect
|
||||||
github.com/go-faster/yaml v0.4.6 // indirect
|
github.com/go-faster/yaml v0.4.6 // indirect
|
||||||
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250128161936-077ca0a936bf // indirect
|
github.com/google/pprof v0.0.0-20250128161936-077ca0a936bf // indirect
|
||||||
@@ -41,13 +44,16 @@ require (
|
|||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/ogen-go/ogen v1.10.0 // indirect
|
github.com/ogen-go/ogen v1.10.0 // indirect
|
||||||
github.com/onsi/gomega v1.36.2 // indirect
|
github.com/onsi/gomega v1.36.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
github.com/tcnksm/go-gitconfig v0.1.2 // indirect
|
github.com/tcnksm/go-gitconfig v0.1.2 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -49,6 +49,8 @@ github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38=
|
|||||||
github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
|
github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
|
||||||
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
||||||
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
||||||
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
@@ -56,6 +58,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
|||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
@@ -111,6 +115,9 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -128,6 +135,10 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6
|
|||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
||||||
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||||
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
@@ -150,6 +161,8 @@ github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7
|
|||||||
github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
|
github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||||
@@ -224,6 +237,7 @@ golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func (a *Alist) Save(ctx context.Context, filePath, storagePath string) error {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Authorization", a.token)
|
req.Header.Set("Authorization", a.token)
|
||||||
req.Header.Set("File-Path", url.PathEscape(storagePath))
|
req.Header.Set("File-Path", url.PathEscape(storagePath))
|
||||||
req.Header.Set("As-Task", "true")
|
// req.Header.Set("As-Task", "true")
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
req.ContentLength = filestat.Size()
|
req.ContentLength = filestat.Size()
|
||||||
|
|
||||||
@@ -192,7 +192,6 @@ func (a *Alist) NewUploadStream(ctx context.Context, storagePath string) (io.Wri
|
|||||||
|
|
||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
|
|
||||||
// 创建上传流对象
|
|
||||||
us := &uploadStream{
|
us := &uploadStream{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
client: a.client,
|
client: a.client,
|
||||||
@@ -215,7 +214,7 @@ func (a *Alist) NewUploadStream(ctx context.Context, storagePath string) (io.Wri
|
|||||||
|
|
||||||
req.Header.Set("Authorization", a.token)
|
req.Header.Set("Authorization", a.token)
|
||||||
req.Header.Set("File-Path", url.PathEscape(storagePath))
|
req.Header.Set("File-Path", url.PathEscape(storagePath))
|
||||||
req.Header.Set("As-Task", "true")
|
// req.Header.Set("As-Task", "true")
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
resp, err := a.client.Do(req)
|
resp, err := a.client.Do(req)
|
||||||
|
|||||||
71
storage/minio/client.go
Normal file
71
storage/minio/client.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
|
"github.com/krau/SaveAny-Bot/logger"
|
||||||
|
"github.com/krau/SaveAny-Bot/types"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Minio struct {
|
||||||
|
config config.MinioStorageConfig
|
||||||
|
client *minio.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) Init(cfg config.StorageConfig) error {
|
||||||
|
minioConfig, ok := cfg.(*config.MinioStorageConfig)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to cast minio config")
|
||||||
|
}
|
||||||
|
if err := minioConfig.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.config = *minioConfig
|
||||||
|
|
||||||
|
client, err := minio.New(m.config.Endpoint, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(m.config.AccessKeyID, m.config.SecretAccessKey, ""),
|
||||||
|
Secure: m.config.UseSSL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create minio client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := client.BucketExists(context.Background(), m.config.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check bucket existence: %w", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("bucket %s does not exist", m.config.BucketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.client = client
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) Type() types.StorageType {
|
||||||
|
return types.StorageTypeMinio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) Name() string {
|
||||||
|
return m.config.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) JoinStoragePath(task types.Task) string {
|
||||||
|
return path.Join(m.config.BasePath, task.StoragePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) Save(ctx context.Context, localFilePath, storagePath string) error {
|
||||||
|
logger.L.Infof("Saving file %s to %s", localFilePath, storagePath)
|
||||||
|
|
||||||
|
_, err := m.client.FPutObject(ctx, m.config.BucketName, storagePath, localFilePath, minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to upload file to minio: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
92
storage/minio/stream.go
Normal file
92
storage/minio/stream.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/krau/SaveAny-Bot/logger"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MinioWriter struct {
|
||||||
|
pipeWriter *io.PipeWriter
|
||||||
|
done chan error
|
||||||
|
path string
|
||||||
|
ctx context.Context
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MinioWriter) Write(p []byte) (n int, err error) {
|
||||||
|
select {
|
||||||
|
case <-w.ctx.Done():
|
||||||
|
return 0, w.ctx.Err()
|
||||||
|
default:
|
||||||
|
return w.pipeWriter.Write(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MinioWriter) Close() error {
|
||||||
|
if w.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.closed = true
|
||||||
|
|
||||||
|
if err := w.pipeWriter.Close(); err != nil {
|
||||||
|
return fmt.Errorf("failed to close pipe writer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-w.done:
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("upload failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-w.ctx.Done():
|
||||||
|
return fmt.Errorf("upload cancelled: %w", w.ctx.Err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Minio) NewUploadStream(ctx context.Context, storagePath string) (io.WriteCloser, error) {
|
||||||
|
logger.L.Infof("Creating upload stream for %s", storagePath)
|
||||||
|
|
||||||
|
uploadCtx, cancel := context.WithCancel(ctx)
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
done := make(chan error, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
done <- fmt.Errorf("panic during upload: %v", r)
|
||||||
|
}
|
||||||
|
pipeReader.Close()
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
info, err := m.client.PutObject(
|
||||||
|
uploadCtx,
|
||||||
|
m.config.BucketName,
|
||||||
|
storagePath,
|
||||||
|
pipeReader,
|
||||||
|
-1,
|
||||||
|
minio.PutObjectOptions{},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.L.Errorf("Failed to upload to %s: %v", storagePath, err)
|
||||||
|
done <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.L.Infof("uploaded %d bytes to %s", info.Size, storagePath)
|
||||||
|
done <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &MinioWriter{
|
||||||
|
pipeWriter: pipeWriter,
|
||||||
|
done: done,
|
||||||
|
path: storagePath,
|
||||||
|
ctx: uploadCtx,
|
||||||
|
closed: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/krau/SaveAny-Bot/storage/alist"
|
"github.com/krau/SaveAny-Bot/storage/alist"
|
||||||
"github.com/krau/SaveAny-Bot/storage/local"
|
"github.com/krau/SaveAny-Bot/storage/local"
|
||||||
"github.com/krau/SaveAny-Bot/storage/webdav"
|
"github.com/krau/SaveAny-Bot/storage/webdav"
|
||||||
|
"github.com/krau/SaveAny-Bot/storage/minio"
|
||||||
"github.com/krau/SaveAny-Bot/types"
|
"github.com/krau/SaveAny-Bot/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@ var storageConstructors = map[string]StorageConstructor{
|
|||||||
string(types.StorageTypeAlist): func() Storage { return new(alist.Alist) },
|
string(types.StorageTypeAlist): func() Storage { return new(alist.Alist) },
|
||||||
string(types.StorageTypeLocal): func() Storage { return new(local.Local) },
|
string(types.StorageTypeLocal): func() Storage { return new(local.Local) },
|
||||||
string(types.StorageTypeWebdav): func() Storage { return new(webdav.Webdav) },
|
string(types.StorageTypeWebdav): func() Storage { return new(webdav.Webdav) },
|
||||||
|
string(types.StorageTypeMinio): func() Storage { return new(minio.Minio) },
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStorage(cfg config.StorageConfig) (Storage, error) {
|
func NewStorage(cfg config.StorageConfig) (Storage, error) {
|
||||||
|
|||||||
@@ -25,13 +25,15 @@ var (
|
|||||||
StorageTypeLocal StorageType = "local"
|
StorageTypeLocal StorageType = "local"
|
||||||
StorageTypeWebdav StorageType = "webdav"
|
StorageTypeWebdav StorageType = "webdav"
|
||||||
StorageTypeAlist StorageType = "alist"
|
StorageTypeAlist StorageType = "alist"
|
||||||
|
StorageTypeMinio StorageType = "minio"
|
||||||
)
|
)
|
||||||
|
|
||||||
var StorageTypes = []StorageType{StorageTypeLocal, StorageTypeAlist, StorageTypeWebdav}
|
var StorageTypes = []StorageType{StorageTypeLocal, StorageTypeAlist, StorageTypeWebdav, StorageTypeMinio}
|
||||||
var StorageTypeDisplay = map[StorageType]string{
|
var StorageTypeDisplay = map[StorageType]string{
|
||||||
StorageTypeLocal: "本地磁盘",
|
StorageTypeLocal: "本地磁盘",
|
||||||
StorageTypeWebdav: "WebDAV",
|
StorageTypeWebdav: "WebDAV",
|
||||||
StorageTypeAlist: "Alist",
|
StorageTypeAlist: "Alist",
|
||||||
|
StorageTypeMinio: "Minio",
|
||||||
}
|
}
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user