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