Author: LakeFox
Email: [email protected]
Date: Sun, 20 Apr 2025 21:14:43 -0600
email/main.go
Delete
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)
}