Files
chatlog/pkg/model/message.go
Shen Junzheng 78cce92ce3 x
2025-03-19 13:11:09 +08:00

302 lines
8.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import (
"fmt"
"strings"
"time"
"github.com/sjzar/chatlog/pkg/model/wxproto"
"github.com/sjzar/chatlog/pkg/util"
"google.golang.org/protobuf/proto"
)
const (
// Source
WeChatV3 = "wechatv3"
WeChatV4 = "wechatv4"
)
type Message struct {
Sequence int64 `json:"sequence"` // 消息序号10位时间戳 + 3位序号
CreateTime time.Time `json:"createTime"` // 消息创建时间10位时间戳
TalkerID int `json:"talkerID"` // 聊天对象Name2ID 表序号,索引值
Talker string `json:"talker"` // 聊天对象,微信 ID or 群 ID
IsSender int `json:"isSender"` // 是否为发送消息0 接收消息1 发送消息
Type int `json:"type"` // 消息类型
SubType int `json:"subType"` // 消息子类型
Content string `json:"content"` // 消息内容,文字聊天内容 或 XML
CompressContent []byte `json:"compressContent"` // 非文字聊天内容,如图片、语音、视频等
IsChatRoom bool `json:"isChatRoom"` // 是否为群聊消息
ChatRoomSender string `json:"chatRoomSender"` // 群聊消息发送人
// Fill Info
// 从联系人等信息中填充
DisplayName string `json:"-"` // 显示名称
CharRoomName string `json:"-"` // 群聊名称
Version string `json:"-"` // 消息版本,内部判断
}
// CREATE TABLE MSG (
// localId INTEGER PRIMARY KEY AUTOINCREMENT,
// TalkerId INT DEFAULT 0,
// MsgSvrID INT,
// Type INT,
// SubType INT,
// IsSender INT,
// CreateTime INT,
// Sequence INT DEFAULT 0,
// StatusEx INT DEFAULT 0,
// FlagEx INT,
// Status INT,
// MsgServerSeq INT,
// MsgSequence INT,
// StrTalker TEXT,
// StrContent TEXT,
// DisplayContent TEXT,
// Reserved0 INT DEFAULT 0,
// Reserved1 INT DEFAULT 0,
// Reserved2 INT DEFAULT 0,
// Reserved3 INT DEFAULT 0,
// Reserved4 TEXT,
// Reserved5 TEXT,
// Reserved6 TEXT,
// CompressContent BLOB,
// BytesExtra BLOB,
// BytesTrans BLOB
// )
type MessageV3 struct {
Sequence int64 `json:"Sequence"` // 消息序号10位时间戳 + 3位序号
CreateTime int64 `json:"CreateTime"` // 消息创建时间10位时间戳
TalkerID int `json:"TalkerId"` // 聊天对象Name2ID 表序号,索引值
StrTalker string `json:"StrTalker"` // 聊天对象,微信 ID or 群 ID
IsSender int `json:"IsSender"` // 是否为发送消息0 接收消息1 发送消息
Type int `json:"Type"` // 消息类型
SubType int `json:"SubType"` // 消息子类型
StrContent string `json:"StrContent"` // 消息内容,文字聊天内容 或 XML
CompressContent []byte `json:"CompressContent"` // 非文字聊天内容,如图片、语音、视频等
BytesExtra []byte `json:"BytesExtra"` // protobuf 额外数据,记录群聊发送人等信息
// 非关键信息,后续有需要再加入
// LocalID int64 `json:"localId"`
// MsgSvrID int64 `json:"MsgSvrID"`
// StatusEx int `json:"StatusEx"`
// FlagEx int `json:"FlagEx"`
// Status int `json:"Status"`
// MsgServerSeq int64 `json:"MsgServerSeq"`
// MsgSequence int64 `json:"MsgSequence"`
// DisplayContent string `json:"DisplayContent"`
// Reserved0 int `json:"Reserved0"`
// Reserved1 int `json:"Reserved1"`
// Reserved2 int `json:"Reserved2"`
// Reserved3 int `json:"Reserved3"`
// Reserved4 string `json:"Reserved4"`
// Reserved5 string `json:"Reserved5"`
// Reserved6 string `json:"Reserved6"`
// BytesTrans []byte `json:"BytesTrans"`
}
func (m *MessageV3) Wrap() *Message {
isChatRoom := strings.HasSuffix(m.StrTalker, "@chatroom")
var chatRoomSender string
if len(m.BytesExtra) != 0 && isChatRoom {
chatRoomSender = ParseBytesExtra(m.BytesExtra)
}
return &Message{
Sequence: m.Sequence,
CreateTime: time.Unix(m.CreateTime, 0),
TalkerID: m.TalkerID,
Talker: m.StrTalker,
IsSender: m.IsSender,
Type: m.Type,
SubType: m.SubType,
Content: m.StrContent,
CompressContent: m.CompressContent,
IsChatRoom: isChatRoom,
ChatRoomSender: chatRoomSender,
Version: WeChatV3,
}
}
// CREATE TABLE Msg_xxxxxxxxxxxx(
// local_id INTEGER PRIMARY KEY AUTOINCREMENT,
// server_id INTEGER,
// local_type INTEGER,
// sort_seq INTEGER,
// real_sender_id INTEGER,
// create_time INTEGER,
// status INTEGER,
// upload_status INTEGER,
// download_status INTEGER,
// server_seq INTEGER,
// origin_source INTEGER,
// source TEXT,
// message_content TEXT,
// compress_content TEXT,
// packed_info_data BLOB,
// WCDB_CT_message_content INTEGER DEFAULT NULL,
// WCDB_CT_source INTEGER DEFAULT NULL
// )
type MessageV4 struct {
SortSeq int64 `json:"sort_seq"` // 消息序号10位时间戳 + 3位序号
LocalType int `json:"local_type"` // 消息类型
RealSenderID int `json:"real_sender_id"` // 发送人 ID对应 Name2Id 表序号
CreateTime int64 `json:"create_time"` // 消息创建时间10位时间戳
MessageContent []byte `json:"message_content"` // 消息内容,文字聊天内容 或 zstd 压缩内容
PackedInfoData []byte `json:"packed_info_data"` // 额外数据,类似 proto格式与 v3 有差异
Status int `json:"status"` // 消息状态2 是已发送4 是已接收,可以用于判断 IsSender猜测
// 非关键信息,后续有需要再加入
// LocalID int `json:"local_id"`
// ServerID int64 `json:"server_id"`
// UploadStatus int `json:"upload_status"`
// DownloadStatus int `json:"download_status"`
// ServerSeq int `json:"server_seq"`
// OriginSource int `json:"origin_source"`
// Source string `json:"source"`
// CompressContent string `json:"compress_content"`
}
func (m *MessageV4) Wrap(id2Name map[int]string, isChatRoom bool) *Message {
_m := &Message{
Sequence: m.SortSeq,
CreateTime: time.Unix(m.CreateTime, 0),
TalkerID: m.RealSenderID, // 依赖 Name2Id 表进行转换为 StrTalker
CompressContent: m.PackedInfoData,
Type: m.LocalType,
Version: WeChatV4,
}
if name, ok := id2Name[m.RealSenderID]; ok {
_m.Talker = name
}
if m.Status == 2 {
_m.IsSender = 1
}
if util.IsNormalString(m.MessageContent) {
_m.Content = string(m.MessageContent)
} else {
_m.CompressContent = m.MessageContent
}
if isChatRoom {
_m.IsChatRoom = true
split := strings.Split(_m.Content, "\n")
if len(split) > 1 {
_m.Content = split[1]
_m.ChatRoomSender = strings.TrimSuffix(split[0], ":")
}
}
return _m
}
// ParseBytesExtra 解析额外数据
// 按需解析
func ParseBytesExtra(b []byte) (chatRoomSender string) {
var pbMsg wxproto.BytesExtra
if err := proto.Unmarshal(b, &pbMsg); err != nil {
return
}
if pbMsg.Items == nil {
return
}
for _, item := range pbMsg.Items {
if item.Type == 1 {
return item.Value
}
}
return
}
func (m *Message) PlainText(showChatRoom bool) string {
buf := strings.Builder{}
talker := m.Talker
if m.IsSender == 1 {
talker = "我"
} else if m.IsChatRoom {
talker = m.ChatRoomSender
}
if m.DisplayName != "" {
buf.WriteString(m.DisplayName)
buf.WriteString("(")
buf.WriteString(talker)
buf.WriteString(")")
} else {
buf.WriteString(talker)
}
buf.WriteString(" ")
if m.IsChatRoom && showChatRoom {
buf.WriteString("[")
if m.CharRoomName != "" {
buf.WriteString(m.CharRoomName)
buf.WriteString("(")
buf.WriteString(m.Talker)
buf.WriteString(")")
} else {
buf.WriteString(m.Talker)
}
buf.WriteString("] ")
}
buf.WriteString(m.CreateTime.Format("2006-01-02 15:04:05"))
buf.WriteString("\n")
switch m.Type {
case 1:
buf.WriteString(m.Content)
case 3:
buf.WriteString("[图片]")
case 34:
buf.WriteString("[语音]")
case 43:
buf.WriteString("[视频]")
case 47:
buf.WriteString("[动画表情]")
case 49:
switch m.SubType {
case 6:
buf.WriteString("[文件]")
case 8:
buf.WriteString("[GIF表情]")
case 19:
buf.WriteString("[合并转发]")
case 33, 36:
buf.WriteString("[小程序]")
case 57:
buf.WriteString("[引用]")
case 63:
buf.WriteString("[视频号]")
case 87:
buf.WriteString("[群公告]")
case 2000:
buf.WriteString("[转账]")
case 2003:
buf.WriteString("[红包封面]")
default:
buf.WriteString("[分享]")
}
case 50:
buf.WriteString("[语音通话]")
case 10000:
buf.WriteString("[系统消息]")
default:
buf.WriteString(fmt.Sprintf("Type: %d Content: %s", m.Type, m.Content))
}
buf.WriteString("\n")
return buf.String()
}