Improve [Client] Support Exporting Messages with various formats

- [+] feat(export): add message exporting functionality with JSON support
- [+] feat(readme): update documentation to include message export feature
- [+] refactor(.gitignore): change ignored files from emails.csv to test.csv and add test.json
- [+] refactor(client): update message handling to use map structure instead of MessageDetails struct
This commit is contained in:
H0llyW00dzZ 2025-01-24 14:01:47 +07:00
parent 9ce7f8dc5c
commit 6057f410ec
Signed by: H0llyW00dzZ
GPG Key ID: A0F9424A7002343A
7 changed files with 180 additions and 23 deletions

3
.gitignore vendored
View File

@ -30,5 +30,6 @@ tmp/
# Project build
bin
emails.csv
test.csv
run.go
test.json

View File

@ -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)
}
}
```

29
client/export_messages.go Normal file
View File

@ -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)
}

21
client/get_multi_user.go Normal file
View File

@ -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
}

View File

@ -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()

15
export/exporter.go Normal file
View File

@ -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
}

36
export/json_exporter.go Normal file
View File

@ -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)
}