feat(whatsapp): reuse shared dispatcher for text commands

This commit is contained in:
mingmxren 2026-03-01 02:33:44 +08:00
parent 9350e9d4c4
commit 414877da9d
4 changed files with 107 additions and 0 deletions

View file

@ -11,6 +11,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels" "github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity" "github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
@ -22,6 +23,7 @@ type WhatsAppChannel struct {
conn *websocket.Conn conn *websocket.Conn
config config.WhatsAppConfig config config.WhatsAppConfig
url string url string
dispatcher commands.Dispatching
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
mu sync.Mutex mu sync.Mutex
@ -42,6 +44,7 @@ func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsA
BaseChannel: base, BaseChannel: base,
config: cfg, config: cfg,
url: cfg.BridgeURL, url: cfg.BridgeURL,
dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(nil))),
connected: false, connected: false,
}, nil }, nil
} }
@ -248,5 +251,34 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]any) {
return return
} }
if c.tryHandleCommand(c.ctx, content, chatID, senderID, messageID) {
return
}
c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender) 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
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -30,6 +30,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels" "github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity" "github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/logger"
@ -55,6 +56,7 @@ type WhatsAppNativeChannel struct {
mu sync.Mutex mu sync.Mutex
runCtx context.Context runCtx context.Context
runCancel context.CancelFunc runCancel context.CancelFunc
dispatcher commands.Dispatching
reconnectMu sync.Mutex reconnectMu sync.Mutex
reconnecting bool reconnecting bool
stopping atomic.Bool // set once Stop begins; prevents new wg.Add calls stopping atomic.Bool // set once Stop begins; prevents new wg.Add calls
@ -76,6 +78,7 @@ func NewWhatsAppNativeChannel(
BaseChannel: base, BaseChannel: base,
config: cfg, config: cfg,
storePath: storePath, storePath: storePath,
dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(nil))),
} }
return c, nil return c, nil
} }
@ -387,6 +390,9 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) {
if !c.IsAllowedSender(sender) { if !c.IsAllowedSender(sender) {
return return
} }
if c.tryHandleCommand(c.runCtx, content, chatID, senderID, messageID) {
return
}
logger.DebugCF( logger.DebugCF(
"whatsapp", "whatsapp",
@ -396,6 +402,29 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) {
c.HandleMessage(c.runCtx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender) 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 { func (c *WhatsAppNativeChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
if !c.IsRunning() { if !c.IsRunning() {
return channels.ErrNotRunning return channels.ErrNotRunning