1. 实现最后浏览位置记录和书签功能

2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
This commit is contained in:
HAL 2025-03-08 15:27:46 +08:00
parent a8addf11eb
commit 3d6d890212
14 changed files with 1499 additions and 551 deletions

View File

@ -102,4 +102,7 @@ A: Win7电脑需要安装WebView2运行时才能正常使用。github release版
- 微信数据库解密和数据库的使用 [PyWxDump](https://github.com/xaoyaoo/PyWxDump/tree/master)
- silk语音消息解码 [silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
- PCM转MP3 [lame](https://github.com/viert/lame.git)
- Dat图片解码 [wechatDatDecode](https://github.com/liuggchen/wechatDatDecode)
- Dat图片解码 [wechatDatDecode](https://github.com/liuggchen/wechatDatDecode)
## 交流/讨论
![](./res/wechatQR.png)

165
app.go
View File

@ -23,7 +23,7 @@ const (
configDefaultUserKey = "userConfig.defaultUser"
configUsersKey = "userConfig.users"
configExportPathKey = "exportPath"
appVersion = "v1.1.0"
appVersion = "v1.2.0"
)
type FileLoader struct {
@ -183,14 +183,15 @@ func (a *App) startup(ctx context.Context) {
}
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
return false
}
func (a *App) shutdown(ctx context.Context) {
if a.provider != nil {
a.provider.WechatWechatDataProviderClose()
a.provider = nil
}
return false
log.Printf("App Version %s exit!", appVersion)
}
func (a *App) GetWeChatAllInfo() string {
@ -713,3 +714,159 @@ func (a *App) SaveFileDialog(file string, alisa string) string {
return ""
}
func (a *App) GetSessionLastTime(userName string) string {
if a.provider == nil || userName == "" {
lastTime := &wechat.WeChatLastTime{}
lastTimeString, _ := json.Marshal(lastTime)
return string(lastTimeString)
}
lastTime := a.provider.WeChatGetSessionLastTime(userName)
lastTimeString, _ := json.Marshal(lastTime)
return string(lastTimeString)
}
func (a *App) SetSessionLastTime(userName string, stamp int64, messageId string) string {
if a.provider == nil {
return ""
}
lastTime := &wechat.WeChatLastTime{
UserName: userName,
Timestamp: stamp,
MessageId: messageId,
}
err := a.provider.WeChatSetSessionLastTime(lastTime)
if err != nil {
log.Println("WeChatSetSessionLastTime failed:", err.Error())
return err.Error()
}
return ""
}
func (a *App) SetSessionBookMask(userName, tag, info string) string {
if a.provider == nil || userName == "" {
return "invaild params"
}
err := a.provider.WeChatSetSessionBookMask(userName, tag, info)
if err != nil {
log.Println("WeChatSetSessionBookMask failed:", err.Error())
return err.Error()
}
return ""
}
func (a *App) DelSessionBookMask(markId string) string {
if a.provider == nil || markId == "" {
return "invaild params"
}
err := a.provider.WeChatDelSessionBookMask(markId)
if err != nil {
log.Println("WeChatDelSessionBookMask failed:", err.Error())
return err.Error()
}
return ""
}
func (a *App) GetSessionBookMaskList(userName string) string {
if a.provider == nil || userName == "" {
return "invaild params"
}
markLIst, err := a.provider.WeChatGetSessionBookMaskList(userName)
if err != nil {
log.Println("WeChatGetSessionBookMaskList failed:", err.Error())
_list := &wechat.WeChatBookMarkList{}
_listString, _ := json.Marshal(_list)
return string(_listString)
}
markLIstString, _ := json.Marshal(markLIst)
return string(markLIstString)
}
func (a *App) SelectedDirDialog(title string) string {
dialogOptions := runtime.OpenDialogOptions{
Title: title,
}
selectedDir, err := runtime.OpenDirectoryDialog(a.ctx, dialogOptions)
if err != nil {
log.Println("OpenDirectoryDialog:", err)
return ""
}
if selectedDir == "" {
return ""
}
return selectedDir
}
func (a *App) ExportWeChatDataByUserName(userName, path string) string {
if a.provider == nil || userName == "" || path == "" {
return "invaild params" + userName
}
if !utils.PathIsCanWriteFile(path) {
log.Println("PathIsCanWriteFile: " + path)
return "PathIsCanWriteFile: " + path
}
exPath := path + "\\" + "wechatDataBackup_" + userName
if _, err := os.Stat(exPath); err != nil {
os.MkdirAll(exPath, os.ModePerm)
} else {
return "path exist:" + exPath
}
log.Println("ExportWeChatDataByUserName:", userName, exPath)
err := a.provider.WeChatExportDataByUserName(userName, exPath)
if err != nil {
log.Println("WeChatExportDataByUserName failed:", err)
return "WeChatExportDataByUserName failed:" + err.Error()
}
exeSrcPath := a.FLoader.FilePrefix + "\\" + "wechatDataBackup.exe"
exeDstPath := exPath + "\\" + "wechatDataBackup.exe"
_, err = utils.CopyFile(exeSrcPath, exeDstPath)
if err != nil {
log.Println("CopyFile:", err)
return "CopyFile:" + err.Error()
}
config := map[string]interface{}{
"exportpath": ".\\",
"userconfig": map[string]interface{}{
"defaultuser": a.defaultUser,
"users": []string{a.defaultUser},
},
}
configJson, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Println("MarshalIndent:", err)
return "MarshalIndent:" + err.Error()
}
configPath := exPath + "\\" + "config.json"
err = os.WriteFile(configPath, configJson, os.ModePerm)
if err != nil {
log.Println("WriteFile:", err)
return "WriteFile:" + err.Error()
}
return ""
}
func (a *App) GetAppIsShareData() bool {
if a.provider != nil {
return a.provider.IsShareData
}
return false
}

View File

@ -1,3 +1,11 @@
## v1.2.0
1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
## v1.1.0
1. 支持转账、通话、链接消息的显示
2. 支持名片、视频号、QQ音乐、小程序、定位等消息的显示

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

533
frontend/dist/assets/index.e4a8c0f4.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
frontend/dist/assets/share.3d7abf39.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -4,8 +4,8 @@
<meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>wechatDataBackup</title>
<script type="module" crossorigin src="/assets/index.04e85ebe.js"></script>
<link rel="stylesheet" href="/assets/index.3ddb0aa4.css">
<script type="module" crossorigin src="/assets/index.e4a8c0f4.js"></script>
<link rel="stylesheet" href="/assets/index.0393a903.css">
</head>
<body >
<div id="root"></div>

View File

@ -51,6 +51,7 @@ func main() {
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
OnBeforeClose: app.beforeClose,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
@ -58,6 +59,6 @@ func main() {
})
if err != nil {
println("Error:", err.Error())
log.Println("Error:", err.Error())
}
}

View File

@ -185,7 +185,11 @@ func removeCustomTags(input string) string {
}
func Html2Text(htmlStr string) string {
if htmlStr[0] != '<' {
// if htmlStr == "" {
// return ""
// }
if len(htmlStr) == 0 || htmlStr[0] != '<' {
return htmlStr
}

View File

@ -747,6 +747,7 @@ func hasDeviceSybmol(buffer []byte) int {
{'p', 'a', 'd', '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00},
{'i', 'p', 'h', 'o', 'n', 'e', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00},
{'i', 'p', 'a', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00},
{'O', 'H', 'O', 'S', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00},
}
for _, syb := range sybmols {
if index := bytes.Index(buffer, syb); index != -1 {

View File

@ -130,9 +130,10 @@ type VoipInfo struct {
}
type ChannelsInfo struct {
ThumbPath string
ThumbCache string
NickName string
ThumbPath string
ThumbCache string
NickName string
Description string
}
type MusicInfo struct {
@ -219,6 +220,23 @@ type WeChatAccountInfo struct {
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
@ -231,17 +249,20 @@ type WechatDataProvider struct {
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
@ -299,6 +320,26 @@ func CreateWechatDataProvider(resPath string, prefixRes string) (*WechatDataProv
}
}
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)
@ -324,6 +365,7 @@ func CreateWechatDataProvider(resPath string, prefixRes string) (*WechatDataProv
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)
@ -357,6 +399,13 @@ func (P *WechatDataProvider) WechatWechatDataProviderClose() {
}
}
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 {
@ -374,7 +423,7 @@ func (P *WechatDataProvider) WechatGetUserInfoByName(name string) (*WeChatUserIn
// log.Println(querySql)
err := P.microMsg.QueryRow(querySql).Scan(&UserName, &Alias, &ReMark, &NickName)
if err != nil {
log.Println("not found User:", err)
// log.Println("not found User:", err)
return info, err
}
@ -580,9 +629,9 @@ func (P *WechatDataProvider) weChatGetMessageListByTime(userName string, time in
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 CreateTime desc limit %d;"
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, StrTalker, StrContent, CompressContent, BytesExtra FROM MSG Where StrTalker='%s' And CreateTime>%d order by CreateTime asc limit %d) AS SubQuery order by CreateTime desc;"
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)
@ -994,10 +1043,12 @@ func (P *WechatDataProvider) wechatMessageCompressContentHandle(msg *WeChatMessa
} 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")
@ -1043,6 +1094,11 @@ func (P *WechatDataProvider) wechatMessageVisitHandke(msg *WeChatMessage) {
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
}
}
}
@ -1068,7 +1124,7 @@ func (P *WechatDataProvider) wechatMessageGetUserInfo(msg *WeChatMessage) {
pinfo, err := P.WechatGetUserInfoByNameOnCache(who)
if err != nil {
log.Println("WechatGetUserInfoByNameOnCache:", err)
// log.Println("WechatGetUserInfoByNameOnCache:", err)
return
}
@ -1196,6 +1252,10 @@ func weChatMessageTypeFilter(msg *WeChatMessage, msgType string) bool {
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("群成员"):]
@ -1255,7 +1315,7 @@ func (P *WechatDataProvider) WechatGetUserInfoByNameOnCache(name string) (*WeCha
pinfo, err = P.WechatGetUserInfoByName(name)
}
if err != nil {
log.Printf("WechatGetUserInfoByName %s failed: %v\n", name, err)
// log.Printf("WechatGetUserInfoByName %s failed: %v\n", name, err)
return nil, err
}
@ -1391,3 +1451,717 @@ func isLinkSubType(subType int) bool {
}
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(&timestamp, &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
}