mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-07-12 16:02:05 +08:00
fix: implement watch media group handler and enhance media processing logic
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,4 +9,6 @@ cache.db
|
|||||||
temp/
|
temp/
|
||||||
.hugo_build.lock
|
.hugo_build.lock
|
||||||
playwright/
|
playwright/
|
||||||
testplugins/
|
testplugins/
|
||||||
|
*.exe
|
||||||
|
tmp-*
|
||||||
@@ -26,7 +26,7 @@ func BuildDirHelpStyling(dirs []database.Dir) []styling.StyledTextOption {
|
|||||||
styling.Blockquote(func() string {
|
styling.Blockquote(func() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for _, dir := range dirs {
|
for _, dir := range dirs {
|
||||||
sb.WriteString(fmt.Sprintf("%d: ", dir.ID))
|
fmt.Fprintf(&sb, "%d: ", dir.ID)
|
||||||
sb.WriteString(dir.StorageName)
|
sb.WriteString(dir.StorageName)
|
||||||
sb.WriteString(" - ")
|
sb.WriteString(" - ")
|
||||||
sb.WriteString(dir.Path)
|
sb.WriteString(dir.Path)
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/celestix/gotgproto/dispatcher"
|
"github.com/celestix/gotgproto/dispatcher"
|
||||||
"github.com/celestix/gotgproto/ext"
|
"github.com/celestix/gotgproto/ext"
|
||||||
@@ -16,10 +18,12 @@ import (
|
|||||||
"github.com/krau/SaveAny-Bot/common/i18n"
|
"github.com/krau/SaveAny-Bot/common/i18n"
|
||||||
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
"github.com/krau/SaveAny-Bot/common/i18n/i18nk"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
"github.com/krau/SaveAny-Bot/common/utils/tgutil"
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
"github.com/krau/SaveAny-Bot/core"
|
"github.com/krau/SaveAny-Bot/core"
|
||||||
"github.com/krau/SaveAny-Bot/core/tasks/tfile"
|
coretfile "github.com/krau/SaveAny-Bot/core/tasks/tfile"
|
||||||
"github.com/krau/SaveAny-Bot/database"
|
"github.com/krau/SaveAny-Bot/database"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/enums/fnamest"
|
"github.com/krau/SaveAny-Bot/pkg/enums/fnamest"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/tfile"
|
||||||
"github.com/krau/SaveAny-Bot/storage"
|
"github.com/krau/SaveAny-Bot/storage"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
@@ -151,6 +155,53 @@ func handleUnwatchCmd(ctx *ext.Context, update *ext.Update) error {
|
|||||||
return dispatcher.EndGroups
|
return dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type watchMediaGroupHandler struct {
|
||||||
|
groups map[int64]map[uint][]tfile.TGFileMessage // chatID -> userID -> files
|
||||||
|
timers map[int64]map[uint]*time.Timer
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var watchMediaGroupMgr = &watchMediaGroupHandler{
|
||||||
|
groups: make(map[int64]map[uint][]tfile.TGFileMessage),
|
||||||
|
timers: make(map[int64]map[uint]*time.Timer),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watchMediaGroupHandler) addFile(chatID int64, userID uint, file tfile.TGFileMessage, timeout time.Duration, callback func([]tfile.TGFileMessage)) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.groups[chatID] == nil {
|
||||||
|
w.groups[chatID] = make(map[uint][]tfile.TGFileMessage)
|
||||||
|
}
|
||||||
|
if w.timers[chatID] == nil {
|
||||||
|
w.timers[chatID] = make(map[uint]*time.Timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timer, exists := w.timers[chatID][userID]; exists {
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.groups[chatID][userID] = append(w.groups[chatID][userID], file)
|
||||||
|
|
||||||
|
w.timers[chatID][userID] = time.AfterFunc(timeout, func() {
|
||||||
|
w.mu.Lock()
|
||||||
|
files := w.groups[chatID][userID]
|
||||||
|
delete(w.groups[chatID], userID)
|
||||||
|
delete(w.timers[chatID], userID)
|
||||||
|
if len(w.groups[chatID]) == 0 {
|
||||||
|
delete(w.groups, chatID)
|
||||||
|
}
|
||||||
|
if len(w.timers[chatID]) == 0 {
|
||||||
|
delete(w.timers, chatID)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if len(files) > 0 {
|
||||||
|
callback(files)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
||||||
if userclient.GetCtx() == nil {
|
if userclient.GetCtx() == nil {
|
||||||
return
|
return
|
||||||
@@ -224,6 +275,24 @@ func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
|||||||
}
|
}
|
||||||
file.SetName(sb.String())
|
file.SetName(sb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a media group and if rules specify NEW-FOR-ALBUM
|
||||||
|
groupID, isGroup := file.Message().GetGroupedID()
|
||||||
|
needAlbumHandling := false
|
||||||
|
if isGroup && groupID != 0 && user.ApplyRule && user.Rules != nil {
|
||||||
|
_, _, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||||
|
needAlbumHandling = matchedDirPath.NeedNewForAlbum()
|
||||||
|
}
|
||||||
|
|
||||||
|
if needAlbumHandling {
|
||||||
|
// For media groups with NEW-FOR-ALBUM rule, collect all files of the same group
|
||||||
|
watchMediaGroupMgr.addFile(event.ChatID, user.ID, file, time.Duration(config.C().Telegram.MediaGroupTimeout)*time.Second, func(files []tfile.TGFileMessage) {
|
||||||
|
processWatchMediaGroup(ctx, user, stor, "", files)
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process single file or media group without album folder creation
|
||||||
var dirPath string
|
var dirPath string
|
||||||
if user.ApplyRule && user.Rules != nil {
|
if user.ApplyRule && user.Rules != nil {
|
||||||
matched, matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
matched, matchedStorageName, matchedDirPath := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||||
@@ -243,7 +312,7 @@ func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
|||||||
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 := tfile.NewTGFileTask(taskid, injectCtx, file, stor, storagePath, nil)
|
task, err := coretfile.NewTGFileTask(taskid, injectCtx, file, stor, storagePath, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("create task failed: %s", err)
|
logger.Errorf("create task failed: %s", err)
|
||||||
continue
|
continue
|
||||||
@@ -256,3 +325,97 @@ func listenMediaMessageEvent(ch chan userclient.MediaMessageEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processWatchMediaGroup(ctx *ext.Context, user *database.User, stor storage.Storage, dirPath string, files []tfile.TGFileMessage) {
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
if len(files) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
useRule := user.ApplyRule && user.Rules != nil
|
||||||
|
|
||||||
|
applyRule := func(file tfile.TGFileMessage) (string, ruleutil.MatchedDirPath) {
|
||||||
|
if !useRule {
|
||||||
|
return stor.Name(), ruleutil.MatchedDirPath(dirPath)
|
||||||
|
}
|
||||||
|
matched, storName, dirP := ruleutil.ApplyRule(ctx, user.Rules, ruleutil.NewInput(file))
|
||||||
|
if !matched {
|
||||||
|
return stor.Name(), ruleutil.MatchedDirPath(dirPath)
|
||||||
|
}
|
||||||
|
storname := storName.String()
|
||||||
|
if !storName.Usable() {
|
||||||
|
storname = stor.Name()
|
||||||
|
}
|
||||||
|
return storname, dirP
|
||||||
|
}
|
||||||
|
|
||||||
|
type albumFile struct {
|
||||||
|
file tfile.TGFileMessage
|
||||||
|
storage storage.Storage
|
||||||
|
}
|
||||||
|
albumFiles := make(map[int64][]albumFile)
|
||||||
|
|
||||||
|
// Collect files by group ID
|
||||||
|
for _, file := range files {
|
||||||
|
storName, ruleDirPath := applyRule(file)
|
||||||
|
fileStor := stor
|
||||||
|
if storName != stor.Name() && storName != "" {
|
||||||
|
var err error
|
||||||
|
fileStor, err = storage.GetStorageByUserIDAndName(ctx, user.ChatID, storName)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to get storage by user ID and name: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupId, isGroup := file.Message().GetGroupedID()
|
||||||
|
if !isGroup || groupId == 0 {
|
||||||
|
logger.Warnf("File %s is not in a group, skipping", file.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ruleDirPath.NeedNewForAlbum() {
|
||||||
|
logger.Warnf("File %s does not need album folder, skipping", file.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := albumFiles[groupId]; !ok {
|
||||||
|
albumFiles[groupId] = make([]albumFile, 0)
|
||||||
|
}
|
||||||
|
albumFiles[groupId] = append(albumFiles[groupId], albumFile{
|
||||||
|
file: file,
|
||||||
|
storage: fileStor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process album files with folder creation
|
||||||
|
injectCtx := tgutil.ExtWithContext(ctx.Context, ctx)
|
||||||
|
totalTasks := 0
|
||||||
|
for groupID, afiles := range albumFiles {
|
||||||
|
if len(afiles) <= 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first file's name (without extension) as album folder name
|
||||||
|
albumDir := strings.TrimSuffix(path.Base(afiles[0].file.Name()), path.Ext(afiles[0].file.Name()))
|
||||||
|
albumStor := afiles[0].storage
|
||||||
|
|
||||||
|
logger.Infof("Creating album folder for group %d: %s with %d files", groupID, albumDir, len(afiles))
|
||||||
|
|
||||||
|
for _, af := range afiles {
|
||||||
|
afstorPath := af.storage.JoinStoragePath(path.Join(dirPath, albumDir, af.file.Name()))
|
||||||
|
taskid := xid.New().String()
|
||||||
|
task, err := coretfile.NewTGFileTask(taskid, injectCtx, af.file, albumStor, afstorPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("create task failed for album file: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := core.AddTask(injectCtx, task); err != nil {
|
||||||
|
logger.Errorf("add task failed: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalTasks++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Infof("Added %d watch media tasks for user %d", totalTasks, user.ChatID)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user