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
+45
View File
@@ -5,12 +5,22 @@ import (
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/browser"
"github.com/shirou/gopsutil/v3/disk"
"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) {
key, err := registry.OpenKey(registry.CLASSES_ROOT, fmt.Sprintf(`.%s`, fileExtension), registry.QUERY_VALUE)
if err != nil {
@@ -75,3 +85,38 @@ func OpenFileOrExplorer(filePath string, explorer bool) error {
fmt.Println("Command executed successfully")
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
}
+150 -2
View File
@@ -49,6 +49,11 @@ type wechatMediaMSG struct {
Buf []byte
}
type wechatHeadImgMSG struct {
userName string
Buf []byte
}
func GetWeChatAllInfo() *WeChatInfoList {
list := GetWeChatInfo()
@@ -73,6 +78,121 @@ func ExportWeChatAllData(info WeChatInfo, expPath string, progress chan<- string
exportWeChatBat(info, expPath, progress)
exportWeChatVideoAndFile(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) {
@@ -171,7 +291,7 @@ func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string)
return
default:
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))
progress <- totalPercentStr
time.Sleep(time.Second)
@@ -182,7 +302,7 @@ func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string)
wg.Wait()
close(quitChan)
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) {
@@ -793,3 +913,31 @@ func getPathFileNumber(targetPath string, fileSuffix string) int64 {
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")
}
+145 -15
View File
@@ -67,6 +67,8 @@ type WeChatUserInfo struct {
NickName string `json:"NickName"`
SmallHeadImgUrl string `json:"SmallHeadImgUrl"`
BigHeadImgUrl string `json:"BigHeadImgUrl"`
LocalHeadImgUrl string `json:"LocalHeadImgUrl"`
IsGroup bool `json:"IsGroup"`
}
type WeChatSession struct {
@@ -75,7 +77,7 @@ type WeChatSession struct {
Content string `json:"Content"`
UserInfo WeChatUserInfo `json:"UserInfo"`
Time uint64 `json:"Time"`
IsGroup bool `json:IsGroup`
IsGroup bool `json:"IsGroup"`
}
type WeChatSessionList struct {
@@ -144,6 +146,19 @@ type WeChatUserList struct {
Total int `json:"Total"`
}
type WeChatContact struct {
WeChatUserInfo
PYInitial string
QuanPin string
RemarkPYInitial string
RemarkQuanPin string
}
type WeChatContactList struct {
Users []WeChatContact `json:"Users"`
Total int `json:"Total"`
}
type WeChatAccountInfo struct {
AccountName string `json:"AccountName"`
AliasName string `json:"AliasName"`
@@ -151,6 +166,7 @@ type WeChatAccountInfo struct {
NickName string `json:"NickName"`
SmallHeadImgUrl string `json:"SmallHeadImgUrl"`
BigHeadImgUrl string `json:"BigHeadImgUrl"`
LocalHeadImgUrl string `json:"LocalHeadImgUrl"`
}
type wechatMsgDB struct {
@@ -161,14 +177,16 @@ type wechatMsgDB struct {
}
type WechatDataProvider struct {
resPath string
microMsg *sql.DB
resPath string
prefixResPath string
microMsg *sql.DB
msgDBs []*wechatMsgDB
userInfoMap map[string]WeChatUserInfo
userInfoMtx sync.Mutex
SelfInfo *WeChatUserInfo
SelfInfo *WeChatUserInfo
ContactList *WeChatContactList
}
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) 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.resPath = resPath
provider.prefixResPath = prefixRes
provider.msgDBs = make([]*wechatMsgDB, 0)
log.Println(resPath)
userName := filepath.Base(resPath)
MicroMsgDBPath := resPath + "\\Msg\\" + MicroMsgDB
if _, err := os.Stat(MicroMsgDBPath); err != nil {
log.Println("CreateWechatDataProvider failed", MicroMsgDBPath, err)
return provider, err
}
microMsg, err := sql.Open("sqlite3", MicroMsgDBPath)
if err != nil {
log.Printf("open db %s error: %v", MicroMsgDBPath, err)
@@ -217,11 +262,19 @@ func CreateWechatDataProvider(resPath string) (*WechatDataProvider, error) {
}
provider.userInfoMap = make(map[string]WeChatUserInfo)
provider.microMsg = microMsg
provider.SelfInfo, err = provider.WechatGetUserInfoByName(userName)
provider.SelfInfo, err = provider.WechatGetUserInfoByNameOnCache(userName)
if err != nil {
log.Printf("WechatGetUserInfoByName %s failed: %v", userName, err)
return provider, err
}
provider.ContactList, err = provider.wechatGetAllContact()
if err != nil {
log.Println("wechatGetAllContact failed", err)
return provider, err
}
sort.Sort(byName(provider.ContactList.Users))
log.Println("Contact number:", provider.ContactList.Total)
provider.userInfoMap[userName] = *provider.SelfInfo
log.Println("resPath:", provider.resPath)
return provider, nil
@@ -256,7 +309,7 @@ func (P *WechatDataProvider) WechatGetUserInfoByName(name string) (*WeChatUserIn
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
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.SmallHeadImgUrl = smallHeadImgUrl
info.BigHeadImgUrl = bigHeadImgUrl
info.IsGroup = strings.HasSuffix(UserName, "@chatroom")
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.resPath, name)
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", P.prefixResPath, name)
if _, err = os.Stat(localHeadImgPath); err == nil {
info.LocalHeadImgUrl = relativePath
}
// log.Println(info)
return info, nil
}
@@ -299,7 +358,7 @@ func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (
continue
}
if len(strContent) == 0 {
log.Printf("%s cotent nil\n", strUsrName)
// log.Printf("%s cotent nil\n", strUsrName)
continue
}
@@ -308,7 +367,7 @@ func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (
session.Content = strContent
session.Time = nTime
session.IsGroup = strings.HasSuffix(strUsrName, "@chatroom")
info, err := P.WechatGetUserInfoByName(strUsrName)
info, err := P.WechatGetUserInfoByNameOnCache(strUsrName)
if err != nil {
log.Printf("WechatGetUserInfoByName %s failed\n", strUsrName)
continue
@@ -321,6 +380,29 @@ func (P *WechatDataProvider) WeChatGetSessionList(pageIndex int, pageSize int) (
return List, nil
}
func (P *WechatDataProvider) WeChatGetContactList(pageIndex int, pageSize int) (*WeChatUserList, error) {
List := &WeChatUserList{}
List.Users = make([]WeChatUserInfo, 0)
if P.ContactList.Total <= pageIndex*pageSize {
return List, nil
}
end := (pageIndex * pageSize) + pageSize
if end > P.ContactList.Total {
end = P.ContactList.Total
}
log.Printf("P.ContactList.Total %d, start %d, end %d", P.ContactList.Total, pageIndex*pageSize, end)
var info WeChatUserInfo
for _, contact := range P.ContactList.Users[pageIndex*pageSize : end] {
info = contact.WeChatUserInfo
List.Users = append(List.Users, info)
List.Total += 1
}
return List, nil
}
func (P *WechatDataProvider) WeChatGetMessageListByTime(userName string, time int64, pageSize int, direction Message_Search_Direction) (*WeChatMessageList, error) {
List := &WeChatMessageList{}
@@ -583,23 +665,23 @@ func (P *WechatDataProvider) wechatMessageExtraHandle(msg *WeChatMessage) {
}
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) {
msg.ThumbPath = P.resPath + ext.Field2[len(P.SelfInfo.UserName):]
msg.ThumbPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
}
case 4:
if len(ext.Field2) > 0 {
if msg.Type == Wechat_Message_Type_Misc && msg.SubType == Wechat_Misc_Message_File {
msg.FileInfo.FilePath = P.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)
} 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.VideoPath = P.resPath + ext.Field2[len(P.SelfInfo.UserName):]
msg.ImagePath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
msg.VideoPath = P.prefixResPath + ext.Field2[len(P.SelfInfo.UserName):]
}
}
}
}
if msg.Type == Wechat_Message_Type_Voice {
msg.VoicePath = fmt.Sprintf("%s\\FileStorage\\Voice\\%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
}
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
if _, err := os.Stat(MicroMsgDBPath); err != nil {
log.Println("MicroMsgDBPath:", MicroMsgDBPath, err)
@@ -934,6 +1059,11 @@ func WechatGetAccountInfo(resPath, accountName string) (*WeChatAccountInfo, erro
info.SmallHeadImgUrl = smallHeadImgUrl
info.BigHeadImgUrl = bigHeadImgUrl
localHeadImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", resPath, accountName)
relativePath := fmt.Sprintf("%s\\FileStorage\\HeadImage\\%s.headimg", prefixRes, accountName)
if _, err = os.Stat(localHeadImgPath); err == nil {
info.LocalHeadImgUrl = relativePath
}
// log.Println(info)
return info, nil
}