Closed
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
为企业微信(WeCom)渠道补齐多模态消息处理链路:在 Bot / App / AIBot 三种模式下,新增(或完善)图片/文件等媒体消息的下载、(AIBot 图片)解密、入站消息 mediaRefs 传递,从而让 Agent 能真正拿到可用的媒体内容。
Changes:
- WeCom Bot:为 image/file/mixed 消息下载媒体并通过
mediaRefs传递给HandleMessage - WeCom App:对带
MediaId的入站消息下载媒体并存入MediaStore,同时在文本中追加媒体标签 - WeCom AIBot:支持图片消息
aeskey解密、mixed(text+image) 处理,并将媒体作为mediaRefs传入 Agent
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 20 comments.
| File | Description |
|---|---|
| pkg/channels/wecom/bot.go | 为 Bot 渠道增加媒体下载与 mediaRefs 传递,并新增 URL 下载存储逻辑 |
| pkg/channels/wecom/app.go | 为 App 渠道增加基于 MediaId 的媒体下载/存储与内容标签追加 |
| pkg/channels/wecom/aibot.go | 为 AIBot 增加图片媒体下载、AES 解密、mixed 消息处理与媒体 refs 传递 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Generate temp file path | ||
| tempDir := os.TempDir() | ||
| mediaDir := filepath.Join(tempDir, "picoclaw_media", "wecom") | ||
| os.MkdirAll(mediaDir, 0o755) |
Comment on lines
+1334
to
+1335
| // Use default client - no custom redirects | ||
| resp, err := http.DefaultClient.Do(req) |
Comment on lines
+1756
to
+1758
| } | ||
| defer resp.Body.Close() | ||
|
|
Comment on lines
+654
to
+669
| // Handle media messages (download and store) | ||
| var mediaRefs []string | ||
| store := c.GetMediaStore() | ||
| if store != nil && msg.MediaId != "" { | ||
| mediaRefs = c.downloadInboundMedia(ctx, msg.MsgType, msg.MediaId, messageID, store) | ||
| } | ||
|
|
||
| // Append media tags to content | ||
| if len(mediaRefs) > 0 { | ||
| mediaTag := c.getMediaTag(msg.MsgType) | ||
| if content != "" { | ||
| content += "\n" + mediaTag | ||
| } else { | ||
| content = mediaTag | ||
| } | ||
| } |
Comment on lines
+681
to
+683
| // Create a unique filename to avoid collisions | ||
| uniqueFilename := fmt.Sprintf("%s-%d-%s", msgID, time.Now().Unix(), filename) | ||
| localPath := filepath.Join(mediaDir, uniqueFilename) |
Comment on lines
+684
to
+714
| // Download image and store in media store | ||
| var mediaRefs []string | ||
| store := c.GetMediaStore() | ||
| if store == nil { | ||
| logger.WarnCF("wecom_aibot", "MediaStore not available, image will not be processed", map[string]any{ | ||
| "user_id": userID, | ||
| "chat_id": chatID, | ||
| "msg_id": msg.MsgID, | ||
| }) | ||
| } else { | ||
| var ref string | ||
| scope := channels.BuildMediaScope("wecom_aibot", chatID, msg.MsgID) | ||
|
|
||
| // Try to download via media_id first (WeCom AI Bot callback directly provides media_id) | ||
| if mediaID != "" { | ||
| logger.InfoCF("wecom_aibot", "Trying to download image via media_id", map[string]any{ | ||
| "media_id": mediaID, | ||
| "msg_id": msg.MsgID, | ||
| }) | ||
| ref = c.downloadMediaFromMediaIDNoAuth(ctx, mediaID, "image", store, scope, msg.MsgID) | ||
| } | ||
|
|
||
| // Fallback to URL if media_id download failed or not available | ||
| if ref == "" && imageURL != "" { | ||
| logger.InfoCF("wecom_aibot", "Falling back to URL download", map[string]any{ | ||
| "url": imageURL, | ||
| "msg_id": msg.MsgID, | ||
| "aes_key": aesKey, | ||
| }) | ||
| ref = c.downloadMediaFromURL(ctx, imageURL, "image.jpg", aesKey, store, scope, msg.MsgID) | ||
| } |
Comment on lines
361
to
+413
|
|
||
| // Extract content based on message type | ||
| var content string | ||
| var mediaRefs []string | ||
|
|
||
| scope := channels.BuildMediaScope("wecom", chatID, msg.MsgID) | ||
|
|
||
| switch msg.MsgType { | ||
| case "text": | ||
| content = msg.Text.Content | ||
| case "voice": | ||
| content = msg.Voice.Content // Voice to text content | ||
| case "mixed": | ||
| // For mixed messages, concatenate text items | ||
| // For mixed messages, process text and image items | ||
| for _, item := range msg.Mixed.MsgItem { | ||
| if item.MsgType == "text" { | ||
| content += item.Text.Content | ||
| } | ||
| } | ||
| case "image", "file": | ||
| // For image and file, we don't have text content | ||
| content = "" | ||
| // Download images from mixed messages | ||
| if store := c.GetMediaStore(); store != nil { | ||
| for _, item := range msg.Mixed.MsgItem { | ||
| if item.MsgType == "image" && item.Image.URL != "" { | ||
| ref := c.downloadMediaFromURL(ctx, item.Image.URL, "image.jpg", store, scope, msg.MsgID) | ||
| if ref != "" { | ||
| mediaRefs = append(mediaRefs, ref) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| case "image": | ||
| // Download image from URL | ||
| if msg.Image.URL != "" { | ||
| if store := c.GetMediaStore(); store != nil { | ||
| ref := c.downloadMediaFromURL(ctx, msg.Image.URL, "image.jpg", store, scope, msg.MsgID) | ||
| if ref != "" { | ||
| mediaRefs = append(mediaRefs, ref) | ||
| } | ||
| } | ||
| } | ||
| content = "[image]" | ||
| case "file": | ||
| // Download file from URL | ||
| if msg.File.URL != "" { | ||
| if store := c.GetMediaStore(); store != nil { | ||
| ref := c.downloadMediaFromURL(ctx, msg.File.URL, "file", store, scope, msg.MsgID) | ||
| if ref != "" { | ||
| mediaRefs = append(mediaRefs, ref) | ||
| } | ||
| } | ||
| } | ||
| content = "[file]" | ||
| } |
| // Handle media messages (download and store) | ||
| var mediaRefs []string | ||
| store := c.GetMediaStore() | ||
| if store != nil && msg.MediaId != "" { |
Comment on lines
+786
to
+794
| // Determine file extension based on message type | ||
| var ext string | ||
| switch msgType { | ||
| case "image": | ||
| ext = ".jpg" | ||
| case "voice": | ||
| ext = ".amr" | ||
| case "video": | ||
| ext = ".mp4" |
Comment on lines
+817
to
+821
| // Store in media store | ||
| ref, err := store.Store(localPath, media.MediaMeta{ | ||
| Filename: filename, | ||
| Source: "wecom_app", | ||
| }, scope) |
xuwei-xy
pushed a commit
to xuwei-xy/picoclaw
that referenced
this pull request
Mar 14, 2026
|
@opcache Hi! This PR has had no activity for over 2 weeks, so I'm closing it for now to keep things tidy. If it's still relevant, feel free to reopen it anytime and we'll pick it back up. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 改动描述
为企业微信(WeCom)完整实现多模态消息处理功能,覆盖 App/Bot/AIBot 三个核心模块:
aeskey字段或配置EncodingAESKey进行解密;aeskey时,自动使用配置文件中的EncodingAESKey作为回退;🗣️ 改动类型
🤖 AI 代码生成说明
🔗 关联问题
无关联 issue
📚 技术背景
参考链接
改动原因
问题现象
image: unknown format根本原因
企业微信 AI Bot 返回的图片消息包含加密内容:
aeskey字段(消息级别)🧪 测试环境