2171 lines
64 KiB
Go
2171 lines
64 KiB
Go
package wechat
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
sync "sync"
|
|
"time"
|
|
"wechatDataBackup/pkg/utils"
|
|
|
|
"github.com/beevik/etree"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/pierrec/lz4"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
const (
|
|
Wechat_Message_Type_Text = 1
|
|
Wechat_Message_Type_Picture = 3
|
|
Wechat_Message_Type_Voice = 34
|
|
Wechat_Message_Type_Visit_Card = 42
|
|
Wechat_Message_Type_Video = 43
|
|
Wechat_Message_Type_Emoji = 47
|
|
Wechat_Message_Type_Location = 48
|
|
Wechat_Message_Type_Misc = 49
|
|
Wechat_Message_Type_Voip = 50
|
|
Wechat_Message_Type_System = 10000
|
|
)
|
|
|
|
const (
|
|
Wechat_Misc_Message_TEXT = 1
|
|
Wechat_Misc_Message_Music = 3
|
|
Wechat_Misc_Message_ThirdVideo = 4
|
|
Wechat_Misc_Message_CardLink = 5
|
|
Wechat_Misc_Message_File = 6
|
|
Wechat_Misc_Message_CustomEmoji = 8
|
|
Wechat_Misc_Message_ShareEmoji = 15
|
|
Wechat_Misc_Message_ForwardMessage = 19
|
|
Wechat_Misc_Message_Applet = 33
|
|
Wechat_Misc_Message_Applet2 = 36
|
|
Wechat_Misc_Message_Channels = 51
|
|
Wechat_Misc_Message_Refer = 57
|
|
Wechat_Misc_Message_Live = 63
|
|
Wechat_Misc_Message_Game = 68
|
|
Wechat_Misc_Message_Notice = 87
|
|
Wechat_Misc_Message_Live2 = 88
|
|
Wechat_Misc_Message_TingListen = 92
|
|
Wechat_Misc_Message_Transfer = 2000
|
|
Wechat_Misc_Message_RedPacket = 2003
|
|
)
|
|
|
|
const (
|
|
Wechat_System_Message_Notice = 1
|
|
Wechat_System_Message_Tickle = 4
|
|
Wechat_System_Message_Notice2 = 8000
|
|
)
|
|
|
|
type Message_Search_Direction int
|
|
|
|
const (
|
|
Message_Search_Forward Message_Search_Direction = iota
|
|
Message_Search_Backward
|
|
Message_Search_Both
|
|
)
|
|
|
|
type WeChatUserInfo struct {
|
|
UserName string `json:"UserName"`
|
|
Alias string `json:"Alias"`
|
|
ReMark string `json:"ReMark"`
|
|
NickName string `json:"NickName"`
|
|
SmallHeadImgUrl string `json:"SmallHeadImgUrl"`
|
|
BigHeadImgUrl string `json:"BigHeadImgUrl"`
|
|
LocalHeadImgUrl string `json:"LocalHeadImgUrl"`
|
|
IsGroup bool `json:"IsGroup"`
|
|
}
|
|
|
|
type WeChatSession struct {
|
|
UserName string `json:"UserName"`
|
|
NickName string `json:"NickName"`
|
|
Content string `json:"Content"`
|
|
UserInfo WeChatUserInfo `json:"UserInfo"`
|
|
Time uint64 `json:"Time"`
|
|
IsGroup bool `json:"IsGroup"`
|
|
}
|
|
|
|
type WeChatSessionList struct {
|
|
Total int `json:"Total"`
|
|
Rows []WeChatSession `json:"Rows"`
|
|
}
|
|
|
|
type FileInfo struct {
|
|
FileName string `json:"fileName"`
|
|
FileSize string `json:"fileSize"`
|
|
FilePath string `json:"filePath"`
|
|
FileExt string `json:"fileExt"`
|
|
}
|
|
|
|
type LinkInfo struct {
|
|
Url string `json:"Url"`
|
|
Title string `json:"Title"`
|
|
Description string `json:"Description"`
|
|
DisPlayName string `json:"DisPlayName"`
|
|
}
|
|
|
|
type ReferInfo struct {
|
|
Type int `json:"Type"`
|
|
SubType int `json:"SubType"`
|
|
Svrid int64 `json:"Svrid"`
|
|
Displayname string `json:"Displayname"`
|
|
Content string `json:"Content"`
|
|
}
|
|
|
|
type PayInfo struct {
|
|
Type int
|
|
Memo string
|
|
BeginTime string
|
|
Feedesc string
|
|
}
|
|
|
|
type VoipInfo struct {
|
|
Type int
|
|
Msg string
|
|
}
|
|
|
|
type ChannelsInfo struct {
|
|
ThumbPath string
|
|
ThumbCache string
|
|
NickName string
|
|
Description string
|
|
}
|
|
|
|
type MusicInfo struct {
|
|
ThumbPath string
|
|
Title string
|
|
Description string
|
|
DisPlayName string
|
|
DataUrl string
|
|
}
|
|
|
|
type LocationInfo struct {
|
|
Label string
|
|
PoiName string
|
|
X string
|
|
Y string
|
|
ThumbPath string
|
|
}
|
|
|
|
type WeChatMessage struct {
|
|
LocalId int `json:"LocalId"`
|
|
MsgSvrId string `json:"MsgSvrId"`
|
|
Type int `json:"type"`
|
|
SubType int `json:"SubType"`
|
|
IsSender int `json:"IsSender"`
|
|
CreateTime int64 `json:"createTime"`
|
|
Talker string `json:"talker"`
|
|
Content string `json:"content"`
|
|
ThumbPath string `json:"ThumbPath"`
|
|
ImagePath string `json:"ImagePath"`
|
|
VideoPath string `json:"VideoPath"`
|
|
FileInfo FileInfo `json:"fileInfo"`
|
|
EmojiPath string `json:"EmojiPath"`
|
|
VoicePath string `json:"VoicePath"`
|
|
IsChatRoom bool `json:"isChatRoom"`
|
|
UserInfo WeChatUserInfo `json:"userInfo"`
|
|
LinkInfo LinkInfo `json:"LinkInfo"`
|
|
ReferInfo ReferInfo `json:"ReferInfo"`
|
|
PayInfo PayInfo `json:"PayInfo"`
|
|
VoipInfo VoipInfo `json:"VoipInfo"`
|
|
VisitInfo WeChatUserInfo `json:"VisitInfo"`
|
|
ChannelsInfo ChannelsInfo `json:"ChannelsInfo"`
|
|
MusicInfo MusicInfo `json:"MusicInfo"`
|
|
LocationInfo LocationInfo `json:"LocationInfo"`
|
|
compressContent []byte
|
|
bytesExtra []byte
|
|
}
|
|
|
|
type WeChatMessageList struct {
|
|
MsgType string `json:"MsgType"`
|
|
KeyWord string `json:"KeyWord"`
|
|
Total int `json:"Total"`
|
|
Rows []WeChatMessage `json:"Rows"`
|
|
}
|
|
|
|
type WeChatMessageDate struct {
|
|
Date []string `json:"Date"`
|
|
Total int `json:"Total"`
|
|
}
|
|
|
|
type WeChatUserList struct {
|
|
Users []WeChatUserInfo `json:"Users"`
|
|
Total int `json:"Total"`
|
|
}
|
|
|
|
type WeChatContact struct {
|
|
WeChatUserInfo
|
|
PYInitial string
|
|
QuanPin string
|
|
RemarkPYInitial string
|
|
RemarkQuanPin string
|
|
}
|
|
|
|
type WeChatContactList struct {
|
|
Users []WeChatContact `json:"Users"`
|
|
Total int `json:"Total"`
|
|
}
|
|
|
|
type WeChatAccountInfo struct {
|
|
AccountName string `json:"AccountName"`
|
|
AliasName string `json:"AliasName"`
|
|
ReMarkName string `json:"ReMarkName"`
|
|
NickName string `json:"NickName"`
|
|
SmallHeadImgUrl string `json:"SmallHeadImgUrl"`
|
|
BigHeadImgUrl string `json:"BigHeadImgUrl"`
|
|
LocalHeadImgUrl string `json:"LocalHeadImgUrl"`
|
|
}
|
|
|
|
type WeChatLastTime struct {
|
|
UserName string `json:"UserName"`
|
|
Timestamp int64 `json:"Timestamp"`
|
|
MessageId string `json:"MessageId"`
|
|
}
|
|
|
|
type WeChatBookMark struct {
|
|
MarkId string `json:"MarkId"`
|
|
Tag string `json:"Tag"`
|
|
Info string `json:"Info"`
|
|
}
|
|
|
|
type WeChatBookMarkList struct {
|
|
Marks []WeChatBookMark `json:"Marks"`
|
|
Total int `json:"Total"`
|
|
}
|
|
|
|
type wechatMsgDB struct {
|
|
path string
|
|
db *sql.DB
|
|
startTime int64
|
|
endTime int64
|
|
}
|
|
|
|
type WechatDataProvider struct {
|
|
resPath string
|
|
prefixResPath string
|
|
microMsg *sql.DB
|
|
openIMContact *sql.DB
|
|
userData *sql.DB
|
|
msgDBs []*wechatMsgDB
|
|
userInfoMap map[string]WeChatUserInfo
|
|
userInfoMtx sync.Mutex
|
|
|
|
SelfInfo *WeChatUserInfo
|
|
ContactList *WeChatContactList
|
|
IsShareData bool
|
|
}
|
|
|
|
const (
|
|
MicroMsgDB = "MicroMsg.db"
|
|
OpenIMContactDB = "OpenIMContact.db"
|
|
UserDataDB = "UserData.db"
|
|
)
|
|
|
|
type byTime []*wechatMsgDB
|
|
|
|
func (a byTime) Len() int { return len(a) }
|
|
func (a byTime) Less(i, j int) bool { return a[i].startTime > a[j].startTime }
|
|
func (a byTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type byName []WeChatContact
|
|
|
|
func (c byName) Len() int { return len(c) }
|
|
func (c byName) Less(i, j int) bool {
|
|
var a, b string
|
|
if c[i].RemarkQuanPin != "" {
|
|
a = c[i].RemarkQuanPin
|
|
} else {
|
|
a = c[i].QuanPin
|
|
}
|
|
|
|
if c[j].RemarkQuanPin != "" {
|
|
b = c[j].RemarkQuanPin
|
|
} else {
|
|
b = c[j].QuanPin
|
|
}
|
|
|
|
return strings.Compare(a, b) < 0
|
|
}
|
|
func (c byName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
|
|
|
func CreateWechatDataProvider(resPath string, prefixRes string) (*WechatDataProvider, error) {
|
|
provider := &WechatDataProvider{}
|
|
provider.resPath = resPath
|
|
provider.prefixResPath = prefixRes
|
|
provider.msgDBs = make([]*wechatMsgDB, 0)
|
|
log.Println(resPath)
|
|
|
|
userName := filepath.Base(resPath)
|
|
MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB
|
|
if _, err := os.Stat(MicroMsgDBPath); err != nil {
|
|
log.Println("CreateWechatDataProvider failed", MicroMsgDBPath, err)
|
|
return provider, err
|
|
}
|
|
microMsg, err := sql.Open("sqlite3", MicroMsgDBPath)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", MicroMsgDBPath, err)
|
|
return provider, err
|
|
}
|
|
|
|
var openIMContact *sql.DB
|
|
OpenIMContactDBPath := resPath + "\\Msg\\" + OpenIMContactDB
|
|
if _, err := os.Stat(OpenIMContactDBPath); err == nil {
|
|
openIMContact, err = sql.Open("sqlite3", OpenIMContactDBPath)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", OpenIMContactDBPath, err)
|
|
}
|
|
}
|
|
|
|
UserDataDBPath := resPath + "\\Msg\\" + UserDataDB
|
|
userData := openUserDataDB(UserDataDBPath)
|
|
if userData == nil {
|
|
log.Printf("open db %s error: %v", UserDataDBPath, err)
|
|
return provider, err
|
|
}
|
|
|
|
msgDBPath := fmt.Sprintf("%s\\Msg\\Multi\\MSG.db", provider.resPath)
|
|
if _, err := os.Stat(msgDBPath); err == nil {
|
|
log.Println("msgDBPath", msgDBPath)
|
|
msgDB, err := wechatOpenMsgDB(msgDBPath)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", msgDBPath, err)
|
|
} else {
|
|
provider.msgDBs = append(provider.msgDBs, msgDB)
|
|
log.Printf("MSG.db start %d - %d end\n", msgDB.startTime, msgDB.endTime)
|
|
provider.IsShareData = true
|
|
}
|
|
}
|
|
|
|
index := 0
|
|
for {
|
|
msgDBPath := fmt.Sprintf("%s\\Msg\\Multi\\MSG%d.db", provider.resPath, index)
|
|
if _, err := os.Stat(msgDBPath); err != nil {
|
|
log.Println("msgDBPath end", msgDBPath)
|
|
break
|
|
}
|
|
|
|
msgDB, err := wechatOpenMsgDB(msgDBPath)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", msgDBPath, err)
|
|
index += 1
|
|
continue
|
|
}
|
|
provider.msgDBs = append(provider.msgDBs, msgDB)
|
|
log.Printf("MSG%d.db start %d - %d end\n", index, msgDB.startTime, msgDB.endTime)
|
|
index += 1
|
|
}
|
|
sort.Sort(byTime(provider.msgDBs))
|
|
for _, db := range provider.msgDBs {
|
|
log.Printf("%s start %d - %d end\n", db.path, db.startTime, db.endTime)
|
|
}
|
|
provider.userInfoMap = make(map[string]WeChatUserInfo)
|
|
provider.microMsg = microMsg
|
|
provider.openIMContact = openIMContact
|
|
provider.userData = userData
|
|
provider.SelfInfo, err = provider.WechatGetUserInfoByNameOnCache(userName)
|
|
if err != nil {
|
|
log.Printf("WechatGetUserInfoByName %s failed: %v", userName, err)
|
|
return provider, err
|
|
}
|
|
|
|
provider.ContactList, err = provider.wechatGetAllContact()
|
|
if err != nil {
|
|
log.Println("wechatGetAllContact failed", err)
|
|
return provider, err
|
|
}
|
|
sort.Sort(byName(provider.ContactList.Users))
|
|
log.Println("Contact number:", provider.ContactList.Total)
|
|
provider.userInfoMap[userName] = *provider.SelfInfo
|
|
log.Println("resPath:", provider.resPath)
|
|
return provider, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WechatWechatDataProviderClose() {
|
|
if P.microMsg != nil {
|
|
err := P.microMsg.Close()
|
|
if err != nil {
|
|
log.Println("db close:", err)
|
|
}
|
|
}
|
|
|
|
if P.openIMContact != nil {
|
|
err := P.openIMContact.Close()
|
|
if err != nil {
|
|
log.Println("db close:", err)
|
|
}
|
|
}
|
|
|
|
if P.userData != nil {
|
|
err := P.userData.Close()
|
|
if err != nil {
|
|
log.Println("db close:", err)
|
|
}
|
|
}
|
|
|
|
for _, db := range P.msgDBs {
|
|
err := db.db.Close()
|
|
if err != nil {
|
|
log.Println("db close:", err)
|
|
}
|
|
}
|
|
log.Println("WechatWechatDataProviderClose:", P.resPath)
|
|
}
|
|
|
|
func (P *WechatDataProvider) WechatGetUserInfoByName(name string) (*WeChatUserInfo, error) {
|
|
info := &WeChatUserInfo{}
|
|
|
|
var UserName, Alias, ReMark, NickName string
|
|
querySql := fmt.Sprintf("select ifnull(UserName,'') as UserName, ifnull(Alias,'') as Alias, ifnull(ReMark,'') as ReMark, ifnull(NickName,'') as NickName from Contact where UserName='%s';", name)
|
|
// log.Println(querySql)
|
|
err := P.microMsg.QueryRow(querySql).Scan(&UserName, &Alias, &ReMark, &NickName)
|
|
if err != nil {
|
|
// log.Println("not found User:", err)
|
|
return info, err
|
|
}
|
|
|
|
// log.Printf("UserName %s, Alias %s, ReMark %s, NickName %s\n", UserName, Alias, ReMark, NickName)
|
|
|
|
var smallHeadImgUrl, bigHeadImgUrl string
|
|
querySql = fmt.Sprintf("select ifnull(smallHeadImgUrl,'') as smallHeadImgUrl, ifnull(bigHeadImgUrl,'') as bigHeadImgUrl from ContactHeadImgUrl where usrName='%s';", UserName)
|
|
// log.Println(querySql)
|
|
err = P.microMsg.QueryRow(querySql).Scan(&smallHeadImgUrl, &bigHeadImgUrl)
|
|
if err != nil {
|
|
log.Println("not find headimg", err)
|
|
}
|
|
|
|
info.UserName = UserName
|
|
info.Alias = Alias
|
|
info.ReMark = ReMark
|
|
info.NickName = NickName
|
|
info.SmallHeadImgUrl = smallHeadImgUrl
|
|
info.BigHeadImgUrl = bigHeadImgUrl
|
|
info.IsGroup = strings.HasSuffix(UserName, "@chatroom")
|
|
|
|
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.resPath, name)
|
|
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.prefixResPath, name)
|
|
if _, err = os.Stat(localHeadImgPath); err == nil {
|
|
info.LocalHeadImgUrl = relativePath
|
|
}
|
|
// log.Println(info)
|
|
return info, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WechatGetOpenIMMUserInfoByName(name string) (*WeChatUserInfo, error) {
|
|
info := &WeChatUserInfo{}
|
|
|
|
var UserName, ReMark, NickName string
|
|
querySql := fmt.Sprintf("select ifnull(UserName,'') as UserName, ifnull(ReMark,'') as ReMark, ifnull(NickName,'') as NickName from OpenIMContact where UserName='%s';", name)
|
|
// log.Println(querySql)
|
|
if P.openIMContact != nil {
|
|
err := P.openIMContact.QueryRow(querySql).Scan(&UserName, &ReMark, &NickName)
|
|
if err != nil {
|
|
log.Println("not found User:", err)
|
|
return info, err
|
|
}
|
|
}
|
|
|
|
log.Printf("UserName %s, ReMark %s, NickName %s\n", UserName, ReMark, NickName)
|
|
|
|
var smallHeadImgUrl, bigHeadImgUrl string
|
|
querySql = fmt.Sprintf("select ifnull(smallHeadImgUrl,'') as smallHeadImgUrl, ifnull(bigHeadImgUrl,'') as bigHeadImgUrl from ContactHeadImgUrl where usrName='%s';", UserName)
|
|
// log.Println(querySql)
|
|
err := P.microMsg.QueryRow(querySql).Scan(&smallHeadImgUrl, &bigHeadImgUrl)
|
|
if err != nil {
|
|
log.Println("not find headimg", err)
|
|
}
|
|
|
|
info.UserName = UserName
|
|
info.Alias = ""
|
|
info.ReMark = ReMark
|
|
info.NickName = NickName
|
|
info.SmallHeadImgUrl = smallHeadImgUrl
|
|
info.BigHeadImgUrl = bigHeadImgUrl
|
|
info.IsGroup = strings.HasSuffix(UserName, "@chatroom")
|
|
|
|
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.resPath, name)
|
|
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.prefixResPath, name)
|
|
if _, err = os.Stat(localHeadImgPath); err == nil {
|
|
info.LocalHeadImgUrl = relativePath
|
|
}
|
|
// log.Println(info)
|
|
return info, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (*WeChatSessionList, error) {
|
|
List := &WeChatSessionList{}
|
|
List.Rows = make([]WeChatSession, 0)
|
|
|
|
querySql := fmt.Sprintf("select ifnull(strUsrName,'') as strUsrName,ifnull(strNickName,'') as strNickName,ifnull(strContent,'') as strContent, nMsgType, nTime from Session order by nOrder desc limit %d, %d;", pageIndex*pageSize, pageSize)
|
|
dbRows, err := P.microMsg.Query(querySql)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return List, err
|
|
}
|
|
defer dbRows.Close()
|
|
|
|
var strUsrName, strNickName, strContent string
|
|
var nTime uint64
|
|
var nMsgType int
|
|
for dbRows.Next() {
|
|
var session WeChatSession
|
|
err = dbRows.Scan(&strUsrName, &strNickName, &strContent, &nMsgType, &nTime)
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
if len(strContent) == 0 {
|
|
// log.Printf("%s cotent nil\n", strUsrName)
|
|
continue
|
|
}
|
|
|
|
session.UserName = strUsrName
|
|
session.NickName = strNickName
|
|
session.Content = systemMsgParse(nMsgType, strContent)
|
|
session.Time = nTime
|
|
session.IsGroup = strings.HasSuffix(strUsrName, "@chatroom")
|
|
info, err := P.WechatGetUserInfoByNameOnCache(strUsrName)
|
|
if err != nil {
|
|
log.Printf("WechatGetUserInfoByName %s failed\n", strUsrName)
|
|
continue
|
|
}
|
|
session.UserInfo = *info
|
|
List.Rows = append(List.Rows, session)
|
|
List.Total += 1
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetContactList(pageIndex int, pageSize int) (*WeChatUserList, error) {
|
|
List := &WeChatUserList{}
|
|
List.Users = make([]WeChatUserInfo, 0)
|
|
|
|
if P.ContactList.Total <= pageIndex*pageSize {
|
|
return List, nil
|
|
}
|
|
end := (pageIndex * pageSize) + pageSize
|
|
if end > P.ContactList.Total {
|
|
end = P.ContactList.Total
|
|
}
|
|
|
|
log.Printf("P.ContactList.Total %d, start %d, end %d", P.ContactList.Total, pageIndex*pageSize, end)
|
|
var info WeChatUserInfo
|
|
for _, contact := range P.ContactList.Users[pageIndex*pageSize : end] {
|
|
info = contact.WeChatUserInfo
|
|
List.Users = append(List.Users, info)
|
|
List.Total += 1
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetMessageListByTime(userName string, time int64, pageSize int, direction Message_Search_Direction) (*WeChatMessageList, error) {
|
|
|
|
List := &WeChatMessageList{}
|
|
List.Rows = make([]WeChatMessage, 0)
|
|
selectTime := time
|
|
selectpageSize := pageSize
|
|
|
|
if direction == Message_Search_Both {
|
|
selectpageSize = pageSize / 2
|
|
}
|
|
for direction == Message_Search_Forward || direction == Message_Search_Both {
|
|
selectList, err := P.weChatGetMessageListByTime(userName, selectTime, selectpageSize, Message_Search_Forward)
|
|
if err != nil {
|
|
return List, err
|
|
}
|
|
|
|
if selectList.Total == 0 {
|
|
break
|
|
}
|
|
|
|
selectTime = selectList.Rows[selectList.Total-1].CreateTime - 1
|
|
selectpageSize -= selectList.Total
|
|
List.Total += selectList.Total
|
|
List.Rows = append(List.Rows, selectList.Rows...)
|
|
if selectpageSize <= 0 {
|
|
break
|
|
}
|
|
log.Printf("Forward selectTime %d, selectpageSize %d\n", selectTime, selectpageSize)
|
|
}
|
|
|
|
selectTime = time
|
|
if direction == Message_Search_Both {
|
|
selectpageSize = pageSize / 2
|
|
}
|
|
for direction == Message_Search_Backward || direction == Message_Search_Both {
|
|
selectList, err := P.weChatGetMessageListByTime(userName, selectTime, selectpageSize, Message_Search_Backward)
|
|
if err != nil {
|
|
return List, err
|
|
}
|
|
|
|
if selectList.Total == 0 {
|
|
break
|
|
}
|
|
|
|
selectTime = selectList.Rows[0].CreateTime + 1
|
|
selectpageSize -= selectList.Total
|
|
List.Total += selectList.Total
|
|
List.Rows = append(selectList.Rows, List.Rows...)
|
|
if selectpageSize <= 0 {
|
|
break
|
|
}
|
|
log.Printf("Backward selectTime %d, selectpageSize %d\n", selectTime, selectpageSize)
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) weChatGetMessageListByTime(userName string, time int64, pageSize int, direction Message_Search_Direction) (*WeChatMessageList, error) {
|
|
List := &WeChatMessageList{}
|
|
List.Rows = make([]WeChatMessage, 0)
|
|
index := P.wechatFindDBIndex(userName, time, direction)
|
|
if index == -1 {
|
|
log.Printf("Not found %s %d data\n", userName, time)
|
|
return List, nil
|
|
}
|
|
|
|
sqlFormat := "select localId,MsgSvrID,Type,SubType,IsSender,CreateTime,ifnull(StrTalker,'') as StrTalker, ifnull(StrContent,'') as StrContent,ifnull(CompressContent,'') as CompressContent,ifnull(BytesExtra,'') as BytesExtra from MSG Where StrTalker='%s' And CreateTime<=%d order by Sequence desc limit %d;"
|
|
if direction == Message_Search_Backward {
|
|
sqlFormat = "select localId,MsgSvrID,Type,SubType,IsSender,CreateTime,ifnull(StrTalker,'') as StrTalker, ifnull(StrContent,'') as StrContent,ifnull(CompressContent,'') as CompressContent,ifnull(BytesExtra,'') as BytesExtra from ( select localId, MsgSvrID, Type, SubType, IsSender, CreateTime, Sequence, StrTalker, StrContent, CompressContent, BytesExtra FROM MSG Where StrTalker='%s' And CreateTime>%d order by Sequence asc limit %d) AS SubQuery order by Sequence desc;"
|
|
}
|
|
querySql := fmt.Sprintf(sqlFormat, userName, time, pageSize)
|
|
log.Println(querySql)
|
|
|
|
rows, err := P.msgDBs[index].db.Query(querySql)
|
|
if err != nil {
|
|
log.Printf("%s failed %v\n", querySql, err)
|
|
return List, nil
|
|
}
|
|
defer rows.Close()
|
|
var localId, Type, SubType, IsSender int
|
|
var MsgSvrID, CreateTime int64
|
|
var StrTalker, StrContent string
|
|
var CompressContent, BytesExtra []byte
|
|
|
|
for rows.Next() {
|
|
message := WeChatMessage{}
|
|
err = rows.Scan(&localId, &MsgSvrID, &Type, &SubType, &IsSender, &CreateTime,
|
|
&StrTalker, &StrContent, &CompressContent, &BytesExtra)
|
|
if err != nil {
|
|
log.Println("rows.Scan failed", err)
|
|
return List, err
|
|
}
|
|
|
|
message.LocalId = localId
|
|
message.MsgSvrId = fmt.Sprintf("%d", MsgSvrID)
|
|
message.Type = Type
|
|
message.SubType = SubType
|
|
message.IsSender = IsSender
|
|
message.CreateTime = CreateTime
|
|
message.Talker = StrTalker
|
|
message.Content = systemMsgParse(Type, StrContent)
|
|
message.IsChatRoom = strings.HasSuffix(StrTalker, "@chatroom")
|
|
message.compressContent = make([]byte, len(CompressContent))
|
|
message.bytesExtra = make([]byte, len(BytesExtra))
|
|
copy(message.compressContent, CompressContent)
|
|
copy(message.bytesExtra, BytesExtra)
|
|
P.wechatMessageExtraHandle(&message)
|
|
P.wechatMessageGetUserInfo(&message)
|
|
P.wechatMessageEmojiHandle(&message)
|
|
P.wechatMessageCompressContentHandle(&message)
|
|
P.wechatMessageVoipHandle(&message)
|
|
P.wechatMessageVisitHandke(&message)
|
|
P.wechatMessageLocationHandke(&message)
|
|
List.Rows = append(List.Rows, message)
|
|
List.Total += 1
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
log.Println("rows.Scan failed", err)
|
|
return List, err
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetMessageListByKeyWord(userName string, time int64, keyWord string, msgType string, pageSize int) (*WeChatMessageList, error) {
|
|
List := &WeChatMessageList{}
|
|
List.Rows = make([]WeChatMessage, 0)
|
|
List.KeyWord = keyWord
|
|
List.MsgType = msgType
|
|
_time := time
|
|
selectPagesize := pageSize
|
|
if keyWord != "" || msgType != "" {
|
|
selectPagesize = 600
|
|
}
|
|
for {
|
|
log.Println("time:", _time, keyWord)
|
|
rawList, err := P.weChatGetMessageListByTime(userName, _time, selectPagesize, Message_Search_Forward)
|
|
if err != nil {
|
|
log.Println("weChatGetMessageListByTime failed: ", err)
|
|
return nil, err
|
|
}
|
|
log.Println("rawList.Total:", rawList.Total)
|
|
if rawList.Total == 0 {
|
|
if List.Total == 0 {
|
|
log.Printf("user %s not find [%s]\n", userName, keyWord)
|
|
}
|
|
break
|
|
}
|
|
|
|
for i, _ := range rawList.Rows {
|
|
if weChatMessageTypeFilter(&rawList.Rows[i], msgType) && (len(keyWord) == 0 || weChatMessageContains(&rawList.Rows[i], keyWord)) {
|
|
List.Rows = append(List.Rows, rawList.Rows[i])
|
|
List.Total += 1
|
|
if List.Total >= pageSize {
|
|
return List, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
_time = rawList.Rows[rawList.Total-1].CreateTime - 1
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetMessageListByType(userName string, time int64, pageSize int, msgType string, direction Message_Search_Direction) (*WeChatMessageList, error) {
|
|
|
|
List := &WeChatMessageList{}
|
|
List.Rows = make([]WeChatMessage, 0)
|
|
List.MsgType = msgType
|
|
selectTime := time
|
|
selectpageSize := 30
|
|
needSize := pageSize
|
|
|
|
if msgType != "" {
|
|
selectpageSize = 600
|
|
}
|
|
if direction == Message_Search_Both {
|
|
needSize = pageSize / 2
|
|
}
|
|
for direction == Message_Search_Forward || direction == Message_Search_Both {
|
|
selectList, err := P.weChatGetMessageListByTime(userName, selectTime, selectpageSize, Message_Search_Forward)
|
|
if err != nil {
|
|
return List, err
|
|
}
|
|
|
|
if selectList.Total == 0 {
|
|
break
|
|
}
|
|
|
|
for i, _ := range selectList.Rows {
|
|
if weChatMessageTypeFilter(&selectList.Rows[i], msgType) {
|
|
List.Rows = append(List.Rows, selectList.Rows[i])
|
|
List.Total += 1
|
|
needSize -= 1
|
|
if needSize <= 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if needSize <= 0 {
|
|
break
|
|
}
|
|
selectTime = selectList.Rows[selectList.Total-1].CreateTime - 1
|
|
log.Printf("Forward selectTime %d, selectpageSize %d needSize %d\n", selectTime, selectpageSize, needSize)
|
|
}
|
|
|
|
selectTime = time
|
|
if direction == Message_Search_Both {
|
|
needSize = pageSize / 2
|
|
}
|
|
for direction == Message_Search_Backward || direction == Message_Search_Both {
|
|
selectList, err := P.weChatGetMessageListByTime(userName, selectTime, selectpageSize, Message_Search_Backward)
|
|
if err != nil {
|
|
return List, err
|
|
}
|
|
|
|
if selectList.Total == 0 {
|
|
break
|
|
}
|
|
|
|
tmpTotal := 0
|
|
tmpRows := make([]WeChatMessage, 0)
|
|
for i := selectList.Total - 1; i >= 0; i-- {
|
|
if weChatMessageTypeFilter(&selectList.Rows[i], msgType) {
|
|
tmpRows = append([]WeChatMessage{selectList.Rows[i]}, tmpRows...)
|
|
tmpTotal += 1
|
|
needSize -= 1
|
|
if needSize <= 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if tmpTotal > 0 {
|
|
List.Rows = append(tmpRows, List.Rows...)
|
|
List.Total += tmpTotal
|
|
}
|
|
selectTime = selectList.Rows[0].CreateTime + 1
|
|
if needSize <= 0 {
|
|
break
|
|
}
|
|
log.Printf("Backward selectTime %d, selectpageSize %d needSize %d\n", selectTime, selectpageSize, needSize)
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetMessageDate(userName string) (*WeChatMessageDate, error) {
|
|
messageData := &WeChatMessageDate{}
|
|
messageData.Date = make([]string, 0)
|
|
messageData.Total = 0
|
|
|
|
_time := time.Now().Unix()
|
|
|
|
for {
|
|
index := P.wechatFindDBIndex(userName, _time, Message_Search_Forward)
|
|
if index == -1 {
|
|
log.Println("wechat find db end")
|
|
return messageData, nil
|
|
}
|
|
|
|
sqlFormat := " SELECT DISTINCT strftime('%%Y-%%m-%%d', datetime(CreateTime+28800, 'unixepoch')) FROM MSG WHERE StrTalker='%s' order by CreateTime desc;"
|
|
querySql := fmt.Sprintf(sqlFormat, userName)
|
|
|
|
rows, err := P.msgDBs[index].db.Query(querySql)
|
|
if err != nil {
|
|
log.Printf("%s failed %v\n", querySql, err)
|
|
return messageData, nil
|
|
}
|
|
defer rows.Close()
|
|
|
|
var date string
|
|
for rows.Next() {
|
|
err = rows.Scan(&date)
|
|
if err != nil {
|
|
log.Println("rows.Scan failed", err)
|
|
return messageData, err
|
|
}
|
|
|
|
messageData.Date = append(messageData.Date, date)
|
|
messageData.Total += 1
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
log.Println("rows.Scan failed", err)
|
|
return messageData, err
|
|
}
|
|
|
|
_time = P.wechatGetLastMessageCreateTime(userName, index)
|
|
if -1 == _time {
|
|
log.Println("wechatGetLastMessageCreateTime failed")
|
|
return messageData, errors.New("wechatGetLastMessageCreateTime failed")
|
|
}
|
|
|
|
_time -= 1
|
|
}
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetChatRoomUserList(chatroom string) (*WeChatUserList, error) {
|
|
userList := &WeChatUserList{}
|
|
userList.Users = make([]WeChatUserInfo, 0)
|
|
userList.Total = 0
|
|
|
|
sqlFormat := "select UserNameList from ChatRoom where ChatRoomName='%s';"
|
|
querySql := fmt.Sprintf(sqlFormat, chatroom)
|
|
|
|
var userNameListStr string
|
|
err := P.microMsg.QueryRow(querySql).Scan(&userNameListStr)
|
|
if err != nil {
|
|
log.Println("Scan: ", err)
|
|
return nil, err
|
|
}
|
|
|
|
userNameArray := strings.Split(userNameListStr, "^G")
|
|
log.Println("userNameArray:", userNameArray)
|
|
|
|
for _, userName := range userNameArray {
|
|
pinfo, err := P.WechatGetUserInfoByNameOnCache(userName)
|
|
if err == nil {
|
|
userList.Users = append(userList.Users, *pinfo)
|
|
userList.Total += 1
|
|
}
|
|
}
|
|
|
|
return userList, nil
|
|
}
|
|
|
|
func (info WeChatUserInfo) String() string {
|
|
return fmt.Sprintf("NickName:[%s] Alias:[%s], NickName:[%s], ReMark:[%s], SmallHeadImgUrl:[%s], BigHeadImgUrl[%s]",
|
|
info.NickName, info.Alias, info.NickName, info.ReMark, info.SmallHeadImgUrl, info.BigHeadImgUrl)
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageExtraHandle(msg *WeChatMessage) {
|
|
var extra MessageBytesExtra
|
|
err := proto.Unmarshal(msg.bytesExtra, &extra)
|
|
if err != nil {
|
|
log.Println("proto.Unmarshal failed", err)
|
|
return
|
|
}
|
|
|
|
for _, ext := range extra.Message2 {
|
|
switch ext.Field1 {
|
|
case 1:
|
|
if msg.IsChatRoom {
|
|
msg.UserInfo.UserName = ext.Field2
|
|
}
|
|
case 3:
|
|
if len(ext.Field2) > 0 {
|
|
if msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video || msg.Type == Wechat_Message_Type_Misc {
|
|
msg.ThumbPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
|
|
}
|
|
|
|
if msg.Type == Wechat_Message_Type_Misc && (msg.SubType == Wechat_Misc_Message_Music || msg.SubType == Wechat_Misc_Message_TingListen) {
|
|
msg.MusicInfo.ThumbPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
|
|
} else if msg.Type == Wechat_Message_Type_Location {
|
|
msg.LocationInfo.ThumbPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
|
|
}
|
|
}
|
|
case 4:
|
|
if len(ext.Field2) > 0 {
|
|
if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_File {
|
|
msg.FileInfo.FilePath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
|
|
msg.FileInfo.FileName = filepath.Base(ext.Field2)
|
|
} else if msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video || msg.Type == Wechat_Message_Type_Misc {
|
|
msg.ImagePath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
|
|
msg.VideoPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if msg.Type == Wechat_Message_Type_Voice {
|
|
msg.VoicePath = fmt.Sprintf("%s\\FileStorage\\Voice\\%s.mp3", P.prefixResPath, msg.MsgSvrId)
|
|
}
|
|
}
|
|
|
|
type EmojiMsg struct {
|
|
XMLName xml.Name `xml:"msg"`
|
|
Emoji Emoji `xml:"emoji"`
|
|
}
|
|
|
|
type Emoji struct {
|
|
XMLName xml.Name `xml:"emoji"`
|
|
CdnURL string `xml:"cdnurl,attr"`
|
|
Thumburl string `xml:"thumburl,attr"`
|
|
Width string `xml:"width,attr"`
|
|
Height string `xml:"height,attr"`
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageEmojiHandle(msg *WeChatMessage) {
|
|
if msg.Type != Wechat_Message_Type_Emoji {
|
|
return
|
|
}
|
|
|
|
emojiMsg := EmojiMsg{}
|
|
err := xml.Unmarshal([]byte(msg.Content), &emojiMsg)
|
|
if err != nil {
|
|
log.Println("xml.Unmarshal failed: ", err, msg.Content)
|
|
return
|
|
}
|
|
|
|
msg.EmojiPath = emojiMsg.Emoji.CdnURL
|
|
}
|
|
|
|
type xmlDocument struct {
|
|
*etree.Document
|
|
}
|
|
|
|
func NewxmlDocument(e *etree.Document) *xmlDocument {
|
|
return &xmlDocument{e}
|
|
}
|
|
|
|
func (e *xmlDocument) FindElementValue(path string) string {
|
|
item := e.FindElement(path)
|
|
if item != nil {
|
|
return item.Text()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageCompressContentHandle(msg *WeChatMessage) {
|
|
if len(msg.compressContent) == 0 {
|
|
return
|
|
}
|
|
|
|
unCompressContent := make([]byte, len(msg.compressContent)*10)
|
|
ulen, err := lz4.UncompressBlock(msg.compressContent, unCompressContent)
|
|
if err != nil {
|
|
log.Println("UncompressBlock failed:", err, msg.MsgSvrId)
|
|
return
|
|
}
|
|
|
|
compMsg := etree.NewDocument()
|
|
if err := compMsg.ReadFromBytes(unCompressContent[:ulen-1]); err != nil {
|
|
// os.WriteFile("D:\\tmp\\"+string(msg.LocalId)+".xml", unCompressContent[:ulen], 0600)
|
|
log.Println("ReadFromBytes failed:", err)
|
|
return
|
|
}
|
|
root := NewxmlDocument(compMsg)
|
|
if msg.Type == Wechat_Message_Type_Misc && isLinkSubType(msg.SubType) {
|
|
msg.LinkInfo.Title = root.FindElementValue("/msg/appmsg/title")
|
|
msg.LinkInfo.Description = root.FindElementValue("/msg/appmsg/des")
|
|
msg.LinkInfo.Url = root.FindElementValue("/msg/appmsg/url")
|
|
msg.LinkInfo.DisPlayName = root.FindElementValue("/msg/appmsg/sourcedisplayname")
|
|
appName := root.FindElementValue("/msg/appinfo/appname")
|
|
if len(msg.LinkInfo.DisPlayName) == 0 && len(appName) > 0 {
|
|
msg.LinkInfo.DisPlayName = appName
|
|
}
|
|
thumburl := root.FindElementValue("/msg/appmsg/thumburl")
|
|
if len(msg.ThumbPath) == 0 && len(thumburl) > 0 && strings.HasPrefix(thumburl, "http") {
|
|
msg.ThumbPath = thumburl
|
|
}
|
|
} else if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_Refer {
|
|
msg.Content = root.FindElementValue("/msg/appmsg/title")
|
|
msg.ReferInfo.Type, _ = strconv.Atoi(root.FindElementValue("/msg/appmsg/refermsg/type"))
|
|
msg.ReferInfo.Svrid, _ = strconv.ParseInt(root.FindElementValue("/msg/appmsg/refermsg/svrid"), 10, 64)
|
|
msg.ReferInfo.Displayname = root.FindElementValue("/msg/appmsg/refermsg/displayname")
|
|
msg.ReferInfo.Content = root.FindElementValue("/msg/appmsg/refermsg/content")
|
|
|
|
if msg.ReferInfo.Type == Wechat_Message_Type_Misc {
|
|
contentXML := etree.NewDocument()
|
|
if err := contentXML.ReadFromString(msg.ReferInfo.Content); err != nil {
|
|
log.Println("ReadFromString failed:", err)
|
|
return
|
|
}
|
|
|
|
root := NewxmlDocument(contentXML)
|
|
msg.ReferInfo.Content = root.FindElementValue("/msg/appmsg/title")
|
|
msg.ReferInfo.SubType, _ = strconv.Atoi(root.FindElementValue("/msg/appmsg/type"))
|
|
}
|
|
} else if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_Transfer {
|
|
msg.PayInfo.Type, _ = strconv.Atoi(root.FindElementValue("/msg/appmsg/wcpayinfo/paysubtype"))
|
|
msg.PayInfo.Feedesc = root.FindElementValue("/msg/appmsg/wcpayinfo/feedesc")
|
|
msg.PayInfo.BeginTime = root.FindElementValue("/msg/appmsg/wcpayinfo/begintransfertime")
|
|
msg.PayInfo.Memo = root.FindElementValue("/msg/appmsg/wcpayinfo/pay_memo")
|
|
} else if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_TEXT {
|
|
msg.Content = root.FindElementValue("/msg/appmsg/title")
|
|
} else if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_Channels {
|
|
msg.ChannelsInfo.NickName = root.FindElementValue("/msg/appmsg/finderFeed/nickname")
|
|
msg.ChannelsInfo.ThumbPath = root.FindElementValue("/msg/appmsg/finderFeed/mediaList/media/thumbUrl")
|
|
msg.ChannelsInfo.Description = root.FindElementValue("/msg/appmsg/finderFeed/desc")
|
|
msg.ChannelsInfo.ThumbPath = P.urlconvertCacheName(msg.ChannelsInfo.ThumbPath, msg.CreateTime)
|
|
} else if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_Live {
|
|
msg.ChannelsInfo.NickName = root.FindElementValue("/msg/appmsg/finderLive/nickname")
|
|
msg.ChannelsInfo.ThumbPath = root.FindElementValue("/msg/appmsg/finderLive/media/coverUrl")
|
|
msg.ChannelsInfo.Description = root.FindElementValue("/msg/appmsg/finderLive/desc")
|
|
msg.ChannelsInfo.ThumbPath = P.urlconvertCacheName(msg.ChannelsInfo.ThumbPath, msg.CreateTime)
|
|
} else if msg.Type == Wechat_Message_Type_Misc && (msg.SubType == Wechat_Misc_Message_Music || msg.SubType == Wechat_Misc_Message_TingListen) {
|
|
msg.MusicInfo.Title = root.FindElementValue("/msg/appmsg/title")
|
|
msg.MusicInfo.Description = root.FindElementValue("/msg/appmsg/des")
|
|
msg.MusicInfo.DataUrl = root.FindElementValue("/msg/appmsg/dataurl")
|
|
msg.MusicInfo.DisPlayName = root.FindElementValue("/msg/appinfo/appname")
|
|
}
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageVoipHandle(msg *WeChatMessage) {
|
|
if msg.Type != Wechat_Message_Type_Voip {
|
|
return
|
|
}
|
|
|
|
xmlMsg := etree.NewDocument()
|
|
if err := xmlMsg.ReadFromBytes([]byte(msg.Content)); err != nil {
|
|
// os.WriteFile("D:\\tmp\\"+string(msg.LocalId)+".xml", unCompressContent[:ulen], 0600)
|
|
log.Println("ReadFromBytes failed:", err)
|
|
return
|
|
}
|
|
root := NewxmlDocument(xmlMsg)
|
|
msg.VoipInfo.Type, _ = strconv.Atoi(root.FindElementValue("/voipmsg/VoIPBubbleMsg/room_type"))
|
|
msg.VoipInfo.Msg = root.FindElementValue("/voipmsg/VoIPBubbleMsg/msg")
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageVisitHandke(msg *WeChatMessage) {
|
|
if msg.Type != Wechat_Message_Type_Visit_Card {
|
|
return
|
|
}
|
|
|
|
attr := utils.HtmlMsgGetAttr(msg.Content, "msg")
|
|
userName, exists := attr["username"]
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
userInfo, err := P.WechatGetUserInfoByNameOnCache(userName)
|
|
if err == nil {
|
|
msg.VisitInfo = *userInfo
|
|
} else {
|
|
msg.VisitInfo.UserName = userName
|
|
msg.VisitInfo.Alias = attr["alias"]
|
|
msg.VisitInfo.NickName = attr["nickname"]
|
|
msg.VisitInfo.SmallHeadImgUrl = attr["smallheadimgurl"]
|
|
msg.VisitInfo.BigHeadImgUrl = attr["bigheadimgurl"]
|
|
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.resPath, userName)
|
|
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.prefixResPath, userName)
|
|
if _, err = os.Stat(localHeadImgPath); err == nil {
|
|
msg.VisitInfo.LocalHeadImgUrl = relativePath
|
|
}
|
|
}
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageLocationHandke(msg *WeChatMessage) {
|
|
if msg.Type != Wechat_Message_Type_Location {
|
|
return
|
|
}
|
|
|
|
attr := utils.HtmlMsgGetAttr(msg.Content, "location")
|
|
msg.LocationInfo.Label = attr["label"]
|
|
msg.LocationInfo.PoiName = attr["poiname"]
|
|
msg.LocationInfo.X = attr["x"]
|
|
msg.LocationInfo.Y = attr["y"]
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatMessageGetUserInfo(msg *WeChatMessage) {
|
|
who := msg.Talker
|
|
if msg.IsSender == 1 {
|
|
who = P.SelfInfo.UserName
|
|
} else if msg.IsChatRoom {
|
|
who = msg.UserInfo.UserName
|
|
}
|
|
|
|
pinfo, err := P.WechatGetUserInfoByNameOnCache(who)
|
|
if err != nil {
|
|
// log.Println("WechatGetUserInfoByNameOnCache:", err)
|
|
return
|
|
}
|
|
|
|
msg.UserInfo = *pinfo
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatFindDBIndex(userName string, time int64, direction Message_Search_Direction) int {
|
|
if direction == Message_Search_Forward {
|
|
index := 0
|
|
for {
|
|
if index >= len(P.msgDBs) {
|
|
return -1
|
|
}
|
|
msgDB := P.msgDBs[index]
|
|
|
|
if msgDB.startTime > time {
|
|
index += 1
|
|
continue
|
|
}
|
|
|
|
rowId := 0
|
|
querySql := fmt.Sprintf("select rowid from Name2ID where UsrName='%s';", userName)
|
|
err := msgDB.db.QueryRow(querySql).Scan(&rowId)
|
|
if err != nil {
|
|
log.Printf("Scan: %v\n", err)
|
|
index += 1
|
|
continue
|
|
}
|
|
|
|
querySql = fmt.Sprintf(" select rowid from MSG where StrTalker='%s' AND CreateTime<=%d limit 1;", userName, time)
|
|
log.Printf("in %s, %s\n", msgDB.path, querySql)
|
|
err = msgDB.db.QueryRow(querySql).Scan(&rowId)
|
|
if err != nil {
|
|
log.Printf("Scan: %v\n", err)
|
|
index += 1
|
|
continue
|
|
}
|
|
|
|
log.Printf("Select in %d %s\n", index, msgDB.path)
|
|
return index
|
|
}
|
|
} else {
|
|
index := len(P.msgDBs) - 1
|
|
for {
|
|
if index < 0 {
|
|
return -1
|
|
}
|
|
msgDB := P.msgDBs[index]
|
|
|
|
if msgDB.endTime < time {
|
|
index -= 1
|
|
continue
|
|
}
|
|
|
|
rowId := 0
|
|
querySql := fmt.Sprintf("select rowid from Name2ID where UsrName='%s';", userName)
|
|
err := msgDB.db.QueryRow(querySql).Scan(&rowId)
|
|
if err != nil {
|
|
log.Printf("Scan: %v\n", err)
|
|
index -= 1
|
|
continue
|
|
}
|
|
|
|
querySql = fmt.Sprintf(" select rowid from MSG where StrTalker='%s' AND CreateTime>%d limit 1;", userName, time)
|
|
log.Printf("in %s, %s\n", msgDB.path, querySql)
|
|
err = msgDB.db.QueryRow(querySql).Scan(&rowId)
|
|
if err != nil {
|
|
log.Printf("Scan: %v\n", err)
|
|
index -= 1
|
|
continue
|
|
}
|
|
|
|
log.Printf("Select in %d %s\n", index, msgDB.path)
|
|
return index
|
|
}
|
|
}
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatGetLastMessageCreateTime(userName string, index int) int64 {
|
|
if index >= len(P.msgDBs) {
|
|
return -1
|
|
}
|
|
sqlFormat := "SELECT CreateTime FROM MSG WHERE StrTalker='%s' order by CreateTime asc limit 1;"
|
|
querySql := fmt.Sprintf(sqlFormat, userName)
|
|
var lastTime int64
|
|
err := P.msgDBs[index].db.QueryRow(querySql).Scan(&lastTime)
|
|
if err != nil {
|
|
log.Println("select DB lastTime failed:", index, ":", err)
|
|
return -1
|
|
}
|
|
|
|
return lastTime
|
|
}
|
|
|
|
func weChatMessageContains(msg *WeChatMessage, chars string) bool {
|
|
|
|
switch msg.Type {
|
|
case Wechat_Message_Type_Text:
|
|
return strings.Contains(msg.Content, chars)
|
|
case Wechat_Message_Type_Location:
|
|
return strings.Contains(msg.LocationInfo.Label, chars) || strings.Contains(msg.LocationInfo.PoiName, chars)
|
|
case Wechat_Message_Type_Misc:
|
|
switch msg.SubType {
|
|
case Wechat_Misc_Message_CardLink, Wechat_Misc_Message_ThirdVideo, Wechat_Misc_Message_Applet, Wechat_Misc_Message_Applet2:
|
|
return strings.Contains(msg.LinkInfo.Title, chars) || strings.Contains(msg.LinkInfo.Description, chars)
|
|
case Wechat_Misc_Message_Refer:
|
|
return strings.Contains(msg.Content, chars)
|
|
case Wechat_Misc_Message_File:
|
|
return strings.Contains(msg.FileInfo.FileName, chars)
|
|
default:
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func weChatMessageTypeFilter(msg *WeChatMessage, msgType string) bool {
|
|
switch msgType {
|
|
case "":
|
|
return true
|
|
case "文件":
|
|
return msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_File
|
|
case "图片与视频":
|
|
return msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video
|
|
case "链接":
|
|
return msg.Type == Wechat_Message_Type_Misc && (msg.SubType == Wechat_Misc_Message_CardLink || msg.SubType == Wechat_Misc_Message_ThirdVideo)
|
|
case "语音":
|
|
return msg.Type == Wechat_Message_Type_Voice
|
|
case "通话":
|
|
return msg.Type == Wechat_Message_Type_Voip
|
|
default:
|
|
if strings.HasPrefix(msgType, "群成员") {
|
|
userName := msgType[len("群成员"):]
|
|
return msg.UserInfo.UserName == userName
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
func wechatOpenMsgDB(path string) (*wechatMsgDB, error) {
|
|
msgDB := wechatMsgDB{}
|
|
|
|
db, err := sql.Open("sqlite3", path)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", path, err)
|
|
return nil, err
|
|
}
|
|
msgDB.db = db
|
|
msgDB.path = path
|
|
querySql := "select CreateTime from MSG order by CreateTime asc limit 1;"
|
|
err = msgDB.db.QueryRow(querySql).Scan(&msgDB.startTime)
|
|
if err != nil {
|
|
log.Println("select DB startTime failed:", path, ":", err)
|
|
msgDB.db.Close()
|
|
return nil, err
|
|
}
|
|
|
|
querySql = "select CreateTime from MSG order by CreateTime desc limit 1;"
|
|
err = msgDB.db.QueryRow(querySql).Scan(&msgDB.endTime)
|
|
if err != nil {
|
|
log.Println("select DB endTime failed:", path, ":", err)
|
|
msgDB.db.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return &msgDB, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WechatGetUserInfoByNameOnCache(name string) (*WeChatUserInfo, error) {
|
|
|
|
// log.Printf("who: %s", who)
|
|
|
|
P.userInfoMtx.Lock()
|
|
defer P.userInfoMtx.Unlock()
|
|
|
|
info, ok := P.userInfoMap[name]
|
|
if ok {
|
|
return &info, nil
|
|
}
|
|
|
|
var pinfo *WeChatUserInfo
|
|
var err error
|
|
if strings.HasSuffix(name, "@openim") {
|
|
pinfo, err = P.WechatGetOpenIMMUserInfoByName(name)
|
|
} else {
|
|
pinfo, err = P.WechatGetUserInfoByName(name)
|
|
}
|
|
if err != nil {
|
|
// log.Printf("WechatGetUserInfoByName %s failed: %v\n", name, err)
|
|
return nil, err
|
|
}
|
|
|
|
P.userInfoMap[name] = *pinfo
|
|
|
|
return pinfo, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) wechatGetAllContact() (*WeChatContactList, error) {
|
|
List := &WeChatContactList{}
|
|
List.Users = make([]WeChatContact, 0)
|
|
|
|
querySql := fmt.Sprintf("select ifnull(UserName,'') as UserName,Reserved1,Reserved2,ifnull(PYInitial,'') as PYInitial,ifnull(QuanPin,'') as QuanPin,ifnull(RemarkPYInitial,'') as RemarkPYInitial,ifnull(RemarkQuanPin,'') as RemarkQuanPin from Contact desc;")
|
|
dbRows, err := P.microMsg.Query(querySql)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return List, err
|
|
}
|
|
defer dbRows.Close()
|
|
|
|
var UserName string
|
|
var Reserved1, Reserved2 int
|
|
for dbRows.Next() {
|
|
var Contact WeChatContact
|
|
err = dbRows.Scan(&UserName, &Reserved1, &Reserved2, &Contact.PYInitial, &Contact.QuanPin, &Contact.RemarkPYInitial, &Contact.RemarkQuanPin)
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
if Reserved1 != 1 || Reserved2 != 1 {
|
|
// log.Printf("%s is not your contact", UserName)
|
|
continue
|
|
}
|
|
info, err := P.WechatGetUserInfoByNameOnCache(UserName)
|
|
if err != nil {
|
|
log.Printf("WechatGetUserInfoByName %s failed\n", UserName)
|
|
continue
|
|
}
|
|
|
|
if info.NickName == "" && info.ReMark == "" {
|
|
continue
|
|
}
|
|
Contact.WeChatUserInfo = *info
|
|
List.Users = append(List.Users, Contact)
|
|
List.Total += 1
|
|
}
|
|
|
|
return List, nil
|
|
}
|
|
|
|
func WechatGetAccountInfo(resPath, prefixRes, accountName string) (*WeChatAccountInfo, error) {
|
|
MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB
|
|
if _, err := os.Stat(MicroMsgDBPath); err != nil {
|
|
log.Println("MicroMsgDBPath:", MicroMsgDBPath, err)
|
|
return nil, err
|
|
}
|
|
|
|
microMsg, err := sql.Open("sqlite3", MicroMsgDBPath)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", MicroMsgDBPath, err)
|
|
return nil, err
|
|
}
|
|
defer microMsg.Close()
|
|
|
|
info := &WeChatAccountInfo{}
|
|
|
|
var UserName, Alias, ReMark, NickName string
|
|
querySql := fmt.Sprintf("select ifnull(UserName,'') as UserName, ifnull(Alias,'') as Alias, ifnull(ReMark,'') as ReMark, ifnull(NickName,'') as NickName from Contact where UserName='%s';", accountName)
|
|
// log.Println(querySql)
|
|
err = microMsg.QueryRow(querySql).Scan(&UserName, &Alias, &ReMark, &NickName)
|
|
if err != nil {
|
|
log.Println("not found User:", err)
|
|
return nil, err
|
|
}
|
|
|
|
log.Printf("UserName %s, Alias %s, ReMark %s, NickName %s\n", UserName, Alias, ReMark, NickName)
|
|
|
|
var smallHeadImgUrl, bigHeadImgUrl string
|
|
querySql = fmt.Sprintf("select ifnull(smallHeadImgUrl,'') as smallHeadImgUrl, ifnull(bigHeadImgUrl,'') as bigHeadImgUrl from ContactHeadImgUrl where usrName='%s';", UserName)
|
|
// log.Println(querySql)
|
|
err = microMsg.QueryRow(querySql).Scan(&smallHeadImgUrl, &bigHeadImgUrl)
|
|
if err != nil {
|
|
log.Println("not find headimg", err)
|
|
}
|
|
|
|
info.AccountName = UserName
|
|
info.AliasName = Alias
|
|
info.ReMarkName = ReMark
|
|
info.NickName = NickName
|
|
info.SmallHeadImgUrl = smallHeadImgUrl
|
|
info.BigHeadImgUrl = bigHeadImgUrl
|
|
|
|
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", resPath, accountName)
|
|
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", prefixRes, accountName)
|
|
if _, err = os.Stat(localHeadImgPath); err == nil {
|
|
info.LocalHeadImgUrl = relativePath
|
|
}
|
|
// log.Println(info)
|
|
return info, nil
|
|
}
|
|
|
|
func systemMsgParse(msgType int, content string) string {
|
|
if msgType != Wechat_Message_Type_System {
|
|
return content
|
|
}
|
|
|
|
return utils.Html2Text(content)
|
|
}
|
|
|
|
func (P *WechatDataProvider) urlconvertCacheName(url string, timestamp int64) string {
|
|
t := time.Unix(timestamp, 0)
|
|
yearMonth := t.Format("2006-01")
|
|
md5String := utils.Hash256Sum([]byte(url))
|
|
realPath := fmt.Sprintf("%s\\FileStorage\\Cache\\%s\\%s.jpg", P.resPath, yearMonth, md5String)
|
|
path := fmt.Sprintf("%s\\FileStorage\\Cache\\%s\\%s.jpg", P.prefixResPath, yearMonth, md5String)
|
|
|
|
if _, err := os.Stat(realPath); err == nil {
|
|
return path
|
|
}
|
|
|
|
return url
|
|
}
|
|
|
|
func isLinkSubType(subType int) bool {
|
|
targetSubTypes := map[int]bool{
|
|
Wechat_Misc_Message_CardLink: true,
|
|
Wechat_Misc_Message_ThirdVideo: true,
|
|
Wechat_Misc_Message_ShareEmoji: true,
|
|
Wechat_Misc_Message_Applet: true,
|
|
Wechat_Misc_Message_Applet2: true,
|
|
Wechat_Misc_Message_Game: true,
|
|
}
|
|
return targetSubTypes[subType]
|
|
}
|
|
|
|
func openUserDataDB(path string) *sql.DB {
|
|
if _, err := os.Stat(path); err == nil {
|
|
sql, err := sql.Open("sqlite3", path)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", path, err)
|
|
return nil
|
|
}
|
|
|
|
return sql
|
|
}
|
|
|
|
db, err := sql.Open("sqlite3", path)
|
|
if err != nil {
|
|
log.Printf("open db %s error: %v", path, err)
|
|
return nil
|
|
}
|
|
|
|
createLastTimeTable := `
|
|
CREATE TABLE IF NOT EXISTS lastTime (
|
|
localId INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
userName TEXT,
|
|
timestamp INT,
|
|
messageId TEXT,
|
|
Reserved0 INT DEFAULT 0,
|
|
Reserved1 INT DEFAULT 0,
|
|
Reserved2 TEXT,
|
|
Reserved3 TEXT
|
|
);`
|
|
|
|
_, err = db.Exec(createLastTimeTable)
|
|
if err != nil {
|
|
log.Printf("create lastTime table failed: %v", err)
|
|
db.Close()
|
|
return nil
|
|
}
|
|
|
|
createBookMarkTable := `
|
|
CREATE TABLE IF NOT EXISTS bookMark (
|
|
localId INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
userName TEXT,
|
|
markId TEXT,
|
|
tag TEXT,
|
|
info TEXT,
|
|
Reserved0 INT DEFAULT 0,
|
|
Reserved1 INT DEFAULT 0,
|
|
Reserved2 TEXT,
|
|
Reserved3 TEXT
|
|
);`
|
|
|
|
_, err = db.Exec(createBookMarkTable)
|
|
if err != nil {
|
|
log.Printf("create bookMark table failed: %v", err)
|
|
db.Close()
|
|
return nil
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetSessionLastTime(userName string) *WeChatLastTime {
|
|
lastTime := &WeChatLastTime{}
|
|
if P.userData == nil {
|
|
log.Println("userData DB is nill")
|
|
return lastTime
|
|
}
|
|
|
|
var timestamp int64
|
|
var messageId string
|
|
querySql := fmt.Sprintf("select timestamp, messageId from lastTime where userName='%s';", userName)
|
|
err := P.userData.QueryRow(querySql).Scan(×tamp, &messageId)
|
|
if err != nil {
|
|
log.Println("select DB timestamp failed:", err)
|
|
return lastTime
|
|
}
|
|
|
|
lastTime.UserName = userName
|
|
lastTime.MessageId = messageId
|
|
lastTime.Timestamp = timestamp
|
|
return lastTime
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatSetSessionLastTime(lastTime *WeChatLastTime) error {
|
|
var count int
|
|
querySql := fmt.Sprintf("select COUNT(*) from lastTime where userName='%s';", lastTime.UserName)
|
|
err := P.userData.QueryRow(querySql).Scan(&count)
|
|
if err != nil {
|
|
log.Println("select DB timestamp count failed:", err)
|
|
return err
|
|
}
|
|
|
|
if count > 0 {
|
|
_, err := P.userData.Exec("UPDATE lastTime SET timestamp = ?, messageId = ? WHERE userName = ?", lastTime.Timestamp, lastTime.MessageId, lastTime.UserName)
|
|
if err != nil {
|
|
return fmt.Errorf("update timestamp failed: %v", err)
|
|
}
|
|
} else {
|
|
_, err := P.userData.Exec("INSERT INTO lastTime (userName, timestamp, messageId) VALUES (?, ?, ?)", lastTime.UserName, lastTime.Timestamp, lastTime.MessageId)
|
|
if err != nil {
|
|
return fmt.Errorf("insert failed: %v", err)
|
|
}
|
|
}
|
|
|
|
log.Printf("WeChatSetSessionLastTime %s %d %s done!\n", lastTime.UserName, lastTime.Timestamp, lastTime.MessageId)
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatSetSessionBookMask(userName, tag, info string) error {
|
|
markId := utils.Hash256Sum([]byte(info))
|
|
querySql := fmt.Sprintf("select COUNT(*) from bookMark where markId='%s';", markId)
|
|
var count int
|
|
|
|
err := P.userData.QueryRow(querySql).Scan(&count)
|
|
if err != nil {
|
|
log.Println("select DB markId count failed:", err)
|
|
return err
|
|
}
|
|
|
|
if count > 0 {
|
|
log.Printf("exist userName: %s, tag: %s, info: %s, markId: %s\n", userName, tag, info, markId)
|
|
return nil
|
|
}
|
|
|
|
_, err = P.userData.Exec("INSERT INTO bookMark (userName, markId, tag, info) VALUES (?, ?, ?, ?)", userName, markId, tag, info)
|
|
if err != nil {
|
|
return fmt.Errorf("insert failed: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatDelSessionBookMask(markId string) error {
|
|
querySql := fmt.Sprintf("select COUNT(*) from bookMark where markId='%s';", markId)
|
|
var count int
|
|
|
|
err := P.userData.QueryRow(querySql).Scan(&count)
|
|
if err != nil {
|
|
log.Println("select DB markId count failed:", err)
|
|
return err
|
|
}
|
|
|
|
if count > 0 {
|
|
_, err = P.userData.Exec("DELETE from bookMark where markId=?", markId)
|
|
if err != nil {
|
|
return fmt.Errorf("delete failed: %v", err)
|
|
}
|
|
} else {
|
|
log.Printf("markId %s not exits\n", markId)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatGetSessionBookMaskList(userName string) (*WeChatBookMarkList, error) {
|
|
markList := &WeChatBookMarkList{}
|
|
markList.Marks = make([]WeChatBookMark, 0)
|
|
markList.Total = 0
|
|
|
|
querySql := fmt.Sprintf("select markId, tag, info from bookMark where userName='%s';", userName)
|
|
log.Println("querySql:", querySql)
|
|
|
|
rows, err := P.userData.Query(querySql)
|
|
if err != nil {
|
|
log.Printf("%s failed %v\n", querySql, err)
|
|
return markList, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var markId, tag, info string
|
|
for rows.Next() {
|
|
err = rows.Scan(&markId, &tag, &info)
|
|
if err != nil {
|
|
log.Println("rows.Scan failed", err)
|
|
return markList, err
|
|
}
|
|
|
|
markList.Marks = append(markList.Marks, WeChatBookMark{MarkId: markId, Tag: tag, Info: info})
|
|
markList.Total += 1
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
log.Println("rows.Scan failed", err)
|
|
return markList, err
|
|
}
|
|
|
|
return markList, nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatExportDataByUserName(userName, exportPath string) error {
|
|
|
|
err := P.WeChatExportDBByUserName(userName, exportPath)
|
|
if err != nil {
|
|
log.Println("WeChatExportDBByUserName:", err)
|
|
return err
|
|
}
|
|
|
|
err = P.WeChatExportFileByUserName(userName, exportPath)
|
|
if err != nil {
|
|
log.Println("WeChatExportFileByUserName:", err)
|
|
return err
|
|
}
|
|
log.Println("WeChatExportDataByUserName done")
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatExportDBByUserName(userName, exportPath string) error {
|
|
msgPath := fmt.Sprintf("%s\\User\\%s\\Msg", exportPath, P.SelfInfo.UserName)
|
|
multiPath := fmt.Sprintf("%s\\Multi", msgPath)
|
|
if _, err := os.Stat(multiPath); err != nil {
|
|
if err := os.MkdirAll(multiPath, 0644); err != nil {
|
|
log.Printf("MkdirAll %s failed: %v\n", multiPath, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := P.weChatExportMicroMsgDBByUserName(userName, msgPath)
|
|
if err != nil {
|
|
log.Println("weChatExportMicroMsgDBByUserName failed:", err)
|
|
return err
|
|
}
|
|
|
|
err = P.weChatExportMsgDBByUserName(userName, multiPath)
|
|
if err != nil {
|
|
log.Println("weChatExportMsgDBByUserName failed:", err)
|
|
return err
|
|
}
|
|
|
|
err = P.weChatExportUserDataDBByUserName(userName, msgPath)
|
|
if err != nil {
|
|
log.Println("weChatExportUserDataDBByUserName failed:", err)
|
|
return err
|
|
}
|
|
|
|
err = P.weChatExportOpenIMContactDBByUserName(userName, msgPath)
|
|
if err != nil {
|
|
log.Println("weChatExportOpenIMContactDBByUserName failed:", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) weChatExportMicroMsgDBByUserName(userName, exportPath string) error {
|
|
exMicroMsgDBPath := exportPath + "\\" + MicroMsgDB
|
|
if _, err := os.Stat(exMicroMsgDBPath); err == nil {
|
|
log.Println("exist", exMicroMsgDBPath)
|
|
return errors.New("exist " + exMicroMsgDBPath)
|
|
}
|
|
|
|
exMicroMsgDB, err := sql.Open("sqlite3", exMicroMsgDBPath)
|
|
if err != nil {
|
|
log.Println("db open", err)
|
|
return err
|
|
}
|
|
defer exMicroMsgDB.Close()
|
|
|
|
tables := []string{"Contact", "ContactHeadImgUrl", "Session"}
|
|
IsGroup := false
|
|
if strings.HasSuffix(userName, "@chatroom") {
|
|
IsGroup = true
|
|
tables = append(tables, "ChatRoom", "ChatRoomInfo")
|
|
}
|
|
|
|
err = wechatCopyDBTables(exMicroMsgDB, P.microMsg, tables)
|
|
if err != nil {
|
|
log.Println("wechatCopyDBTables:", err)
|
|
return err
|
|
}
|
|
|
|
copyContactData := func(users []string) error {
|
|
columns := "UserName, Alias, EncryptUserName, DelFlag, Type, VerifyFlag, Reserved1, Reserved2, Reserved3, Reserved4, Remark, NickName, LabelIDList, DomainList, ChatRoomType, PYInitial, QuanPin, RemarkPYInitial, RemarkQuanPin, BigHeadImgUrl, SmallHeadImgUrl, HeadImgMd5, ChatRoomNotify, Reserved5, Reserved6, Reserved7, ExtraBuf, Reserved8, Reserved9, Reserved10, Reserved11"
|
|
err = wechatCopyTableData(exMicroMsgDB, P.microMsg, "Contact", columns, "UserName", users)
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData Contact:", err)
|
|
return err
|
|
}
|
|
|
|
columns = "usrName, smallHeadImgUrl, bigHeadImgUrl, headImgMd5, reverse0, reverse1"
|
|
err = wechatCopyTableData(exMicroMsgDB, P.microMsg, "ContactHeadImgUrl", columns, "usrName", users)
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData ContactHeadImgUrl:", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
err = copyContactData([]string{userName, P.SelfInfo.UserName})
|
|
if err != nil {
|
|
log.Println("copyContactData:", err)
|
|
return err
|
|
}
|
|
|
|
columns := "strUsrName, nOrder, nUnReadCount, parentRef, Reserved0, Reserved1, strNickName, nStatus, nIsSend, strContent, nMsgType, nMsgLocalID, nMsgStatus, nTime, editContent, othersAtMe, Reserved2, Reserved3, Reserved4, Reserved5, bytesXml"
|
|
err = wechatCopyTableData(exMicroMsgDB, P.microMsg, "Session", columns, "strUsrName", []string{userName})
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData Session:", err)
|
|
return err
|
|
}
|
|
|
|
if !IsGroup {
|
|
return nil
|
|
}
|
|
|
|
uList, err := P.WeChatGetChatRoomUserList(userName)
|
|
if err != nil {
|
|
log.Println("WeChatGetChatRoomUserList failed:", err)
|
|
return err
|
|
}
|
|
|
|
userNames := make([]string, 0, 100)
|
|
for i := range uList.Users {
|
|
userNames = append(userNames, uList.Users[i].UserName)
|
|
if len(userNames) >= 100 || i == len(uList.Users)-1 {
|
|
err = copyContactData(userNames)
|
|
if err != nil {
|
|
log.Println("copyContactData:", err)
|
|
}
|
|
userNames = userNames[:0]
|
|
}
|
|
}
|
|
|
|
columns = "ChatRoomName, UserNameList, DisplayNameList, ChatRoomFlag, Owner, IsShowName, SelfDisplayName, Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, RoomData, Reserved7, Reserved8"
|
|
err = wechatCopyTableData(exMicroMsgDB, P.microMsg, "ChatRoom", columns, "ChatRoomName", []string{userName})
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData ChatRoom:", err)
|
|
return err
|
|
}
|
|
|
|
columns = "ChatRoomName, Announcement, InfoVersion, AnnouncementEditor, AnnouncementPublishTime, ChatRoomStatus, Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, Reserved7, Reserved8"
|
|
err = wechatCopyTableData(exMicroMsgDB, P.microMsg, "ChatRoomInfo", columns, "ChatRoomName", []string{userName})
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData ChatRoom:", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) weChatExportMsgDBByUserName(userName, exportPath string) error {
|
|
exMsgDBPath := exportPath + "\\" + "MSG.db"
|
|
if _, err := os.Stat(exMsgDBPath); err == nil {
|
|
log.Println("exist", exMsgDBPath)
|
|
return errors.New("exist " + exMsgDBPath)
|
|
}
|
|
|
|
exMsgDB, err := sql.Open("sqlite3", exMsgDBPath)
|
|
if err != nil {
|
|
log.Println("db open", err)
|
|
return err
|
|
}
|
|
defer exMsgDB.Close()
|
|
|
|
if len(P.msgDBs) == 0 {
|
|
return fmt.Errorf("P.msgDBs len = 0")
|
|
}
|
|
|
|
tables := []string{"MSG", "Name2ID"}
|
|
err = wechatCopyDBTables(exMsgDB, P.msgDBs[0].db, tables)
|
|
if err != nil {
|
|
log.Println("wechatCopyDBTables:", err)
|
|
return err
|
|
}
|
|
|
|
columns := "TalkerId, MsgSvrID, Type, SubType, IsSender, CreateTime, Sequence, StatusEx, FlagEx, Status, MsgServerSeq, MsgSequence, StrTalker, StrContent, DisplayContent, Reserved0, Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, CompressContent, BytesExtra, BytesTrans"
|
|
for _, msgDB := range P.msgDBs {
|
|
err = wechatCopyTableData(exMsgDB, msgDB.db, "MSG", columns, "StrTalker", []string{userName})
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData MSG:", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
columns = "UsrName"
|
|
for _, msgDB := range P.msgDBs {
|
|
err = wechatCopyTableData(exMsgDB, msgDB.db, "Name2ID", columns, "UsrName", []string{userName})
|
|
if err != nil {
|
|
continue
|
|
}
|
|
// log.Println("Name2ID:", userName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) weChatExportUserDataDBByUserName(userName, exportPath string) error {
|
|
exUserDataDBPath := exportPath + "\\" + UserDataDB
|
|
if _, err := os.Stat(exUserDataDBPath); err == nil {
|
|
log.Println("exist", exUserDataDBPath)
|
|
return errors.New("exist " + exUserDataDBPath)
|
|
}
|
|
|
|
exUserDataDB, err := sql.Open("sqlite3", exUserDataDBPath)
|
|
if err != nil {
|
|
log.Println("db open", err)
|
|
return err
|
|
}
|
|
defer exUserDataDB.Close()
|
|
|
|
tables := []string{"lastTime", "bookMark"}
|
|
err = wechatCopyDBTables(exUserDataDB, P.userData, tables)
|
|
if err != nil {
|
|
log.Println("wechatCopyDBTables:", err)
|
|
return err
|
|
}
|
|
|
|
columns := "localId,userName,timestamp,messageId,Reserved0,Reserved1,Reserved2,Reserved3"
|
|
err = wechatCopyTableData(exUserDataDB, P.userData, "lastTime", columns, "userName", []string{userName})
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData lastTime:", err)
|
|
return err
|
|
}
|
|
|
|
columns = "localId, userName, markId, tag, info, Reserved0, Reserved1, Reserved2, Reserved3"
|
|
err = wechatCopyTableData(exUserDataDB, P.userData, "bookMark", columns, "userName", []string{userName})
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData bookMark:", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) weChatExportOpenIMContactDBByUserName(userName, exportPath string) error {
|
|
hasOpenIM := false
|
|
IsGroup := false
|
|
if strings.HasSuffix(userName, "@openim") {
|
|
hasOpenIM = true
|
|
}
|
|
|
|
userNames := make([]string, 0)
|
|
if strings.HasSuffix(userName, "@chatroom") {
|
|
IsGroup = true
|
|
uList, err := P.WeChatGetChatRoomUserList(userName)
|
|
if err != nil {
|
|
log.Println("WeChatGetChatRoomUserList failed:", err)
|
|
return err
|
|
}
|
|
for i := range uList.Users {
|
|
if strings.HasSuffix(uList.Users[i].UserName, "@openim") {
|
|
userNames = append(userNames, uList.Users[i].UserName)
|
|
hasOpenIM = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasOpenIM || P.openIMContact == nil {
|
|
log.Println("not Open Im")
|
|
return nil
|
|
}
|
|
|
|
exOpenIMContactDBPath := exportPath + "\\" + OpenIMContactDB
|
|
if _, err := os.Stat(exOpenIMContactDBPath); err == nil {
|
|
log.Println("exist", exOpenIMContactDBPath)
|
|
return errors.New("exist " + exOpenIMContactDBPath)
|
|
}
|
|
|
|
exOpenIMContactDB, err := sql.Open("sqlite3", exOpenIMContactDBPath)
|
|
if err != nil {
|
|
log.Println("db open", err)
|
|
return err
|
|
}
|
|
defer exOpenIMContactDB.Close()
|
|
|
|
tables := []string{"OpenIMContact"}
|
|
err = wechatCopyDBTables(exOpenIMContactDB, P.openIMContact, tables)
|
|
if err != nil {
|
|
log.Println("wechatCopyDBTables:", err)
|
|
return err
|
|
}
|
|
|
|
copyContactData := func(users []string) error {
|
|
columns := "UserName, NickName, Type, Remark, BigHeadImgUrl, SmallHeadImgUrl, Source, NickNamePYInit, NickNameQuanPin, RemarkPYInit, RemarkQuanPin, CustomInfoDetail, CustomInfoDetailVisible, AntiSpamTicket, AppId, Sex, DescWordingId, Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, Reserved7, Reserved8, ExtraBuf"
|
|
err = wechatCopyTableData(exOpenIMContactDB, P.openIMContact, "OpenIMContact", columns, "UserName", users)
|
|
if err != nil {
|
|
log.Println("wechatCopyTableData OpenIMContact:", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if !IsGroup {
|
|
return copyContactData([]string{userName})
|
|
}
|
|
|
|
chunkSize := 100
|
|
for i := 0; i < len(userNames); i += chunkSize {
|
|
end := i + chunkSize
|
|
if end > len(userNames) {
|
|
end = len(userNames)
|
|
}
|
|
err = copyContactData(userNames[i:end])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func wechatCopyDBTables(dts, src *sql.DB, tables []string) error {
|
|
for _, tab := range tables {
|
|
querySql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s';", tab)
|
|
// log.Println("querySql:", querySql)
|
|
rows, err := src.Query(querySql)
|
|
if err != nil {
|
|
rows.Close()
|
|
log.Println("src.Query", err)
|
|
continue
|
|
}
|
|
|
|
var createStatements []string
|
|
for rows.Next() {
|
|
var sql string
|
|
if err := rows.Scan(&sql); err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
if sql != "" {
|
|
createStatements = append(createStatements, sql)
|
|
}
|
|
}
|
|
rows.Close()
|
|
// log.Println("createStatements:", createStatements)
|
|
tx, err := dts.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %v", err)
|
|
}
|
|
|
|
for _, stmt := range createStatements {
|
|
if _, err := tx.Exec(stmt); err != nil {
|
|
tx.Rollback()
|
|
return fmt.Errorf("failed to execute statement: %s, error: %v", stmt, err)
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return fmt.Errorf("failed to commit transaction: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func wechatCopyTableData(dts, src *sql.DB, tableName, columns, conditionField string, conditionValue []string) error {
|
|
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s = '%s'", columns, tableName, conditionField, conditionValue[0])
|
|
if len(conditionValue) > 1 {
|
|
query = fmt.Sprintf("SELECT %s FROM %s WHERE %s IN ('%s')", columns, tableName, conditionField, strings.Join(conditionValue, "','"))
|
|
}
|
|
// log.Println("query:", query)
|
|
rows, err := src.Query(query)
|
|
if err != nil {
|
|
return fmt.Errorf("query src failed: %v", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
tx, err := dts.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("dts.Begin failed: %v", err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
} else {
|
|
tx.Commit()
|
|
}
|
|
}()
|
|
|
|
columnList := strings.Split(columns, ",")
|
|
placeholders := strings.Repeat("?, ", len(columnList))
|
|
placeholders = placeholders[:len(placeholders)-2]
|
|
insertQuery := fmt.Sprintf("INSERT OR IGNORE INTO %s (%s) VALUES (%s)", tableName, columns, placeholders)
|
|
// log.Println("wechatCopyTableData:", insertQuery)
|
|
stmt, err := tx.Prepare(insertQuery)
|
|
if err != nil {
|
|
return fmt.Errorf("prepare insertquery: %v", err)
|
|
}
|
|
defer stmt.Close()
|
|
|
|
for rows.Next() {
|
|
values := make([]interface{}, len(columnList))
|
|
valuePtrs := make([]interface{}, len(columnList))
|
|
for i := range values {
|
|
valuePtrs[i] = &values[i]
|
|
}
|
|
|
|
if err := rows.Scan(valuePtrs...); err != nil {
|
|
return fmt.Errorf("scan rows failed: %v", err)
|
|
}
|
|
|
|
if _, err := stmt.Exec(values...); err != nil {
|
|
return fmt.Errorf("insert data failed: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (P *WechatDataProvider) WeChatExportFileByUserName(userName, exportPath string) error {
|
|
|
|
topDir := filepath.Dir(P.resPath)
|
|
topDir = filepath.Dir(topDir)
|
|
pageSize := 600
|
|
_time := time.Now().Unix()
|
|
taskChan := make(chan [2]string, 100)
|
|
var wg sync.WaitGroup
|
|
|
|
taskSend := func(topDir, path, exportPath string, taskChan chan [2]string) {
|
|
if path == "" {
|
|
return
|
|
}
|
|
srcFile := topDir + path
|
|
if _, err := os.Stat(srcFile); err != nil {
|
|
// log.Println("no exist:", srcFile)
|
|
return
|
|
}
|
|
|
|
dstFile := exportPath + path
|
|
dstDir := filepath.Dir(dstFile)
|
|
if _, err := os.Stat(dstDir); err != nil {
|
|
os.MkdirAll(dstDir, os.ModePerm)
|
|
}
|
|
|
|
task := [2]string{srcFile, dstFile}
|
|
taskChan <- task
|
|
}
|
|
|
|
for i := 0; i < 20; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for task := range taskChan {
|
|
// log.Println("copy: ", task[0], task[1])
|
|
utils.CopyFile(task[0], task[1])
|
|
}
|
|
}()
|
|
}
|
|
|
|
for {
|
|
mlist, err := P.WeChatGetMessageListByTime(userName, _time, pageSize, Message_Search_Forward)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
paths := make([]string, 0)
|
|
for _, m := range mlist.Rows {
|
|
switch m.Type {
|
|
case Wechat_Message_Type_Picture:
|
|
paths = append(paths, m.ThumbPath, m.ImagePath)
|
|
case Wechat_Message_Type_Voice:
|
|
paths = append(paths, m.VoicePath)
|
|
case Wechat_Message_Type_Visit_Card:
|
|
paths = append(paths, m.VisitInfo.LocalHeadImgUrl)
|
|
case Wechat_Message_Type_Video:
|
|
paths = append(paths, m.ThumbPath, m.VideoPath)
|
|
case Wechat_Message_Type_Location:
|
|
paths = append(paths, m.LocationInfo.ThumbPath)
|
|
case Wechat_Message_Type_Misc:
|
|
switch m.SubType {
|
|
case Wechat_Misc_Message_Music:
|
|
paths = append(paths, m.MusicInfo.ThumbPath)
|
|
case Wechat_Misc_Message_ThirdVideo:
|
|
paths = append(paths, m.ThumbPath)
|
|
case Wechat_Misc_Message_CardLink:
|
|
paths = append(paths, m.ThumbPath)
|
|
case Wechat_Misc_Message_File:
|
|
paths = append(paths, m.FileInfo.FilePath)
|
|
case Wechat_Misc_Message_Applet:
|
|
paths = append(paths, m.ThumbPath)
|
|
case Wechat_Misc_Message_Applet2:
|
|
paths = append(paths, m.ThumbPath)
|
|
case Wechat_Misc_Message_Channels:
|
|
paths = append(paths, m.ChannelsInfo.ThumbPath)
|
|
case Wechat_Misc_Message_Live:
|
|
paths = append(paths, m.ChannelsInfo.ThumbPath)
|
|
case Wechat_Misc_Message_Game:
|
|
paths = append(paths, m.ThumbPath)
|
|
case Wechat_Misc_Message_TingListen:
|
|
paths = append(paths, m.MusicInfo.ThumbPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, path := range paths {
|
|
taskSend(topDir, path, exportPath, taskChan)
|
|
}
|
|
|
|
if mlist.Total < pageSize {
|
|
break
|
|
}
|
|
_time = mlist.Rows[mlist.Total-1].CreateTime - 1
|
|
}
|
|
log.Println("message file done")
|
|
//copy HeadImage
|
|
taskSend(topDir, P.SelfInfo.LocalHeadImgUrl, exportPath, taskChan)
|
|
info, err := P.WechatGetUserInfoByNameOnCache(userName)
|
|
if err == nil {
|
|
taskSend(topDir, info.LocalHeadImgUrl, exportPath, taskChan)
|
|
}
|
|
|
|
if strings.HasSuffix(userName, "@chatroom") {
|
|
uList, err := P.WeChatGetChatRoomUserList(userName)
|
|
if err == nil {
|
|
for _, user := range uList.Users {
|
|
taskSend(topDir, user.LocalHeadImgUrl, exportPath, taskChan)
|
|
}
|
|
}
|
|
}
|
|
log.Println("HeadImage file done")
|
|
close(taskChan)
|
|
wg.Wait()
|
|
|
|
return nil
|
|
}
|