using MailKit; using MailKit.Net.Imap; using MailKit.Search; using MailKit.Security; namespace GerbilManagerWebAPI.Inbox { /// /// Live Gmail reader over IMAP (App Password auth). Reads UIDs newer than the last seen, /// fetches envelope + Gmail thread id + the plain-text body. Gated on Julian's App Password /// (tests use a fake IGmailMailReader). /// public sealed class GmailMailReader : IGmailMailReader { public async Task> FetchAsync(MailConnection connection, uint sinceUid, CancellationToken ct = default) { using var client = new ImapClient(); await client.ConnectAsync("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect, ct); await client.AuthenticateAsync(connection.GmailAddress, connection.AppPassword, ct); var folder = string.Equals(connection.Folder, "INBOX", StringComparison.OrdinalIgnoreCase) ? client.Inbox : await client.GetFolderAsync(connection.Folder, ct); await folder.OpenAsync(FolderAccess.ReadOnly, ct); var range = new UniqueIdRange(new UniqueId(sinceUid + 1), UniqueId.MaxValue); var uids = await folder.SearchAsync(SearchQuery.Uids(range), ct); const MessageSummaryItems items = MessageSummaryItems.Envelope | MessageSummaryItems.UniqueId | MessageSummaryItems.GMailThreadId | MessageSummaryItems.InternalDate; var summaries = await folder.FetchAsync(uids, items, ct); var result = new List(); foreach (var s in summaries) { var env = s.Envelope; var msg = await folder.GetMessageAsync(s.UniqueId, ct); // body + threading headers var from = env?.From?.Mailboxes?.FirstOrDefault(); result.Add(new MailSummary( MessageId: env?.MessageId ?? msg.MessageId ?? "", ThreadId: s.GMailThreadId?.ToString(), InReplyTo: msg.InReplyTo, References: msg.References is { Count: > 0 } ? string.Join(' ', msg.References) : null, FromAddress: from?.Address ?? "", FromName: string.IsNullOrWhiteSpace(from?.Name) ? null : from!.Name, Subject: env?.Subject ?? msg.Subject, BodyText: msg.TextBody, ReceivedAt: s.InternalDate ?? env?.Date ?? DateTimeOffset.UtcNow, Uid: s.UniqueId.Id)); } await client.DisconnectAsync(true, ct); return result; } } }