mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
Pairing requests and message handlers were silently using MasterTenantID instead of the correct tenant because context.Context had no tenant set. Root cause: instance_loader.go passed bare ctx (no tenant) to startChannelWithTimeout; channel handlers also missing store.WithTenantID at entry points. Channels fixed: - instance_loader.go: pass instCtx (with tenant) to startChannelWithTimeout - Telegram, Bitrix24, Feishu: add store.WithTenantID at handler entry - Facebook: add ctx param to handleMessagingEvent/handleCommentEvent + webhook_handler - Pancake: add ctx param to handleMessagingEvent/handleCommentEvent + webhook_handler Also fix consolidation pruner: PruneExpired SQL had tenant_id filter that caused zero rows to ever be deleted (context had no tenant). Remove tenant filter — pruning is a global maintenance operation. Add regression tests: - internal/channels/pancake/tenant_context_test.go (6 tests) - internal/channels/facebook/tenant_context_test.go (4 tests) - internal/consolidation/prune_expired_test.go (5 tests) Co-authored-by: Bruno Clermont <bruno.clermont@gmail.com>
104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
package facebook
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// handleMessagingEvent processes a Messenger inbox event.
|
|
func (ch *Channel) handleMessagingEvent(ctx context.Context, entry WebhookEntry, event MessagingEvent) {
|
|
ctx = store.WithTenantID(ctx, ch.TenantID())
|
|
// Feature gate.
|
|
if !ch.config.Features.MessengerAutoReply {
|
|
return
|
|
}
|
|
|
|
// Page routing guard (before dedup write).
|
|
if entry.ID != ch.pageID {
|
|
return
|
|
}
|
|
|
|
// Track admin (page) replies: when the page itself sends a message,
|
|
// record the recipient's chat ID so the bot skips auto-reply for that
|
|
// conversation during the cooldown window.
|
|
if event.Sender.ID == ch.pageID {
|
|
if event.Recipient.ID != "" {
|
|
eventAt := messagingEventTime(event.Timestamp)
|
|
if ch.isBotEcho(event.Recipient.ID, eventAt) {
|
|
slog.Debug("facebook: bot echo ignored", "chat_id", event.Recipient.ID)
|
|
return
|
|
}
|
|
ch.adminReplied.Store(event.Recipient.ID, eventAt)
|
|
slog.Debug("facebook: admin reply tracked", "chat_id", event.Recipient.ID)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Skip delivery/read receipts and other non-content events.
|
|
if event.Message == nil && event.Postback == nil {
|
|
return
|
|
}
|
|
|
|
// Dedup by message MID or postback signature (include payload to reduce collision risk).
|
|
var eventKey string
|
|
switch {
|
|
case event.Message != nil:
|
|
eventKey = "msg:" + event.Message.MID
|
|
case event.Postback != nil:
|
|
eventKey = fmt.Sprintf("postback:%s:%d:%s", event.Sender.ID, event.Timestamp, event.Postback.Payload)
|
|
}
|
|
if ch.isDup(eventKey) {
|
|
slog.Debug("facebook: duplicate messaging event skipped", "key", eventKey)
|
|
return
|
|
}
|
|
|
|
// Check if admin already replied to this conversation recently.
|
|
senderID := event.Sender.ID
|
|
if ch.adminRepliedRecently(senderID, time.Now()) {
|
|
slog.Info("facebook: skipping auto-reply (admin replied recently)", "chat_id", senderID)
|
|
return
|
|
}
|
|
|
|
// Extract text content.
|
|
var content string
|
|
switch {
|
|
case event.Message != nil && event.Message.Text != "":
|
|
content = event.Message.Text
|
|
case event.Postback != nil:
|
|
content = event.Postback.Title
|
|
default:
|
|
// Attachment-only message — skip for now.
|
|
return
|
|
}
|
|
|
|
// Messenger sessions are 1:1: chatID = senderID (channel name scopes the session).
|
|
chatID := senderID
|
|
|
|
metadata := map[string]string{
|
|
"fb_mode": "messenger",
|
|
"message_id": eventKey,
|
|
"page_id": ch.pageID,
|
|
"sender_id": senderID,
|
|
}
|
|
if ch.config.MessengerOptions.SessionTimeout != "" {
|
|
metadata["session_timeout"] = ch.config.MessengerOptions.SessionTimeout
|
|
}
|
|
|
|
ch.HandleMessage(senderID, chatID, content, nil, metadata, "direct")
|
|
}
|
|
|
|
func messagingEventTime(ts int64) time.Time {
|
|
switch {
|
|
case ts > 1_000_000_000_000:
|
|
return time.UnixMilli(ts)
|
|
case ts > 0:
|
|
return time.Unix(ts, 0)
|
|
default:
|
|
return time.Now()
|
|
}
|
|
}
|