From 27fc3b65e9eab01e2e2b7f8393a13369cf394652 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 19 Feb 2026 13:11:23 -0500 Subject: [PATCH] feat: add time parameter to MoveToJunk, ReadEmail, and AnalyzeSpam When multiple emails share the same subject and date, the optional time parameter disambiguates by finding the closest match to the specified time (e.g., '13:46', '1:46 PM'). Co-Authored-By: Claude Opus 4.6 --- EmailSearch/EmailSearchTools.cs | 59 +++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/EmailSearch/EmailSearchTools.cs b/EmailSearch/EmailSearchTools.cs index 29dca91..f879424 100644 --- a/EmailSearch/EmailSearchTools.cs +++ b/EmailSearch/EmailSearchTools.cs @@ -80,13 +80,16 @@ public class EmailSearchTools public static string MoveToJunk( [Description("Exact or partial subject line to match")] string subject, [Description("Date of the email (supports: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy)")] string date, - [Description("Outlook folder to search for the email: Inbox, SentMail, Drafts, All, or any custom folder name (default Inbox)")] string folder = "Inbox") + [Description("Outlook folder to search for the email: Inbox, SentMail, Drafts, All, or any custom folder name (default Inbox)")] string folder = "Inbox", + [Description("Optional time to find the closest match when multiple emails share the same subject and date (e.g., '13:46', '1:46 PM')")] string? time = null) { try { if (!TryParseDate(date, out var targetDate)) return $"Invalid date format '{date}'. Supported formats: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy"; + TimeSpan? targetTime = TryParseTime(time); + using var outlookApp = new OutlookApp(); var ns = outlookApp.GetNamespace("MAPI"); var foldersToSearch = GetFoldersToSearch(ns, folder); @@ -94,7 +97,7 @@ public class EmailSearchTools foreach (var mailFolder in foldersToSearch) { - var mail = FindEmail(mailFolder, subject, targetDate); + var mail = FindEmail(mailFolder, subject, targetDate, targetTime); if (mail != null) { var emailSubject = mail.Subject; @@ -115,20 +118,23 @@ public class EmailSearchTools public static string ReadEmail( [Description("Exact or partial subject line to match")] string subject, [Description("Date of the email (supports: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy)")] string date, - [Description("Outlook folder: Inbox, SentMail, Drafts, DeletedItems, Junk, All, or any custom folder name (default All)")] string folder = "All") + [Description("Outlook folder: Inbox, SentMail, Drafts, DeletedItems, Junk, All, or any custom folder name (default All)")] string folder = "All", + [Description("Optional time to find the closest match when multiple emails share the same subject and date (e.g., '13:46', '1:46 PM')")] string? time = null) { try { if (!TryParseDate(date, out var targetDate)) return $"Invalid date format '{date}'. Supported formats: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy"; + TimeSpan? targetTime = TryParseTime(time); + using var outlookApp = new OutlookApp(); var ns = outlookApp.GetNamespace("MAPI"); var foldersToSearch = GetFoldersToSearch(ns, folder); foreach (var mailFolder in foldersToSearch) { - var mail = FindEmail(mailFolder, subject, targetDate); + var mail = FindEmail(mailFolder, subject, targetDate, targetTime); if (mail != null) return FormatFullEmail(mail); } @@ -145,20 +151,23 @@ public class EmailSearchTools public static string AnalyzeSpam( [Description("Exact or partial subject line to match")] string subject, [Description("Date of the email (supports: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy)")] string date, - [Description("Outlook folder: Inbox, SentMail, Drafts, DeletedItems, Junk, All, or any custom folder name (default All)")] string folder = "All") + [Description("Outlook folder: Inbox, SentMail, Drafts, DeletedItems, Junk, All, or any custom folder name (default All)")] string folder = "All", + [Description("Optional time to find the closest match when multiple emails share the same subject and date (e.g., '13:46', '1:46 PM')")] string? time = null) { try { if (!TryParseDate(date, out var targetDate)) return $"Invalid date format '{date}'. Supported formats: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy"; + TimeSpan? targetTime = TryParseTime(time); + using var outlookApp = new OutlookApp(); var ns = outlookApp.GetNamespace("MAPI"); var foldersToSearch = GetFoldersToSearch(ns, folder); foreach (var mailFolder in foldersToSearch) { - var mail = FindEmail(mailFolder, subject, targetDate); + var mail = FindEmail(mailFolder, subject, targetDate, targetTime); if (mail != null) { var detector = new SpamDetector(); @@ -436,7 +445,7 @@ public class EmailSearchTools } } - private static MailItem? FindEmail(MAPIFolder folder, string subject, DateTime targetDate) + private static MailItem? FindEmail(MAPIFolder folder, string subject, DateTime targetDate, TimeSpan? targetTime = null) { var items = folder.Items; items.Sort("[ReceivedTime]", true); @@ -445,6 +454,31 @@ public class EmailSearchTools var filter = $"[ReceivedTime] >= '{targetDate:MM/dd/yyyy}' AND [ReceivedTime] < '{targetDate.AddDays(1):MM/dd/yyyy}'"; var filteredItems = items.Restrict(filter); + if (targetTime.HasValue) + { + // Find the email closest to the specified time + var targetDateTime = targetDate.Date + targetTime.Value; + MailItem? bestMatch = null; + var bestDiff = TimeSpan.MaxValue; + + foreach (var item in filteredItems) + { + if (item is MailItem mail && + mail.Subject != null && + mail.Subject.Contains(subject, StringComparison.OrdinalIgnoreCase)) + { + var diff = (mail.ReceivedTime - targetDateTime).Duration(); + if (diff < bestDiff) + { + bestDiff = diff; + bestMatch = mail; + } + } + } + + return bestMatch; + } + foreach (var item in filteredItems) { if (item is MailItem mail && @@ -664,6 +698,17 @@ public class EmailSearchTools out result); } + private static TimeSpan? TryParseTime(string? timeString) + { + if (string.IsNullOrWhiteSpace(timeString)) + return null; + + if (DateTime.TryParse(timeString, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out var parsed)) + return parsed.TimeOfDay; + + return null; + } + private static OlImportance? ParseImportance(string? importance) { if (string.IsNullOrEmpty(importance)) return null;