mirror of
https://github.com/teest114514/chatlog_alpha.git
synced 2026-03-22 02:42:26 +08:00
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.
900 lines
23 KiB
Go
900 lines
23 KiB
Go
package chatlog
|
||
|
||
import (
|
||
"fmt"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"github.com/rs/zerolog/log"
|
||
"github.com/sjzar/chatlog/internal/chatlog/ctx"
|
||
"github.com/sjzar/chatlog/internal/ui/footer"
|
||
"github.com/sjzar/chatlog/internal/ui/form"
|
||
"github.com/sjzar/chatlog/internal/ui/help"
|
||
"github.com/sjzar/chatlog/internal/ui/infobar"
|
||
"github.com/sjzar/chatlog/internal/ui/menu"
|
||
"github.com/sjzar/chatlog/internal/wechat"
|
||
|
||
"github.com/gdamore/tcell/v2"
|
||
"github.com/rivo/tview"
|
||
)
|
||
|
||
const (
|
||
RefreshInterval = 1000 * time.Millisecond
|
||
)
|
||
|
||
type App struct {
|
||
*tview.Application
|
||
|
||
ctx *ctx.Context
|
||
m *Manager
|
||
stopRefresh chan struct{}
|
||
|
||
// page
|
||
mainPages *tview.Pages
|
||
infoBar *infobar.InfoBar
|
||
tabPages *tview.Pages
|
||
footer *footer.Footer
|
||
|
||
// tab
|
||
menu *menu.Menu
|
||
help *help.Help
|
||
activeTab int
|
||
tabCount int
|
||
}
|
||
|
||
func NewApp(ctx *ctx.Context, m *Manager) *App {
|
||
app := &App{
|
||
ctx: ctx,
|
||
m: m,
|
||
Application: tview.NewApplication(),
|
||
mainPages: tview.NewPages(),
|
||
infoBar: infobar.New(),
|
||
tabPages: tview.NewPages(),
|
||
footer: footer.New(),
|
||
menu: menu.New("主菜单"),
|
||
help: help.New(),
|
||
}
|
||
|
||
app.initMenu()
|
||
|
||
app.updateMenuItemsState()
|
||
|
||
return app
|
||
}
|
||
|
||
func (a *App) Run() error {
|
||
|
||
flex := tview.NewFlex().
|
||
SetDirection(tview.FlexRow).
|
||
AddItem(a.infoBar, infobar.InfoBarViewHeight, 0, false).
|
||
AddItem(a.tabPages, 0, 1, true).
|
||
AddItem(a.footer, 1, 1, false)
|
||
|
||
a.mainPages.AddPage("main", flex, true, true)
|
||
|
||
a.tabPages.
|
||
AddPage("0", a.menu, true, true).
|
||
AddPage("1", a.help, true, false)
|
||
a.tabCount = 2
|
||
|
||
a.SetInputCapture(a.inputCapture)
|
||
|
||
go a.refresh()
|
||
|
||
if err := a.SetRoot(a.mainPages, true).EnableMouse(false).Run(); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (a *App) Stop() {
|
||
// 添加一个通道用于停止刷新 goroutine
|
||
if a.stopRefresh != nil {
|
||
close(a.stopRefresh)
|
||
}
|
||
a.Application.Stop()
|
||
}
|
||
|
||
func (a *App) updateMenuItemsState() {
|
||
// 查找并更新自动解密菜单项
|
||
for _, item := range a.menu.GetItems() {
|
||
// 更新自动解密菜单项
|
||
if item.Index == 6 {
|
||
if a.ctx.AutoDecrypt {
|
||
item.Name = "停止自动解密"
|
||
item.Description = "停止监控数据目录更新,不再自动解密新增数据"
|
||
} else {
|
||
item.Name = "开启自动解密"
|
||
item.Description = "监控数据目录更新,自动解密新增数据"
|
||
}
|
||
}
|
||
|
||
// 更新HTTP服务菜单项
|
||
if item.Index == 5 {
|
||
if a.ctx.HTTPEnabled {
|
||
item.Name = "停止 HTTP 服务"
|
||
item.Description = "停止本地 HTTP & MCP 服务器"
|
||
} else {
|
||
item.Name = "启动 HTTP 服务"
|
||
item.Description = "启动本地 HTTP & MCP 服务器"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (a *App) switchTab(step int) {
|
||
index := (a.activeTab + step) % a.tabCount
|
||
if index < 0 {
|
||
index = a.tabCount - 1
|
||
}
|
||
a.activeTab = index
|
||
a.tabPages.SwitchToPage(fmt.Sprint(a.activeTab))
|
||
}
|
||
|
||
func (a *App) refresh() {
|
||
tick := time.NewTicker(RefreshInterval)
|
||
defer tick.Stop()
|
||
|
||
for {
|
||
select {
|
||
case <-a.stopRefresh:
|
||
return
|
||
case <-tick.C:
|
||
// 如果当前账号为空,尝试查找微信进程
|
||
if a.ctx.Current == nil {
|
||
// 获取微信实例
|
||
instances := a.m.wechat.GetWeChatInstances()
|
||
if len(instances) > 0 {
|
||
// 找到微信进程,设置第一个为当前账号
|
||
a.ctx.SwitchCurrent(instances[0])
|
||
log.Info().Msgf("检测到微信进程,PID: %d,已设置为当前账号", instances[0].PID)
|
||
}
|
||
}
|
||
|
||
// 刷新当前账号状态(如果存在)
|
||
if a.ctx.Current != nil {
|
||
originalName := a.ctx.Current.Name
|
||
a.ctx.Current.RefreshStatus()
|
||
if a.ctx.Current.Name != originalName {
|
||
a.ctx.SwitchCurrent(a.ctx.Current)
|
||
} else {
|
||
a.ctx.Refresh()
|
||
}
|
||
}
|
||
|
||
if a.ctx.AutoDecrypt || a.ctx.HTTPEnabled {
|
||
a.m.RefreshSession()
|
||
}
|
||
a.infoBar.UpdateAccount(a.ctx.Account)
|
||
a.infoBar.UpdateBasicInfo(a.ctx.PID, a.ctx.FullVersion, a.ctx.ExePath)
|
||
a.infoBar.UpdateStatus(a.ctx.Status)
|
||
a.infoBar.UpdateDataKey(a.ctx.DataKey)
|
||
a.infoBar.UpdateImageKey(a.ctx.ImgKey)
|
||
a.infoBar.UpdatePlatform(a.ctx.Platform)
|
||
a.infoBar.UpdateDataUsageDir(a.ctx.DataUsage, a.ctx.DataDir)
|
||
a.infoBar.UpdateWorkUsageDir(a.ctx.WorkUsage, a.ctx.WorkDir)
|
||
if a.ctx.LastSession.Unix() > 1000000000 {
|
||
a.infoBar.UpdateSession(a.ctx.LastSession.Format("2006-01-02 15:04:05"))
|
||
}
|
||
if a.ctx.HTTPEnabled {
|
||
a.infoBar.UpdateHTTPServer(fmt.Sprintf("[green][已启动][white] [%s]", a.ctx.HTTPAddr))
|
||
} else {
|
||
a.infoBar.UpdateHTTPServer("[未启动]")
|
||
}
|
||
if a.ctx.AutoDecrypt {
|
||
a.infoBar.UpdateAutoDecrypt("[green][已开启][white]")
|
||
} else {
|
||
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()
|
||
}
|
||
}
|
||
}
|
||
|
||
func (a *App) inputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||
|
||
// 如果当前页面不是主页面,ESC 键返回主页面
|
||
if a.mainPages.HasPage("submenu") && event.Key() == tcell.KeyEscape {
|
||
a.mainPages.RemovePage("submenu")
|
||
a.mainPages.SwitchToPage("main")
|
||
return nil
|
||
}
|
||
|
||
if a.tabPages.HasFocus() {
|
||
switch event.Key() {
|
||
case tcell.KeyLeft:
|
||
a.switchTab(-1)
|
||
return nil
|
||
case tcell.KeyRight:
|
||
a.switchTab(1)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
switch event.Key() {
|
||
case tcell.KeyCtrlC:
|
||
a.Stop()
|
||
}
|
||
|
||
return event
|
||
}
|
||
|
||
func (a *App) initMenu() {
|
||
getDataKey := &menu.Item{
|
||
Index: 2,
|
||
Name: "获取图片密钥",
|
||
Description: "扫描内存获取图片密钥(需微信V4)",
|
||
Selected: func(i *menu.Item) {
|
||
modal := tview.NewModal()
|
||
modal.SetText("正在扫描内存获取图片密钥...\n请确保微信已登录并浏览过图片")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
go func() {
|
||
err := a.m.GetImageKey()
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
// 解密失败
|
||
modal.SetText("获取图片密钥失败: " + err.Error())
|
||
} else {
|
||
// 解密成功
|
||
modal.SetText("获取图片密钥成功")
|
||
}
|
||
|
||
// 添加确认按钮
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
},
|
||
}
|
||
|
||
restartAndGetDataKey := &menu.Item{
|
||
Index: 3,
|
||
Name: "重启并获取密钥",
|
||
Description: "结束当前微信进程,重启后获取密钥",
|
||
Selected: func(i *menu.Item) {
|
||
modal := tview.NewModal().SetText("正在重启微信并获取密钥...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
go func() {
|
||
err := a.m.RestartAndGetDataKey()
|
||
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
modal.SetText("操作失败: " + err.Error())
|
||
} else {
|
||
modal.SetText("操作成功,请检查密钥是否已更新")
|
||
}
|
||
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
},
|
||
}
|
||
|
||
decryptData := &menu.Item{
|
||
Index: 4,
|
||
Name: "解密数据",
|
||
Description: "解密数据文件",
|
||
Selected: func(i *menu.Item) {
|
||
// 创建一个没有按钮的模态框,显示"解密中..."
|
||
modal := tview.NewModal().
|
||
SetText("解密中...")
|
||
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台执行解密操作
|
||
go func() {
|
||
// 执行解密
|
||
err := a.m.DecryptDBFiles()
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
// 解密失败
|
||
modal.SetText("解密失败: " + err.Error())
|
||
} else {
|
||
// 解密成功
|
||
modal.SetText("解密数据成功")
|
||
}
|
||
|
||
// 添加确认按钮
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
},
|
||
}
|
||
|
||
httpServer := &menu.Item{
|
||
Index: 5,
|
||
Name: "启动 HTTP 服务",
|
||
Description: "启动本地 HTTP & MCP 服务器",
|
||
Selected: func(i *menu.Item) {
|
||
modal := tview.NewModal()
|
||
|
||
// 根据当前服务状态执行不同操作
|
||
if !a.ctx.HTTPEnabled {
|
||
// HTTP 服务未启动,启动服务
|
||
modal.SetText("正在启动 HTTP 服务...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台启动服务
|
||
go func() {
|
||
err := a.m.StartService()
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
// 启动失败
|
||
modal.SetText("启动 HTTP 服务失败: " + err.Error())
|
||
} else {
|
||
// 启动成功
|
||
modal.SetText("已启动 HTTP 服务")
|
||
}
|
||
|
||
// 更改菜单项名称
|
||
a.updateMenuItemsState()
|
||
|
||
// 添加确认按钮
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
} else {
|
||
// HTTP 服务已启动,停止服务
|
||
modal.SetText("正在停止 HTTP 服务...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台停止服务
|
||
go func() {
|
||
err := a.m.StopService()
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
// 停止失败
|
||
modal.SetText("停止 HTTP 服务失败: " + err.Error())
|
||
} else {
|
||
// 停止成功
|
||
modal.SetText("已停止 HTTP 服务")
|
||
}
|
||
|
||
// 更改菜单项名称
|
||
a.updateMenuItemsState()
|
||
|
||
// 添加确认按钮
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
}
|
||
},
|
||
}
|
||
|
||
autoDecrypt := &menu.Item{
|
||
Index: 6,
|
||
Name: "开启自动解密",
|
||
Description: "自动解密新增的数据文件",
|
||
Selected: func(i *menu.Item) {
|
||
modal := tview.NewModal()
|
||
|
||
// 根据当前自动解密状态执行不同操作
|
||
if !a.ctx.AutoDecrypt {
|
||
// 自动解密未开启,开启自动解密
|
||
modal.SetText("正在开启自动解密...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台开启自动解密
|
||
go func() {
|
||
err := a.m.StartAutoDecrypt()
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
// 开启失败
|
||
modal.SetText("开启自动解密失败: " + err.Error())
|
||
} else {
|
||
// 开启成功
|
||
if a.ctx.Version == 3 {
|
||
modal.SetText("已开启自动解密\n3.x版本数据文件更新不及时,有低延迟需求请使用4.0版本")
|
||
} else {
|
||
modal.SetText("已开启自动解密")
|
||
}
|
||
}
|
||
|
||
// 更改菜单项名称
|
||
a.updateMenuItemsState()
|
||
|
||
// 添加确认按钮
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
} else {
|
||
// 自动解密已开启,停止自动解密
|
||
modal.SetText("正在停止自动解密...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台停止自动解密
|
||
go func() {
|
||
err := a.m.StopAutoDecrypt()
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
if err != nil {
|
||
// 停止失败
|
||
modal.SetText("停止自动解密失败: " + err.Error())
|
||
} else {
|
||
// 停止成功
|
||
modal.SetText("已停止自动解密")
|
||
}
|
||
|
||
// 更改菜单项名称
|
||
a.updateMenuItemsState()
|
||
|
||
// 添加确认按钮
|
||
modal.AddButtons([]string{"OK"})
|
||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
a.SetFocus(modal)
|
||
})
|
||
}()
|
||
}
|
||
},
|
||
}
|
||
|
||
setting := &menu.Item{
|
||
Index: 7,
|
||
Name: "设置",
|
||
Description: "设置应用程序选项",
|
||
Selected: a.settingSelected,
|
||
}
|
||
|
||
selectAccount := &menu.Item{
|
||
Index: 8,
|
||
Name: "切换账号",
|
||
Description: "切换当前操作的账号,可以选择进程或历史账号",
|
||
Selected: a.selectAccountSelected,
|
||
}
|
||
|
||
a.menu.AddItem(getDataKey)
|
||
a.menu.AddItem(restartAndGetDataKey)
|
||
a.menu.AddItem(decryptData)
|
||
a.menu.AddItem(httpServer)
|
||
a.menu.AddItem(autoDecrypt)
|
||
a.menu.AddItem(setting)
|
||
a.menu.AddItem(selectAccount)
|
||
|
||
a.menu.AddItem(&menu.Item{
|
||
Index: 9,
|
||
Name: "退出",
|
||
Description: "退出程序",
|
||
Selected: func(i *menu.Item) {
|
||
a.Stop()
|
||
},
|
||
})
|
||
}
|
||
|
||
// settingItem 表示一个设置项
|
||
type settingItem struct {
|
||
name string
|
||
description string
|
||
action func()
|
||
}
|
||
|
||
func (a *App) settingSelected(i *menu.Item) {
|
||
|
||
settings := []settingItem{
|
||
{
|
||
name: "设置 HTTP 服务地址",
|
||
description: "配置 HTTP 服务监听的地址",
|
||
action: a.settingHTTPPort,
|
||
},
|
||
{
|
||
name: "设置工作目录",
|
||
description: "配置数据解密后的存储目录",
|
||
action: a.settingWorkDir,
|
||
},
|
||
{
|
||
name: "设置数据密钥",
|
||
description: "配置数据解密密钥",
|
||
action: a.settingDataKey,
|
||
},
|
||
{
|
||
name: "设置图片密钥",
|
||
description: "配置图片解密密钥",
|
||
action: a.settingImgKey,
|
||
},
|
||
{
|
||
name: "设置数据目录",
|
||
description: "配置微信数据文件所在目录",
|
||
action: a.settingDataDir,
|
||
},
|
||
}
|
||
|
||
subMenu := menu.NewSubMenu("设置")
|
||
for idx, setting := range settings {
|
||
item := &menu.Item{
|
||
Index: idx + 1,
|
||
Name: setting.name,
|
||
Description: setting.description,
|
||
Selected: func(action func()) func(*menu.Item) {
|
||
return func(*menu.Item) {
|
||
action()
|
||
}
|
||
}(setting.action),
|
||
}
|
||
subMenu.AddItem(item)
|
||
}
|
||
|
||
a.mainPages.AddPage("submenu", subMenu, true, true)
|
||
a.SetFocus(subMenu)
|
||
}
|
||
|
||
// settingHTTPPort 设置 HTTP 端口
|
||
func (a *App) settingHTTPPort() {
|
||
// 使用我们的自定义表单组件
|
||
formView := form.NewForm("设置 HTTP 地址")
|
||
|
||
// 临时存储用户输入的值
|
||
tempHTTPAddr := a.ctx.HTTPAddr
|
||
|
||
// 添加输入字段 - 不再直接设置HTTP地址,而是更新临时变量
|
||
formView.AddInputField("地址", tempHTTPAddr, 0, nil, func(text string) {
|
||
tempHTTPAddr = text // 只更新临时变量
|
||
})
|
||
|
||
// 添加按钮 - 点击保存时才设置HTTP地址
|
||
formView.AddButton("保存", func() {
|
||
a.m.SetHTTPAddr(tempHTTPAddr) // 在这里设置HTTP地址
|
||
a.mainPages.RemovePage("submenu2")
|
||
a.showInfo("HTTP 地址已设置为 " + a.ctx.HTTPAddr)
|
||
})
|
||
|
||
formView.AddButton("取消", func() {
|
||
a.mainPages.RemovePage("submenu2")
|
||
})
|
||
|
||
a.mainPages.AddPage("submenu2", formView, true, true)
|
||
a.SetFocus(formView)
|
||
}
|
||
|
||
// settingWorkDir 设置工作目录
|
||
func (a *App) settingWorkDir() {
|
||
// 使用我们的自定义表单组件
|
||
formView := form.NewForm("设置工作目录")
|
||
|
||
// 临时存储用户输入的值
|
||
tempWorkDir := a.ctx.WorkDir
|
||
|
||
// 添加输入字段 - 不再直接设置工作目录,而是更新临时变量
|
||
formView.AddInputField("工作目录", tempWorkDir, 0, nil, func(text string) {
|
||
tempWorkDir = text // 只更新临时变量
|
||
})
|
||
|
||
// 添加按钮 - 点击保存时才设置工作目录
|
||
formView.AddButton("保存", func() {
|
||
a.ctx.SetWorkDir(tempWorkDir) // 在这里设置工作目录
|
||
a.mainPages.RemovePage("submenu2")
|
||
a.showInfo("工作目录已设置为 " + a.ctx.WorkDir)
|
||
})
|
||
|
||
formView.AddButton("取消", func() {
|
||
a.mainPages.RemovePage("submenu2")
|
||
})
|
||
|
||
a.mainPages.AddPage("submenu2", formView, true, true)
|
||
a.SetFocus(formView)
|
||
}
|
||
|
||
// settingDataKey 设置数据密钥
|
||
func (a *App) settingDataKey() {
|
||
// 使用我们的自定义表单组件
|
||
formView := form.NewForm("设置数据密钥")
|
||
|
||
// 临时存储用户输入的值
|
||
tempDataKey := a.ctx.DataKey
|
||
|
||
// 添加输入字段 - 不直接设置数据密钥,而是更新临时变量
|
||
formView.AddInputField("数据密钥", tempDataKey, 0, nil, func(text string) {
|
||
tempDataKey = text // 只更新临时变量
|
||
})
|
||
|
||
// 添加按钮 - 点击保存时才设置数据密钥
|
||
formView.AddButton("保存", func() {
|
||
a.ctx.DataKey = tempDataKey // 设置数据密钥
|
||
a.mainPages.RemovePage("submenu2")
|
||
a.showInfo("数据密钥已设置")
|
||
})
|
||
|
||
formView.AddButton("取消", func() {
|
||
a.mainPages.RemovePage("submenu2")
|
||
})
|
||
|
||
a.mainPages.AddPage("submenu2", formView, true, true)
|
||
a.SetFocus(formView)
|
||
}
|
||
|
||
// settingImgKey 设置图片密钥 (ImgKey)
|
||
func (a *App) settingImgKey() {
|
||
formView := form.NewForm("设置图片密钥")
|
||
|
||
tempImgKey := a.ctx.ImgKey
|
||
|
||
formView.AddInputField("图片密钥", tempImgKey, 0, nil, func(text string) {
|
||
tempImgKey = text
|
||
})
|
||
|
||
formView.AddButton("保存", func() {
|
||
a.ctx.SetImgKey(tempImgKey)
|
||
a.mainPages.RemovePage("submenu2")
|
||
a.showInfo("图片密钥已设置")
|
||
})
|
||
|
||
formView.AddButton("取消", func() {
|
||
a.mainPages.RemovePage("submenu2")
|
||
})
|
||
|
||
a.mainPages.AddPage("submenu2", formView, true, true)
|
||
a.SetFocus(formView)
|
||
}
|
||
|
||
// settingDataDir 设置数据目录
|
||
func (a *App) settingDataDir() {
|
||
// 使用我们的自定义表单组件
|
||
formView := form.NewForm("设置数据目录")
|
||
|
||
// 临时存储用户输入的值
|
||
tempDataDir := a.ctx.DataDir
|
||
|
||
// 添加输入字段 - 不直接设置数据目录,而是更新临时变量
|
||
formView.AddInputField("数据目录", tempDataDir, 0, nil, func(text string) {
|
||
tempDataDir = text // 只更新临时变量
|
||
})
|
||
|
||
// 添加按钮 - 点击保存时才设置数据目录
|
||
formView.AddButton("保存", func() {
|
||
a.ctx.DataDir = tempDataDir // 设置数据目录
|
||
a.mainPages.RemovePage("submenu2")
|
||
a.showInfo("数据目录已设置为 " + a.ctx.DataDir)
|
||
})
|
||
|
||
formView.AddButton("取消", func() {
|
||
a.mainPages.RemovePage("submenu2")
|
||
})
|
||
|
||
a.mainPages.AddPage("submenu2", formView, true, true)
|
||
a.SetFocus(formView)
|
||
}
|
||
|
||
// selectAccountSelected 处理切换账号菜单项的选择事件
|
||
func (a *App) selectAccountSelected(i *menu.Item) {
|
||
// 创建子菜单
|
||
subMenu := menu.NewSubMenu("切换账号")
|
||
|
||
// 添加微信进程
|
||
instances := a.m.wechat.GetWeChatInstances()
|
||
if len(instances) > 0 {
|
||
// 添加实例标题
|
||
subMenu.AddItem(&menu.Item{
|
||
Index: 0,
|
||
Name: "--- 微信进程 ---",
|
||
Description: "",
|
||
Hidden: false,
|
||
Selected: nil,
|
||
})
|
||
|
||
// 添加实例列表
|
||
for idx, instance := range instances {
|
||
// 创建一个实例描述
|
||
description := fmt.Sprintf("版本: %s 目录: %s", instance.FullVersion, instance.DataDir)
|
||
|
||
// 标记当前选中的实例
|
||
name := fmt.Sprintf("%s [%d]", instance.Name, instance.PID)
|
||
if a.ctx.Current != nil && a.ctx.Current.PID == instance.PID {
|
||
name = name + " [当前]"
|
||
}
|
||
|
||
// 创建菜单项
|
||
instanceItem := &menu.Item{
|
||
Index: idx + 1,
|
||
Name: name,
|
||
Description: description,
|
||
Hidden: false,
|
||
Selected: func(instance *wechat.Account) func(*menu.Item) {
|
||
return func(*menu.Item) {
|
||
// 如果是当前账号,则无需切换
|
||
if a.ctx.Current != nil && a.ctx.Current.PID == instance.PID {
|
||
a.mainPages.RemovePage("submenu")
|
||
a.showInfo("已经是当前账号")
|
||
return
|
||
}
|
||
|
||
// 显示切换中的模态框
|
||
modal := tview.NewModal().SetText("正在切换账号...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台执行切换操作
|
||
go func() {
|
||
err := a.m.Switch(instance, "")
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
a.mainPages.RemovePage("modal")
|
||
a.mainPages.RemovePage("submenu")
|
||
|
||
if err != nil {
|
||
// 切换失败
|
||
a.showError(fmt.Errorf("切换账号失败: %v", err))
|
||
} else {
|
||
// 切换成功
|
||
a.showInfo("切换账号成功")
|
||
// 更新菜单状态
|
||
a.updateMenuItemsState()
|
||
}
|
||
})
|
||
}()
|
||
}
|
||
}(instance),
|
||
}
|
||
subMenu.AddItem(instanceItem)
|
||
}
|
||
}
|
||
|
||
// 添加历史账号
|
||
if len(a.ctx.History) > 0 {
|
||
// 添加历史账号标题
|
||
subMenu.AddItem(&menu.Item{
|
||
Index: 100,
|
||
Name: "--- 历史账号 ---",
|
||
Description: "",
|
||
Hidden: false,
|
||
Selected: nil,
|
||
})
|
||
|
||
// 添加历史账号列表
|
||
idx := 101
|
||
for account, hist := range a.ctx.History {
|
||
// 创建一个账号描述
|
||
description := fmt.Sprintf("版本: %s 目录: %s", hist.FullVersion, hist.DataDir)
|
||
|
||
// 标记当前选中的账号
|
||
name := account
|
||
if name == "" {
|
||
name = filepath.Base(hist.DataDir)
|
||
}
|
||
if a.ctx.DataDir == hist.DataDir {
|
||
name = name + " [当前]"
|
||
}
|
||
|
||
// 创建菜单项
|
||
histItem := &menu.Item{
|
||
Index: idx,
|
||
Name: name,
|
||
Description: description,
|
||
Hidden: false,
|
||
Selected: func(account string) func(*menu.Item) {
|
||
return func(*menu.Item) {
|
||
// 如果是当前账号,则无需切换
|
||
if a.ctx.Current != nil && a.ctx.DataDir == a.ctx.History[account].DataDir {
|
||
a.mainPages.RemovePage("submenu")
|
||
a.showInfo("已经是当前账号")
|
||
return
|
||
}
|
||
|
||
// 显示切换中的模态框
|
||
modal := tview.NewModal().SetText("正在切换账号...")
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
|
||
// 在后台执行切换操作
|
||
go func() {
|
||
err := a.m.Switch(nil, account)
|
||
|
||
// 在主线程中更新UI
|
||
a.QueueUpdateDraw(func() {
|
||
a.mainPages.RemovePage("modal")
|
||
a.mainPages.RemovePage("submenu")
|
||
|
||
if err != nil {
|
||
// 切换失败
|
||
a.showError(fmt.Errorf("切换账号失败: %v", err))
|
||
} else {
|
||
// 切换成功
|
||
a.showInfo("切换账号成功")
|
||
// 更新菜单状态
|
||
a.updateMenuItemsState()
|
||
}
|
||
})
|
||
}()
|
||
}
|
||
}(account),
|
||
}
|
||
idx++
|
||
subMenu.AddItem(histItem)
|
||
}
|
||
}
|
||
|
||
// 如果没有账号可选择
|
||
if len(a.ctx.History) == 0 && len(instances) == 0 {
|
||
subMenu.AddItem(&menu.Item{
|
||
Index: 1,
|
||
Name: "无可用账号",
|
||
Description: "未检测到微信进程或历史账号",
|
||
Hidden: false,
|
||
Selected: nil,
|
||
})
|
||
}
|
||
|
||
// 显示子菜单
|
||
a.mainPages.AddPage("submenu", subMenu, true, true)
|
||
a.SetFocus(subMenu)
|
||
}
|
||
|
||
// showModal 显示一个模态对话框
|
||
func (a *App) showModal(text string, buttons []string, doneFunc func(buttonIndex int, buttonLabel string)) {
|
||
modal := tview.NewModal().
|
||
SetText(text).
|
||
AddButtons(buttons).
|
||
SetDoneFunc(doneFunc)
|
||
|
||
a.mainPages.AddPage("modal", modal, true, true)
|
||
a.SetFocus(modal)
|
||
}
|
||
|
||
// showError 显示错误对话框
|
||
func (a *App) showError(err error) {
|
||
a.showModal(err.Error(), []string{"OK"}, func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
}
|
||
|
||
// showInfo 显示信息对话框
|
||
func (a *App) showInfo(text string) {
|
||
a.showModal(text, []string{"OK"}, func(buttonIndex int, buttonLabel string) {
|
||
a.mainPages.RemovePage("modal")
|
||
})
|
||
}
|