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