chore(build): 更新.gitignore配置和清理Wails临时文件
- 添加dist/目录到.gitignore,用于排除打包输出的绿色免安装版 - 添加Wails打包过程中的临时文件和自动生成文件到.gitignore - 删除build/windows/installer/wails_tools.nsh自动生成文件 - 添加Windows安装器临时目录和Webview2安装文件到忽略列表 feat(docs): 添加万川平台对接文档和产品素材 - 创建万川平台登录到获取模型信息的流程说明文档 - 添加万川平台对接实施计划文档 - 新增产品图片、公司简介图、宣传海报、教程截图、案例展示等素材文件 refactor(runtime): 扩展通知功能类型定义 - 添加NotificationOptions接口定义 - 添加NotificationAction接口定义 - 添加NotificationCategory接口定义 - 扩展通知相关的运行时API类型声明,包括初始化、发送、注册分类等功能
This commit is contained in:
@@ -351,9 +351,56 @@ func extractAutoReplyMessage(clientID int32, raw map[string]interface{}) autoRep
|
||||
} else {
|
||||
msg.MessageType = "non_text"
|
||||
}
|
||||
logEmptyMediaDiagnostics(msg, raw)
|
||||
return msg
|
||||
}
|
||||
|
||||
// logEmptyMediaDiagnostics 仅在"图片/视频/表情等非文本媒体消息,且 URL/本地路径/FileID 三个来源全空"
|
||||
// 时触发,把媒体相关字段(已脱敏)打到日志,用于定位企微 DLL 回调实际下发了哪些字段。
|
||||
// 文本消息(11041)与字段已成功填充的消息都不会触发,避免刷屏与泄露正常聊天内容。
|
||||
func logEmptyMediaDiagnostics(msg autoReplyMessage, raw map[string]interface{}) {
|
||||
if globalLogger == nil {
|
||||
return
|
||||
}
|
||||
// 只关心需要拉取媒体文件来识别的类型;纯文本/位置等不在此列。
|
||||
switch msg.RawType {
|
||||
case 11042, 11043, 11047: // image / video / link(图片/表情)
|
||||
default:
|
||||
return
|
||||
}
|
||||
hasMediaSource := strings.TrimSpace(msg.MediaURL) != "" ||
|
||||
strings.TrimSpace(msg.MediaLocalPath) != "" ||
|
||||
strings.TrimSpace(msg.MediaFileID) != ""
|
||||
if hasMediaSource {
|
||||
return // 字段已填到,下载链路可以走,无需诊断
|
||||
}
|
||||
|
||||
// 只提取诊断相关的字段,避免泄露用户昵称、群名、会话内容等敏感信息
|
||||
diagnosticFields := make(map[string]interface{})
|
||||
diagnosticFields["event"] = raw["event"]
|
||||
diagnosticFields["type"] = raw["type"]
|
||||
if data, ok := raw["data"].(map[string]interface{}); ok {
|
||||
// 只记录媒体相关字段和类型标识
|
||||
for _, key := range []string{"event", "content_type", "contentType", "messageType", "message_type",
|
||||
"image_url", "imageUrl", "preview_img_url", "previewImgUrl",
|
||||
"md_url", "mdUrl", "ld_url", "ldUrl", "url",
|
||||
"file_id", "fileId", "local_path", "localPath",
|
||||
"media_kind", "mediaKind", "aes_key", "aesKey", "auth_key", "authKey"} {
|
||||
if val, exists := data[key]; exists && val != nil {
|
||||
diagnosticFields[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diagJSON, err := json.Marshal(diagnosticFields)
|
||||
if err != nil {
|
||||
globalLogger.Warn("[媒体诊断] rawType=%d 媒体字段全空,且诊断数据序列化失败: %v", msg.RawType, err)
|
||||
return
|
||||
}
|
||||
globalLogger.Warn("[媒体诊断] rawType=%d mediaKind=%s 媒体字段(URL/本地路径/FileID)全空,无法识别。诊断字段: %s",
|
||||
msg.RawType, msg.MediaKind, string(diagJSON))
|
||||
}
|
||||
|
||||
func rawTypeFromEvent(raw map[string]interface{}) int {
|
||||
event := strings.TrimSpace(stringFromAny(raw["event"]))
|
||||
if event == "" {
|
||||
@@ -361,6 +408,7 @@ func rawTypeFromEvent(raw map[string]interface{}) int {
|
||||
event = strings.TrimSpace(stringFromAny(data["event"]))
|
||||
}
|
||||
}
|
||||
// 优先使用 event 字段(DLL 真实事件):20002=文本 20003=图片 20004=视频 20012=语音 20005=文件 20014=链接
|
||||
switch event {
|
||||
case "20002":
|
||||
return 11041
|
||||
@@ -375,18 +423,24 @@ func rawTypeFromEvent(raw map[string]interface{}) int {
|
||||
case "20014":
|
||||
return 11047
|
||||
}
|
||||
// event 为空时才用 content_type(模拟事件或老版本 DLL):2=文本 101=图片 103=视频 16=语音 102=文件 6=位置 13=链接
|
||||
// 注意不能把文本(2)误判成图片,否则会触发图片识别并回退"无法识别"话术。
|
||||
if data, ok := raw["data"].(map[string]interface{}); ok {
|
||||
switch intFromAny(firstNonNil(data["messageType"], data["content_type"], data["contentType"])) {
|
||||
switch intFromAny(firstNonNil(data["content_type"], data["contentType"])) {
|
||||
case 2:
|
||||
return 11041
|
||||
case 101:
|
||||
return 11042
|
||||
case 4:
|
||||
case 103:
|
||||
return 11043
|
||||
case 16:
|
||||
return 11044
|
||||
case 6:
|
||||
case 102:
|
||||
return 11045
|
||||
case 48:
|
||||
case 6:
|
||||
return 11046
|
||||
case 13:
|
||||
return 11047
|
||||
}
|
||||
}
|
||||
return 0
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -286,36 +287,54 @@ func syncAutoReplyMaterials(root string, indexPath string) (autoReplyMaterialSyn
|
||||
|
||||
func discoverAutoReplyMaterials(root string) []AutoReplyMaterial {
|
||||
dir := resolveAutoReplyPath(root)
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
items := make([]AutoReplyMaterial, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
items := make([]AutoReplyMaterial, 0, 8)
|
||||
// 递归遍历子目录(filepath.WalkDir):支持 config/materials 下任意层级嵌套。
|
||||
// Path 存相对 root 的子路径并统一为 / 分隔;顶层文件相对路径即文件名,向后兼容旧索引。
|
||||
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil // 单个条目出错跳过,不中断整体扫描
|
||||
}
|
||||
name := entry.Name()
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
name := d.Name()
|
||||
if strings.EqualFold(name, "materials.json") {
|
||||
continue
|
||||
return nil
|
||||
}
|
||||
materialType := inferMaterialType(name)
|
||||
if materialType == "" {
|
||||
continue
|
||||
return nil
|
||||
}
|
||||
rel, relErr := filepath.Rel(dir, path)
|
||||
if relErr != nil {
|
||||
rel = name
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
title := strings.TrimSuffix(name, filepath.Ext(name))
|
||||
keywords := defaultMaterialKeywords(title, materialType)
|
||||
// 把子目录名也并入关键词,便于"发我<目录名>的图/文件"命中。
|
||||
// 只添加目录名本身,不再分词,避免关键词过度膨胀。
|
||||
if dirPart := filepath.ToSlash(filepath.Dir(rel)); dirPart != "." && dirPart != "" {
|
||||
for _, seg := range strings.Split(dirPart, "/") {
|
||||
if seg = strings.TrimSpace(seg); seg != "" {
|
||||
keywords = append(keywords, seg)
|
||||
}
|
||||
}
|
||||
keywords = dedupeNonEmptyStrings(keywords)
|
||||
}
|
||||
items = append(items, AutoReplyMaterial{
|
||||
ID: materialIDFromTitle(title),
|
||||
ID: materialIDFromTitle(strings.TrimSuffix(rel, filepath.Ext(rel))),
|
||||
Title: title,
|
||||
Keywords: defaultMaterialKeywords(title, materialType),
|
||||
Keywords: keywords,
|
||||
QuestionPatterns: defaultMaterialQuestionPatterns(title),
|
||||
MaterialType: materialType,
|
||||
Path: name,
|
||||
Path: rel,
|
||||
Caption: defaultMaterialCaption(materialType),
|
||||
Priority: 1,
|
||||
Enabled: true,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return normalizeAutoReplyMaterials(items)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user