diff --git a/go.mod b/go.mod index a73bd7a..112d117 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/vasyansk/imap-copier go 1.22.0 -require github.com/jackc/pgx/v5 v5.7.0 +require ( + github.com/emersion/go-imap/v2 v2.0.0-beta.8 + github.com/jackc/pgx/v5 v5.7.0 +) require ( github.com/jackc/pgpassfile v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7bacfd8..924a0ce 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emersion/go-imap/v2 v2.0.0-beta.8 h1:5IXZK1E33DyeP526320J3RS7eFlCYGFgtbrfapqDPug= +github.com/emersion/go-imap/v2 v2.0.0-beta.8/go.mod h1:dhoFe2Q0PwLrMD7oZw8ODuaD0vLYPe5uj2wcOMnvh48= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= diff --git a/internal/imapx/messagekey.go b/internal/imapx/messagekey.go new file mode 100644 index 0000000..09a50fe --- /dev/null +++ b/internal/imapx/messagekey.go @@ -0,0 +1,37 @@ +package imapx + +import ( + "crypto/md5" + "fmt" + "strings" + + "github.com/emersion/go-imap/v2" +) + +func MessageKey(env *imap.Envelope, size int64) string { + if env != nil && env.MessageID != "" { + return env.MessageID + } + var b strings.Builder + if env != nil { + b.WriteString(addrList(env.From)) + b.WriteByte('|') + b.WriteString(addrList(env.To)) + b.WriteByte('|') + b.WriteString(env.Subject) + b.WriteByte('|') + b.WriteString(env.Date.UTC().Format("2006-01-02T15:04:05Z")) + } + b.WriteByte('|') + fmt.Fprintf(&b, "%d", size) + sum := md5.Sum([]byte(b.String())) + return fmt.Sprintf("h:%x", sum) +} + +func addrList(addrs []imap.Address) string { + parts := make([]string, 0, len(addrs)) + for _, a := range addrs { + parts = append(parts, a.Mailbox+"@"+a.Host) + } + return strings.Join(parts, ",") +} diff --git a/internal/imapx/messagekey_test.go b/internal/imapx/messagekey_test.go new file mode 100644 index 0000000..8d46c57 --- /dev/null +++ b/internal/imapx/messagekey_test.go @@ -0,0 +1,32 @@ +package imapx + +import ( + "testing" + "time" + + "github.com/emersion/go-imap/v2" +) + +func TestMessageKeyPrefersMessageID(t *testing.T) { + env := &imap.Envelope{MessageID: ""} + if got := MessageKey(env, 100); got != "" { + t.Fatalf("got %q, want ", got) + } +} + +func TestMessageKeyFallbackStable(t *testing.T) { + env := &imap.Envelope{ + Subject: "Hi", + Date: time.Unix(1700000000, 0).UTC(), + From: []imap.Address{{Mailbox: "a", Host: "x.com"}}, + To: []imap.Address{{Mailbox: "b", Host: "y.com"}}, + } + k1 := MessageKey(env, 42) + k2 := MessageKey(env, 42) + if k1 != k2 { + t.Fatal("fallback key must be deterministic") + } + if MessageKey(env, 43) == k1 { + t.Fatal("different size must change key") + } +}