初始化仓库
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
package weixin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chenhg5/cc-connect/core"
|
||||
)
|
||||
|
||||
func imageDecryptMaterial(img *imageItem) (encParam, aesKeyBase64 string, ok bool) {
|
||||
if img == nil || img.Media == nil {
|
||||
return "", "", false
|
||||
}
|
||||
encParam = strings.TrimSpace(img.Media.EncryptQueryParam)
|
||||
if encParam == "" {
|
||||
return "", "", false
|
||||
}
|
||||
if hx := strings.TrimSpace(img.AESKeyHex); hx != "" {
|
||||
raw, err := hex.DecodeString(hx)
|
||||
if err == nil && len(raw) == 16 {
|
||||
return encParam, base64.StdEncoding.EncodeToString(raw), true
|
||||
}
|
||||
}
|
||||
if k := strings.TrimSpace(img.Media.AESKey); k != "" {
|
||||
return encParam, k, true
|
||||
}
|
||||
return encParam, "", false
|
||||
}
|
||||
|
||||
func (p *Platform) collectInboundMedia(ctx context.Context, items []messageItem) (images []core.ImageAttachment, files []core.FileAttachment, audio *core.AudioAttachment) {
|
||||
if p == nil || len(items) == 0 || strings.TrimSpace(p.cdnBaseURL) == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
dlCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
client := p.cdnHttpClient
|
||||
if client == nil {
|
||||
client = p.httpClient
|
||||
}
|
||||
base := p.cdnBaseURL
|
||||
|
||||
// Deduplicate identical CDN references within one message (duplicate items / retries).
|
||||
seenEnc := make(map[string]struct{})
|
||||
tryEnc := func(enc string) bool {
|
||||
if enc == "" {
|
||||
return false
|
||||
}
|
||||
if _, ok := seenEnc[enc]; ok {
|
||||
return false
|
||||
}
|
||||
seenEnc[enc] = struct{}{}
|
||||
return true
|
||||
}
|
||||
var extraVoiceN int
|
||||
|
||||
for _, it := range items {
|
||||
switch it.Type {
|
||||
case messageItemImage:
|
||||
img := it.ImageItem
|
||||
enc, keyB64, hasKey := imageDecryptMaterial(img)
|
||||
if enc == "" || !tryEnc(enc) {
|
||||
continue
|
||||
}
|
||||
var buf []byte
|
||||
var err error
|
||||
if hasKey && keyB64 != "" {
|
||||
buf, err = downloadAndDecryptCDN(dlCtx, client, base, enc, keyB64, "weixin inbound image")
|
||||
} else {
|
||||
buf, err = downloadPlainCDN(dlCtx, client, base, enc, "weixin inbound image-plain")
|
||||
}
|
||||
if err != nil {
|
||||
slog.Warn("weixin: inbound image CDN failed", "error", err)
|
||||
continue
|
||||
}
|
||||
mt := detectImageMime(buf)
|
||||
images = append(images, core.ImageAttachment{MimeType: mt, Data: buf})
|
||||
|
||||
case messageItemFile:
|
||||
f := it.FileItem
|
||||
if f == nil || f.Media == nil {
|
||||
continue
|
||||
}
|
||||
enc := strings.TrimSpace(f.Media.EncryptQueryParam)
|
||||
keyB64 := strings.TrimSpace(f.Media.AESKey)
|
||||
if enc == "" || keyB64 == "" || !tryEnc(enc) {
|
||||
continue
|
||||
}
|
||||
buf, err := downloadAndDecryptCDN(dlCtx, client, base, enc, keyB64, "weixin inbound file")
|
||||
if err != nil {
|
||||
slog.Warn("weixin: inbound file CDN failed", "error", err)
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(f.FileName)
|
||||
if name == "" {
|
||||
name = "attachment.bin"
|
||||
}
|
||||
mt := mime.TypeByExtension(filepath.Ext(name))
|
||||
if mt == "" {
|
||||
mt = "application/octet-stream"
|
||||
}
|
||||
files = append(files, core.FileAttachment{MimeType: mt, Data: buf, FileName: name})
|
||||
|
||||
case messageItemVideo:
|
||||
v := it.VideoItem
|
||||
if v == nil || v.Media == nil {
|
||||
continue
|
||||
}
|
||||
enc := strings.TrimSpace(v.Media.EncryptQueryParam)
|
||||
keyB64 := strings.TrimSpace(v.Media.AESKey)
|
||||
if enc == "" || keyB64 == "" || !tryEnc(enc) {
|
||||
continue
|
||||
}
|
||||
buf, err := downloadAndDecryptCDN(dlCtx, client, base, enc, keyB64, "weixin inbound video")
|
||||
if err != nil {
|
||||
slog.Warn("weixin: inbound video CDN failed", "error", err)
|
||||
continue
|
||||
}
|
||||
files = append(files, core.FileAttachment{MimeType: "video/mp4", Data: buf, FileName: "video.mp4"})
|
||||
|
||||
case messageItemVoice:
|
||||
v := it.VoiceItem
|
||||
if v == nil || v.Media == nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(v.Text) != "" {
|
||||
// WeChat ASR text is enough when present; avoid STT path / duplicate handling.
|
||||
continue
|
||||
}
|
||||
enc := strings.TrimSpace(v.Media.EncryptQueryParam)
|
||||
keyB64 := strings.TrimSpace(v.Media.AESKey)
|
||||
if enc == "" || keyB64 == "" || !tryEnc(enc) {
|
||||
continue
|
||||
}
|
||||
buf, err := downloadAndDecryptCDN(dlCtx, client, base, enc, keyB64, "weixin inbound voice")
|
||||
if err != nil {
|
||||
slog.Warn("weixin: inbound voice CDN failed", "error", err)
|
||||
continue
|
||||
}
|
||||
a := &core.AudioAttachment{
|
||||
MimeType: "audio/silk",
|
||||
Data: buf,
|
||||
Format: "silk",
|
||||
}
|
||||
if audio == nil {
|
||||
audio = a
|
||||
} else {
|
||||
// core.Message carries one Audio; extra raw voice segments go as file attachments for the agent.
|
||||
extraVoiceN++
|
||||
files = append(files, core.FileAttachment{
|
||||
MimeType: "audio/silk",
|
||||
Data: buf,
|
||||
FileName: fmt.Sprintf("voice_%d.silk", extraVoiceN),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return images, files, audio
|
||||
}
|
||||
Reference in New Issue
Block a user