diff --git a/internal/chatlog/http/route.go b/internal/chatlog/http/route.go index 08e0358..f05402d 100644 --- a/internal/chatlog/http/route.go +++ b/internal/chatlog/http/route.go @@ -307,10 +307,9 @@ func (s *Service) handleMedia(c *gin.Context, _type string) { var _err error for _, k := range keys { - if len(k) != 32 { - absolutePath := filepath.Join(s.conf.GetDataDir(), k) - if _, err := os.Stat(absolutePath); err == nil { - c.Redirect(http.StatusFound, "/data/"+k) + if strings.Contains(k, "/") { + if absolutePath, err := s.findPath(_type, k); err == nil { + c.Redirect(http.StatusFound, "/data/"+absolutePath) return } } @@ -339,6 +338,28 @@ func (s *Service) handleMedia(c *gin.Context, _type string) { } } +func (s *Service) findPath(_type string, key string) (string, error) { + absolutePath := filepath.Join(s.conf.GetDataDir(), key) + if _, err := os.Stat(absolutePath); err == nil { + return key, nil + } + switch _type { + case "image": + for _, suffix := range []string{"_h.dat", ".dat", "_t.dat"} { + if _, err := os.Stat(absolutePath + suffix); err == nil { + return key + suffix, nil + } + } + case "video": + for _, suffix := range []string{".mp4", "_thumb.jpg"} { + if _, err := os.Stat(absolutePath + suffix); err == nil { + return key + suffix, nil + } + } + } + return "", errors.ErrMediaNotFound +} + func (s *Service) handleMediaData(c *gin.Context) { relativePath := filepath.Clean(c.Param("path")) diff --git a/internal/errors/errors.go b/internal/errors/errors.go index bdb45c5..6fe36d6 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -118,3 +118,7 @@ func Err(c *gin.Context, err error) { c.JSON(http.StatusInternalServerError, err.Error()) } + +func Is(err, target error) bool { + return errors.Is(err, target) +} diff --git a/internal/model/message.go b/internal/model/message.go index 316e827..71c16b4 100644 --- a/internal/model/message.go +++ b/internal/model/message.go @@ -245,14 +245,9 @@ func (m *Message) PlainTextContent() string { keylist = append(keylist, md5) } } - if m.Contents["imgfile"] != nil { - if imgfile, ok := m.Contents["imgfile"].(string); ok { - keylist = append(keylist, imgfile) - } - } - if m.Contents["thumb"] != nil { - if thumb, ok := m.Contents["thumb"].(string); ok { - keylist = append(keylist, thumb) + if m.Contents["path"] != nil { + if path, ok := m.Contents["path"].(string); ok { + keylist = append(keylist, path) } } return fmt.Sprintf("![图片](http://%s/image/%s)", m.Contents["host"], strings.Join(keylist, ",")) @@ -275,14 +270,9 @@ func (m *Message) PlainTextContent() string { keylist = append(keylist, rawmd5) } } - if m.Contents["videofile"] != nil { - if videofile, ok := m.Contents["videofile"].(string); ok { - keylist = append(keylist, videofile) - } - } - if m.Contents["thumb"] != nil { - if thumb, ok := m.Contents["thumb"].(string); ok { - keylist = append(keylist, thumb) + if m.Contents["path"] != nil { + if path, ok := m.Contents["path"].(string); ok { + keylist = append(keylist, path) } } return fmt.Sprintf("![视频](http://%s/video/%s)", m.Contents["host"], strings.Join(keylist, ",")) diff --git a/internal/model/message_v4.go b/internal/model/message_v4.go index 5aa116b..1183f38 100644 --- a/internal/model/message_v4.go +++ b/internal/model/message_v4.go @@ -90,12 +90,10 @@ func (m *MessageV4) Wrap(talker string) *Message { if _m.Type == 3 && packedInfo.Image != nil { _talkerMd5Bytes := md5.Sum([]byte(talker)) talkerMd5 := hex.EncodeToString(_talkerMd5Bytes[:]) - _m.Contents["imgfile"] = filepath.Join("msg", "attach", talkerMd5, _m.Time.Format("2006-01"), "Img", fmt.Sprintf("%s.dat", packedInfo.Image.Md5)) - _m.Contents["thumb"] = filepath.Join("msg", "attach", talkerMd5, _m.Time.Format("2006-01"), "Img", fmt.Sprintf("%s_t.dat", packedInfo.Image.Md5)) + _m.Contents["path"] = filepath.Join("msg", "attach", talkerMd5, _m.Time.Format("2006-01"), "Img", packedInfo.Image.Md5) } if _m.Type == 43 && packedInfo.Video != nil { - _m.Contents["videofile"] = filepath.Join("msg", "video", _m.Time.Format("2006-01"), fmt.Sprintf("%s.mp4", packedInfo.Video.Md5)) - _m.Contents["thumb"] = filepath.Join("msg", "video", _m.Time.Format("2006-01"), fmt.Sprintf("%s_thumb.jpg", packedInfo.Video.Md5)) + _m.Contents["path"] = filepath.Join("msg", "video", _m.Time.Format("2006-01"), packedInfo.Video.Md5) } } } diff --git a/internal/wechatdb/datasource/v4/datasource.go b/internal/wechatdb/datasource/v4/datasource.go index 8dc69d8..5b86b9a 100644 --- a/internal/wechatdb/datasource/v4/datasource.go +++ b/internal/wechatdb/datasource/v4/datasource.go @@ -162,6 +162,10 @@ func (ds *DataSource) initMessageDbs() error { infos[i].EndTime = infos[i+1].StartTime } } + if len(ds.messageInfos) > 0 && len(infos) < len(ds.messageInfos) { + log.Warn().Msgf("message db count decreased from %d to %d, skip init", len(ds.messageInfos), len(infos)) + return nil + } ds.messageInfos = infos return nil } @@ -603,10 +607,20 @@ func (ds *DataSource) GetMedia(ctx context.Context, _type string, key string) (* switch _type { case "image": table = "image_hardlink_info_v3" + // 4.1.0 版本开始使用 v4 表 + if !ds.IsExist(Media, table) { + table = "image_hardlink_info_v4" + } case "video": table = "video_hardlink_info_v3" + if !ds.IsExist(Media, table) { + table = "video_hardlink_info_v4" + } case "file": table = "file_hardlink_info_v3" + if !ds.IsExist(Media, table) { + table = "file_hardlink_info_v4" + } case "voice": return ds.GetVoice(ctx, key) default: @@ -659,8 +673,8 @@ func (ds *DataSource) GetMedia(ctx context.Context, _type string, key string) (* mediaV4.Type = _type media = mediaV4.Wrap() - // 跳过缩略图 - if _type == "image" && !strings.Contains(media.Name, "_t") { + // 优先返回高清图 + if _type == "image" && strings.HasSuffix(mediaV4.Name, "_h.dat") { break } } @@ -672,6 +686,22 @@ func (ds *DataSource) GetMedia(ctx context.Context, _type string, key string) (* return media, nil } +func (ds *DataSource) IsExist(_db string, table string) bool { + db, err := ds.dbm.GetDB(_db) + if err != nil { + return false + } + var tableName string + query := "SELECT name FROM sqlite_master WHERE type='table' AND name=?;" + if err = db.QueryRow(query, table).Scan(&tableName); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false + } + return false + } + return true +} + func (ds *DataSource) GetVoice(ctx context.Context, key string) (*model.Media, error) { if key == "" { return nil, errors.ErrKeyEmpty diff --git a/internal/wechatdb/datasource/windowsv3/datasource.go b/internal/wechatdb/datasource/windowsv3/datasource.go index 60713d2..1c64ff1 100644 --- a/internal/wechatdb/datasource/windowsv3/datasource.go +++ b/internal/wechatdb/datasource/windowsv3/datasource.go @@ -56,7 +56,7 @@ var Groups = []*dbm.Group{ }, { Name: Voice, - Pattern: `^MediaMSG([0-9])?\.db$`, + Pattern: `^MediaMSG([0-9]?[0-9])?\.db$`, BlackList: []string{}, }, } @@ -207,6 +207,10 @@ func (ds *DataSource) initMessageDbs() error { infos[i].EndTime = infos[i+1].StartTime } } + if len(ds.messageInfos) > 0 && len(infos) < len(ds.messageInfos) { + log.Warn().Msgf("message db count decreased from %d to %d, skip init", len(ds.messageInfos), len(infos)) + return nil + } ds.messageInfos = infos return nil } diff --git a/internal/wechatdb/repository/chatroom.go b/internal/wechatdb/repository/chatroom.go index c69f6b0..23beb37 100644 --- a/internal/wechatdb/repository/chatroom.go +++ b/internal/wechatdb/repository/chatroom.go @@ -5,17 +5,14 @@ import ( "sort" "strings" + "github.com/rs/zerolog/log" + "github.com/sjzar/chatlog/internal/errors" "github.com/sjzar/chatlog/internal/model" ) // initChatRoomCache 初始化群聊缓存 func (r *Repository) initChatRoomCache(ctx context.Context) error { - // 加载所有群聊到缓存 - chatRooms, err := r.ds.GetChatRooms(ctx, "", 0, 0) - if err != nil { - return err - } chatRoomMap := make(map[string]*model.ChatRoom) remarkToChatRoom := make(map[string][]*model.ChatRoom) @@ -24,6 +21,13 @@ func (r *Repository) initChatRoomCache(ctx context.Context) error { chatRoomRemark := make([]string, 0) chatRoomNickName := make([]string, 0) + // 加载所有群聊到缓存 + // 暂时忽略获取不到群聊的错误 + chatRooms, err := r.ds.GetChatRooms(ctx, "", 0, 0) + if err != nil { + log.Error().Err(err).Msg("Failed to load chat rooms") + } + for _, chatRoom := range chatRooms { // 补充群聊信息(从联系人中获取 Remark 和 NickName) r.enrichChatRoom(chatRoom) diff --git a/internal/wechatdb/repository/contact.go b/internal/wechatdb/repository/contact.go index 63902fc..14ee879 100644 --- a/internal/wechatdb/repository/contact.go +++ b/internal/wechatdb/repository/contact.go @@ -5,17 +5,14 @@ import ( "sort" "strings" + "github.com/rs/zerolog/log" + "github.com/sjzar/chatlog/internal/errors" "github.com/sjzar/chatlog/internal/model" ) // initContactCache 初始化联系人缓存 func (r *Repository) initContactCache(ctx context.Context) error { - // 加载所有联系人到缓存 - contacts, err := r.ds.GetContacts(ctx, "", 0, 0) - if err != nil { - return err - } contactMap := make(map[string]*model.Contact) aliasMap := make(map[string][]*model.Contact) @@ -28,6 +25,13 @@ func (r *Repository) initContactCache(ctx context.Context) error { remarkList := make([]string, 0) nickNameList := make([]string, 0) + // 加载所有联系人到缓存 + // 暂时忽略获取不到联系人的错误 + contacts, err := r.ds.GetContacts(ctx, "", 0, 0) + if err != nil { + log.Error().Err(err).Msg("Failed to load contacts") + } + for _, contact := range contacts { contactMap[contact.UserName] = contact contactList = append(contactList, contact.UserName)