From 7568d3d288ca86b7b5da613fd972faf305ecf189 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 7 Apr 2026 11:42:40 +0100 Subject: [PATCH] feat: add TrustedSenderService to scan Sent folder for trusted contacts --- .../Services/TrustedSenderService.cs | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/SpamGuard/Services/TrustedSenderService.cs diff --git a/src/SpamGuard/Services/TrustedSenderService.cs b/src/SpamGuard/Services/TrustedSenderService.cs new file mode 100644 index 0000000..7ca917a --- /dev/null +++ b/src/SpamGuard/Services/TrustedSenderService.cs @@ -0,0 +1,97 @@ +// src/SpamGuard/Services/TrustedSenderService.cs +namespace SpamGuard.Services; + +using MailKit; +using MailKit.Search; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MimeKit; +using SpamGuard.Configuration; +using SpamGuard.State; + +public sealed class TrustedSenderService : BackgroundService +{ + private readonly ImapClientFactory _imapFactory; + private readonly TrustedSenderStore _store; + private readonly SpamGuardOptions _options; + private readonly ILogger _logger; + + public TrustedSenderService( + ImapClientFactory imapFactory, + TrustedSenderStore store, + IOptions options, + ILogger logger) + { + _imapFactory = imapFactory; + _store = store; + _options = options.Value; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("TrustedSenderService started"); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + await ScanSentFolderAsync(stoppingToken); + _store.Save(); + _logger.LogInformation("Trusted sender scan complete. {Count} trusted senders", _store.Count); + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error scanning sent folder"); + } + + await Task.Delay(TimeSpan.FromMinutes(_options.Monitoring.TrustedSenderRefreshMinutes), stoppingToken); + } + } + + private async Task ScanSentFolderAsync(CancellationToken ct) + { + using var client = await _imapFactory.CreateConnectedClientAsync(ct); + + var sentFolder = client.GetFolder(MailKit.SpecialFolder.Sent) + ?? throw new InvalidOperationException("Could not find Sent folder"); + + await sentFolder.OpenAsync(FolderAccess.ReadOnly, ct); + + var uids = await sentFolder.SearchAsync(SearchQuery.All, ct); + _logger.LogDebug("Found {Count} messages in Sent folder", uids.Count); + + var addresses = new List(); + foreach (var uid in uids) + { + var headers = await sentFolder.GetHeadersAsync(uid, ct); + ExtractAddresses(headers, addresses); + } + + _store.AddRange(addresses); + await client.DisconnectAsync(true, ct); + } + + private static void ExtractAddresses(HeaderList headers, List addresses) + { + foreach (var headerName in new[] { "To", "Cc" }) + { + var value = headers[headerName]; + if (string.IsNullOrEmpty(value)) continue; + + if (InternetAddressList.TryParse(value, out var list)) + { + foreach (var address in list.Mailboxes) + { + if (!string.IsNullOrEmpty(address.Address)) + addresses.Add(address.Address); + } + } + } + } +}