From 9f8bfb4bce299f01464e87912a7d0330aa141d00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:22:38 +0000 Subject: [PATCH 1/7] Initial plan From 25d8822ffd43911c21e06d385bbb2aa0232132c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:26:43 +0000 Subject: [PATCH 2/7] Add ClubAffairsReminder class with weekly and daily reminder functionality Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com> --- .../ClubAffairsReminder.cs | 337 ++++++++++++++++++ .../PluginMain.cs | 19 +- 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs diff --git a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs new file mode 100644 index 0000000..a37cb8c --- /dev/null +++ b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs @@ -0,0 +1,337 @@ +using System.Text; +using HuaJiBot.NET.Bot; +using Ical.Net.CalendarComponents; +using Timer = System.Timers.Timer; + +namespace HuaJiBot.NET.Plugin.Calendar; + +/// +/// 社团事务临期提醒功能 +/// 提供一周预告和一天提醒功能 +/// +internal class ClubAffairsReminder : IDisposable +{ + public PluginConfig Config { get; } + public BotService Service { get; } + private readonly Func _getCalendar; + private Ical.Net.Calendar? Calendar => _getCalendar(); + private readonly Timer _dailyCheckTimer; + private const int CheckIntervalMinutes = 60; // 每小时检查一次 + private DateTimeOffset _lastWeeklySummaryDate = DateTimeOffset.MinValue; + private DateTimeOffset _lastDailyReminderDate = DateTimeOffset.MinValue; + + public ClubAffairsReminder( + BotService service, + PluginConfig config, + Func getCalendar + ) + { + Service = service; + Config = config; + _getCalendar = getCalendar; + _dailyCheckTimer = new Timer(TimeSpan.FromMinutes(CheckIntervalMinutes)); + _dailyCheckTimer.Elapsed += (_, _) => CheckAndSendReminders(); + _dailyCheckTimer.AutoReset = true; + + // 初始延迟检查 + Task.Delay(30_000).ContinueWith(_ => CheckAndSendReminders()); + } + + public void Start() + { + _dailyCheckTimer.Start(); + } + + private void CheckAndSendReminders() + { + try + { + var now = Utils.NetworkTime.Now; + + // 检查是否应该发送每周汇总(每周一上午9:00) + if (ShouldSendWeeklySummary(now)) + { + SendWeeklySummary(now); + _lastWeeklySummaryDate = now.Date; + } + + // 检查是否应该发送每日提醒(每天上午9:00) + if (ShouldSendDailyReminder(now)) + { + SendDailyReminder(now); + _lastDailyReminderDate = now.Date; + } + } + catch (Exception ex) + { + Service.LogError("社团事务提醒任务出现异常", ex); + } + } + + private bool ShouldSendWeeklySummary(DateTimeOffset now) + { + // 每周一上午9:00发送 + if (now.DayOfWeek != DayOfWeek.Monday) + return false; + + if (now.Hour != 9) + return false; + + // 检查今天是否已经发送过 + return now.Date != _lastWeeklySummaryDate.Date; + } + + private bool ShouldSendDailyReminder(DateTimeOffset now) + { + // 每天上午9:00发送 + if (now.Hour != 9) + return false; + + // 检查今天是否已经发送过 + return now.Date != _lastDailyReminderDate.Date; + } + + private void SendWeeklySummary(DateTimeOffset now) + { + if (Calendar is null) + { + Service.Log("[社团事务] 日历为空,跳过每周汇总发送"); + return; + } + + var weekStart = now; + var weekEnd = now.AddDays(7); + + var upcomingEvents = Calendar + .GetEvents(weekStart, weekEnd) + .OrderBy(x => x.period.StartTime) + .ToList(); + + if (upcomingEvents.Count == 0) + { + Service.Log("[社团事务] 本周无即将到期的事务"); + return; + } + + var message = BuildWeeklySummaryMessage(now, weekEnd, upcomingEvents); + + foreach (var group in Config.ReminderGroups) + { + if (group.Mode == PluginConfig.ReminderFilterConfig.FilterMode.Default || + ShouldSendToGroup(upcomingEvents, group)) + { + Service.SendGroupMessageAsync(null, group.GroupId, message); + Service.Log($"[社团事务] 已向群组 {group.GroupId} 发送每周汇总"); + } + } + } + + private void SendDailyReminder(DateTimeOffset now) + { + if (Calendar is null) + { + Service.Log("[社团事务] 日历为空,跳过每日提醒发送"); + return; + } + + var tomorrow = now.Date.AddDays(1); + var dayAfterTomorrow = tomorrow.AddDays(1); + + var tomorrowEvents = Calendar + .GetEvents(tomorrow, dayAfterTomorrow) + .OrderBy(x => x.period.StartTime) + .ToList(); + + if (tomorrowEvents.Count == 0) + { + Service.Log("[社团事务] 明天无即将到期的事务"); + return; + } + + var message = BuildDailyReminderMessage(tomorrow, tomorrowEvents); + + foreach (var group in Config.ReminderGroups) + { + var groupEvents = FilterEventsForGroup(tomorrowEvents, group); + if (groupEvents.Count > 0) + { + var groupMessage = BuildDailyReminderMessage(tomorrow, groupEvents); + Service.SendGroupMessageAsync(null, group.GroupId, groupMessage); + Service.Log($"[社团事务] 已向群组 {group.GroupId} 发送每日提醒"); + } + } + } + + private bool ShouldSendToGroup( + List<(CalendarExtensions.Period period, CalendarEvent e)> events, + PluginConfig.ReminderFilterConfig group + ) + { + if (group.Mode == PluginConfig.ReminderFilterConfig.FilterMode.Default) + return true; + + return events.Any(evt => + { + var e = evt.e; + var list = group.Keywords; + return group.Mode switch + { + PluginConfig.ReminderFilterConfig.FilterMode.WhiteList => list.Any(x => + (e.Summary?.Contains(x) ?? false) + || (e.Description?.Contains(x) ?? false) + || (e.Location ?? "").Contains(x) + ), + PluginConfig.ReminderFilterConfig.FilterMode.BlackList => !list.Any(x => + (e.Summary?.Contains(x) ?? false) + || (e.Description?.Contains(x) ?? false) + || (e.Location ?? "").Contains(x) + ), + _ => false, + }; + }); + } + + private List<(CalendarExtensions.Period period, CalendarEvent e)> FilterEventsForGroup( + List<(CalendarExtensions.Period period, CalendarEvent e)> events, + PluginConfig.ReminderFilterConfig group + ) + { + if (group.Mode == PluginConfig.ReminderFilterConfig.FilterMode.Default) + return events; + + return events + .Where(evt => + { + var e = evt.e; + var list = group.Keywords; + return group.Mode switch + { + PluginConfig.ReminderFilterConfig.FilterMode.WhiteList => list.Any(x => + (e.Summary?.Contains(x) ?? false) + || (e.Description?.Contains(x) ?? false) + || (e.Location ?? "").Contains(x) + ), + PluginConfig.ReminderFilterConfig.FilterMode.BlackList => !list.Any(x => + (e.Summary?.Contains(x) ?? false) + || (e.Description?.Contains(x) ?? false) + || (e.Location ?? "").Contains(x) + ), + _ => true, + }; + }) + .ToList(); + } + + private string BuildWeeklySummaryMessage( + DateTimeOffset weekStart, + DateTimeOffset weekEnd, + List<(CalendarExtensions.Period period, CalendarEvent e)> events + ) + { + var sb = new StringBuilder(); + sb.AppendLine( + $"📢 社团事务一周预告({weekStart:MM月dd日}~{weekEnd:MM月dd日})" + ); + sb.AppendLine("以下事务将在一周内到期,请相关负责人提前准备:"); + sb.AppendLine(); + + for (int i = 0; i < events.Count; i++) + { + var (period, e) = events[i]; + var emoji = GetNumberEmoji(i + 1); + var dateStr = period.StartTime.ToString("MM月dd日"); + var responsible = ExtractResponsible(e); + sb.AppendLine( + $"{emoji} [{dateStr}] {e.Summary ?? "未命名事务"}{(string.IsNullOrEmpty(responsible) ? "" : $"(负责人:{responsible})")}" + ); + } + + sb.AppendLine(); + sb.AppendLine("✅ 请大家合理安排时间,确保事项按时完成。"); + sb.AppendLine("—— 社团事务提醒机器人 🤖"); + + return sb.ToString(); + } + + private string BuildDailyReminderMessage( + DateTimeOffset tomorrow, + List<(CalendarExtensions.Period period, CalendarEvent e)> events + ) + { + var sb = new StringBuilder(); + sb.AppendLine("⏰ 临期提醒"); + sb.AppendLine($"明天({tomorrow:MM月dd日})截止的社团事务:"); + sb.AppendLine(); + + foreach (var (period, e) in events) + { + var responsible = ExtractResponsible(e); + sb.AppendLine( + $"• {e.Summary ?? "未命名事务"}{(string.IsNullOrEmpty(responsible) ? "" : $"(负责人:{responsible})")}" + ); + } + + sb.AppendLine(); + sb.AppendLine("请务必在截止前完成相关工作!"); + sb.AppendLine("—— 社团事务提醒机器人 🤖"); + + return sb.ToString(); + } + + private string ExtractResponsible(CalendarEvent e) + { + // 尝试从描述中提取负责人信息 + if (string.IsNullOrWhiteSpace(e.Description)) + return string.Empty; + + // 查找常见的负责人标识 + var patterns = new[] { "负责人:", "负责人:", "责任人:", "责任人:" }; + foreach (var pattern in patterns) + { + var index = e.Description.IndexOf(pattern, StringComparison.OrdinalIgnoreCase); + if (index >= 0) + { + var startIndex = index + pattern.Length; + var endIndex = e.Description.IndexOfAny( + new[] { '\n', '\r', ')', ')', ',', ',' }, + startIndex + ); + if (endIndex > startIndex) + { + return e.Description.Substring(startIndex, endIndex - startIndex).Trim(); + } + else if (startIndex < e.Description.Length) + { + var remaining = e.Description.Substring(startIndex).Trim(); + // 取前20个字符作为负责人名字 + return remaining.Length > 20 ? remaining.Substring(0, 20) : remaining; + } + } + } + + return string.Empty; + } + + private string GetNumberEmoji(int number) + { + return number switch + { + 1 => "1️⃣", + 2 => "2️⃣", + 3 => "3️⃣", + 4 => "4️⃣", + 5 => "5️⃣", + 6 => "6️⃣", + 7 => "7️⃣", + 8 => "8️⃣", + 9 => "9️⃣", + 10 => "🔟", + _ => $"{number}.", + }; + } + + public void Dispose() + { + _dailyCheckTimer?.Dispose(); + } +} diff --git a/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs b/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs index d52281f..d552b29 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs @@ -42,6 +42,7 @@ public PluginMain() private RemoteSync Sync => _sync.Value; private Ical.Net.Calendar? Calendar => _sync.Value.Calendar; private ReminderTask? _reminderTask; + private ClubAffairsReminder? _clubAffairsReminder; protected override void Initialize() { @@ -57,6 +58,19 @@ protected override void Initialize() } ); _reminderTask.Start(); + + // 启动社团事务临期提醒功能 + _clubAffairsReminder = new( + Service, + Config, + () => + { + _ = Sync.UpdateCalendarAsync(); + return Calendar; + } + ); + _clubAffairsReminder.Start(); + Service.Log("[日程] 社团事务临期提醒功能已启动"); } private readonly Dictionary _cache = new(); @@ -201,5 +215,8 @@ public override IEnumerable? ExportFunctions } } - protected override void Unload() { } + protected override void Unload() + { + _clubAffairsReminder?.Dispose(); + } } From 13d46c941878ad2a2a0ac5f3a6f871c89a7a1214 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:27:54 +0000 Subject: [PATCH 3/7] Add configuration options for club affairs reminder feature Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com> --- .../ClubAffairsReminder.cs | 19 +++--- .../PluginMain.cs | 60 +++++++++++++++---- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs index a37cb8c..e8934fc 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs @@ -48,15 +48,15 @@ private void CheckAndSendReminders() { var now = Utils.NetworkTime.Now; - // 检查是否应该发送每周汇总(每周一上午9:00) - if (ShouldSendWeeklySummary(now)) + // 检查是否应该发送每周汇总 + if (Config.ClubAffairsReminder.EnableWeeklySummary && ShouldSendWeeklySummary(now)) { SendWeeklySummary(now); _lastWeeklySummaryDate = now.Date; } - // 检查是否应该发送每日提醒(每天上午9:00) - if (ShouldSendDailyReminder(now)) + // 检查是否应该发送每日提醒 + if (Config.ClubAffairsReminder.EnableDailyReminder && ShouldSendDailyReminder(now)) { SendDailyReminder(now); _lastDailyReminderDate = now.Date; @@ -70,11 +70,12 @@ private void CheckAndSendReminders() private bool ShouldSendWeeklySummary(DateTimeOffset now) { - // 每周一上午9:00发送 - if (now.DayOfWeek != DayOfWeek.Monday) + // 根据配置的星期几发送 + if (now.DayOfWeek != Config.ClubAffairsReminder.WeeklySummaryDayOfWeek) return false; - if (now.Hour != 9) + // 根据配置的小时发送 + if (now.Hour != Config.ClubAffairsReminder.WeeklySummaryHour) return false; // 检查今天是否已经发送过 @@ -83,8 +84,8 @@ private bool ShouldSendWeeklySummary(DateTimeOffset now) private bool ShouldSendDailyReminder(DateTimeOffset now) { - // 每天上午9:00发送 - if (now.Hour != 9) + // 根据配置的小时发送 + if (now.Hour != Config.ClubAffairsReminder.DailyReminderHour) return false; // 检查今天是否已经发送过 diff --git a/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs b/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs index d552b29..f5199b3 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs @@ -12,6 +12,37 @@ public class PluginConfig : ConfigBase public int MinRange = -128; public int MaxRange = 48; public ReminderFilterConfig[] ReminderGroups = []; + + // 社团事务临期提醒配置 + public ClubAffairsReminderConfig ClubAffairsReminder { get; set; } = new(); + + public class ClubAffairsReminderConfig + { + /// + /// 是否启用每周汇总提醒(默认启用) + /// + public bool EnableWeeklySummary { get; set; } = true; + + /// + /// 是否启用每日提醒(默认启用) + /// + public bool EnableDailyReminder { get; set; } = true; + + /// + /// 每周汇总发送时间(小时,0-23,默认9点) + /// + public int WeeklySummaryHour { get; set; } = 9; + + /// + /// 每日提醒发送时间(小时,0-23,默认9点) + /// + public int DailyReminderHour { get; set; } = 9; + + /// + /// 每周汇总发送在星期几(0=周日,1=周一,默认1=周一) + /// + public DayOfWeek WeeklySummaryDayOfWeek { get; set; } = DayOfWeek.Monday; + } public class ReminderFilterConfig { @@ -60,17 +91,24 @@ protected override void Initialize() _reminderTask.Start(); // 启动社团事务临期提醒功能 - _clubAffairsReminder = new( - Service, - Config, - () => - { - _ = Sync.UpdateCalendarAsync(); - return Calendar; - } - ); - _clubAffairsReminder.Start(); - Service.Log("[日程] 社团事务临期提醒功能已启动"); + if (Config.ClubAffairsReminder.EnableWeeklySummary || Config.ClubAffairsReminder.EnableDailyReminder) + { + _clubAffairsReminder = new( + Service, + Config, + () => + { + _ = Sync.UpdateCalendarAsync(); + return Calendar; + } + ); + _clubAffairsReminder.Start(); + Service.Log("[日程] 社团事务临期提醒功能已启动"); + } + else + { + Service.Log("[日程] 社团事务临期提醒功能已禁用"); + } } private readonly Dictionary _cache = new(); From be02ff7ffe4c78f6aa080eacbace0f9a26a561f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:29:56 +0000 Subject: [PATCH 4/7] Add tests and documentation for club affairs reminder feature Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com> --- .../ClubAffairsReminder.README.md | 101 ++++++++++++++++ .../ClubAffairsReminderTest.cs | 110 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.README.md create mode 100644 src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs diff --git a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.README.md b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.README.md new file mode 100644 index 0000000..c7c862e --- /dev/null +++ b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.README.md @@ -0,0 +1,101 @@ +# 社团事务临期提醒功能 + +## 功能介绍 + +社团事务临期提醒功能是 Calendar 插件的一部分,用于帮助社团成员更好地管理即将到期的事务,避免遗漏重要事项。 + +## 功能特性 + +### 1. 每周汇总预告 + +在配置的时间(默认:每周一上午 9:00)发送一条汇总消息,列出接下来一周内即将到期的社团事务。 + +**示例消息:** + +``` +📢 社团事务一周预告(10月20日~10月26日) +以下事务将在一周内到期,请相关负责人提前准备: + +1️⃣ [10月22日] 迎新晚会节目单提交(负责人:李华) +2️⃣ [10月24日] 社团经费报销材料上交(负责人:张敏) +3️⃣ [10月25日] 社团展板更新完成(负责人:王强) + +✅ 请大家合理安排时间,确保事项按时完成。 +—— 社团事务提醒机器人 🤖 +``` + +### 2. 每日临期提醒 + +在配置的时间(默认:每天上午 9:00)发送单条提醒消息,提醒明天即将截止的事务。 + +**示例消息:** + +``` +⏰ 临期提醒 +明天(10月25日)截止的社团事务: + +• 社团展板更新完成(负责人:王强) + +请务必在截止前完成相关工作! +—— 社团事务提醒机器人 🤖 +``` + +## 配置说明 + +在插件配置文件中,可以通过 `ClubAffairsReminder` 节点进行配置: + +```json +{ + "ClubAffairsReminder": { + "EnableWeeklySummary": true, + "EnableDailyReminder": true, + "WeeklySummaryHour": 9, + "DailyReminderHour": 9, + "WeeklySummaryDayOfWeek": 1 + } +} +``` + +### 配置项说明 + +| 配置项 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `EnableWeeklySummary` | bool | `true` | 是否启用每周汇总提醒 | +| `EnableDailyReminder` | bool | `true` | 是否启用每日提醒 | +| `WeeklySummaryHour` | int | `9` | 每周汇总发送时间(小时,0-23) | +| `DailyReminderHour` | int | `9` | 每日提醒发送时间(小时,0-23) | +| `WeeklySummaryDayOfWeek` | int | `1` | 每周汇总发送在星期几(0=周日,1=周一,...,6=周六) | + +## 负责人信息提取 + +系统会自动从事务描述中提取负责人信息。支持以下格式: + +- `负责人:张三` +- `负责人:张三` +- `责任人:张三` +- `责任人:张三` + +## 过滤规则 + +提醒消息遵循现有的 `ReminderGroups` 配置,支持白名单、黑名单和默认模式: + +- **Default(默认)**:发送所有事务提醒 +- **WhiteList(白名单)**:仅发送包含指定关键词的事务 +- **BlackList(黑名单)**:不发送包含指定关键词的事务 + +关键词匹配会检查事务的概要(Summary)、描述(Description)和地点(Location)字段。 + +## 工作原理 + +1. 系统每小时检查一次当前时间 +2. 在配置的时间点,检查是否需要发送提醒 +3. 使用日期记录防止同一天重复发送 +4. 根据过滤规则筛选要发送的群组和事务 +5. 自动提取负责人信息并格式化消息 + +## 注意事项 + +- 提醒功能需要正确配置 `ReminderGroups` 才能发送消息 +- 系统使用网络时间(NetworkTime)确保时间准确性 +- 日历数据需要成功同步才能发送提醒 +- 如果某一天没有即将到期的事务,不会发送空消息 diff --git a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs new file mode 100644 index 0000000..7a76b91 --- /dev/null +++ b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs @@ -0,0 +1,110 @@ +namespace HuaJiBot.NET.UnitTest; + +internal class ClubAffairsReminderTest +{ + [Test] + public void TestWeeklySummaryDayOfWeek() + { + // Test that Monday is correctly identified for weekly summary + var monday = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); // Monday + var tuesday = new DateTimeOffset(2024, 10, 22, 9, 0, 0, TimeSpan.FromHours(8)); // Tuesday + + Assert.That(monday.DayOfWeek, Is.EqualTo(DayOfWeek.Monday)); + Assert.That(tuesday.DayOfWeek, Is.Not.EqualTo(DayOfWeek.Monday)); + } + + [Test] + public void TestTimeComparison() + { + // Test that hour comparison works correctly for reminder timing + var nineAm = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); + var tenAm = new DateTimeOffset(2024, 10, 21, 10, 0, 0, TimeSpan.FromHours(8)); + + Assert.That(nineAm.Hour, Is.EqualTo(9)); + Assert.That(tenAm.Hour, Is.EqualTo(10)); + } + + [Test] + public void TestDateComparison() + { + // Test date comparison for preventing duplicate reminders + var date1 = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); + var date2 = new DateTimeOffset(2024, 10, 21, 10, 0, 0, TimeSpan.FromHours(8)); + var date3 = new DateTimeOffset(2024, 10, 22, 9, 0, 0, TimeSpan.FromHours(8)); + + Assert.That(date1.Date, Is.EqualTo(date2.Date)); + Assert.That(date1.Date, Is.Not.EqualTo(date3.Date)); + } + + [Test] + public void TestWeekRange() + { + // Test that week range calculation is correct + var start = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); + var end = start.AddDays(7); + + var expectedEnd = new DateTimeOffset(2024, 10, 28, 9, 0, 0, TimeSpan.FromHours(8)); + Assert.That(end, Is.EqualTo(expectedEnd)); + } + + [Test] + public void TestDayRange() + { + // Test that tomorrow calculation is correct + var today = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); + var tomorrow = today.Date.AddDays(1); + var dayAfterTomorrow = tomorrow.AddDays(1); + + var expectedTomorrow = new DateTimeOffset(2024, 10, 22, 0, 0, 0, TimeSpan.FromHours(8)); + var expectedDayAfter = new DateTimeOffset(2024, 10, 23, 0, 0, 0, TimeSpan.FromHours(8)); + + Assert.That(tomorrow, Is.EqualTo(expectedTomorrow)); + Assert.That(dayAfterTomorrow, Is.EqualTo(expectedDayAfter)); + } + + [Test] + public void TestNumberEmoji() + { + // Test emoji mapping + var testCases = new Dictionary + { + { 1, "1️⃣" }, + { 2, "2️⃣" }, + { 3, "3️⃣" }, + { 4, "4️⃣" }, + { 5, "5️⃣" }, + { 6, "6️⃣" }, + { 7, "7️⃣" }, + { 8, "8️⃣" }, + { 9, "9️⃣" }, + { 10, "🔟" }, + }; + + foreach (var testCase in testCases) + { + var emoji = GetNumberEmoji(testCase.Key); + Assert.That(emoji, Is.EqualTo(testCase.Value)); + } + + // Test fallback for numbers > 10 + Assert.That(GetNumberEmoji(11), Is.EqualTo("11.")); + } + + private string GetNumberEmoji(int number) + { + return number switch + { + 1 => "1️⃣", + 2 => "2️⃣", + 3 => "3️⃣", + 4 => "4️⃣", + 5 => "5️⃣", + 6 => "6️⃣", + 7 => "7️⃣", + 8 => "8️⃣", + 9 => "9️⃣", + 10 => "🔟", + _ => $"{number}.", + }; + } +} From 396c4e45fb67b5cb4e5fa2c101ff12f43a43b2ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:31:44 +0000 Subject: [PATCH 5/7] Fix type mismatch in ClubAffairsReminderTest Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com> --- src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs index 7a76b91..9485e86 100644 --- a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs +++ b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs @@ -52,11 +52,11 @@ public void TestDayRange() { // Test that tomorrow calculation is correct var today = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); - var tomorrow = today.Date.AddDays(1); + var tomorrow = today.AddDays(1).Date; var dayAfterTomorrow = tomorrow.AddDays(1); - var expectedTomorrow = new DateTimeOffset(2024, 10, 22, 0, 0, 0, TimeSpan.FromHours(8)); - var expectedDayAfter = new DateTimeOffset(2024, 10, 23, 0, 0, 0, TimeSpan.FromHours(8)); + var expectedTomorrow = new DateTime(2024, 10, 22, 0, 0, 0); + var expectedDayAfter = new DateTime(2024, 10, 23, 0, 0, 0); Assert.That(tomorrow, Is.EqualTo(expectedTomorrow)); Assert.That(dayAfterTomorrow, Is.EqualTo(expectedDayAfter)); From e4ba8dfb857b3770327547744c4c305282a735fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:34:59 +0000 Subject: [PATCH 6/7] Fix DateTimeOffset type consistency in ClubAffairsReminder Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com> --- src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs | 4 ++-- src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs index e8934fc..86c2c77 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs @@ -135,8 +135,8 @@ private void SendDailyReminder(DateTimeOffset now) return; } - var tomorrow = now.Date.AddDays(1); - var dayAfterTomorrow = tomorrow.AddDays(1); + var tomorrow = new DateTimeOffset(now.Date.AddDays(1), now.Offset); + var dayAfterTomorrow = new DateTimeOffset(now.Date.AddDays(2), now.Offset); var tomorrowEvents = Calendar .GetEvents(tomorrow, dayAfterTomorrow) diff --git a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs index 9485e86..15e345f 100644 --- a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs +++ b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs @@ -52,11 +52,11 @@ public void TestDayRange() { // Test that tomorrow calculation is correct var today = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); - var tomorrow = today.AddDays(1).Date; - var dayAfterTomorrow = tomorrow.AddDays(1); + var tomorrow = new DateTimeOffset(today.Date.AddDays(1), today.Offset); + var dayAfterTomorrow = new DateTimeOffset(today.Date.AddDays(2), today.Offset); - var expectedTomorrow = new DateTime(2024, 10, 22, 0, 0, 0); - var expectedDayAfter = new DateTime(2024, 10, 23, 0, 0, 0); + var expectedTomorrow = new DateTimeOffset(2024, 10, 22, 0, 0, 0, TimeSpan.FromHours(8)); + var expectedDayAfter = new DateTimeOffset(2024, 10, 23, 0, 0, 0, TimeSpan.FromHours(8)); Assert.That(tomorrow, Is.EqualTo(expectedTomorrow)); Assert.That(dayAfterTomorrow, Is.EqualTo(expectedDayAfter)); From 8067d584d4d32c06039a4fc20cbbb106f71299d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:36:35 +0000 Subject: [PATCH 7/7] Add clarifying comments for DateTimeOffset usage in date range queries Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com> --- src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs | 1 + src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs index 86c2c77..0c53c26 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs @@ -135,6 +135,7 @@ private void SendDailyReminder(DateTimeOffset now) return; } + // 创建明天和后天的DateTimeOffset,时间设置为00:00:00以获取整天的事务 var tomorrow = new DateTimeOffset(now.Date.AddDays(1), now.Offset); var dayAfterTomorrow = new DateTimeOffset(now.Date.AddDays(2), now.Offset); diff --git a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs index 15e345f..5d89172 100644 --- a/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs +++ b/src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs @@ -51,6 +51,7 @@ public void TestWeekRange() public void TestDayRange() { // Test that tomorrow calculation is correct + // 创建午夜时间的DateTimeOffset以匹配生产代码中用于日期范围查询的行为 var today = new DateTimeOffset(2024, 10, 21, 9, 0, 0, TimeSpan.FromHours(8)); var tomorrow = new DateTimeOffset(today.Date.AddDays(1), today.Offset); var dayAfterTomorrow = new DateTimeOffset(today.Date.AddDays(2), today.Offset);