mail2couch/go/mail/imap.go
Ole-Morten Duesund 1e4a67d4cb feat: implement Go-based mail2couch with working IMAP and CouchDB integration
- Add configuration system with automatic file discovery (current dir, config subdir, user home, XDG config)
- Implement IMAP client with TLS connection, authentication, and mailbox listing
- Add CouchDB integration with database creation and document storage
- Support folder filtering (include/exclude) and date filtering (since parameter)
- Include duplicate detection to prevent re-storing existing messages
- Add comprehensive error handling and logging throughout
- Structure code in clean packages: config, mail, couch
- Application currently uses placeholder messages to test the storage pipeline
- Ready for real IMAP message parsing implementation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 17:18:20 +02:00

127 lines
2.9 KiB
Go

package mail
import (
"fmt"
"log"
"time"
"github.com/emersion/go-imap/v2/imapclient"
"mail2couch/config"
)
// ImapClient wraps the IMAP client
type ImapClient struct {
*imapclient.Client
}
// Message represents an email message retrieved from IMAP
type Message struct {
UID uint32
From []string
To []string
Subject string
Date time.Time
Body string
Headers map[string][]string
}
// NewImapClient creates a new IMAP client from the configuration
func NewImapClient(source *config.MailSource) (*ImapClient, error) {
addr := fmt.Sprintf("%s:%d", source.Host, source.Port)
client, err := imapclient.DialTLS(addr, nil)
if err != nil {
return nil, fmt.Errorf("failed to dial IMAP server: %w", err)
}
if err := client.Login(source.User, source.Password).Wait(); err != nil {
return nil, fmt.Errorf("failed to login: %w", err)
}
return &ImapClient{client}, nil
}
// ListMailboxes lists all available mailboxes
func (c *ImapClient) ListMailboxes() ([]string, error) {
var mailboxes []string
cmd := c.List("", "*", nil)
infos, err := cmd.Collect()
if err != nil {
return nil, err
}
for _, info := range infos {
mailboxes = append(mailboxes, info.Mailbox)
}
return mailboxes, nil
}
// GetMessages retrieves messages from a specific mailbox (simplified version)
func (c *ImapClient) GetMessages(mailbox string, since *time.Time) ([]*Message, error) {
// Select the mailbox
mbox, err := c.Select(mailbox, nil).Wait()
if err != nil {
return nil, fmt.Errorf("failed to select mailbox %s: %w", mailbox, err)
}
if mbox.NumMessages == 0 {
return []*Message{}, nil
}
// For now, just return placeholder messages to test the flow
var messages []*Message
numToFetch := mbox.NumMessages
if numToFetch > 5 {
numToFetch = 5 // Limit to 5 messages for testing
}
for i := uint32(1); i <= numToFetch; i++ {
msg := &Message{
UID: i,
From: []string{"test@example.com"},
To: []string{"user@example.com"},
Subject: fmt.Sprintf("Message %d from %s", i, mailbox),
Date: time.Now(),
Body: fmt.Sprintf("This is a placeholder message %d from mailbox %s", i, mailbox),
Headers: make(map[string][]string),
}
messages = append(messages, msg)
}
return messages, nil
}
// ShouldProcessMailbox checks if a mailbox should be processed based on filters
func (c *ImapClient) ShouldProcessMailbox(mailbox string, filter *config.FolderFilter) bool {
// If include list is specified, mailbox must be in it
if len(filter.Include) > 0 {
found := false
for _, included := range filter.Include {
if mailbox == included {
found = true
break
}
}
if !found {
return false
}
}
// If exclude list is specified, mailbox must not be in it
for _, excluded := range filter.Exclude {
if mailbox == excluded {
return false
}
}
return true
}
// Logout logs the client out
func (c *ImapClient) Logout() {
if err := c.Client.Logout(); err != nil {
log.Printf("Failed to logout: %v", err)
}
}