Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee5e0b8ff0 | ||
|
|
6423fb25a7 | ||
|
|
03907f2d32 | ||
|
|
9e5042bda1 | ||
|
|
4ebacb02c1 | ||
|
|
818ac9b240 | ||
|
|
fc4a112f08 | ||
|
|
7a2274baa0 | ||
|
|
69a3ed6f4e | ||
|
|
36f3dd83fc | ||
|
|
501b9d844a | ||
|
|
03cec7ec01 | ||
|
|
dc0debcd1c | ||
|
|
4b136bd41e | ||
|
|
d703f11ea0 | ||
|
|
3ce9926967 | ||
|
|
80146176f0 | ||
|
|
14ba2afdf8 | ||
|
|
f4d427a1cb | ||
|
|
f84c83a7e2 | ||
|
|
cb6540c017 | ||
|
|
e7bab27543 | ||
|
|
f693bd6103 | ||
|
|
75f52569a0 | ||
|
|
c795f957a9 | ||
|
|
3b85911e3d |
@@ -6,6 +6,6 @@
|
|||||||
downloads/
|
downloads/
|
||||||
data/
|
data/
|
||||||
cache/
|
cache/
|
||||||
docs
|
docs/
|
||||||
config.example.toml
|
config.example.toml
|
||||||
docker-compose.*
|
docker-compose.*
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
custom: [
|
custom: [
|
||||||
"https://afdian.com/a/acherkrau"
|
"https://afdian.com/a/unvapp"
|
||||||
]
|
]
|
||||||
27
.github/workflows/build-docker.yml
vendored
27
.github/workflows/build-docker.yml
vendored
@@ -29,13 +29,7 @@ jobs:
|
|||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=sha
|
type=sha
|
||||||
type=raw,value=latest
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=tag
|
|
||||||
labels: |
|
|
||||||
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
|
|
||||||
org.opencontainers.image.source=https://github.com/krau/SaveAny-Bot
|
|
||||||
org.opencontainers.image.url=https://github.com/krau/SaveAny-Bot
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -50,23 +44,20 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract version from Git Ref
|
|
||||||
id: extract_version
|
|
||||||
run: |
|
|
||||||
VERSION=$(echo "${{ github.ref }}" | sed 's/refs\/tags\/v//')
|
|
||||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=gha
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: |
|
||||||
|
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ steps.meta.outputs.version }}
|
VERSION=${{ steps.meta.outputs.version }}
|
||||||
GitCommit=${{ github.sha }}
|
GitCommit=${{ github.sha }}
|
||||||
BuildTime=${{ format(github.event.repository.updated_at, 'yyyy-MM-dd HH:mm:ss') }}
|
BuildTime=${{ fromJson(toJSON(github.event.repository.pushed_at)) }}
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|||||||
15
Dockerfile
15
Dockerfile
@@ -6,22 +6,31 @@ ARG BuildTime="Unknown"
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY go.mod go.sum ./
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||||
--mount=type=cache,target=/go/pkg \
|
--mount=type=cache,target=/go/pkg \
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
go build -trimpath \
|
go build -trimpath \
|
||||||
-ldflags "-s -w \
|
-ldflags "-s -w \
|
||||||
-X github.com/krau/SaveAny-Bot/common.Version=${VERSION} \
|
-X github.com/krau/SaveAny-Bot/common.Version=${VERSION} \
|
||||||
-X github.com/krau/SaveAny-Bot/common.GitCommit=${GiTCommit} \
|
-X github.com/krau/SaveAny-Bot/common.GitCommit=${GitCommit} \
|
||||||
-X github.com/krau/SaveAny-Bot/common.BuildTime=${BuildTime}" \
|
-X github.com/krau/SaveAny-Bot/common.BuildTime=${BuildTime}" \
|
||||||
-o saveany-bot .
|
-o saveany-bot .
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=builder /app/saveany-bot .
|
COPY --from=builder /app/saveany-bot .
|
||||||
|
COPY entrypoint.sh .
|
||||||
|
|
||||||
ENTRYPOINT ["/app/saveany-bot"]
|
RUN chmod +x /app/saveany-bot && \
|
||||||
|
chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
11
README.md
11
README.md
@@ -48,6 +48,13 @@
|
|||||||
<sub><b>Krau</b></sub>
|
<sub><b>Krau</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Silentely">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/22141172?v=4" width="100;" alt="Silentely"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Abner</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/TG-Twilight">
|
<a href="https://github.com/TG-Twilight">
|
||||||
<img src="https://avatars.githubusercontent.com/u/121682528?v=4" width="100;" alt="TG-Twilight"/>
|
<img src="https://avatars.githubusercontent.com/u/121682528?v=4" width="100;" alt="TG-Twilight"/>
|
||||||
@@ -63,8 +70,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/ahcorn">
|
<a href="https://github.com/AHCorn">
|
||||||
<img src="https://avatars.githubusercontent.com/u/42889600?v=4" width="100;" alt="ahcorn"/>
|
<img src="https://avatars.githubusercontent.com/u/42889600?v=4" width="100;" alt="AHCorn"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>安和</b></sub>
|
<sub><b>安和</b></sub>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/celestix/gotgproto"
|
"github.com/celestix/gotgproto"
|
||||||
@@ -14,21 +13,12 @@ import (
|
|||||||
"github.com/gotd/td/tg"
|
"github.com/gotd/td/tg"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers"
|
||||||
"github.com/krau/SaveAny-Bot/client/middleware"
|
"github.com/krau/SaveAny-Bot/client/middleware"
|
||||||
|
"github.com/krau/SaveAny-Bot/common/utils/netutil"
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/ncruces/go-sqlite3/gormlite"
|
"github.com/ncruces/go-sqlite3/gormlite"
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Client *gotgproto.Client
|
|
||||||
|
|
||||||
func newProxyDialer(proxyUrl string) (proxy.Dialer, error) {
|
|
||||||
url, err := url.Parse(proxyUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return proxy.FromURL(url, proxy.Direct)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(ctx context.Context) {
|
func Init(ctx context.Context) {
|
||||||
log.FromContext(ctx).Info("初始化 Bot...")
|
log.FromContext(ctx).Info("初始化 Bot...")
|
||||||
resultChan := make(chan struct {
|
resultChan := make(chan struct {
|
||||||
@@ -38,7 +28,7 @@ func Init(ctx context.Context) {
|
|||||||
go func() {
|
go func() {
|
||||||
var resolver dcs.Resolver
|
var resolver dcs.Resolver
|
||||||
if config.Cfg.Telegram.Proxy.Enable && config.Cfg.Telegram.Proxy.URL != "" {
|
if config.Cfg.Telegram.Proxy.Enable && config.Cfg.Telegram.Proxy.URL != "" {
|
||||||
dialer, err := newProxyDialer(config.Cfg.Telegram.Proxy.URL)
|
dialer, err := netutil.NewProxyDialer(config.Cfg.Telegram.Proxy.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultChan <- struct {
|
resultChan <- struct {
|
||||||
client *gotgproto.Client
|
client *gotgproto.Client
|
||||||
@@ -52,7 +42,8 @@ func Init(ctx context.Context) {
|
|||||||
} else {
|
} else {
|
||||||
resolver = dcs.DefaultResolver()
|
resolver = dcs.DefaultResolver()
|
||||||
}
|
}
|
||||||
client, err := gotgproto.NewClient(config.Cfg.Telegram.AppID,
|
client, err := gotgproto.NewClient(
|
||||||
|
config.Cfg.Telegram.AppID,
|
||||||
config.Cfg.Telegram.AppHash,
|
config.Cfg.Telegram.AppHash,
|
||||||
gotgproto.ClientTypeBot(config.Cfg.Telegram.Token),
|
gotgproto.ClientTypeBot(config.Cfg.Telegram.Token),
|
||||||
&gotgproto.ClientOpts{
|
&gotgproto.ClientOpts{
|
||||||
@@ -104,8 +95,7 @@ func Init(ctx context.Context) {
|
|||||||
if result.err != nil {
|
if result.err != nil {
|
||||||
log.FromContext(ctx).Fatalf("初始化 Bot 失败: %s", result.err)
|
log.FromContext(ctx).Fatalf("初始化 Bot 失败: %s", result.err)
|
||||||
}
|
}
|
||||||
Client = result.client
|
handlers.Register(result.client.Dispatcher)
|
||||||
handlers.Register(Client.Dispatcher)
|
|
||||||
log.FromContext(ctx).Info("Bot 初始化完成")
|
log.FromContext(ctx).Info("Bot 初始化完成")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ func handleMessageLink(ctx *ext.Context, update *ext.Update) error {
|
|||||||
editReplied("构建存储选择键盘失败: "+err.Error(), nil)
|
editReplied("构建存储选择键盘失败: "+err.Error(), nil)
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
editReplied(fmt.Sprintf("找到 %d 个文件, 请选择存储位置", len(files)),
|
editReplied(fmt.Sprintf("找到 %d 个文件, 请选择存储位置", len(files)), markup)
|
||||||
markup)
|
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/celestix/gotgproto/dispatcher"
|
"github.com/celestix/gotgproto/dispatcher"
|
||||||
"github.com/celestix/gotgproto/ext"
|
"github.com/celestix/gotgproto/ext"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/gotd/td/tg"
|
||||||
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/mediautil"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/shortcut"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/shortcut"
|
||||||
|
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tcbdata"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
"github.com/krau/SaveAny-Bot/storage"
|
"github.com/krau/SaveAny-Bot/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleMediaMessage(ctx *ext.Context, update *ext.Update) error {
|
func handleMediaMessage(ctx *ext.Context, update *ext.Update) error {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
message := update.EffectiveMessage.Message
|
message := update.EffectiveMessage.Message
|
||||||
|
groupID, isGroup := message.GetGroupedID()
|
||||||
|
if isGroup && groupID != 0 {
|
||||||
|
return handleGroupMediaMessage(ctx, update, message, groupID)
|
||||||
|
}
|
||||||
logger.Debugf("Got media: %s", message.Media.TypeName())
|
logger.Debugf("Got media: %s", message.Media.TypeName())
|
||||||
|
|
||||||
msg, file, err := shortcut.GetFileFromMessageWithReply(ctx, update, message)
|
msg, file, err := shortcut.GetFileFromMessageWithReply(ctx, update, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -38,6 +52,10 @@ func handleSilentSaveMedia(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
message := update.EffectiveMessage.Message
|
message := update.EffectiveMessage.Message
|
||||||
|
groupID, isGroup := message.GetGroupedID()
|
||||||
|
if isGroup && groupID != 0 {
|
||||||
|
return handleGroupMediaMessage(ctx, update, message, groupID)
|
||||||
|
}
|
||||||
logger.Debugf("Got media: %s", message.Media.TypeName())
|
logger.Debugf("Got media: %s", message.Media.TypeName())
|
||||||
userID := update.GetUserChat().GetID()
|
userID := update.GetUserChat().GetID()
|
||||||
msg, file, err := shortcut.GetFileFromMessageWithReply(ctx, update, message)
|
msg, file, err := shortcut.GetFileFromMessageWithReply(ctx, update, message)
|
||||||
@@ -46,3 +64,96 @@ func handleSilentSaveMedia(ctx *ext.Context, update *ext.Update) error {
|
|||||||
}
|
}
|
||||||
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, stor, "", file, msg.ID)
|
return shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userID, stor, "", file, msg.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MediaGroupHandler struct {
|
||||||
|
groups map[int64][]tfile.TGFileMessage
|
||||||
|
timers map[int64]*time.Timer
|
||||||
|
mu sync.Mutex
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var mediaGroupHandler = &MediaGroupHandler{
|
||||||
|
groups: make(map[int64][]tfile.TGFileMessage),
|
||||||
|
timers: make(map[int64]*time.Timer),
|
||||||
|
timeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGroupMediaMessage(ctx *ext.Context, update *ext.Update, message *tg.Message, groupID int64) error {
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
media := message.Media
|
||||||
|
supported := mediautil.IsSupported(media)
|
||||||
|
if !supported {
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
file, err := tfile.FromMediaMessage(media, ctx.Raw, message, tfile.WithNameIfEmpty(
|
||||||
|
tgutil.GenFileNameFromMessage(*message),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to get file from media: %s", err)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
mediaGroupHandler.mu.Lock()
|
||||||
|
defer mediaGroupHandler.mu.Unlock()
|
||||||
|
if mediaGroupHandler.groups[groupID] == nil {
|
||||||
|
mediaGroupHandler.groups[groupID] = make([]tfile.TGFileMessage, 0)
|
||||||
|
}
|
||||||
|
mediaGroupHandler.groups[groupID] = append(mediaGroupHandler.groups[groupID], file)
|
||||||
|
|
||||||
|
if timer, exists := mediaGroupHandler.timers[groupID]; exists {
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
mediaGroupHandler.timers[groupID] = time.AfterFunc(mediaGroupHandler.timeout, func() {
|
||||||
|
processMediaGroup(ctx, update, groupID)
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func processMediaGroup(ctx *ext.Context, update *ext.Update, groupID int64) {
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
mediaGroupHandler.mu.Lock()
|
||||||
|
items := mediaGroupHandler.groups[groupID]
|
||||||
|
delete(mediaGroupHandler.groups, groupID)
|
||||||
|
delete(mediaGroupHandler.timers, groupID)
|
||||||
|
mediaGroupHandler.mu.Unlock()
|
||||||
|
if len(items) == 0 {
|
||||||
|
logger.Warn("No media items to process for group", "groupID", groupID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Debugf("Processing media group %d with %d items", groupID, len(items))
|
||||||
|
|
||||||
|
userId := update.GetUserChat().GetID()
|
||||||
|
msg, err := ctx.Reply(update, ext.ReplyTextString("正在保存文件..."), nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to reply: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stor := storage.FromContext(ctx)
|
||||||
|
if stor != nil {
|
||||||
|
// In silent mode
|
||||||
|
if len(items) == 1 {
|
||||||
|
shortcut.CreateAndAddTGFileTaskWithEdit(ctx, userId, stor, "", items[0], msg.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shortcut.CreateAndAddBatchTGFileTaskWithEdit(ctx, userId, stor, "", items, msg.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stors := storage.GetUserStorages(ctx, userId)
|
||||||
|
markup, err := msgelem.BuildAddSelectStorageKeyboard(stors, tcbdata.Add{
|
||||||
|
Files: items,
|
||||||
|
AsBatch: len(items) > 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("构建存储选择键盘失败: %s", err)
|
||||||
|
ctx.EditMessage(userId, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: msg.ID,
|
||||||
|
Message: "构建存储选择键盘失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.EditMessage(userId, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: msg.ID,
|
||||||
|
Message: fmt.Sprintf("共 %d 个文件, 请选择存储位置", len(items)),
|
||||||
|
ReplyMarkup: markup,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ func handleBatchSave(ctx *ext.Context, update *ext.Update, args []string) error
|
|||||||
if !supported {
|
if !supported {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
file, err := tfile.FromMediaMessage(media, msg, tfile.WithNameIfEmpty(tgutil.GenFileNameFromMessage(*msg)))
|
file, err := tfile.FromMediaMessage(media, ctx.Raw, msg, tfile.WithNameIfEmpty(tgutil.GenFileNameFromMessage(*msg)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Errorf("获取文件失败: %s", err)
|
log.FromContext(ctx).Errorf("获取文件失败: %s", err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package ruleutil
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/convertor"
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/krau/SaveAny-Bot/database"
|
"github.com/krau/SaveAny-Bot/database"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/consts"
|
"github.com/krau/SaveAny-Bot/pkg/consts"
|
||||||
@@ -33,11 +35,22 @@ func (m matchedStorName) String() string {
|
|||||||
return string(m)
|
return string(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m matchedStorName) IsValid() bool {
|
// can we use this storage name directly?
|
||||||
|
func (m matchedStorName) IsUsable() bool {
|
||||||
return m != "" && m != consts.RuleStorNameChosen
|
return m != "" && m != consts.RuleStorNameChosen
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (matchedStorageName matchedStorName, dirPath string) {
|
type MatchedDirPath string
|
||||||
|
|
||||||
|
func (m MatchedDirPath) String() string {
|
||||||
|
return string(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MatchedDirPath) NeedNewForAlbum() bool {
|
||||||
|
return m != "" && m == consts.RuleDirPathNewForAlbum
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (matchedStorageName matchedStorName, dirPath MatchedDirPath) {
|
||||||
if inputs == nil || len(rules) == 0 {
|
if inputs == nil || len(rules) == 0 {
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
@@ -56,7 +69,7 @@ func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (m
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
dirPath = ru.StoragePath()
|
dirPath = MatchedDirPath(ru.StoragePath())
|
||||||
matchedStorageName = matchedStorName(ru.StorageName())
|
matchedStorageName = matchedStorName(ru.StorageName())
|
||||||
}
|
}
|
||||||
case ruleenum.MessageRegex.String():
|
case ruleenum.MessageRegex.String():
|
||||||
@@ -71,7 +84,26 @@ func ApplyRule(ctx context.Context, rules []database.Rule, inputs *ruleInput) (m
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
dirPath = ru.StoragePath()
|
dirPath = MatchedDirPath(ru.StoragePath())
|
||||||
|
matchedStorageName = matchedStorName(ru.StorageName())
|
||||||
|
}
|
||||||
|
case ruleenum.IsAlbum.String():
|
||||||
|
matchAlbum, err := convertor.ToBool(ur.Data)
|
||||||
|
if err != nil {
|
||||||
|
matchAlbum = false
|
||||||
|
}
|
||||||
|
ru, err := rule.NewRuleMediaType(ur.StorageName, ur.DirPath, matchAlbum)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to create rule: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok, err := ru.Match(inputs.File.Message().GroupedID != 0)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to match rule: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
dirPath = MatchedDirPath(ru.StoragePath())
|
||||||
matchedStorageName = matchedStorName(ru.StorageName())
|
matchedStorageName = matchedStorName(ru.StorageName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import (
|
|||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/mediautil"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/mediautil"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem"
|
||||||
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/re"
|
"github.com/krau/SaveAny-Bot/client/bot/handlers/utils/re"
|
||||||
|
uc "github.com/krau/SaveAny-Bot/client/user"
|
||||||
"github.com/krau/SaveAny-Bot/common/cache"
|
"github.com/krau/SaveAny-Bot/common/cache"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/tphutil"
|
"github.com/krau/SaveAny-Bot/common/utils/tphutil"
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/telegraph"
|
"github.com/krau/SaveAny-Bot/pkg/telegraph"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
)
|
)
|
||||||
@@ -46,7 +48,7 @@ func GetFileFromMessageWithReply(ctx *ext.Context, update *ext.Update, message *
|
|||||||
} else {
|
} else {
|
||||||
options = append(options, tfile.WithNameIfEmpty(tgutil.GenFileNameFromMessage(*message)))
|
options = append(options, tfile.WithNameIfEmpty(tgutil.GenFileNameFromMessage(*message)))
|
||||||
}
|
}
|
||||||
file, err = tfile.FromMediaMessage(media, message, options...)
|
file, err = tfile.FromMediaMessage(media, ctx.Raw, message, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to get file from media: %s", err)
|
logger.Errorf("Failed to get file from media: %s", err)
|
||||||
ctx.Reply(update, ext.ReplyTextString("获取文件失败: "+err.Error()), nil)
|
ctx.Reply(update, ext.ReplyTextString("获取文件失败: "+err.Error()), nil)
|
||||||
@@ -81,8 +83,8 @@ func GetFilesFromUpdateLinkMessageWithReplyEdit(ctx *ext.Context, update *ext.Up
|
|||||||
}
|
}
|
||||||
|
|
||||||
files = make([]tfile.TGFileMessage, 0, len(msgLinks))
|
files = make([]tfile.TGFileMessage, 0, len(msgLinks))
|
||||||
addFile := func(msg *tg.Message) {
|
addFile := func(client tfile.DlerClient, msg *tg.Message) {
|
||||||
if msg == nil {
|
if msg == nil || msg.Media == nil {
|
||||||
logger.Warn("message is nil, skipping")
|
logger.Warn("message is nil, skipping")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -91,25 +93,31 @@ func GetFilesFromUpdateLinkMessageWithReplyEdit(ctx *ext.Context, update *ext.Up
|
|||||||
logger.Debugf("message %d has no media", msg.GetID())
|
logger.Debugf("message %d has no media", msg.GetID())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
file, err := tfile.FromMediaMessage(media, msg, tfile.WithNameIfEmpty(tgutil.GenFileNameFromMessage(*msg)))
|
file, err := tfile.FromMediaMessage(media, client, msg, tfile.WithNameIfEmpty(tgutil.GenFileNameFromMessage(*msg)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to create file from media: %s", err)
|
logger.Errorf("failed to create file from media: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
files = append(files, file)
|
files = append(files, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tctx := ctx
|
||||||
|
if config.Cfg.Telegram.Userbot.Enable {
|
||||||
|
tctx = uc.GetCtx()
|
||||||
|
}
|
||||||
|
|
||||||
for _, link := range msgLinks {
|
for _, link := range msgLinks {
|
||||||
linkUrl, err := url.Parse(link)
|
linkUrl, err := url.Parse(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to parse message link %s: %s", link, err)
|
logger.Errorf("failed to parse message link %s: %s", link, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
chatId, msgId, err := tgutil.ParseMessageLink(ctx, link)
|
chatId, msgId, err := tgutil.ParseMessageLink(tctx, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to parse message link %s: %s", link, err)
|
logger.Errorf("failed to parse message link %s: %s", link, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msg, err := tgutil.GetMessageByID(ctx, chatId, msgId)
|
msg, err := tgutil.GetMessageByID(tctx, chatId, msgId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to get message by ID: %s", err)
|
logger.Errorf("failed to get message by ID: %s", err)
|
||||||
continue
|
continue
|
||||||
@@ -121,11 +129,11 @@ func GetFilesFromUpdateLinkMessageWithReplyEdit(ctx *ext.Context, update *ext.Up
|
|||||||
logger.Errorf("failed to get grouped messages: %s", err)
|
logger.Errorf("failed to get grouped messages: %s", err)
|
||||||
} else {
|
} else {
|
||||||
for _, gmsg := range gmsgs {
|
for _, gmsg := range gmsgs {
|
||||||
addFile(gmsg)
|
addFile(tctx.Raw, gmsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addFile(msg)
|
addFile(tctx.Raw, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package shortcut
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/celestix/gotgproto/dispatcher"
|
"github.com/celestix/gotgproto/dispatcher"
|
||||||
"github.com/celestix/gotgproto/ext"
|
"github.com/celestix/gotgproto/ext"
|
||||||
@@ -34,8 +35,8 @@ func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage
|
|||||||
}
|
}
|
||||||
if user.ApplyRule && user.Rules != nil {
|
if user.ApplyRule && user.Rules != nil {
|
||||||
matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||||
dirPath = matchedDirPath
|
dirPath = matchedDirPath.String()
|
||||||
if matchedStorageName.IsValid() {
|
if matchedStorageName.IsUsable() {
|
||||||
stor, err = storage.GetStorageByUserIDAndName(ctx, user.ChatID, matchedStorageName.String())
|
stor, err = storage.GetStorageByUserIDAndName(ctx, user.ChatID, matchedStorageName.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to get storage by user ID and name: %s", err)
|
logger.Errorf("Failed to get storage by user ID and name: %s", err)
|
||||||
@@ -51,7 +52,7 @@ func CreateAndAddTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor storage
|
|||||||
storagePath := stor.JoinStoragePath(path.Join(dirPath, file.Name()))
|
storagePath := stor.JoinStoragePath(path.Join(dirPath, file.Name()))
|
||||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||||
taskid := xid.New().String()
|
taskid := xid.New().String()
|
||||||
task, err := tftask.NewTGFileTask(taskid, injectCtx, file, ctx.Raw, stor, storagePath,
|
task, err := tftask.NewTGFileTask(taskid, injectCtx, file, stor, storagePath,
|
||||||
tftask.NewProgressTrack(
|
tftask.NewProgressTrack(
|
||||||
trackMsgID,
|
trackMsgID,
|
||||||
userID))
|
userID))
|
||||||
@@ -93,19 +94,28 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
})
|
})
|
||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
useRule := user.ApplyRule && user.Rules != nil
|
useRule := user.ApplyRule && user.Rules != nil
|
||||||
applyRule := func(file tfile.TGFileMessage) (string, string) {
|
|
||||||
|
applyRule := func(file tfile.TGFileMessage) (string, ruleutil.MatchedDirPath) {
|
||||||
if !useRule {
|
if !useRule {
|
||||||
return stor.Name(), dirPath
|
return stor.Name(), ruleutil.MatchedDirPath(dirPath)
|
||||||
}
|
}
|
||||||
storName, dirP := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
storName, dirP := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||||
if !storName.IsValid() {
|
|
||||||
return stor.Name(), dirP
|
storname := storName.String()
|
||||||
|
if !storName.IsUsable() {
|
||||||
|
storname = stor.Name()
|
||||||
}
|
}
|
||||||
return storName.String(), dirP
|
return storname, dirP
|
||||||
}
|
}
|
||||||
|
|
||||||
elems := make([]batchtftask.TaskElement, 0, len(files))
|
elems := make([]batchtftask.TaskElement, 0, len(files))
|
||||||
|
type albumFile struct {
|
||||||
|
file tfile.TGFileMessage
|
||||||
|
storage storage.Storage
|
||||||
|
}
|
||||||
|
albumFiles := make(map[int64][]albumFile, 0)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
storName, dirPath := applyRule(file)
|
storName, dirPath := applyRule(file)
|
||||||
fileStor := stor
|
fileStor := stor
|
||||||
@@ -120,21 +130,59 @@ func CreateAndAddBatchTGFileTaskWithEdit(ctx *ext.Context, userID int64, stor st
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storPath := fileStor.JoinStoragePath(path.Join(dirPath, file.Name()))
|
if !dirPath.NeedNewForAlbum() {
|
||||||
elem, err := batchtftask.NewTaskElement(fileStor, storPath, file)
|
storPath := fileStor.JoinStoragePath(path.Join(dirPath.String(), file.Name()))
|
||||||
if err != nil {
|
elem, err := batchtftask.NewTaskElement(fileStor, storPath, file)
|
||||||
logger.Errorf("Failed to create task element: %s", err)
|
if err != nil {
|
||||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
logger.Errorf("Failed to create task element: %s", err)
|
||||||
ID: trackMsgID,
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
Message: "任务创建失败: " + err.Error(),
|
ID: trackMsgID,
|
||||||
|
Message: "任务创建失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
elems = append(elems, *elem)
|
||||||
|
} else {
|
||||||
|
groupId, isGroup := file.Message().GetGroupedID()
|
||||||
|
if !isGroup || groupId == 0 {
|
||||||
|
logger.Warnf("File %s is not in a group, skipping album handling", file.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := albumFiles[groupId]; !ok {
|
||||||
|
albumFiles[groupId] = make([]albumFile, 0)
|
||||||
|
}
|
||||||
|
albumFiles[groupId] = append(albumFiles[groupId], albumFile{
|
||||||
|
file: file,
|
||||||
|
storage: fileStor,
|
||||||
})
|
})
|
||||||
return dispatcher.EndGroups
|
|
||||||
}
|
}
|
||||||
elems = append(elems, *elem)
|
|
||||||
}
|
}
|
||||||
|
for _, afiles := range albumFiles {
|
||||||
|
if len(afiles) <= 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 对于需要新建目录的文件, 将第一个文件的文件名(去除扩展名)作为目录名
|
||||||
|
// 存储以第一个文件的存储为准
|
||||||
|
albumDir := strings.TrimSuffix(path.Base(afiles[0].file.Name()), path.Ext(afiles[0].file.Name()))
|
||||||
|
albumStor := afiles[0].storage
|
||||||
|
for _, af := range afiles {
|
||||||
|
afstorPath := af.storage.JoinStoragePath(path.Join(dirPath, albumDir, af.file.Name()))
|
||||||
|
elem, err := batchtftask.NewTaskElement(albumStor, afstorPath, af.file)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to create task element for album file: %s", err)
|
||||||
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
ID: trackMsgID,
|
||||||
|
Message: "任务创建失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
}
|
||||||
|
elems = append(elems, *elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||||
taskid := xid.New().String()
|
taskid := xid.New().String()
|
||||||
task := batchtftask.NewBatchTGFileTask(taskid, injectCtx, elems, ctx.Raw, batchtftask.NewProgressTracker(trackMsgID, userID), true)
|
task := batchtftask.NewBatchTGFileTask(taskid, injectCtx, elems, batchtftask.NewProgressTracker(trackMsgID, userID), true)
|
||||||
if err := core.AddTask(injectCtx, task); err != nil {
|
if err := core.AddTask(injectCtx, task); err != nil {
|
||||||
logger.Errorf("Failed to add batch task: %s", err)
|
logger.Errorf("Failed to add batch task: %s", err)
|
||||||
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
ctx.EditMessage(userID, &tg.MessagesEditMessageRequest{
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import (
|
|||||||
"github.com/gotd/td/telegram"
|
"github.com/gotd/td/telegram"
|
||||||
"github.com/krau/SaveAny-Bot/client/middleware/recovery"
|
"github.com/krau/SaveAny-Bot/client/middleware/recovery"
|
||||||
"github.com/krau/SaveAny-Bot/client/middleware/retry"
|
"github.com/krau/SaveAny-Bot/client/middleware/retry"
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://github.com/iyear/tdl/blob/master/core/tclient/tclient.go
|
// https://github.com/iyear/tdl/blob/master/core/tclient/tclient.go
|
||||||
func NewDefaultMiddlewares(ctx context.Context, timeout time.Duration) []telegram.Middleware {
|
func NewDefaultMiddlewares(ctx context.Context, timeout time.Duration) []telegram.Middleware {
|
||||||
return []telegram.Middleware{
|
return []telegram.Middleware{
|
||||||
recovery.New(ctx, newBackoff(timeout)),
|
recovery.New(ctx, newBackoff(timeout)),
|
||||||
retry.New(5),
|
retry.New(config.Cfg.Telegram.RpcRetry),
|
||||||
floodwait.NewSimpleWaiter(),
|
floodwait.NewSimpleWaiter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/go-faster/errors"
|
|
||||||
"github.com/gotd/td/bin"
|
"github.com/gotd/td/bin"
|
||||||
"github.com/gotd/td/telegram"
|
"github.com/gotd/td/telegram"
|
||||||
"github.com/gotd/td/tg"
|
"github.com/gotd/td/tg"
|
||||||
@@ -37,7 +36,8 @@ func (r retry) Handle(next tg.Invoker) telegram.InvokeFunc {
|
|||||||
retries++
|
retries++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return errors.Wrap(err, "retry middleware skip")
|
// retry middleware skip
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
type termialAuthConversator struct{}
|
type terminalAuthConversator struct{}
|
||||||
|
|
||||||
func (t *termialAuthConversator) AskPhoneNumber() (string, error) {
|
func (t *terminalAuthConversator) AskPhoneNumber() (string, error) {
|
||||||
phone := ""
|
phone := ""
|
||||||
err := huh.NewInput().Title("Your Phone Number").
|
err := huh.NewInput().Title("Your Phone Number").
|
||||||
Placeholder("+44 123456").
|
Placeholder("+44 123456").
|
||||||
@@ -29,7 +29,7 @@ func (t *termialAuthConversator) AskPhoneNumber() (string, error) {
|
|||||||
return strings.TrimSpace(phone), nil
|
return strings.TrimSpace(phone), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *termialAuthConversator) AskCode() (string, error) {
|
func (t *terminalAuthConversator) AskCode() (string, error) {
|
||||||
code := ""
|
code := ""
|
||||||
err := huh.NewInput().Title("Your Code").
|
err := huh.NewInput().Title("Your Code").
|
||||||
Placeholder("123456").
|
Placeholder("123456").
|
||||||
@@ -45,7 +45,7 @@ func (t *termialAuthConversator) AskCode() (string, error) {
|
|||||||
return strings.TrimSpace(code), nil
|
return strings.TrimSpace(code), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *termialAuthConversator) AskPassword() (string, error) {
|
func (t *terminalAuthConversator) AskPassword() (string, error) {
|
||||||
pwd := ""
|
pwd := ""
|
||||||
|
|
||||||
err := huh.NewInput().Title("Your 2FA Password").
|
err := huh.NewInput().Title("Your 2FA Password").
|
||||||
@@ -61,7 +61,7 @@ func (t *termialAuthConversator) AskPassword() (string, error) {
|
|||||||
return strings.TrimSpace(pwd), nil
|
return strings.TrimSpace(pwd), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *termialAuthConversator) AuthStatus(authStatus gotgproto.AuthStatus) {
|
func (t *terminalAuthConversator) AuthStatus(authStatus gotgproto.AuthStatus) {
|
||||||
switch authStatus.Event {
|
switch authStatus.Event {
|
||||||
case gotgproto.AuthStatusPhoneRetrial:
|
case gotgproto.AuthStatusPhoneRetrial:
|
||||||
color.Red("The phone number you just entered seems to be incorrect,")
|
color.Red("The phone number you just entered seems to be incorrect,")
|
||||||
@@ -5,46 +5,82 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/celestix/gotgproto"
|
"github.com/celestix/gotgproto"
|
||||||
|
"github.com/celestix/gotgproto/dispatcher"
|
||||||
"github.com/celestix/gotgproto/ext"
|
"github.com/celestix/gotgproto/ext"
|
||||||
"github.com/celestix/gotgproto/sessionMaker"
|
"github.com/celestix/gotgproto/sessionMaker"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/gotd/td/telegram/dcs"
|
||||||
"github.com/krau/SaveAny-Bot/client/middleware"
|
"github.com/krau/SaveAny-Bot/client/middleware"
|
||||||
|
"github.com/krau/SaveAny-Bot/common/utils/netutil"
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/ncruces/go-sqlite3/gormlite"
|
"github.com/ncruces/go-sqlite3/gormlite"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var UC *gotgproto.Client
|
var uc *gotgproto.Client
|
||||||
var ectx *ext.Context
|
var ectx *ext.Context
|
||||||
|
|
||||||
func GetCtx() *ext.Context {
|
func GetCtx() *ext.Context {
|
||||||
|
if uc == nil {
|
||||||
|
panic("User client is not initialized, please call Login first")
|
||||||
|
}
|
||||||
if ectx != nil {
|
if ectx != nil {
|
||||||
// UC.RefreshContext(ectx)
|
|
||||||
return ectx
|
return ectx
|
||||||
}
|
}
|
||||||
ectx = UC.CreateContext()
|
ectx = uc.CreateContext()
|
||||||
return ectx
|
return ectx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetClient() *gotgproto.Client {
|
||||||
|
if uc == nil {
|
||||||
|
panic("User client is not initialized, please call Login first")
|
||||||
|
}
|
||||||
|
return uc
|
||||||
|
}
|
||||||
|
|
||||||
func Login(ctx context.Context) (*gotgproto.Client, error) {
|
func Login(ctx context.Context) (*gotgproto.Client, error) {
|
||||||
log.FromContext(ctx).Debug("Logging in as user client")
|
log.FromContext(ctx).Debug("Logging in user client")
|
||||||
if UC != nil {
|
if uc != nil {
|
||||||
return UC, nil
|
return uc, nil
|
||||||
}
|
}
|
||||||
res := make(chan struct {
|
res := make(chan struct {
|
||||||
client *gotgproto.Client
|
client *gotgproto.Client
|
||||||
err error
|
err error
|
||||||
})
|
})
|
||||||
go func() {
|
go func() {
|
||||||
|
var resolver dcs.Resolver
|
||||||
|
if config.Cfg.Telegram.Proxy.Enable && config.Cfg.Telegram.Proxy.URL != "" {
|
||||||
|
dialer, err := netutil.NewProxyDialer(config.Cfg.Telegram.Proxy.URL)
|
||||||
|
if err != nil {
|
||||||
|
res <- struct {
|
||||||
|
client *gotgproto.Client
|
||||||
|
err error
|
||||||
|
}{nil, err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolver = dcs.Plain(dcs.PlainOptions{
|
||||||
|
Dial: dialer.(proxy.ContextDialer).DialContext,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolver = dcs.DefaultResolver()
|
||||||
|
}
|
||||||
tclient, err := gotgproto.NewClient(
|
tclient, err := gotgproto.NewClient(
|
||||||
config.Cfg.Telegram.AppID,
|
config.Cfg.Telegram.AppID,
|
||||||
config.Cfg.Telegram.AppHash,
|
config.Cfg.Telegram.AppHash,
|
||||||
gotgproto.ClientTypePhone(""),
|
gotgproto.ClientTypePhone(""),
|
||||||
&gotgproto.ClientOpts{
|
&gotgproto.ClientOpts{
|
||||||
Session: sessionMaker.SqlSession(gormlite.Open(config.Cfg.Telegram.Userbot.Session)),
|
Session: sessionMaker.SqlSession(gormlite.Open(config.Cfg.Telegram.Userbot.Session)),
|
||||||
AuthConversator: &termialAuthConversator{},
|
AuthConversator: &terminalAuthConversator{},
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
DisableCopyright: true,
|
DisableCopyright: true,
|
||||||
|
Resolver: resolver,
|
||||||
|
MaxRetries: config.Cfg.Telegram.RpcRetry,
|
||||||
|
AutoFetchReply: true,
|
||||||
Middlewares: middleware.NewDefaultMiddlewares(ctx, 5*time.Minute),
|
Middlewares: middleware.NewDefaultMiddlewares(ctx, 5*time.Minute),
|
||||||
|
ErrorHandler: func(ctx *ext.Context, u *ext.Update, s string) error {
|
||||||
|
log.FromContext(ctx).Errorf("Unhandled error: %s", s)
|
||||||
|
return dispatcher.EndGroups
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,7 +105,8 @@ func Login(ctx context.Context) (*gotgproto.Client, error) {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
UC = r.client
|
uc = r.client
|
||||||
return UC, nil
|
log.FromContext(ctx).Infof("User client logged in successfully: %s", uc.Self.FirstName+" "+uc.Self.LastName)
|
||||||
|
return uc, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,16 +51,14 @@ func initAll(ctx context.Context) {
|
|||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
i18n.Init(config.Cfg.Lang)
|
i18n.Init(config.Cfg.Lang)
|
||||||
logger.Info(i18n.T(i18nk.Initing))
|
logger.Info(i18n.T(i18nk.Initing))
|
||||||
|
database.Init(ctx)
|
||||||
|
storage.LoadStorages(ctx)
|
||||||
if config.Cfg.Telegram.Userbot.Enable {
|
if config.Cfg.Telegram.Userbot.Enable {
|
||||||
uc, err := userclient.Login(ctx)
|
_, err := userclient.Login(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("User client login failed: %s", err)
|
logger.Fatalf("User client login failed: %s", err)
|
||||||
}
|
}
|
||||||
logger.Infof("User client logged in as %s", uc.Self.FirstName)
|
|
||||||
}
|
}
|
||||||
database.Init(ctx)
|
|
||||||
storage.LoadStorages(ctx)
|
|
||||||
|
|
||||||
bot.Init(ctx)
|
bot.Init(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
common/utils/netutil/proxy.go
Normal file
15
common/utils/netutil/proxy.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package netutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewProxyDialer(proxyUrl string) (proxy.Dialer, error) {
|
||||||
|
url, err := url.Parse(proxyUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return proxy.FromURL(url, proxy.Direct)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/celestix/gotgproto/ext"
|
"github.com/celestix/gotgproto/ext"
|
||||||
|
"github.com/celestix/gotgproto/functions"
|
||||||
"github.com/duke-git/lancet/v2/maputil"
|
"github.com/duke-git/lancet/v2/maputil"
|
||||||
|
|
||||||
"github.com/duke-git/lancet/v2/mathutil"
|
"github.com/duke-git/lancet/v2/mathutil"
|
||||||
@@ -19,6 +20,9 @@ import (
|
|||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// generate a file name from the message content and media type
|
||||||
|
//
|
||||||
|
// it will never return an empty string
|
||||||
func GenFileNameFromMessage(message tg.Message) string {
|
func GenFileNameFromMessage(message tg.Message) string {
|
||||||
ext := func(media tg.MessageMediaClass) string {
|
ext := func(media tg.MessageMediaClass) string {
|
||||||
switch media := media.(type) {
|
switch media := media.(type) {
|
||||||
@@ -27,11 +31,11 @@ func GenFileNameFromMessage(message tg.Message) string {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
ext := mimetype.Lookup(doc.MimeType).Extension()
|
mmt := mimetype.Lookup(doc.MimeType)
|
||||||
if ext == "" {
|
if mmt == nil || mmt.Extension() == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return ext
|
return mmt.Extension()
|
||||||
case *tg.MessageMediaPhoto:
|
case *tg.MessageMediaPhoto:
|
||||||
return ".jpg"
|
return ".jpg"
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,13 @@ func GenFileNameFromMessage(message tg.Message) string {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
filename = fmt.Sprintf("%d_%s", message.GetID(), xid.New().String())
|
mname, err := functions.GetMediaFileNameWithId(message.Media)
|
||||||
|
if err != nil {
|
||||||
|
filename = fmt.Sprintf("%d_%s", message.GetID(), xid.New().String())
|
||||||
|
} else {
|
||||||
|
filename = mname
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return filename + ext
|
return filename + ext
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,34 @@
|
|||||||
#创建文件时,若需要保留中文注释,请务必确保本文件编码为 UTF-8 ,否则会无法读取。
|
# 创建文件时,若需要保留中文注释,请务必确保本文件编码为 UTF-8 ,否则会无法读取。
|
||||||
workers = 4 # 同时下载文件数
|
# 更详细的配置请在 https://sabot.unv.app/deployment/configuration 查看
|
||||||
retry = 3 # 下载失败重试次数
|
workers = 4 # 同时下载文件数
|
||||||
threads = 4 # 单个任务下载最大线程数
|
retry = 3 # 下载失败重试次数
|
||||||
stream = false # 使用stream模式, 详情请查看文档
|
threads = 4 # 单个任务下载使用的最大线程数
|
||||||
|
stream = false # 使用流式传输模式, 建议仅在硬盘空间十分有限时使用.
|
||||||
|
|
||||||
[telegram]
|
[telegram]
|
||||||
# Bot Token
|
# Bot Token
|
||||||
# 更换 Bot Token 后请删除数据库文件和 session.db
|
# 更换 Bot Token 后请删除会话数据库文件 (默认路径为 data/session.db )
|
||||||
token = ""
|
token = ""
|
||||||
# Telegram API 配置, 若不配置也可运行, 将使用默认的 API ID 和 API HASH
|
# Telegram API 配置, 若不配置也可运行, 将使用默认的 API ID 和 API HASH
|
||||||
# 推荐使用自己的 API ID 和 API HASH (https://my.telegram.org)
|
# 推荐使用自己的 API ID 和 API HASH (https://my.telegram.org)
|
||||||
# app_id = 1025907
|
# app_id = 1025907
|
||||||
# app_hash = "452b0359b988148995f22ff0f4229750"
|
# app_hash = "452b0359b988148995f22ff0f4229750"
|
||||||
|
|
||||||
# 初始化超时时间, 单位: 秒
|
|
||||||
timeout = 60
|
|
||||||
# flood_retry = 5
|
|
||||||
# rpc_retry = 5
|
|
||||||
|
|
||||||
[telegram.proxy]
|
[telegram.proxy]
|
||||||
# 启用代理连接 telegram, 只支持 socks5
|
# 启用代理连接 telegram, 只支持 socks5
|
||||||
enable = false
|
enable = false
|
||||||
url = "socks5://127.0.0.1:7890"
|
url = "socks5://127.0.0.1:7890"
|
||||||
|
|
||||||
# 用户列表
|
|
||||||
[[users]]
|
|
||||||
# telegram user id
|
|
||||||
id = 114514
|
|
||||||
# 使用黑名单模式,开启后下方留空以使用所有存储,反之则为白名单,白名单请在下方输入允许的存储名
|
|
||||||
blacklist = true
|
|
||||||
# 将列表留空并开启黑名单模式以允许使用所有存储,此处示例为黑名单模式,用户 114514 可使用所有存储
|
|
||||||
storages = []
|
|
||||||
|
|
||||||
[[users]]
|
|
||||||
id = 123456
|
|
||||||
blacklist = false # 使用白名单模式,此时,用户123456 仅可使用下方列表中的存储
|
|
||||||
# 此时该用户只能使用名为 本机1 的存储
|
|
||||||
storages = ["本机1"]
|
|
||||||
|
|
||||||
# 存储列表
|
# 存储列表
|
||||||
[[storages]]
|
[[storages]]
|
||||||
# 标识名, 需要唯一
|
# 标识名, 需要唯一
|
||||||
name = "本机1"
|
name = "本机1"
|
||||||
# 存储类型, 目前可用: local, alist, webdav, minio
|
# 存储类型, 目前可用: local, alist, webdav, minio, telegram
|
||||||
type = "local"
|
type = "local"
|
||||||
# 启用存储
|
# 启用存储
|
||||||
enable = true
|
enable = true
|
||||||
# 文件保存根路径
|
# 文件保存根路径
|
||||||
base_path = "./downloads"
|
base_path = "./downloads"
|
||||||
|
|
||||||
[[storages]]
|
|
||||||
name = "MyAlist"
|
|
||||||
type = "alist"
|
|
||||||
enable = false #记得启用
|
|
||||||
base_path = '/'
|
|
||||||
url = 'https://alist.com'
|
|
||||||
username = 'admin'
|
|
||||||
password = 'password'
|
|
||||||
# alist token 刷新时间
|
|
||||||
# 86400--1天 604800--7天 1296000--15天 2592000--30天 15552000--180天
|
|
||||||
token_exp = 86400
|
|
||||||
# alist 可直接使用 token 登录, 此时 username, password, token_exp 将被忽略
|
|
||||||
# 请自行在 alist 侧配置合理的 token 过期时间
|
|
||||||
# token = ""
|
|
||||||
|
|
||||||
[[storages]]
|
[[storages]]
|
||||||
name = "MyWebdav"
|
name = "MyWebdav"
|
||||||
type = "webdav"
|
type = "webdav"
|
||||||
@@ -73,28 +38,17 @@ url = 'https://example.com/dav'
|
|||||||
username = 'username'
|
username = 'username'
|
||||||
password = 'password'
|
password = 'password'
|
||||||
|
|
||||||
[[storages]]
|
# 用户列表
|
||||||
name = "MyMinio"
|
[[users]]
|
||||||
type = "minio"
|
# telegram user id
|
||||||
enable = true
|
id = 114514
|
||||||
endpoint = 'play.min.io'
|
# 存储过滤列表, 元素为存储标识名.
|
||||||
use_ssl = true
|
# 将该列表留空并开启黑名单过滤模式以允许使用所有存储,此处示例为黑名单模式,用户 114514 可使用所有存储
|
||||||
access_key_id = 'Q3AM3UQ867SPQQA43P2F'
|
storages = []
|
||||||
secret_access_key = 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG'
|
# 使用列表过滤黑名单模式,反之则为白名单,白名单请在列表中指定可用的存储.
|
||||||
bucket_name = 'saveanybot'
|
blacklist = true
|
||||||
base_path = '/path/telegram'
|
|
||||||
|
|
||||||
[[storages]]
|
|
||||||
name = "mychannel"
|
|
||||||
type = "telegram"
|
|
||||||
enable = true
|
|
||||||
chat_id = 1820371480
|
|
||||||
|
|
||||||
# [temp]
|
|
||||||
# # 下载文件临时目录, 请不要在此目录下存放任何其他文件
|
|
||||||
# base_path = "cache/"
|
|
||||||
|
|
||||||
# [db]
|
|
||||||
# path = "data/data.db" # 数据库文件路径
|
|
||||||
# session = "data/session.db"
|
|
||||||
|
|
||||||
|
[[users]]
|
||||||
|
id = 123456
|
||||||
|
storages = ["本机1"]
|
||||||
|
blacklist = false # 使用白名单模式,此时,用户 123456 仅可使用标识名为 '本地1' 的存储
|
||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/duke-git/lancet/v2/retry"
|
"github.com/duke-git/lancet/v2/retry"
|
||||||
"github.com/krau/SaveAny-Bot/common/tdler"
|
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/fsutil"
|
"github.com/krau/SaveAny-Bot/common/utils/fsutil"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/ioutil"
|
"github.com/krau/SaveAny-Bot/common/utils/ioutil"
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,10 +61,12 @@ func (t *Task) processElement(ctx context.Context, elem TaskElement) error {
|
|||||||
t.Progress.OnProgress(ctx, t)
|
t.Progress.OnProgress(ctx, t)
|
||||||
})
|
})
|
||||||
errg.Go(func() error {
|
errg.Go(func() error {
|
||||||
|
defer pw.Close()
|
||||||
logger.Info("Starting file download in stream mode")
|
logger.Info("Starting file download in stream mode")
|
||||||
_, err := tdler.NewDownloader(t.client, elem.File).Stream(uploadCtx, wr)
|
_, err := tfile.NewDownloader(elem.File).Stream(uploadCtx, wr)
|
||||||
if closeErr := pw.CloseWithError(err); closeErr != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to close pipe writer: %v", closeErr)
|
logger.Errorf("Failed to download file: %v", err)
|
||||||
|
pw.CloseWithError(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -88,7 +90,7 @@ func (t *Task) processElement(ctx context.Context, elem TaskElement) error {
|
|||||||
t.downloaded.Add(int64(n))
|
t.downloaded.Add(int64(n))
|
||||||
t.Progress.OnProgress(ctx, t)
|
t.Progress.OnProgress(ctx, t)
|
||||||
})
|
})
|
||||||
_, err = tdler.NewDownloader(t.client, elem.File).Parallel(ctx, wrAt)
|
_, err = tfile.NewDownloader(elem.File).Parallel(ctx, wrAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download file: %w", err)
|
return fmt.Errorf("failed to download file: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/krau/SaveAny-Bot/common/tdler"
|
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
@@ -30,7 +29,6 @@ type Task struct {
|
|||||||
Progress ProgressTracker
|
Progress ProgressTracker
|
||||||
IgnoreErrors bool // if true, errors during processing will be ignored
|
IgnoreErrors bool // if true, errors during processing will be ignored
|
||||||
downloaded atomic.Int64
|
downloaded atomic.Int64
|
||||||
client tdler.Client
|
|
||||||
totalSize int64
|
totalSize int64
|
||||||
processing map[string]TaskElementInfo
|
processing map[string]TaskElementInfo
|
||||||
failed map[string]error // errors for each element
|
failed map[string]error // errors for each element
|
||||||
@@ -73,14 +71,12 @@ func NewBatchTGFileTask(
|
|||||||
id string,
|
id string,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
files []TaskElement,
|
files []TaskElement,
|
||||||
client tdler.Client,
|
|
||||||
progress ProgressTracker,
|
progress ProgressTracker,
|
||||||
ignoreErrors bool,
|
ignoreErrors bool,
|
||||||
) *Task {
|
) *Task {
|
||||||
task := &Task{
|
task := &Task{
|
||||||
ID: id,
|
ID: id,
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
client: client,
|
|
||||||
Elems: files,
|
Elems: files,
|
||||||
Progress: progress,
|
Progress: progress,
|
||||||
downloaded: atomic.Int64{},
|
downloaded: atomic.Int64{},
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/krau/SaveAny-Bot/common/tdler"
|
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/fsutil"
|
"github.com/krau/SaveAny-Bot/common/utils/fsutil"
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
"github.com/krau/SaveAny-Bot/pkg/enums/ctxkey"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Task) Execute(ctx context.Context) error {
|
func (t *Task) Execute(ctx context.Context) error {
|
||||||
@@ -36,7 +36,7 @@ func (t *Task) Execute(ctx context.Context) error {
|
|||||||
defer func() {
|
defer func() {
|
||||||
t.Progress.OnDone(ctx, t, err)
|
t.Progress.OnDone(ctx, t, err)
|
||||||
}()
|
}()
|
||||||
_, err = tdler.NewDownloader(t.client, t.File).Parallel(ctx, wrAt)
|
_, err = tfile.NewDownloader(t.File).Parallel(ctx, wrAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download file: %w", err)
|
return fmt.Errorf("failed to download file: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/krau/SaveAny-Bot/common/tdler"
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,10 +21,12 @@ func executeStream(ctx context.Context, task *Task) error {
|
|||||||
})
|
})
|
||||||
wr := newWriter(ctx, pw, task.Progress, task)
|
wr := newWriter(ctx, pw, task.Progress, task)
|
||||||
errg.Go(func() error {
|
errg.Go(func() error {
|
||||||
|
defer pw.Close()
|
||||||
logger.Info("Starting file download in stream mode")
|
logger.Info("Starting file download in stream mode")
|
||||||
_, err := tdler.NewDownloader(task.client, task.File).Stream(uploadCtx, wr)
|
_, err := tfile.NewDownloader(task.File).Stream(uploadCtx, wr)
|
||||||
if closeErr := pw.CloseWithError(err); closeErr != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to close pipe writer: %v", closeErr)
|
logger.Errorf("Failed to download file: %v", err)
|
||||||
|
pw.CloseWithError(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/krau/SaveAny-Bot/common/tdler"
|
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
"github.com/krau/SaveAny-Bot/pkg/enums/tasktype"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
@@ -19,7 +18,6 @@ type Task struct {
|
|||||||
Storage storage.Storage
|
Storage storage.Storage
|
||||||
Path string
|
Path string
|
||||||
Progress ProgressTracker
|
Progress ProgressTracker
|
||||||
client tdler.Client
|
|
||||||
stream bool // true if the file should be downloaded in stream mode
|
stream bool // true if the file should be downloaded in stream mode
|
||||||
localPath string
|
localPath string
|
||||||
}
|
}
|
||||||
@@ -32,7 +30,6 @@ func NewTGFileTask(
|
|||||||
id string,
|
id string,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
file tfile.TGFile,
|
file tfile.TGFile,
|
||||||
client tdler.Client,
|
|
||||||
stor storage.Storage,
|
stor storage.Storage,
|
||||||
path string,
|
path string,
|
||||||
progress ProgressTracker,
|
progress ProgressTracker,
|
||||||
@@ -46,7 +43,6 @@ func NewTGFileTask(
|
|||||||
tftask := &Task{
|
tftask := &Task{
|
||||||
ID: id,
|
ID: id,
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
client: client,
|
|
||||||
File: file,
|
File: file,
|
||||||
Storage: stor,
|
Storage: stor,
|
||||||
Path: path,
|
Path: path,
|
||||||
@@ -58,7 +54,6 @@ func NewTGFileTask(
|
|||||||
tfileTask := &Task{
|
tfileTask := &Task{
|
||||||
ID: id,
|
ID: id,
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
client: client,
|
|
||||||
File: file,
|
File: file,
|
||||||
Storage: stor,
|
Storage: stor,
|
||||||
Path: path,
|
Path: path,
|
||||||
|
|||||||
@@ -51,9 +51,20 @@ Stream 模式对于磁盘空间有限的部署环境十分有用, 但也有一
|
|||||||
- `app_id`, `app_hash`: Telegram API ID & Hash, 在 [Telegram API](https://my.telegram.org/apps) 创建应用获取, 若不提供则使用默认值.
|
- `app_id`, `app_hash`: Telegram API ID & Hash, 在 [Telegram API](https://my.telegram.org/apps) 创建应用获取, 若不提供则使用默认值.
|
||||||
- `flood_retry`: Flood 控制重试次数, 默认为 5.
|
- `flood_retry`: Flood 控制重试次数, 默认为 5.
|
||||||
- `rpc_retry`: RPC 请求重试次数, 默认为 5.
|
- `rpc_retry`: RPC 请求重试次数, 默认为 5.
|
||||||
- `proxy`: 代理配置, 可选项.
|
- `proxy`: 代理配置, 可选.
|
||||||
- `enable`: 是否启用代理.
|
- `enable`: 是否启用代理.
|
||||||
- `url`: 代理地址, 只支持 `socks5://`
|
- `url`: 代理地址, 只支持 `socks5://`
|
||||||
|
- `userbot`: userbot 配置, 可选.
|
||||||
|
- `enable`: 启用 userbot 集成, 需要登录用户账号, 此时请务必使用自己的 api id & hash.
|
||||||
|
- `session`: userbot 会话文件路径, 默认为 `data/usersession.db`.
|
||||||
|
|
||||||
|
{{< hint warning >}}
|
||||||
|
启用 userbot 集成后, bot 可以下载私密频道和群组的文件, 但具有无法避免的账号被封禁的风险.
|
||||||
|
<br />
|
||||||
|
并且, 由于上游依赖问题, 该功能不稳定, 会出现获取文件失败的情况.
|
||||||
|
<br />
|
||||||
|
开启 userbot 集成后第一次启动 bot 时需要通过终端交互输入手机号, 2FA 和验证码, 如果你使用 docker 部署, 请进入容器内执行相关操作.
|
||||||
|
{{< /hint >}}
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[telegram]
|
[telegram]
|
||||||
@@ -65,6 +76,9 @@ rpc_retry = 5
|
|||||||
[telegram.proxy]
|
[telegram.proxy]
|
||||||
enable = false
|
enable = false
|
||||||
url = "socks5://127.0.0.1:7890"
|
url = "socks5://127.0.0.1:7890"
|
||||||
|
[telegram.userbot]
|
||||||
|
enable = false
|
||||||
|
session = "data/usersession.db"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 存储端列表
|
### 存储端列表
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ Bot 接受两种消息: 文件和链接.
|
|||||||
|
|
||||||
在开启静默模式之前, 需要使用 `/storage` 命令设置默认保存位置.
|
在开启静默模式之前, 需要使用 `/storage` 命令设置默认保存位置.
|
||||||
|
|
||||||
|
|
||||||
## 存储规则
|
## 存储规则
|
||||||
|
|
||||||
允许你为 Bot 在上传文件到存储时设置一些重定向规则, 用于自动整理所保存的文件.
|
允许你为 Bot 在上传文件到存储时设置一些重定向规则, 用于自动整理所保存的文件.
|
||||||
@@ -37,6 +36,7 @@ Bot 接受两种消息: 文件和链接.
|
|||||||
|
|
||||||
1. FILENAME-REGEX
|
1. FILENAME-REGEX
|
||||||
2. MESSAGE-REGEX
|
2. MESSAGE-REGEX
|
||||||
|
3. IS-ALBUM
|
||||||
|
|
||||||
添加规则的基本语法:
|
添加规则的基本语法:
|
||||||
|
|
||||||
@@ -65,3 +65,17 @@ FILENAME-REGEX (?i)\.(mp4|mkv|ts|avi|flv)$ MyAlist /视频
|
|||||||
### MESSAGE-REGEX
|
### 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 的存储下, 并为每个相册新建一个文件夹(由第一个文件生成)来存储它们.
|
||||||
18
entrypoint.sh
Normal file
18
entrypoint.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ -n "$CONFIG_URL" ]; then
|
||||||
|
echo "[INFO] Downloading config from $CONFIG_URL"
|
||||||
|
if curl -sSLo /app/config.toml "$CONFIG_URL"; then
|
||||||
|
echo "[INFO] Configuration downloaded successfully"
|
||||||
|
else
|
||||||
|
echo "[ERROR] Failed to download config from $CONFIG_URL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /app/config.toml ]; then
|
||||||
|
echo "[ERROR] Missing config.toml: 请通过挂载或 CONFIG_URL 提供配置文件"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /app/saveany-bot
|
||||||
62
go.mod
62
go.mod
@@ -12,29 +12,29 @@ require (
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.9
|
github.com/gabriel-vasile/mimetype v1.4.9
|
||||||
github.com/go-faster/errors v0.7.1
|
github.com/go-faster/errors v0.7.1
|
||||||
github.com/gotd/contrib v0.21.0
|
github.com/gotd/contrib v0.21.0
|
||||||
github.com/gotd/td v0.125.0
|
github.com/gotd/td v0.129.0
|
||||||
github.com/minio/minio-go/v7 v7.0.92
|
github.com/minio/minio-go/v7 v7.0.95
|
||||||
github.com/rhysd/go-github-selfupdate v1.2.3
|
github.com/rhysd/go-github-selfupdate v1.2.3
|
||||||
github.com/rs/xid v1.6.0
|
github.com/rs/xid v1.6.0
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
golang.org/x/net v0.41.0
|
golang.org/x/net v0.42.0
|
||||||
golang.org/x/time v0.12.0
|
golang.org/x/time v0.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AnimeKaizoku/cacher v1.0.2 // indirect
|
github.com/AnimeKaizoku/cacher v1.0.3 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/catppuccin/go v0.3.0 // indirect
|
github.com/catppuccin/go v0.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/bubbles v0.21.0 // indirect
|
github.com/charmbracelet/bubbles v0.21.0 // indirect
|
||||||
github.com/charmbracelet/bubbletea v1.3.4 // indirect
|
github.com/charmbracelet/bubbletea v1.3.6 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.3.1 // indirect
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
github.com/charmbracelet/x/exp/strings v0.0.0-20250725211024-d60e1b0112b2 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/coder/websocket v1.8.13 // indirect
|
github.com/coder/websocket v1.8.13 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
@@ -47,7 +47,7 @@ require (
|
|||||||
github.com/go-faster/yaml v0.4.6 // indirect
|
github.com/go-faster/yaml v0.4.6 // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
@@ -57,13 +57,13 @@ require (
|
|||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
github.com/minio/crc64nvme v1.1.0 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
@@ -73,7 +73,7 @@ require (
|
|||||||
github.com/ncruces/julianday v1.0.0 // indirect
|
github.com/ncruces/julianday v1.0.0 // indirect
|
||||||
github.com/ogen-go/ogen v1.14.0 // indirect
|
github.com/ogen-go/ogen v1.14.0 // indirect
|
||||||
github.com/onsi/gomega v1.36.2 // indirect
|
github.com/onsi/gomega v1.36.2 // indirect
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
@@ -82,48 +82,50 @@ require (
|
|||||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||||
github.com/tinylib/msgp v1.3.0 // indirect
|
github.com/tinylib/msgp v1.3.0 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
golang.org/x/mod v0.25.0 // indirect
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
golang.org/x/oauth2 v0.30.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.org/x/tools v0.34.0 // indirect
|
golang.org/x/tools v0.35.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
modernc.org/libc v1.65.10 // indirect
|
modernc.org/libc v1.66.6 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.38.0 // indirect
|
modernc.org/sqlite v1.38.2 // indirect
|
||||||
rsc.io/qr v0.2.0 // indirect
|
rsc.io/qr v0.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgraph-io/ristretto/v2 v2.2.0
|
github.com/dgraph-io/ristretto/v2 v2.2.0
|
||||||
github.com/duke-git/lancet/v2 v2.3.6
|
github.com/duke-git/lancet/v2 v2.3.7
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/ncruces/go-sqlite3 v0.26.1
|
github.com/ncruces/go-sqlite3 v0.27.1
|
||||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0
|
github.com/ncruces/go-sqlite3/gormlite v0.24.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
github.com/sagikazarmark/locafero v0.10.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
github.com/spf13/afero v1.14.0 // indirect
|
||||||
github.com/spf13/cast v1.9.2 // indirect
|
github.com/spf13/cast v1.9.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.7 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||||
golang.org/x/sync v0.15.0
|
golang.org/x/sync v0.16.0
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
golang.org/x/text v0.26.0
|
golang.org/x/text v0.27.0
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gorm.io/gorm v1.30.0
|
gorm.io/gorm v1.30.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/celestix/gotgproto v1.0.0-beta21 => github.com/krau/gotgproto v0.0.0-20250730080659-caaadb4b1f35
|
||||||
|
|||||||
123
go.sum
123
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/AnimeKaizoku/cacher v1.0.2 h1:7Bf5qRylWb7q2Evib0OXlhG37/t7BP2HK/7IyPvSmGQ=
|
github.com/AnimeKaizoku/cacher v1.0.3-0.20250508132714-ddc7471efeef h1:y8llZexgesBazUg/zdIlrZKHPFJ2zk9YxlLAJ5x4DpQ=
|
||||||
github.com/AnimeKaizoku/cacher v1.0.2/go.mod h1:jw0de/b0K6W7Y3T9rHCMGVKUf6oG7hENNcssxYcZTCc=
|
github.com/AnimeKaizoku/cacher v1.0.3-0.20250508132714-ddc7471efeef/go.mod h1:jw0de/b0K6W7Y3T9rHCMGVKUf6oG7hENNcssxYcZTCc=
|
||||||
|
github.com/AnimeKaizoku/cacher v1.0.3 h1:foNAmLfY/DXfA4yEy4uP6WK2Ni7JC+s3QhZv72Dn6zs=
|
||||||
|
github.com/AnimeKaizoku/cacher v1.0.3/go.mod h1:jw0de/b0K6W7Y3T9rHCMGVKUf6oG7hENNcssxYcZTCc=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||||
@@ -14,26 +16,26 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
|
|||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||||
github.com/celestix/gotgproto v1.0.0-beta21 h1:VUuAC/Kj5Sdu/WZan3ZUb0GFNAavFxMYxmHAhCBX0J8=
|
|
||||||
github.com/celestix/gotgproto v1.0.0-beta21/go.mod h1:viDkHe9rBegJoEE/jNuFfbBM0XZ3pSx/ugjaNaVnbvU=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||||
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
|
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
|
||||||
github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
|
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
|
||||||
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
|
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
|
||||||
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
|
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
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/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 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
||||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||||
@@ -42,8 +44,10 @@ github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9
|
|||||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
github.com/charmbracelet/x/exp/strings v0.0.0-20250629123816-066ae234febc h1:XFsX2G2Z1k1p9/52+7TYs2iYW//XCJXSD7xWlEeGvBM=
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
github.com/charmbracelet/x/exp/strings v0.0.0-20250629123816-066ae234febc/go.mod h1:Rgw3/F+xlcUc5XygUtimVSxAqCOsqyvJjqF5UHRvc5k=
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20250725211024-d60e1b0112b2 h1:mI6RFtm+NvDgzRhAL1GEFeOqaJkG+9gBvEnk55uJHKc=
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20250725211024-d60e1b0112b2/go.mod h1:Rgw3/F+xlcUc5XygUtimVSxAqCOsqyvJjqF5UHRvc5k=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||||
@@ -65,6 +69,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
|
|||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/duke-git/lancet/v2 v2.3.6 h1:NKxSSh+dlgp37funvxLCf3xLBeUYa7VW1thYQP6j3Y8=
|
github.com/duke-git/lancet/v2 v2.3.6 h1:NKxSSh+dlgp37funvxLCf3xLBeUYa7VW1thYQP6j3Y8=
|
||||||
github.com/duke-git/lancet/v2 v2.3.6/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
github.com/duke-git/lancet/v2 v2.3.6/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||||
|
github.com/duke-git/lancet/v2 v2.3.7 h1:nnNBA9KyoqwbPm4nFmEFVIbXeAmpqf6IDCH45+HHHNs=
|
||||||
|
github.com/duke-git/lancet/v2 v2.3.7/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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
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 h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
@@ -97,12 +103,14 @@ 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-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@@ -125,8 +133,10 @@ github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk=
|
|||||||
github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
|
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 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
|
||||||
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
||||||
github.com/gotd/td v0.125.0 h1:XGygdCC37887z4MK39tq6lgq82XvkDtdF7WaLptSS3g=
|
github.com/gotd/td v0.127.0 h1:81Gs9AM8zgA1PE1/rUHAZtY/aW3aTGbZAAQw/ztKO3E=
|
||||||
github.com/gotd/td v0.125.0/go.mod h1:7BKKxCD1m3XXsryHXt5OzoufvhK5gRW5cgehYyUw8o0=
|
github.com/gotd/td v0.127.0/go.mod h1:QsMlkwf9QmV5Oe+td8ykWHxPPxGU8l7Jb1M5oZ1B73Q=
|
||||||
|
github.com/gotd/td v0.129.0 h1:8arlrzBK6qXjMCz1ltBVMCN/Nrc0negTq9mmIQnHyxA=
|
||||||
|
github.com/gotd/td v0.129.0/go.mod h1:t9A85Tp/ujnYZwAgBM+hCoVAEagciAZxLBhoDsP7Yno=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
|
||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
|
||||||
@@ -139,8 +149,10 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
|
|||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -149,6 +161,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/krau/gotgproto v0.0.0-20250730080659-caaadb4b1f35 h1:CVzeQNKRgkXm8DQX5bszxI73RcOVGTOFbMynhgjA2lQ=
|
||||||
|
github.com/krau/gotgproto v0.0.0-20250730080659-caaadb4b1f35/go.mod h1:xjZlGA8ABRKkfGMmkHKyz520hK6pMfyE8yxpSTqohME=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.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 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
@@ -161,10 +175,14 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
|||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||||
|
github.com/minio/crc64nvme v1.1.0 h1:e/tAguZ+4cw32D+IO/8GSf5UVr9y+3eJcxZI2WOO/7Q=
|
||||||
|
github.com/minio/crc64nvme v1.1.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
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/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw=
|
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
|
||||||
github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0=
|
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
@@ -175,8 +193,10 @@ 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/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 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/ncruces/go-sqlite3 v0.26.1 h1:lBXmbmucH1Bsj57NUQR6T84UoMN7jnNImhF+ibEITJU=
|
github.com/ncruces/go-sqlite3 v0.26.2 h1:5UkIBwdfMN2irpVI1dgi9TjTUlxNI06Rti1C8O7ZKVg=
|
||||||
github.com/ncruces/go-sqlite3 v0.26.1/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
github.com/ncruces/go-sqlite3 v0.26.2/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||||
|
github.com/ncruces/go-sqlite3 v0.27.1 h1:suqlM7xhSyDVMV9RgX99MCPqt9mB6YOCzHZuiI36K34=
|
||||||
|
github.com/ncruces/go-sqlite3 v0.27.1/go.mod h1:gpF5s+92aw2MbDmZK0ZOnCdFlpe11BH20CTspVqri0c=
|
||||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0 h1:81sHeq3CCdhjoqAB650n5wEdRlLO9VBvosArskcN3+c=
|
github.com/ncruces/go-sqlite3/gormlite v0.24.0 h1:81sHeq3CCdhjoqAB650n5wEdRlLO9VBvosArskcN3+c=
|
||||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0/go.mod h1:vXfVWdBfg7qOgqQqHpzUWl9LLswD0h+8mK4oouaV2oc=
|
github.com/ncruces/go-sqlite3/gormlite v0.24.0/go.mod h1:vXfVWdBfg7qOgqQqHpzUWl9LLswD0h+8mK4oouaV2oc=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
@@ -193,8 +213,8 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
|||||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
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/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -213,10 +233,14 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||||
|
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
|
||||||
|
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||||
@@ -225,6 +249,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
|||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||||
|
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
@@ -244,12 +270,12 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
|||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
@@ -262,16 +288,24 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
@@ -280,6 +314,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -287,16 +323,22 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@@ -313,16 +355,23 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
|
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||||
|
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
|
||||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
||||||
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI=
|
||||||
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
|
modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.66.1 h1:4uQsntXbVyAgrV+j6NhKvDiUypoJL48BWQx6sy9y8ok=
|
||||||
|
modernc.org/libc v1.66.1/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo=
|
||||||
|
modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
|
||||||
|
modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
@@ -333,6 +382,8 @@ modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
|||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||||
|
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||||
|
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package consts
|
package consts
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RuleStorNameChosen = "CHOSEN"
|
RuleStorNameChosen = "CHOSEN"
|
||||||
|
RuleDirPathNewForAlbum = "NEW-FOR-ALBUM" // create a new directory for album files
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package tglimit
|
package tglimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gotd/td/telegram/uploader"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MaxPartSize = 1024 * 1024
|
MaxPartSize = 1024 * 1024
|
||||||
MaxUploadPartSize = 512 * 1024
|
MaxUploadPartSize = uploader.MaximumPartSize
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type RuleType string
|
|||||||
const (
|
const (
|
||||||
FileNameRegex RuleType = "FILENAME-REGEX"
|
FileNameRegex RuleType = "FILENAME-REGEX"
|
||||||
MessageRegex RuleType = "MESSAGE-REGEX"
|
MessageRegex RuleType = "MESSAGE-REGEX"
|
||||||
|
IsAlbum RuleType = "IS-ALBUM"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r RuleType) String() string {
|
func (r RuleType) String() string {
|
||||||
@@ -12,5 +13,5 @@ func (r RuleType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Values() []RuleType {
|
func Values() []RuleType {
|
||||||
return []RuleType{FileNameRegex, MessageRegex}
|
return []RuleType{FileNameRegex, MessageRegex, IsAlbum}
|
||||||
}
|
}
|
||||||
|
|||||||
38
pkg/rule/is_album.go
Normal file
38
pkg/rule/is_album.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
ruleenum "github.com/krau/SaveAny-Bot/pkg/enums/rule"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleClass[bool] = (*RuleMediaType)(nil)
|
||||||
|
|
||||||
|
type RuleMediaType struct {
|
||||||
|
storInfo
|
||||||
|
matchAlbum bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RuleMediaType) Type() ruleenum.RuleType {
|
||||||
|
return ruleenum.IsAlbum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RuleMediaType) Match(input bool) (bool, error) {
|
||||||
|
return r.matchAlbum == input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RuleMediaType) StorageName() string {
|
||||||
|
return r.storName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RuleMediaType) StoragePath() string {
|
||||||
|
return r.storPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRuleMediaType(storName, storPath string, matchAlbum bool) (*RuleMediaType, error) {
|
||||||
|
return &RuleMediaType{
|
||||||
|
storInfo: storInfo{
|
||||||
|
storName: storName,
|
||||||
|
storPath: storPath,
|
||||||
|
},
|
||||||
|
matchAlbum: matchAlbum,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
package tdler
|
package tfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gotd/td/telegram/downloader"
|
"github.com/gotd/td/telegram/downloader"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/dlutil"
|
"github.com/krau/SaveAny-Bot/common/utils/dlutil"
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/consts/tglimit"
|
"github.com/krau/SaveAny-Bot/pkg/consts/tglimit"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client interface {
|
type DlerClient interface {
|
||||||
downloader.Client
|
downloader.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDownloader(client Client, file tfile.TGFile) *downloader.Builder {
|
func NewDownloader(file TGFile) *downloader.Builder {
|
||||||
return downloader.NewDownloader().WithPartSize(tglimit.MaxPartSize).
|
return downloader.NewDownloader().WithPartSize(tglimit.MaxPartSize).
|
||||||
Download(client, file.Location()).WithThreads(dlutil.BestThreads(file.Size(), config.Cfg.Threads))
|
Download(file.Dler(), file.Location()).WithThreads(dlutil.BestThreads(file.Size(), config.Cfg.Threads))
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type TGFile interface {
|
type TGFile interface {
|
||||||
Location() tg.InputFileLocationClass
|
Location() tg.InputFileLocationClass
|
||||||
|
Dler() DlerClient // witch client to use for downloading
|
||||||
Size() int64
|
Size() int64
|
||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,7 @@ type tgFile struct {
|
|||||||
size int64
|
size int64
|
||||||
name string
|
name string
|
||||||
message *tg.Message
|
message *tg.Message
|
||||||
|
dler DlerClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *tgFile) Location() tg.InputFileLocationClass {
|
func (f *tgFile) Location() tg.InputFileLocationClass {
|
||||||
@@ -42,11 +44,20 @@ func (f *tgFile) Message() *tg.Message {
|
|||||||
return f.message
|
return f.message
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTGFile(location tg.InputFileLocationClass, size int64, name string,
|
func (f *tgFile) Dler() DlerClient {
|
||||||
|
return f.dler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTGFile(
|
||||||
|
location tg.InputFileLocationClass,
|
||||||
|
dler DlerClient,
|
||||||
|
size int64,
|
||||||
|
name string,
|
||||||
opts ...TGFileOptions,
|
opts ...TGFileOptions,
|
||||||
) TGFile {
|
) TGFile {
|
||||||
f := &tgFile{
|
f := &tgFile{
|
||||||
location: location,
|
location: location,
|
||||||
|
dler: dler,
|
||||||
size: size,
|
size: size,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
@@ -56,7 +67,7 @@ func NewTGFile(location tg.InputFileLocationClass, size int64, name string,
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromMedia(media tg.MessageMediaClass, opts ...TGFileOptions) (TGFile, error) {
|
func FromMedia(media tg.MessageMediaClass, client DlerClient, opts ...TGFileOptions) (TGFile, error) {
|
||||||
switch m := media.(type) {
|
switch m := media.(type) {
|
||||||
case *tg.MessageMediaDocument:
|
case *tg.MessageMediaDocument:
|
||||||
document, ok := m.Document.AsNotEmpty()
|
document, ok := m.Document.AsNotEmpty()
|
||||||
@@ -70,14 +81,13 @@ func FromMedia(media tg.MessageMediaClass, opts ...TGFileOptions) (TGFile, error
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file := &tgFile{
|
file := NewTGFile(
|
||||||
location: document.AsInputDocumentFileLocation(),
|
document.AsInputDocumentFileLocation(),
|
||||||
size: document.Size,
|
client,
|
||||||
name: fileName,
|
document.Size,
|
||||||
}
|
fileName,
|
||||||
for _, opt := range opts {
|
opts...,
|
||||||
opt(file)
|
)
|
||||||
}
|
|
||||||
return file, nil
|
return file, nil
|
||||||
case *tg.MessageMediaPhoto:
|
case *tg.MessageMediaPhoto:
|
||||||
photo, ok := m.Photo.AsNotEmpty()
|
photo, ok := m.Photo.AsNotEmpty()
|
||||||
@@ -99,26 +109,26 @@ func FromMedia(media tg.MessageMediaClass, opts ...TGFileOptions) (TGFile, error
|
|||||||
location.FileReference = photo.GetFileReference()
|
location.FileReference = photo.GetFileReference()
|
||||||
location.ThumbSize = size.GetType()
|
location.ThumbSize = size.GetType()
|
||||||
fileName := fmt.Sprintf("photo_%s_%d.jpg", time.Now().Format("2006-01-02_15-04-05"), photo.GetID())
|
fileName := fmt.Sprintf("photo_%s_%d.jpg", time.Now().Format("2006-01-02_15-04-05"), photo.GetID())
|
||||||
file := &tgFile{
|
file := NewTGFile(
|
||||||
location: location,
|
location,
|
||||||
size: 0,
|
client,
|
||||||
name: fileName,
|
0, // Photo size is not available in InputPhotoFileLocation
|
||||||
}
|
fileName,
|
||||||
for _, opt := range opts {
|
opts...,
|
||||||
opt(file)
|
)
|
||||||
}
|
|
||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unsupported media type: %T", media)
|
return nil, fmt.Errorf("unsupported media type: %T", media)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromMediaMessage(media tg.MessageMediaClass, msg *tg.Message, opts ...TGFileOptions) (TGFileMessage, error) {
|
func FromMediaMessage(media tg.MessageMediaClass, client DlerClient, msg *tg.Message, opts ...TGFileOptions) (TGFileMessage, error) {
|
||||||
file, err := FromMedia(media, opts...)
|
file, err := FromMedia(media, client, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &tgFile{
|
return &tgFile{
|
||||||
location: file.Location(),
|
location: file.Location(),
|
||||||
|
dler: file.Dler(),
|
||||||
size: file.Size(),
|
size: file.Size(),
|
||||||
name: file.Name(),
|
name: file.Name(),
|
||||||
message: msg,
|
message: msg,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Minio struct {
|
type Minio struct {
|
||||||
@@ -61,7 +62,7 @@ func (m *Minio) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Minio) JoinStoragePath(p string) string {
|
func (m *Minio) JoinStoragePath(p string) string {
|
||||||
return path.Join(m.config.BasePath, p)
|
return strings.TrimPrefix(path.Join(m.config.BasePath, p), "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Minio) Save(ctx context.Context, r io.Reader, storagePath string) error {
|
func (m *Minio) Save(ctx context.Context, r io.Reader, storagePath string) error {
|
||||||
@@ -72,6 +73,11 @@ func (m *Minio) Save(ctx context.Context, r io.Reader, storagePath string) error
|
|||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; m.Exists(ctx, candidate); i++ {
|
for i := 1; m.Exists(ctx, candidate); i++ {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
|
if i > 1000 {
|
||||||
|
m.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
||||||
|
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
size := int64(-1)
|
size := int64(-1)
|
||||||
if length := ctx.Value(ctxkey.ContentLength); length != nil {
|
if length := ctx.Value(ctxkey.ContentLength); length != nil {
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/convertor"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/gotd/td/telegram/message"
|
"github.com/gotd/td/telegram/message"
|
||||||
"github.com/gotd/td/telegram/message/styling"
|
"github.com/gotd/td/telegram/message/styling"
|
||||||
@@ -70,9 +73,17 @@ func (t *Telegram) Save(ctx context.Context, r io.Reader, storagePath string) er
|
|||||||
if tctx == nil {
|
if tctx == nil {
|
||||||
return fmt.Errorf("failed to get telegram context")
|
return fmt.Errorf("failed to get telegram context")
|
||||||
}
|
}
|
||||||
peer := tctx.PeerStorage.GetInputPeerById(t.config.ChatID)
|
chatID := t.config.ChatID
|
||||||
|
if after, ok0 := strings.CutPrefix(convertor.ToString(chatID), "-100"); ok0 {
|
||||||
|
cid, err := strconv.ParseInt(after, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse chat ID: %w", err)
|
||||||
|
}
|
||||||
|
chatID = cid
|
||||||
|
}
|
||||||
|
peer := tctx.PeerStorage.GetInputPeerById(chatID)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
return fmt.Errorf("failed to get input peer for chat ID %d", t.config.ChatID)
|
return fmt.Errorf("failed to get input peer for chat ID %d", chatID)
|
||||||
}
|
}
|
||||||
mtype, err := mimetype.DetectReader(rs)
|
mtype, err := mimetype.DetectReader(rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -97,12 +108,11 @@ func (t *Telegram) Save(ctx context.Context, r io.Reader, storagePath string) er
|
|||||||
caption := styling.Plain(filename)
|
caption := styling.Plain(filename)
|
||||||
docb := message.UploadedDocument(file, caption).
|
docb := message.UploadedDocument(file, caption).
|
||||||
Filename(filename).
|
Filename(filename).
|
||||||
ForceFile(true).
|
ForceFile(false).
|
||||||
MIME(mtype.String())
|
MIME(mtype.String())
|
||||||
|
|
||||||
var mediaOpt message.MediaOption = docb
|
|
||||||
sender := tctx.Sender
|
sender := tctx.Sender
|
||||||
_, err = sender.WithUploader(upler).To(peer).Media(ctx, mediaOpt)
|
_, err = sender.WithUploader(upler).To(peer).Media(ctx, docb)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ func TestWriteFile(t *testing.T) {
|
|||||||
remotePath: "hello.txt",
|
remotePath: "hello.txt",
|
||||||
content: "Hello webdav",
|
content: "Hello webdav",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
remotePath: "//nested/dir/test.txt",
|
||||||
|
content: "Nested file",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
remotePath: "nested/dir/test.txt",
|
remotePath: "nested/dir/test.txt",
|
||||||
content: "Nested file",
|
content: "Nested file",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
config "github.com/krau/SaveAny-Bot/config/storage"
|
config "github.com/krau/SaveAny-Bot/config/storage"
|
||||||
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
|
||||||
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Webdav struct {
|
type Webdav struct {
|
||||||
@@ -56,6 +57,11 @@ func (w *Webdav) Save(ctx context.Context, r io.Reader, storagePath string) erro
|
|||||||
candidate := storagePath
|
candidate := storagePath
|
||||||
for i := 1; w.Exists(ctx, candidate); i++ {
|
for i := 1; w.Exists(ctx, candidate); i++ {
|
||||||
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
candidate = fmt.Sprintf("%s_%d%s", base, i, ext)
|
||||||
|
if i > 1000 {
|
||||||
|
w.logger.Errorf("Too many attempts to find a unique filename for %s", storagePath)
|
||||||
|
candidate = fmt.Sprintf("%s_%s%s", base, xid.New().String(), ext)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.client.MkDir(ctx, path.Dir(candidate)); err != nil {
|
if err := w.client.MkDir(ctx, path.Dir(candidate)); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user