Skip to content

Commit 25d8822

Browse files
CopilotLazuliKao
andcommitted
Add ClubAffairsReminder class with weekly and daily reminder functionality
Co-authored-by: LazuliKao <46601807+LazuliKao@users.noreply.github.com>
1 parent 9f8bfb4 commit 25d8822

2 files changed

Lines changed: 355 additions & 1 deletion

File tree

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
using System.Text;
2+
using HuaJiBot.NET.Bot;
3+
using Ical.Net.CalendarComponents;
4+
using Timer = System.Timers.Timer;
5+
6+
namespace HuaJiBot.NET.Plugin.Calendar;
7+
8+
/// <summary>
9+
/// 社团事务临期提醒功能
10+
/// 提供一周预告和一天提醒功能
11+
/// </summary>
12+
internal class ClubAffairsReminder : IDisposable
13+
{
14+
public PluginConfig Config { get; }
15+
public BotService Service { get; }
16+
private readonly Func<Ical.Net.Calendar?> _getCalendar;
17+
private Ical.Net.Calendar? Calendar => _getCalendar();
18+
private readonly Timer _dailyCheckTimer;
19+
private const int CheckIntervalMinutes = 60; // 每小时检查一次
20+
private DateTimeOffset _lastWeeklySummaryDate = DateTimeOffset.MinValue;
21+
private DateTimeOffset _lastDailyReminderDate = DateTimeOffset.MinValue;
22+
23+
public ClubAffairsReminder(
24+
BotService service,
25+
PluginConfig config,
26+
Func<Ical.Net.Calendar?> getCalendar
27+
)
28+
{
29+
Service = service;
30+
Config = config;
31+
_getCalendar = getCalendar;
32+
_dailyCheckTimer = new Timer(TimeSpan.FromMinutes(CheckIntervalMinutes));
33+
_dailyCheckTimer.Elapsed += (_, _) => CheckAndSendReminders();
34+
_dailyCheckTimer.AutoReset = true;
35+
36+
// 初始延迟检查
37+
Task.Delay(30_000).ContinueWith(_ => CheckAndSendReminders());
38+
}
39+
40+
public void Start()
41+
{
42+
_dailyCheckTimer.Start();
43+
}
44+
45+
private void CheckAndSendReminders()
46+
{
47+
try
48+
{
49+
var now = Utils.NetworkTime.Now;
50+
51+
// 检查是否应该发送每周汇总(每周一上午9:00)
52+
if (ShouldSendWeeklySummary(now))
53+
{
54+
SendWeeklySummary(now);
55+
_lastWeeklySummaryDate = now.Date;
56+
}
57+
58+
// 检查是否应该发送每日提醒(每天上午9:00)
59+
if (ShouldSendDailyReminder(now))
60+
{
61+
SendDailyReminder(now);
62+
_lastDailyReminderDate = now.Date;
63+
}
64+
}
65+
catch (Exception ex)
66+
{
67+
Service.LogError("社团事务提醒任务出现异常", ex);
68+
}
69+
}
70+
71+
private bool ShouldSendWeeklySummary(DateTimeOffset now)
72+
{
73+
// 每周一上午9:00发送
74+
if (now.DayOfWeek != DayOfWeek.Monday)
75+
return false;
76+
77+
if (now.Hour != 9)
78+
return false;
79+
80+
// 检查今天是否已经发送过
81+
return now.Date != _lastWeeklySummaryDate.Date;
82+
}
83+
84+
private bool ShouldSendDailyReminder(DateTimeOffset now)
85+
{
86+
// 每天上午9:00发送
87+
if (now.Hour != 9)
88+
return false;
89+
90+
// 检查今天是否已经发送过
91+
return now.Date != _lastDailyReminderDate.Date;
92+
}
93+
94+
private void SendWeeklySummary(DateTimeOffset now)
95+
{
96+
if (Calendar is null)
97+
{
98+
Service.Log("[社团事务] 日历为空,跳过每周汇总发送");
99+
return;
100+
}
101+
102+
var weekStart = now;
103+
var weekEnd = now.AddDays(7);
104+
105+
var upcomingEvents = Calendar
106+
.GetEvents(weekStart, weekEnd)
107+
.OrderBy(x => x.period.StartTime)
108+
.ToList();
109+
110+
if (upcomingEvents.Count == 0)
111+
{
112+
Service.Log("[社团事务] 本周无即将到期的事务");
113+
return;
114+
}
115+
116+
var message = BuildWeeklySummaryMessage(now, weekEnd, upcomingEvents);
117+
118+
foreach (var group in Config.ReminderGroups)
119+
{
120+
if (group.Mode == PluginConfig.ReminderFilterConfig.FilterMode.Default ||
121+
ShouldSendToGroup(upcomingEvents, group))
122+
{
123+
Service.SendGroupMessageAsync(null, group.GroupId, message);
124+
Service.Log($"[社团事务] 已向群组 {group.GroupId} 发送每周汇总");
125+
}
126+
}
127+
}
128+
129+
private void SendDailyReminder(DateTimeOffset now)
130+
{
131+
if (Calendar is null)
132+
{
133+
Service.Log("[社团事务] 日历为空,跳过每日提醒发送");
134+
return;
135+
}
136+
137+
var tomorrow = now.Date.AddDays(1);
138+
var dayAfterTomorrow = tomorrow.AddDays(1);
139+
140+
var tomorrowEvents = Calendar
141+
.GetEvents(tomorrow, dayAfterTomorrow)
142+
.OrderBy(x => x.period.StartTime)
143+
.ToList();
144+
145+
if (tomorrowEvents.Count == 0)
146+
{
147+
Service.Log("[社团事务] 明天无即将到期的事务");
148+
return;
149+
}
150+
151+
var message = BuildDailyReminderMessage(tomorrow, tomorrowEvents);
152+
153+
foreach (var group in Config.ReminderGroups)
154+
{
155+
var groupEvents = FilterEventsForGroup(tomorrowEvents, group);
156+
if (groupEvents.Count > 0)
157+
{
158+
var groupMessage = BuildDailyReminderMessage(tomorrow, groupEvents);
159+
Service.SendGroupMessageAsync(null, group.GroupId, groupMessage);
160+
Service.Log($"[社团事务] 已向群组 {group.GroupId} 发送每日提醒");
161+
}
162+
}
163+
}
164+
165+
private bool ShouldSendToGroup(
166+
List<(CalendarExtensions.Period period, CalendarEvent e)> events,
167+
PluginConfig.ReminderFilterConfig group
168+
)
169+
{
170+
if (group.Mode == PluginConfig.ReminderFilterConfig.FilterMode.Default)
171+
return true;
172+
173+
return events.Any(evt =>
174+
{
175+
var e = evt.e;
176+
var list = group.Keywords;
177+
return group.Mode switch
178+
{
179+
PluginConfig.ReminderFilterConfig.FilterMode.WhiteList => list.Any(x =>
180+
(e.Summary?.Contains(x) ?? false)
181+
|| (e.Description?.Contains(x) ?? false)
182+
|| (e.Location ?? "").Contains(x)
183+
),
184+
PluginConfig.ReminderFilterConfig.FilterMode.BlackList => !list.Any(x =>
185+
(e.Summary?.Contains(x) ?? false)
186+
|| (e.Description?.Contains(x) ?? false)
187+
|| (e.Location ?? "").Contains(x)
188+
),
189+
_ => false,
190+
};
191+
});
192+
}
193+
194+
private List<(CalendarExtensions.Period period, CalendarEvent e)> FilterEventsForGroup(
195+
List<(CalendarExtensions.Period period, CalendarEvent e)> events,
196+
PluginConfig.ReminderFilterConfig group
197+
)
198+
{
199+
if (group.Mode == PluginConfig.ReminderFilterConfig.FilterMode.Default)
200+
return events;
201+
202+
return events
203+
.Where(evt =>
204+
{
205+
var e = evt.e;
206+
var list = group.Keywords;
207+
return group.Mode switch
208+
{
209+
PluginConfig.ReminderFilterConfig.FilterMode.WhiteList => list.Any(x =>
210+
(e.Summary?.Contains(x) ?? false)
211+
|| (e.Description?.Contains(x) ?? false)
212+
|| (e.Location ?? "").Contains(x)
213+
),
214+
PluginConfig.ReminderFilterConfig.FilterMode.BlackList => !list.Any(x =>
215+
(e.Summary?.Contains(x) ?? false)
216+
|| (e.Description?.Contains(x) ?? false)
217+
|| (e.Location ?? "").Contains(x)
218+
),
219+
_ => true,
220+
};
221+
})
222+
.ToList();
223+
}
224+
225+
private string BuildWeeklySummaryMessage(
226+
DateTimeOffset weekStart,
227+
DateTimeOffset weekEnd,
228+
List<(CalendarExtensions.Period period, CalendarEvent e)> events
229+
)
230+
{
231+
var sb = new StringBuilder();
232+
sb.AppendLine(
233+
$"📢 社团事务一周预告({weekStart:MM月dd日}{weekEnd:MM月dd日})"
234+
);
235+
sb.AppendLine("以下事务将在一周内到期,请相关负责人提前准备:");
236+
sb.AppendLine();
237+
238+
for (int i = 0; i < events.Count; i++)
239+
{
240+
var (period, e) = events[i];
241+
var emoji = GetNumberEmoji(i + 1);
242+
var dateStr = period.StartTime.ToString("MM月dd日");
243+
var responsible = ExtractResponsible(e);
244+
sb.AppendLine(
245+
$"{emoji} [{dateStr}] {e.Summary ?? "未命名事务"}{(string.IsNullOrEmpty(responsible) ? "" : $"(负责人:{responsible})")}"
246+
);
247+
}
248+
249+
sb.AppendLine();
250+
sb.AppendLine("✅ 请大家合理安排时间,确保事项按时完成。");
251+
sb.AppendLine("—— 社团事务提醒机器人 🤖");
252+
253+
return sb.ToString();
254+
}
255+
256+
private string BuildDailyReminderMessage(
257+
DateTimeOffset tomorrow,
258+
List<(CalendarExtensions.Period period, CalendarEvent e)> events
259+
)
260+
{
261+
var sb = new StringBuilder();
262+
sb.AppendLine("⏰ 临期提醒");
263+
sb.AppendLine($"明天({tomorrow:MM月dd日})截止的社团事务:");
264+
sb.AppendLine();
265+
266+
foreach (var (period, e) in events)
267+
{
268+
var responsible = ExtractResponsible(e);
269+
sb.AppendLine(
270+
$"• {e.Summary ?? "未命名事务"}{(string.IsNullOrEmpty(responsible) ? "" : $"(负责人:{responsible})")}"
271+
);
272+
}
273+
274+
sb.AppendLine();
275+
sb.AppendLine("请务必在截止前完成相关工作!");
276+
sb.AppendLine("—— 社团事务提醒机器人 🤖");
277+
278+
return sb.ToString();
279+
}
280+
281+
private string ExtractResponsible(CalendarEvent e)
282+
{
283+
// 尝试从描述中提取负责人信息
284+
if (string.IsNullOrWhiteSpace(e.Description))
285+
return string.Empty;
286+
287+
// 查找常见的负责人标识
288+
var patterns = new[] { "负责人:", "负责人:", "责任人:", "责任人:" };
289+
foreach (var pattern in patterns)
290+
{
291+
var index = e.Description.IndexOf(pattern, StringComparison.OrdinalIgnoreCase);
292+
if (index >= 0)
293+
{
294+
var startIndex = index + pattern.Length;
295+
var endIndex = e.Description.IndexOfAny(
296+
new[] { '\n', '\r', ')', ')', ',', ',' },
297+
startIndex
298+
);
299+
if (endIndex > startIndex)
300+
{
301+
return e.Description.Substring(startIndex, endIndex - startIndex).Trim();
302+
}
303+
else if (startIndex < e.Description.Length)
304+
{
305+
var remaining = e.Description.Substring(startIndex).Trim();
306+
// 取前20个字符作为负责人名字
307+
return remaining.Length > 20 ? remaining.Substring(0, 20) : remaining;
308+
}
309+
}
310+
}
311+
312+
return string.Empty;
313+
}
314+
315+
private string GetNumberEmoji(int number)
316+
{
317+
return number switch
318+
{
319+
1 => "1️⃣",
320+
2 => "2️⃣",
321+
3 => "3️⃣",
322+
4 => "4️⃣",
323+
5 => "5️⃣",
324+
6 => "6️⃣",
325+
7 => "7️⃣",
326+
8 => "8️⃣",
327+
9 => "9️⃣",
328+
10 => "🔟",
329+
_ => $"{number}.",
330+
};
331+
}
332+
333+
public void Dispose()
334+
{
335+
_dailyCheckTimer?.Dispose();
336+
}
337+
}

