mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 02:16:56 +00:00
feat: implement slash command handling and text feedback for WhatsApp channel (#819)
This commit is contained in:
1 parent
61993b4387
commit
5773eec167
3 files changed
+116
No files matched your search
@@ -0,0 +1,96 @@
|
||||
package whatsapp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/tools"
|
||||
)
|
||||
|
||||
const menuHelpText = "Available commands:\n" +
|
||||
"/reset — Reset conversation history\n" +
|
||||
"/stop — Stop current running task\n" +
|
||||
"/stopall — Stop all running tasks\n" +
|
||||
"/menu — Show this help message\n" +
|
||||
"\nJust send a message to chat with the AI."
|
||||
|
||||
// handleCommand checks if the message is a known slash command and handles it.
|
||||
// Returns true if the message was handled (caller should stop processing).
|
||||
func (c *Channel) handleCommand(ctx context.Context, text, senderID, chatID, peerKind string, chatJID types.JID) bool {
|
||||
if len(text) == 0 || text[0] != '/' {
|
||||
return false
|
||||
}
|
||||
|
||||
cmd := strings.SplitN(text, " ", 2)[0]
|
||||
cmd = strings.ToLower(cmd)
|
||||
|
||||
switch cmd {
|
||||
case "/reset":
|
||||
c.Bus().PublishInbound(bus.InboundMessage{
|
||||
Channel: c.Name(),
|
||||
SenderID: senderID,
|
||||
ChatID: chatID,
|
||||
Content: "/reset",
|
||||
PeerKind: peerKind,
|
||||
AgentID: c.AgentID(),
|
||||
UserID: stripSenderUserID(senderID),
|
||||
TenantID: c.TenantID(),
|
||||
Metadata: map[string]string{
|
||||
tools.MetaCommand: "reset",
|
||||
},
|
||||
})
|
||||
c.sendText(chatJID, "Conversation history has been reset.")
|
||||
return true
|
||||
|
||||
case "/stop":
|
||||
c.Bus().PublishInbound(bus.InboundMessage{
|
||||
Channel: c.Name(),
|
||||
SenderID: senderID,
|
||||
ChatID: chatID,
|
||||
Content: "/stop",
|
||||
PeerKind: peerKind,
|
||||
AgentID: c.AgentID(),
|
||||
UserID: stripSenderUserID(senderID),
|
||||
TenantID: c.TenantID(),
|
||||
Metadata: map[string]string{
|
||||
tools.MetaCommand: "stop",
|
||||
},
|
||||
})
|
||||
// Feedback is sent by the consumer after cancel result is known.
|
||||
return true
|
||||
|
||||
case "/stopall":
|
||||
c.Bus().PublishInbound(bus.InboundMessage{
|
||||
Channel: c.Name(),
|
||||
SenderID: senderID,
|
||||
ChatID: chatID,
|
||||
Content: "/stopall",
|
||||
PeerKind: peerKind,
|
||||
AgentID: c.AgentID(),
|
||||
UserID: stripSenderUserID(senderID),
|
||||
TenantID: c.TenantID(),
|
||||
Metadata: map[string]string{
|
||||
tools.MetaCommand: "stopall",
|
||||
},
|
||||
})
|
||||
// Feedback is sent by the consumer after cancel result is known.
|
||||
return true
|
||||
|
||||
case "/menu":
|
||||
c.sendText(chatJID, menuHelpText)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// stripSenderUserID extracts the user ID portion before the '|' separator.
|
||||
func stripSenderUserID(senderID string) string {
|
||||
if idx := strings.IndexByte(senderID, '|'); idx > 0 {
|
||||
return senderID[:idx]
|
||||
}
|
||||
return senderID
|
||||
}
|
||||
@@ -94,6 +94,11 @@ func (c *Channel) handleIncomingMessage(evt *events.Message) {
|
||||
|
||||
content := extractTextContent(evt.Message)
|
||||
|
||||
// Command interception: check for slash commands before the normal pipeline.
|
||||
if handled := c.handleCommand(ctx, content, senderID, chatID, peerKind, chatJID); handled {
|
||||
return
|
||||
}
|
||||
|
||||
var mediaList []media.MediaInfo
|
||||
mediaList = c.downloadMedia(evt)
|
||||
|
||||
|
||||
@@ -117,6 +117,21 @@ func (c *Channel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendText sends a plain text message directly to a WhatsApp JID.
|
||||
// Used for command feedback (e.g. /menu, /reset confirmation).
|
||||
func (c *Channel) sendText(chatJID types.JID, text string) {
|
||||
if c.client == nil || !c.client.IsConnected() {
|
||||
slog.Warn("whatsapp: cannot send text, not connected")
|
||||
return
|
||||
}
|
||||
waMsg := &waE2E.Message{
|
||||
Conversation: proto.String(text),
|
||||
}
|
||||
if _, err := c.client.SendMessage(c.ctx, chatJID, waMsg); err != nil {
|
||||
slog.Error("whatsapp: failed to send command reply", "error", err, "chat", chatJID.String())
|
||||
}
|
||||
}
|
||||
|
||||
// buildMediaMessage uploads media to WhatsApp and returns the message proto.
|
||||
func (c *Channel) buildMediaMessage(data []byte, mime, caption string) (*waE2E.Message, error) {
|
||||
switch {
|
||||
|
||||
Reference in new issue
Block a user