1. 优化头像显示,优先使用本地头像显示

2. 支持自定义导出路径、增加对导出路径的权限检测
3. 增加联系人显示
4. 滚动条滑块调整小大
5. 支持已经打开日志所在文件夹、日志增加回滚功能
This commit is contained in:
HAL 2024-11-03 00:54:25 +08:00
parent 073d586aea
commit 331cd8125d
14 changed files with 1088 additions and 921 deletions

238
app.go
View File

@ -5,8 +5,10 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"wechatDataBackup/pkg/utils" "wechatDataBackup/pkg/utils"
"wechatDataBackup/pkg/wechat" "wechatDataBackup/pkg/wechat"
@ -18,9 +20,37 @@ const (
defaultConfig = "config" defaultConfig = "config"
configDefaultUserKey = "userConfig.defaultUser" configDefaultUserKey = "userConfig.defaultUser"
configUsersKey = "userConfig.users" configUsersKey = "userConfig.users"
appVersion = "v1.0.3" configExportPathKey = "exportPath"
appVersion = "v1.0.4"
) )
type FileLoader struct {
http.Handler
FilePrefix string
}
func NewFileLoader(prefix string) *FileLoader {
return &FileLoader{FilePrefix: prefix}
}
func (h *FileLoader) SetFilePrefix(prefix string) {
h.FilePrefix = prefix
log.Println("SetFilePrefix", h.FilePrefix)
}
func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
var err error
requestedFilename := h.FilePrefix + "\\" + strings.TrimPrefix(req.URL.Path, "/")
// log.Println("Requesting file:", requestedFilename)
fileData, err := os.ReadFile(requestedFilename)
if err != nil {
res.WriteHeader(http.StatusBadRequest)
res.Write([]byte(fmt.Sprintf("Could not load file %s", requestedFilename)))
}
res.Write(fileData)
}
// App struct // App struct
type App struct { type App struct {
ctx context.Context ctx context.Context
@ -29,6 +59,8 @@ type App struct {
defaultUser string defaultUser string
users []string users []string
firstStart bool firstStart bool
FLoader *FileLoader
} }
type WeChatInfo struct { type WeChatInfo struct {
@ -51,22 +83,39 @@ type WeChatAccountInfos struct {
Total int `json:"Total"` Total int `json:"Total"`
} }
type ErrorMessage struct {
ErrorStr string `json:"error"`
}
// NewApp creates a new App application struct // NewApp creates a new App application struct
func NewApp() *App { func NewApp() *App {
a := &App{} a := &App{}
a.FLoader = NewFileLoader(".\\")
viper.SetConfigName(defaultConfig) viper.SetConfigName(defaultConfig)
viper.SetConfigType("json") viper.SetConfigType("json")
viper.AddConfigPath(".") viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err == nil { if err := viper.ReadInConfig(); err == nil {
a.defaultUser = viper.GetString(configDefaultUserKey) a.defaultUser = viper.GetString(configDefaultUserKey)
a.users = viper.GetStringSlice(configUsersKey) a.users = viper.GetStringSlice(configUsersKey)
prefix := viper.GetString(configExportPathKey)
if prefix != "" {
log.Println("SetFilePrefix", prefix)
a.FLoader.SetFilePrefix(prefix)
}
a.scanAccountByPath(prefix)
// log.Println(a.defaultUser) // log.Println(a.defaultUser)
// log.Println(a.users) // log.Println(a.users)
} else { } else {
a.firstStart = true if a.scanAccountByPath(".\\") != nil {
log.Println("not config exist") log.Println("not config exist")
} }
}
log.Printf("default: %s users: %v\n", a.defaultUser, a.users)
if len(a.users) == 0 {
a.firstStart = true
}
return a return a
} }
@ -93,6 +142,11 @@ func (a *App) GetWeChatAllInfo() string {
infoList.Info = make([]WeChatInfo, 0) infoList.Info = make([]WeChatInfo, 0)
infoList.Total = 0 infoList.Total = 0
if a.provider != nil {
a.provider.WechatWechatDataProviderClose()
a.provider = nil
}
a.infoList = wechat.GetWeChatAllInfo() a.infoList = wechat.GetWeChatAllInfo()
for i := range a.infoList.Info { for i := range a.infoList.Info {
var info WeChatInfo var info WeChatInfo
@ -135,12 +189,13 @@ func (a *App) ExportWeChatAllData(full bool, acountName string) {
return return
} }
_, err := os.Stat(".\\User") prefixExportPath := a.FLoader.FilePrefix + "\\User\\"
_, err := os.Stat(prefixExportPath)
if err != nil { if err != nil {
os.Mkdir(".\\User", os.ModeDir) os.Mkdir(prefixExportPath, os.ModeDir)
} }
expPath := ".\\User\\" + pInfo.AcountName expPath := prefixExportPath + pInfo.AcountName
_, err = os.Stat(expPath) _, err = os.Stat(expPath)
if err == nil { if err == nil {
if !full { if !full {
@ -177,7 +232,7 @@ func (a *App) ExportWeChatAllData(full bool, acountName string) {
}() }()
} }
func (a *App) createWechatDataProvider(resPath string) error { func (a *App) createWechatDataProvider(resPath string, prefix string) error {
if a.provider != nil && a.provider.SelfInfo != nil && filepath.Base(resPath) == a.provider.SelfInfo.UserName { if a.provider != nil && a.provider.SelfInfo != nil && filepath.Base(resPath) == a.provider.SelfInfo.UserName {
log.Println("WechatDataProvider not need create:", a.provider.SelfInfo.UserName) log.Println("WechatDataProvider not need create:", a.provider.SelfInfo.UserName)
return nil return nil
@ -189,7 +244,7 @@ func (a *App) createWechatDataProvider(resPath string) error {
log.Println("createWechatDataProvider WechatWechatDataProviderClose") log.Println("createWechatDataProvider WechatWechatDataProviderClose")
} }
provider, err := wechat.CreateWechatDataProvider(resPath) provider, err := wechat.CreateWechatDataProvider(resPath, prefix)
if err != nil { if err != nil {
log.Println("CreateWechatDataProvider failed:", resPath) log.Println("CreateWechatDataProvider failed:", resPath)
return err return err
@ -202,22 +257,29 @@ func (a *App) createWechatDataProvider(resPath string) error {
} }
func (a *App) WeChatInit() { func (a *App) WeChatInit() {
expPath := ".\\User\\" + a.defaultUser if len(a.defaultUser) == 0 {
if a.createWechatDataProvider(expPath) == nil { log.Println("not defaultUser")
return
}
expPath := a.FLoader.FilePrefix + "\\User\\" + a.defaultUser
prefixPath := "\\User\\" + a.defaultUser
wechat.ExportWeChatHeadImage(expPath)
if a.createWechatDataProvider(expPath, prefixPath) == nil {
infoJson, _ := json.Marshal(a.provider.SelfInfo) infoJson, _ := json.Marshal(a.provider.SelfInfo)
runtime.EventsEmit(a.ctx, "selfInfo", string(infoJson)) runtime.EventsEmit(a.ctx, "selfInfo", string(infoJson))
} }
} }
func (a *App) GetWechatSessionList(pageIndex int, pageSize int) string { func (a *App) GetWechatSessionList(pageIndex int, pageSize int) string {
expPath := ".\\User\\" + a.defaultUser if a.provider == nil {
if a.createWechatDataProvider(expPath) != nil { log.Println("provider not init")
return "" return "{\"Total\":0}"
} }
log.Printf("pageIndex: %d\n", pageIndex) log.Printf("pageIndex: %d\n", pageIndex)
list, err := a.provider.WeChatGetSessionList(pageIndex, pageSize) list, err := a.provider.WeChatGetSessionList(pageIndex, pageSize)
if err != nil { if err != nil {
return "" return "{\"Total\":0}"
} }
listStr, _ := json.Marshal(list) listStr, _ := json.Marshal(list)
@ -225,6 +287,22 @@ func (a *App) GetWechatSessionList(pageIndex int, pageSize int) string {
return string(listStr) return string(listStr)
} }
func (a *App) GetWechatContactList(pageIndex int, pageSize int) string {
if a.provider == nil {
log.Println("provider not init")
return "{\"Total\":0}"
}
log.Printf("pageIndex: %d\n", pageIndex)
list, err := a.provider.WeChatGetContactList(pageIndex, pageSize)
if err != nil {
return "{\"Total\":0}"
}
listStr, _ := json.Marshal(list)
log.Println("WeChatGetContactList:", list.Total)
return string(listStr)
}
func (a *App) GetWechatMessageListByTime(userName string, time int64, pageSize int, direction string) string { func (a *App) GetWechatMessageListByTime(userName string, time int64, pageSize int, direction string) string {
log.Println("GetWechatMessageList:", userName, pageSize, time, direction) log.Println("GetWechatMessageList:", userName, pageSize, time, direction)
if len(userName) == 0 { if len(userName) == 0 {
@ -284,6 +362,7 @@ func (a *App) GetWechatMessageDate(userName string) string {
func (a *App) setCurrentConfig() { func (a *App) setCurrentConfig() {
viper.Set(configDefaultUserKey, a.defaultUser) viper.Set(configDefaultUserKey, a.defaultUser)
viper.Set(configUsersKey, a.users) viper.Set(configUsersKey, a.users)
viper.Set(configExportPathKey, a.FLoader.FilePrefix)
err := viper.SafeWriteConfig() err := viper.SafeWriteConfig()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -314,7 +393,9 @@ func (a *App) OpenFileOrExplorer(filePath string, explorer bool) string {
// filePath = root + filePath[1:] // filePath = root + filePath[1:]
// } // }
// log.Println("OpenFileOrExplorer:", filePath) // log.Println("OpenFileOrExplorer:", filePath)
err := utils.OpenFileOrExplorer(filePath, explorer)
path := a.FLoader.FilePrefix + filePath
err := utils.OpenFileOrExplorer(path, explorer)
if err != nil { if err != nil {
return "{\"result\": \"OpenFileOrExplorer failed\", \"status\":\"failed\"}" return "{\"result\": \"OpenFileOrExplorer failed\", \"status\":\"failed\"}"
} }
@ -349,13 +430,14 @@ func (a *App) GetWechatLocalAccountInfo() string {
infos.Total = 0 infos.Total = 0
infos.CurrentAccount = a.defaultUser infos.CurrentAccount = a.defaultUser
for i := range a.users { for i := range a.users {
resPath := ".\\User\\" + a.users[i] resPath := a.FLoader.FilePrefix + "\\User\\" + a.users[i]
if _, err := os.Stat(resPath); err != nil { if _, err := os.Stat(resPath); err != nil {
log.Println("GetWechatLocalAccountInfo:", resPath, err) log.Println("GetWechatLocalAccountInfo:", resPath, err)
continue continue
} }
info, err := wechat.WechatGetAccountInfo(resPath, a.users[i]) prefixResPath := "\\User\\" + a.users[i]
info, err := wechat.WechatGetAccountInfo(resPath, prefixResPath, a.users[i])
if err != nil { if err != nil {
log.Println("GetWechatLocalAccountInfo", err) log.Println("GetWechatLocalAccountInfo", err)
continue continue
@ -386,3 +468,127 @@ func (a *App) WechatSwitchAccount(account string) bool {
return false return false
} }
func (a *App) GetExportPathStat() string {
path := a.FLoader.FilePrefix
log.Println("utils.GetPathStat ++")
stat, err := utils.GetPathStat(path)
log.Println("utils.GetPathStat --")
if err != nil {
log.Println("GetPathStat error:", path, err)
var msg ErrorMessage
msg.ErrorStr = fmt.Sprintf("%s:%v", path, err)
msgStr, _ := json.Marshal(msg)
return string(msgStr)
}
statString, _ := json.Marshal(stat)
return string(statString)
}
func (a *App) ExportPathIsCanWrite() bool {
path := a.FLoader.FilePrefix
return utils.PathIsCanWriteFile(path)
}
func (a *App) OpenExportPath() {
path := a.FLoader.FilePrefix
runtime.BrowserOpenURL(a.ctx, path)
}
func (a *App) OpenDirectoryDialog() string {
dialogOptions := runtime.OpenDialogOptions{
Title: "选择导出路径",
}
selectedDir, err := runtime.OpenDirectoryDialog(a.ctx, dialogOptions)
if err != nil {
log.Println("OpenDirectoryDialog:", err)
return ""
}
if selectedDir == "" {
log.Println("Cancel selectedDir")
return ""
}
if selectedDir == a.FLoader.FilePrefix {
log.Println("same path No need SetFilePrefix")
return ""
}
if !utils.PathIsCanWriteFile(selectedDir) {
log.Println("PathIsCanWriteFile:", selectedDir, "error")
return ""
}
a.FLoader.SetFilePrefix(selectedDir)
log.Println("OpenDirectoryDialog:", selectedDir)
a.scanAccountByPath(selectedDir)
return selectedDir
}
func (a *App) scanAccountByPath(path string) error {
infos := WeChatAccountInfos{}
infos.Info = make([]wechat.WeChatAccountInfo, 0)
infos.Total = 0
infos.CurrentAccount = ""
userPath := path + "\\User\\"
if _, err := os.Stat(userPath); err != nil {
return err
}
dirs, err := os.ReadDir(userPath)
if err != nil {
log.Println("ReadDir", err)
return err
}
for i := range dirs {
if !dirs[i].Type().IsDir() {
continue
}
log.Println("dirs[i].Name():", dirs[i].Name())
resPath := path + "\\User\\" + dirs[i].Name()
prefixResPath := "\\User\\" + dirs[i].Name()
info, err := wechat.WechatGetAccountInfo(resPath, prefixResPath, dirs[i].Name())
if err != nil {
log.Println("GetWechatLocalAccountInfo", err)
continue
}
infos.Info = append(infos.Info, *info)
infos.Total += 1
}
users := make([]string, 0)
for i := 0; i < infos.Total; i++ {
users = append(users, infos.Info[i].AccountName)
}
a.users = users
found := false
for i := range a.users {
if a.defaultUser == a.users[i] {
found = true
}
}
if !found {
a.defaultUser = ""
}
if a.defaultUser == "" && len(a.users) > 0 {
a.defaultUser = a.users[0]
}
if len(a.users) > 0 {
a.setCurrentConfig()
}
return nil
}
func (a *App) OepnLogFileExplorer() {
utils.OpenFileOrExplorer(".\\app.log", true)
}

View File

@ -1,3 +1,10 @@
## v1.0.4
1. 优化头像显示,优先使用本地头像显示
2. 支持自定义导出路径、增加对导出路径的权限检测
3. 增加联系人显示
4. 滚动条滑块调整小大
5. 支持已经打开日志所在文件夹、日志增加回滚功能
## v1.0.3 ## v1.0.3
1. 增加首次使用的引导功能 1. 增加首次使用的引导功能
2. 增加多开微信可选择导出功能 2. 增加多开微信可选择导出功能

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

503
frontend/dist/assets/index.e451d83d.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>wechatDataBackup</title> <title>wechatDataBackup</title>
<script type="module" crossorigin src="/assets/index.425764f5.js"></script> <script type="module" crossorigin src="/assets/index.e451d83d.js"></script>
<link rel="stylesheet" href="/assets/index.b10575d2.css"> <link rel="stylesheet" href="/assets/index.bee21395.css">
</head> </head>
<body > <body >
<div id="root"></div> <div id="root"></div>

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/wailsapp/wails/v2 v2.9.1 github.com/wailsapp/wails/v2 v2.9.1
golang.org/x/sys v0.20.0 golang.org/x/sys v0.20.0
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (

40
main.go
View File

@ -2,51 +2,35 @@ package main
import ( import (
"embed" "embed"
"fmt"
"io" "io"
"log" "log"
"net/http"
"os" "os"
"strings"
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/assetserver"
"gopkg.in/natefinch/lumberjack.v2"
) )
//go:embed all:frontend/dist //go:embed all:frontend/dist
var assets embed.FS var assets embed.FS
type FileLoader struct {
http.Handler
}
func NewFileLoader() *FileLoader {
return &FileLoader{}
}
func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
var err error
requestedFilename := strings.TrimPrefix(req.URL.Path, "/")
// println("Requesting file:", requestedFilename)
fileData, err := os.ReadFile(requestedFilename)
if err != nil {
res.WriteHeader(http.StatusBadRequest)
res.Write([]byte(fmt.Sprintf("Could not load file %s", requestedFilename)))
}
res.Write(fileData)
}
func init() { func init() {
// log output format
log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
} }
func main() { func main() {
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) logJack := &lumberjack.Logger{
defer file.Close() Filename: "./app.log",
MaxSize: 5,
MaxBackups: 1,
MaxAge: 30,
Compress: false,
}
defer logJack.Close()
multiWriter := io.MultiWriter(file, os.Stdout) multiWriter := io.MultiWriter(logJack, os.Stdout)
// 设置日志输出目标为文件 // 设置日志输出目标为文件
log.SetOutput(multiWriter) log.SetOutput(multiWriter)
log.Println("====================== wechatDataBackup ======================") log.Println("====================== wechatDataBackup ======================")
@ -62,7 +46,7 @@ func main() {
Height: 768, Height: 768,
AssetServer: &assetserver.Options{ AssetServer: &assetserver.Options{
Assets: assets, Assets: assets,
Handler: NewFileLoader(), Handler: app.FLoader,
}, },
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup, OnStartup: app.startup,

View File

@ -5,12 +5,22 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"github.com/pkg/browser" "github.com/pkg/browser"
"github.com/shirou/gopsutil/v3/disk"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
) )
type PathStat struct {
Path string `json:"path"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
}
func getDefaultProgram(fileExtension string) (string, error) { func getDefaultProgram(fileExtension string) (string, error) {
key, err := registry.OpenKey(registry.CLASSES_ROOT, fmt.Sprintf(`.%s`, fileExtension), registry.QUERY_VALUE) key, err := registry.OpenKey(registry.CLASSES_ROOT, fmt.Sprintf(`.%s`, fileExtension), registry.QUERY_VALUE)
if err != nil { if err != nil {
@ -75,3 +85,38 @@ func OpenFileOrExplorer(filePath string, explorer bool) error {
fmt.Println("Command executed successfully") fmt.Println("Command executed successfully")
return nil return nil
} }
func GetPathStat(path string) (PathStat, error) {
pathStat := PathStat{}
absPath, err := filepath.Abs(path)
if err != nil {
return pathStat, err
}
stat, err := disk.Usage(absPath)
if err != nil {
return pathStat, err
}
pathStat.Path = stat.Path
pathStat.Total = stat.Total
pathStat.Used = stat.Used
pathStat.Free = stat.Free
pathStat.UsedPercent = stat.UsedPercent
return pathStat, nil
}
func PathIsCanWriteFile(path string) bool {
filepath := fmt.Sprintf("%s\\CanWrite.txt", path)
file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return false
}
file.Close()
os.Remove(filepath)
return true
}

View File

@ -49,6 +49,11 @@ type wechatMediaMSG struct {
Buf []byte Buf []byte
} }
type wechatHeadImgMSG struct {
userName string
Buf []byte
}
func GetWeChatAllInfo() *WeChatInfoList { func GetWeChatAllInfo() *WeChatInfoList {
list := GetWeChatInfo() list := GetWeChatInfo()
@ -73,6 +78,121 @@ func ExportWeChatAllData(info WeChatInfo, expPath string, progress chan<- string
exportWeChatBat(info, expPath, progress) exportWeChatBat(info, expPath, progress)
exportWeChatVideoAndFile(info, expPath, progress) exportWeChatVideoAndFile(info, expPath, progress)
exportWeChatVoice(info, expPath, progress) exportWeChatVoice(info, expPath, progress)
exportWeChatHeadImage(info, expPath, progress)
}
func exportWeChatHeadImage(info WeChatInfo, expPath string, progress chan<- string) {
progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Head Image\", \"progress\": 81}"
headImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage", expPath)
if _, err := os.Stat(headImgPath); err != nil {
if err := os.MkdirAll(headImgPath, 0644); err != nil {
log.Printf("MkdirAll %s failed: %v\n", headImgPath, err)
progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v error\"}", err)
return
}
}
handleNumber := int64(0)
fileNumber := int64(0)
var wg sync.WaitGroup
var reportWg sync.WaitGroup
quitChan := make(chan struct{})
MSGChan := make(chan wechatHeadImgMSG, 100)
go func() {
for {
miscDBPath := fmt.Sprintf("%s\\Msg\\Misc.db", expPath)
_, err := os.Stat(miscDBPath)
if err != nil {
log.Println("no exist:", miscDBPath)
break
}
db, err := sql.Open("sqlite3", miscDBPath)
if err != nil {
log.Printf("open %s failed: %v\n", miscDBPath, err)
break
}
defer db.Close()
err = db.QueryRow("select count(*) from ContactHeadImg1;").Scan(&fileNumber)
if err != nil {
log.Println("select count(*) failed", err)
break
}
log.Println("ContactHeadImg1 fileNumber", fileNumber)
rows, err := db.Query("select ifnull(usrName,'') as usrName, ifnull(smallHeadBuf,'') as smallHeadBuf from ContactHeadImg1;")
if err != nil {
log.Printf("Query failed: %v\n", err)
break
}
msg := wechatHeadImgMSG{}
for rows.Next() {
err := rows.Scan(&msg.userName, &msg.Buf)
if err != nil {
log.Println("Scan failed: ", err)
break
}
MSGChan <- msg
}
break
}
close(MSGChan)
}()
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for msg := range MSGChan {
imgPath := fmt.Sprintf("%s\\%s.headimg", headImgPath, msg.userName)
for {
// log.Println("imgPath:", imgPath, len(msg.Buf))
_, err := os.Stat(imgPath)
if err == nil {
break
}
if len(msg.userName) == 0 || len(msg.Buf) == 0 {
break
}
err = os.WriteFile(imgPath, msg.Buf[:], 0666)
if err != nil {
log.Println("WriteFile:", imgPath, err)
}
break
}
atomic.AddInt64(&handleNumber, 1)
}
}()
}
reportWg.Add(1)
go func() {
defer reportWg.Done()
for {
select {
case <-quitChan:
log.Println("WeChat Head Image report progress end")
return
default:
if fileNumber != 0 {
filePercent := float64(handleNumber) / float64(fileNumber)
totalPercent := 81 + (filePercent * (100 - 81))
totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat Head Image doing\", \"progress\": %d}", int(totalPercent))
progress <- totalPercentStr
}
time.Sleep(time.Second)
}
}
}()
wg.Wait()
close(quitChan)
reportWg.Wait()
progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Head Image end\", \"progress\": 100}"
} }
func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string) { func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string) {
@ -171,7 +291,7 @@ func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string)
return return
default: default:
filePercent := float64(handleNumber) / float64(fileNumber) filePercent := float64(handleNumber) / float64(fileNumber)
totalPercent := 61 + (filePercent * (100 - 61)) totalPercent := 61 + (filePercent * (80 - 61))
totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat voice doing\", \"progress\": %d}", int(totalPercent)) totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat voice doing\", \"progress\": %d}", int(totalPercent))
progress <- totalPercentStr progress <- totalPercentStr
time.Sleep(time.Second) time.Sleep(time.Second)
@ -182,7 +302,7 @@ func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string)
wg.Wait() wg.Wait()
close(quitChan) close(quitChan)
reportWg.Wait() reportWg.Wait()
progress <- "{\"status\":\"processing\", \"result\":\"export WeChat voice end\", \"progress\": 100}" progress <- "{\"status\":\"processing\", \"result\":\"export WeChat voice end\", \"progress\": 80}"
} }
func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- string) { func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- string) {
@ -793,3 +913,31 @@ func getPathFileNumber(targetPath string, fileSuffix string) int64 {
return number return number
} }
func ExportWeChatHeadImage(exportPath string) {
progress := make(chan string)
info := WeChatInfo{}
miscDBPath := fmt.Sprintf("%s\\Msg\\Misc.db", exportPath)
_, err := os.Stat(miscDBPath)
if err != nil {
log.Println("no exist:", miscDBPath)
return
}
headImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage", exportPath)
if _, err := os.Stat(headImgPath); err == nil {
log.Println("has HeadImage")
return
}
go func() {
exportWeChatHeadImage(info, exportPath, progress)
close(progress)
}()
for p := range progress {
log.Println(p)
}
log.Println("ExportWeChatHeadImage done")
}

View File

@ -67,6 +67,8 @@ type WeChatUserInfo struct {
NickName string `json:"NickName"` NickName string `json:"NickName"`
SmallHeadImgUrl string `json:"SmallHeadImgUrl"` SmallHeadImgUrl string `json:"SmallHeadImgUrl"`
BigHeadImgUrl string `json:"BigHeadImgUrl"` BigHeadImgUrl string `json:"BigHeadImgUrl"`
LocalHeadImgUrl string `json:"LocalHeadImgUrl"`
IsGroup bool `json:"IsGroup"`
} }
type WeChatSession struct { type WeChatSession struct {
@ -75,7 +77,7 @@ type WeChatSession struct {
Content string `json:"Content"` Content string `json:"Content"`
UserInfo WeChatUserInfo `json:"UserInfo"` UserInfo WeChatUserInfo `json:"UserInfo"`
Time uint64 `json:"Time"` Time uint64 `json:"Time"`
IsGroup bool `json:IsGroup` IsGroup bool `json:"IsGroup"`
} }
type WeChatSessionList struct { type WeChatSessionList struct {
@ -144,6 +146,19 @@ type WeChatUserList struct {
Total int `json:"Total"` Total int `json:"Total"`
} }
type WeChatContact struct {
WeChatUserInfo
PYInitial string
QuanPin string
RemarkPYInitial string
RemarkQuanPin string
}
type WeChatContactList struct {
Users []WeChatContact `json:"Users"`
Total int `json:"Total"`
}
type WeChatAccountInfo struct { type WeChatAccountInfo struct {
AccountName string `json:"AccountName"` AccountName string `json:"AccountName"`
AliasName string `json:"AliasName"` AliasName string `json:"AliasName"`
@ -151,6 +166,7 @@ type WeChatAccountInfo struct {
NickName string `json:"NickName"` NickName string `json:"NickName"`
SmallHeadImgUrl string `json:"SmallHeadImgUrl"` SmallHeadImgUrl string `json:"SmallHeadImgUrl"`
BigHeadImgUrl string `json:"BigHeadImgUrl"` BigHeadImgUrl string `json:"BigHeadImgUrl"`
LocalHeadImgUrl string `json:"LocalHeadImgUrl"`
} }
type wechatMsgDB struct { type wechatMsgDB struct {
@ -162,6 +178,7 @@ type wechatMsgDB struct {
type WechatDataProvider struct { type WechatDataProvider struct {
resPath string resPath string
prefixResPath string
microMsg *sql.DB microMsg *sql.DB
msgDBs []*wechatMsgDB msgDBs []*wechatMsgDB
@ -169,6 +186,7 @@ type WechatDataProvider struct {
userInfoMtx sync.Mutex userInfoMtx sync.Mutex
SelfInfo *WeChatUserInfo SelfInfo *WeChatUserInfo
ContactList *WeChatContactList
} }
const ( const (
@ -181,13 +199,40 @@ func (a byTime) Len() int { return len(a) }
func (a byTime) Less(i, j int) bool { return a[i].startTime > a[j].startTime } func (a byTime) Less(i, j int) bool { return a[i].startTime > a[j].startTime }
func (a byTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func CreateWechatDataProvider(resPath string) (*WechatDataProvider, error) { type byName []WeChatContact
func (c byName) Len() int { return len(c) }
func (c byName) Less(i, j int) bool {
var a, b string
if c[i].RemarkQuanPin != "" {
a = c[i].RemarkQuanPin
} else {
a = c[i].QuanPin
}
if c[j].RemarkQuanPin != "" {
b = c[j].RemarkQuanPin
} else {
b = c[j].QuanPin
}
return strings.Compare(a, b) < 0
}
func (c byName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func CreateWechatDataProvider(resPath string, prefixRes string) (*WechatDataProvider, error) {
provider := &WechatDataProvider{} provider := &WechatDataProvider{}
provider.resPath = resPath provider.resPath = resPath
provider.prefixResPath = prefixRes
provider.msgDBs = make([]*wechatMsgDB, 0) provider.msgDBs = make([]*wechatMsgDB, 0)
log.Println(resPath) log.Println(resPath)
userName := filepath.Base(resPath) userName := filepath.Base(resPath)
MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB
if _, err := os.Stat(MicroMsgDBPath); err != nil {
log.Println("CreateWechatDataProvider failed", MicroMsgDBPath, err)
return provider, err
}
microMsg, err := sql.Open("sqlite3", MicroMsgDBPath) microMsg, err := sql.Open("sqlite3", MicroMsgDBPath)
if err != nil { if err != nil {
log.Printf("open db %s error: %v", MicroMsgDBPath, err) log.Printf("open db %s error: %v", MicroMsgDBPath, err)
@ -217,11 +262,19 @@ func CreateWechatDataProvider(resPath string) (*WechatDataProvider, error) {
} }
provider.userInfoMap = make(map[string]WeChatUserInfo) provider.userInfoMap = make(map[string]WeChatUserInfo)
provider.microMsg = microMsg provider.microMsg = microMsg
provider.SelfInfo, err = provider.WechatGetUserInfoByName(userName) provider.SelfInfo, err = provider.WechatGetUserInfoByNameOnCache(userName)
if err != nil { if err != nil {
log.Printf("WechatGetUserInfoByName %s failed: %v", userName, err) log.Printf("WechatGetUserInfoByName %s failed: %v", userName, err)
return provider, err return provider, err
} }
provider.ContactList, err = provider.wechatGetAllContact()
if err != nil {
log.Println("wechatGetAllContact failed", err)
return provider, err
}
sort.Sort(byName(provider.ContactList.Users))
log.Println("Contact number:", provider.ContactList.Total)
provider.userInfoMap[userName] = *provider.SelfInfo provider.userInfoMap[userName] = *provider.SelfInfo
log.Println("resPath:", provider.resPath) log.Println("resPath:", provider.resPath)
return provider, nil return provider, nil
@ -256,7 +309,7 @@ func (P *WechatDataProvider) WechatGetUserInfoByName(name string) (*WeChatUserIn
return info, err return info, err
} }
log.Printf("UserName %s, Alias %s, ReMark %s, NickName %s\n", UserName, Alias, ReMark, NickName) // log.Printf("UserName %s, Alias %s, ReMark %s, NickName %s\n", UserName, Alias, ReMark, NickName)
var smallHeadImgUrl, bigHeadImgUrl string var smallHeadImgUrl, bigHeadImgUrl string
querySql = fmt.Sprintf("select ifnull(smallHeadImgUrl,'') as smallHeadImgUrl, ifnull(bigHeadImgUrl,'') as bigHeadImgUrl from ContactHeadImgUrl where usrName='%s';", UserName) querySql = fmt.Sprintf("select ifnull(smallHeadImgUrl,'') as smallHeadImgUrl, ifnull(bigHeadImgUrl,'') as bigHeadImgUrl from ContactHeadImgUrl where usrName='%s';", UserName)
@ -272,7 +325,13 @@ func (P *WechatDataProvider) WechatGetUserInfoByName(name string) (*WeChatUserIn
info.NickName = NickName info.NickName = NickName
info.SmallHeadImgUrl = smallHeadImgUrl info.SmallHeadImgUrl = smallHeadImgUrl
info.BigHeadImgUrl = bigHeadImgUrl info.BigHeadImgUrl = bigHeadImgUrl
info.IsGroup = strings.HasSuffix(UserName, "@chatroom")
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.resPath, name)
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.prefixResPath, name)
if _, err = os.Stat(localHeadImgPath); err == nil {
info.LocalHeadImgUrl = relativePath
}
// log.Println(info) // log.Println(info)
return info, nil return info, nil
} }
@ -299,7 +358,7 @@ func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (
continue continue
} }
if len(strContent) == 0 { if len(strContent) == 0 {
log.Printf("%s cotent nil\n", strUsrName) // log.Printf("%s cotent nil\n", strUsrName)
continue continue
} }
@ -308,7 +367,7 @@ func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (
session.Content = strContent session.Content = strContent
session.Time = nTime session.Time = nTime
session.IsGroup = strings.HasSuffix(strUsrName, "@chatroom") session.IsGroup = strings.HasSuffix(strUsrName, "@chatroom")
info, err := P.WechatGetUserInfoByName(strUsrName) info, err := P.WechatGetUserInfoByNameOnCache(strUsrName)
if err != nil { if err != nil {
log.Printf("WechatGetUserInfoByName %s failed\n", strUsrName) log.Printf("WechatGetUserInfoByName %s failed\n", strUsrName)
continue continue
@ -321,6 +380,29 @@ func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (
return List, nil return List, nil
} }
func (P *WechatDataProvider) WeChatGetContactList(pageIndex int, pageSize int) (*WeChatUserList, error) {
List := &WeChatUserList{}
List.Users = make([]WeChatUserInfo, 0)
if P.ContactList.Total <= pageIndex*pageSize {
return List, nil
}
end := (pageIndex * pageSize) + pageSize
if end > P.ContactList.Total {
end = P.ContactList.Total
}
log.Printf("P.ContactList.Total %d, start %d, end %d", P.ContactList.Total, pageIndex*pageSize, end)
var info WeChatUserInfo
for _, contact := range P.ContactList.Users[pageIndex*pageSize : end] {
info = contact.WeChatUserInfo
List.Users = append(List.Users, info)
List.Total += 1
}
return List, nil
}
func (P *WechatDataProvider) WeChatGetMessageListByTime(userName string, time int64, pageSize int, direction Message_Search_Direction) (*WeChatMessageList, error) { func (P *WechatDataProvider) WeChatGetMessageListByTime(userName string, time int64, pageSize int, direction Message_Search_Direction) (*WeChatMessageList, error) {
List := &WeChatMessageList{} List := &WeChatMessageList{}
@ -583,23 +665,23 @@ func (P *WechatDataProvider) wechatMessageExtraHandle(msg *WeChatMessage) {
} }
case 3: case 3:
if len(ext.Field2) > 0 && (msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video || msg.Type == Wechat_Message_Type_Misc) { if len(ext.Field2) > 0 && (msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video || msg.Type == Wechat_Message_Type_Misc) {
msg.ThumbPath = P.resPath + ext.Field2[len(P.SelfInfo.UserName):] msg.ThumbPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
} }
case 4: case 4:
if len(ext.Field2) > 0 { if len(ext.Field2) > 0 {
if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_File { if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_File {
msg.FileInfo.FilePath = P.resPath + ext.Field2[len(P.SelfInfo.UserName):] msg.FileInfo.FilePath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
msg.FileInfo.FileName = filepath.Base(ext.Field2) msg.FileInfo.FileName = filepath.Base(ext.Field2)
} else if msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video || msg.Type == Wechat_Message_Type_Misc { } else if msg.Type == Wechat_Message_Type_Picture || msg.Type == Wechat_Message_Type_Video || msg.Type == Wechat_Message_Type_Misc {
msg.ImagePath = P.resPath + ext.Field2[len(P.SelfInfo.UserName):] msg.ImagePath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
msg.VideoPath = P.resPath + ext.Field2[len(P.SelfInfo.UserName):] msg.VideoPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
} }
} }
} }
} }
if msg.Type == Wechat_Message_Type_Voice { if msg.Type == Wechat_Message_Type_Voice {
msg.VoicePath = fmt.Sprintf("%s\\FileStorage\\Voice\\%d.mp3", P.resPath, msg.MsgSvrId) msg.VoicePath = fmt.Sprintf("%s\\FileStorage\\Voice\\%d.mp3", P.prefixResPath, msg.MsgSvrId)
} }
} }
@ -892,7 +974,50 @@ func (P *WechatDataProvider) WechatGetUserInfoByNameOnCache(name string) (*WeCha
return pinfo, nil return pinfo, nil
} }
func WechatGetAccountInfo(resPath, accountName string) (*WeChatAccountInfo, error) { func (P *WechatDataProvider) wechatGetAllContact() (*WeChatContactList, error) {
List := &WeChatContactList{}
List.Users = make([]WeChatContact, 0)
querySql := fmt.Sprintf("select ifnull(UserName,'') as UserName,Reserved1,Reserved2,ifnull(PYInitial,'') as PYInitial,ifnull(QuanPin,'') as QuanPin,ifnull(RemarkPYInitial,'') as RemarkPYInitial,ifnull(RemarkQuanPin,'') as RemarkQuanPin from Contact desc;")
dbRows, err := P.microMsg.Query(querySql)
if err != nil {
log.Println(err)
return List, err
}
defer dbRows.Close()
var UserName string
var Reserved1, Reserved2 int
for dbRows.Next() {
var Contact WeChatContact
err = dbRows.Scan(&UserName, &Reserved1, &Reserved2, &Contact.PYInitial, &Contact.QuanPin, &Contact.RemarkPYInitial, &Contact.RemarkQuanPin)
if err != nil {
log.Println(err)
continue
}
if Reserved1 != 1 || Reserved2 != 1 {
// log.Printf("%s is not your contact", UserName)
continue
}
info, err := P.WechatGetUserInfoByNameOnCache(UserName)
if err != nil {
log.Printf("WechatGetUserInfoByName %s failed\n", UserName)
continue
}
if info.NickName == "" && info.ReMark == "" {
continue
}
Contact.WeChatUserInfo = *info
List.Users = append(List.Users, Contact)
List.Total += 1
}
return List, nil
}
func WechatGetAccountInfo(resPath, prefixRes, accountName string) (*WeChatAccountInfo, error) {
MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB
if _, err := os.Stat(MicroMsgDBPath); err != nil { if _, err := os.Stat(MicroMsgDBPath); err != nil {
log.Println("MicroMsgDBPath:", MicroMsgDBPath, err) log.Println("MicroMsgDBPath:", MicroMsgDBPath, err)
@ -934,6 +1059,11 @@ func WechatGetAccountInfo(resPath, accountName string) (*WeChatAccountInfo, erro
info.SmallHeadImgUrl = smallHeadImgUrl info.SmallHeadImgUrl = smallHeadImgUrl
info.BigHeadImgUrl = bigHeadImgUrl info.BigHeadImgUrl = bigHeadImgUrl
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", resPath, accountName)
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", prefixRes, accountName)
if _, err = os.Stat(localHeadImgPath); err == nil {
info.LocalHeadImgUrl = relativePath
}
// log.Println(info) // log.Println(info)
return info, nil return info, nil
} }