src/HuaJiBot.NET.Plugin.Calendar/PluginMain.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public PluginMain()
4242
private RemoteSync Sync => _sync.Value;
4343
private Ical.Net.Calendar? Calendar => _sync.Value.Calendar;
4444
private ReminderTask? _reminderTask;
45+
private ClubAffairsReminder? _clubAffairsReminder;
4546

4647
protected override void Initialize()
4748
{
@@ -57,6 +58,19 @@ protected override void Initialize()
5758
}
5859
);
5960
_reminderTask.Start();
61+
62+
// 启动社团事务临期提醒功能
63+
_clubAffairsReminder = new(
64+
Service,
65+
Config,
66+
() =>
67+
{
68+
_ = Sync.UpdateCalendarAsync();
69+
return Calendar;
70+
}
71+
);
72+
_clubAffairsReminder.Start();
73+
Service.Log("[日程] 社团事务临期提醒功能已启动");
6074
}
6175

6276
private readonly Dictionary<string, DateTimeOffset> _cache = new();
@@ -201,5 +215,8 @@ public override IEnumerable<AgentFunctionInfo>? ExportFunctions
201215
}
202216
}
203217

204-
protected override void Unload() { }
218+
protected override void Unload()
219+
{
220+
_clubAffairsReminder?.Dispose();
221+
}
205222
}

0 commit comments

Comments
 (0)