feat(identity): add unified user identity with canonical platform:id format

Introduce SenderInfo struct and pkg/identity package to standardize user
identification across all channels. Each channel now constructs structured
sender info (platform, platformID, canonicalID, username, displayName)
instead of ad-hoc string IDs. Allow-list matching supports all legacy
formats (numeric ID, @username, id|username) plus the new canonical
"platform:id" format. Session key resolution also handles canonical
peerIDs for backward-compatible identity link matching.
This commit is contained in:
Hoshina
2026-02-23 06:56:48 +08:00
parent f645e9a377
commit 56d80373eb
20 changed files with 742 additions and 34 deletions
+41 -3
View File
@@ -9,6 +9,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
)
@@ -20,6 +21,7 @@ type Channel interface {
Send(ctx context.Context, msg bus.OutboundMessage) error
IsRunning() bool
IsAllowed(senderID string) bool
IsAllowedSender(sender bus.SenderInfo) bool
}
// BaseChannelOption is a functional option for configuring a BaseChannel.
@@ -168,22 +170,58 @@ func (c *BaseChannel) IsAllowed(senderID string) bool {
return false
}
// IsAllowedSender checks whether a structured SenderInfo is permitted by the allow-list.
// It delegates to identity.MatchAllowed for each entry, providing unified matching
// across all legacy formats and the new canonical "platform:id" format.
func (c *BaseChannel) IsAllowedSender(sender bus.SenderInfo) bool {
if len(c.allowList) == 0 {
return true
}
for _, allowed := range c.allowList {
if identity.MatchAllowed(sender, allowed) {
return true
}
}
return false
}
func (c *BaseChannel) HandleMessage(
ctx context.Context,
peer bus.Peer,
messageID, senderID, chatID, content string,
media []string,
metadata map[string]string,
senderOpts ...bus.SenderInfo,
) {
if !c.IsAllowed(senderID) {
return
// Use SenderInfo-based allow check when available, else fall back to string
var sender bus.SenderInfo
if len(senderOpts) > 0 {
sender = senderOpts[0]
}
if sender.CanonicalID != "" || sender.PlatformID != "" {
if !c.IsAllowedSender(sender) {
return
}
} else {
if !c.IsAllowed(senderID) {
return
}
}
// Set SenderID to canonical if available, otherwise keep the raw senderID
resolvedSenderID := senderID
if sender.CanonicalID != "" {
resolvedSenderID = sender.CanonicalID
}
scope := BuildMediaScope(c.name, chatID, messageID)
msg := bus.InboundMessage{
Channel: c.name,
SenderID: senderID,
SenderID: resolvedSenderID,
Sender: sender,
ChatID: chatID,
Content: content,
Media: media,
+88
View File
@@ -3,6 +3,7 @@ package channels
import (
"testing"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/config"
)
@@ -175,3 +176,90 @@ func TestShouldRespondInGroup(t *testing.T) {
})
}
}
func TestIsAllowedSender(t *testing.T) {
tests := []struct {
name string
allowList []string
sender bus.SenderInfo
want bool
}{
{
name: "empty allowlist allows all",
allowList: nil,
sender: bus.SenderInfo{PlatformID: "anyone"},
want: true,
},
{
name: "numeric ID matches PlatformID",
allowList: []string{"123456"},
sender: bus.SenderInfo{
Platform: "telegram",
PlatformID: "123456",
CanonicalID: "telegram:123456",
},
want: true,
},
{
name: "canonical format matches",
allowList: []string{"telegram:123456"},
sender: bus.SenderInfo{
Platform: "telegram",
PlatformID: "123456",
CanonicalID: "telegram:123456",
},
want: true,
},
{
name: "canonical format wrong platform",
allowList: []string{"discord:123456"},
sender: bus.SenderInfo{
Platform: "telegram",
PlatformID: "123456",
CanonicalID: "telegram:123456",
},
want: false,
},
{
name: "@username matches",
allowList: []string{"@alice"},
sender: bus.SenderInfo{
Platform: "telegram",
PlatformID: "123456",
CanonicalID: "telegram:123456",
Username: "alice",
},
want: true,
},
{
name: "compound id|username matches by ID",
allowList: []string{"123456|alice"},
sender: bus.SenderInfo{
Platform: "telegram",
PlatformID: "123456",
CanonicalID: "telegram:123456",
Username: "alice",
},
want: true,
},
{
name: "non matching sender denied",
allowList: []string{"654321"},
sender: bus.SenderInfo{
Platform: "telegram",
PlatformID: "123456",
CanonicalID: "telegram:123456",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ch := NewBaseChannel("test", nil, nil, tt.allowList)
if got := ch.IsAllowedSender(tt.sender); got != tt.want {
t.Fatalf("IsAllowedSender(%+v) = %v, want %v", tt.sender, got, tt.want)
}
})
}
}
+14 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -182,8 +183,20 @@ func (c *DingTalkChannel) onChatBotMessageReceived(
"preview": utils.Truncate(content, 50),
})
// Build sender info
sender := bus.SenderInfo{
Platform: "dingtalk",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("dingtalk", senderID),
DisplayName: senderNick,
}
if !c.IsAllowedSender(sender) {
return nil, nil
}
// Handle the message through the base channel
c.HandleMessage(ctx, peer, "", senderID, chatID, content, nil, metadata)
c.HandleMessage(ctx, peer, "", senderID, chatID, content, nil, metadata, sender)
// Return nil to indicate we've handled the message asynchronously
// The response will be sent through the message bus
+18 -8
View File
@@ -13,6 +13,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/utils"
@@ -263,7 +264,20 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
}
// Check allowlist first to avoid downloading attachments for rejected users
if !c.IsAllowed(m.Author.ID) {
sender := bus.SenderInfo{
Platform: "discord",
PlatformID: m.Author.ID,
CanonicalID: identity.BuildCanonicalID("discord", m.Author.ID),
Username: m.Author.Username,
}
// Build display name
displayName := m.Author.Username
if m.Author.Discriminator != "" && m.Author.Discriminator != "0" {
displayName += "#" + m.Author.Discriminator
}
sender.DisplayName = displayName
if !c.IsAllowedSender(sender) {
logger.DebugCF("discord", "Message rejected by allowlist", map[string]any{
"user_id": m.Author.ID,
})
@@ -297,10 +311,6 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
}
senderID := m.Author.ID
senderName := m.Author.Username
if m.Author.Discriminator != "" && m.Author.Discriminator != "0" {
senderName += "#" + m.Author.Discriminator
}
mediaPaths := make([]string, 0, len(m.Attachments))
@@ -358,7 +368,7 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
}
logger.DebugCF("discord", "Received message", map[string]any{
"sender_name": senderName,
"sender_name": sender.DisplayName,
"sender_id": senderID,
"preview": utils.Truncate(content, 50),
})
@@ -375,13 +385,13 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
metadata := map[string]string{
"user_id": senderID,
"username": m.Author.Username,
"display_name": senderName,
"display_name": sender.DisplayName,
"guild_id": m.GuildID,
"channel_id": m.ChannelID,
"is_dm": fmt.Sprintf("%t", m.GuildID == ""),
}
c.HandleMessage(c.ctx, peer, m.ID, senderID, m.ChannelID, content, mediaPaths, metadata)
c.HandleMessage(c.ctx, peer, m.ID, senderID, m.ChannelID, content, mediaPaths, metadata, sender)
}
// startTyping starts a continuous typing indicator loop for the given chatID.
+12 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -189,7 +190,17 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim.
"preview": utils.Truncate(content, 80),
})
c.HandleMessage(ctx, peer, messageID, senderID, chatID, content, nil, metadata)
senderInfo := bus.SenderInfo{
Platform: "feishu",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("feishu", senderID),
}
if !c.IsAllowedSender(senderInfo) {
return nil
}
c.HandleMessage(ctx, peer, messageID, senderID, chatID, content, nil, metadata, senderInfo)
return nil
}
+12 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/utils"
@@ -370,7 +371,17 @@ func (c *LINEChannel) processEvent(event lineEvent) {
// Show typing/loading indicator (requires user ID, not group ID)
c.sendLoading(senderID)
c.HandleMessage(c.ctx, peer, msg.ID, senderID, chatID, content, mediaPaths, metadata)
sender := bus.SenderInfo{
Platform: "line",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("line", senderID),
}
if !c.IsAllowedSender(sender) {
return
}
c.HandleMessage(c.ctx, peer, msg.ID, senderID, chatID, content, mediaPaths, metadata, sender)
}
// isBotMentioned checks if the bot is mentioned in the message.
+12
View File
@@ -11,6 +11,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
)
@@ -179,6 +180,16 @@ func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) {
"h": fmt.Sprintf("%.0f", h),
}
sender := bus.SenderInfo{
Platform: "maixcam",
PlatformID: "maixcam",
CanonicalID: identity.BuildCanonicalID("maixcam", "maixcam"),
}
if !c.IsAllowedSender(sender) {
return
}
c.HandleMessage(
c.ctx,
bus.Peer{Kind: "channel", ID: "default"},
@@ -188,6 +199,7 @@ func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) {
content,
[]string{},
metadata,
sender,
)
}
+23 -2
View File
@@ -15,6 +15,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/utils"
@@ -823,7 +824,13 @@ func (c *OneBotChannel) handleRawEvent(raw *oneBotRawEvent) {
switch raw.PostType {
case "message":
if userID, err := parseJSONInt64(raw.UserID); err == nil && userID > 0 {
if !c.IsAllowed(strconv.FormatInt(userID, 10)) {
// Build minimal sender for allowlist check
sender := bus.SenderInfo{
Platform: "onebot",
PlatformID: strconv.FormatInt(userID, 10),
CanonicalID: identity.BuildCanonicalID("onebot", strconv.FormatInt(userID, 10)),
}
if !c.IsAllowedSender(sender) {
logger.DebugCF("onebot", "Message rejected by allowlist", map[string]any{
"user_id": userID,
})
@@ -1040,7 +1047,21 @@ func (c *OneBotChannel) handleMessage(raw *oneBotRawEvent) {
}
}
c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, parsed.Media, metadata)
senderInfo := bus.SenderInfo{
Platform: "onebot",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("onebot", senderID),
DisplayName: sender.Nickname,
}
if !c.IsAllowedSender(senderInfo) {
logger.DebugCF("onebot", "Message rejected by allowlist (senderInfo)", map[string]any{
"sender": senderID,
})
return
}
c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, parsed.Media, metadata, senderInfo)
}
func (c *OneBotChannel) isDuplicate(messageID string) bool {
+12 -1
View File
@@ -16,6 +16,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
)
@@ -420,7 +421,17 @@ func (c *PicoChannel) handleMessageSend(pc *picoConn, msg PicoMessage) {
}
}
c.HandleMessage(c.ctx, peer, msg.ID, senderID, chatID, content, nil, metadata)
sender := bus.SenderInfo{
Platform: "pico",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("pico", senderID),
}
if !c.IsAllowedSender(sender) {
return
}
c.HandleMessage(c.ctx, peer, msg.ID, senderID, chatID, content, nil, metadata, sender)
}
// truncate truncates a string to maxLen runes.
+23
View File
@@ -16,6 +16,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
)
@@ -168,6 +169,16 @@ func (c *QQChannel) handleC2CMessage() event.C2CMessageEventHandler {
// 转发到消息总线
metadata := map[string]string{}
sender := bus.SenderInfo{
Platform: "qq",
PlatformID: data.Author.ID,
CanonicalID: identity.BuildCanonicalID("qq", data.Author.ID),
}
if !c.IsAllowedSender(sender) {
return nil
}
c.HandleMessage(c.ctx,
bus.Peer{Kind: "direct", ID: senderID},
data.ID,
@@ -176,6 +187,7 @@ func (c *QQChannel) handleC2CMessage() event.C2CMessageEventHandler {
content,
[]string{},
metadata,
sender,
)
return nil
@@ -224,6 +236,16 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler {
"group_id": data.GroupID,
}
sender := bus.SenderInfo{
Platform: "qq",
PlatformID: data.Author.ID,
CanonicalID: identity.BuildCanonicalID("qq", data.Author.ID),
}
if !c.IsAllowedSender(sender) {
return nil
}
c.HandleMessage(c.ctx,
bus.Peer{Kind: "group", ID: data.GroupID},
data.ID,
@@ -232,6 +254,7 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler {
content,
[]string{},
metadata,
sender,
)
return nil
+36 -6
View File
@@ -13,6 +13,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/utils"
@@ -252,7 +253,12 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
}
// 检查白名单,避免为被拒绝的用户下载附件
if !c.IsAllowed(ev.User) {
sender := bus.SenderInfo{
Platform: "slack",
PlatformID: ev.User,
CanonicalID: identity.BuildCanonicalID("slack", ev.User),
}
if !c.IsAllowedSender(sender) {
logger.DebugCF("slack", "Message rejected by allowlist", map[string]any{
"user_id": ev.User,
})
@@ -360,7 +366,7 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
"has_thread": threadTS != "",
})
c.HandleMessage(c.ctx, peer, messageTS, senderID, chatID, content, mediaPaths, metadata)
c.HandleMessage(c.ctx, peer, messageTS, senderID, chatID, content, mediaPaths, metadata, sender)
}
func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
@@ -368,7 +374,11 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
return
}
if !c.IsAllowed(ev.User) {
if !c.IsAllowedSender(bus.SenderInfo{
Platform: "slack",
PlatformID: ev.User,
CanonicalID: identity.BuildCanonicalID("slack", ev.User),
}) {
logger.DebugCF("slack", "Mention rejected by allowlist", map[string]any{
"user_id": ev.User,
})
@@ -376,6 +386,11 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
}
senderID := ev.User
mentionSender := bus.SenderInfo{
Platform: "slack",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("slack", senderID),
}
channelID := ev.Channel
threadTS := ev.ThreadTimeStamp
messageTS := ev.TimeStamp
@@ -433,7 +448,7 @@ func (c *SlackChannel) handleAppMention(ev *slackevents.AppMentionEvent) {
"team_id": c.teamID,
}
c.HandleMessage(c.ctx, mentionPeer, messageTS, senderID, chatID, content, nil, metadata)
c.HandleMessage(c.ctx, mentionPeer, messageTS, senderID, chatID, content, nil, metadata, mentionSender)
}
func (c *SlackChannel) handleSlashCommand(event socketmode.Event) {
@@ -446,7 +461,12 @@ func (c *SlackChannel) handleSlashCommand(event socketmode.Event) {
c.socketClient.Ack(*event.Request)
}
if !c.IsAllowed(cmd.UserID) {
cmdSender := bus.SenderInfo{
Platform: "slack",
PlatformID: cmd.UserID,
CanonicalID: identity.BuildCanonicalID("slack", cmd.UserID),
}
if !c.IsAllowedSender(cmdSender) {
logger.DebugCF("slack", "Slash command rejected by allowlist", map[string]any{
"user_id": cmd.UserID,
})
@@ -476,7 +496,17 @@ func (c *SlackChannel) handleSlashCommand(event socketmode.Event) {
"text": utils.Truncate(content, 50),
})
c.HandleMessage(c.ctx, bus.Peer{Kind: "channel", ID: channelID}, "", senderID, chatID, content, nil, metadata)
c.HandleMessage(
c.ctx,
bus.Peer{Kind: "channel", ID: channelID},
"",
senderID,
chatID,
content,
nil,
metadata,
cmdSender,
)
}
func (c *SlackChannel) downloadSlackFile(file slack.File) string {
+14 -8
View File
@@ -19,6 +19,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/utils"
@@ -289,21 +290,25 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
return fmt.Errorf("message sender (user) is nil")
}
senderID := fmt.Sprintf("%d", user.ID)
if user.Username != "" {
senderID = fmt.Sprintf("%d|%s", user.ID, user.Username)
platformID := fmt.Sprintf("%d", user.ID)
sender := bus.SenderInfo{
Platform: "telegram",
PlatformID: platformID,
CanonicalID: identity.BuildCanonicalID("telegram", platformID),
Username: user.Username,
DisplayName: user.FirstName,
}
// 检查白名单,避免为被拒绝的用户下载附件
if !c.IsAllowed(senderID) {
if !c.IsAllowedSender(sender) {
logger.DebugCF("telegram", "Message rejected by allowlist", map[string]any{
"user_id": senderID,
"user_id": platformID,
})
return nil
}
chatID := message.Chat.ID
c.chatIDs[senderID] = chatID
c.chatIDs[platformID] = chatID
content := ""
mediaPaths := []string{}
@@ -401,7 +406,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
}
logger.DebugCF("telegram", "Received message", map[string]any{
"sender_id": senderID,
"sender_id": sender.CanonicalID,
"chat_id": fmt.Sprintf("%d", chatID),
"preview": utils.Truncate(content, 50),
})
@@ -451,11 +456,12 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
c.HandleMessage(c.ctx,
peer,
messageID,
fmt.Sprintf("%d", user.ID),
platformID,
fmt.Sprintf("%d", chatID),
content,
mediaPaths,
metadata,
sender,
)
return nil
}
+9 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -629,8 +630,15 @@ func (c *WeComAppChannel) processMessage(ctx context.Context, msg WeComXMLMessag
"preview": utils.Truncate(content, 50),
})
// Build sender info
appSender := bus.SenderInfo{
Platform: "wecom",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("wecom", senderID),
}
// Handle the message through the base channel
c.HandleMessage(ctx, peer, messageID, senderID, chatID, content, nil, metadata)
c.HandleMessage(ctx, peer, messageID, senderID, chatID, content, nil, metadata, appSender)
}
// tokenRefreshLoop periodically refreshes the access token
+13 -1
View File
@@ -15,6 +15,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -398,8 +399,19 @@ func (c *WeComBotChannel) processMessage(ctx context.Context, msg WeComBotMessag
"preview": utils.Truncate(content, 50),
})
// Build sender info
sender := bus.SenderInfo{
Platform: "wecom",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("wecom", senderID),
}
if !c.IsAllowedSender(sender) {
return
}
// Handle the message through the base channel
c.HandleMessage(ctx, peer, msg.MsgID, senderID, chatID, content, nil, metadata)
c.HandleMessage(ctx, peer, msg.MsgID, senderID, chatID, content, nil, metadata, sender)
}
// sendWebhookReply sends a reply using the webhook URL
+15 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -224,5 +225,18 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]any) {
"preview": utils.Truncate(content, 50),
})
c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, mediaPaths, metadata)
sender := bus.SenderInfo{
Platform: "whatsapp",
PlatformID: senderID,
CanonicalID: identity.BuildCanonicalID("whatsapp", senderID),
}
if display, ok := metadata["user_name"]; ok {
sender.DisplayName = display
}
if !c.IsAllowedSender(sender) {
return
}
c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender)
}