package main import ( "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) const configFileName = "config.yml" type Config struct { From FromConfig `yaml:"from"` Defaults DefaultsConfig `yaml:"defaults"` Signature string `yaml:"signature"` UnsafeHTML bool `yaml:"unsafe_html"` Accounts []Account `yaml:"accounts"` } type FromConfig struct { Account string `yaml:"account"` } type DefaultsConfig struct { Encryption string `yaml:"encryption"` Insecure bool `yaml:"insecure"` } type Account struct { Name string `yaml:"name"` Email string `yaml:"email"` Provider string `yaml:"provider"` Username string `yaml:"username"` Password string `yaml:"password"` CheckID *bool `yaml:"check_id"` IMAP IMAPConfig `yaml:"imap"` SMTP SMTPConfig `yaml:"smtp"` } type IMAPConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` Encryption string `yaml:"encryption"` InsecureSkipVerify bool `yaml:"insecure"` } type SMTPConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` Username string `yaml:"username"` Password string `yaml:"password"` Encryption string `yaml:"encryption"` InsecureSkipVerify bool `yaml:"insecure"` } var defaultConfig = Config{ From: FromConfig{}, Defaults: DefaultsConfig{Encryption: "starttls"}, Signature: "", UnsafeHTML: false, } var providerDefaults = map[string]struct { IMAPHost string IMAPPort int IMAPEncryption string SMTPHost string SMTPPort int SMTPEncryption string }{ "163": { IMAPHost: "imap.163.com", IMAPPort: 993, IMAPEncryption: "ssl", SMTPHost: "smtp.163.com", SMTPPort: 465, SMTPEncryption: "ssl", }, "QQ": { IMAPHost: "imap.qq.com", IMAPPort: 993, IMAPEncryption: "ssl", SMTPHost: "smtp.qq.com", SMTPPort: 465, SMTPEncryption: "ssl", }, } var imapProviders = map[string]IMAPConfig{ "163": { Host: "imap.163.com", Port: 993, Username: "", Password: "", }, "QQ": { Host: "imap.qq.com", Port: 993, Username: "", Password: "", }, } func normalizeAccount(acc Account, defaults DefaultsConfig) Account { if acc.Provider == "" { acc.Provider = getProviderName(acc.Email) } if providerDefaults, ok := providerDefaults[acc.Provider]; ok { if acc.IMAP.Host == "" { acc.IMAP.Host = providerDefaults.IMAPHost acc.IMAP.Port = providerDefaults.IMAPPort } if acc.IMAP.Encryption == "" && providerDefaults.IMAPEncryption != "" { acc.IMAP.Encryption = providerDefaults.IMAPEncryption } if acc.SMTP.Host == "" { acc.SMTP.Host = providerDefaults.SMTPHost acc.SMTP.Port = providerDefaults.SMTPPort } if acc.SMTP.Encryption == "" && providerDefaults.SMTPEncryption != "" { acc.SMTP.Encryption = providerDefaults.SMTPEncryption } } if defaults.Encryption != "" { if acc.IMAP.Encryption == "" { acc.IMAP.Encryption = defaults.Encryption } if acc.SMTP.Encryption == "" { acc.SMTP.Encryption = defaults.Encryption } } if acc.IMAP.InsecureSkipVerify == false && defaults.Insecure { acc.IMAP.InsecureSkipVerify = defaults.Insecure } if acc.SMTP.InsecureSkipVerify == false && defaults.Insecure { acc.SMTP.InsecureSkipVerify = defaults.Insecure } if acc.Username == "" { acc.Username = acc.Email } if acc.IMAP.Username == "" { acc.IMAP.Username = acc.Username } if acc.IMAP.Password == "" { acc.IMAP.Password = acc.Password } if acc.SMTP.Username == "" { acc.SMTP.Username = acc.Username } if acc.SMTP.Password == "" { acc.SMTP.Password = acc.Password } if acc.Name == "" { acc.Name = acc.Provider } if acc.CheckID == nil { defaultCheckID := ProjectConfig.ProvidersNeedingCheckID[acc.Provider] acc.CheckID = &defaultCheckID } return acc } func isValidEmail(email string) bool { return strings.Contains(email, "@") && strings.Contains(email, ".") } func getValidFromAddress(from, smtpUsername string) string { if isValidEmail(from) { return from } if isValidEmail(smtpUsername) { return smtpUsername } return from } func getConfigPath() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { return "", err } configDir := filepath.Join(homeDir, ".config", "pop") return filepath.Join(configDir, configFileName), nil } func getConfigDir() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(homeDir, ".config", "pop"), nil } func loadConfig() (Config, error) { configPath, err := getConfigPath() if err != nil { return defaultConfig, err } data, err := os.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { return defaultConfig, nil } return defaultConfig, err } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return defaultConfig, err } return cfg, nil } func saveConfig(cfg Config) error { configDir, err := getConfigDir() if err != nil { return err } if err := os.MkdirAll(configDir, 0755); err != nil { return err } configPath, err := getConfigPath() if err != nil { return err } data, err := yaml.Marshal(&cfg) if err != nil { return err } return os.WriteFile(configPath, data, 0644) } func configExists() bool { configPath, err := getConfigPath() if err != nil { return false } _, err = os.Stat(configPath) return err == nil } func getAccounts() ([]Account, error) { cfg, err := loadConfig() if err != nil { return nil, err } accounts := cfg.Accounts for i := range accounts { accounts[i] = normalizeAccount(accounts[i], cfg.Defaults) } return accounts, nil } func getProviderName(email string) string { if strings.HasSuffix(email, "@163.com") || strings.HasSuffix(email, "@vip.163.com") { return "163" } if strings.HasSuffix(email, "@qq.com") || strings.HasSuffix(email, "@vip.qq.com") { return "QQ" } return "custom" } func getIMAPHost(email string) string { if strings.HasSuffix(email, "@163.com") { return "imap.163.com" } if strings.HasSuffix(email, "@qq.com") { return "imap.qq.com" } return "" } func getDefaultFromEmail() string { cfg, err := loadConfig() if err != nil || cfg.From.Account == "" { return "" } accounts, err := getAccounts() if err != nil { return "" } for _, acc := range accounts { if acc.Name == cfg.From.Account { if acc.Email != "" { return acc.Email } return acc.Username } } return "" } func getDefaultAccount(accounts []Account, accountName string) *Account { if accountName == "" { return nil } for i := range accounts { if accounts[i].Name == accountName { return &accounts[i] } } return nil } type Info struct { Name string Version string Vendor string } var ProjectConfig = struct { Info Info ProvidersNeedingCheckID map[string]bool }{ Info: Info{ Name: "pop", Version: "1.0", Vendor: "charmbracelet", }, ProvidersNeedingCheckID: map[string]bool{ "163": true, "QQ": true, }, }