package channels import ( "context" "fmt" "strings" "sync/atomic" "clawgo/pkg/bus" "clawgo/pkg/logger" ) type Channel interface { Name() string Start(ctx context.Context) error Stop(ctx context.Context) error Send(ctx context.Context, msg bus.OutboundMessage) error IsRunning() bool IsAllowed(senderID string) bool HealthCheck(ctx context.Context) error } // ActionCapable is an optional capability interface for channels that support non-send message actions. type ActionCapable interface { SupportsAction(action string) bool } type BaseChannel struct { config interface{} bus *bus.MessageBus running atomic.Bool name string allowList []string } func NewBaseChannel(name string, config interface{}, bus *bus.MessageBus, allowList []string) *BaseChannel { return &BaseChannel{ config: config, bus: bus, name: name, allowList: allowList, } } func (c *BaseChannel) Name() string { return c.name } func (c *BaseChannel) IsRunning() bool { return c.running.Load() } func (c *BaseChannel) HealthCheck(ctx context.Context) error { if !c.IsRunning() { return fmt.Errorf("%s channel not running", c.name) } return nil } func (c *BaseChannel) IsAllowed(senderID string) bool { if len(c.allowList) == 0 { return true } // Normalize sender id for channels that include display suffix, e.g. "12345|alice". rawSenderID := senderID if idx := strings.Index(senderID, "|"); idx > 0 { rawSenderID = senderID[:idx] } for _, allowed := range c.allowList { if senderID == allowed || rawSenderID == allowed { return true } } return false } func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []string, metadata map[string]string) { if !c.IsAllowed(senderID) { logger.WarnCF("channels", "Message rejected by allowlist", map[string]interface{}{ logger.FieldChannel: c.name, logger.FieldSenderID: senderID, logger.FieldChatID: chatID, }) return } // Build session key: channel:chatID sessionKey := fmt.Sprintf("%s:%s", c.name, chatID) msg := bus.InboundMessage{ Channel: c.name, SenderID: senderID, ChatID: chatID, Content: content, Media: media, Metadata: metadata, SessionKey: sessionKey, } c.bus.PublishInbound(msg) } func (c *BaseChannel) setRunning(running bool) { c.running.Store(running) }