Commits
Diff
package email
import (
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"io"
"log"
"mime/quotedprintable"
"net/mail"
"strings"
)
type Inbox struct {
Client *client.Client
Threads []Thread
}
type Thread struct {
Tag string `json:"tag" xml:"tag" text:"Tag"`
Root Message `json:"root" xml:"root" text:"Root"`
Subject string `json:"subject" xml:"subject" text:"Subject"`
ThreadId string `json:"threadid" xml:"threadid" text:"Thread Id"`
}
type Message struct {
Children []*Message `json:"children" xml:"children>message" text:"Children"`
InReplyTo string `json:"inreplyto" xml:"inreplyto" text:"In Reply To,omitempty"`
To string `json:"to" xml:"to" text:"To"`
From string `json:"from" xml:"from" text:"from"`
Cc string `json:"cc" xml:"cc" text:"CC"`
Bcc string `json:"bcc" xml:"bcc" text:"BCC"`
Tag string `json:"tag" xml:"tag" text:"Tag"`
Subject string `json:"subject" xml:"subject" text:"Subject"`
Body string `json:"body" xml:"body" text:"Body"`
MessageId string `json:"messageId" xml:"messageid" text:"Message ID"`
}
func Connect(server, username, password string) Inbox {
// Connect to server
c, err := client.DialTLS(server, nil)
if err != nil {
log.Fatal(err)
}
// Login
if err := c.Login(username, password); err != nil {
log.Fatal(err)
}
return Inbox{
Threads: []Thread{},
Client: c,
}
}
func (i *Inbox) GetMessages(inbox string) {
mbox, _ := i.Client.Select(inbox, false)
from := uint32(1)
to := mbox.Messages
seqset := new(imap.SeqSet)
seqset.AddRange(from, to)
messages := make(chan *imap.Message, 10)
done := make(chan error, 1)
go func() {
done <- i.Client.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, imap.FetchItem("BODY.PEEK[]")}, messages)
}()
// Initialize a map to store messages by their In-Reply-To for efficient parent-child linking
messageMap := make(map[string]*Message)
for msg := range messages {
tags := parseTags(msg.Envelope.Subject)
if len(tags) > 0 {
m := Message{
Children: []*Message{},
}
holder := ""
for _, bodyPart := range msg.Body {
if bodyPart != nil {
b, _ := io.ReadAll(bodyPart)
holder += string(b)
}
}
msgReader, _ := mail.ReadMessage(strings.NewReader(holder))
body, _ := io.ReadAll(msgReader.Body)
decoder := quotedprintable.NewReader(strings.NewReader(string(body)))
decoded, _ := io.ReadAll(decoder)
m.Body = string(decoded)
m.Tag = tags[0]
m.Subject = parseSubject(msg.Envelope.Subject)
m.InReplyTo = msg.Envelope.InReplyTo
var to, from, cc, bcc string
for _, v := range msg.Envelope.To {
to += ", "+v.AtDomainList
}
for _, v := range msg.Envelope.From {
from += ", "+v.AtDomainList
}
for _, v := range msg.Envelope.Cc {
cc += ", "+v.AtDomainList
}
for _, v := range msg.Envelope.Bcc {
bcc += ", "+v.AtDomainList
}
m.To = to
m.From = from
m.Cc = cc
m.Bcc = bcc
m.InReplyTo = msg.Envelope.InReplyTo
// Store the message in the map using its Message-ID as the key
messageID := msg.Envelope.MessageId
m.MessageId = messageID[1:strings.Index(messageID,"@")]
messageMap[messageID] = &m
}
}
// Link parent and child messages
for _, msg := range messageMap {
if msg.InReplyTo != "" { // Only process root-level messages initially
if parentMsg, ok := messageMap[msg.InReplyTo]; ok {
parentMsg.Children = append(parentMsg.Children, msg)
}
}
}
// Create threads from root-level messages
for _, v := range messageMap {
if v.InReplyTo == "" {
thread := Thread{
Tag: v.Tag,
Root: *v, // Dereference here to avoid storing a pointer in the Thread root
Subject: v.Subject,
ThreadId: v.MessageId,
}
i.Threads = append(i.Threads, thread)
}
}
}
func parseTags(subject string) []string {
tags := []string{}
currentTag := ""
record := false
for _, v := range subject {
if v == ']' && record {
record = false
tags = append(tags, currentTag)
currentTag = ""
}
if record {
currentTag += string(v)
}
if v == '[' && !record {
record = true
}
}
return tags
}
func parseSubject(subject string) string {
var title string
for i := len(subject) - 1; i >= 0; i-- {
if subject[i] == ']' {
break
} else {
title = string(subject[i]) + title
}
}
return strings.TrimSpace(title)
}