diff --git a/Services/GuildUpdateService.cs b/Services/GuildUpdateService.cs index 8bb438c..b977591 100644 --- a/Services/GuildUpdateService.cs +++ b/Services/GuildUpdateService.cs @@ -7,12 +7,16 @@ using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Formatting; +using Remora.Discord.Gateway.Responders; using Remora.Discord.Interactivity; using Remora.Rest.Core; using Remora.Results; namespace Boyfriend.Services; +/// +/// Handles executing guild updates (also called "ticks") once per second. +/// public class GuildUpdateService : BackgroundService { private readonly IDiscordRestChannelAPI _channelApi; private readonly GuildDataService _dataService; @@ -35,6 +39,11 @@ public class GuildUpdateService : BackgroundService { _utility = utility; } + /// + /// Activates a periodic timer with a 1 second interval and adds guild update tasks on each timer tick. + /// + /// If update tasks take longer than 1 second, the next timer tick will be skipped. + /// The cancellation token for this operation. protected override async Task ExecuteAsync(CancellationToken ct) { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); var tasks = new List(); @@ -47,6 +56,28 @@ public class GuildUpdateService : BackgroundService { } } + /// + /// Runs an update ("tick") for a guild with the provided . + /// + /// + /// This method does the following: + /// + /// Automatically unbans users once their ban period has expired. + /// Sends reminders about an upcoming scheduled event. + /// Sends scheduled event start notifications. + /// Sends scheduled event completion notifications. + /// + /// This is done here and not in a for the following reasons: + /// + /// + /// Downtime would affect the reliability of notifications and automatic unbans if this logic were to be in a + /// . + /// + /// The Discord API doesn't provide necessary about scheduled event updates. + /// + /// + /// The ID of the guild to update. + /// The cancellation token for this operation. private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) { var data = await _dataService.GetData(guildId, ct); Messages.Culture = data.Culture; @@ -76,7 +107,7 @@ public class GuildUpdateService : BackgroundService { if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset && !storedEvent.EarlyNotificationSent) { - var earlyResult = await SendScheduledEventStartedMessage(scheduledEvent, data, true, ct); + var earlyResult = await SendScheduledEventUpdatedMessage(scheduledEvent, data, true, ct); if (earlyResult.IsSuccess) storedEvent.EarlyNotificationSent = true; else @@ -95,7 +126,7 @@ public class GuildUpdateService : BackgroundService { GuildScheduledEventStatus.Scheduled => await SendScheduledEventCreatedMessage(scheduledEvent, data.Configuration, ct), GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed => - await SendScheduledEventStartedMessage(scheduledEvent, data, false, ct), + await SendScheduledEventUpdatedMessage(scheduledEvent, data, false, ct), _ => Result.FromError(new ArgumentOutOfRangeError(nameof(scheduledEvent.Status))) }; @@ -110,6 +141,10 @@ public class GuildUpdateService : BackgroundService { /// when a scheduled event is created /// in a guild's if one is set. /// + /// The scheduled event that has just been created. + /// The configuration of the guild containing the scheduled event. + /// The cancellation token for this operation. + /// A notification sending result which may or may not have succeeded. private async Task SendScheduledEventCreatedMessage( IGuildScheduledEvent scheduledEvent, GuildConfiguration config, CancellationToken ct = default) { var currentUserResult = await _userApi.GetCurrentUserAsync(ct); @@ -189,7 +224,12 @@ public class GuildUpdateService : BackgroundService { /// when a scheduled event is about to start, has started or completed /// in a guild's if one is set. /// - private async Task SendScheduledEventStartedMessage( + /// The scheduled event that is about to start, has started or completed. + /// The data for the guild containing the scheduled event. + /// Controls whether or not a reminder for the scheduled event should be sent instead of the event started/completed notification + /// The cancellation token for this operation + /// A reminder/notification sending result which may or may not have succeeded. + private async Task SendScheduledEventUpdatedMessage( IGuildScheduledEvent scheduledEvent, GuildData data, bool early, CancellationToken ct = default) { var currentUserResult = await _userApi.GetCurrentUserAsync(ct); if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult); @@ -233,7 +273,8 @@ public class GuildUpdateService : BackgroundService { return Result.FromError(new ArgumentOutOfRangeError(nameof(scheduledEvent.EntityType))); } - var contentResult = await _utility.GetEventNotificationMentions(data, scheduledEvent, ct); + var contentResult = await _utility.GetEventNotificationMentions( + scheduledEvent, data.Configuration, ct); if (!contentResult.IsDefined(out content)) return Result.FromError(contentResult); diff --git a/Services/UtilityService.cs b/Services/UtilityService.cs index 4c6da4d..b4ff6fb 100644 --- a/Services/UtilityService.cs +++ b/Services/UtilityService.cs @@ -102,11 +102,26 @@ public class UtilityService : IHostedService { return Result.FromSuccess(null); } + /// + /// Gets the string mentioning all s related to a scheduled + /// event. + /// + /// + /// If the guild configuration enables , then the + /// will also be mentioned. + /// + /// + /// The scheduled event whose subscribers will be mentioned if the guild configuration enables + /// . + /// + /// The configuration of the guild containing the scheduled event + /// The cancellation token for this operation. + /// A result containing the string which may or may not have succeeded. public async Task> GetEventNotificationMentions( - GuildData data, IGuildScheduledEvent scheduledEvent, CancellationToken ct = default) { + IGuildScheduledEvent scheduledEvent, GuildConfiguration config, CancellationToken ct = default) { var builder = new StringBuilder(); - var receivers = data.Configuration.EventStartedReceivers; - var role = data.Configuration.EventNotificationRole.ToDiscordSnowflake(); + var receivers = config.EventStartedReceivers; + var role = config.EventNotificationRole.ToDiscordSnowflake(); var usersResult = await _eventApi.GetGuildScheduledEventUsersAsync( scheduledEvent.GuildID, scheduledEvent.ID, withMember: true, ct: ct); if (!usersResult.IsDefined(out var users)) return Result.FromError(usersResult);