Compare commits

...

9 Commits
v1.1.0 ... main

Author SHA1 Message Date
HAL
260c7306ad 1. 修复FileStorage\Image文件没有导出的问题
2. 增加图片定位到聊天位置的功能
3. 导出界面增加提示
2025-04-16 00:00:48 +08:00
HAL
26399847ee update readme 2025-04-04 00:12:41 +08:00
HAL
640f27ce67 1. 修改程序ICON 2025-03-31 23:20:55 +08:00
HAL
6ddb45770f 1. UI统一使用“思源黑体”字体
2. 修复导出分享会话时可执行文件可能拷贝错误的情况
3. 修复微信存储路径为网盘时,解密会失败的情况
2025-03-31 23:01:13 +08:00
HAL
4d9043cccf 1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
2025-03-08 16:32:59 +08:00
HAL
3cc400bcbd update readme 2025-03-08 15:33:54 +08:00
HAL
3d6d890212 1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
2025-03-08 15:27:46 +08:00
git-jiadong
a8addf11eb
wechat QR code 2025-03-02 22:55:25 +08:00
HAL
81b873048b update readme 2025-01-16 22:48:17 +08:00
29 changed files with 1598 additions and 580 deletions

View File

@ -29,11 +29,12 @@ jobs:
submodules: recursive
- name: Build wails
uses: dAppServer/wails-build-action@v2.2
uses: dAppServer/wails-build-action@main
id: build
with:
build-name: ${{ matrix.build.name }}
sign: false
build-platform: ${{ matrix.build.platform }}
package: true
go-version: '1.21'
go-version: '1.21'
wails-version: "v2.9.1"

3
.gitignore vendored
View File

@ -30,4 +30,5 @@ env
User
config.json
wechatDataBackup.exe
app.log
app.log
app-*.log

View File

@ -1,5 +1,34 @@
<p align="center" style="text-align: center">
<img src="./res/logo_256.png" width="15%"><br/>
</p>
<p align="center">
<b>wechatDataBackup: PC微信聊天记录数据导出工具</b>
<br/>
<br/>
<a href="https://github.com/git-jiadong/wechatDataBackup/stargazers">
<img src="https://img.shields.io/github/stars/git-jiadong/wechatDataBackup" alt="GitHub Star"/>
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/releases">
<img src="https://img.shields.io/github/downloads/git-jiadong/wechatDataBackup/total" alt="downloads" />
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/releases">
<img src="https://img.shields.io/github/v/release/git-jiadong/wechatDataBackup" alt="releases version"/>
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/commits/main">
<img src="https://img.shields.io/github/last-commit/git-jiadong/wechatDataBackup" alt="last commit" />
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup" >
<img src="https://img.shields.io/github/languages/top/git-jiadong/wechatDataBackup" alt="languages"/>
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup" >
<img src="https://img.shields.io/github/repo-size/git-jiadong/wechatDataBackup" alt="repo size" />
</a>
<a href="https://github.com/git-jiadong/wechatDataBackup/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/git-jiadong/wechatDataBackup" alt="license" />
</a>
</p>
# wechatDataBackup
PC微信聊天记录数据导出工具
* 基于wails开发 + React前端实现PC端微信聊天记录一键导出功能。
* 导出后数据可以做永久化保存,即使微信停止支持,聊天记录也可以随时查看。
@ -59,6 +88,10 @@ wails build
- [x] 头像使用本地头像
- [ ] 支持更多消息类型显示
- [x] 图片查看器重绘
- [x] 支持会话导出分享
- [x] 支持自动定位到最后浏览位置
- [x] 支持书签功能
- [x] 支持单聊会话对话人位置调换功能
- [ ] 实现表情预先下载(实现完全离线查看)
- [ ] 聊天报告
- [ ] AI本地模型应用
@ -94,4 +127,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)

172
app.go
View File

