ultisuite-backend/internal/mail/imap/folders.go
R3D347HR4Y bb5be669c1 Implement IMAP sync pipeline with rules and webhook support
- Introduced a new sync pipeline for IMAP that integrates a rules engine and webhook execution.
- Enhanced the `SyncWorker` to support attachment management and folder synchronization.
- Added functionality to detect special folder types (Sent, Drafts, Trash, Archive, Spam) during sync.
- Implemented a database schema for tracking rule executions and their outcomes.
- Created unit tests for the new rules engine and webhook execution logic.
- Updated migration scripts to accommodate new database structures for rule executions and folder states.
- Enhanced error handling and logging throughout the sync process for better observability.
2026-05-22 17:38:39 +02:00

111 lines
2.6 KiB
Go

package imap
import (
"strings"
"github.com/emersion/go-imap/v2"
)
var folderDisplayNames = map[string]string{
"inbox": "Inbox",
"sent": "Sent",
"drafts": "Drafts",
"trash": "Trash",
"archive": "Archive",
"spam": "Spam",
}
var folderNameHeuristics = map[string]string{
"inbox": "inbox",
"sent": "sent",
"sent items": "sent",
"sent messages": "sent",
"[gmail]/sent mail": "sent",
"inbox.sent": "sent",
"drafts": "drafts",
"[gmail]/drafts": "drafts",
"trash": "trash",
"deleted": "trash",
"[gmail]/trash": "trash",
"archive": "archive",
"all mail": "archive",
"[gmail]/all mail": "archive",
"junk": "spam",
"spam": "spam",
"[gmail]/spam": "spam",
}
// DetectFolderType classifies an IMAP mailbox for sync using SPECIAL-USE
// attributes when present, otherwise name heuristics. NoSelect mailboxes
// return "" so callers can skip sync.
func DetectFolderType(mailbox string, attrs []imap.MailboxAttr) string {
for _, attr := range attrs {
if attr == imap.MailboxAttrNoSelect {
return ""
}
}
if folderType := folderTypeFromAttrs(attrs); folderType != "" {
return folderType
}
return folderTypeFromName(mailbox)
}
// DisplayName returns a user-facing folder label from the detected type,
// or the last path segment of mailbox when type is custom or unknown.
func DisplayName(mailbox, folderType string) string {
folderType = strings.ToLower(strings.TrimSpace(folderType))
if name, ok := folderDisplayNames[folderType]; ok {
return name
}
return mailboxLeaf(mailbox)
}
func folderTypeFromAttrs(attrs []imap.MailboxAttr) string {
for _, attr := range attrs {
switch attr {
case imap.MailboxAttrSent:
return "sent"
case imap.MailboxAttrDrafts:
return "drafts"
case imap.MailboxAttrTrash:
return "trash"
case imap.MailboxAttrArchive:
return "archive"
case imap.MailboxAttrJunk:
return "spam"
}
}
return ""
}
func folderTypeFromName(mailbox string) string {
full := strings.ToLower(strings.TrimSpace(mailbox))
if folderType, ok := folderNameHeuristics[full]; ok {
return folderType
}
leaf := strings.ToLower(mailboxLeaf(mailbox))
if folderType, ok := folderNameHeuristics[leaf]; ok {
return folderType
}
return "custom"
}
func mailboxLeaf(mailbox string) string {
mailbox = strings.TrimSpace(mailbox)
if mailbox == "" {
return ""
}
leaf := mailbox
for _, sep := range []string{"/", "."} {
if idx := strings.LastIndex(leaf, sep); idx >= 0 && idx < len(leaf)-len(sep) {
leaf = leaf[idx+len(sep):]
}
}
return leaf
}