This commit is contained in:
HugeFrog24
2026-09-03 21:55:36 +02:00
parent c36e1846f5
commit 89458480f3
17 changed files with 1593 additions and 127 deletions
+40 -26
View File
@@ -8,18 +8,14 @@ import (
// pendingIntake holds a chat's coalescing window. The buffered message bodies
// are deliberately absent: screenIncomingMessage has already persisted each
// message to the database and to chat memory by the time it is buffered, so the
// flushed turn picks them all up from memory. What is kept here is the metadata
// flushed turn picks them all up from memory. What is kept here is the request
// the turn needs, always refreshed to the most recent message in the batch.
type pendingIntake struct {
chatID, userID int64
username, firstName, lastName, languageCode string
isPremium bool
messageTime int
businessConnectionID string
allEmojiOnly bool
count int
seq uint64
timer *time.Timer
turnRequest
allEmojiOnly bool // every message in the batch was emoji only
count int
seq uint64
timer *time.Timer
}
// bufferIntake holds a text message for the configured quiet window instead of
@@ -48,27 +44,47 @@ func (b *Bot) bufferIntake(
}
// Reply metadata tracks the most recent message in the batch.
pending.chatID = chatID
pending.userID = userID
pending.username = username
pending.firstName = firstName
pending.lastName = lastName
pending.isPremium = isPremium
pending.languageCode = languageCode
pending.messageTime = messageTime
pending.businessConnectionID = businessConnectionID
pending.turnRequest = turnRequest{
ctx: ctx, chatID: chatID, userID: userID,
username: username, firstName: firstName, lastName: lastName,
isPremium: isPremium, languageCode: languageCode, messageTime: messageTime,
businessConnectionID: businessConnectionID,
}
pending.allEmojiOnly = pending.allEmojiOnly && isEmojiOnly
pending.count++
b.armIntakeLocked(pending, window)
}
// armIntakeLocked starts or restarts the quiet-window timer. The caller holds
// intakeBuffersMu.
func (b *Bot) armIntakeLocked(pending *pendingIntake, window time.Duration) {
if pending.timer != nil {
pending.timer.Stop()
}
seq := pending.seq
ctx, chatID, seq := pending.ctx, pending.chatID, pending.seq
pending.timer = time.AfterFunc(window, func() {
b.flushIntake(ctx, chatID, seq)
})
}
// touchIntake restarts the quiet window for a chat with a pending batch, as an
// edit to one of its messages does, and reports whether there was one.
// isEmojiOnly is the edited text's classification; an edit can only make the
// batch less emoji-only, never more, since other messages are not re-read.
func (b *Bot) touchIntake(chatID int64, isEmojiOnly bool) bool {
b.intakeBuffersMu.Lock()
defer b.intakeBuffersMu.Unlock()
pending, exists := b.intakeBuffers[chatID]
if !exists {
return false
}
pending.allEmojiOnly = pending.allEmojiOnly && isEmojiOnly
b.armIntakeLocked(pending, b.config.DebounceWindow())
return true
}
// flushIntake dispatches the coalesced turn for a chat. seq guards against a
// timer that had already fired before its Stop call landed: a stale goroutine
// would otherwise flush a buffer belonging to a later batch.
@@ -88,12 +104,10 @@ func (b *Bot) flushIntake(ctx context.Context, chatID int64, seq uint64) {
b.config.ID, captured.count, chatID)
}
b.respondToChat(
ctx, chatID, captured.userID, captured.allEmojiOnly,
captured.username, captured.firstName, captured.lastName,
captured.isPremium, captured.languageCode, captured.messageTime,
captured.businessConnectionID,
)
req := captured.turnRequest
req.ctx = ctx
req.isEmojiOnly = captured.allEmojiOnly
b.respondToChat(req)
}
// cancelIntake drops a chat's pending buffer without dispatching, returning how