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) }