382 lines
9.4 KiB
Go
382 lines
9.4 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sjzar/chatlog/pkg/util"
|
|
)
|
|
|
|
var Debug = false
|
|
|
|
const (
|
|
WeChatV3 = "wechatv3"
|
|
WeChatV4 = "wechatv4"
|
|
WeChatDarwinV3 = "wechatdarwinv3"
|
|
)
|
|
|
|
type Message struct {
|
|
Version string `json:"-"` // 消息版本,内部判断
|
|
Seq int64 `json:"seq"` // 消息序号,10位时间戳 + 3位序号
|
|
Time time.Time `json:"time"` // 消息创建时间,10位时间戳
|
|
Talker string `json:"talker"` // 聊天对象,微信 ID or 群 ID
|
|
TalkerName string `json:"talkerName"` // 聊天对象名称
|
|
IsChatRoom bool `json:"isChatRoom"` // 是否为群聊消息
|
|
Sender string `json:"sender"` // 发送人,微信 ID
|
|
SenderName string `json:"senderName"` // 发送人名称
|
|
IsSelf bool `json:"isSelf"` // 是否为自己发送的消息
|
|
Type int64 `json:"type"` // 消息类型
|
|
SubType int64 `json:"subType"` // 消息子类型
|
|
Content string `json:"content"` // 消息内容,文字聊天内容
|
|
Contents map[string]interface{} `json:"contents,omitempty"` // 消息内容,多媒体消息,采用更灵活的记录方式
|
|
|
|
// Debug Info
|
|
MediaMsg *MediaMsg `json:"mediaMsg,omitempty"` // 原始多媒体消息,XML 格式
|
|
SysMsg *SysMsg `json:"sysMsg,omitempty"` // 原始系统消息,XML 格式
|
|
}
|
|
|
|
func (m *Message) ParseMediaInfo(data string) error {
|
|
|
|
m.Type, m.SubType = util.SplitInt64ToTwoInt32(m.Type)
|
|
|
|
if m.Type == 1 {
|
|
m.Content = data
|
|
return nil
|
|
}
|
|
|
|
if m.Type == 10000 {
|
|
var sysMsg SysMsg
|
|
if err := xml.Unmarshal([]byte(data), &sysMsg); err != nil {
|
|
m.Content = data
|
|
return nil
|
|
}
|
|
if Debug {
|
|
m.SysMsg = &sysMsg
|
|
}
|
|
m.Sender = "系统消息"
|
|
m.SenderName = ""
|
|
m.Content = sysMsg.String()
|
|
return nil
|
|
}
|
|
|
|
var msg MediaMsg
|
|
err := xml.Unmarshal([]byte(data), &msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if m.Contents == nil {
|
|
m.Contents = make(map[string]interface{})
|
|
}
|
|
|
|
if Debug {
|
|
m.MediaMsg = &msg
|
|
}
|
|
|
|
switch m.Type {
|
|
case 3:
|
|
m.Contents["md5"] = msg.Image.MD5
|
|
case 43:
|
|
if msg.Video.Md5 != "" {
|
|
m.Contents["md5"] = msg.Video.Md5
|
|
}
|
|
if msg.Video.RawMd5 != "" {
|
|
m.Contents["rawmd5"] = msg.Video.RawMd5
|
|
}
|
|
case 49:
|
|
m.SubType = int64(msg.App.Type)
|
|
switch m.SubType {
|
|
case 5:
|
|
// 链接
|
|
m.Contents["title"] = msg.App.Title
|
|
m.Contents["url"] = msg.App.URL
|
|
case 6:
|
|
// 文件
|
|
m.Contents["title"] = msg.App.Title
|
|
m.Contents["md5"] = msg.App.MD5
|
|
case 19:
|
|
// 合并转发
|
|
m.Contents["title"] = msg.App.Title
|
|
m.Contents["desc"] = msg.App.Des
|
|
if msg.App.RecordItem == nil {
|
|
break
|
|
}
|
|
recordInfo := &RecordInfo{}
|
|
err := xml.Unmarshal([]byte(msg.App.RecordItem.CDATA), recordInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.Contents["recordInfo"] = recordInfo
|
|
case 33, 36:
|
|
// 小程序
|
|
m.Contents["title"] = msg.App.SourceDisplayName
|
|
m.Contents["url"] = msg.App.URL
|
|
case 51:
|
|
// 视频号
|
|
if msg.App.FinderFeed == nil {
|
|
break
|
|
}
|
|
m.Contents["title"] = msg.App.FinderFeed.Desc
|
|
if len(msg.App.FinderFeed.MediaList.Media) > 0 {
|
|
m.Contents["url"] = msg.App.FinderFeed.MediaList.Media[0].URL
|
|
}
|
|
case 57:
|
|
// 引用
|
|
m.Content = msg.App.Title
|
|
if msg.App.ReferMsg == nil {
|
|
break
|
|
}
|
|
subMsg := &Message{
|
|
Type: int64(msg.App.ReferMsg.Type),
|
|
Time: time.Unix(msg.App.ReferMsg.CreateTime, 0),
|
|
Sender: msg.App.ReferMsg.ChatUsr,
|
|
SenderName: msg.App.ReferMsg.DisplayName,
|
|
}
|
|
if subMsg.Sender == "" {
|
|
subMsg.Sender = msg.App.ReferMsg.FromUsr
|
|
}
|
|
if err := subMsg.ParseMediaInfo(msg.App.ReferMsg.Content); err != nil {
|
|
break
|
|
}
|
|
m.Contents["refer"] = subMsg
|
|
case 62:
|
|
// 拍一拍
|
|
if msg.App.PatMsg == nil {
|
|
break
|
|
}
|
|
if len(msg.App.PatMsg.Records.Record) == 0 {
|
|
break
|
|
}
|
|
m.Sender = msg.App.PatMsg.Records.Record[0].FromUser
|
|
m.Content = msg.App.PatMsg.Records.Record[0].Templete
|
|
case 2000:
|
|
// 微信转账
|
|
if msg.App.WCPayInfo == nil {
|
|
break
|
|
}
|
|
// 1 实时转账
|
|
// 3 实时转账收钱回执
|
|
// 4 转账退还回执
|
|
// 5 非实时转账收钱回执
|
|
// 7 非实时转账
|
|
_type := ""
|
|
switch msg.App.WCPayInfo.PaySubType {
|
|
case 1, 7:
|
|
_type = "发送 "
|
|
case 3, 5:
|
|
_type = "接收 "
|
|
case 4:
|
|
_type = "退还 "
|
|
}
|
|
payMemo := ""
|
|
if len(msg.App.WCPayInfo.PayMemo) > 0 {
|
|
payMemo = "(" + msg.App.WCPayInfo.PayMemo + ")"
|
|
}
|
|
m.Content = fmt.Sprintf("[转账|%s%s]%s", _type, msg.App.WCPayInfo.FeeDesc, payMemo)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Message) SetContent(key string, value interface{}) {
|
|
if m.Contents == nil {
|
|
m.Contents = make(map[string]interface{})
|
|
}
|
|
m.Contents[key] = value
|
|
}
|
|
|
|
func (m *Message) PlainText(showChatRoom bool, timeFormat string, host string) string {
|
|
|
|
if timeFormat == "" {
|
|
timeFormat = "01-02 15:04:05"
|
|
}
|
|
|
|
m.SetContent("host", host)
|
|
|
|
buf := strings.Builder{}
|
|
|
|
sender := m.Sender
|
|
if m.IsSelf {
|
|
sender = "我"
|
|
}
|
|
if m.SenderName != "" {
|
|
buf.WriteString(m.SenderName)
|
|
buf.WriteString("(")
|
|
buf.WriteString(sender)
|
|
buf.WriteString(")")
|
|
} else {
|
|
buf.WriteString(sender)
|
|
}
|
|
buf.WriteString(" ")
|
|
|
|
if m.IsChatRoom && showChatRoom {
|
|
buf.WriteString("[")
|
|
if m.TalkerName != "" {
|
|
buf.WriteString(m.TalkerName)
|
|
buf.WriteString("(")
|
|
buf.WriteString(m.Talker)
|
|
buf.WriteString(")")
|
|
} else {
|
|
buf.WriteString(m.Talker)
|
|
}
|
|
buf.WriteString("] ")
|
|
}
|
|
|
|
buf.WriteString(m.Time.Format(timeFormat))
|
|
buf.WriteString("\n")
|
|
|
|
buf.WriteString(m.PlainTextContent())
|
|
buf.WriteString("\n")
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
func (m *Message) PlainTextContent() string {
|
|
switch m.Type {
|
|
case 1:
|
|
return m.Content
|
|
case 3:
|
|
keylist := make([]string, 0)
|
|
if m.Contents["md5"] != nil {
|
|
if md5, ok := m.Contents["md5"].(string); ok {
|
|
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)
|
|
}
|
|
}
|
|
return fmt.Sprintf("", m.Contents["host"], strings.Join(keylist, ","))
|
|
case 34:
|
|
if voice, ok := m.Contents["voice"]; ok {
|
|
return fmt.Sprintf("[语音](http://%s/voice/%s)", m.Contents["host"], voice)
|
|
}
|
|
return "[语音]"
|
|
case 42:
|
|
return "[名片]"
|
|
case 43:
|
|
keylist := make([]string, 0)
|
|
if m.Contents["md5"] != nil {
|
|
if md5, ok := m.Contents["md5"].(string); ok {
|
|
keylist = append(keylist, md5)
|
|
}
|
|
}
|
|
if m.Contents["rawmd5"] != nil {
|
|
if rawmd5, ok := m.Contents["rawmd5"].(string); ok {
|
|
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)
|
|
}
|
|
}
|
|
return fmt.Sprintf("", m.Contents["host"], strings.Join(keylist, ","))
|
|
case 47:
|
|
return "[动画表情]"
|
|
case 49:
|
|
switch m.SubType {
|
|
case 5:
|
|
return fmt.Sprintf("[链接|%s](%s)", m.Contents["title"], m.Contents["url"])
|
|
case 6:
|
|
return fmt.Sprintf("[文件|%s](http://%s/file/%s)", m.Contents["title"], m.Contents["host"], m.Contents["md5"])
|
|
case 8:
|
|
return "[GIF表情]"
|
|
case 19:
|
|
_recordInfo, ok := m.Contents["recordInfo"]
|
|
if !ok {
|
|
return "[合并转发]"
|
|
}
|
|
recordInfo, ok := _recordInfo.(*RecordInfo)
|
|
if !ok {
|
|
return "[合并转发]"
|
|
}
|
|
host := ""
|
|
if m.Contents["host"] != nil {
|
|
host = m.Contents["host"].(string)
|
|
}
|
|
return recordInfo.String("", host)
|
|
case 33, 36:
|
|
if m.Contents["title"] == "" {
|
|
return "[小程序]"
|
|
}
|
|
return fmt.Sprintf("[小程序|%s](%s)", m.Contents["title"], m.Contents["url"])
|
|
case 51:
|
|
if m.Contents["title"] == "" {
|
|
return "[视频号]"
|
|
} else {
|
|
return fmt.Sprintf("[视频号|%s](%s)", m.Contents["title"], m.Contents["url"])
|
|
}
|
|
case 57:
|
|
_refer, ok := m.Contents["refer"]
|
|
if !ok {
|
|
if m.Content == "" {
|
|
return "[引用]"
|
|
}
|
|
return "> [引用]\n" + m.Content
|
|
}
|
|
refer, ok := _refer.(*Message)
|
|
if !ok {
|
|
if m.Content == "" {
|
|
return "[引用]"
|
|
}
|
|
return "> [引用]\n" + m.Content
|
|
}
|
|
buf := strings.Builder{}
|
|
host := ""
|
|
if m.Contents["host"] != nil {
|
|
host = m.Contents["host"].(string)
|
|
}
|
|
referContent := refer.PlainText(false, "", host)
|
|
for _, line := range strings.Split(referContent, "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
buf.WriteString("> ")
|
|
buf.WriteString(line)
|
|
buf.WriteString("\n")
|
|
}
|
|
buf.WriteString(m.Content)
|
|
return buf.String()
|
|
case 62:
|
|
return m.Content
|
|
case 63:
|
|
return "[视频号]"
|
|
case 87:
|
|
return "[群公告]"
|
|
case 2000:
|
|
return m.Content
|
|
case 2001:
|
|
return "[红包]"
|
|
case 2003:
|
|
return "[红包封面]"
|
|
default:
|
|
return "[分享]"
|
|
}
|
|
case 50:
|
|
return "[语音通话]"
|
|
case 10000:
|
|
return m.Content
|
|
default:
|
|
content := m.Content
|
|
if len(content) > 120 {
|
|
content = content[:120] + "<...>"
|
|
}
|
|
return fmt.Sprintf("Type: %d Content: %s", m.Type, content)
|
|
}
|
|
}
|