@ -23,7 +23,7 @@ const (
configDefaultUserKey = "userConfig.defaultUser"
configUsersKey = "userConfig.users"
configExportPathKey = "exportPath"
appVersion = "v1.1.0"
appVersion = "v1.2.4"
)
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,166 @@ 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()
}
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()
}
exeSrcPath, err := os.Executable()
if err != nil {
log.Println("Executable:", exeSrcPath)
return "Executable:" + err.Error()
}
exeDstPath := exPath + "\\" + "wechatDataBackup.exe"
log.Printf("Copy [%s] -> [%s]\n", exeSrcPath, exeDstPath)
_, err = utils.CopyFile(exeSrcPath, exeDstPath)
if err != nil {
log.Println("CopyFile:", err)
return "CopyFile:" + err.Error()
}
return ""
return ""
}
func (a *App) GetAppIsShareData() bool {
if a.provider != nil {
return a.provider.IsShareData
}
return false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -1,3 +1,24 @@
## v1.2.4
1. 修复FileStorage\Image文件没有导出的问题
2. 增加图片定位到聊天位置的功能
3. 导出界面增加提示
## v1.2.3
1. 修改程序ICON
## v1.2.2
1. UI统一使用“思源黑体”字体
2. 修复导出分享会话时可执行文件可能拷贝错误的情况
3. 修复微信存储路径为网盘时,解密会失败的情况
## v1.2.0
1. 实现最后浏览位置记录和书签功能
2. 实现会话导出分享功能
3. 增加语音消息和通话消息的筛选
4. 实现单聊会话对话人位置调换功能
5. 添加繁体和英语表情的解析
6. 修复相近时间的消息可能会出现顺序不对的问题
## v1.1.0
1. 支持转账、通话、链接消息的显示
2. 支持名片、视频号、QQ音乐、小程序、定位等消息的显示

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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.8be36b27.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
frontend/dist/assets/logo.8df6944e.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

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.8be36b27.js"></script>
<link rel="stylesheet" href="/assets/index.00f6955e.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

@ -310,6 +310,7 @@ func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- s
videoRootPath := info.FilePath + "\\FileStorage\\Video"
fileRootPath := info.FilePath + "\\FileStorage\\File"
cacheRootPath := info.FilePath + "\\FileStorage\\Cache"
rootPaths := []string{videoRootPath, fileRootPath, cacheRootPath}
handleNumber := int64(0)
@ -326,6 +327,9 @@ func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- s
go func() {
for _, rootPath := range rootPaths {
log.Println(rootPath)
if _, err := os.Stat(rootPath); err != nil {
continue
}
err := filepath.Walk(rootPath, func(path string, finfo os.FileInfo, err error) error {
if err != nil {
log.Printf("filepath.Walk%v\n", err)
@ -399,43 +403,51 @@ func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- s
func exportWeChatBat(info WeChatInfo, expPath string, progress chan<- string) {
progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Dat start\", \"progress\": 21}"
datRootPath := info.FilePath + "\\FileStorage\\MsgAttach"
fileInfo, err := os.Stat(datRootPath)
if err != nil || !fileInfo.IsDir() {
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%s error\"}", datRootPath)
return
}
imageRootPath := info.FilePath + "\\FileStorage\\Image"
rootPaths := []string{datRootPath, imageRootPath}
handleNumber := int64(0)
fileNumber := getPathFileNumber(datRootPath, ".dat")
fileNumber := int64(0)
for i := range rootPaths {
fileNumber += getPathFileNumber(rootPaths[i], ".dat")
}
log.Println("DatFileNumber ", fileNumber)
var wg sync.WaitGroup
var reportWg sync.WaitGroup
quitChan := make(chan struct{})
taskChan := make(chan [2]string, 100)
go func() {
err = filepath.Walk(datRootPath, func(path string, finfo os.FileInfo, err error) error {
if err != nil {
log.Printf("filepath.Walk%v\n", err)
return err
for i := range rootPaths {
if _, err := os.Stat(rootPaths[i]); err != nil {
continue
}
if !finfo.IsDir() && strings.HasSuffix(path, ".dat") {
expFile := expPath + path[len(info.FilePath):]
_, err := os.Stat(filepath.Dir(expFile))
err := filepath.Walk(rootPaths[i], func(path string, finfo os.FileInfo, err error) error {
if err != nil {
os.MkdirAll(filepath.Dir(expFile), 0644)
log.Printf("filepath.Walk%v\n", err)
return err
}
if !finfo.IsDir() && strings.HasSuffix(path, ".dat") {
expFile := expPath + path[len(info.FilePath):]
_, err := os.Stat(filepath.Dir(expFile))
if err != nil {
os.MkdirAll(filepath.Dir(expFile), 0644)
}
task := [2]string{path, expFile}
taskChan <- task
return nil
}
task := [2]string{path, expFile}
taskChan <- task
return nil
})
if err != nil {
log.Println("filepath.Walk:", err)
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err)
}
return nil
})
if err != nil {
log.Println("filepath.Walk:", err)
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err)
}
close(taskChan)
}()
@ -445,7 +457,7 @@ func exportWeChatBat(info WeChatInfo, expPath string, progress chan<- string) {
go func() {
defer wg.Done()
for task := range taskChan {
_, err = os.Stat(task[1])
_, err := os.Stat(task[1])
if err == nil {
atomic.AddInt64(&handleNumber, 1)
continue
@ -598,7 +610,7 @@ func GetWeChatInfo() (list *WeChatInfoList) {
for _, f := range files {
if strings.HasSuffix(f.Path, "\\Media.db") {
// fmt.Printf("opened %s\n", f.Path[4:])
filePath := f.Path[4:]
filePath := f.Path
parts := strings.Split(filePath, string(filepath.Separator))
if len(parts) < 4 {
log.Println("Error filePath " + filePath)
@ -747,6 +759,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 {
@ -181,6 +182,7 @@ type WeChatMessage struct {
}
type WeChatMessageList struct {
MsgType string `json:"MsgType"`
KeyWord string `json:"KeyWord"`
Total int `json:"Total"`
Rows []WeChatMessage `json:"Rows"`
@ -219,6 +221,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 +250,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 +321,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 +366,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 +400,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 +424,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 +630,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)
@ -643,6 +693,7 @@ func (P *WechatDataProvider) WeChatGetMessageListByKeyWord(userName string, time
List := &WeChatMessageList{}
List.Rows = make([]WeChatMessage, 0)
List.KeyWord = keyWord
List.MsgType = msgType
_time := time
selectPagesize := pageSize
if keyWord != "" || msgType != "" {
@ -683,6 +734,7 @@ func (P *WechatDataProvider) WeChatGetMessageListByType(userName string, time in
List := &WeChatMessageList{}
List.Rows = make([]WeChatMessage, 0)
List.MsgType = msgType
selectTime := time
selectpageSize := 30
needSize := pageSize
@ -994,10 +1046,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 +1097,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 +1127,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 +1255,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 +1318,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 +1454,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
}

BIN
res/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

BIN
res/logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
res/logo_256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
res/wechatQR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB