This commit is contained in:
Shen Junzheng
2025-03-21 21:45:08 +08:00
parent 78cce92ce3
commit 80c7e67106
86 changed files with 7061 additions and 2316 deletions

View File

@@ -0,0 +1,164 @@
package darwin
import (
"fmt"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/shirou/gopsutil/v4/process"
log "github.com/sirupsen/logrus"
"github.com/sjzar/chatlog/internal/wechat/model"
"github.com/sjzar/chatlog/pkg/appver"
)
const (
V3ProcessName = "WeChat"
V4ProcessName = "Weixin"
V3DBFile = "Message/msg_0.db"
V4DBFile = "db_storage/message/message_0.db"
)
// Detector 实现 macOS 平台的进程检测器
type Detector struct{}
// NewDetector 创建一个新的 macOS 检测器
func NewDetector() *Detector {
return &Detector{}
}
// FindProcesses 查找所有微信进程并返回它们的信息
func (d *Detector) FindProcesses() ([]*model.Process, error) {
processes, err := process.Processes()
if err != nil {
log.Errorf("获取进程列表失败: %v", err)
return nil, err
}
var result []*model.Process
for _, p := range processes {
name, err := p.Name()
if err != nil || (name != V3ProcessName && name != V4ProcessName) {
continue
}
// 获取进程信息
procInfo, err := d.getProcessInfo(p)
if err != nil {
log.Errorf("获取进程 %d 的信息失败: %v", p.Pid, err)
continue
}
result = append(result, procInfo)
}
return result, nil
}
// getProcessInfo 获取微信进程的详细信息
func (d *Detector) getProcessInfo(p *process.Process) (*model.Process, error) {
procInfo := &model.Process{
PID: uint32(p.Pid),
Status: model.StatusOffline,
Platform: model.PlatformMacOS,
}
// 获取可执行文件路径
exePath, err := p.Exe()
if err != nil {
log.Error(err)
return nil, err
}
procInfo.ExePath = exePath
// 获取版本信息
// 注意macOS 的版本获取方式可能与 Windows 不同
versionInfo, err := appver.New(exePath)
if err != nil {
log.Error(err)
procInfo.Version = 3
procInfo.FullVersion = "3.0.0"
} else {
procInfo.Version = versionInfo.Version
procInfo.FullVersion = versionInfo.FullVersion
}
// 初始化附加信息(数据目录、账户名)
if err := d.initializeProcessInfo(p, procInfo); err != nil {
log.Errorf("初始化进程信息失败: %v", err)
// 即使初始化失败也返回部分信息
}
return procInfo, nil
}
// initializeProcessInfo 获取进程的数据目录和账户名
func (d *Detector) initializeProcessInfo(p *process.Process, info *model.Process) error {
// 使用 lsof 命令获取进程打开的文件
files, err := d.getOpenFiles(int(p.Pid))
if err != nil {
log.Error("获取打开文件列表失败: ", err)
return err
}
dbPath := V3DBFile
if info.Version == 4 {
dbPath = V4DBFile
}
for _, filePath := range files {
if strings.Contains(filePath, dbPath) {
parts := strings.Split(filePath, string(filepath.Separator))
if len(parts) < 4 {
log.Debug("无效的文件路径格式: " + filePath)
continue
}
// v3:
// /Users/sarv/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/<id>/Message/msg_0.db
// v4:
// /Users/sarv/Library/Containers/com.tencent.xWeChat/Data/Documents/xwechat_files/<id>/db_storage/message/message_0.db
info.Status = model.StatusOnline
if info.Version == 4 {
info.DataDir = strings.Join(parts[:len(parts)-3], string(filepath.Separator))
info.AccountName = parts[len(parts)-4]
} else {
info.DataDir = strings.Join(parts[:len(parts)-2], string(filepath.Separator))
info.AccountName = parts[len(parts)-3]
}
return nil
}
}
return nil
}
// getOpenFiles 使用 lsof 命令获取进程打开的文件列表
func (d *Detector) getOpenFiles(pid int) ([]string, error) {
// 执行 lsof -p <pid> 命令,使用 -F n 选项只输出文件名
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid), "-F", "n")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("执行 lsof 命令失败: %v", err)
}
// 解析 lsof -F n 输出
// 格式为: n/path/to/file
lines := strings.Split(string(output), "\n")
var files []string
for _, line := range lines {
if strings.HasPrefix(line, "n") {
// 移除前缀 'n' 获取文件路径
filePath := line[1:]
if filePath != "" {
files = append(files, filePath)
}
}
}
return files, nil
}

View File

