diff --git a/.gitignore b/.gitignore index 40858a5..48d5a45 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,6 @@ tmp/ # Project build bin -emails.csv +test.csv run.go +test.json diff --git a/README.md b/README.md index 532089d..f56c3b1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # IMAP Client Package + [![Go Reference](https://pkg.go.dev/badge/git.b0zal.io/H0llyW00dzZ/imap.svg)](https://pkg.go.dev/git.b0zal.io/H0llyW00dzZ/imap) [![Go Report Card](https://goreportcard.com/badge/git.b0zal.io/H0llyW00dzZ/imap)](https://goreportcard.com/report/git.b0zal.io/H0llyW00dzZ/imap) -This package provides a simple interface to manage IMAP connections for single or multiple users. It allows you to connect to an IMAP server, list messages, and manage multiple user accounts. +This package provides a simple interface to manage IMAP connections for single or multiple users. It allows you to connect to an IMAP server, list messages, export messages, and manage multiple user accounts. ## Features - Connect and disconnect from an IMAP server - List messages in a specified mailbox +- Export messages to various formats - Manage multiple users with separate IMAP clients ## Installation @@ -60,7 +62,7 @@ func main() { } for _, msg := range messages { - fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg.ID, msg.From, msg.Subject, msg.Body) + fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg[client.KeyID], msg[client.KeyFrom], msg[client.KeySubject], msg[client.KeyBody]) } } ``` @@ -102,7 +104,57 @@ func main() { } for _, msg := range messages { - fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg.ID, msg.From, msg.Subject, msg.Body) + fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg[client.KeyID], msg[client.KeyFrom], msg[client.KeySubject], msg[client.KeyBody]) + } +} +``` + +### Exporting Messages + +```go +package main + +import ( + "log" + "os" + + "git.b0zal.io/H0llyW00dzZ/imap/client" + "git.b0zal.io/H0llyW00dzZ/imap/export" +) + +func main() { + config := &client.Config{ + Username: "user@example.com", + Password: "password", + Server: "imap.example.com:993", + InsecureSkipVerify: true, + } + + imapClient := client.NewIMAP(config) + + err := imapClient.Connect() + if err != nil { + log.Fatalf("Failed to connect: %v", err) + } + defer imapClient.Disconnect() + + messageConfig := client.MessageConfig{ + GrabID: true, + GrabFrom: true, + GrabSubject: true, + GrabBody: true, + } + + file, err := os.Create("messages.json") + if err != nil { + log.Fatalf("Failed to create file: %v", err) + } + defer file.Close() + + exporter := &export.JSONExporter{Encoder: export.DefaultJSONEncoder} + err = imapClient.ExportMessagesTo(file, exporter, "INBOX", 10, messageConfig) + if err != nil { + log.Fatalf("Failed to export messages: %v", err) } } ``` diff --git a/client/export_messages.go b/client/export_messages.go new file mode 100644 index 0000000..68f25f8 --- /dev/null +++ b/client/export_messages.go @@ -0,0 +1,29 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +import ( + "fmt" + "io" + + "git.b0zal.io/H0llyW00dzZ/imap/export" +) + +// ExportMessagesTo uses the provided exporter to write messages to the writer +func (c *IMAPClient) ExportMessagesTo(writer io.Writer, exporter export.Exporter, mailbox string, numMessages uint32, config MessageConfig) error { + if c.client == nil { + return fmt.Errorf("client is not connected") + } + + // Fetch messages based on the provided MessageConfig and number of messages + messages, err := c.ListMessages(mailbox, numMessages, config) + if err != nil { + return fmt.Errorf("failed to list messages: %v", err) + } + + // Use the exporter to write messages + return exporter.Export(messages, writer) +} diff --git a/client/get_multi_user.go b/client/get_multi_user.go new file mode 100644 index 0000000..a321498 --- /dev/null +++ b/client/get_multi_user.go @@ -0,0 +1,21 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package client + +import "fmt" + +// GetClient retrieves the IMAPClient for a specific user +func (m *MultiUserIMAP) GetClient(username string) (*IMAPClient, error) { + m.mu.Lock() + defer m.mu.Unlock() + + client, exists := m.clients[username] + if !exists { + return nil, fmt.Errorf("user not found: %s", username) + } + + return client, nil +} diff --git a/client/list_messages.go b/client/list_messages.go index 728e43c..ffa9afe 100644 --- a/client/list_messages.go +++ b/client/list_messages.go @@ -12,18 +12,19 @@ import ( "github.com/valyala/bytebufferpool" ) -// MessageDetails holds the details of an email message. -// This struct allows the caller to access and format the message's -// ID, sender information, subject, and body as needed. -type MessageDetails struct { - ID string - From []string - Subject string - Body string -} +const ( + // KeyID is the key for the message ID + KeyID = "id" + // KeyFrom is the key for the sender's address + KeyFrom = "from" + // KeySubject is the key for the message subject + KeySubject = "subject" + // KeyBody is the key for the message body + KeyBody = "body" +) // ListMessages lists the messages in the specified mailbox based on the MessageConfig -func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config MessageConfig) ([]MessageDetails, error) { +func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config MessageConfig) ([]map[string]any, error) { if c.client == nil { return nil, fmt.Errorf("client is not connected") } @@ -52,26 +53,28 @@ func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config Mes done <- c.client.Fetch(seqset, items, messages) }() - var results []MessageDetails + var results []map[string]any for msg := range messages { - details := MessageDetails{} + details := make(map[string]any) - if config.GrabID { - details.ID = msg.Envelope.MessageId + if config.GrabID && msg.Envelope.MessageId != "" { + details[KeyID] = msg.Envelope.MessageId } if config.GrabFrom { + var from []string for _, addr := range msg.Envelope.From { - details.From = append(details.From, addr.Address()) + from = append(from, addr.Address()) } + details[KeyFrom] = from } - if config.GrabSubject { - details.Subject = msg.Envelope.Subject + if config.GrabSubject && msg.Envelope.Subject != "" { + details[KeySubject] = msg.Envelope.Subject } if config.GrabBody { for _, literal := range msg.Body { buf := bytebufferpool.Get() if _, err := buf.ReadFrom(literal); err == nil { - details.Body = buf.String() + details[KeyBody] = buf.String() } buf.Reset() // Reset the buffer before returning it to the pool. bytebufferpool.Put(buf) @@ -88,7 +91,7 @@ func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config Mes } // ListUserMessages lists messages for a specific user based on the MessageConfig -func (m *MultiUserIMAP) ListUserMessages(username, mailbox string, numMessages uint32, config MessageConfig) ([]MessageDetails, error) { +func (m *MultiUserIMAP) ListUserMessages(username, mailbox string, numMessages uint32, config MessageConfig) ([]map[string]any, error) { m.mu.Lock() defer m.mu.Unlock() diff --git a/export/exporter.go b/export/exporter.go new file mode 100644 index 0000000..a1857a4 --- /dev/null +++ b/export/exporter.go @@ -0,0 +1,15 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package export + +import ( + "io" +) + +// Exporter defines an interface for exporting messages +type Exporter interface { + Export(messages []map[string]any, writer io.Writer) error +} diff --git a/export/json_exporter.go b/export/json_exporter.go new file mode 100644 index 0000000..73cb0e3 --- /dev/null +++ b/export/json_exporter.go @@ -0,0 +1,36 @@ +// Copyright (c) 2025 H0llyW00dzZ All rights reserved. +// +// By accessing or using this software, you agree to be bound by the terms +// of the License Agreement, which you can find at LICENSE files. + +package export + +import ( + "encoding/json" + "fmt" + "io" +) + +// JSONEncoderFunc defines a function type for custom JSON encoding +type JSONEncoderFunc func(v any, writer io.Writer) error + +// JSONExporter implements the Exporter interface for JSON with a custom encoder +type JSONExporter struct { + Encoder JSONEncoderFunc +} + +// Export writes messages to the writer using the custom JSON encoder +func (e *JSONExporter) Export(messages []map[string]any, writer io.Writer) error { + for _, msg := range messages { + if err := e.Encoder(msg, writer); err != nil { + return fmt.Errorf("failed to encode message to JSON: %v", err) + } + } + return nil +} + +// DefaultJSONEncoder is the default JSON encoding function using the standard library +func DefaultJSONEncoder(v any, writer io.Writer) error { + encoder := json.NewEncoder(writer) + return encoder.Encode(v) +}