mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-20 00:15:16 +00:00
feat(channels): add Facebook Messenger and Pancake channel integrations (#731)
Add two new channel implementations for Facebook Fanpage (comment + Messenger auto-reply, first inbox DM) and Pancake/pages.fm (multi-platform inbox via Facebook, Zalo, Instagram, TikTok, WhatsApp, LINE). Key features: - Facebook: comment auto-reply, Messenger auto-reply, first inbox DM, HMAC-SHA256 webhook verification, multi-page webhook routing - Pancake: multi-platform inbox, outbound echo dedup with HTML normalization, race-condition-safe echo fingerprinting, platform-aware formatting - Bootstrap skip: pre-fill USER.md from channel metadata (Pancake) - SanitizeDisplayName across all channels (defense in depth) Code audit fixes: - Fix truncateForTikTok byte→rune slicing (UTF-8 corruption) - Fix empty message.ID shared dedup slot (silent message loss) - Fix DisplayName markdown injection in buildPrefilledUser - Consolidate duplicate ChannelMeta type (agent→bootstrap) - Compile-time interface assertions, alphabetical type constants - Per-message logs demoted to slog.Debug, errors.As for wrapped errors - UI: alphabetical channel ordering, complete config schemas - Remove deprecated WhatsApp bridge_url, fix nested error parsing
This commit is contained in:
1 parent
7205e6bb3b
commit
fb8afd41bf
44 files changed
+4214
-39
No files matched your search
@@ -20,6 +20,8 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/eventbus"
|
||||
kg "github.com/nextlevelbuilder/goclaw/internal/knowledgegraph"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels/discord"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels/facebook"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels/pancake"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels/feishu"
|
||||
slackchannel "github.com/nextlevelbuilder/goclaw/internal/channels/slack"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels/telegram"
|
||||
@@ -417,6 +419,8 @@ func runGateway() {
|
||||
instanceLoader.RegisterFactory(channels.TypeZaloPersonal, zalopersonal.FactoryWithPendingStore(pgStores.PendingMessages))
|
||||
instanceLoader.RegisterFactory(channels.TypeWhatsApp, whatsapp.FactoryWithDB(pgStores.DB, pgStores.PendingMessages, "pgx"))
|
||||
instanceLoader.RegisterFactory(channels.TypeSlack, slackchannel.FactoryWithPendingStore(pgStores.PendingMessages))
|
||||
instanceLoader.RegisterFactory(channels.TypeFacebook, facebook.Factory)
|
||||
instanceLoader.RegisterFactory(channels.TypePancake, pancake.Factory)
|
||||
if err := instanceLoader.LoadAll(context.Background()); err != nil {
|
||||
slog.Error("failed to load channel instances from DB", "error", err)
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ func buildEnsureUserProfile(as store.AgentStore) agent.EnsureUserProfileFunc {
|
||||
// isNew=true seeds all files; isNew=false only seeds if user has zero files
|
||||
// (avoids re-seeding BOOTSTRAP.md after auto-cleanup on server restart).
|
||||
func buildSeedUserFiles(as store.AgentStore) agent.SeedUserFilesFunc {
|
||||
return func(ctx context.Context, agentID uuid.UUID, userID, agentType string, isNew bool) error {
|
||||
_, err := bootstrap.SeedUserFiles(ctx, as, agentID, userID, agentType, !isNew)
|
||||
return func(ctx context.Context, agentID uuid.UUID, userID, agentType string, isNew bool, channelMeta *bootstrap.ChannelMeta) error {
|
||||
_, err := bootstrap.SeedUserFiles(ctx, as, agentID, userID, agentType, !isNew, channelMeta)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,18 @@ func extractSessionMetadata(msg bus.InboundMessage, peerKind string) map[string]
|
||||
return meta
|
||||
}
|
||||
|
||||
// buildPancakeSessionLabel returns "Pancake:{senderName}:{pageName}" with non-empty parts only.
|
||||
func buildPancakeSessionLabel(senderName, pageName string) string {
|
||||
label := "Pancake"
|
||||
if senderName != "" {
|
||||
label += ":" + senderName
|
||||
}
|
||||
if pageName != "" {
|
||||
label += ":" + pageName
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
// buildAnnounceOutMeta builds outbound metadata for announce messages so that
|
||||
// Send() can route replies to the correct forum topic or DM thread.
|
||||
func buildAnnounceOutMeta(localKey string) map[string]string {
|
||||
|
||||
@@ -108,6 +108,12 @@ func processNormalMessage(
|
||||
}
|
||||
}
|
||||
|
||||
// Set session label for Pancake channels: "Pancake:{SenderName}:{PageName}"
|
||||
if msg.Metadata["pancake_mode"] != "" {
|
||||
label := buildPancakeSessionLabel(msg.Metadata["display_name"], msg.Metadata["page_name"])
|
||||
deps.SessStore.SetLabel(ctx, sessionKey, label)
|
||||
}
|
||||
|
||||
// Auto-collect channel contacts for the contact selector.
|
||||
// Skip internal senders (system:*, notification:*, teammate:*, ticker:*, session_send_tool).
|
||||
if deps.ContactCollector != nil && msg.SenderID != "" && !bus.IsInternalSender(msg.SenderID) {
|
||||
|
||||
@@ -157,6 +157,7 @@ func wireExtras(
|
||||
ContextFileLoader: contextFileLoader,
|
||||
BootstrapCleanup: buildBootstrapCleanup(stores.Agents),
|
||||
CacheInvalidate: buildCacheInvalidate(contextFileInterceptor),
|
||||
DefaultTimezone: appCfg.Cron.DefaultTimezone,
|
||||
InjectionAction: injectionAction,
|
||||
MaxMessageChars: appCfg.Gateway.MaxMessageChars,
|
||||
CompactionCfg: appCfg.Agents.Defaults.Compaction,
|
||||
|
||||
Reference in new issue
Block a user