package main import ( "fmt" "strings" "time" imap "github.com/BrianLeishman/go-imap" ) type ReceivedEmail struct { UID uint32 From string FromName string Subject string Date time.Time Preview string Account string AccountID string } func getEmailProviderName(email string) string { if len(email) == 0 { return "Email" } atIdx := -1 for i := len(email) - 1; i >= 0; i-- { if email[i] == '@' { atIdx = i break } } if atIdx == -1 { return "Email" } domain := email[atIdx+1:] for i := len(domain) - 1; i >= 0; i-- { if domain[i] == '.' { return domain[:i] } } return domain } func FetchUnreadEmails(account Account, days int) ([]ReceivedEmail, error) { if account.IMAP.Host == "" { return nil, fmt.Errorf("IMAP host not configured for account: %s", account.Email) } port := account.IMAP.Port if port == 0 { port = 993 } username := account.IMAP.Username if username == "" { username = account.Email } m, err := imap.New(username, account.IMAP.Password, account.IMAP.Host, port) if err != nil { return nil, fmt.Errorf("failed to connect to IMAP server: %w", err) } defer m.Close() if account.CheckID != nil && *account.CheckID { info := ProjectConfig.Info idCmd := fmt.Sprintf("ID (\"name\" \"%s\" \"version\" \"%s\" \"vendor\" \"%s\")", info.Name, info.Version, info.Vendor) _, err := m.Exec(idCmd, false, 0, func(line []byte) error { return nil }) if err != nil { fmt.Printf("WARNING: failed to send IMAP ID: %v\n", err) } m.Exec("NOOP", false, 0, nil) } err = m.SelectFolder("INBOX") if err != nil { return nil, fmt.Errorf("failed to select inbox: %w", err) } sinceDate := time.Now().AddDate(0, 0, -days) uids, err := m.GetUIDs(imap.Search().Since(sinceDate).Unseen().Build()) if err != nil { return nil, fmt.Errorf("failed to search emails: %w", err) } if len(uids) == 0 { return nil, nil } overviews, err := m.GetOverviews(uids...) if err != nil { return nil, fmt.Errorf("failed to fetch emails: %w", err) } var emails []ReceivedEmail accountName := getEmailProviderName(account.Email) accountID := account.Email if account.Name != "" { accountName = account.Name accountID = account.Name } for uid, email := range overviews { from := "" if len(email.From) > 0 { for _, addr := range email.From { from = addr break } } emails = append(emails, ReceivedEmail{ UID: uint32(uid), From: from, FromName: "", Subject: email.Subject, Date: email.Sent, Preview: fmt.Sprintf("(%.1f KB)", float64(email.Size)/1024), Account: accountName, AccountID: accountID, }) } return emails, nil } func GetAccountByEmail(email string) (Account, error) { accounts, err := getAccounts() if err != nil { return Account{}, err } for _, acc := range accounts { if acc.Email == email || acc.Name == email { return acc, nil } } return Account{}, fmt.Errorf("account not found: %s", email) } func GetAccountByID(id string) (Account, error) { accounts, err := getAccounts() if err != nil { return Account{}, err } for _, acc := range accounts { if acc.Email == id || acc.Name == id { return acc, nil } } return Account{}, fmt.Errorf("account not found: %s", id) } func FetchAllUnreadEmails(days int) ([]ReceivedEmail, error) { accounts, err := getAccounts() if err != nil { return nil, err } if len(accounts) == 0 { return nil, fmt.Errorf("no accounts configured") } var allEmails []ReceivedEmail for _, account := range accounts { emails, err := FetchUnreadEmails(account, days) if err != nil { continue } allEmails = append(allEmails, emails...) } sortEmailsByDate(allEmails) return allEmails, nil } func sortEmailsByDate(emails []ReceivedEmail) { for i := 0; i < len(emails)-1; i++ { for j := i + 1; j < len(emails); j++ { if emails[j].Date.After(emails[i].Date) { emails[i], emails[j] = emails[j], emails[i] } } } } type EmailDetail struct { UID uint32 From string FromName string To string Cc string Subject string Date time.Time TextBody string HTMLBody string Account string AccountID string } func FetchEmailDetailByUID(accountID string, uid uint32) (*EmailDetail, error) { account, err := GetAccountByID(accountID) if err != nil { return nil, err } if account.IMAP.Host == "" { return nil, fmt.Errorf("IMAP host not configured for account: %s", account.Email) } port := account.IMAP.Port if port == 0 { port = 993 } username := account.IMAP.Username if username == "" { username = account.Email } m, err := imap.New(username, account.IMAP.Password, account.IMAP.Host, port) if err != nil { return nil, fmt.Errorf("failed to connect to IMAP server: %w", err) } defer m.Close() if account.CheckID != nil && *account.CheckID { info := ProjectConfig.Info idCmd := fmt.Sprintf("ID (\"name\" \"%s\" \"version\" \"%s\" \"vendor\" \"%s\")", info.Name, info.Version, info.Vendor) _, err := m.Exec(idCmd, false, 0, func(line []byte) error { return nil }) if err != nil { fmt.Printf("WARNING: failed to send IMAP ID: %v\n", err) } m.Exec("NOOP", false, 0, nil) } err = m.SelectFolder("INBOX") if err != nil { return nil, fmt.Errorf("failed to select inbox: %w", err) } emails, err := m.GetEmails(int(uid)) if err != nil { return nil, fmt.Errorf("failed to fetch email: %w", err) } if len(emails) == 0 { return nil, fmt.Errorf("email not found") } var email imap.Email found := false for i := range emails { if emails[i] != nil { email = *emails[i] found = true break } } if !found { return nil, fmt.Errorf("email not found or has been removed") } accountName := getEmailProviderName(account.Email) accountIDName := account.Email if account.Name != "" { accountName = account.Name accountIDName = account.Name } var fromName string var fromAddr string if email.From != nil { for name, addr := range email.From { fromName = name fromAddr = addr break } } var toList []string if email.To != nil { for name, addr := range email.To { if name != "" { toList = append(toList, fmt.Sprintf("%s <%s>", name, addr)) } else { toList = append(toList, addr) } } } var ccList []string if email.CC != nil { for name, addr := range email.CC { if name != "" { ccList = append(ccList, fmt.Sprintf("%s <%s>", name, addr)) } else { ccList = append(ccList, addr) } } } fromStr := fromAddr if fromName != "" { fromStr = fmt.Sprintf("%s <%s>", fromName, fromAddr) } return &EmailDetail{ UID: uint32(email.UID), From: fromStr, FromName: fromName, To: strings.Join(toList, ", "), Cc: strings.Join(ccList, ", "), Subject: email.Subject, Date: email.Sent, TextBody: email.Text, HTMLBody: email.HTML, Account: accountName, AccountID: accountIDName, }, nil }