Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0982abe7bc | ||
|
|
e74839b8e9 | ||
|
|
58ce8275b2 | ||
|
|
8f67f778a3 | ||
|
|
159dba6224 | ||
|
|
22d773da10 | ||
|
|
69ccfa664f | ||
|
|
38355dfd14 | ||
|
|
0940258b4d |
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/shortcut"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
||||
"github.com/krau/SaveAny-Bot/database"
|
||||
"github.com/krau/SaveAny-Bot/config"
|
||||
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||
@@ -53,9 +53,13 @@ func handleGroupMediaMessage(ctx *ext.Context, update *ext.Update, message *tg.M
|
||||
if !supported {
|
||||
return dispatcher.EndGroups
|
||||
}
|
||||
file, err := tfile.FromMediaMessage(media, ctx.Raw, message, tfile.WithNameIfEmpty(
|
||||
tgutil.GenFileNameFromMessage(*message),
|
||||
))
|
||||
userId := update.GetUserChat().GetID()
|
||||
userDB, err := database.GetUserByChatID(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tfOpts := mediautil.TfileOptions(ctx, userDB, message)
|
||||
file, err := tfile.FromMediaMessage(media, ctx.Raw, message, tfOpts...)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to get file from media: %s", err)
|
||||
return dispatcher.EndGroups
|
||||
|
||||
@@ -252,6 +252,16 @@ func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
||||
logger.Errorf("Failed to get storage by user ID %d and name %s: %v", user.ChatID, user.DefaultStorage, err)
|
||||
continue
|
||||
}
|
||||
// Resolve the default directory path from user.DefaultDir
|
||||
var defaultDirPath string
|
||||
if user.DefaultDir != 0 {
|
||||
dir, err := database.GetDirByID(ctx, user.DefaultDir)
|
||||
if err != nil {
|
||||
logger.Warnf("Failed to get default dir for user %d: %v, using root", user.ChatID, err)
|
||||
} else {
|
||||
defaultDirPath = dir.Path
|
||||
}
|
||||
}
|
||||
switch user.FilenameStrategy {
|
||||
case fnamest.Message.String():
|
||||
file.SetName(tgutil.GenFileNameFromMessage(*file.Message()))
|
||||
@@ -286,14 +296,14 @@ func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
||||
|
||||
if needAlbumHandling {
|
||||
// For media groups with NEW-FOR-ALBUM rule, collect all files of the same group
|
||||
watchMediaGroupMgr.addFile(event.ChatID, user.ID, file, time.Duration(config.C().Telegram.MediaGroupTimeout)*time.Second, func(files []tfile.TGFileMessage) {
|
||||
processWatchMediaGroup(ctx, user, stor, "", files)
|
||||
watchMediaGroupMgr.addFile(event.ChatID, user.ID, file, time.Duration(max(config.C().Telegram.MediaGroupTimeout, 1))*time.Second, func(files []tfile.TGFileMessage) {
|
||||
processWatchMediaGroup(ctx, user, stor, defaultDirPath, files)
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Process single file or media group without album folder creation
|
||||
var dirPath string
|
||||
dirPath := defaultDirPath
|
||||
if user.ApplyRule && user.Rules != nil {
|
||||
matched, matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||
if !matched {
|
||||
@@ -352,6 +362,7 @@ func processWatchMediaGroup(ctx *ext.Context, user *database.User, stor storage.
|
||||
type albumFile struct {
|
||||
file tfile.TGFileMessage
|
||||
storage storage.Storage
|
||||
dirPath string
|
||||
}
|
||||
albumFiles := make(map[int64][]albumFile)
|
||||
|
||||
@@ -374,9 +385,11 @@ func processWatchMediaGroup(ctx *ext.Context, user *database.User, stor storage.
|
||||
continue
|
||||
}
|
||||
|
||||
if !ruleDirPath.NeedNewForAlbum() {
|
||||
logger.Warnf("File %s does not need album folder, skipping", file.Name())
|
||||
continue
|
||||
// Use the effective dirPath: if rule returns NEW-FOR-ALBUM sentinel, fall back to the
|
||||
// base dirPath passed in (which is defaultDirPath from the caller).
|
||||
effectiveDirPath := string(ruleDirPath)
|
||||
if ruleDirPath.NeedNewForAlbum() {
|
||||
effectiveDirPath = dirPath
|
||||
}
|
||||
|
||||
if _, ok := albumFiles[groupId]; !ok {
|
||||
@@ -385,6 +398,7 @@ func processWatchMediaGroup(ctx *ext.Context, user *database.User, stor storage.
|
||||
albumFiles[groupId] = append(albumFiles[groupId], albumFile{
|
||||
file: file,
|
||||
storage: fileStor,
|
||||
dirPath: effectiveDirPath,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -403,7 +417,7 @@ func processWatchMediaGroup(ctx *ext.Context, user *database.User, stor storage.
|
||||
logger.Infof("Creating album folder for group %d: %s with %d files", groupID, albumDir, len(afiles))
|
||||
|
||||
for _, af := range afiles {
|
||||
afstorPath := path.Join(dirPath, albumDir, af.file.Name())
|
||||
afstorPath := path.Join(af.dirPath, albumDir, af.file.Name())
|
||||
taskid := xid.New().String()
|
||||
task, err := coretfile.NewTGFileTask(taskid, injectCtx, af.file, albumStor, afstorPath, nil)
|
||||
if err != nil {
|
||||
|
||||
25
cmd/run.go
25
cmd/run.go
@@ -2,9 +2,9 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"slices"
|
||||
@@ -27,14 +27,27 @@ import (
|
||||
func Run(cmd *cobra.Command, _ []string) {
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
logger := log.NewWithOptions(os.Stdout, log.Options{
|
||||
Level: log.DebugLevel,
|
||||
Level: log.InfoLevel,
|
||||
ReportTimestamp: true,
|
||||
TimeFormat: time.TimeOnly,
|
||||
ReportCaller: true,
|
||||
})
|
||||
log.SetDefault(logger)
|
||||
ctx = log.WithContext(ctx, logger)
|
||||
|
||||
exitChan, err := initAll(ctx, cmd)
|
||||
configFile := config.GetConfigFile(cmd)
|
||||
if err := config.Init(ctx, configFile); err != nil {
|
||||
logger.Fatal("Init failed", "error", err)
|
||||
}
|
||||
|
||||
level, err := log.ParseLevel(strings.TrimSpace(config.C().Log.Level))
|
||||
if err != nil {
|
||||
logger.Warn("Invalid log level, fallback to debug", "level", config.C().Log.Level, "error", err)
|
||||
level = log.DebugLevel
|
||||
}
|
||||
logger.SetLevel(level)
|
||||
|
||||
exitChan, err := initAll(ctx)
|
||||
if err != nil {
|
||||
logger.Fatal("Init failed", "error", err)
|
||||
}
|
||||
@@ -51,11 +64,7 @@ func Run(cmd *cobra.Command, _ []string) {
|
||||
cleanCache()
|
||||
}
|
||||
|
||||
func initAll(ctx context.Context, cmd *cobra.Command) (<-chan struct{}, error) {
|
||||
configFile := config.GetConfigFile(cmd)
|
||||
if err := config.Init(ctx, configFile); err != nil {
|
||||
return nil, fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
func initAll(ctx context.Context) (<-chan struct{}, error) {
|
||||
cache.Init()
|
||||
logger := log.FromContext(ctx)
|
||||
i18n.Init(config.C().Lang)
|
||||
|
||||
@@ -5,6 +5,10 @@ retry = 3 # 下载失败重试次数
|
||||
threads = 4 # 单个任务下载使用的最大线程数
|
||||
stream = false # 使用流式传输模式, 建议仅在硬盘空间十分有限时使用.
|
||||
|
||||
[log]
|
||||
# 日志级别, 可选: debug, info, warn, error, fatal
|
||||
level = "debug"
|
||||
|
||||
[telegram]
|
||||
# Bot Token
|
||||
# 更换 Bot Token 后请删除会话数据库文件 (默认路径为 data/session.db )
|
||||
@@ -73,4 +77,4 @@ blacklist = true
|
||||
[[users]]
|
||||
id = 123456
|
||||
storages = ["本机1"]
|
||||
blacklist = false # 使用白名单模式,此时,用户 123456 仅可使用标识名为 '本地1' 的存储
|
||||
blacklist = false # 使用白名单模式,此时,用户 123456 仅可使用标识名为 '本地1' 的存储
|
||||
|
||||
@@ -17,6 +17,7 @@ func RegisterFlags(cmd *cobra.Command) {
|
||||
flags.Bool("stream", false, "enable stream mode")
|
||||
flags.Bool("no-clean-cache", false, "do not clean cache on exit")
|
||||
flags.String("proxy", "", "proxy URL (http, https, socks5, socks5h)")
|
||||
flags.String("log-level", "", "log level (trace/debug, info, warn, error, fatal)")
|
||||
|
||||
// Telegram 配置
|
||||
flags.String("telegram-token", "", "telegram bot token")
|
||||
@@ -54,6 +55,7 @@ func bindFlags(cmd *cobra.Command) {
|
||||
viper.BindPFlag("stream", flags.Lookup("stream"))
|
||||
viper.BindPFlag("no_clean_cache", flags.Lookup("no-clean-cache"))
|
||||
viper.BindPFlag("proxy", flags.Lookup("proxy"))
|
||||
viper.BindPFlag("log.level", flags.Lookup("log-level"))
|
||||
|
||||
// Telegram
|
||||
viper.BindPFlag("telegram.token", flags.Lookup("telegram-token"))
|
||||
|
||||
5
config/log.go
Normal file
5
config/log.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package config
|
||||
|
||||
type logConfig struct {
|
||||
Level string `toml:"level" mapstructure:"level" json:"level"`
|
||||
}
|
||||
@@ -23,6 +23,7 @@ type Config struct {
|
||||
Threads int `toml:"threads" mapstructure:"threads" json:"threads"`
|
||||
Stream bool `toml:"stream" mapstructure:"stream" json:"stream"`
|
||||
Proxy string `toml:"proxy" mapstructure:"proxy" json:"proxy"`
|
||||
Log logConfig `toml:"log" mapstructure:"log" json:"log"`
|
||||
Aria2 aria2Config `toml:"aria2" mapstructure:"aria2" json:"aria2"`
|
||||
API apiConfig `toml:"api" mapstructure:"api" json:"api"`
|
||||
|
||||
@@ -100,10 +101,11 @@ func Init(ctx context.Context, configFile ...string) error {
|
||||
|
||||
defaultConfigs := map[string]any{
|
||||
// 基础配置
|
||||
"lang": "zh-Hans",
|
||||
"workers": 3,
|
||||
"retry": 3,
|
||||
"threads": 4,
|
||||
"lang": "zh-Hans",
|
||||
"workers": 3,
|
||||
"retry": 3,
|
||||
"threads": 4,
|
||||
"log.level": "debug",
|
||||
|
||||
// 缓存配置
|
||||
"cache.ttl": 86400,
|
||||
@@ -135,12 +137,6 @@ func Init(ctx context.Context, configFile ...string) error {
|
||||
viper.SetDefault(key, value)
|
||||
}
|
||||
|
||||
if err := viper.SafeWriteConfigAs("config.toml"); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileAlreadyExistsError); !ok {
|
||||
return fmt.Errorf("error saving default config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
fmt.Println("Error reading config file, ", err)
|
||||
return err
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/gormlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -13,248 +13,4 @@ To use the bot's Telegram file saving feature, you need to send or forward the f
|
||||
|
||||
1. File or media messages, such as images, videos, documents, etc.
|
||||
2. Telegram message links, for example: `https://t.me/acherkrau/1097`. **Even if the channel prohibits forwarding and saving, the bot can still download its files.**
|
||||
3. Telegra.ph article links. The bot will download all images in the article.
|
||||
|
||||
## Silent Mode (silent)
|
||||
|
||||
Use the `/silent` command to toggle silent mode.
|
||||
|
||||
By default, silent mode is off, and the bot will ask you for the save location of each file.
|
||||
|
||||
When silent mode is enabled, the bot will save files directly to the default location without confirmation.
|
||||
|
||||
Before enabling silent mode, you need to set the default save location using the `/storage` command.
|
||||
|
||||
|
||||
## Storage Rules
|
||||
|
||||
Storage rules allow you to define redirection rules when the bot uploads files to storage, so that saved files are automatically organized.
|
||||
|
||||
See: <a href="https://github.com/krau/SaveAny-Bot/issues/28" target="_blank">#28</a>
|
||||
|
||||
Currently supported rule types:
|
||||
|
||||
1. FILENAME-REGEX
|
||||
2. MESSAGE-REGEX
|
||||
3. IS-ALBUM
|
||||
|
||||
Basic syntax for adding rules:
|
||||
|
||||
"RuleType RuleContent StorageName Path"
|
||||
|
||||
Pay attention to spaces; the bot can only parse correctly formatted syntax. Below is an example of a valid rule command:
|
||||
|
||||
```
|
||||
/rule add FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /videos
|
||||
```
|
||||
|
||||
In addition, if `CHOSEN` is used as the storage name in the rule, it means files will be stored under the path of the storage you selected by clicking the inline button.
|
||||
|
||||
Rule types:
|
||||
|
||||
### FILENAME-REGEX
|
||||
|
||||
Matches based on filename regex. The rule content must be a valid regular expression, such as:
|
||||
|
||||
```
|
||||
FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /videos
|
||||
```
|
||||
|
||||
This means files with extensions mp4, mkv, ts, avi, flv will be saved to the `/videos` directory in the storage named `MyAlist` (also affected by the `base_path` in the configuration file).
|
||||
|
||||
### MESSAGE-REGEX
|
||||
|
||||
Similar to the above, but matches based on the text content of the message itself.
|
||||
|
||||
### IS-ALBUM
|
||||
|
||||
Matches album messages (media groups). Rule content can only be `true` or `false`.
|
||||
|
||||
If the path in the rule uses `NEW-FOR-ALBUM`, the bot will create a new folder for each media group and store all files of that group there. See: https://github.com/krau/SaveAny-Bot/issues/87
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
IS-ALBUM true MyWebdav NEW-FOR-ALBUM
|
||||
```
|
||||
|
||||
This will save media-group messages to the storage named `MyWebdav`, creating a new folder (generated from the first file) for each album.
|
||||
|
||||
## Watch Chats
|
||||
|
||||
{{< hint warning >}}
|
||||
This feature requires enabling UserBot integration.
|
||||
{{< /hint >}}
|
||||
|
||||
You can watch messages in a specific chat and automatically save them to the default storage, following storage rules. You can also add filters so that only matching messages are saved.
|
||||
|
||||
Watch a chat:
|
||||
|
||||
```
|
||||
/watch <chat_id/username> [filter]
|
||||
```
|
||||
|
||||
Stop watching:
|
||||
|
||||
```
|
||||
/unwatch <chat_id/username>
|
||||
```
|
||||
|
||||
Filter types:
|
||||
|
||||
### msgre
|
||||
|
||||
Regex-match the message text. For example:
|
||||
|
||||
```
|
||||
/watch 12345678 msgre:.*hello.*
|
||||
```
|
||||
|
||||
This will watch the chat with ID `12345678`, and only save messages whose text contains `hello`.
|
||||
|
||||
## Direct Download Links
|
||||
|
||||
Use the `/dl` command to directly download one or more HTTP/HTTPS files to storage.
|
||||
|
||||
```bash
|
||||
/dl <url1> [url2] [url3] ...
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
/dl https://example.com/file.zip
|
||||
/dl https://example.com/file1.zip https://example.com/file2.zip
|
||||
```
|
||||
|
||||
The bot will validate the link format and then ask you to select the target storage location.
|
||||
|
||||
## Aria2 Download
|
||||
|
||||
{{< hint warning >}}
|
||||
This feature requires enabling Aria2 in the configuration file and configuring the RPC connection.
|
||||
{{< /hint >}}
|
||||
|
||||
Use the `/aria2dl` command to download files via the Aria2 download manager, supporting HTTP/HTTPS, FTP, BitTorrent, and other protocols.
|
||||
|
||||
```bash
|
||||
/aria2dl <uri1> [uri2] [uri3] ...
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Download HTTP link
|
||||
/aria2dl https://example.com/file.zip
|
||||
|
||||
# Download magnet link
|
||||
/aria2dl magnet:?xt=urn:btih:...
|
||||
|
||||
# Download torrent file (need to upload .torrent file first)
|
||||
/aria2dl https://example.com/file.torrent
|
||||
```
|
||||
|
||||
Configure Aria2:
|
||||
|
||||
Add to `config.toml`:
|
||||
|
||||
```toml
|
||||
[aria2]
|
||||
enable = true
|
||||
url = "http://localhost:6800/jsonrpc"
|
||||
secret = "your-rpc-secret" # If rpc-secret is configured
|
||||
remove_after_transfer = true # Remove local files after transfer
|
||||
```
|
||||
|
||||
## yt-dlp Video Download
|
||||
|
||||
{{< hint warning >}}
|
||||
This feature requires the yt-dlp command-line tool installed on your system.
|
||||
{{< /hint >}}
|
||||
|
||||
Use the `/ytdlp` command to download videos and audio from supported video websites, including YouTube, Bilibili, Twitter, and 1000+ other sites.
|
||||
|
||||
```bash
|
||||
/ytdlp <url1> [url2] [flags...]
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Basic download
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
# Download multiple videos
|
||||
/ytdlp https://www.youtube.com/watch?v=video1 https://www.youtube.com/watch?v=video2
|
||||
|
||||
# Use custom parameters
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ -f best
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ --extract-audio --audio-format mp3
|
||||
```
|
||||
|
||||
Common parameters:
|
||||
|
||||
- `-f <format>`: Specify download format (e.g., `best`, `worst`, `bestvideo+bestaudio`)
|
||||
- `--extract-audio`: Extract audio
|
||||
- `--audio-format <format>`: Audio format (e.g., `mp3`, `m4a`, `wav`)
|
||||
- `--write-sub`: Download subtitles
|
||||
- `--write-thumbnail`: Download thumbnail
|
||||
|
||||
For more parameters, see [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp#usage-and-options).
|
||||
|
||||
## Storage Transfer
|
||||
|
||||
Use the `/transfer` command to transfer files directly between different storages without going through Telegram.
|
||||
|
||||
```bash
|
||||
/transfer <source_storage>:/<source_path> [filter]
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `source_storage`: Source storage name
|
||||
- `source_path`: Source path
|
||||
- `filter`: Optional regex filter to transfer only matching files
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Transfer entire directory
|
||||
/transfer local1:/downloads
|
||||
|
||||
# Transfer files from specified path
|
||||
/transfer alist1:/media/photos
|
||||
|
||||
# Transfer only mp4 files
|
||||
/transfer webdav1:/videos ".*\.mp4$"
|
||||
|
||||
# Transfer image files
|
||||
/transfer local1:/pictures "(?i)\.(jpg|png|gif)$"
|
||||
```
|
||||
|
||||
The bot will:
|
||||
|
||||
1. List all files in the source path
|
||||
2. Apply the filter (if provided)
|
||||
3. Display file count and total size
|
||||
4. Ask you to select the target storage
|
||||
5. Ask you to select the target directory (if configured for that storage)
|
||||
6. Start the transfer task
|
||||
|
||||
Notes:
|
||||
|
||||
- Source storage must support listing and reading
|
||||
- Target storage must support writing
|
||||
- Real-time progress is displayed during transfer
|
||||
- Transfer tasks can be cancelled
|
||||
|
||||
## Save Files Outside Telegram
|
||||
|
||||
Besides files on Telegram, the bot can also save files from other websites via JavaScript plugins or built-in parsers.
|
||||
|
||||
> See the [Contributing Parsers](../contribute) document for details.
|
||||
|
||||
Just send links that match the requirements of a parser to the bot. Currently built-in parsers include:
|
||||
|
||||
- Twitter
|
||||
- Kemono
|
||||
3. Telegra.ph article links. The bot will download all images in the article.
|
||||
442
docs/content/en/usage/api.md
Normal file
442
docs/content/en/usage/api.md
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
title: "HTTP API"
|
||||
weight: 20
|
||||
---
|
||||
|
||||
# HTTP API
|
||||
|
||||
SaveAny-Bot provides an HTTP API that allows you to programmatically create download/transfer tasks, query task status, cancel tasks, and more — without going through Telegram.
|
||||
|
||||
## Enabling the API
|
||||
|
||||
Add or modify the following section in `config.toml`:
|
||||
|
||||
```toml
|
||||
[api]
|
||||
enable = true
|
||||
host = "0.0.0.0" # Bind address, default 0.0.0.0
|
||||
port = 8080 # Listen port, default 8080
|
||||
token = "your-token" # Auth token — strongly recommended
|
||||
```
|
||||
|
||||
You can also override these settings with environment variables (prefix `SAVEANY_`):
|
||||
|
||||
| Environment Variable | Config Key |
|
||||
|---|---|
|
||||
| `SAVEANY_API_ENABLE` | `api.enable` |
|
||||
| `SAVEANY_API_HOST` | `api.host` |
|
||||
| `SAVEANY_API_PORT` | `api.port` |
|
||||
| `SAVEANY_API_TOKEN` | `api.token` |
|
||||
|
||||
{{< hint warning >}}
|
||||
If `token` is empty, the API server will be accessible **without any authentication**, which is a security risk.
|
||||
{{< /hint >}}
|
||||
|
||||
## Authentication
|
||||
|
||||
When `token` is configured, all API requests must include a Bearer token in the HTTP header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-token>
|
||||
```
|
||||
|
||||
On authentication failure, the server returns `401`:
|
||||
|
||||
```json
|
||||
{ "error": "unauthorized", "message": "invalid token" }
|
||||
```
|
||||
|
||||
## Error Response Format
|
||||
|
||||
All errors use a consistent JSON format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "error_code",
|
||||
"message": "human readable description"
|
||||
}
|
||||
```
|
||||
|
||||
Common error codes:
|
||||
|
||||
| Error Code | HTTP Status | Meaning |
|
||||
|---|---|---|
|
||||
| `unauthorized` | 401 | Authentication failed |
|
||||
| `method_not_allowed` | 405 | Wrong HTTP method |
|
||||
| `invalid_request` | 400 | Malformed request body or parameters |
|
||||
| `task_creation_failed` | 400 | Failed to create task |
|
||||
| `task_not_found` | 404 | Task ID does not exist |
|
||||
| `cancel_failed` | 500 | Failed to cancel task |
|
||||
| `internal_error` | 500 | Internal server error |
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### GET /health — Health Check
|
||||
|
||||
No authentication required.
|
||||
|
||||
**Response `200 OK`:**
|
||||
|
||||
```json
|
||||
{ "status": "ok" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/storages — List Storages
|
||||
|
||||
Returns all currently loaded storage backends.
|
||||
|
||||
**Response `200 OK`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"storages": [
|
||||
{ "name": "local", "type": "local" },
|
||||
{ "name": "MyMinio", "type": "s3" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/task-types — List Supported Task Types
|
||||
|
||||
**Response `200 OK`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"types": [
|
||||
"directlinks",
|
||||
"ytdlp",
|
||||
"aria2",
|
||||
"parseditem",
|
||||
"tgfiles",
|
||||
"tphpics",
|
||||
"transfer"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /api/v1/tasks — Create Task
|
||||
|
||||
**Request headers:**
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<task_type>",
|
||||
"storage": "<storage_name>",
|
||||
"path": "<subpath>",
|
||||
"webhook": "<callback_url>",
|
||||
"params": { }
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `type` | string | Yes | Task type — see below |
|
||||
| `storage` | string | Yes | Target storage name, must match a name in your config |
|
||||
| `path` | string | No | Subdirectory path within the storage |
|
||||
| `webhook` | string | No | Callback URL invoked when the task reaches a terminal state |
|
||||
| `params` | object | Yes | Type-specific parameters — see below |
|
||||
|
||||
**Response `201 Created`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "abc123xyz",
|
||||
"type": "directlinks",
|
||||
"status": "queued",
|
||||
"created_at": "2026-03-11T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Task Types and params
|
||||
|
||||
##### directlinks — Direct URL Download
|
||||
|
||||
Download one or more files from direct HTTP/HTTPS URLs.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "directlinks",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"params": {
|
||||
"urls": [
|
||||
"https://example.com/file.zip",
|
||||
"https://example.com/other.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `urls` | []string | Yes | List of download URLs, at least 1 |
|
||||
|
||||
##### ytdlp — yt-dlp Media Download
|
||||
|
||||
{{< hint warning >}}
|
||||
Requires yt-dlp to be installed on the system.
|
||||
{{< /hint >}}
|
||||
|
||||
Download videos or audio via yt-dlp, supporting YouTube, Bilibili, and 1000+ other sites.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ytdlp",
|
||||
"storage": "local",
|
||||
"path": "videos",
|
||||
"params": {
|
||||
"urls": ["https://www.youtube.com/watch?v=xxx"],
|
||||
"flags": ["--extract-audio", "--audio-format", "mp3"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `urls` | []string | Yes | List of media URLs, at least 1 |
|
||||
| `flags` | []string | No | Extra yt-dlp command-line flags |
|
||||
|
||||
##### aria2 — Aria2 Download
|
||||
|
||||
{{< hint warning >}}
|
||||
Requires Aria2 to be enabled and configured (RPC) in the config file.
|
||||
{{< /hint >}}
|
||||
|
||||
Download files via the Aria2 download manager, supporting HTTP/HTTPS, FTP, BitTorrent (magnet links, torrent files), and more.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "aria2",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"params": {
|
||||
"urls": ["magnet:?xt=urn:btih:..."],
|
||||
"options": { "split": "4" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `urls` | []string | Yes | List of download URIs, at least 1 |
|
||||
| `options` | map[string]string | No | Aria2 download options |
|
||||
|
||||
##### parseditem — Parser Plugin Download
|
||||
|
||||
Hand a URL off to a registered JS plugin or built-in parser for processing and downloading.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "parseditem",
|
||||
"storage": "local",
|
||||
"path": "parsed",
|
||||
"params": {
|
||||
"url": "https://some-site.com/page"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `url` | string | Yes | The URL to parse |
|
||||
|
||||
Returns `400 task_creation_failed` if no parser is able to handle the URL.
|
||||
|
||||
##### tgfiles — Telegram Message File Download
|
||||
|
||||
Download files from Telegram messages via message links. Supported link formats:
|
||||
|
||||
- `https://t.me/username/123` — public channel or group
|
||||
- `https://t.me/c/123456789/123` — private channel by numeric ID
|
||||
- `https://t.me/c/123456789/111/456` — topic message (thread ID / message ID)
|
||||
- `https://t.me/username/111/456` — topic under a username-based chat
|
||||
|
||||
If the message is part of a media group (album), all files in the group are downloaded by default. Append `?single` to the link to force downloading only the single specified message.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tgfiles",
|
||||
"storage": "local",
|
||||
"path": "telegram",
|
||||
"params": {
|
||||
"message_links": [
|
||||
"https://t.me/username/123",
|
||||
"https://t.me/c/1234567890/456"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `message_links` | []string | Yes | List of Telegram message links, at least 1 |
|
||||
|
||||
##### tphpics — Telegraph Article Images
|
||||
|
||||
Download all images from a Telegra.ph article.
|
||||
|
||||
Supported URL prefixes: `https://telegra.ph/`, `http://telegra.ph/`, `https://telegraph.co/`, `http://telegraph.co/`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tphpics",
|
||||
"storage": "local",
|
||||
"path": "telegraph",
|
||||
"params": {
|
||||
"telegraph_url": "https://telegra.ph/Some-Article-01-01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `telegraph_url` | string | Yes | URL of the Telegra.ph article |
|
||||
|
||||
##### transfer — Storage-to-Storage Transfer
|
||||
|
||||
Transfer files directly between two storage backends without going through Telegram. The source storage must support both listing and reading.
|
||||
|
||||
{{< hint info >}}
|
||||
For `transfer` tasks, the top-level `storage` field is still required for validation, but the actual storages used are determined by `source_storage` and `target_storage` inside `params`.
|
||||
{{< /hint >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "transfer",
|
||||
"storage": "local",
|
||||
"params": {
|
||||
"source_storage": "MyS3",
|
||||
"source_path": "backups/",
|
||||
"target_storage": "LocalDisk",
|
||||
"target_path": "restored/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `source_storage` | string | Yes | Source storage name |
|
||||
| `source_path` | string | Yes | Path within the source storage; must contain at least one file |
|
||||
| `target_storage` | string | Yes | Target storage name |
|
||||
| `target_path` | string | Yes | Destination path within the target storage |
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/tasks — List All Tasks
|
||||
|
||||
Returns all tasks created via the API. Task records are stored in memory only and are cleared on restart.
|
||||
|
||||
**Response `200 OK`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"task_id": "abc123xyz",
|
||||
"type": "directlinks",
|
||||
"status": "running",
|
||||
"title": "file.zip",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"error": "",
|
||||
"created_at": "2026-03-11T10:00:00Z",
|
||||
"updated_at": "2026-03-11T10:00:05Z",
|
||||
"progress": {
|
||||
"total_bytes": 10485760,
|
||||
"downloaded_bytes": 5242880,
|
||||
"percent": 50.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
The `progress` field is only included when `total_bytes > 0`. The `error` field is only included when non-empty.
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/tasks/{task_id} — Get Task
|
||||
|
||||
**Path parameter:** `task_id` — the ID returned when the task was created.
|
||||
|
||||
**Response `200 OK`:** Same structure as a single task object from the list above.
|
||||
|
||||
**Error responses:**
|
||||
- `400 invalid_request` — no task ID in path
|
||||
- `404 task_not_found` — task does not exist
|
||||
|
||||
---
|
||||
|
||||
### DELETE /api/v1/tasks/{task_id} — Cancel Task
|
||||
|
||||
**Path parameter:** `task_id`
|
||||
|
||||
**Response `200 OK`:**
|
||||
|
||||
```json
|
||||
{ "message": "task cancelled successfully" }
|
||||
```
|
||||
|
||||
**Error responses:**
|
||||
- `400 invalid_request` — no task ID in path
|
||||
- `404 task_not_found` — task does not exist
|
||||
- `500 cancel_failed` — cancellation failed
|
||||
|
||||
---
|
||||
|
||||
## Task Statuses
|
||||
|
||||
| Status | Meaning |
|
||||
|---|---|
|
||||
| `queued` | Task is queued and waiting to run |
|
||||
| `running` | Task is currently executing |
|
||||
| `completed` | Task finished successfully |
|
||||
| `failed` | Task encountered an error |
|
||||
| `cancelled` | Task was cancelled via the DELETE endpoint |
|
||||
|
||||
---
|
||||
|
||||
## Webhook Callbacks
|
||||
|
||||
When a `webhook` URL is provided in the create request, SaveAny-Bot sends a `POST` request to that URL when the task reaches a terminal state (`completed`, `failed`, or `cancelled`).
|
||||
|
||||
**Callback request headers:**
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
User-Agent: SaveAny-Bot/1.0
|
||||
```
|
||||
|
||||
**Callback request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "abc123xyz",
|
||||
"type": "directlinks",
|
||||
"status": "completed",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"completed_at": "2026-03-11T10:01:00Z",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
`completed_at` is only present when status is `completed` or `failed`. `error` is only present when non-empty.
|
||||
|
||||
**Retry policy:** Up to 3 attempts, with delays of 1s, 2s, and 3s between retries. Each request has a 30-second timeout.
|
||||
41
docs/content/en/usage/aria2.md
Normal file
41
docs/content/en/usage/aria2.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "Aria2 Download"
|
||||
weight: 6
|
||||
---
|
||||
|
||||
# Aria2 Download
|
||||
|
||||
{{< hint warning >}}
|
||||
This feature requires enabling Aria2 in the configuration file and configuring the RPC connection.
|
||||
{{< /hint >}}
|
||||
|
||||
Use the `/aria2dl` command to download files via the Aria2 download manager, supporting HTTP/HTTPS, FTP, BitTorrent, and other protocols.
|
||||
|
||||
```bash
|
||||
/aria2dl <uri1> [uri2] [uri3] ...
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Download HTTP link
|
||||
/aria2dl https://example.com/file.zip
|
||||
|
||||
# Download magnet link
|
||||
/aria2dl magnet:?xt=urn:btih:...
|
||||
|
||||
# Download torrent file (need to upload .torrent file first)
|
||||
/aria2dl https://example.com/file.torrent
|
||||
```
|
||||
|
||||
Configure Aria2:
|
||||
|
||||
Add to `config.toml`:
|
||||
|
||||
```toml
|
||||
[aria2]
|
||||
enable = true
|
||||
url = "http://localhost:6800/jsonrpc"
|
||||
secret = "your-rpc-secret" # If rpc-secret is configured
|
||||
remove_after_transfer = true # Remove local files after transfer
|
||||
```
|
||||
21
docs/content/en/usage/directlinks.md
Normal file
21
docs/content/en/usage/directlinks.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "Direct Download Links"
|
||||
weight: 5
|
||||
---
|
||||
|
||||
# Direct Download Links
|
||||
|
||||
Use the `/dl` command to directly download one or more HTTP/HTTPS files to storage.
|
||||
|
||||
```bash
|
||||
/dl <url1> [url2] [url3] ...
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
/dl https://example.com/file.zip
|
||||
/dl https://example.com/file1.zip https://example.com/file2.zip
|
||||
```
|
||||
|
||||
The bot will validate the link format and then ask you to select the target storage location.
|
||||
15
docs/content/en/usage/parsers.md
Normal file
15
docs/content/en/usage/parsers.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "Save Files Outside Telegram"
|
||||
weight: 9
|
||||
---
|
||||
|
||||
# Save Files Outside Telegram
|
||||
|
||||
Besides files on Telegram, the bot can also save files from other websites via JavaScript plugins or built-in parsers.
|
||||
|
||||
> See the [Contributing Parsers](../contribute) document for details.
|
||||
|
||||
Just send links that match the requirements of a parser to the bot. Currently built-in parsers include:
|
||||
|
||||
- Twitter
|
||||
- Kemono
|
||||
58
docs/content/en/usage/rules.md
Normal file
58
docs/content/en/usage/rules.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "Storage Rules"
|
||||
weight: 3
|
||||
---
|
||||
|
||||
# Storage Rules
|
||||
|
||||
Storage rules allow you to define redirection rules when the bot uploads files to storage, so that saved files are automatically organized.
|
||||
|
||||
See: <a href="https://github.com/krau/SaveAny-Bot/issues/28" target="_blank">#28</a>
|
||||
|
||||
Currently supported rule types:
|
||||
|
||||
1. FILENAME-REGEX
|
||||
2. MESSAGE-REGEX
|
||||
3. IS-ALBUM
|
||||
|
||||
Basic syntax for adding rules:
|
||||
|
||||
"RuleType RuleContent StorageName Path"
|
||||
|
||||
Pay attention to spaces; the bot can only parse correctly formatted syntax. Below is an example of a valid rule command:
|
||||
|
||||
```
|
||||
/rule add FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /videos
|
||||
```
|
||||
|
||||
In addition, if `CHOSEN` is used as the storage name in the rule, it means files will be stored under the path of the storage you selected by clicking the inline button.
|
||||
|
||||
Rule types:
|
||||
|
||||
## FILENAME-REGEX
|
||||
|
||||
Matches based on filename regex. The rule content must be a valid regular expression, such as:
|
||||
|
||||
```
|
||||
FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /videos
|
||||
```
|
||||
|
||||
This means files with extensions mp4, mkv, ts, avi, flv will be saved to the `/videos` directory in the storage named `MyAlist` (also affected by the `base_path` in the configuration file).
|
||||
|
||||
## MESSAGE-REGEX
|
||||
|
||||
Similar to the above, but matches based on the text content of the message itself.
|
||||
|
||||
## IS-ALBUM
|
||||
|
||||
Matches album messages (media groups). Rule content can only be `true` or `false`.
|
||||
|
||||
If the path in the rule uses `NEW-FOR-ALBUM`, the bot will create a new folder for each media group and store all files of that group there. See: https://github.com/krau/SaveAny-Bot/issues/87
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
IS-ALBUM true MyWebdav NEW-FOR-ALBUM
|
||||
```
|
||||
|
||||
This will save media-group messages to the storage named `MyWebdav`, creating a new folder (generated from the first file) for each album.
|
||||
14
docs/content/en/usage/silent.md
Normal file
14
docs/content/en/usage/silent.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "Silent Mode"
|
||||
weight: 2
|
||||
---
|
||||
|
||||
# Silent Mode (silent)
|
||||
|
||||
Use the `/silent` command to toggle silent mode.
|
||||
|
||||
By default, silent mode is off, and the bot will ask you for the save location of each file.
|
||||
|
||||
When silent mode is enabled, the bot will save files directly to the default location without confirmation.
|
||||
|
||||
Before enabling silent mode, you need to set the default save location using the `/storage` command.
|
||||
50
docs/content/en/usage/transfer.md
Normal file
50
docs/content/en/usage/transfer.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "Storage Transfer"
|
||||
weight: 8
|
||||
---
|
||||
|
||||
# Storage Transfer
|
||||
|
||||
Use the `/transfer` command to transfer files directly between different storages without going through Telegram.
|
||||
|
||||
```bash
|
||||
/transfer <source_storage>:/<source_path> [filter]
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `source_storage`: Source storage name
|
||||
- `source_path`: Source path
|
||||
- `filter`: Optional regex filter to transfer only matching files
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Transfer entire directory
|
||||
/transfer local1:/downloads
|
||||
|
||||
# Transfer files from specified path
|
||||
/transfer alist1:/media/photos
|
||||
|
||||
# Transfer only mp4 files
|
||||
/transfer webdav1:/videos ".*\.mp4$"
|
||||
|
||||
# Transfer image files
|
||||
/transfer local1:/pictures "(?i)\.(jpg|png|gif)$"
|
||||
```
|
||||
|
||||
The bot will:
|
||||
|
||||
1. List all files in the source path
|
||||
2. Apply the filter (if provided)
|
||||
3. Display file count and total size
|
||||
4. Ask you to select the target storage
|
||||
5. Ask you to select the target directory (if configured for that storage)
|
||||
6. Start the transfer task
|
||||
|
||||
Notes:
|
||||
|
||||
- Source storage must support listing and reading
|
||||
- Target storage must support writing
|
||||
- Real-time progress is displayed during transfer
|
||||
- Transfer tasks can be cancelled
|
||||
36
docs/content/en/usage/watch.md
Normal file
36
docs/content/en/usage/watch.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "Watch Chats"
|
||||
weight: 4
|
||||
---
|
||||
|
||||
# Watch Chats
|
||||
|
||||
{{< hint warning >}}
|
||||
This feature requires enabling UserBot integration.
|
||||
{{< /hint >}}
|
||||
|
||||
You can watch messages in a specific chat and automatically save them to the default storage, following storage rules. You can also add filters so that only matching messages are saved.
|
||||
|
||||
Watch a chat:
|
||||
|
||||
```
|
||||
/watch <chat_id/username> [filter]
|
||||
```
|
||||
|
||||
Stop watching:
|
||||
|
||||
```
|
||||
/unwatch <chat_id/username>
|
||||
```
|
||||
|
||||
Filter types:
|
||||
|
||||
## msgre
|
||||
|
||||
Regex-match the message text. For example:
|
||||
|
||||
```
|
||||
/watch 12345678 msgre:.*hello.*
|
||||
```
|
||||
|
||||
This will watch the chat with ID `12345678`, and only save messages whose text contains `hello`.
|
||||
40
docs/content/en/usage/ytdlp.md
Normal file
40
docs/content/en/usage/ytdlp.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "yt-dlp Video Download"
|
||||
weight: 7
|
||||
---
|
||||
|
||||
# yt-dlp Video Download
|
||||
|
||||
{{< hint warning >}}
|
||||
This feature requires the yt-dlp command-line tool installed on your system.
|
||||
{{< /hint >}}
|
||||
|
||||
Use the `/ytdlp` command to download videos and audio from supported video websites, including YouTube, Bilibili, Twitter, and 1000+ other sites.
|
||||
|
||||
```bash
|
||||
/ytdlp <url1> [url2] [flags...]
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Basic download
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
# Download multiple videos
|
||||
/ytdlp https://www.youtube.com/watch?v=video1 https://www.youtube.com/watch?v=video2
|
||||
|
||||
# Use custom parameters
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ -f best
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ --extract-audio --audio-format mp3
|
||||
```
|
||||
|
||||
Common parameters:
|
||||
|
||||
- `-f <format>`: Specify download format (e.g., `best`, `worst`, `bestvideo+bestaudio`)
|
||||
- `--extract-audio`: Extract audio
|
||||
- `--audio-format <format>`: Audio format (e.g., `mp3`, `m4a`, `wav`)
|
||||
- `--write-sub`: Download subtitles
|
||||
- `--write-thumbnail`: Download thumbnail
|
||||
|
||||
For more parameters, see [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp#usage-and-options).
|
||||
@@ -13,248 +13,4 @@ weight: 10
|
||||
|
||||
1. 文件或媒体消息, 如图片, 视频, 文档等
|
||||
2. Telegram 消息链接, 例如: `https://t.me/acherkrau/1097`. **即使频道禁止了转发和保存, Bot 依然可以下载其文件.**
|
||||
3. Telegra.ph 的文章链接, Bot 将下载其中的所有图片
|
||||
|
||||
## 静默模式 (silent)
|
||||
|
||||
使用 `/silent` 命令可以开关静默模式.
|
||||
|
||||
默认情况下不开启静默模式, Bot 会询问你每个文件的保存位置.
|
||||
|
||||
开启静默模式后, Bot 会直接保存文件到默认位置, 无需确认.
|
||||
|
||||
在开启静默模式之前, 需要使用 `/storage` 命令设置默认保存位置.
|
||||
|
||||
## 存储规则
|
||||
|
||||
允许你为 Bot 在上传文件到存储时设置一些重定向规则, 用于自动整理所保存的文件.
|
||||
|
||||
见: <a href="https://github.com/krau/SaveAny-Bot/issues/28" target="_blank">#28</a>
|
||||
|
||||
目前支持的规则类型:
|
||||
|
||||
1. FILENAME-REGEX
|
||||
2. MESSAGE-REGEX
|
||||
3. IS-ALBUM
|
||||
|
||||
添加规则的基本语法:
|
||||
|
||||
"规则类型 规则内容 存储名 路径"
|
||||
|
||||
注意空格的使用, 语法正确 bot 才能解析, 以下是一条合法的添加规则命令:
|
||||
|
||||
```
|
||||
/rule add FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
||||
```
|
||||
|
||||
此外, 规则中的存储名若使用 "CHOSEN" , 则表示存储到点击按钮选择的存储端的路径下
|
||||
|
||||
规则类型:
|
||||
|
||||
### FILENAME-REGEX
|
||||
|
||||
根据文件名正则匹配, 规则内容要求为一个合法的正则表达式, 如
|
||||
|
||||
```
|
||||
FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
||||
```
|
||||
|
||||
表示将文件名后缀为 mp4,mkv,ts,avi,flv 的文件放到名为 MyAlist 存储下的 /视频 目录内 (同时受配置文件中的 `base_path` 影响)
|
||||
|
||||
### MESSAGE-REGEX
|
||||
|
||||
同上, 但是是根据消息本身的文本内容正则匹配
|
||||
|
||||
### IS-ALBUM
|
||||
|
||||
匹配相册消息 (media group), 规则内容只能为 `true` 或 `false`.
|
||||
|
||||
规则中的路径若使用 "NEW-FOR-ALBUM" , 则表示为该组消息新建一个文件夹来存储它们. 见: https://github.com/krau/SaveAny-Bot/issues/87
|
||||
|
||||
例如:
|
||||
|
||||
```
|
||||
IS-ALBUM true MyWebdav NEW-FOR-ALBUM
|
||||
```
|
||||
|
||||
这将会把以 media group 形式发送的消息保存到名为 MyWebdav 的存储下, 并为每个相册新建一个文件夹(由第一个文件生成)来存储它们.
|
||||
|
||||
|
||||
## 监听聊天
|
||||
|
||||
{{< hint warning >}}
|
||||
该功能需开启 UserBot 集成.
|
||||
{{< /hint >}}
|
||||
|
||||
监听指定聊天的消息, 并自动保存到默认存储中, 遵从存储规则, 并且可以设置过滤器来只保存匹配的消息.
|
||||
|
||||
监听聊天:
|
||||
|
||||
```
|
||||
/watch <chat_id/username> [filter]
|
||||
```
|
||||
|
||||
取消监听:
|
||||
|
||||
```
|
||||
/unwatch <chat_id/username>
|
||||
```
|
||||
|
||||
过滤器类型:
|
||||
|
||||
### msgre
|
||||
|
||||
正则匹配消息文本, 例如:
|
||||
|
||||
```
|
||||
/watch 12345678 msgre:.*hello.*
|
||||
```
|
||||
|
||||
这将会监听 ID 为 12345678 的聊天, 并且只保存消息文本中包含 "hello" 的消息.
|
||||
|
||||
## 直接下载链接
|
||||
|
||||
使用 `/dl` 命令可以直接下载一个或多个 HTTP/HTTPS 链接的文件到存储中.
|
||||
|
||||
```bash
|
||||
/dl <url1> [url2] [url3] ...
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
/dl https://example.com/file.zip
|
||||
/dl https://example.com/file1.zip https://example.com/file2.zip
|
||||
```
|
||||
|
||||
Bot 会验证链接格式, 然后让你选择目标存储位置.
|
||||
|
||||
## Aria2 下载
|
||||
|
||||
{{< hint warning >}}
|
||||
该功能需要在配置文件中启用 Aria2 并配置 RPC 连接.
|
||||
{{< /hint >}}
|
||||
|
||||
使用 `/aria2dl` 命令可以通过 Aria2 下载管理器下载文件, 支持 HTTP/HTTPS、FTP、BitTorrent 等多种协议.
|
||||
|
||||
```bash
|
||||
/aria2dl <uri1> [uri2] [uri3] ...
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 下载 HTTP 链接
|
||||
/aria2dl https://example.com/file.zip
|
||||
|
||||
# 下载磁力链接
|
||||
/aria2dl magnet:?xt=urn:btih:...
|
||||
|
||||
# 下载种子文件 (需要先上传 .torrent 文件)
|
||||
/aria2dl https://example.com/file.torrent
|
||||
```
|
||||
|
||||
配置 Aria2:
|
||||
|
||||
在 `config.toml` 中添加:
|
||||
|
||||
```toml
|
||||
[aria2]
|
||||
enable = true
|
||||
url = "http://localhost:6800/jsonrpc"
|
||||
secret = "your-rpc-secret" # 如果配置了 rpc-secret
|
||||
remove_after_transfer = true # 转存完成后删除本地文件
|
||||
```
|
||||
|
||||
## yt-dlp 视频下载
|
||||
|
||||
{{< hint warning >}}
|
||||
该功能需要在系统中安装 yt-dlp 命令行工具.
|
||||
{{< /hint >}}
|
||||
|
||||
使用 `/ytdlp` 命令可以下载支持的视频网站的视频和音频, 支持 YouTube、Bilibili、Twitter 等 1000+ 个网站.
|
||||
|
||||
```bash
|
||||
/ytdlp <url1> [url2] [flags...]
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 基本下载
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
# 下载多个视频
|
||||
/ytdlp https://www.youtube.com/watch?v=video1 https://www.youtube.com/watch?v=video2
|
||||
|
||||
# 使用自定义参数
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ -f best
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ --extract-audio --audio-format mp3
|
||||
```
|
||||
|
||||
常用参数:
|
||||
|
||||
- `-f <format>`: 指定下载格式 (如 `best`, `worst`, `bestvideo+bestaudio`)
|
||||
- `--extract-audio`: 提取音频
|
||||
- `--audio-format <format>`: 音频格式 (如 `mp3`, `m4a`, `wav`)
|
||||
- `--write-sub`: 下载字幕
|
||||
- `--write-thumbnail`: 下载缩略图
|
||||
|
||||
更多参数请参考 [yt-dlp 文档](https://github.com/yt-dlp/yt-dlp#usage-and-options).
|
||||
|
||||
## 存储间传输
|
||||
|
||||
使用 `/transfer` 命令可以在不同存储之间直接传输文件, 无需经过 Telegram.
|
||||
|
||||
```bash
|
||||
/transfer <source_storage>:/<source_path> [filter]
|
||||
```
|
||||
|
||||
参数说明:
|
||||
|
||||
- `source_storage`: 源存储名称
|
||||
- `source_path`: 源路径
|
||||
- `filter`: 可选的正则表达式过滤器, 只传输匹配的文件
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 传输整个目录
|
||||
/transfer local1:/downloads
|
||||
|
||||
# 传输指定路径的文件
|
||||
/transfer alist1:/media/photos
|
||||
|
||||
# 只传输 mp4 文件
|
||||
/transfer webdav1:/videos ".*\.mp4$"
|
||||
|
||||
# 传输图片文件
|
||||
/transfer local1:/pictures "(?i)\.(jpg|png|gif)$"
|
||||
```
|
||||
|
||||
Bot 会:
|
||||
|
||||
1. 列出源路径下的所有文件
|
||||
2. 应用过滤器 (如果提供)
|
||||
3. 显示文件数量和总大小
|
||||
4. 让你选择目标存储
|
||||
5. 让你选择目标目录 (如果该存储配置了目录)
|
||||
6. 开始传输任务
|
||||
|
||||
注意:
|
||||
|
||||
- 源存储必须支持列举和读取功能
|
||||
- 目标存储必须支持写入功能
|
||||
- 传输过程显示实时进度
|
||||
- 支持取消正在进行的传输任务
|
||||
|
||||
## 转存 Telegram 之外的文件
|
||||
|
||||
除了 Telegram 上的文件, Bot 还可通过 JavaScript 插件或内置解析器来支持转存其他网站的文件.
|
||||
|
||||
> 查看[贡献解析器](../contribute)文档了解详情
|
||||
|
||||
只需向 Bot 发送符合解析器要求的链接即可使用, 当前内置的解析器:
|
||||
|
||||
- Twitter
|
||||
- Kemono
|
||||
3. Telegra.ph 的文章链接, Bot 将下载其中的所有图片
|
||||
442
docs/content/zh/usage/api.md
Normal file
442
docs/content/zh/usage/api.md
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
title: "HTTP API"
|
||||
weight: 20
|
||||
---
|
||||
|
||||
# HTTP API
|
||||
|
||||
SaveAny-Bot 提供了一套 HTTP API,允许你通过程序化方式创建下载/转存任务、查询任务状态、取消任务等,无需通过 Telegram 操作。
|
||||
|
||||
## 启用 API
|
||||
|
||||
在 `config.toml` 中添加或修改以下配置:
|
||||
|
||||
```toml
|
||||
[api]
|
||||
enable = true
|
||||
host = "0.0.0.0" # 监听地址,默认 0.0.0.0
|
||||
port = 8080 # 监听端口,默认 8080
|
||||
token = "your-token" # 鉴权 Token,强烈建议设置
|
||||
```
|
||||
|
||||
也可通过环境变量覆盖(前缀 `SAVEANY_`):
|
||||
|
||||
| 环境变量 | 对应配置项 |
|
||||
|---|---|
|
||||
| `SAVEANY_API_ENABLE` | `api.enable` |
|
||||
| `SAVEANY_API_HOST` | `api.host` |
|
||||
| `SAVEANY_API_PORT` | `api.port` |
|
||||
| `SAVEANY_API_TOKEN` | `api.token` |
|
||||
|
||||
{{< hint warning >}}
|
||||
若 `token` 为空,API 服务将**不进行任何鉴权**即可访问,存在安全风险。
|
||||
{{< /hint >}}
|
||||
|
||||
## 鉴权
|
||||
|
||||
当配置了 `token` 时,所有 API 请求均需在 HTTP 请求头中携带 Bearer Token:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-token>
|
||||
```
|
||||
|
||||
鉴权失败时返回 `401`:
|
||||
|
||||
```json
|
||||
{ "error": "unauthorized", "message": "invalid token" }
|
||||
```
|
||||
|
||||
## 错误响应格式
|
||||
|
||||
所有错误均使用统一的 JSON 格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "error_code",
|
||||
"message": "错误说明"
|
||||
}
|
||||
```
|
||||
|
||||
常见错误码:
|
||||
|
||||
| 错误码 | HTTP 状态 | 含义 |
|
||||
|---|---|---|
|
||||
| `unauthorized` | 401 | 鉴权失败 |
|
||||
| `method_not_allowed` | 405 | HTTP 方法不正确 |
|
||||
| `invalid_request` | 400 | 请求体/参数非法 |
|
||||
| `task_creation_failed` | 400 | 任务创建失败 |
|
||||
| `task_not_found` | 404 | 任务 ID 不存在 |
|
||||
| `cancel_failed` | 500 | 取消任务失败 |
|
||||
| `internal_error` | 500 | 服务器内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 接口列表
|
||||
|
||||
### GET /health — 健康检查
|
||||
|
||||
无需鉴权。
|
||||
|
||||
**响应 `200 OK`:**
|
||||
|
||||
```json
|
||||
{ "status": "ok" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/storages — 列出存储
|
||||
|
||||
返回当前所有已加载的存储后端。
|
||||
|
||||
**响应 `200 OK`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"storages": [
|
||||
{ "name": "local", "type": "local" },
|
||||
{ "name": "MyMinio", "type": "s3" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/task-types — 列出支持的任务类型
|
||||
|
||||
**响应 `200 OK`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"types": [
|
||||
"directlinks",
|
||||
"ytdlp",
|
||||
"aria2",
|
||||
"parseditem",
|
||||
"tgfiles",
|
||||
"tphpics",
|
||||
"transfer"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /api/v1/tasks — 创建任务
|
||||
|
||||
**请求头:**
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<任务类型>",
|
||||
"storage": "<存储名>",
|
||||
"path": "<子目录>",
|
||||
"webhook": "<回调URL>",
|
||||
"params": { }
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `type` | string | 是 | 任务类型,见下文 |
|
||||
| `storage` | string | 是 | 目标存储名,须与配置中的存储名一致 |
|
||||
| `path` | string | 否 | 存储内的子目录路径 |
|
||||
| `webhook` | string | 否 | 任务完成/失败时的回调地址 |
|
||||
| `params` | object | 是 | 各任务类型的专属参数,见下文 |
|
||||
|
||||
**响应 `201 Created`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "abc123xyz",
|
||||
"type": "directlinks",
|
||||
"status": "queued",
|
||||
"created_at": "2026-03-11T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 任务类型与 params
|
||||
|
||||
##### directlinks — 直接下载链接
|
||||
|
||||
下载一个或多个 HTTP/HTTPS 直链文件。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "directlinks",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"params": {
|
||||
"urls": [
|
||||
"https://example.com/file.zip",
|
||||
"https://example.com/other.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `urls` | []string | 是 | 下载地址列表,至少 1 条 |
|
||||
|
||||
##### ytdlp — yt-dlp 视频下载
|
||||
|
||||
{{< hint warning >}}
|
||||
需要在系统中安装 yt-dlp。
|
||||
{{< /hint >}}
|
||||
|
||||
通过 yt-dlp 下载视频/音频,支持 YouTube、Bilibili 等 1000+ 网站。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ytdlp",
|
||||
"storage": "local",
|
||||
"path": "videos",
|
||||
"params": {
|
||||
"urls": ["https://www.youtube.com/watch?v=xxx"],
|
||||
"flags": ["--extract-audio", "--audio-format", "mp3"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `urls` | []string | 是 | 媒体链接列表,至少 1 条 |
|
||||
| `flags` | []string | 否 | 额外的 yt-dlp 命令行参数 |
|
||||
|
||||
##### aria2 — Aria2 下载
|
||||
|
||||
{{< hint warning >}}
|
||||
需要在配置文件中启用并配置 Aria2 RPC。
|
||||
{{< /hint >}}
|
||||
|
||||
通过 Aria2 下载管理器下载文件,支持 HTTP/HTTPS、FTP、BitTorrent(磁力链接、种子)等协议。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "aria2",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"params": {
|
||||
"urls": ["magnet:?xt=urn:btih:..."],
|
||||
"options": { "split": "4" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `urls` | []string | 是 | 下载地址列表,至少 1 条 |
|
||||
| `options` | map[string]string | 否 | Aria2 下载选项 |
|
||||
|
||||
##### parseditem — 解析器下载
|
||||
|
||||
将 URL 交由已注册的 JS 插件或内置解析器处理后下载。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "parseditem",
|
||||
"storage": "local",
|
||||
"path": "parsed",
|
||||
"params": {
|
||||
"url": "https://some-site.com/page"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `url` | string | 是 | 待解析的页面 URL |
|
||||
|
||||
若没有任何解析器能处理该 URL,则返回 `400 task_creation_failed`。
|
||||
|
||||
##### tgfiles — Telegram 消息文件下载
|
||||
|
||||
通过 Telegram 消息链接下载文件。支持以下链接格式:
|
||||
|
||||
- `https://t.me/username/123` — 公开频道/群组
|
||||
- `https://t.me/c/123456789/123` — 私有频道(数字 ID)
|
||||
- `https://t.me/c/123456789/111/456` — 话题消息
|
||||
- `https://t.me/username/111/456` — 用户名频道下的话题消息
|
||||
|
||||
若消息属于媒体组(相册),默认下载整组文件。在链接末尾追加 `?single` 可强制只下载单条消息的文件。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tgfiles",
|
||||
"storage": "local",
|
||||
"path": "telegram",
|
||||
"params": {
|
||||
"message_links": [
|
||||
"https://t.me/username/123",
|
||||
"https://t.me/c/1234567890/456"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `message_links` | []string | 是 | Telegram 消息链接列表,至少 1 条 |
|
||||
|
||||
##### tphpics — Telegraph 文章图片下载
|
||||
|
||||
下载 Telegra.ph 文章中的所有图片。
|
||||
|
||||
支持的链接前缀:`https://telegra.ph/`、`http://telegra.ph/`、`https://telegraph.co/`、`http://telegraph.co/`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tphpics",
|
||||
"storage": "local",
|
||||
"path": "telegraph",
|
||||
"params": {
|
||||
"telegraph_url": "https://telegra.ph/Some-Article-01-01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `telegraph_url` | string | 是 | Telegra.ph 文章 URL |
|
||||
|
||||
##### transfer — 存储间文件传输
|
||||
|
||||
在两个存储后端之间直接传输文件,无需经过 Telegram。源存储须支持列举(list)和读取(read)操作。
|
||||
|
||||
{{< hint info >}}
|
||||
`transfer` 任务中,顶层的 `storage` 字段仍然必须填写(用于通过参数校验),但实际使用的存储由 `params` 中的 `source_storage` 和 `target_storage` 决定。
|
||||
{{< /hint >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "transfer",
|
||||
"storage": "local",
|
||||
"params": {
|
||||
"source_storage": "MyS3",
|
||||
"source_path": "backups/",
|
||||
"target_storage": "LocalDisk",
|
||||
"target_path": "restored/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| params 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `source_storage` | string | 是 | 源存储名 |
|
||||
| `source_path` | string | 是 | 源存储中的路径,须包含至少一个文件 |
|
||||
| `target_storage` | string | 是 | 目标存储名 |
|
||||
| `target_path` | string | 是 | 目标存储中的路径 |
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/tasks — 列出所有任务
|
||||
|
||||
返回所有 API 创建的任务(仅在内存中保留,重启后清空)。
|
||||
|
||||
**响应 `200 OK`:**
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"task_id": "abc123xyz",
|
||||
"type": "directlinks",
|
||||
"status": "running",
|
||||
"title": "file.zip",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"error": "",
|
||||
"created_at": "2026-03-11T10:00:00Z",
|
||||
"updated_at": "2026-03-11T10:00:05Z",
|
||||
"progress": {
|
||||
"total_bytes": 10485760,
|
||||
"downloaded_bytes": 5242880,
|
||||
"percent": 50.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
`progress` 字段仅在 `total_bytes > 0` 时出现。`error` 字段仅在有错误时出现。
|
||||
|
||||
---
|
||||
|
||||
### GET /api/v1/tasks/{task_id} — 查询任务
|
||||
|
||||
**路径参数:** `task_id` — 创建任务时返回的 ID。
|
||||
|
||||
**响应 `200 OK`:** 同上列表中的单个任务对象。
|
||||
|
||||
**错误响应:**
|
||||
- `400 invalid_request` — 路径中未提供 task_id
|
||||
- `404 task_not_found` — 任务不存在
|
||||
|
||||
---
|
||||
|
||||
### DELETE /api/v1/tasks/{task_id} — 取消任务
|
||||
|
||||
**路径参数:** `task_id`
|
||||
|
||||
**响应 `200 OK`:**
|
||||
|
||||
```json
|
||||
{ "message": "task cancelled successfully" }
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
- `400 invalid_request` — 路径中未提供 task_id
|
||||
- `404 task_not_found` — 任务不存在
|
||||
- `500 cancel_failed` — 取消操作失败
|
||||
|
||||
---
|
||||
|
||||
## 任务状态
|
||||
|
||||
| 状态值 | 含义 |
|
||||
|---|---|
|
||||
| `queued` | 已入队,等待执行 |
|
||||
| `running` | 正在执行 |
|
||||
| `completed` | 已成功完成 |
|
||||
| `failed` | 执行失败 |
|
||||
| `cancelled` | 已通过 DELETE 接口取消 |
|
||||
|
||||
---
|
||||
|
||||
## Webhook 回调
|
||||
|
||||
创建任务时可设置 `webhook` 字段。当任务进入终态(`completed`、`failed`、`cancelled`)时,Bot 会向该地址发送一个 `POST` 请求。
|
||||
|
||||
**回调请求头:**
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
User-Agent: SaveAny-Bot/1.0
|
||||
```
|
||||
|
||||
**回调请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "abc123xyz",
|
||||
"type": "directlinks",
|
||||
"status": "completed",
|
||||
"storage": "local",
|
||||
"path": "downloads",
|
||||
"completed_at": "2026-03-11T10:01:00Z",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
`completed_at` 仅在状态为 `completed` 或 `failed` 时出现。`error` 仅在有错误时出现。
|
||||
|
||||
**重试机制:** 最多重试 3 次,重试间隔依次为 1 秒、2 秒、3 秒。每次请求超时为 30 秒。
|
||||
41
docs/content/zh/usage/aria2.md
Normal file
41
docs/content/zh/usage/aria2.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "Aria2 下载"
|
||||
weight: 6
|
||||
---
|
||||
|
||||
# Aria2 下载
|
||||
|
||||
{{< hint warning >}}
|
||||
该功能需要在配置文件中启用 Aria2 并配置 RPC 连接.
|
||||
{{< /hint >}}
|
||||
|
||||
使用 `/aria2dl` 命令可以通过 Aria2 下载管理器下载文件, 支持 HTTP/HTTPS、FTP、BitTorrent 等多种协议.
|
||||
|
||||
```bash
|
||||
/aria2dl <uri1> [uri2] [uri3] ...
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 下载 HTTP 链接
|
||||
/aria2dl https://example.com/file.zip
|
||||
|
||||
# 下载磁力链接
|
||||
/aria2dl magnet:?xt=urn:btih:...
|
||||
|
||||
# 下载种子文件 (需要先上传 .torrent 文件)
|
||||
/aria2dl https://example.com/file.torrent
|
||||
```
|
||||
|
||||
配置 Aria2:
|
||||
|
||||
在 `config.toml` 中添加:
|
||||
|
||||
```toml
|
||||
[aria2]
|
||||
enable = true
|
||||
url = "http://localhost:6800/jsonrpc"
|
||||
secret = "your-rpc-secret" # 如果配置了 rpc-secret
|
||||
remove_after_transfer = true # 转存完成后删除本地文件
|
||||
```
|
||||
21
docs/content/zh/usage/directlinks.md
Normal file
21
docs/content/zh/usage/directlinks.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "直接下载链接"
|
||||
weight: 5
|
||||
---
|
||||
|
||||
# 直接下载链接
|
||||
|
||||
使用 `/dl` 命令可以直接下载一个或多个 HTTP/HTTPS 链接的文件到存储中.
|
||||
|
||||
```bash
|
||||
/dl <url1> [url2] [url3] ...
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
/dl https://example.com/file.zip
|
||||
/dl https://example.com/file1.zip https://example.com/file2.zip
|
||||
```
|
||||
|
||||
Bot 会验证链接格式, 然后让你选择目标存储位置.
|
||||
15
docs/content/zh/usage/parsers.md
Normal file
15
docs/content/zh/usage/parsers.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "转存 Telegram 之外的文件"
|
||||
weight: 9
|
||||
---
|
||||
|
||||
# 转存 Telegram 之外的文件
|
||||
|
||||
除了 Telegram 上的文件, Bot 还可通过 JavaScript 插件或内置解析器来支持转存其他网站的文件.
|
||||
|
||||
> 查看[贡献解析器](../contribute)文档了解详情
|
||||
|
||||
只需向 Bot 发送符合解析器要求的链接即可使用, 当前内置的解析器:
|
||||
|
||||
- Twitter
|
||||
- Kemono
|
||||
58
docs/content/zh/usage/rules.md
Normal file
58
docs/content/zh/usage/rules.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "存储规则"
|
||||
weight: 3
|
||||
---
|
||||
|
||||
# 存储规则
|
||||
|
||||
允许你为 Bot 在上传文件到存储时设置一些重定向规则, 用于自动整理所保存的文件.
|
||||
|
||||
见: <a href="https://github.com/krau/SaveAny-Bot/issues/28" target="_blank">#28</a>
|
||||
|
||||
目前支持的规则类型:
|
||||
|
||||
1. FILENAME-REGEX
|
||||
2. MESSAGE-REGEX
|
||||
3. IS-ALBUM
|
||||
|
||||
添加规则的基本语法:
|
||||
|
||||
"规则类型 规则内容 存储名 路径"
|
||||
|
||||
注意空格的使用, 语法正确 bot 才能解析, 以下是一条合法的添加规则命令:
|
||||
|
||||
```
|
||||
/rule add FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
||||
```
|
||||
|
||||
此外, 规则中的存储名若使用 "CHOSEN" , 则表示存储到点击按钮选择的存储端的路径下
|
||||
|
||||
规则类型:
|
||||
|
||||
## FILENAME-REGEX
|
||||
|
||||
根据文件名正则匹配, 规则内容要求为一个合法的正则表达式, 如
|
||||
|
||||
```
|
||||
FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
||||
```
|
||||
|
||||
表示将文件名后缀为 mp4,mkv,ts,avi,flv 的文件放到名为 MyAlist 存储下的 /视频 目录内 (同时受配置文件中的 `base_path` 影响)
|
||||
|
||||
## MESSAGE-REGEX
|
||||
|
||||
同上, 但是是根据消息本身的文本内容正则匹配
|
||||
|
||||
## IS-ALBUM
|
||||
|
||||
匹配相册消息 (media group), 规则内容只能为 `true` 或 `false`.
|
||||
|
||||
规则中的路径若使用 "NEW-FOR-ALBUM" , 则表示为该组消息新建一个文件夹来存储它们. 见: https://github.com/krau/SaveAny-Bot/issues/87
|
||||
|
||||
例如:
|
||||
|
||||
```
|
||||
IS-ALBUM true MyWebdav NEW-FOR-ALBUM
|
||||
```
|
||||
|
||||
这将会把以 media group 形式发送的消息保存到名为 MyWebdav 的存储下, 并为每个相册新建一个文件夹(由第一个文件生成)来存储它们.
|
||||
14
docs/content/zh/usage/silent.md
Normal file
14
docs/content/zh/usage/silent.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "静默模式"
|
||||
weight: 2
|
||||
---
|
||||
|
||||
# 静默模式 (silent)
|
||||
|
||||
使用 `/silent` 命令可以开关静默模式.
|
||||
|
||||
默认情况下不开启静默模式, Bot 会询问你每个文件的保存位置.
|
||||
|
||||
开启静默模式后, Bot 会直接保存文件到默认位置, 无需确认.
|
||||
|
||||
在开启静默模式之前, 需要使用 `/storage` 命令设置默认保存位置.
|
||||
50
docs/content/zh/usage/transfer.md
Normal file
50
docs/content/zh/usage/transfer.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "存储间传输"
|
||||
weight: 8
|
||||
---
|
||||
|
||||
# 存储间传输
|
||||
|
||||
使用 `/transfer` 命令可以在不同存储之间直接传输文件, 无需经过 Telegram.
|
||||
|
||||
```bash
|
||||
/transfer <source_storage>:/<source_path> [filter]
|
||||
```
|
||||
|
||||
参数说明:
|
||||
|
||||
- `source_storage`: 源存储名称
|
||||
- `source_path`: 源路径
|
||||
- `filter`: 可选的正则表达式过滤器, 只传输匹配的文件
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 传输整个目录
|
||||
/transfer local1:/downloads
|
||||
|
||||
# 传输指定路径的文件
|
||||
/transfer alist1:/media/photos
|
||||
|
||||
# 只传输 mp4 文件
|
||||
/transfer webdav1:/videos ".*\.mp4$"
|
||||
|
||||
# 传输图片文件
|
||||
/transfer local1:/pictures "(?i)\.(jpg|png|gif)$"
|
||||
```
|
||||
|
||||
Bot 会:
|
||||
|
||||
1. 列出源路径下的所有文件
|
||||
2. 应用过滤器 (如果提供)
|
||||
3. 显示文件数量和总大小
|
||||
4. 让你选择目标存储
|
||||
5. 让你选择目标目录 (如果该存储配置了目录)
|
||||
6. 开始传输任务
|
||||
|
||||
注意:
|
||||
|
||||
- 源存储必须支持列举和读取功能
|
||||
- 目标存储必须支持写入功能
|
||||
- 传输过程显示实时进度
|
||||
- 支持取消正在进行的传输任务
|
||||
36
docs/content/zh/usage/watch.md
Normal file
36
docs/content/zh/usage/watch.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "监听聊天"
|
||||
weight: 4
|
||||
---
|
||||
|
||||
# 监听聊天
|
||||
|
||||
{{< hint warning >}}
|
||||
该功能需开启 UserBot 集成.
|
||||
{{< /hint >}}
|
||||
|
||||
监听指定聊天的消息, 并自动保存到默认存储中, 遵从存储规则, 并且可以设置过滤器来只保存匹配的消息.
|
||||
|
||||
监听聊天:
|
||||
|
||||
```
|
||||
/watch <chat_id/username> [filter]
|
||||
```
|
||||
|
||||
取消监听:
|
||||
|
||||
```
|
||||
/unwatch <chat_id/username>
|
||||
```
|
||||
|
||||
过滤器类型:
|
||||
|
||||
## msgre
|
||||
|
||||
正则匹配消息文本, 例如:
|
||||
|
||||
```
|
||||
/watch 12345678 msgre:.*hello.*
|
||||
```
|
||||
|
||||
这将会监听 ID 为 12345678 的聊天, 并且只保存消息文本中包含 "hello" 的消息.
|
||||
40
docs/content/zh/usage/ytdlp.md
Normal file
40
docs/content/zh/usage/ytdlp.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "yt-dlp 视频下载"
|
||||
weight: 7
|
||||
---
|
||||
|
||||
# yt-dlp 视频下载
|
||||
|
||||
{{< hint warning >}}
|
||||
该功能需要在系统中安装 yt-dlp 命令行工具.
|
||||
{{< /hint >}}
|
||||
|
||||
使用 `/ytdlp` 命令可以下载支持的视频网站的视频和音频, 支持 YouTube、Bilibili、Twitter 等 1000+ 个网站.
|
||||
|
||||
```bash
|
||||
/ytdlp <url1> [url2] [flags...]
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 基本下载
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
# 下载多个视频
|
||||
/ytdlp https://www.youtube.com/watch?v=video1 https://www.youtube.com/watch?v=video2
|
||||
|
||||
# 使用自定义参数
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ -f best
|
||||
/ytdlp https://www.youtube.com/watch?v=dQw4w9WgXcQ --extract-audio --audio-format mp3
|
||||
```
|
||||
|
||||
常用参数:
|
||||
|
||||
- `-f <format>`: 指定下载格式 (如 `best`, `worst`, `bestvideo+bestaudio`)
|
||||
- `--extract-audio`: 提取音频
|
||||
- `--audio-format <format>`: 音频格式 (如 `mp3`, `m4a`, `wav`)
|
||||
- `--write-sub`: 下载字幕
|
||||
- `--write-thumbnail`: 下载缩略图
|
||||
|
||||
更多参数请参考 [yt-dlp 文档](https://github.com/yt-dlp/yt-dlp#usage-and-options).
|
||||
@@ -9,10 +9,5 @@ if [ -n "$CONFIG_URL" ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f /app/config.toml ]; then
|
||||
echo "[ERROR] Missing config.toml: Please provide the configuration file via mounting or CONFIG_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec /app/saveany-bot
|
||||
72
go.mod
72
go.mod
@@ -9,36 +9,36 @@ require (
|
||||
github.com/charmbracelet/bubbles v1.0.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/charmbracelet/log v1.0.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.13
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/gotd/contrib v0.21.1
|
||||
github.com/gotd/td v0.140.0
|
||||
github.com/gotd/td v0.143.0
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3
|
||||
github.com/krau/ffmpeg-go v0.6.0
|
||||
github.com/lrstanley/go-ytdlp v1.3.2
|
||||
github.com/minio/minio-go/v7 v7.0.98
|
||||
github.com/lrstanley/go-ytdlp v1.3.5
|
||||
github.com/minio/minio-go/v7 v7.0.100
|
||||
github.com/playwright-community/playwright-go v0.5700.1
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/unvgo/ghselfupdate v1.0.1
|
||||
github.com/yapingcat/gomedia v0.0.0-20240906162731-17feea57090c
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/term v0.40.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/time v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AnimeKaizoku/cacher v1.0.3 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.1 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.2 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
@@ -56,14 +56,14 @@ require (
|
||||
github.com/go-faster/xor v1.0.0 // indirect
|
||||
github.com/go-faster/yaml v0.4.6 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc // indirect
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gotd/ige v0.2.2 // indirect
|
||||
github.com/gotd/neo v0.1.5 // indirect
|
||||
@@ -72,19 +72,20 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // 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.21 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/ncruces/go-sqlite3-wasm v1.1.1-0.20260409221933-87e4b35a38d0 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/ogen-go/ogen v1.20.1 // indirect
|
||||
github.com/ogen-go/ogen v1.20.3 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
@@ -92,40 +93,39 @@ require (
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.3 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.69.0 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.46.1 // indirect
|
||||
modernc.org/sqlite v1.48.2 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0
|
||||
github.com/dop251/goja v0.0.0-20260226184354-913bd86fb70c
|
||||
github.com/duke-git/lancet/v2 v2.3.8
|
||||
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c
|
||||
github.com/duke-git/lancet/v2 v2.3.9
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/ncruces/go-sqlite3 v0.30.5
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2
|
||||
github.com/ncruces/go-sqlite3 v0.33.3 // indirect
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.33.3
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
@@ -133,9 +133,9 @@ require (
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
153
go.sum
153
go.sum
@@ -4,8 +4,8 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
|
||||
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
@@ -48,16 +48,16 @@ github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5f
|
||||
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
|
||||
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/log v1.0.0 h1:HVVVMmfOorfj3BA9i8X8UL69Hoz9lI0PYwXfJvOdRc4=
|
||||
github.com/charmbracelet/log v1.0.0/go.mod h1:uYgY3SmLpwJWxmlrPwXvzVYujxis1vAKRV/0VQB7yWA=
|
||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
@@ -82,16 +82,16 @@ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa5
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20260226184354-913bd86fb70c h1:hIlkLbQ+tYoUqlG42LnxwGcohL5jaGqD8mGeJWavm8A=
|
||||
github.com/dop251/goja v0.0.0-20260226184354-913bd86fb70c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/duke-git/lancet/v2 v2.3.8 h1:dlkqn6Nj2LRWFuObNxttkMHxrFeaV6T26JR8jbEVbPg=
|
||||
github.com/duke-git/lancet/v2 v2.3.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c h1:OcLmPfx1T1RmZVHHFwWMPaZDdRf0DBMZOFMVWJa7Pdk=
|
||||
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/duke-git/lancet/v2 v2.3.9 h1:ZxUvfoEY7YbsGIeoXRxHWIkRCAt6VN7UBKWgCCqBB3U=
|
||||
github.com/duke-git/lancet/v2 v2.3.9/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@@ -115,8 +115,8 @@ 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-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=
|
||||
github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
@@ -141,8 +141,8 @@ github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQF
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg=
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gotd/contrib v0.21.1 h1:NSF+0YEnosQ34QEo2o4s6MA5YFDAor1LVvLhN1L3H1M=
|
||||
@@ -151,8 +151,8 @@ github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk=
|
||||
github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
|
||||
github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
|
||||
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
||||
github.com/gotd/td v0.140.0 h1:trNBzTnhNtNwHsFp5qwKnNxQRAZJ6/BRE+uH3Lojauk=
|
||||
github.com/gotd/td v0.140.0/go.mod h1:0ZkRxG7N+5ooG7/zdRXcnGautGPM6IKmyPQvdsAeF20=
|
||||
github.com/gotd/td v0.143.0 h1:p0U/Nn92zXmAsahDn5CIVzay2kQ36lBBENT/FlWR2nQ=
|
||||
github.com/gotd/td v0.143.0/go.mod h1:8GA5ecTI5iswLwBAlqf0u6/+j+BqSWUARSrX2Xk1usQ=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
|
||||
@@ -165,8 +165,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3 h1:2713fQZ560HxoNVgfJH41GKzjMjIG+DW4hH6nYXfXW8=
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20250916175020-ebf3e50324d3/go.mod h1:S4S9jGBVlLri0OeqrSSbCGG5vsI6he06UJyuz1WT1EE=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
@@ -178,24 +178,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/krau/ffmpeg-go v0.6.0 h1:F4HWvOrKXQsfLsFTOnUfP0HY6WISJqOrsAFGSIzkKto=
|
||||
github.com/krau/ffmpeg-go v0.6.0/go.mod h1:sa7/bWHB6fO9j4lhmxnWQ1U07o+dE1leFjhctotxU7A=
|
||||
github.com/lrstanley/go-ytdlp v1.3.2 h1:ktOav5X8+ZByuaQPFUF3uiPxofw0L5MoQtck6iIkWhI=
|
||||
github.com/lrstanley/go-ytdlp v1.3.2/go.mod h1:VgjnTrvkTf+23JuySjyPq1iQ8ijSovBtTPpXH5XrLtI=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lrstanley/go-ytdlp v1.3.5 h1:eT+29mK3Lp+XPMQOH25+jVerrrjifYW1o3IkTYJ9SMs=
|
||||
github.com/lrstanley/go-ytdlp v1.3.5/go.mod h1:VgjnTrvkTf+23JuySjyPq1iQ8ijSovBtTPpXH5XrLtI=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
|
||||
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
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.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
|
||||
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
|
||||
github.com/minio/minio-go/v7 v7.0.100 h1:ShkWi8Tyj9RtU57OQB2HIXKz4bFgtVib0bbT1sbtLI8=
|
||||
github.com/minio/minio-go/v7 v7.0.100/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
||||
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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
@@ -204,20 +204,22 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/ncruces/go-sqlite3 v0.30.5 h1:6usmTQ6khriL8oWilkAZSJM/AIpAlVL2zFrlcpDldCE=
|
||||
github.com/ncruces/go-sqlite3 v0.30.5/go.mod h1:0I0JFflTKzfs3Ogfv8erP7CCoV/Z8uxigVDNOR0AQ5E=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2 h1:FZ8mic14xTatssTkHCrelh9nPeFdXuzgMoNGkfuFbBU=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2/go.mod h1:W9WLBbqrrOIh2dqFZkeC/xKALG2LDIHY91jowahOdtI=
|
||||
github.com/ncruces/go-sqlite3 v0.33.3 h1:6jCR3KuGvJSEwhaQrkHDGeIe2qCQ6nOUDNsPz7ZIotw=
|
||||
github.com/ncruces/go-sqlite3 v0.33.3/go.mod h1:t2Osfw0wcKzJTgv2EvrkTtVLqlbKTA5Yvwb2ypAlBcY=
|
||||
github.com/ncruces/go-sqlite3-wasm v1.1.1-0.20260409221933-87e4b35a38d0 h1:ymE9H30x1AyW5VfMNkJC9teuI2W1jjMsQS7kc6zl6Tg=
|
||||
github.com/ncruces/go-sqlite3-wasm v1.1.1-0.20260409221933-87e4b35a38d0/go.mod h1:/H3+JykPsfSlvKbOxNSx9kKwm3ecqQGzyCs1e9KkNsU=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.33.3 h1:JzLk8XymgvHvy60ib5MtNmd0fIYwGi7FUj2DpRFmnWQ=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.33.3/go.mod h1:qDjzlaffXDGg5bhZs2VaaSY0Qb3rsiKq0O4pXkmQfHI=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||
github.com/ogen-go/ogen v1.20.1 h1:AFpIeI2rS37TNIMRQTHhAkThICQpa1p+Pceu7HP7xsA=
|
||||
github.com/ogen-go/ogen v1.20.1/go.mod h1:eXQeqzIfw9qUjXdpqNtkX+XCvhlWNymqU1bm7S7y8iU=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/ogen-go/ogen v1.20.3 h1:1tvJuJE0BnQ7Nukd6ykiTOP0ucfL0yrAjHUg3S1DCQk=
|
||||
github.com/ogen-go/ogen v1.20.3/go.mod h1:sJ1pJVp4S1RcSZlYIiMLo0QSMSt2pls4zfrc+hNKnzk=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -261,8 +263,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
@@ -278,12 +278,12 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d h1:Ns9kd1Rwzw7t0BR8XMphenji4SmIoNZPn8zhYmaVKP8=
|
||||
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3l++MlthCm+koNi0tcUCX3anayogF0Pa/sp24k=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
@@ -299,29 +299,29 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -329,34 +329,33 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -372,10 +371,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.31.0 h1:/bsaxqdgX3gy/0DboxcvWrc3NpzH+6wpFfI/ZaA/hrg=
|
||||
modernc.org/ccgo/v4 v4.31.0/go.mod h1:jKe8kPBjIN/VdGTVqARTQ8N1gAziBmiISY8j5HoKwjg=
|
||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
@@ -384,8 +383,8 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.69.0 h1:YQJ5QMSReTgQ3QFmI0dudfjXIjCcYTUxcH8/9P9f0D8=
|
||||
modernc.org/libc v1.69.0/go.mod h1:YfLLduUEbodNV2xLU5JOnRHBTAHVHsVW3bVYGw0ZCV4=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -394,8 +393,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
|
||||
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
Reference in New Issue
Block a user