mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(channels): add MediaSender optional interface for outbound media
Add outbound media sending capability so the agent can publish media attachments (images, files, audio, video) through channels via the bus. - Add MediaPart and OutboundMediaMessage types to bus - Add PublishOutboundMedia/SubscribeOutboundMedia bus methods - Add MediaSender interface discovered via type assertion by Manager - Add media dispatch/worker in Manager with shared retry logic - Extend ToolResult with Media field and MediaResult constructor - Publish outbound media from agent loop on tool results - Implement SendMedia for Telegram, Discord, Slack, LINE, OneBot, WeCom
This commit is contained in:
@@ -431,6 +431,117 @@ func (c *OneBotChannel) Send(ctx context.Context, msg bus.OutboundMessage) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMedia implements the channels.MediaSender interface.
|
||||
func (c *OneBotChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) error {
|
||||
if !c.IsRunning() {
|
||||
return channels.ErrNotRunning
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
conn := c.conn
|
||||
c.mu.Unlock()
|
||||
|
||||
if conn == nil {
|
||||
return fmt.Errorf("OneBot WebSocket not connected")
|
||||
}
|
||||
|
||||
store := c.GetMediaStore()
|
||||
if store == nil {
|
||||
return fmt.Errorf("no media store available: %w", channels.ErrSendFailed)
|
||||
}
|
||||
|
||||
// Build media segments
|
||||
var segments []oneBotMessageSegment
|
||||
for _, part := range msg.Parts {
|
||||
localPath, err := store.Resolve(part.Ref)
|
||||
if err != nil {
|
||||
logger.ErrorCF("onebot", "Failed to resolve media ref", map[string]any{
|
||||
"ref": part.Ref,
|
||||
"error": err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
segType := "image"
|
||||
switch part.Type {
|
||||
case "image":
|
||||
segType = "image"
|
||||
case "video":
|
||||
segType = "video"
|
||||
case "audio":
|
||||
segType = "record"
|
||||
default:
|
||||
segType = "file"
|
||||
}
|
||||
|
||||
segments = append(segments, oneBotMessageSegment{
|
||||
Type: segType,
|
||||
Data: map[string]any{"file": "file://" + localPath},
|
||||
})
|
||||
|
||||
if part.Caption != "" {
|
||||
segments = append(segments, oneBotMessageSegment{
|
||||
Type: "text",
|
||||
Data: map[string]any{"text": part.Caption},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(segments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
chatID := msg.ChatID
|
||||
var action, idKey string
|
||||
var rawID string
|
||||
if rest, ok := strings.CutPrefix(chatID, "group:"); ok {
|
||||
action, idKey, rawID = "send_group_msg", "group_id", rest
|
||||
} else if rest, ok := strings.CutPrefix(chatID, "private:"); ok {
|
||||
action, idKey, rawID = "send_private_msg", "user_id", rest
|
||||
} else {
|
||||
action, idKey, rawID = "send_private_msg", "user_id", chatID
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(rawID, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s in chatID: %s: %w", idKey, chatID, channels.ErrSendFailed)
|
||||
}
|
||||
|
||||
echo := fmt.Sprintf("send_%d", atomic.AddInt64(&c.echoCounter, 1))
|
||||
|
||||
req := oneBotAPIRequest{
|
||||
Action: action,
|
||||
Params: map[string]any{idKey: id, "message": segments},
|
||||
Echo: echo,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal OneBot request: %w", err)
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
err = conn.WriteMessage(websocket.TextMessage, data)
|
||||
_ = conn.SetWriteDeadline(time.Time{})
|
||||
c.writeMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
logger.ErrorCF("onebot", "Failed to send media message", map[string]any{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return fmt.Errorf("onebot send media: %w", channels.ErrTemporary)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OneBotChannel) buildMessageSegments(chatID, content string) []oneBotMessageSegment {
|
||||
var segments []oneBotMessageSegment
|
||||
|
||||
|
||||
Reference in New Issue
Block a user