@@ -0,0 +1,36 @@
package process
import (
"github.com/sjzar/chatlog/internal/wechat/model"
"github.com/sjzar/chatlog/internal/wechat/process/darwin"
"github.com/sjzar/chatlog/internal/wechat/process/windows"
)
type Detector interface {
FindProcesses() ([]*model.Process, error)
}
// NewDetector 创建适合当前平台的检测器
func NewDetector(platform string) Detector {
// 根据平台返回对应的实现
switch platform {
case "windows":
return windows.NewDetector()
case "darwin":
return darwin.NewDetector()
default:
// 默认返回一个空实现
return &nullDetector{}
}
}
// nullDetector 空实现
type nullDetector struct{}
func (d *nullDetector) FindProcesses() ([]*model.Process, error) {
return nil, nil
}
func (d *nullDetector) GetProcessInfo(pid uint32) (*model.Process, error) {
return nil, nil
}

View File

@@ -0,0 +1,101 @@
package windows
import (
"strings"
"github.com/shirou/gopsutil/v4/process"
log "github.com/sirupsen/logrus"
"github.com/sjzar/chatlog/internal/wechat/model"
"github.com/sjzar/chatlog/pkg/appver"
)
const (
V3ProcessName = "WeChat"
V4ProcessName = "Weixin"
V3DBFile = "Msg\\Misc.db"
V4DBFile = "db_storage\\message\\message_0.db"
)
// Detector 实现 Windows 平台的进程检测器
type Detector struct{}
// NewDetector 创建一个新的 Windows 检测器
func NewDetector() *Detector {
return &Detector{}
}
// FindProcesses 查找所有微信进程并返回它们的信息
func (d *Detector) FindProcesses() ([]*model.Process, error) {
processes, err := process.Processes()
if err != nil {
log.Errorf("获取进程列表失败: %v", err)
return nil, err
}
var result []*model.Process
for _, p := range processes {
name, err := p.Name()
name = strings.TrimSuffix(name, ".exe")
if err != nil || (name != V3ProcessName && name != V4ProcessName) {
continue
}
// v4 存在同名进程,需要继续判断 cmdline
if name == V4ProcessName {
cmdline, err := p.Cmdline()
if err != nil {
log.Error(err)
continue
}
if strings.Contains(cmdline, "--") {
continue
}
}
// 获取进程信息
procInfo, err := d.getProcessInfo(p)
if err != nil {
log.Errorf("获取进程 %d 的信息失败: %v", p.Pid, err)
continue
}
result = append(result, procInfo)
}
return result, nil
}
// getProcessInfo 获取微信进程的详细信息
func (d *Detector) getProcessInfo(p *process.Process) (*model.Process, error) {
procInfo := &model.Process{
PID: uint32(p.Pid),
Status: model.StatusOffline,
Platform: model.PlatformWindows,
}
// 获取可执行文件路径
exePath, err := p.Exe()
if err != nil {
log.Error(err)
return nil, err
}
procInfo.ExePath = exePath
// 获取版本信息
versionInfo, err := appver.New(exePath)
if err != nil {
log.Error(err)
return nil, err
}
procInfo.Version = versionInfo.Version
procInfo.FullVersion = versionInfo.FullVersion
// 初始化附加信息(数据目录、账户名)
if err := initializeProcessInfo(p, procInfo); err != nil {
log.Errorf("初始化进程信息失败: %v", err)
// 即使初始化失败也返回部分信息
}
return procInfo, nil
}

View File

@@ -0,0 +1,12 @@
//go:build !windows
package windows
import (
"github.com/shirou/gopsutil/v4/process"
"github.com/sjzar/chatlog/internal/wechat/model"
)
func initializeProcessInfo(p *process.Process, info *model.Process) error {
return nil
}

View File

@@ -0,0 +1,48 @@
package windows
import (
"path/filepath"
"strings"
"github.com/shirou/gopsutil/v4/process"
log "github.com/sirupsen/logrus"
"github.com/sjzar/chatlog/internal/wechat/model"
)
// initializeProcessInfo 获取进程的数据目录和账户名
func initializeProcessInfo(p *process.Process, info *model.Process) error {
files, err := p.OpenFiles()
if err != nil {
log.Error("获取打开文件列表失败: ", err)
return err
}
dbPath := V3DBFile
if info.Version == 4 {
dbPath = V4DBFile
}
for _, f := range files {
if strings.HasSuffix(f.Path, dbPath) {
filePath := f.Path[4:] // 移除 "\\?\" 前缀
parts := strings.Split(filePath, string(filepath.Separator))
if len(parts) < 4 {
log.Debug("无效的文件路径格式: " + filePath)
continue
}
info.Status = model.StatusOnline
if info.Version == 4 {
info.DataDir = strings.Join(parts[:len(parts)-3], string(filepath.Separator))
info.AccountName = parts[len(parts)-4]
} else {
info.DataDir = strings.Join(parts[:len(parts)-2], string(filepath.Separator))
info.AccountName = parts[len(parts)-3]
}
return nil
}
}
return nil
}