- Introduced Weixin integration support with new configuration options for WeChat iLink Bot. - Updated existing adapters (DingTalk, Discord, Feishu, Telegram) to include sender_id and app_id in message metadata for improved context handling. - Enhanced dispatcher logic to accommodate the new Weixin adapter and ensure proper initialization and shutdown processes. - Improved message handling across integrations to support typing indicators, providing a more interactive user experience.
140 lines
2.9 KiB
Go
140 lines
2.9 KiB
Go
package discord
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
agentcontext "github.com/yaoapp/yao/agent/context"
|
|
events "github.com/yaoapp/yao/agent/robot/events"
|
|
"github.com/yaoapp/yao/event"
|
|
dcapi "github.com/yaoapp/yao/integrations/discord"
|
|
)
|
|
|
|
// handleMessages processes a batch of Discord messages.
|
|
func (a *Adapter) handleMessages(ctx context.Context, entry *botEntry, cms []*dcapi.ConvertedMessage) {
|
|
if len(cms) == 0 {
|
|
return
|
|
}
|
|
|
|
var allParts []interface{}
|
|
var lastCM *dcapi.ConvertedMessage
|
|
|
|
for _, cm := range cms {
|
|
if cm == nil {
|
|
continue
|
|
}
|
|
|
|
// Skip bot commands (messages starting with /)
|
|
if strings.HasPrefix(strings.TrimSpace(cm.Text), "/") && !cm.HasMedia() {
|
|
continue
|
|
}
|
|
|
|
dedupKey := fmt.Sprintf("dc:%s:%s", entry.robotID, cm.MessageID)
|
|
if !a.dedup.markSeen(dedupKey) {
|
|
continue
|
|
}
|
|
|
|
parts := buildContentParts(cm)
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
|
|
allParts = append(allParts, parts...)
|
|
lastCM = cm
|
|
}
|
|
|
|
if len(allParts) == 0 || lastCM == nil {
|
|
return
|
|
}
|
|
|
|
content := mergeContentParts(allParts)
|
|
|
|
msgPayload := events.MessagePayload{
|
|
RobotID: entry.robotID,
|
|
Messages: []agentcontext.Message{
|
|
{Role: agentcontext.RoleUser, Content: content},
|
|
},
|
|
Metadata: &events.MessageMetadata{
|
|
Channel: "discord",
|
|
MessageID: lastCM.MessageID,
|
|
AppID: entry.appID,
|
|
ChatID: lastCM.ChannelID,
|
|
SenderID: lastCM.AuthorID,
|
|
SenderName: lastCM.AuthorName,
|
|
Locale: events.NormalizeLocale(discordLocale(lastCM.Locale)),
|
|
Extra: map[string]any{
|
|
"discord_message_id": lastCM.MessageID,
|
|
"guild_id": lastCM.GuildID,
|
|
"is_dm": lastCM.IsDM,
|
|
"sender_id": lastCM.AuthorID,
|
|
"app_id": entry.appID,
|
|
},
|
|
},
|
|
}
|
|
|
|
if _, err := event.Push(ctx, events.Message, msgPayload); err != nil {
|
|
log.Error("discord adapter: event.Push robot.message failed robot=%s: %v", entry.robotID, err)
|
|
}
|
|
}
|
|
|
|
func buildContentParts(cm *dcapi.ConvertedMessage) []interface{} {
|
|
var parts []interface{}
|
|
|
|
if cm.HasText() {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "text",
|
|
"text": cm.Text,
|
|
})
|
|
}
|
|
|
|
for _, mi := range cm.MediaItems {
|
|
url := mi.Wrapper
|
|
if url == "" {
|
|
url = mi.URL
|
|
}
|
|
if url == "" {
|
|
continue
|
|
}
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "file",
|
|
"file_url": url,
|
|
"mime_type": mi.ContentType,
|
|
"file_name": mi.FileName,
|
|
})
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
func mergeContentParts(parts []interface{}) interface{} {
|
|
allText := true
|
|
for _, p := range parts {
|
|
m, ok := p.(map[string]interface{})
|
|
if !ok || m["type"] != "text" {
|
|
allText = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if allText {
|
|
var buf strings.Builder
|
|
for i, p := range parts {
|
|
if i > 0 {
|
|
buf.WriteString("\n")
|
|
}
|
|
m := p.(map[string]interface{})
|
|
buf.WriteString(m["text"].(string))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
func discordLocale(locale string) string {
|
|
if locale == "" {
|
|
return "en"
|
|
}
|
|
return locale
|
|
}
|