diff --git a/internal/model/message_darwinv3.go b/internal/model/message_darwinv3.go index fe73554..7245ecf 100644 --- a/internal/model/message_darwinv3.go +++ b/internal/model/message_darwinv3.go @@ -1,6 +1,11 @@ package model import ( + "crypto/md5" + "encoding/hex" + "fmt" + "os" + "path/filepath" "strings" "time" ) @@ -29,7 +34,7 @@ type MessageDarwinV3 struct { MesDes int `json:"mesDes"` // 0: 发送, 1: 接收 } -func (m *MessageDarwinV3) Wrap(talker string) *Message { +func (m *MessageDarwinV3) Wrap(talker string, dataDir string) *Message { _m := &Message{ Time: time.Unix(m.MsgCreateTime, 0), @@ -38,6 +43,7 @@ func (m *MessageDarwinV3) Wrap(talker string) *Message { IsChatRoom: strings.HasSuffix(talker, "@chatroom"), IsSelf: m.MesDes == 0, Version: WeChatDarwinV3, + Contents: make(map[string]interface{}), } content := m.MsgContent @@ -53,5 +59,65 @@ func (m *MessageDarwinV3) Wrap(talker string) *Message { _m.ParseMediaInfo(content) + // 图片消息 + if _m.Type == 3 && dataDir != "" { + // 计算talker的MD5 + talkerMD5 := md5.Sum([]byte(talker)) + talkerMD5Str := hex.EncodeToString(talkerMD5[:]) + + // 将消息时间转换成秒 + timeStr := fmt.Sprintf("%d", m.MsgCreateTime) + + // 构建搜索目录 + searchDir := filepath.Join(dataDir, "Message", "MessageTemp", talkerMD5Str, "Image") + + // 按优先级查找图片文件 + patterns := []string{ + fmt.Sprintf("%s_.pic.jpg", timeStr), + fmt.Sprintf("%s_.pic_hd.jpg", timeStr), + fmt.Sprintf("%s_.pic_thumb.jpg", timeStr), + } + + for _, pattern := range patterns { + if entries, err := os.ReadDir(searchDir); err == nil { + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), pattern) { + // 找到文件,设置相对路径 + _m.Contents["imgfile"] = filepath.Join("/Message/MessageTemp", talkerMD5Str, "Image", entry.Name()) + break + } + } + if _, exists := _m.Contents["imgfile"]; exists { + break // 找到文件就退出 + } + } + } + } + + // 视频消息 + if _m.Type == 43 && dataDir != "" { + // 计算talker的MD5 + talkerMD5 := md5.Sum([]byte(talker)) + talkerMD5Str := hex.EncodeToString(talkerMD5[:]) + + // 将消息时间转换成秒 + timeStr := fmt.Sprintf("%d", m.MsgCreateTime) + + // 构建搜索目录 + searchDir := filepath.Join(dataDir, "Message", "MessageTemp", talkerMD5Str, "Video") + + // 查找视频文件 + videoPattern := fmt.Sprintf("%s.mp4", timeStr) + if entries, err := os.ReadDir(searchDir); err == nil { + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), videoPattern) { + // 找到文件,设置相对路径 + _m.Contents["videofile"] = filepath.Join("/Message/MessageTemp", talkerMD5Str, "Video", entry.Name()) + break + } + } + } + } + return _m } diff --git a/internal/model/message_v3.go b/internal/model/message_v3.go index 2471883..9419a7e 100644 --- a/internal/model/message_v3.go +++ b/internal/model/message_v3.go @@ -1,7 +1,10 @@ package model import ( + "crypto/md5" + "encoding/hex" "fmt" + "os" "path/filepath" "strings" "time" @@ -52,7 +55,7 @@ type MessageV3 struct { BytesExtra []byte `json:"BytesExtra"` // protobuf 额外数据,记录群聊发送人等信息 } -func (m *MessageV3) Wrap() *Message { +func (m *MessageV3) Wrap(dataDir string) *Message { _m := &Message{ Seq: m.Sequence, @@ -64,6 +67,7 @@ func (m *MessageV3) Wrap() *Message { SubType: int64(m.SubType), Content: m.StrContent, Version: WeChatV3, + Contents: make(map[string]interface{}), } if !_m.IsChatRoom && !_m.IsSelf { @@ -79,6 +83,66 @@ func (m *MessageV3) Wrap() *Message { _m.ParseMediaInfo(_m.Content) + // 图片消息 + if _m.Type == 3 && dataDir != "" { + // 计算talker的MD5 + talkerMD5 := md5.Sum([]byte(m.StrTalker)) + talkerMD5Str := hex.EncodeToString(talkerMD5[:]) + + // 将消息时间转换成秒 + timeStr := fmt.Sprintf("%d", m.CreateTime) + + // 构建搜索目录 + searchDir := filepath.Join(dataDir, "Message", "MessageTemp", talkerMD5Str, "Image") + + // 按优先级查找图片文件 + patterns := []string{ + fmt.Sprintf("%s_.pic.jpg", timeStr), + fmt.Sprintf("%s_.pic_hd.jpg", timeStr), + fmt.Sprintf("%s_.pic_thumb.jpg", timeStr), + } + + for _, pattern := range patterns { + if entries, err := os.ReadDir(searchDir); err == nil { + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), pattern) { + // 找到文件,设置相对路径 + _m.Contents["imgfile"] = filepath.Join("/Message/MessageTemp", talkerMD5Str, "Image", entry.Name()) + break + } + } + if _, exists := _m.Contents["imgfile"]; exists { + break // 找到文件就退出 + } + } + } + } + + // 视频消息 + if _m.Type == 43 && dataDir != "" { + // 计算talker的MD5 + talkerMD5 := md5.Sum([]byte(m.StrTalker)) + talkerMD5Str := hex.EncodeToString(talkerMD5[:]) + + // 将消息时间转换成秒 + timeStr := fmt.Sprintf("%d", m.CreateTime) + + // 构建搜索目录 + searchDir := filepath.Join(dataDir, "Message", "MessageTemp", talkerMD5Str, "Video") + + // 查找视频文件 + videoPattern := fmt.Sprintf("%s.mp4", timeStr) + if entries, err := os.ReadDir(searchDir); err == nil { + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), videoPattern) { + // 找到文件,设置相对路径 + _m.Contents["videofile"] = filepath.Join("/Message/MessageTemp", talkerMD5Str, "Video", entry.Name()) + break + } + } + } + } + // 语音消息 if _m.Type == 34 { _m.Contents["voice"] = fmt.Sprint(m.MsgSvrID) diff --git a/internal/model/message_v4.go b/internal/model/message_v4.go index b01ab00..2e9a325 100644 --- a/internal/model/message_v4.go +++ b/internal/model/message_v4.go @@ -5,6 +5,7 @@ import ( "crypto/md5" "encoding/hex" "fmt" + "os" "path/filepath" "strings" "time" @@ -45,7 +46,7 @@ type MessageV4 struct { Status int `json:"status"` // 消息状态,2 是已发送,4 是已接收,可以用于判断 IsSender(FIXME 不准, 需要判断 UserName) } -func (m *MessageV4) Wrap(talker string) *Message { +func (m *MessageV4) Wrap(talker string, dataDir string) *Message { _m := &Message{ Seq: m.SortSeq, @@ -80,6 +81,66 @@ func (m *MessageV4) Wrap(talker string) *Message { _m.ParseMediaInfo(content) + // 图片消息 + if _m.Type == 3 && dataDir != "" { + // 计算talker的MD5 + talkerMD5 := md5.Sum([]byte(talker)) + talkerMD5Str := hex.EncodeToString(talkerMD5[:]) + + // 将消息时间转换成秒 + timeStr := fmt.Sprintf("%d", m.CreateTime) + + // 构建搜索目录 + searchDir := filepath.Join(dataDir, "Message", "MessageTemp", talkerMD5Str, "Image") + + // 按优先级查找图片文件 + patterns := []string{ + fmt.Sprintf("%s_.pic.jpg", timeStr), + fmt.Sprintf("%s_.pic_hd.jpg", timeStr), + fmt.Sprintf("%s_.pic_thumb.jpg", timeStr), + } + + for _, pattern := range patterns { + if entries, err := os.ReadDir(searchDir); err == nil { + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), pattern) { + // 找到文件,设置相对路径 + _m.Contents["imgfile"] = filepath.Join("/Message/MessageTemp", talkerMD5Str, "Image", entry.Name()) + break + } + } + if _, exists := _m.Contents["imgfile"]; exists { + break // 找到文件就退出 + } + } + } + } + + // 视频消息 + if _m.Type == 43 && dataDir != "" { + // 计算talker的MD5 + talkerMD5 := md5.Sum([]byte(talker)) + talkerMD5Str := hex.EncodeToString(talkerMD5[:]) + + // 将消息时间转换成秒 + timeStr := fmt.Sprintf("%d", m.CreateTime) + + // 构建搜索目录 + searchDir := filepath.Join(dataDir, "Message", "MessageTemp", talkerMD5Str, "Video") + + // 查找视频文件 + videoPattern := fmt.Sprintf("%s.mp4", timeStr) + if entries, err := os.ReadDir(searchDir); err == nil { + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), videoPattern) { + // 找到文件,设置相对路径 + _m.Contents["videofile"] = filepath.Join("/Message/MessageTemp", talkerMD5Str, "Video", entry.Name()) + break + } + } + } + } + // 语音消息 if _m.Type == 34 { _m.Contents["voice"] = fmt.Sprint(m.ServerID) diff --git a/internal/wechatdb/datasource/darwinv3/datasource.go b/internal/wechatdb/datasource/darwinv3/datasource.go index f8f97ca..57e5fee 100644 --- a/internal/wechatdb/datasource/darwinv3/datasource.go +++ b/internal/wechatdb/datasource/darwinv3/datasource.go @@ -57,16 +57,18 @@ var Groups = []*dbm.Group{ } type DataSource struct { - path string - dbm *dbm.DBManager + path string + dataDir string + dbm *dbm.DBManager talkerDBMap map[string]string user2DisplayName map[string]string } -func New(path string) (*DataSource, error) { +func New(path string, dataDir string) (*DataSource, error) { ds := &DataSource{ path: path, + dataDir: dataDir, dbm: dbm.NewDBManager(path), talkerDBMap: make(map[string]string), user2DisplayName: make(map[string]string), @@ -277,7 +279,7 @@ func (ds *DataSource) GetMessages(ctx context.Context, startTime, endTime time.T } // 将消息包装为通用模型 - message := msg.Wrap(talkerItem) + message := msg.Wrap(talkerItem, ds.dataDir) // 应用sender过滤 if len(senders) > 0 { diff --git a/internal/wechatdb/datasource/datasource.go b/internal/wechatdb/datasource/datasource.go index 2f7cb7f..c2b84d4 100644 --- a/internal/wechatdb/datasource/datasource.go +++ b/internal/wechatdb/datasource/datasource.go @@ -39,11 +39,11 @@ type DataSource interface { func New(workDir string, dataDir string, platform string, version int) (DataSource, error) { switch { case platform == "windows" && version == 3: - return windowsv3.New(workDir) + return windowsv3.New(workDir, dataDir) case platform == "windows" && version == 4: return v4.New(workDir, dataDir) case platform == "darwin" && version == 3: - return darwinv3.New(workDir) + return darwinv3.New(workDir, dataDir) case platform == "darwin" && version == 4: return v4.New(workDir, dataDir) default: diff --git a/internal/wechatdb/datasource/v4/datasource.go b/internal/wechatdb/datasource/v4/datasource.go index 5ecee18..1fa5b40 100644 --- a/internal/wechatdb/datasource/v4/datasource.go +++ b/internal/wechatdb/datasource/v4/datasource.go @@ -291,7 +291,7 @@ func (ds *DataSource) GetMessages(ctx context.Context, startTime, endTime time.T } // 将消息转换为标准格式 - message := msg.Wrap(talkerItem) + message := msg.Wrap(talkerItem, ds.dataDir) // 应用sender过滤 if len(senders) > 0 { diff --git a/internal/wechatdb/datasource/windowsv3/datasource.go b/internal/wechatdb/datasource/windowsv3/datasource.go index 60713d2..7fd5fb8 100644 --- a/internal/wechatdb/datasource/windowsv3/datasource.go +++ b/internal/wechatdb/datasource/windowsv3/datasource.go @@ -71,17 +71,19 @@ type MessageDBInfo struct { // DataSource 实现了 DataSource 接口 type DataSource struct { - path string - dbm *dbm.DBManager + path string + dataDir string + dbm *dbm.DBManager // 消息数据库信息 messageInfos []MessageDBInfo } // New 创建一个新的 WindowsV3DataSource -func New(path string) (*DataSource, error) { +func New(path string, dataDir string) (*DataSource, error) { ds := &DataSource{ path: path, + dataDir: dataDir, dbm: dbm.NewDBManager(path), messageInfos: make([]MessageDBInfo, 0), } @@ -327,8 +329,8 @@ func (ds *DataSource) GetMessages(ctx context.Context, startTime, endTime time.T msg.CompressContent = compressContent msg.BytesExtra = bytesExtra - // 将消息转换为标准格式 - message := msg.Wrap() + // 将消息转换为标准格式 + message := msg.Wrap(ds.dataDir) // 应用sender过滤 if len(senders) > 0 {