mirror of
https://github.com/teest114514/chatlog_alpha.git
synced 2026-03-20 17:07:50 +08:00
Enhance auto decryption and UI message preview
Added pre-check and error handler for auto decryption: the system now tests decryption before enabling auto mode and stops with a popup if errors occur during operation. The footer UI now displays a real-time preview of the latest message, including sender, time, and content summary, with improved fallback for missing nicknames. Also fixed a bug where batch decryption incorrectly reported success when all files failed.
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(xargs:*)",
|
||||
"Bash(tree:*)",
|
||||
"Bash(go build:*)",
|
||||
"Bash(go run:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
12
README.md
12
README.md
@@ -4,6 +4,8 @@
|
||||
|
||||
未经修改的源代码在 [main 分支](https://github.com/CJYKK/chatlog_backup/tree/main),本人不对代码中的任何内容负责。
|
||||
|
||||
感谢 [wx_key](https://github.com/ycccccccy/wx_key) 项目提供的解密源码
|
||||
|
||||
目前测试成功微信版本:4.1.5.30
|
||||
|
||||
## 项目概述
|
||||
@@ -95,6 +97,16 @@
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2025年12月16日
|
||||
- **自动解密机制优化**:
|
||||
- 增加开启前预检:开启自动解密前会自动运行一次解密测试,失败则禁止开启。
|
||||
- 增加故障自动熔断:运行过程中若解密失败(如密钥失效),会自动停止服务并弹窗提示,防止错误循环。
|
||||
- **UI 交互增强**:
|
||||
- 底部状态栏增加最新消息预览:实时显示最新一条消息的发送人、时间及内容摘要。
|
||||
- 优化发送人显示逻辑:昵称缺失时自动降级显示账号 ID。
|
||||
- **修复**:
|
||||
- 修复批量解密时即便所有文件失败仍提示成功的 Bug。
|
||||
|
||||
### 2025年12月15日
|
||||
- **重构密钥获取逻辑**:实现 Data Key (DLL) 和 Image Key (原生扫描) 的职责分离与并行执行。
|
||||
- **优化图片密钥获取**:适配 Dart 版逻辑,支持 60 秒轮询等待,允许用户后置操作(打开图片)。
|
||||
|
||||
@@ -188,6 +188,15 @@ func (a *App) refresh() {
|
||||
a.infoBar.UpdateAutoDecrypt("[未开启]")
|
||||
}
|
||||
|
||||
// Update latest message in footer
|
||||
if session, err := a.m.GetLatestSession(); err == nil && session != nil {
|
||||
sender := session.NickName
|
||||
if sender == "" {
|
||||
sender = session.UserName
|
||||
}
|
||||
a.footer.UpdateLatestMessage(sender, session.NTime.Format("15:04:05"), session.Content)
|
||||
}
|
||||
|
||||
a.Draw()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/sjzar/chatlog/internal/chatlog/database"
|
||||
"github.com/sjzar/chatlog/internal/chatlog/http"
|
||||
"github.com/sjzar/chatlog/internal/chatlog/wechat"
|
||||
"github.com/sjzar/chatlog/internal/model"
|
||||
iwechat "github.com/sjzar/chatlog/internal/wechat"
|
||||
"github.com/sjzar/chatlog/pkg/config"
|
||||
"github.com/sjzar/chatlog/pkg/util"
|
||||
@@ -296,10 +297,29 @@ func (m *Manager) StartAutoDecrypt() error {
|
||||
if m.ctx.DataKey == "" || m.ctx.DataDir == "" {
|
||||
return fmt.Errorf("请先获取密钥")
|
||||
}
|
||||
|
||||
// 尝试运行一次解密,验证环境和密钥是否正常
|
||||
// 如果解密失败,说明配置或环境有问题,不应开启自动解密
|
||||
if err := m.DecryptDBFiles(); err != nil {
|
||||
return fmt.Errorf("初始解密失败,无法开启自动解密: %w", err)
|
||||
}
|
||||
|
||||
if m.ctx.WorkDir == "" {
|
||||
return fmt.Errorf("请先执行解密数据")
|
||||
}
|
||||
|
||||
m.wechat.SetAutoDecryptErrorHandler(func(err error) {
|
||||
log.Error().Err(err).Msg("自动解密失败,停止服务")
|
||||
m.StopAutoDecrypt()
|
||||
|
||||
if m.app != nil {
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
m.app.showError(fmt.Errorf("自动解密失败,已停止服务: %v", err))
|
||||
m.app.updateMenuItemsState()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if err := m.wechat.StartAutoDecrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -334,6 +354,20 @@ func (m *Manager) RefreshSession() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetLatestSession() (*model.Session, error) {
|
||||
if m.db == nil || m.db.GetDB() == nil {
|
||||
return nil, nil
|
||||
}
|
||||
resp, err := m.db.GetSessions("", 1, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.Items) > 0 {
|
||||
return resp.Items[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *Manager) CommandKey(configPath string, pid int, force bool, showXorKey bool) (string, error) {
|
||||
|
||||
var err error
|
||||
|
||||
@@ -29,6 +29,7 @@ type Service struct {
|
||||
pendingActions map[string]bool
|
||||
mutex sync.Mutex
|
||||
fm *filemonitor.FileMonitor
|
||||
errorHandler func(error)
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
@@ -47,6 +48,11 @@ func NewService(conf Config) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
// SetAutoDecryptErrorHandler sets the callback for auto decryption errors
|
||||
func (s *Service) SetAutoDecryptErrorHandler(handler func(error)) {
|
||||
s.errorHandler = handler
|
||||
}
|
||||
|
||||
// GetWeChatInstances returns all running WeChat instances
|
||||
func (s *Service) GetWeChatInstances() []*wechat.Account {
|
||||
wechat.Load()
|
||||
@@ -145,7 +151,11 @@ func (s *Service) waitAndProcess(dbFile string) {
|
||||
s.mutex.Unlock()
|
||||
|
||||
log.Debug().Msgf("Processing file: %s", dbFile)
|
||||
s.DecryptDBFile(dbFile)
|
||||
if err := s.DecryptDBFile(dbFile); err != nil {
|
||||
if s.errorHandler != nil {
|
||||
s.errorHandler(err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
@@ -203,12 +213,21 @@ func (s *Service) DecryptDBFiles() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
failCount := 0
|
||||
|
||||
for _, dbFile := range dbFiles {
|
||||
if err := s.DecryptDBFile(dbFile); err != nil {
|
||||
log.Debug().Msgf("DecryptDBFile %s failed: %v", dbFile, err)
|
||||
lastErr = err
|
||||
failCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(dbFiles) > 0 && failCount == len(dbFiles) {
|
||||
return fmt.Errorf("decryption failed for all %d files, last error: %w", len(dbFiles), lastErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,17 +15,19 @@ const (
|
||||
|
||||
type Footer struct {
|
||||
*tview.Flex
|
||||
title string
|
||||
copyRight *tview.TextView
|
||||
help *tview.TextView
|
||||
title string
|
||||
copyRight *tview.TextView
|
||||
help *tview.TextView
|
||||
latestMessage *tview.TextView
|
||||
}
|
||||
|
||||
func New() *Footer {
|
||||
footer := &Footer{
|
||||
Flex: tview.NewFlex(),
|
||||
title: Title,
|
||||
copyRight: tview.NewTextView(),
|
||||
help: tview.NewTextView(),
|
||||
Flex: tview.NewFlex(),
|
||||
title: Title,
|
||||
copyRight: tview.NewTextView(),
|
||||
help: tview.NewTextView(),
|
||||
latestMessage: tview.NewTextView(),
|
||||
}
|
||||
|
||||
footer.copyRight.
|
||||
@@ -39,7 +41,7 @@ func New() *Footer {
|
||||
footer.help.
|
||||
SetDynamicColors(true).
|
||||
SetWrap(true).
|
||||
SetTextAlign(tview.AlignRight)
|
||||
SetTextAlign(tview.AlignCenter)
|
||||
footer.help.
|
||||
SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
|
||||
|
||||
@@ -52,9 +54,17 @@ func New() *Footer {
|
||||
style.GetColorHex(style.MenuBgColor), style.GetColorHex(style.PageHeaderFgColor),
|
||||
)
|
||||
|
||||
footer.latestMessage.
|
||||
SetDynamicColors(true).
|
||||
SetWrap(false).
|
||||
SetTextAlign(tview.AlignRight)
|
||||
footer.latestMessage.
|
||||
SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
|
||||
|
||||
footer.
|
||||
AddItem(footer.copyRight, 0, 1, false).
|
||||
AddItem(footer.help, 0, 1, false)
|
||||
AddItem(footer.help, 0, 2, false).
|
||||
AddItem(footer.latestMessage, 0, 2, false)
|
||||
|
||||
return footer
|
||||
}
|
||||
@@ -66,3 +76,16 @@ func (f *Footer) SetCopyRight(text string) {
|
||||
func (f *Footer) SetHelp(text string) {
|
||||
f.help.SetText(text)
|
||||
}
|
||||
|
||||
func (f *Footer) UpdateLatestMessage(sender, time, content string) {
|
||||
runes := []rune(content)
|
||||
if len(runes) > 15 {
|
||||
content = string(runes[:15]) + "..."
|
||||
}
|
||||
// [颜色]发送人 [颜色]时间 [颜色]内容
|
||||
text := fmt.Sprintf("[%s]%s [%s]%s [%s]%s",
|
||||
style.GetColorHex(style.PageHeaderFgColor), sender,
|
||||
style.GetColorHex(style.InfoBarItemFgColor), time,
|
||||
style.GetColorHex(style.FgColor), content)
|
||||
f.latestMessage.SetText(text)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user