package ctx import ( "encoding/json" "errors" "os" "path/filepath" "sync" "time" "github.com/rs/zerolog/log" "github.com/sjzar/chatlog/internal/chatlog/conf" "github.com/sjzar/chatlog/internal/wechat" "github.com/sjzar/chatlog/pkg/config" "github.com/sjzar/chatlog/pkg/util" ) const ( DefalutHTTPAddr = "127.0.0.1:5030" ) // Context is a context for a chatlog. // It is used to store information about the chatlog. type Context struct { conf *conf.TUIConfig cm *config.Manager mu sync.RWMutex History map[string]conf.ProcessConfig speech *conf.SpeechConfig speechPath string // 微信账号相关状态 Account string Platform string Version int FullVersion string DataDir string DataKey string DataUsage string ImgKey string // 工作目录相关状态 WorkDir string WorkUsage string // HTTP服务相关状态 HTTPEnabled bool HTTPAddr string // 自动解密 AutoDecrypt bool LastSession time.Time // 当前选中的微信实例 Current *wechat.Account PID int ExePath string Status string // 所有可用的微信实例 WeChatInstances []*wechat.Account } func New(configPath string) (*Context, error) { conf, tcm, err := conf.LoadTUIConfig(configPath) if err != nil { return nil, err } ctx := &Context{ conf: conf, cm: tcm, } ctx.loadConfig() return ctx, nil } func (c *Context) loadConfig() { c.History = c.conf.ParseHistory() c.SwitchHistory(c.conf.LastAccount) c.Refresh() if c.cm != nil { speechPath := filepath.Join(c.cm.Path, "whisper.json") c.speechPath = speechPath if data, err := os.ReadFile(speechPath); err == nil { var sc conf.SpeechConfig if err := json.Unmarshal(data, &sc); err != nil { log.Debug().Err(err).Msg("failed to parse speech config") } else { sc.Normalize() c.speech = &sc } } else if os.IsNotExist(err) { defaultSpeech := conf.SpeechConfig{Enabled: true, Provider: "openai", Model: "whisper-1"} defaultSpeech.PrepareForSave() payload, marshalErr := json.MarshalIndent(defaultSpeech, "", " ") if marshalErr != nil { log.Error().Err(marshalErr).Msg("failed to marshal default speech config") } else if writeErr := os.WriteFile(speechPath, payload, 0600); writeErr != nil { log.Error().Err(writeErr).Msg("failed to write default speech config") } else { defaultSpeech.Normalize() c.speech = &defaultSpeech log.Info().Str("path", speechPath).Msg("created default speech config") } } if c.speech != nil { if c.speech.Model == "" { c.speech.Model = "whisper-1" } c.speech.Enabled = true } } } func (c *Context) SaveSpeechConfig(cfg *conf.SpeechConfig) error { if cfg == nil { return errors.New("speech config is nil") } if c.cm == nil { return errors.New("config manager unavailable") } cfg.Enabled = true cfg.Normalize() cfg.PrepareForSave() path := c.speechPath if path == "" { path = filepath.Join(c.cm.Path, "whisper.json") c.speechPath = path } payload, err := json.MarshalIndent(cfg, "", " ") if err != nil { return err } if err := os.WriteFile(path, payload, 0o600); err != nil { return err } cfgCopy := *cfg cfgCopy.Normalize() c.mu.Lock() defer c.mu.Unlock() c.speech = &cfgCopy return nil } func (c *Context) SwitchHistory(account string) { c.mu.Lock() defer c.mu.Unlock() c.Current = nil c.PID = 0 c.ExePath = "" c.Status = "" history, ok := c.History[account] if ok { c.Account = history.Account c.Platform = history.Platform c.Version = history.Version c.FullVersion = history.FullVersion c.DataKey = history.DataKey c.ImgKey = history.ImgKey c.DataDir = history.DataDir c.WorkDir = history.WorkDir c.HTTPEnabled = history.HTTPEnabled c.HTTPAddr = history.HTTPAddr } else { c.Account = "" c.Platform = "" c.Version = 0 c.FullVersion = "" c.DataKey = "" c.ImgKey = "" c.DataDir = "" c.WorkDir = "" c.HTTPEnabled = false c.HTTPAddr = "" } } func (c *Context) SwitchCurrent(info *wechat.Account) { c.SwitchHistory(info.Name) c.mu.Lock() defer c.mu.Unlock() c.Current = info c.Refresh() } func (c *Context) Refresh() { if c.Current != nil { c.Account = c.Current.Name c.Platform = c.Current.Platform c.Version = c.Current.Version c.FullVersion = c.Current.FullVersion c.PID = int(c.Current.PID) c.ExePath = c.Current.ExePath c.Status = c.Current.Status if c.Current.Key != "" && c.Current.Key != c.DataKey { c.DataKey = c.Current.Key } if c.Current.ImgKey != "" && c.Current.ImgKey != c.ImgKey { c.ImgKey = c.Current.ImgKey } if c.Current.DataDir != "" && c.Current.DataDir != c.DataDir { c.DataDir = c.Current.DataDir } } if c.DataUsage == "" && c.DataDir != "" { go func() { c.DataUsage = util.GetDirSize(c.DataDir) }() } if c.WorkUsage == "" && c.WorkDir != "" { go func() { c.WorkUsage = util.GetDirSize(c.WorkDir) }() } } func (c *Context) GetDataDir() string { return c.DataDir } func (c *Context) GetWorkDir() string { return c.WorkDir } func (c *Context) GetPlatform() string { return c.Platform } func (c *Context) GetVersion() int { return c.Version } func (c *Context) GetDataKey() string { return c.DataKey } func (c *Context) GetHTTPAddr() string { if c.HTTPAddr == "" { c.HTTPAddr = DefalutHTTPAddr } return c.HTTPAddr } func (c *Context) GetWebhook() *conf.Webhook { return c.conf.Webhook } func (c *Context) GetSpeech() *conf.SpeechConfig { return c.speech } func (c *Context) SetHTTPEnabled(enabled bool) { c.mu.Lock() defer c.mu.Unlock() if c.HTTPEnabled == enabled { return } c.HTTPEnabled = enabled c.UpdateConfig() } func (c *Context) SetHTTPAddr(addr string) { c.mu.Lock() defer c.mu.Unlock() if c.HTTPAddr == addr { return } c.HTTPAddr = addr c.UpdateConfig() } func (c *Context) SetWorkDir(dir string) { c.mu.Lock() defer c.mu.Unlock() if c.WorkDir == dir { return } c.WorkDir = dir c.UpdateConfig() c.Refresh() } func (c *Context) SetDataDir(dir string) { c.mu.Lock() defer c.mu.Unlock() if c.DataDir == dir { return } c.DataDir = dir c.UpdateConfig() c.Refresh() } func (c *Context) SetImgKey(key string) { c.mu.Lock() defer c.mu.Unlock() if c.ImgKey == key { return } c.ImgKey = key c.UpdateConfig() } func (c *Context) SetAutoDecrypt(enabled bool) { c.mu.Lock() defer c.mu.Unlock() if c.AutoDecrypt == enabled { return } c.AutoDecrypt = enabled c.UpdateConfig() } func (c *Context) SetWeChatInstances(instances []*wechat.Account) { c.mu.Lock() defer c.mu.Unlock() c.WeChatInstances = instances } // 更新配置 func (c *Context) UpdateConfig() { pconf := conf.ProcessConfig{ Type: "wechat", Account: c.Account, Platform: c.Platform, Version: c.Version, FullVersion: c.FullVersion, DataDir: c.DataDir, DataKey: c.DataKey, ImgKey: c.ImgKey, WorkDir: c.WorkDir, HTTPEnabled: c.HTTPEnabled, HTTPAddr: c.HTTPAddr, } if c.conf.History == nil { c.conf.History = make([]conf.ProcessConfig, 0) } if len(c.conf.History) == 0 { c.conf.History = append(c.conf.History, pconf) } else { isFind := false for i, v := range c.conf.History { if v.Account == c.Account { isFind = true c.conf.History[i] = pconf break } } if !isFind { c.conf.History = append(c.conf.History, pconf) } } if err := c.cm.SetConfig("last_account", c.Account); err != nil { log.Error().Err(err).Msg("set last_account failed") return } if err := c.cm.SetConfig("history", c.conf.History); err != nil { log.Error().Err(err).Msg("set history failed") return } if len(pconf.DataDir) != 0 { if b, err := json.Marshal(pconf); err == nil { if err := os.WriteFile(filepath.Join(pconf.DataDir, "chatlog.json"), b, 0644); err != nil { log.Error().Err(err).Msg("save chatlog.json failed") } } } }