diff --git a/pkg/channels/whatsapp/whatsapp.go b/pkg/channels/whatsapp/whatsapp.go index 70b3e02bf..f37df9097 100644 --- a/pkg/channels/whatsapp/whatsapp.go +++ b/pkg/channels/whatsapp/whatsapp.go @@ -11,6 +11,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/channels" + "github.com/sipeed/picoclaw/pkg/commands" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/identity" "github.com/sipeed/picoclaw/pkg/logger" @@ -22,6 +23,7 @@ type WhatsAppChannel struct { conn *websocket.Conn config config.WhatsAppConfig url string + dispatcher commands.Dispatching ctx context.Context cancel context.CancelFunc mu sync.Mutex @@ -42,6 +44,7 @@ func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsA BaseChannel: base, config: cfg, url: cfg.BridgeURL, + dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(nil))), connected: false, }, nil } @@ -248,5 +251,34 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]any) { return } + if c.tryHandleCommand(c.ctx, content, chatID, senderID, messageID) { + return + } + c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender) } + +func (c *WhatsAppChannel) tryHandleCommand( + ctx context.Context, + text, chatID, senderID, messageID string, +) bool { + if c.dispatcher == nil { + return false + } + + res := c.dispatcher.Dispatch(ctx, commands.Request{ + Channel: "whatsapp", + ChatID: chatID, + SenderID: senderID, + Text: text, + MessageID: messageID, + }) + if res.Err != nil { + logger.WarnCF("whatsapp", "Command execution failed", map[string]any{ + "command": res.Command, + "error": res.Err.Error(), + }) + } + + return res.Matched +} diff --git a/pkg/channels/whatsapp/whatsapp_command_test.go b/pkg/channels/whatsapp/whatsapp_command_test.go new file mode 100644 index 000000000..b8aac18f0 --- /dev/null +++ b/pkg/channels/whatsapp/whatsapp_command_test.go @@ -0,0 +1,22 @@ +package whatsapp + +import ( + "context" + "testing" + + "github.com/sipeed/picoclaw/pkg/commands" +) + +func TestTryHandleCommand_UsesDispatcher(t *testing.T) { + ch := &WhatsAppChannel{} + called := false + ch.dispatcher = commands.DispatchFunc(func(context.Context, commands.Request) commands.Result { + called = true + return commands.Result{Matched: true} + }) + + handled := ch.tryHandleCommand(context.Background(), "/help", "chat1", "user1", "mid1") + if !handled || !called { + t.Fatalf("handled=%v called=%v", handled, called) + } +} diff --git a/pkg/channels/whatsapp_native/whatsapp_command_test.go b/pkg/channels/whatsapp_native/whatsapp_command_test.go new file mode 100644 index 000000000..fbf85d5bc --- /dev/null +++ b/pkg/channels/whatsapp_native/whatsapp_command_test.go @@ -0,0 +1,24 @@ +//go:build whatsapp_native + +package whatsapp + +import ( + "context" + "testing" + + "github.com/sipeed/picoclaw/pkg/commands" +) + +func TestTryHandleCommand_UsesDispatcher(t *testing.T) { + ch := &WhatsAppNativeChannel{} + called := false + ch.dispatcher = commands.DispatchFunc(func(context.Context, commands.Request) commands.Result { + called = true + return commands.Result{Matched: true} + }) + + handled := ch.tryHandleCommand(context.Background(), "/help", "chat1", "user1", "mid1") + if !handled || !called { + t.Fatalf("handled=%v called=%v", handled, called) + } +} diff --git a/pkg/channels/whatsapp_native/whatsapp_native.go b/pkg/channels/whatsapp_native/whatsapp_native.go index 188a7c8fa..5289d9dcb 100644 --- a/pkg/channels/whatsapp_native/whatsapp_native.go +++ b/pkg/channels/whatsapp_native/whatsapp_native.go @@ -30,6 +30,7 @@ import ( "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/channels" + "github.com/sipeed/picoclaw/pkg/commands" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/identity" "github.com/sipeed/picoclaw/pkg/logger" @@ -55,6 +56,7 @@ type WhatsAppNativeChannel struct { mu sync.Mutex runCtx context.Context runCancel context.CancelFunc + dispatcher commands.Dispatching reconnectMu sync.Mutex reconnecting bool stopping atomic.Bool // set once Stop begins; prevents new wg.Add calls @@ -76,6 +78,7 @@ func NewWhatsAppNativeChannel( BaseChannel: base, config: cfg, storePath: storePath, + dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(nil))), } return c, nil } @@ -387,6 +390,9 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) { if !c.IsAllowedSender(sender) { return } + if c.tryHandleCommand(c.runCtx, content, chatID, senderID, messageID) { + return + } logger.DebugCF( "whatsapp", @@ -396,6 +402,29 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) { c.HandleMessage(c.runCtx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender) } +func (c *WhatsAppNativeChannel) tryHandleCommand( + ctx context.Context, + text, chatID, senderID, messageID string, +) bool { + if c.dispatcher == nil { + return false + } + res := c.dispatcher.Dispatch(ctx, commands.Request{ + Channel: "whatsapp_native", + ChatID: chatID, + SenderID: senderID, + Text: text, + MessageID: messageID, + }) + if res.Err != nil { + logger.WarnCF("whatsapp", "Command execution failed", map[string]any{ + "command": res.Command, + "error": res.Err.Error(), + }) + } + return res.Matched +} + func (c *WhatsAppNativeChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { if !c.IsRunning() { return channels.ErrNotRunning