Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
260c7306ad | ||
![]() |
26399847ee | ||
![]() |
640f27ce67 | ||
![]() |
6ddb45770f | ||
![]() |
4d9043cccf | ||
![]() |
3cc400bcbd | ||
![]() |
3d6d890212 | ||
![]() |
a8addf11eb | ||
![]() |
81b873048b |
5
.github/workflows/publish.yml
vendored
@ -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
@ -30,4 +30,5 @@ env
|
||||
User
|
||||
config.json
|
||||
wechatDataBackup.exe
|
||||
app.log
|
||||
app.log
|
||||
app-*.log
|
40
README.md
@ -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)
|
||||
|
||||
## 交流/讨论
|
||||

|
172
app.go
@ -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
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 411 KiB After Width: | Height: | Size: 82 KiB |
21
changelog.md
@ -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音乐、小程序、定位等消息的显示
|
||||
|
BIN
frontend/dist/assets/110_怄火.896c41d9.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
frontend/dist/assets/112_左哼哼.5425438e.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
frontend/dist/assets/113_哈欠.fe9c8521.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
frontend/dist/assets/favorite.1b38cfe5.png
vendored
Normal file
After Width: | Height: | Size: 4.7 KiB |
1
frontend/dist/assets/index.00f6955e.css
vendored
Normal file
533
frontend/dist/assets/index.04e85ebe.js
vendored
1
frontend/dist/assets/index.3ddb0aa4.css
vendored
533
frontend/dist/assets/index.8be36b27.js
vendored
Normal file
BIN
frontend/dist/assets/logo.8df6944e.png
vendored
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
frontend/dist/assets/red_packet.704bd303.png
vendored
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
frontend/dist/assets/share.3d7abf39.png
vendored
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
frontend/dist/assets/思源黑体-Normal.df5ff3ec.otf
vendored
Normal file
4
frontend/dist/index.html
vendored
@ -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>
|
||||
|
3
main.go
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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(×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
|
||||
}
|
||||
|
BIN
res/logo.png
Normal file
After Width: | Height: | Size: 470 KiB |
BIN
res/logo_128.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
res/logo_256.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
res/wechatQR.png
Normal file
After Width: | Height: | Size: 70 KiB |