Improve [Client] Support Exporting Messages with various formats (#4)
- [+] 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 Reviewed-on: #4 Co-authored-by: H0llyW00dzZ <h0llyw00dzz@pm.me> Co-committed-by: H0llyW00dzZ <h0llyw00dzz@pm.me>
This commit is contained in:
parent
9ce7f8dc5c
commit
ab122177c2
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,5 +30,6 @@ tmp/
|
|||||||
# Project build
|
# Project build
|
||||||
bin
|
bin
|
||||||
|
|
||||||
emails.csv
|
test.csv
|
||||||
run.go
|
run.go
|
||||||
|
test.json
|
||||||
|
58
README.md
58
README.md
@ -1,12 +1,14 @@
|
|||||||
# IMAP Client Package
|
# 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)
|
[![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
|
## Features
|
||||||
|
|
||||||
- Connect and disconnect from an IMAP server
|
- Connect and disconnect from an IMAP server
|
||||||
- List messages in a specified mailbox
|
- List messages in a specified mailbox
|
||||||
|
- Export messages to various formats
|
||||||
- Manage multiple users with separate IMAP clients
|
- Manage multiple users with separate IMAP clients
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@ -60,7 +62,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range messages {
|
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 {
|
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
29
client/export_messages.go
Normal 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
21
client/get_multi_user.go
Normal 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
|
||||||
|
}
|
@ -12,18 +12,19 @@ import (
|
|||||||
"github.com/valyala/bytebufferpool"
|
"github.com/valyala/bytebufferpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageDetails holds the details of an email message.
|
const (
|
||||||
// This struct allows the caller to access and format the message's
|
// KeyID is the key for the message ID
|
||||||
// ID, sender information, subject, and body as needed.
|
KeyID = "id"
|
||||||
type MessageDetails struct {
|
// KeyFrom is the key for the sender's address
|
||||||
ID string
|
KeyFrom = "from"
|
||||||
From []string
|
// KeySubject is the key for the message subject
|
||||||
Subject string
|
KeySubject = "subject"
|
||||||
Body string
|
// KeyBody is the key for the message body
|
||||||
}
|
KeyBody = "body"
|
||||||
|
)
|
||||||
|
|
||||||
// ListMessages lists the messages in the specified mailbox based on the MessageConfig
|
// 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 {
|
if c.client == nil {
|
||||||
return nil, fmt.Errorf("client is not connected")
|
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)
|
done <- c.client.Fetch(seqset, items, messages)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var results []MessageDetails
|
var results []map[string]any
|
||||||
for msg := range messages {
|
for msg := range messages {
|
||||||
details := MessageDetails{}
|
details := make(map[string]any)
|
||||||
|
|
||||||
if config.GrabID {
|
if config.GrabID && msg.Envelope.MessageId != "" {
|
||||||
details.ID = msg.Envelope.MessageId
|
details[KeyID] = msg.Envelope.MessageId
|
||||||
}
|
}
|
||||||
if config.GrabFrom {
|
if config.GrabFrom {
|
||||||
|
var from []string
|
||||||
for _, addr := range msg.Envelope.From {
|
for _, addr := range msg.Envelope.From {
|
||||||
details.From = append(details.From, addr.Address())
|
from = append(from, addr.Address())
|
||||||
}
|
}
|
||||||
|
details[KeyFrom] = from
|
||||||
}
|
}
|
||||||
if config.GrabSubject {
|
if config.GrabSubject && msg.Envelope.Subject != "" {
|
||||||
details.Subject = msg.Envelope.Subject
|
details[KeySubject] = msg.Envelope.Subject
|
||||||
}
|
}
|
||||||
if config.GrabBody {
|
if config.GrabBody {
|
||||||
for _, literal := range msg.Body {
|
for _, literal := range msg.Body {
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
if _, err := buf.ReadFrom(literal); err == nil {
|
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.
|
buf.Reset() // Reset the buffer before returning it to the pool.
|
||||||
bytebufferpool.Put(buf)
|
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
|
// 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()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
15
export/exporter.go
Normal file
15
export/exporter.go
Normal 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
36
export/json_exporter.go
Normal 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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user