- Updated the GetStepsForResume method to return only steps with valid resume statuses (failed or interrupted), improving recovery context handling. - Enhanced error handling in message processing across various integrations, including friendly error messages for users in different locales. - Modified file handling in message integrations (Dingtalk, Discord, Feishu, Telegram, Weixin) to differentiate between image and file types, ensuring appropriate content delivery.
151 lines
3.2 KiB
Go
151 lines
3.2 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
|
|
}
|
|
if strings.HasPrefix(mi.ContentType, "image/") {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "image_url",
|
|
"image_url": map[string]interface{}{
|
|
"url": url,
|
|
"detail": "auto",
|
|
},
|
|
})
|
|
} else {
|
|
parts = append(parts, map[string]interface{}{
|
|
"type": "file",
|
|
"file": map[string]interface{}{
|
|
"url": url,
|
|
"filename": 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
|
|
}
|