First Commit
- [+] feat: add initial implementation of IMAP client package with support for single and multiple user management - [+] chore: add .gitignore file to exclude binaries, IDE files, and environment configurations - [+] docs: add README file with project description, features, installation, usage examples, and license information - [+] docs: add BSD 3-Clause License file - [+] feat(client): implement configuration and connection handling for IMAP clients - [+] feat(client): implement message listing functionality for single and multiple users - [+] feat(client): add support for secure TLS connections in IMAP client - [+] feat(client): create helper functions for initializing single and multi-user IMAP clients - [+] chore: initialize Go module and add dependencies for IMAP and byte buffer pooling
This commit is contained in:
commit
464fdc1bbd
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with "go test -c"
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
# https://idx.google.com it's free
|
||||||
|
.idx
|
||||||
|
|
||||||
|
# .env file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Project build
|
||||||
|
bin
|
||||||
|
|
||||||
|
emails.csv
|
||||||
|
run.go
|
30
LICENSE
Normal file
30
LICENSE
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Copyright (c) 2024, H0llyW00dzZ / H0llyW00dz
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
120
README.md
Normal file
120
README.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# IMAP Client Package
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Connect and disconnect from an IMAP server
|
||||||
|
- List messages in a specified mailbox
|
||||||
|
- Manage multiple users with separate IMAP clients
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install the package, use `go get`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get git.b0zal.io/H0llyW00dzZ/imap
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Single User
|
||||||
|
|
||||||
|
```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()
|
||||||
|
|
||||||
|
messageConfig := client.MessageConfig{
|
||||||
|
GrabID: true,
|
||||||
|
GrabFrom: true,
|
||||||
|
GrabSubject: true,
|
||||||
|
GrabBody: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err := imapClient.ListMessages("INBOX", 10, messageConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to list messages: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range messages {
|
||||||
|
fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg.ID, msg.From, msg.Subject, msg.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Users
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.b0zal.io/H0llyW00dzZ/imap/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
multiUserIMAP := client.NewMultiUserIMAP()
|
||||||
|
|
||||||
|
multiUserIMAP.AddUser("user1@example.com", "password1", "imap.example.com:993", true)
|
||||||
|
multiUserIMAP.AddUser("user2@example.com", "password2", "imap.example.com:993", true)
|
||||||
|
|
||||||
|
err := multiUserIMAP.ConnectUser("user1@example.com")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect user1: %v", err)
|
||||||
|
}
|
||||||
|
defer multiUserIMAP.DisconnectUser("user1@example.com")
|
||||||
|
|
||||||
|
messageConfig := client.MessageConfig{
|
||||||
|
GrabID: true,
|
||||||
|
GrabFrom: true,
|
||||||
|
GrabSubject: true,
|
||||||
|
GrabBody: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err := multiUserIMAP.ListUserMessages("user1@example.com", "INBOX", 10, messageConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to list messages for user1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range messages {
|
||||||
|
fmt.Printf("ID: %s, From: %v, Subject: %s, Body: %s\n", msg.ID, msg.From, msg.Subject, msg.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- Implement functionality to send emails.
|
||||||
|
- Add support for folder management (create, delete, rename).
|
||||||
|
- Enhance error handling and logging.
|
||||||
|
- Support for message search and filtering.
|
||||||
|
- Implement message deletion and flagging.
|
||||||
|
- Add more tests for edge cases and concurrent access.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details.
|
30
client/config.go
Normal file
30
client/config.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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 "github.com/emersion/go-imap/client"
|
||||||
|
|
||||||
|
// Config holds the configuration for the IMAP client
|
||||||
|
type Config struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Server string
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageConfig defines what parts of the message to retrieve
|
||||||
|
type MessageConfig struct {
|
||||||
|
GrabID bool
|
||||||
|
GrabFrom bool
|
||||||
|
GrabSubject bool
|
||||||
|
GrabBody bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMAPClient represents an IMAP client
|
||||||
|
type IMAPClient struct {
|
||||||
|
config *Config
|
||||||
|
client *client.Client
|
||||||
|
}
|
84
client/connection.go
Normal file
84
client/connection.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect establishes a connection to the IMAP server
|
||||||
|
func (c *IMAPClient) Connect() error {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: c.config.InsecureSkipVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := client.DialTLS(c.config.Server, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cl.Login(c.config.Username, c.config.Password); err != nil {
|
||||||
|
return fmt.Errorf("failed to login: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.client = cl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect logs out and closes the connection
|
||||||
|
func (c *IMAPClient) Disconnect() error {
|
||||||
|
if c.client != nil {
|
||||||
|
if err := c.client.Logout(); err != nil {
|
||||||
|
return fmt.Errorf("failed to logout: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUser adds a new user to the MultiUserIMAP
|
||||||
|
func (m *MultiUserIMAP) AddUser(username, password, server string, insecureSkipVerify bool) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
config := &Config{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Server: server,
|
||||||
|
InsecureSkipVerify: insecureSkipVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := NewIMAP(config)
|
||||||
|
m.clients[username] = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectUser connects a specific user
|
||||||
|
func (m *MultiUserIMAP) ConnectUser(username string) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
client, exists := m.clients[username]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("user not found: %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisconnectUser disconnects a specific user
|
||||||
|
func (m *MultiUserIMAP) DisconnectUser(username string) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
client, exists := m.clients[username]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("user not found: %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Disconnect()
|
||||||
|
}
|
102
client/list_messages.go
Normal file
102
client/list_messages.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// 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"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMessages lists the messages in the specified mailbox based on the MessageConfig
|
||||||
|
func (c *IMAPClient) ListMessages(mailbox string, numMessages uint32, config MessageConfig) ([]MessageDetails, error) {
|
||||||
|
if c.client == nil {
|
||||||
|
return nil, fmt.Errorf("client is not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
mbox, err := c.client.Select(mailbox, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to select %s: %v", mailbox, 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[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := make(chan *imap.Message, numMessages)
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
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()
|
||||||
|
defer bytebufferpool.Put(buf)
|
||||||
|
|
||||||
|
_, err := buf.ReadFrom(literal)
|
||||||
|
if err == nil {
|
||||||
|
details.Body = buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results = append(results, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch messages: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, 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) {
|
||||||
|
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.ListMessages(mailbox, numMessages, config)
|
||||||
|
}
|
17
client/new.go
Normal file
17
client/new.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
// NewIMAP creates a new IMAP client with the given configuration
|
||||||
|
func NewIMAP(config *Config) *IMAPClient {
|
||||||
|
if config == nil {
|
||||||
|
config = &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IMAPClient{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
21
client/new_multi_user.go
Normal file
21
client/new_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 "sync"
|
||||||
|
|
||||||
|
// MultiUserIMAP manages multiple IMAP clients for different users
|
||||||
|
type MultiUserIMAP struct {
|
||||||
|
clients map[string]*IMAPClient
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiUserIMAP initializes a new MultiUserIMAP
|
||||||
|
func NewMultiUserIMAP() *MultiUserIMAP {
|
||||||
|
return &MultiUserIMAP{
|
||||||
|
clients: make(map[string]*IMAPClient),
|
||||||
|
}
|
||||||
|
}
|
13
go.mod
Normal file
13
go.mod
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module git.b0zal.io/H0llyW00dzZ/imap
|
||||||
|
|
||||||
|
go 1.23.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/emersion/go-imap v1.2.1
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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/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-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
|
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=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
Loading…
x
Reference in New Issue
Block a user