Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
4c74ebc1c7 | |||
7a43b90f67 | |||
5cab29c344 | |||
e129c63b37 | |||
289fc877ee | |||
2dab893c8e | |||
f22bed1f12 | |||
1574f865c9 | |||
d17022fb85 | |||
3e1a7445f6 | |||
4de658fca2 | |||
3d17ce82ff | |||
ab122177c2 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,5 +30,6 @@ tmp/
|
||||
# Project build
|
||||
bin
|
||||
|
||||
emails.csv
|
||||
test.csv
|
||||
run.go
|
||||
test.json
|
||||
|
116
README.md
116
README.md
@ -1,13 +1,16 @@
|
||||
# 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, manage multiple user accounts, and list available mailboxes.
|
||||
|
||||
## 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
|
||||
- **New**: List all available mailboxes
|
||||
|
||||
## Installation
|
||||
|
||||
@ -16,6 +19,8 @@ To install the package, use `go get`:
|
||||
```bash
|
||||
go get git.b0zal.io/H0llyW00dzZ/imap
|
||||
```
|
||||
> [!NOTE]
|
||||
> If you're in `Indonesia` and using an internet provider like `Telkom (known as Indihome)`, and you encounter issues installing the package, try using a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network).
|
||||
|
||||
## Usage
|
||||
|
||||
@ -47,6 +52,13 @@ func main() {
|
||||
}
|
||||
defer imapClient.Disconnect()
|
||||
|
||||
// New: List all available mailboxes
|
||||
mailboxes, err := imapClient.ListMailboxes()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to list mailboxes: %v", err)
|
||||
}
|
||||
fmt.Println("Mailboxes:", mailboxes)
|
||||
|
||||
messageConfig := client.MessageConfig{
|
||||
GrabID: true,
|
||||
GrabFrom: true,
|
||||
@ -60,7 +72,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])
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -89,6 +101,17 @@ func main() {
|
||||
}
|
||||
defer multiUserIMAP.DisconnectUser("user1@example.com")
|
||||
|
||||
// New: List all available mailboxes for user1
|
||||
client, err := multiUserIMAP.GetClient("user1@example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get client for user1: %v", err)
|
||||
}
|
||||
mailboxes, err := client.ListMailboxes()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to list mailboxes for user1: %v", err)
|
||||
}
|
||||
fmt.Println("User1 Mailboxes:", mailboxes)
|
||||
|
||||
messageConfig := client.MessageConfig{
|
||||
GrabID: true,
|
||||
GrabFrom: true,
|
||||
@ -102,11 +125,98 @@ 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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### List All Available Mailboxes
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.b0zal.io/H0llyW00dzZ/imap/client"
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
// List all available mailboxes
|
||||
mailboxes, err := imapClient.ListMailboxes()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to list mailboxes: %v", err)
|
||||
}
|
||||
fmt.Println("Mailboxes:", mailboxes)
|
||||
}
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- Implement functionality to send emails.
|
||||
|
34
client/cert_test.pem
Normal file
34
client/cert_test.pem
Normal file
@ -0,0 +1,34 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
JRNGkalOfg==
|
||||
-----END CERTIFICATE-----
|
@ -5,7 +5,11 @@
|
||||
|
||||
package client
|
||||
|
||||
import "github.com/emersion/go-imap/client"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap/client"
|
||||
)
|
||||
|
||||
// Config holds the configuration for the IMAP client
|
||||
type Config struct {
|
||||
@ -17,10 +21,17 @@ type Config struct {
|
||||
|
||||
// MessageConfig defines what parts of the message to retrieve
|
||||
type MessageConfig struct {
|
||||
GrabID bool
|
||||
GrabFrom bool
|
||||
GrabSubject bool
|
||||
GrabBody bool
|
||||
GrabID bool
|
||||
GrabFrom bool
|
||||
GrabSubject bool
|
||||
GrabBody bool
|
||||
GrabSender bool
|
||||
GrabReplyTo bool
|
||||
GrabTo bool
|
||||
GrabCc bool
|
||||
GrabBcc bool
|
||||
GrabInReplyTo bool
|
||||
GrabDate bool
|
||||
}
|
||||
|
||||
// IMAPClient represents an IMAP client
|
||||
@ -28,3 +39,11 @@ type IMAPClient struct {
|
||||
config *Config
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrorsClientIsNotConnected is returned when an operation is attempted on a client that is not connected.
|
||||
ErrorsClientIsNotConnected = errors.New("client is not connected")
|
||||
|
||||
// ErrorsFailedToReadBody is returned when there is a failure in reading the body of a message.
|
||||
ErrorsFailedToReadBody = errors.New("failed to read body")
|
||||
)
|
||||
|
@ -5,12 +5,14 @@
|
||||
|
||||
// Package client 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.
|
||||
// list messages, export messages, manage multiple user accounts, and list available mailboxes.
|
||||
//
|
||||
// # 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
|
||||
// - List all available mailboxes
|
||||
//
|
||||
// # Usage
|
||||
//
|
||||
@ -54,10 +56,44 @@
|
||||
// }
|
||||
//
|
||||
// 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])
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// List All Available Mailboxes Example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "log"
|
||||
//
|
||||
// "git.b0zal.io/H0llyW00dzZ/imap/client"
|
||||
// )
|
||||
//
|
||||
// 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()
|
||||
//
|
||||
// mailboxes, err := imapClient.ListMailboxes()
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to list mailboxes: %v", err)
|
||||
// }
|
||||
// fmt.Println("Mailboxes:", mailboxes)
|
||||
// }
|
||||
//
|
||||
// Multiple Users Example:
|
||||
//
|
||||
// package main
|
||||
@ -94,7 +130,55 @@
|
||||
// }
|
||||
//
|
||||
// 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])
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Export Messages Example:
|
||||
//
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
package client
|
||||
|
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 ErrorsClientIsNotConnected
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
154
client/imap_test.go
Normal file
154
client/imap_test.go
Normal file
@ -0,0 +1,154 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"git.b0zal.io/H0llyW00dzZ/imap/client"
|
||||
"github.com/emersion/go-imap/backend/memory"
|
||||
"github.com/emersion/go-imap/server"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func setupTestServer() (net.Listener, *server.Server) {
|
||||
// Create a memory backend
|
||||
be := memory.New()
|
||||
|
||||
// Create a new IMAP server with TLS configuration
|
||||
s := server.New(be)
|
||||
s.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: true, // For testing purposes
|
||||
Certificates: []tls.Certificate{loadTLSCertificate()},
|
||||
}
|
||||
|
||||
// Listen on a random port
|
||||
listener, err := tls.Listen("tcp", "localhost:0", s.TLSConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Start the server
|
||||
go func() {
|
||||
if err := s.Serve(listener); err != nil {
|
||||
log.Println("Server stopped:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return listener, s
|
||||
}
|
||||
|
||||
func loadTLSCertificate() tls.Certificate {
|
||||
cert, err := tls.LoadX509KeyPair("cert_test.pem", "key_test.pem")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load TLS certificate:", err)
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func TestIMAPClient(t *testing.T) {
|
||||
listener, srv := setupTestServer()
|
||||
defer listener.Close()
|
||||
defer srv.Close() // Ensure the server is closed properly
|
||||
|
||||
// Use the existing user in the memory backend
|
||||
config := &client.Config{
|
||||
Username: "username", // Use the predefined username
|
||||
Password: "password", // Use the predefined password
|
||||
Server: listener.Addr().String(),
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
imapClient := client.NewIMAP(config)
|
||||
|
||||
// Test connection
|
||||
err := imapClient.Connect()
|
||||
assert.NoError(t, err, "Failed to connect")
|
||||
|
||||
// Test disconnection
|
||||
err = imapClient.Disconnect()
|
||||
assert.NoError(t, err, "Failed to disconnect")
|
||||
}
|
||||
|
||||
func TestListMessages(t *testing.T) {
|
||||
listener, srv := setupTestServer()
|
||||
defer listener.Close()
|
||||
defer srv.Close() // Ensure the server is closed properly
|
||||
|
||||
// Use the existing user in the memory backend
|
||||
config := &client.Config{
|
||||
Username: "username", // Use the predefined username
|
||||
Password: "password", // Use the predefined password
|
||||
Server: listener.Addr().String(),
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
imapClient := client.NewIMAP(config)
|
||||
|
||||
// Connect to the server
|
||||
err := imapClient.Connect()
|
||||
assert.NoError(t, err, "Failed to connect")
|
||||
defer imapClient.Disconnect()
|
||||
|
||||
// Define the message config to grab specific parts, including body
|
||||
messageConfig := client.MessageConfig{
|
||||
GrabID: true,
|
||||
GrabFrom: true,
|
||||
GrabSubject: true,
|
||||
GrabBody: true, // Grab the full body
|
||||
}
|
||||
|
||||
// List only one message from the INBOX
|
||||
messages, err := imapClient.ListMessages("INBOX", 1, messageConfig)
|
||||
assert.NoError(t, err, "Failed to list messages")
|
||||
|
||||
// Validate that only one message is returned
|
||||
assert.Len(t, messages, 1, "Expected 1 message")
|
||||
|
||||
// Validate the content of the message
|
||||
expectedBody := "From: contact@example.org\r\n" +
|
||||
"To: contact@example.org\r\n" +
|
||||
"Subject: A little message, just for you\r\n" +
|
||||
"Date: Wed, 11 May 2016 14:31:59 +0000\r\n" +
|
||||
"Message-ID: <0000000@localhost/>\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"\r\n" +
|
||||
"Hi there :)"
|
||||
|
||||
assert.Equal(t, expectedBody, messages[0][client.KeyBody], "Unexpected body content")
|
||||
}
|
||||
|
||||
func TestListMailboxes(t *testing.T) {
|
||||
listener, srv := setupTestServer()
|
||||
defer listener.Close()
|
||||
defer srv.Close() // Ensure the server is closed properly
|
||||
|
||||
// Use the existing user in the memory backend
|
||||
config := &client.Config{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
Server: listener.Addr().String(),
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
imapClient := client.NewIMAP(config)
|
||||
|
||||
// Connect to the server
|
||||
err := imapClient.Connect()
|
||||
assert.NoError(t, err, "Failed to connect")
|
||||
defer imapClient.Disconnect()
|
||||
|
||||
// List mailboxes
|
||||
mailboxes, err := imapClient.ListMailboxes()
|
||||
assert.NoError(t, err, "Failed to list mailboxes")
|
||||
|
||||
// Validate the mailboxes
|
||||
expectedMailboxes := []string{"INBOX"}
|
||||
assert.ElementsMatch(t, expectedMailboxes, mailboxes, "Unexpected mailboxes")
|
||||
}
|
52
client/key_test.pem
Normal file
52
client/key_test.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
EY5pJs7DKuAhXPqtWhEDrO79MoVdqA==
|
||||
-----END PRIVATE KEY-----
|
38
client/list_mailboxes.go
Normal file
38
client/list_mailboxes.go
Normal file
@ -0,0 +1,38 @@
|
||||
// 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"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// ListMailboxes retrieves all available mailboxes for the connected user
|
||||
func (c *IMAPClient) ListMailboxes() ([]string, error) {
|
||||
if c.client == nil {
|
||||
return nil, ErrorsClientIsNotConnected
|
||||
}
|
||||
|
||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||
done := make(chan error, 1)
|
||||
|
||||
// Start listing mailboxes
|
||||
go func() {
|
||||
done <- c.client.List("", "*", mailboxes)
|
||||
}()
|
||||
|
||||
var mailboxNames []string
|
||||
for m := range mailboxes {
|
||||
mailboxNames = append(mailboxNames, m.Name)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
return nil, fmt.Errorf("failed to list mailboxes: %v", err)
|
||||
}
|
||||
|
||||
return mailboxNames, nil
|
||||
}
|
@ -7,44 +7,50 @@ package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"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"
|
||||
// KeySender is the key for the sender's address in the envelope
|
||||
KeySender = "sender"
|
||||
// KeyReplyTo is the key for the reply-to addresses
|
||||
KeyReplyTo = "replyTo"
|
||||
// KeyTo is the key for the 'To' addresses
|
||||
KeyTo = "to"
|
||||
// KeyCc is the key for the 'Cc' addresses
|
||||
KeyCc = "cc"
|
||||
// KeyBcc is the key for the 'Bcc' addresses
|
||||
KeyBcc = "bcc"
|
||||
// KeyInReplyTo is the key for the in-reply-to identifier
|
||||
KeyInReplyTo = "inReplyTo"
|
||||
// KeyDate is the key for the message date
|
||||
KeyDate = "date"
|
||||
)
|
||||
|
||||
// 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")
|
||||
return nil, ErrorsClientIsNotConnected
|
||||
}
|
||||
|
||||
mbox, err := c.client.Select(mailbox, false)
|
||||
mbox, err := c.selectMailbox(mailbox)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select %s: %v", mailbox, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
from := uint32(1)
|
||||
to := mbox.Messages
|
||||
if mbox.Messages > numMessages {
|
||||
from = mbox.Messages - numMessages + 1
|
||||
}
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(from, to)
|
||||
|
||||
items := []imap.FetchItem{imap.FetchEnvelope}
|
||||
if config.GrabBody {
|
||||
items = append(items, imap.FetchItem("BODY.PEEK[]"))
|
||||
}
|
||||
seqset := c.createSeqSet(mbox.Messages, numMessages)
|
||||
items := c.getFetchItems(config)
|
||||
|
||||
messages := make(chan *imap.Message, numMessages)
|
||||
done := make(chan error, 1)
|
||||
@ -52,32 +58,9 @@ func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config Mes
|
||||
done <- c.client.Fetch(seqset, items, messages)
|
||||
}()
|
||||
|
||||
var results []MessageDetails
|
||||
for msg := range messages {
|
||||
details := MessageDetails{}
|
||||
|
||||
if config.GrabID {
|
||||
details.ID = msg.Envelope.MessageId
|
||||
}
|
||||
if config.GrabFrom {
|
||||
for _, addr := range msg.Envelope.From {
|
||||
details.From = append(details.From, addr.Address())
|
||||
}
|
||||
}
|
||||
if config.GrabSubject {
|
||||
details.Subject = 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()
|
||||
}
|
||||
buf.Reset() // Reset the buffer before returning it to the pool.
|
||||
bytebufferpool.Put(buf)
|
||||
}
|
||||
}
|
||||
results = append(results, details)
|
||||
results, err := c.processMessages(messages, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
@ -87,8 +70,137 @@ func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config Mes
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// selectMailbox selects the specified mailbox and returns its status.
|
||||
// It returns an error if the mailbox cannot be selected.
|
||||
func (c *IMAPClient) selectMailbox(mailbox string) (*imap.MailboxStatus, error) {
|
||||
mbox, err := c.client.Select(mailbox, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select %s: %v", mailbox, err)
|
||||
}
|
||||
return mbox, nil
|
||||
}
|
||||
|
||||
// createSeqSet creates a sequence set for fetching messages based on the total and desired number of messages.
|
||||
func (c *IMAPClient) createSeqSet(totalMessages, numMessages uint32) *imap.SeqSet {
|
||||
from := uint32(1)
|
||||
to := totalMessages
|
||||
if totalMessages > numMessages {
|
||||
from = totalMessages - numMessages + 1
|
||||
}
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(from, to)
|
||||
return seqset
|
||||
}
|
||||
|
||||
// getFetchItems determines which parts of the message to fetch based on the MessageConfig.
|
||||
func (c *IMAPClient) getFetchItems(config MessageConfig) []imap.FetchItem {
|
||||
items := []imap.FetchItem{imap.FetchEnvelope}
|
||||
if config.GrabBody {
|
||||
items = append(items, imap.FetchItem("BODY.PEEK[]"))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// processMessages processes fetched messages and extracts details based on the MessageConfig.
|
||||
func (c *IMAPClient) processMessages(messages chan *imap.Message, config MessageConfig) ([]map[string]any, error) {
|
||||
var results []map[string]any
|
||||
for msg := range messages {
|
||||
details := c.extractDetails(msg, config)
|
||||
if details != nil {
|
||||
results = append(results, details)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// extractDetails extracts message details based on the MessageConfig.
|
||||
func (c *IMAPClient) extractDetails(msg *imap.Message, config MessageConfig) map[string]any {
|
||||
details := make(map[string]any)
|
||||
|
||||
// Add message ID if configured
|
||||
c.addIfNotEmpty(details, KeyID, config.GrabID, msg.Envelope.MessageId)
|
||||
// Add 'From' addresses if configured
|
||||
c.addAddresses(details, KeyFrom, config.GrabFrom, msg.Envelope.From)
|
||||
// Add subject if configured
|
||||
c.addIfNotEmpty(details, KeySubject, config.GrabSubject, msg.Envelope.Subject)
|
||||
// Add body if configured
|
||||
c.addBody(details, KeyBody, config.GrabBody, msg.Body)
|
||||
// Add sender if configured
|
||||
c.addAddresses(details, KeySender, config.GrabSender, msg.Envelope.Sender)
|
||||
// Add reply-to addresses if configured
|
||||
c.addAddresses(details, KeyReplyTo, config.GrabReplyTo, msg.Envelope.ReplyTo)
|
||||
// Add 'To' addresses if configured
|
||||
c.addAddresses(details, KeyTo, config.GrabTo, msg.Envelope.To)
|
||||
// Add 'Cc' addresses if configured
|
||||
c.addAddresses(details, KeyCc, config.GrabCc, msg.Envelope.Cc)
|
||||
// Add 'Bcc' addresses if configured
|
||||
c.addAddresses(details, KeyBcc, config.GrabBcc, msg.Envelope.Bcc)
|
||||
// Add in-reply-to ID if configured
|
||||
c.addIfNotEmpty(details, KeyInReplyTo, config.GrabInReplyTo, msg.Envelope.InReplyTo)
|
||||
// Add date if configured
|
||||
c.addIfNotZero(details, KeyDate, config.GrabDate, msg.Envelope.Date)
|
||||
|
||||
return details
|
||||
}
|
||||
|
||||
// addIfNotEmpty adds a value to details if it's not empty or nil and grabbing is enabled.
|
||||
func (c *IMAPClient) addIfNotEmpty(details map[string]any, key string, grab bool, value string) {
|
||||
if grab && value != "" && value != "<nil>" {
|
||||
details[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// addAddresses adds email addresses to details if grabbing is enabled.
|
||||
func (c *IMAPClient) addAddresses(details map[string]any, key string, grab bool, addresses []*imap.Address) {
|
||||
if grab && len(addresses) > 0 {
|
||||
details[key] = c.extractAddresses(addresses)
|
||||
}
|
||||
}
|
||||
|
||||
// addBody adds the message body to details if grabbing is enabled.
|
||||
func (c *IMAPClient) addBody(details map[string]any, key string, grab bool, body map[*imap.BodySectionName]imap.Literal) {
|
||||
if grab {
|
||||
content, err := c.extractBody(body)
|
||||
if err == nil && content != "" {
|
||||
details[key] = content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addIfNotZero adds a date to details if it's not zero and grabbing is enabled.
|
||||
func (c *IMAPClient) addIfNotZero(details map[string]any, key string, grab bool, date time.Time) {
|
||||
if grab && !date.IsZero() {
|
||||
details[key] = date
|
||||
}
|
||||
}
|
||||
|
||||
// extractAddresses extracts email addresses from a list of IMAP addresses.
|
||||
func (c *IMAPClient) extractAddresses(addresses []*imap.Address) []string {
|
||||
var from []string
|
||||
for _, addr := range addresses {
|
||||
from = append(from, addr.Address())
|
||||
}
|
||||
return from
|
||||
}
|
||||
|
||||
// extractBody reads and returns the body content from the message body literals.
|
||||
func (c *IMAPClient) extractBody(body map[*imap.BodySectionName]imap.Literal) (string, error) {
|
||||
for _, literal := range body {
|
||||
buf := bytebufferpool.Get()
|
||||
if _, err := buf.ReadFrom(literal); err == nil {
|
||||
result := buf.String()
|
||||
buf.Reset() // Reset the buffer before returning it to the pool.
|
||||
bytebufferpool.Put(buf)
|
||||
return result, nil
|
||||
}
|
||||
bytebufferpool.Put(buf)
|
||||
return "", ErrorsFailedToReadBody
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
|
64
export/docs.go
Normal file
64
export/docs.go
Normal file
@ -0,0 +1,64 @@
|
||||
// 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 provides functionality to export email messages
|
||||
// in various formats. It defines interfaces and implementations
|
||||
// for exporting messages, allowing flexibility in how messages
|
||||
// are written to different outputs.
|
||||
//
|
||||
// # Features
|
||||
// - Define a standard interface for exporting messages
|
||||
// - Implement JSON export functionality with customizable encoding
|
||||
//
|
||||
// # Usage
|
||||
//
|
||||
// JSON Export Example:
|
||||
//
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
package export
|
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
|
||||
}
|
49
export/exporter_test.go
Normal file
49
export/exporter_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.b0zal.io/H0llyW00dzZ/imap/export"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
)
|
||||
|
||||
func TestJSONExporter(t *testing.T) {
|
||||
// Create a sample message
|
||||
messages := []map[string]any{
|
||||
{
|
||||
"id": "1",
|
||||
"from": []string{"contact@example.org"},
|
||||
"subject": "A little message, just for you",
|
||||
"body": "Hi there :)",
|
||||
},
|
||||
}
|
||||
|
||||
// Get a buffer from the pool
|
||||
buffer := bytebufferpool.Get()
|
||||
defer func() {
|
||||
buffer.Reset() // Reset the buffer to prevent data leaks
|
||||
bytebufferpool.Put(buffer) // Return the buffer to the pool
|
||||
}()
|
||||
|
||||
// Create a JSONExporter with the default encoder
|
||||
exporter := &export.JSONExporter{
|
||||
Encoder: export.DefaultJSONEncoder,
|
||||
}
|
||||
|
||||
// Export the messages to the buffer
|
||||
err := exporter.Export(messages, buffer)
|
||||
assert.NoError(t, err, "Failed to export messages")
|
||||
|
||||
// Define the expected JSON output
|
||||
expectedJSON := `[{"body":"Hi there :)","from":["contact@example.org"],"id":"1","subject":"A little message, just for you"}]
|
||||
`
|
||||
|
||||
// Assert that the buffer content matches the expected JSON
|
||||
assert.Equal(t, expectedJSON, buffer.String(), "Unexpected JSON output")
|
||||
}
|
35
export/json_exporter.go
Normal file
35
export/json_exporter.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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 as a JSON array using the custom JSON encoder
|
||||
func (e *JSONExporter) Export(messages []map[string]any, writer io.Writer) error {
|
||||
// Use the encoder to write the entire slice as a JSON array
|
||||
if err := e.Encoder(messages, writer); err != nil {
|
||||
return fmt.Errorf("failed to encode messages 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)
|
||||
}
|
6
go.mod
6
go.mod
@ -8,6 +8,12 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emersion/go-message v0.15.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
11
go.sum
11
go.sum
@ -1,11 +1,19 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0 h1:urgKGqt2JAc9NFJcgncQcohHdiYb803YTH9OQwHBHIY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -14,3 +22,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
Loading…
x
Reference in New Issue
Block a user