// In file: internal/commands/batch.go package commands import ( "TelegramCloud/tgf/internal/state" "TelegramCloud/tgf/internal/utils" "fmt" "math/rand" "strings" "time" "github.com/celestix/gotgproto/dispatcher" "github.com/celestix/gotgproto/dispatcher/handlers" "github.com/celestix/gotgproto/ext" "github.com/gotd/td/telegram/message/styling" "github.com/gotd/td/tg" "go.uber.org/zap" ) func (m *command) LoadBatch(dispatcher dispatcher.Dispatcher) { log := m.log.Named("batch") defer log.Sugar().Info("Loaded stateful batch commands") dispatcher.AddHandler(handlers.NewCommand("batch", batchStart)) dispatcher.AddHandler(handlers.NewCommand("done", batchDone)) dispatcher.AddHandler(handlers.NewCommand("cancel", batchCancel)) } // _activateBatchMode is a helper to enter batch mode and send the confirmation message. func _activateBatchMode(ctx *ext.Context, u *ext.Update) error { // FIX: Define the logger for this function's scope. log := utils.Logger.Named("activateBatchMode") userID := u.EffectiveUser().ID state.GlobalStateManager.EnterBatchMode(userID) msg := `✅ **Batch mode activated.** Send me all the files you want to process. I will collect them without sending immediate replies. When you are finished, send /done to get the final list of links. To abort, send /cancel.` if u.CallbackQuery != nil { peer := ctx.PeerStorage.GetInputPeerById(u.CallbackQuery.UserID) _, err := ctx.Raw.MessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{ Peer: peer, Message: msg, RandomID: rand.Int63(), NoWebpage: true, }) if err != nil { log.Error("Failed to send batch activation message", zap.Error(err)) } } else { ctx.Reply(u, msg, nil) } return dispatcher.EndGroups } func batchStart(ctx *ext.Context, u *ext.Update) error { return _activateBatchMode(ctx, u) } func batchCancel(ctx *ext.Context, u *ext.Update) error { userID := u.EffectiveUser().ID if !state.GlobalStateManager.IsUserInBatch(userID) { ctx.Reply(u, "You are not in batch mode.", nil) return dispatcher.EndGroups } state.GlobalStateManager.ExitBatchMode(userID) ctx.Reply(u, "❌ Batch cancelled.", nil) return dispatcher.EndGroups } func batchDone(ctx *ext.Context, u *ext.Update) error { log := utils.Logger.Named("batchDone") userID := u.EffectiveUser().ID if !state.GlobalStateManager.IsUserInBatch(userID) { ctx.Reply(u, "You are not in batch mode. Start a new one with /batch.", nil) return dispatcher.EndGroups } filesToProcess := state.GlobalStateManager.GetAndClearFiles(userID) if len(filesToProcess) == 0 { ctx.Reply(u, "You haven't sent any files in this batch. Batch cancelled.", nil) return dispatcher.EndGroups } statusMsg, _ := ctx.Reply(u, fmt.Sprintf("Processing %d files... Please wait.", len(filesToProcess)), nil) peer := u.EffectiveChat().GetInputPeer() editStatus := func(text string) { req := &tg.MessagesEditMessageRequest{Peer: peer, ID: statusMsg.ID, Message: text} _, _ = ctx.Raw.MessagesEditMessage(ctx, req) } var links []string totalFiles := len(filesToProcess) for i, msgID := range filesToProcess { editStatus(fmt.Sprintf("Processing file %d of %d...", i+1, totalFiles)) link, _, err := utils.GenerateStreamLink(ctx, u, msgID) if err != nil { log.Error("Error generating link for message in batch", zap.Error(err), zap.Int("messageID", msgID)) links = append(links, fmt.Sprintf("Error processing message ID %d: %s", msgID, err.Error())) } else { links = append(links, link) } time.Sleep(500 * time.Millisecond) } const telegramMessageLimit = 4000 linkBlock := strings.Join(links, "\n") header := fmt.Sprintf("✅ Batch complete! Here are the %d links.\n\nCopy the text below and paste it into your download manager (IDM, 1DM, etc.):", totalFiles) ctx.DeleteMessages(u.EffectiveChat().GetID(), []int{statusMsg.ID}) if len(header)+len(linkBlock) < telegramMessageLimit { messageTextWithOptions := []styling.StyledTextOption{ styling.Plain(header), styling.Plain("\n\n"), styling.Code(linkBlock), } ctx.Reply(u, messageTextWithOptions, &ext.ReplyOpts{ReplyToMessageId: u.EffectiveMessage.ID}) } else { ctx.Reply(u, fmt.Sprintf("Batch complete with %d files, but the list of links is too long to send in a single message.", totalFiles), &ext.ReplyOpts{ReplyToMessageId: u.EffectiveMessage.ID}, ) } return dispatcher.EndGroups }