Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ require (
)

replace (
github.com/ProtonMail/gluon => ../gluon
github.com/ProtonMail/go-autostart => github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
Expand Down
98 changes: 98 additions & 0 deletions internal/services/imapservice/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,104 @@ func (s *Connector) MarkMessagesForwarded(ctx context.Context, _ connector.IMAPS
return s.client.MarkMessagesUnForwarded(ctx, usertypes.MapTo[imap.MessageID, string](messageIDs)...)
}

func (s *Connector) MarkMessagesWithGmailLabels(ctx context.Context, _ connector.IMAPStateWrite, messageIDs []imap.MessageID, labels []string, add bool) error {
msgIDs := usertypes.MapTo[imap.MessageID, string](messageIDs)

for _, labelName := range labels {
labelID, err := s.resolveGmailLabelID(ctx, labelName, add)
if err != nil {
return err
}

if labelID == "" {
// Label not found and we're removing — skip.
continue
}

if add {
if err := s.client.LabelMessages(ctx, msgIDs, labelID); err != nil {
return fmt.Errorf("failed to label messages with %q: %w", labelName, err)
}
} else {
if err := s.client.UnlabelMessages(ctx, msgIDs, labelID); err != nil {
return fmt.Errorf("failed to unlabel messages with %q: %w", labelName, err)
}
}
}

return nil
}

// resolveGmailLabelID looks up a Proton label by name. If the label does not exist and add is true,
// it creates a new label. Returns empty string if the label does not exist and add is false.
func (s *Connector) resolveGmailLabelID(ctx context.Context, labelName string, create bool) (string, error) {
rLabels := s.labels.Read()
label, ok := rLabels.GetLabelByName(labelName)
rLabels.Close()

if ok {
return label.ID, nil
}

if !create {
return "", nil
}

// Auto-create the label.
newLabel, err := s.client.CreateLabel(ctx, proton.CreateLabelReq{
Name: labelName,
Color: "#f66",
Type: proton.LabelTypeLabel,
})
if err != nil {
return "", fmt.Errorf("failed to create label %q: %w", labelName, err)
}

wLabels := s.labels.Write()
wLabels.SetLabel(newLabel.ID, newLabel, "resolveGmailLabelID")
wLabels.Close()

logrus.WithFields(logrus.Fields{
"labelName": labelName,
"labelID": newLabel.ID,
}).Info("Auto-created Proton label for X-GM-LABELS")

return newLabel.ID, nil
}

func (s *Connector) GetGmailLabels(ctx context.Context, messageID imap.MessageID) ([]string, error) {
msg, err := s.client.GetMessage(ctx, string(messageID))
if err != nil {
return nil, fmt.Errorf("failed to get message %v: %w", messageID, err)
}

rLabels := s.labels.Read()
defer rLabels.Close()

var labels []string

for _, labelID := range msg.LabelIDs {
label, ok := rLabels.GetLabel(labelID)
if ok && label.Type == proton.LabelTypeLabel {
labels = append(labels, label.Name)
}
}

return labels, nil
}

func (s *Connector) GetGmailLabelMailboxID(_ context.Context, label string) (imap.MailboxID, bool) {
rLabels := s.labels.Read()
defer rLabels.Close()

l, ok := rLabels.GetLabelByName(label)
if !ok {
return "", false
}

return imap.MailboxID(l.ID), true
}

func (s *Connector) GetUpdates() <-chan imap.Update {
return s.updateCh.GetChannel()
}
Expand Down
19 changes: 19 additions & 0 deletions internal/services/imapservice/shared_labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type labelsRead interface {
Close()
GetLabel(id string) (proton.Label, bool)
GetLabels() []proton.Label
GetLabelByName(name string) (proton.Label, bool)
}

type labelsWrite interface {
Expand Down Expand Up @@ -89,6 +90,16 @@ func (r *rwLabels) getLabelsUnsafe() []proton.Label {
return maps.Values(r.labels)
}

func (r *rwLabels) getLabelByNameUnsafe(name string) (proton.Label, bool) {
for _, label := range r.labels {
if label.Type == proton.LabelTypeLabel && label.Name == name {
return label, true
}
}

return proton.Label{}, false
}

func (r *rwLabels) SetLabels(labels []proton.Label) {
r.lock.Lock()
defer r.lock.Unlock()
Expand Down Expand Up @@ -134,6 +145,10 @@ func (r rwLabelsRead) GetLabels() []proton.Label {
return r.rw.getLabelsUnsafe()
}

func (r rwLabelsRead) GetLabelByName(name string) (proton.Label, bool) {
return r.rw.getLabelByNameUnsafe(name)
}

type rwLabelsWrite struct {
rw *rwLabels
}
Expand All @@ -150,6 +165,10 @@ func (r rwLabelsWrite) GetLabels() []proton.Label {
return r.rw.getLabelsUnsafe()
}

func (r rwLabelsWrite) GetLabelByName(name string) (proton.Label, bool) {
return r.rw.getLabelByNameUnsafe(name)
}

func (r rwLabelsWrite) SetLabel(id string, label proton.Label, actionSource string) {
logAction("SetLabel", actionSource, label.ID)
r.rw.labels[id] = label
Expand Down