mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-10 04:19:48 +00:00
Implements 3 coalescing layers to handle rapid multi-attachment inbounds: - Bus debouncer: delays inbound messages 1s, merges duplicates - Web chat debouncer: buffers client-side inbound frames for batch RPC - Telegram album aggregator: collects album members via AfterFunc+Stop timer Drops media-bypass shortcut (forces 1s media floor). Aggregator enforces: - AfterFunc+Stop timer discipline with ordered drain on stop - 2-tuple (album_id, sender) keying for isolation - Dual DoS caps: max 10 albums per sender, max 100 messages per album - merged_message_ids dedup seeding across all 3 surfaces Closes #63
47 lines
1.6 KiB
Go
47 lines
1.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
|
)
|
|
|
|
// dedupKeyFor builds the inbound-message dedup key.
|
|
// Format must match the inline key built in consumeInboundMessages
|
|
// (gateway_consumer.go) so siblings seeded via seedDedupFromMerged match
|
|
// the lookup performed on subsequent platform retransmits.
|
|
func dedupKeyFor(channel, senderID, chatID, messageID string) string {
|
|
return fmt.Sprintf("%s|%s|%s|%s", channel, senderID, chatID, messageID)
|
|
}
|
|
|
|
// seedDedupFromMerged seeds every sibling message_id from the flushed merged
|
|
// message into the dedup cache. Implements Phase 1 Rule #4: a multi-attachment
|
|
// burst flushes as ONE merged InboundMessage carrying all source ids in
|
|
// metadata["merged_message_ids"]; any subsequent platform retransmit of a
|
|
// sibling member (e.g. Telegram webhook redelivery, WhatsApp retry) MUST be
|
|
// short-circuited at the dedup gate before re-entering the debouncer.
|
|
//
|
|
// No-op when:
|
|
// - dedupe is nil (defensive — call sites should always pass a real cache)
|
|
// - msg.Metadata is nil
|
|
// - merged_message_ids is absent or empty
|
|
func seedDedupFromMerged(dedupe *bus.DedupeCache, msg bus.InboundMessage) {
|
|
if dedupe == nil || msg.Metadata == nil {
|
|
return
|
|
}
|
|
merged := msg.Metadata["merged_message_ids"]
|
|
if merged == "" {
|
|
return
|
|
}
|
|
for _, id := range strings.Split(merged, ",") {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
continue
|
|
}
|
|
// IsDuplicate has a side-effect of recording the key when absent,
|
|
// which is exactly the seeding we want. Discard the boolean.
|
|
_ = dedupe.IsDuplicate(dedupKeyFor(msg.Channel, msg.SenderID, msg.ChatID, id))
|
|
}
|
|
}
|