fix(qq): route group replies correctly and track reply context
This commit is contained in:
parent
ec6da7a530
commit
0eade09809
2 changed files with 155 additions and 6 deletions
|
|
@ -27,9 +27,32 @@ type QQChannel struct {
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
sessionManager botgo.SessionManager
|
sessionManager botgo.SessionManager
|
||||||
processedIDs map[string]bool
|
processedIDs map[string]bool
|
||||||
|
lastMsgIDs map[string]string
|
||||||
|
msgSeqByChat map[string]uint32
|
||||||
|
chatKindByID map[string]string
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
qqChatKindDirect = "direct"
|
||||||
|
qqChatKindGroup = "group"
|
||||||
|
)
|
||||||
|
|
||||||
|
type qqC2CMessageToCreate struct {
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
MsgType int `json:"msg_type"`
|
||||||
|
MsgID string `json:"msg_id,omitempty"`
|
||||||
|
MsgSeq uint32 `json:"msg_seq,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m qqC2CMessageToCreate) GetEventID() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m qqC2CMessageToCreate) GetSendType() dto.SendType {
|
||||||
|
return dto.Text
|
||||||
|
}
|
||||||
|
|
||||||
func NewQQChannel(cfg config.QQConfig, messageBus *bus.MessageBus) (*QQChannel, error) {
|
func NewQQChannel(cfg config.QQConfig, messageBus *bus.MessageBus) (*QQChannel, error) {
|
||||||
base := NewBaseChannel("qq", cfg, messageBus, cfg.AllowFrom)
|
base := NewBaseChannel("qq", cfg, messageBus, cfg.AllowFrom)
|
||||||
|
|
||||||
|
|
@ -37,6 +60,9 @@ func NewQQChannel(cfg config.QQConfig, messageBus *bus.MessageBus) (*QQChannel,
|
||||||
BaseChannel: base,
|
BaseChannel: base,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
processedIDs: make(map[string]bool),
|
processedIDs: make(map[string]bool),
|
||||||
|
lastMsgIDs: make(map[string]string),
|
||||||
|
msgSeqByChat: make(map[string]uint32),
|
||||||
|
chatKindByID: make(map[string]string),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,15 +143,21 @@ func (c *QQChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct message
|
// construct message
|
||||||
msgToCreate := &dto.MessageToCreate{
|
msgToCreate := c.buildC2CMessage(msg.ChatID, msg.Content)
|
||||||
Content: msg.Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
// send C2C message
|
chatKind := c.resolveChatKind(msg.ChatID)
|
||||||
_, err := c.api.PostC2CMessage(ctx, msg.ChatID, msgToCreate)
|
|
||||||
|
var err error
|
||||||
|
if chatKind == qqChatKindGroup {
|
||||||
|
_, err = c.api.PostGroupMessage(ctx, msg.ChatID, msgToCreate)
|
||||||
|
} else {
|
||||||
|
_, err = c.api.PostC2CMessage(ctx, msg.ChatID, msgToCreate)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorCF("qq", "Failed to send C2C message", map[string]any{
|
logger.ErrorCF("qq", "Failed to send QQ message", map[string]any{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
|
"chat": msg.ChatID,
|
||||||
|
"kind": chatKind,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -169,6 +201,8 @@ func (c *QQChannel) handleC2CMessage() event.C2CMessageEventHandler {
|
||||||
"peer_id": senderID,
|
"peer_id": senderID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.recordInboundMessage(senderID, data.ID, qqChatKindDirect)
|
||||||
|
|
||||||
c.HandleMessage(senderID, senderID, content, []string{}, metadata)
|
c.HandleMessage(senderID, senderID, content, []string{}, metadata)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -213,6 +247,8 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler {
|
||||||
"peer_id": data.GroupID,
|
"peer_id": data.GroupID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.recordInboundMessage(data.GroupID, data.ID, qqChatKindGroup)
|
||||||
|
|
||||||
c.HandleMessage(senderID, data.GroupID, content, []string{}, metadata)
|
c.HandleMessage(senderID, data.GroupID, content, []string{}, metadata)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -245,3 +281,47 @@ func (c *QQChannel) isDuplicate(messageID string) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *QQChannel) recordInboundMessage(chatID, messageID, chatKind string) {
|
||||||
|
if chatID == "" || messageID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
c.lastMsgIDs[chatID] = messageID
|
||||||
|
c.msgSeqByChat[chatID] = 0
|
||||||
|
if chatKind != "" {
|
||||||
|
c.chatKindByID[chatID] = chatKind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *QQChannel) buildC2CMessage(chatID, content string) *qqC2CMessageToCreate {
|
||||||
|
msg := &qqC2CMessageToCreate{
|
||||||
|
Content: content,
|
||||||
|
MsgType: int(dto.TextMsg),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if lastMsgID := c.lastMsgIDs[chatID]; lastMsgID != "" {
|
||||||
|
c.msgSeqByChat[chatID]++
|
||||||
|
msg.MsgID = lastMsgID
|
||||||
|
msg.MsgSeq = c.msgSeqByChat[chatID]
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *QQChannel) resolveChatKind(chatID string) string {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
|
if kind := c.chatKindByID[chatID]; kind != "" {
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
|
||||||
|
return qqChatKindDirect
|
||||||
|
}
|
||||||
|
|
|
||||||
69
pkg/channels/qq_test.go
Normal file
69
pkg/channels/qq_test.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tencent-connect/botgo/dto"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQQChannel_RecordInboundAndBuildMessage(t *testing.T) {
|
||||||
|
channel, err := NewQQChannel(config.QQConfig{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create qq channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.recordInboundMessage("group-1", "msg-a", qqChatKindGroup)
|
||||||
|
|
||||||
|
if got := channel.resolveChatKind("group-1"); got != qqChatKindGroup {
|
||||||
|
t.Fatalf("expected chat kind %q, got %q", qqChatKindGroup, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
first := channel.buildC2CMessage("group-1", "hello")
|
||||||
|
if first.MsgType != int(dto.TextMsg) {
|
||||||
|
t.Fatalf("expected msg_type %d, got %d", int(dto.TextMsg), first.MsgType)
|
||||||
|
}
|
||||||
|
if first.MsgID != "msg-a" {
|
||||||
|
t.Fatalf("expected msg_id msg-a, got %q", first.MsgID)
|
||||||
|
}
|
||||||
|
if first.MsgSeq != 1 {
|
||||||
|
t.Fatalf("expected first msg_seq 1, got %d", first.MsgSeq)
|
||||||
|
}
|
||||||
|
|
||||||
|
second := channel.buildC2CMessage("group-1", "hello again")
|
||||||
|
if second.MsgSeq != 2 {
|
||||||
|
t.Fatalf("expected second msg_seq 2, got %d", second.MsgSeq)
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.recordInboundMessage("group-1", "msg-b", qqChatKindGroup)
|
||||||
|
third := channel.buildC2CMessage("group-1", "after new inbound")
|
||||||
|
if third.MsgID != "msg-b" {
|
||||||
|
t.Fatalf("expected latest msg_id msg-b, got %q", third.MsgID)
|
||||||
|
}
|
||||||
|
if third.MsgSeq != 1 {
|
||||||
|
t.Fatalf("expected msg_seq reset to 1 after new inbound, got %d", third.MsgSeq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQQChannel_DefaultChatKindAndMessageWithoutContext(t *testing.T) {
|
||||||
|
channel, err := NewQQChannel(config.QQConfig{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create qq channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := channel.resolveChatKind("unknown-chat"); got != qqChatKindDirect {
|
||||||
|
t.Fatalf("expected default chat kind %q, got %q", qqChatKindDirect, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := channel.buildC2CMessage("unknown-chat", "plain")
|
||||||
|
if msg.MsgType != int(dto.TextMsg) {
|
||||||
|
t.Fatalf("expected msg_type %d, got %d", int(dto.TextMsg), msg.MsgType)
|
||||||
|
}
|
||||||
|
if msg.MsgID != "" {
|
||||||
|
t.Fatalf("expected empty msg_id for chat without context, got %q", msg.MsgID)
|
||||||
|
}
|
||||||
|
if msg.MsgSeq != 0 {
|
||||||
|
t.Fatalf("expected msg_seq 0 for chat without context, got %d", msg.MsgSeq)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue