Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
dotnet-version: '10.x'

- name: Run focused tests
run: dotnet test src/HuaJiBot.NET.UnitTest --filter 'FullyQualifiedName~DailySummaryTest|FullyQualifiedName~AsciiArtToolsTest|FullyQualifiedName~WebDataServiceTest|FullyQualifiedName~VisionInputTest|FullyQualifiedName~FlueBridgeTest'
run: dotnet test src/HuaJiBot.NET.UnitTest --filter 'FullyQualifiedName~DailySummaryTest|FullyQualifiedName~AsciiArtToolsTest|FullyQualifiedName~WebDataServiceTest|FullyQualifiedName~VisionInputTest|FullyQualifiedName~FlueBridgeTest|FullyQualifiedName~ClubAffairsReminderTest|FullyQualifiedName~CalendarOutputTest'

format-check:
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ namespace HuaJiBot.NET.Plugin.Calendar;

internal static partial class CalendarExtensions
{
internal const string SkipNotificationKeyword = "[skip notification]";

internal static bool ShouldSkipNotification(this CalendarEvent calendarEvent) =>
calendarEvent.Description?.Contains(
SkipNotificationKeyword,
StringComparison.OrdinalIgnoreCase
) == true;

/// <summary>
/// Wraps an ical.net <see cref="Ical.Net.DataTypes.Period"/> and converts its times to
/// <see cref="DateTimeOffset"/>. The <paramref name="calendarEvent"/> is required to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
—— 社团事务提醒机器人 🤖
```

### 3. 跳过通知
`[skip notification]`(不区分大小写),系统不会发送该日程的开始提醒、结束提醒、每周汇总或每日临期提醒。日程查询仍会显示该日程。占位日程修复后,请从描述中移除该标记。

## 配置说明

在插件配置文件中,可以通过 `ClubAffairsReminder` 节点进行配置:
Expand Down
8 changes: 6 additions & 2 deletions src/HuaJiBot.NET.Plugin.Calendar/ClubAffairsReminder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ internal bool SendWeeklySummary(DateTimeOffset now)
return false;
}
var weekEnd = now.AddDays(7);
var events = Calendar.GetEvents(now, weekEnd).OrderBy(x => x.period.StartTime).ToList();
var events = Calendar
.GetEvents(now, weekEnd)
.Where(x => !x.e.ShouldSkipNotification())
.OrderBy(x => x.period.StartTime)
.ToList();
foreach (var group in Config.ReminderGroups)
{
var groupEvents = events.Where(x => group.Matches(x.e)).ToList();
Expand Down Expand Up @@ -183,7 +187,7 @@ internal bool SendDailyReminder(DateTimeOffset now)
// Only events starting tomorrow, so a multi-day event is not repeated every day.
var events = Calendar
.GetEvents(tomorrow, dayAfterTomorrow)
.Where(x => x.period.StartTime >= tomorrow)
.Where(x => x.period.StartTime >= tomorrow && !x.e.ShouldSkipNotification())
.OrderBy(x => x.period.StartTime)
.ToList();
foreach (var group in Config.ReminderGroups)
Expand Down
15 changes: 13 additions & 2 deletions src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ private void InvokeCheck()
var remindEnd = end.AddMinutes(RemindBeforeStartMinutes);
foreach (var (period, e) in Calendar.GetEvents(remindStart, remindEnd))
{
if (e.IsAllDay || period.StartTime < remindStart || period.StartTime > remindEnd)
if (
e.IsAllDay
|| e.ShouldSkipNotification()
|| period.StartTime < remindStart
|| period.StartTime > remindEnd
)
continue;
ScheduleReminder(
period.StartTime.AddMinutes(-RemindBeforeStartMinutes) - now,
Expand All @@ -93,7 +98,13 @@ private void InvokeCheck()
remindEnd = end.AddMinutes(RemindBeforeEndMinutes);
foreach (var (period, e) in Calendar.GetEvents(remindStart, remindEnd))
{
if (e.IsAllDay || e.End is null || period.EndTime < remindStart || period.EndTime > remindEnd)
if (
e.IsAllDay
|| e.End is null
|| e.ShouldSkipNotification()
|| period.EndTime < remindStart
|| period.EndTime > remindEnd
)
continue;
ScheduleReminder(
period.EndTime.AddMinutes(-RemindBeforeEndMinutes) - now,
Expand Down
53 changes: 52 additions & 1 deletion src/HuaJiBot.NET.UnitTest/ClubAffairsReminderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ internal class ClubAffairsReminderTest
{
private static readonly DateTimeOffset Now = new(2026, 9, 21, 9, 0, 0, TimeSpan.FromHours(8));

private static CalendarEvent Event(string summary, DateTimeOffset start, DateTimeOffset end) =>
private static CalendarEvent Event(
string summary,
DateTimeOffset start,
DateTimeOffset end,
string? description = null
) =>
new()
{
Summary = summary,
Description = description,
Start = new CalDateTime(start.UtcDateTime, "UTC"),
End = new CalDateTime(end.UtcDateTime, "UTC"),
};
Expand Down Expand Up @@ -82,6 +88,51 @@ public void DailyReminder_SkipsEventsThatStartedEarlier()
Assert.That(SentTo(adapter, "all"), Does.Contain("招新宣讲").And.Not.Contain("例会筹备"));
}

[Test]
public void Reminders_SkipEventsMarkedWithSkipNotification()
{
var calendar = new Ical.Net.Calendar();
calendar.Events.Add(
Event(
"NWDC (待定)",
Now.AddHours(25),
Now.AddHours(27),
"具体日期待确认\n[skip notification]"
)
);
calendar.Events.Add(Event("正式活动", Now.AddHours(26), Now.AddHours(28)));
var config = new PluginConfig
{
ReminderGroups = [new() { GroupId = "all", Mode = FilterMode.BlackList, Keywords = [] }],
};
var adapter = new RecordingAdapter();
var reminder = new ClubAffairsReminder(adapter, config, () => calendar);

Assert.Multiple(() =>
{
Assert.That(reminder.SendWeeklySummary(Now), Is.True);
Assert.That(reminder.SendDailyReminder(Now), Is.True);
Assert.That(SentTo(adapter, "all"), Does.Contain("正式活动").And.Not.Contain("NWDC"));
});
}

[TestCase("[skip notification]")]
[TestCase("Details\n[SKIP NOTIFICATION]\n")]
public void SkipNotificationKeyword_IsCaseInsensitive(string description)
{
var calendarEvent = Event("Placeholder", Now, Now.AddHours(1), description);

Assert.That(calendarEvent.ShouldSkipNotification(), Is.True);
}

[Test]
public void SkipNotificationKeyword_DoesNotHideOtherEvents()
{
var calendarEvent = Event("Real event", Now, Now.AddHours(1), "No notification marker");

Assert.That(calendarEvent.ShouldSkipNotification(), Is.False);
}

[Test]
public void Reminders_WithoutCalendar_AreRetriedLater()
{
Expand Down
Loading