mirror of
https://github.com/HugeFrog24/go-telegram-bot.git
synced 2026-06-29 22:07:12 +00:00
Support for images
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-telegram/bot/models"
|
||||
)
|
||||
|
||||
// albumFlushWindow is the debounce delay before a buffered Telegram media_group
|
||||
// is flushed as a single coalesced user turn. 1s matches the de-facto community
|
||||
// standard across the dominant third-party album plugins (aiogram-media-group,
|
||||
// DieTime/telegraf-media-group) and sits above the sub-100ms values documented
|
||||
// as lossy under network jitter (openclaw#1811). Telegram has no official
|
||||
// "album complete" signal, so timeout-based flush is the only option.
|
||||
const albumFlushWindow = 1 * time.Second
|
||||
|
||||
// pendingAlbum holds a Telegram media_group as its items arrive, plus the
|
||||
// per-user metadata captured from the first item. All items in an album share
|
||||
// the same chat/user, so we record metadata once and reuse it at flush time.
|
||||
type pendingAlbum struct {
|
||||
items []*models.Message
|
||||
// Metadata captured from the first arriving item. Albums always come from
|
||||
// the same user/chat, so these are stable across the buffering window.
|
||||
chatID, userID int64
|
||||
username, firstName, lastName, languageCode string
|
||||
isPremium bool
|
||||
messageTime int
|
||||
isNewChat, isOwner bool
|
||||
businessConnectionID string
|
||||
// timer flushes the album after albumFlushWindow with no further arrivals.
|
||||
// Each new arrival stops the previous timer (best-effort) and installs a
|
||||
// fresh one — the standard debounce pattern.
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// bufferAlbumItem appends an incoming Telegram album item to the per-MediaGroupID
|
||||
// buffer. On first arrival it captures the user/chat metadata and starts the
|
||||
// flush timer; on subsequent arrivals it appends the item and extends the timer.
|
||||
// The 1s debounce gives the rest of the album time to arrive over the network.
|
||||
func (b *Bot) bufferAlbumItem(
|
||||
ctx context.Context,
|
||||
msg *models.Message,
|
||||
chatID, userID int64,
|
||||
username, firstName, lastName string,
|
||||
isPremium bool,
|
||||
languageCode string,
|
||||
messageTime int,
|
||||
isNewChat, isOwner bool,
|
||||
businessConnectionID string,
|
||||
) {
|
||||
b.albumBuffersMu.Lock()
|
||||
defer b.albumBuffersMu.Unlock()
|
||||
|
||||
album, exists := b.albumBuffers[msg.MediaGroupID]
|
||||
if !exists {
|
||||
album = &pendingAlbum{
|
||||
chatID: chatID,
|
||||
userID: userID,
|
||||
username: username,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
isPremium: isPremium,
|
||||
languageCode: languageCode,
|
||||
messageTime: messageTime,
|
||||
isNewChat: isNewChat,
|
||||
isOwner: isOwner,
|
||||
businessConnectionID: businessConnectionID,
|
||||
}
|
||||
b.albumBuffers[msg.MediaGroupID] = album
|
||||
}
|
||||
album.items = append(album.items, msg)
|
||||
|
||||
// Stop the previous timer best-effort; even if it already fired the race
|
||||
// is benign because flushAlbum removes the map entry under the lock — a
|
||||
// late arrival would simply seed a fresh album.
|
||||
if album.timer != nil {
|
||||
album.timer.Stop()
|
||||
}
|
||||
mediaGroupID := msg.MediaGroupID
|
||||
album.timer = time.AfterFunc(albumFlushWindow, func() {
|
||||
b.flushAlbum(ctx, mediaGroupID)
|
||||
})
|
||||
}
|
||||
|
||||
// flushAlbum is called by the flush timer (or by code that needs to force-flush
|
||||
// during shutdown). It removes the album from the buffer, sorts items by
|
||||
// message_id (Telegram does not guarantee in-order arrival), runs the rate-limit
|
||||
// check once, and dispatches to handlePhotoMessage.
|
||||
func (b *Bot) flushAlbum(ctx context.Context, mediaGroupID string) {
|
||||
b.albumBuffersMu.Lock()
|
||||
album, exists := b.albumBuffers[mediaGroupID]
|
||||
if !exists {
|
||||
b.albumBuffersMu.Unlock()
|
||||
return
|
||||
}
|
||||
delete(b.albumBuffers, mediaGroupID)
|
||||
items := album.items
|
||||
captured := *album // copy fields for use after unlock
|
||||
b.albumBuffersMu.Unlock()
|
||||
|
||||
// Sort by Telegram message_id: items in an album arrive as separate Updates
|
||||
// over the network and may interleave. Sorting restores the user's intended
|
||||
// order before we hand them to Claude.
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].ID < items[j].ID })
|
||||
|
||||
// Rate-limit fires once per coalesced album, not once per item.
|
||||
if !b.checkRateLimits(captured.userID) {
|
||||
b.sendRateLimitExceededMessage(ctx, captured.chatID, captured.businessConnectionID)
|
||||
return
|
||||
}
|
||||
|
||||
b.handlePhotoMessage(
|
||||
ctx, items,
|
||||
captured.chatID, captured.userID,
|
||||
captured.username, captured.firstName, captured.lastName,
|
||||
captured.isPremium, captured.languageCode, captured.messageTime,
|
||||
captured.isNewChat, captured.isOwner,
|
||||
captured.businessConnectionID,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user