diff --git a/src/Data/ScheduledEventData.cs b/src/Data/ScheduledEventData.cs
index 7cd0578..8af9c92 100644
--- a/src/Data/ScheduledEventData.cs
+++ b/src/Data/ScheduledEventData.cs
@@ -8,12 +8,20 @@ namespace Boyfriend.Data;
/// This information is stored on disk as a JSON file.
public sealed class ScheduledEventData
{
- public ScheduledEventData(GuildScheduledEventStatus? status)
+ public ScheduledEventData(ulong id, string name, GuildScheduledEventStatus status,
+ DateTimeOffset scheduledStartTime)
{
+ Id = id;
+ Name = name;
Status = status;
+ ScheduledStartTime = scheduledStartTime;
}
+ public ulong Id { get; }
+ public string Name { get; set; }
public bool EarlyNotificationSent { get; set; }
+ public DateTimeOffset ScheduledStartTime { get; set; }
public DateTimeOffset? ActualStartTime { get; set; }
public GuildScheduledEventStatus? Status { get; set; }
+ public bool ScheduleOnStatusUpdated { get; set; } = true;
}
diff --git a/src/Responders/GuildLoadedResponder.cs b/src/Responders/GuildLoadedResponder.cs
index 9dd2f97..a5b8b19 100644
--- a/src/Responders/GuildLoadedResponder.cs
+++ b/src/Responders/GuildLoadedResponder.cs
@@ -50,6 +50,21 @@ public class GuildLoadedResponder : IResponder
data.GetOrCreateMemberData(member.User.Value.ID);
}
+ foreach (var schEvent in guild.GuildScheduledEvents)
+ {
+ if (!data.ScheduledEvents.TryGetValue(schEvent.ID.Value, out var eventData))
+ {
+ data.ScheduledEvents.Add(schEvent.ID.Value, new ScheduledEventData(schEvent.ID.Value,
+ schEvent.Name, schEvent.Status, schEvent.ScheduledStartTime));
+ continue;
+ }
+
+ eventData.Name = schEvent.Name;
+ eventData.ScheduledStartTime = schEvent.ScheduledStartTime;
+ eventData.ScheduleOnStatusUpdated = eventData.Status != schEvent.Status;
+ eventData.Status = schEvent.Status;
+ }
+
if (!GuildSettings.ReceiveStartupMessages.Get(cfg))
{
return Result.FromSuccess();
diff --git a/src/Responders/ScheduledEventCancelledResponder.cs b/src/Responders/ScheduledEventCancelledResponder.cs
deleted file mode 100644
index c35128a..0000000
--- a/src/Responders/ScheduledEventCancelledResponder.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Boyfriend.Data;
-using Boyfriend.Services;
-using JetBrains.Annotations;
-using Remora.Discord.API.Abstractions.Gateway.Events;
-using Remora.Discord.API.Abstractions.Rest;
-using Remora.Discord.Extensions.Embeds;
-using Remora.Discord.Gateway.Responders;
-using Remora.Results;
-
-namespace Boyfriend.Responders;
-
-///
-/// Handles sending a notification when a scheduled event has been cancelled
-/// in a guild's if one is set.
-///
-[UsedImplicitly]
-public class GuildScheduledEventDeleteResponder : IResponder
-{
- private readonly IDiscordRestChannelAPI _channelApi;
- private readonly GuildDataService _guildData;
-
- public GuildScheduledEventDeleteResponder(IDiscordRestChannelAPI channelApi, GuildDataService guildData)
- {
- _channelApi = channelApi;
- _guildData = guildData;
- }
-
- public async Task RespondAsync(IGuildScheduledEventDelete gatewayEvent, CancellationToken ct = default)
- {
- var guildData = await _guildData.GetData(gatewayEvent.GuildID, ct);
- guildData.ScheduledEvents.Remove(gatewayEvent.ID.Value);
-
- if (GuildSettings.EventNotificationChannel.Get(guildData.Settings).Empty())
- {
- return Result.FromSuccess();
- }
-
- var embed = new EmbedBuilder()
- .WithSmallTitle(string.Format(Messages.EventCancelled, gatewayEvent.Name))
- .WithDescription(":(")
- .WithColour(ColorsList.Red)
- .WithCurrentTimestamp()
- .Build();
-
- if (!embed.IsDefined(out var built))
- {
- return Result.FromError(embed);
- }
-
- return (Result)await _channelApi.CreateMessageAsync(
- GuildSettings.EventNotificationChannel.Get(guildData.Settings), embeds: new[] { built }, ct: ct);
- }
-}
diff --git a/src/Responders/ScheduledEventCreatedResponder.cs b/src/Responders/ScheduledEventCreatedResponder.cs
new file mode 100644
index 0000000..36f313a
--- /dev/null
+++ b/src/Responders/ScheduledEventCreatedResponder.cs
@@ -0,0 +1,32 @@
+using Boyfriend.Data;
+using Boyfriend.Services;
+using JetBrains.Annotations;
+using Remora.Discord.API.Abstractions.Gateway.Events;
+using Remora.Discord.Gateway.Responders;
+using Remora.Results;
+
+namespace Boyfriend.Responders;
+
+///
+/// Handles adding a scheduled event to a guild's ScheduledEventData.
+///
+[UsedImplicitly]
+public class ScheduledEventCreatedResponder : IResponder
+{
+ private readonly GuildDataService _guildData;
+
+ public ScheduledEventCreatedResponder(GuildDataService guildData)
+ {
+ _guildData = guildData;
+ }
+
+ public async Task RespondAsync(IGuildScheduledEventCreate gatewayEvent, CancellationToken ct = default)
+ {
+ var data = await _guildData.GetData(gatewayEvent.GuildID, ct);
+ data.ScheduledEvents.Add(gatewayEvent.ID.Value,
+ new ScheduledEventData(gatewayEvent.ID.Value,
+ gatewayEvent.Name, gatewayEvent.Status, gatewayEvent.ScheduledStartTime));
+
+ return Result.FromSuccess();
+ }
+}
diff --git a/src/Responders/ScheduledEventUpdatedResponder.cs b/src/Responders/ScheduledEventUpdatedResponder.cs
new file mode 100644
index 0000000..7db7edd
--- /dev/null
+++ b/src/Responders/ScheduledEventUpdatedResponder.cs
@@ -0,0 +1,30 @@
+using Boyfriend.Services;
+using JetBrains.Annotations;
+using Remora.Discord.API.Abstractions.Gateway.Events;
+using Remora.Discord.Gateway.Responders;
+using Remora.Results;
+
+namespace Boyfriend.Responders;
+
+[UsedImplicitly]
+public class ScheduledEventUpdatedResponder : IResponder
+{
+ private readonly GuildDataService _guildData;
+
+ public ScheduledEventUpdatedResponder(GuildDataService guildData)
+ {
+ _guildData = guildData;
+ }
+
+ public async Task RespondAsync(IGuildScheduledEventUpdate gatewayEvent, CancellationToken ct = default)
+ {
+ var data = await _guildData.GetData(gatewayEvent.GuildID, ct);
+ var eventData = data.ScheduledEvents[gatewayEvent.ID.Value];
+ eventData.Name = gatewayEvent.Name;
+ eventData.ScheduledStartTime = gatewayEvent.ScheduledStartTime;
+ eventData.ScheduleOnStatusUpdated = eventData.Status != gatewayEvent.Status;
+ eventData.Status = gatewayEvent.Status;
+
+ return Result.FromSuccess();
+ }
+}
diff --git a/src/Services/Update/ScheduledEventUpdateService.cs b/src/Services/Update/ScheduledEventUpdateService.cs
index 24b68da..de03fc5 100644
--- a/src/Services/Update/ScheduledEventUpdateService.cs
+++ b/src/Services/Update/ScheduledEventUpdateService.cs
@@ -61,40 +61,56 @@ public sealed class ScheduledEventUpdateService : BackgroundService
return Result.FromError(eventsResult);
}
- foreach (var scheduledEvent in events)
+ foreach (var storedEvent in data.ScheduledEvents.Values)
{
- if (!data.ScheduledEvents.ContainsKey(scheduledEvent.ID.Value))
+ var scheduledEvent = TryGetScheduledEvent(events, storedEvent.Id);
+ if (!scheduledEvent.IsSuccess)
{
- data.ScheduledEvents.Add(scheduledEvent.ID.Value, new ScheduledEventData(null));
+ storedEvent.ScheduleOnStatusUpdated = true;
+ storedEvent.Status = storedEvent.ActualStartTime != null
+ ? GuildScheduledEventStatus.Completed
+ : GuildScheduledEventStatus.Canceled;
}
- var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value];
- if (storedEvent.Status == scheduledEvent.Status)
+ if (!storedEvent.ScheduleOnStatusUpdated)
{
- var tickResult = await TickScheduledEventAsync(guildId, data, scheduledEvent, storedEvent, ct);
+ var tickResult = await TickScheduledEventAsync(guildId, data, scheduledEvent.Entity, storedEvent, ct);
failedResults.AddIfFailed(tickResult);
continue;
}
- var statusChangedResponseResult = scheduledEvent.Status switch
+ var statusUpdatedResponseResult = storedEvent.Status switch
{
GuildScheduledEventStatus.Scheduled =>
- await SendScheduledEventCreatedMessage(scheduledEvent, data.Settings, ct),
- GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed =>
- await SendScheduledEventUpdatedMessage(scheduledEvent, data, ct),
- _ => new ArgumentOutOfRangeError(nameof(scheduledEvent.Status))
+ await SendScheduledEventCreatedMessage(scheduledEvent.Entity, data.Settings, ct),
+ GuildScheduledEventStatus.Canceled =>
+ await SendScheduledEventCancelledMessage(storedEvent, data, ct),
+ GuildScheduledEventStatus.Active =>
+ await SendScheduledEventStartedMessage(scheduledEvent.Entity, data, ct),
+ GuildScheduledEventStatus.Completed =>
+ await SendScheduledEventCompletedMessage(storedEvent, data, ct),
+ _ => new ArgumentOutOfRangeError(nameof(storedEvent.Status))
};
- if (statusChangedResponseResult.IsSuccess)
+ if (statusUpdatedResponseResult.IsSuccess)
{
- storedEvent.Status = scheduledEvent.Status;
+ storedEvent.ScheduleOnStatusUpdated = false;
}
- failedResults.AddIfFailed(statusChangedResponseResult);
+ failedResults.AddIfFailed(statusUpdatedResponseResult);
}
return failedResults.AggregateErrors();
}
+ private static Result TryGetScheduledEvent(IEnumerable from, ulong id)
+ {
+ var filtered = from.Where(schEvent => schEvent.ID == id);
+ var filteredArray = filtered.ToArray();
+ return filteredArray.Any()
+ ? Result.FromSuccess(filteredArray.Single())
+ : new NotFoundError();
+ }
+
private async Task TickScheduledEventAsync(
Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData,
CancellationToken ct)
@@ -240,63 +256,57 @@ public sealed class ScheduledEventUpdateService : BackgroundService
/// The data for the guild containing the scheduled event.
/// The cancellation token for this operation
/// A reminder/notification sending result which may or may not have succeeded.
- private async Task SendScheduledEventUpdatedMessage(
+ private async Task SendScheduledEventStartedMessage(
IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default)
{
- if (scheduledEvent.Status == GuildScheduledEventStatus.Active)
+ data.ScheduledEvents[scheduledEvent.ID.Value].ActualStartTime = DateTimeOffset.UtcNow;
+
+ var embedDescriptionResult = scheduledEvent.EntityType switch
{
- data.ScheduledEvents[scheduledEvent.ID.Value].ActualStartTime = DateTimeOffset.UtcNow;
+ GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice =>
+ GetLocalEventStartedEmbedDescription(scheduledEvent),
+ GuildScheduledEventEntityType.External => GetExternalEventStartedEmbedDescription(scheduledEvent),
+ _ => new ArgumentOutOfRangeError(nameof(scheduledEvent.EntityType))
+ };
- var embedDescriptionResult = scheduledEvent.EntityType switch
- {
- GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice =>
- GetLocalEventStartedEmbedDescription(scheduledEvent),
- GuildScheduledEventEntityType.External => GetExternalEventStartedEmbedDescription(scheduledEvent),
- _ => new ArgumentOutOfRangeError(nameof(scheduledEvent.EntityType))
- };
-
- var contentResult = await _utility.GetEventNotificationMentions(
- scheduledEvent, data.Settings, ct);
- if (!contentResult.IsDefined(out var content))
- {
- return Result.FromError(contentResult);
- }
-
- if (!embedDescriptionResult.IsDefined(out var embedDescription))
- {
- return Result.FromError(embedDescriptionResult);
- }
-
- var startedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventStarted, scheduledEvent.Name))
- .WithDescription(embedDescription)
- .WithColour(ColorsList.Green)
- .WithCurrentTimestamp()
- .Build();
-
- if (!startedEmbed.IsDefined(out var startedBuilt))
- {
- return Result.FromError(startedEmbed);
- }
-
- return (Result)await _channelApi.CreateMessageAsync(
- GuildSettings.EventNotificationChannel.Get(data.Settings),
- content, embeds: new[] { startedBuilt }, ct: ct);
+ var contentResult = await _utility.GetEventNotificationMentions(
+ scheduledEvent, data.Settings, ct);
+ if (!contentResult.IsDefined(out var content))
+ {
+ return Result.FromError(contentResult);
}
- if (scheduledEvent.Status != GuildScheduledEventStatus.Completed)
+ if (!embedDescriptionResult.IsDefined(out var embedDescription))
{
- return new ArgumentOutOfRangeError(nameof(scheduledEvent.Status));
+ return Result.FromError(embedDescriptionResult);
}
- data.ScheduledEvents.Remove(scheduledEvent.ID.Value);
+ var startedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventStarted, scheduledEvent.Name))
+ .WithDescription(embedDescription)
+ .WithColour(ColorsList.Green)
+ .WithCurrentTimestamp()
+ .Build();
- var completedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventCompleted, scheduledEvent.Name))
+ if (!startedEmbed.IsDefined(out var startedBuilt))
+ {
+ return Result.FromError(startedEmbed);
+ }
+
+ return (Result)await _channelApi.CreateMessageAsync(
+ GuildSettings.EventNotificationChannel.Get(data.Settings),
+ content, embeds: new[] { startedBuilt }, ct: ct);
+ }
+
+ private async Task SendScheduledEventCompletedMessage(ScheduledEventData eventData, GuildData data,
+ CancellationToken ct)
+ {
+ var completedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventCompleted, eventData.Name))
.WithDescription(
string.Format(
Messages.EventDuration,
DateTimeOffset.UtcNow.Subtract(
- data.ScheduledEvents[scheduledEvent.ID.Value].ActualStartTime
- ?? scheduledEvent.ScheduledStartTime).ToString()))
+ eventData.ActualStartTime
+ ?? eventData.ScheduledStartTime).ToString()))
.WithColour(ColorsList.Black)
.WithCurrentTimestamp()
.Build();
@@ -306,9 +316,45 @@ public sealed class ScheduledEventUpdateService : BackgroundService
return Result.FromError(completedEmbed);
}
- return (Result)await _channelApi.CreateMessageAsync(
+ var createResult = (Result)await _channelApi.CreateMessageAsync(
GuildSettings.EventNotificationChannel.Get(data.Settings),
embeds: new[] { completedBuilt }, ct: ct);
+ if (createResult.IsSuccess)
+ {
+ data.ScheduledEvents.Remove(eventData.Id);
+ }
+
+ return createResult;
+ }
+
+ private async Task SendScheduledEventCancelledMessage(ScheduledEventData eventData, GuildData data,
+ CancellationToken ct)
+ {
+ if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty())
+ {
+ return Result.FromSuccess();
+ }
+
+ var embed = new EmbedBuilder()
+ .WithSmallTitle(string.Format(Messages.EventCancelled, eventData.Name))
+ .WithDescription(":(")
+ .WithColour(ColorsList.Red)
+ .WithCurrentTimestamp()
+ .Build();
+
+ if (!embed.IsDefined(out var built))
+ {
+ return Result.FromError(embed);
+ }
+
+ var createResult = (Result)await _channelApi.CreateMessageAsync(
+ GuildSettings.EventNotificationChannel.Get(data.Settings), embeds: new[] { built }, ct: ct);
+ if (createResult.IsSuccess)
+ {
+ data.ScheduledEvents.Remove(eventData.Id);
+ }
+
+ return createResult;
}
private static Result GetLocalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent)