config.go

  1package config
  2
  3import (
  4	"encoding/json"
  5	"os"
  6	"path/filepath"
  7
  8	"github.com/google/uuid"
  9)
 10
 11// Account stores the configuration for a single email account.
 12type Account struct {
 13	ID              string `json:"id"`
 14	Name            string `json:"name"`
 15	Email           string `json:"email"`
 16	Password        string `json:"password"`
 17	ServiceProvider string `json:"service_provider"` // "gmail", "icloud", or "custom"
 18	// FetchEmail is the single email address for which messages should be fetched.
 19	// If empty, it will default to `Email` when accounts are added.
 20	FetchEmail string `json:"fetch_email,omitempty"`
 21
 22	// Custom server settings (used when ServiceProvider is "custom")
 23	IMAPServer string `json:"imap_server,omitempty"`
 24	IMAPPort   int    `json:"imap_port,omitempty"`
 25	SMTPServer string `json:"smtp_server,omitempty"`
 26	SMTPPort   int    `json:"smtp_port,omitempty"`
 27}
 28
 29// Config stores the user's email configuration with multiple accounts.
 30type Config struct {
 31	Accounts      []Account `json:"accounts"`
 32	DisableImages bool      `json:"disable_images,omitempty"`
 33}
 34
 35// GetIMAPServer returns the IMAP server address for the account.
 36func (a *Account) GetIMAPServer() string {
 37	switch a.ServiceProvider {
 38	case "gmail":
 39		return "imap.gmail.com"
 40	case "icloud":
 41		return "imap.mail.me.com"
 42	case "custom":
 43		return a.IMAPServer
 44	default:
 45		return ""
 46	}
 47}
 48
 49// GetIMAPPort returns the IMAP port for the account.
 50func (a *Account) GetIMAPPort() int {
 51	switch a.ServiceProvider {
 52	case "gmail", "icloud":
 53		return 993
 54	case "custom":
 55		if a.IMAPPort != 0 {
 56			return a.IMAPPort
 57		}
 58		return 993 // Default IMAP SSL port
 59	default:
 60		return 993
 61	}
 62}
 63
 64// GetSMTPServer returns the SMTP server address for the account.
 65func (a *Account) GetSMTPServer() string {
 66	switch a.ServiceProvider {
 67	case "gmail":
 68		return "smtp.gmail.com"
 69	case "icloud":
 70		return "smtp.mail.me.com"
 71	case "custom":
 72		return a.SMTPServer
 73	default:
 74		return ""
 75	}
 76}
 77
 78// GetSMTPPort returns the SMTP port for the account.
 79func (a *Account) GetSMTPPort() int {
 80	switch a.ServiceProvider {
 81	case "gmail", "icloud":
 82		return 587
 83	case "custom":
 84		if a.SMTPPort != 0 {
 85			return a.SMTPPort
 86		}
 87		return 587 // Default SMTP TLS port
 88	default:
 89		return 587
 90	}
 91}
 92
 93// configDir returns the path to the configuration directory.
 94func configDir() (string, error) {
 95	home, err := os.UserHomeDir()
 96	if err != nil {
 97		return "", err
 98	}
 99	return filepath.Join(home, ".config", "matcha"), nil
100}
101
102// configFile returns the full path to the configuration file.
103func configFile() (string, error) {
104	dir, err := configDir()
105	if err != nil {
106		return "", err
107	}
108	return filepath.Join(dir, "config.json"), nil
109}
110
111// SaveConfig saves the given configuration to the config file.
112func SaveConfig(config *Config) error {
113	path, err := configFile()
114	if err != nil {
115		return err
116	}
117	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
118		return err
119	}
120	data, err := json.MarshalIndent(config, "", "  ")
121	if err != nil {
122		return err
123	}
124	return os.WriteFile(path, data, 0600)
125}
126
127// LoadConfig loads the configuration from the config file.
128func LoadConfig() (*Config, error) {
129	path, err := configFile()
130	if err != nil {
131		return nil, err
132	}
133	data, err := os.ReadFile(path)
134	if err != nil {
135		return nil, err
136	}
137	var config Config
138	if err := json.Unmarshal(data, &config); err != nil {
139		// Try to load legacy single-account config
140		var legacyConfig legacyConfigFormat
141		if legacyErr := json.Unmarshal(data, &legacyConfig); legacyErr == nil && legacyConfig.Email != "" {
142			// Convert legacy config to new format
143			config = Config{
144				Accounts: []Account{
145					{
146						ID:              uuid.New().String(),
147						Name:            legacyConfig.Name,
148						Email:           legacyConfig.Email,
149						Password:        legacyConfig.Password,
150						ServiceProvider: legacyConfig.ServiceProvider,
151						// Default FetchEmail to the legacy Email value
152						FetchEmail: legacyConfig.Email,
153					},
154				},
155			}
156			// Save the migrated config
157			if saveErr := SaveConfig(&config); saveErr != nil {
158				return nil, saveErr
159			}
160			return &config, nil
161		}
162		return nil, err
163	}
164	return &config, nil
165}
166
167// legacyConfigFormat represents the old single-account configuration format.
168type legacyConfigFormat struct {
169	ServiceProvider string `json:"service_provider"`
170	Email           string `json:"email"`
171	Password        string `json:"password"`
172	Name            string `json:"name"`
173}
174
175// AddAccount adds a new account to the configuration.
176func (c *Config) AddAccount(account Account) {
177	if account.ID == "" {
178		account.ID = uuid.New().String()
179	}
180	// Ensure FetchEmail defaults to the login Email if not explicitly set.
181	if account.FetchEmail == "" && account.Email != "" {
182		account.FetchEmail = account.Email
183	}
184	c.Accounts = append(c.Accounts, account)
185}
186
187// RemoveAccount removes an account by its ID.
188func (c *Config) RemoveAccount(id string) bool {
189	for i, acc := range c.Accounts {
190		if acc.ID == id {
191			c.Accounts = append(c.Accounts[:i], c.Accounts[i+1:]...)
192			return true
193		}
194	}
195	return false
196}
197
198// GetAccountByID returns an account by its ID.
199func (c *Config) GetAccountByID(id string) *Account {
200	for i := range c.Accounts {
201		if c.Accounts[i].ID == id {
202			return &c.Accounts[i]
203		}
204	}
205	return nil
206}
207
208// GetAccountByEmail returns an account by its email address.
209func (c *Config) GetAccountByEmail(email string) *Account {
210	for i := range c.Accounts {
211		if c.Accounts[i].Email == email {
212			return &c.Accounts[i]
213		}
214	}
215	return nil
216}
217
218// HasAccounts returns true if there are any configured accounts.
219func (c *Config) HasAccounts() bool {
220	return len(c.Accounts) > 0
221}
222
223// GetFirstAccount returns the first account or nil if none exist.
224func (c *Config) GetFirstAccount() *Account {
225	if len(c.Accounts) > 0 {
226		return &c.Accounts[0]
227	}
228	return nil
229}