Files
SaveAny-Bot/storage/telegram/telegram.go
Krau 900823cdb9 refactor: refactor task logic for better scalability (#76)
* refactor: a big refactor. wip

* refactor: port handle file

* refactor: place all handlers

* fix: task info nil pointer

* feat: enhance task progress tracking and context management

* feat: cancel task

* feat: stream mode

* feat: silent mode

* feat: dir cmd

* refactor: remove unused old file

* feat: rule cmd

* feat: handle silent mode

* feat: batch task

* fix: batch task progress and temp file cleanup

* refactor: update file creation and cleanup methods for better resource management

* feat: add save command with silent mode handling

* feat: message link

* feat: update message prompts to include file count in storage selection

* feat: slient save links

* refactor: reduce dup code

* feat: rule type

* feat: chose dir

* feat: refactor file handling and storage rules, improve error handling and logging

* feat: rule mode

* feat: telegraph pics

* fix: tphpics nil pointer and inaccurate dirpath

* feat: silent save telegraph

* feat: add suffix to avoid file overwrite

* feat: new storage telegram

* chore: tidy go mod
2025-06-15 23:57:49 +08:00

112 lines
3.0 KiB
Go

package telegram
import (
"context"
"fmt"
"io"
"path"
"time"
"github.com/gabriel-vasile/mimetype"
"github.com/gotd/td/telegram/message"
"github.com/gotd/td/telegram/message/styling"
"github.com/gotd/td/telegram/uploader"
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
"github.com/krau/SaveAny-Bot/config"
storconfig "github.com/krau/SaveAny-Bot/config/storage"
"github.com/krau/SaveAny-Bot/pkg/consts/tglimit"
storenum "github.com/krau/SaveAny-Bot/pkg/enums/storage"
"github.com/rs/xid"
"golang.org/x/time/rate"
)
type Telegram struct {
config storconfig.TelegramStorageConfig
limiter *rate.Limiter
}
func (t *Telegram) Init(ctx context.Context, cfg storconfig.StorageConfig) error {
telegramConfig, ok := cfg.(*storconfig.TelegramStorageConfig)
if !ok {
return fmt.Errorf("failed to cast telegram config")
}
if err := telegramConfig.Validate(); err != nil {
return err
}
t.config = *telegramConfig
if t.config.RateLimit <= 0 || t.config.RateBurst <= 0 {
t.config.RateLimit = 2
t.config.RateBurst = 1
}
t.limiter = rate.NewLimiter(rate.Every(time.Duration(t.config.RateLimit)*time.Second), t.config.RateBurst)
return nil
}
func (t *Telegram) Type() storenum.StorageType {
return storenum.Telegram
}
func (t *Telegram) Name() string {
return t.config.Name
}
func (t *Telegram) JoinStoragePath(p string) string {
return path.Clean(p)
}
func (t *Telegram) Exists(ctx context.Context, storagePath string) bool {
return false
}
func (t *Telegram) Save(ctx context.Context, r io.Reader, storagePath string) error {
if err := t.limiter.Wait(ctx); err != nil {
return fmt.Errorf("rate limit failed: %w", err)
}
rs, ok := r.(io.ReadSeeker)
if !ok || rs == nil {
return fmt.Errorf("reader must implement io.ReadSeeker")
}
tctx := tgutil.ExtFromContext(ctx)
if tctx == nil {
return fmt.Errorf("failed to get telegram context")
}
peer := tctx.PeerStorage.GetInputPeerById(t.config.ChatID)
if peer == nil {
return fmt.Errorf("failed to get input peer for chat ID %d", t.config.ChatID)
}
mtype, err := mimetype.DetectReader(rs)
if err != nil {
return fmt.Errorf("failed to detect mimetype: %w", err)
}
filename := path.Base(storagePath)
if filename == "" {
filename = xid.New().String() + mtype.Extension()
}
if _, err := rs.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("failed to seek reader: %w", err)
}
upler := uploader.NewUploader(tctx.Raw).
WithPartSize(tglimit.MaxUploadPartSize).
WithThreads(config.Cfg.Threads)
file, err := upler.FromReader(ctx, filename, rs)
if err != nil {
return fmt.Errorf("failed to upload file to telegram: %w", err)
}
caption := styling.Plain(filename)
docb := message.UploadedDocument(file, caption).
Filename(filename).
ForceFile(true).
MIME(mtype.String())
var mediaOpt message.MediaOption = docb
sender := tctx.Sender
_, err = sender.WithUploader(upler).To(peer).Media(ctx, mediaOpt)
return err
}
func (t *Telegram) CannotStream() string {
return "Telegram storage must use a ReaderSeeker"
}