package plugins import ( "fmt" "regexp" "strconv" "strings" "time" "github.com/Jisin0/Go-Filter-Bot/utils" "github.com/PaulSonOfLars/gotgbot/v2" "github.com/PaulSonOfLars/gotgbot/v2/ext" ) var ( // Matches t.me/c/1234567/89 or t.me/username/89 tgLinkRegex = regexp.MustCompile(`t\.me/(?:c/)?([^/]+)/(\d+)`) ) func parseTelegramLink(link string) (int64, int64, error) { matches := tgLinkRegex.FindStringSubmatch(link) if len(matches) != 3 { return 0, 0, fmt.Errorf("invalid telegram link format") } chatStr := matches[1] msgIDStr := matches[2] msgID, err := strconv.ParseInt(msgIDStr, 10, 64) if err != nil { return 0, 0, fmt.Errorf("invalid message ID") } // Channel ID in t.me/c/123456/789 format chatID, err := strconv.ParseInt(chatStr, 10, 64) if err != nil { return 0, 0, fmt.Errorf("invalid channel ID in link") } if chatID > 0 { chatID, _ = strconv.ParseInt("-100"+chatStr, 10, 64) } return chatID, msgID, nil } func ReplaceCommand(bot *gotgbot.Bot, ctx *ext.Context) error { if !utils.IsAdmin(ctx.EffectiveUser.Id) { ctx.Message.Reply(bot, "Only admins can use this command.", nil) return nil } args := ctx.Args()[1:] if len(args) < 1 { ctx.Message.Reply(bot, "Please provide the target Telegram post link.\nUsage: `/replace https://t.me/channel/123` (replying to a file)", &gotgbot.SendMessageOpts{ParseMode: "Markdown"}) return nil } replyMsg := ctx.Message.ReplyToMessage if replyMsg == nil || (replyMsg.Document == nil && replyMsg.Video == nil && replyMsg.Audio == nil && replyMsg.Photo == nil) { ctx.Message.Reply(bot, "Please reply to a file (Document/Video/Audio/Photo) to replace the destination post.", nil) return nil } link := args[0] chatID, msgID, err := parseTelegramLink(link) if err != nil { ctx.Message.Reply(bot, "Failed to parse link: "+err.Error(), nil) return nil } var inputMedia gotgbot.InputMedia var fileID string if replyMsg.Document != nil { fileID = replyMsg.Document.FileId inputMedia = gotgbot.InputMediaDocument{ Media: gotgbot.InputFileByID(fileID), Caption: replyMsg.Caption, CaptionEntities: replyMsg.CaptionEntities, } } else if replyMsg.Video != nil { fileID = replyMsg.Video.FileId inputMedia = gotgbot.InputMediaVideo{ Media: gotgbot.InputFileByID(fileID), Caption: replyMsg.Caption, CaptionEntities: replyMsg.CaptionEntities, } } else if replyMsg.Audio != nil { fileID = replyMsg.Audio.FileId inputMedia = gotgbot.InputMediaAudio{ Media: gotgbot.InputFileByID(fileID), Caption: replyMsg.Caption, CaptionEntities: replyMsg.CaptionEntities, } } else if len(replyMsg.Photo) > 0 { fileID = replyMsg.Photo[len(replyMsg.Photo)-1].FileId inputMedia = gotgbot.InputMediaPhoto{ Media: gotgbot.InputFileByID(fileID), Caption: replyMsg.Caption, CaptionEntities: replyMsg.CaptionEntities, } } _, _, err = bot.EditMessageMedia(inputMedia, &gotgbot.EditMessageMediaOpts{ ChatId: chatID, MessageId: msgID, }) if err != nil { ctx.Message.Reply(bot, "Failed to replace media. Make sure I am an admin in that channel with edit rights.\nError: `"+err.Error()+"`", &gotgbot.SendMessageOpts{ParseMode: "Markdown"}) return nil } ctx.Message.Reply(bot, "✅ Successfully replaced the file in the target post!", nil) return nil } // BatchReplace will ask for the count and base link, then expect the next 'count' messages to be files. var BatchReplaceState = make(map[int64]*BatchState) type BatchState struct { ChatID int64 StartMsgID int64 Count int CurrentIndex int AdminID int64 } func BatchReplaceCommand(bot *gotgbot.Bot, ctx *ext.Context) error { if !utils.IsAdmin(ctx.EffectiveUser.Id) { ctx.Message.Reply(bot, "Only admins can use this command.", nil) return nil } args := ctx.Args()[1:] if len(args) < 2 { ctx.Message.Reply(bot, "Usage: `/batchreplace `\nExample: `/batchreplace 20 https://t.me/c/12345/100`", &gotgbot.SendMessageOpts{ParseMode: "Markdown"}) return nil } count, err := strconv.Atoi(args[0]) if err != nil || count <= 0 { ctx.Message.Reply(bot, "Invalid count. Must be a positive number.", nil) return nil } link := args[1] chatID, startMsgID, err := parseTelegramLink(link) if err != nil { ctx.Message.Reply(bot, "Failed to parse link: "+err.Error(), nil) return nil } BatchReplaceState[ctx.EffectiveUser.Id] = &BatchState{ ChatID: chatID, StartMsgID: startMsgID, Count: count, CurrentIndex: 0, AdminID: ctx.EffectiveUser.Id, } ctx.Message.Reply(bot, fmt.Sprintf("✅ Batch replace started for %d files.\nTarget starting at post ID: %d\n\nPlease send or forward the %d new files here. I will automatically replace them one by one.", count, startMsgID, count), nil) return nil } func BatchReplaceHandler(bot *gotgbot.Bot, ctx *ext.Context) error { if ctx.Message == nil { return ext.ContinueGroups } state, exists := BatchReplaceState[ctx.EffectiveUser.Id] if !exists { return ext.ContinueGroups } // Check if message is a command like /cancel if strings.HasPrefix(ctx.Message.Text, "/cancel") { delete(BatchReplaceState, ctx.EffectiveUser.Id) ctx.Message.Reply(bot, "❌ Batch replace cancelled.", nil) return ext.ContinueGroups } // We only care about media messages msg := ctx.Message if msg.Document == nil && msg.Video == nil && msg.Audio == nil && msg.Photo == nil { // Not a media message, ignore or warn? return ext.ContinueGroups } targetMsgID := state.StartMsgID + int64(state.CurrentIndex) var inputMedia gotgbot.InputMedia var fileID string if msg.Document != nil { fileID = msg.Document.FileId inputMedia = gotgbot.InputMediaDocument{ Media: gotgbot.InputFileByID(fileID), Caption: msg.Caption, CaptionEntities: msg.CaptionEntities, } } else if msg.Video != nil { fileID = msg.Video.FileId inputMedia = gotgbot.InputMediaVideo{ Media: gotgbot.InputFileByID(fileID), Caption: msg.Caption, CaptionEntities: msg.CaptionEntities, } } else if msg.Audio != nil { fileID = msg.Audio.FileId inputMedia = gotgbot.InputMediaAudio{ Media: gotgbot.InputFileByID(fileID), Caption: msg.Caption, CaptionEntities: msg.CaptionEntities, } } else if len(msg.Photo) > 0 { fileID = msg.Photo[len(msg.Photo)-1].FileId inputMedia = gotgbot.InputMediaPhoto{ Media: gotgbot.InputFileByID(fileID), Caption: msg.Caption, CaptionEntities: msg.CaptionEntities, } } // Sleep slightly to avoid floodwaits if they forward an album (which arrives instantly) time.Sleep(500 * time.Millisecond) _, _, err := bot.EditMessageMedia(inputMedia, &gotgbot.EditMessageMediaOpts{ ChatId: state.ChatID, MessageId: targetMsgID, }) if err != nil { ctx.Message.Reply(bot, fmt.Sprintf("❌ Failed to replace media for post ID %d: %s\nBatch replace aborted.", targetMsgID, err.Error()), nil) delete(BatchReplaceState, ctx.EffectiveUser.Id) return ext.ContinueGroups } state.CurrentIndex++ if state.CurrentIndex >= state.Count { ctx.Message.Reply(bot, fmt.Sprintf("✅ Batch replace successfully completed! %d files replaced.", state.Count), nil) delete(BatchReplaceState, ctx.EffectiveUser.Id) } else { // Send a progress message? Maybe too spammy. } return ext.ContinueGroups }