mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-04-20 00:43:36 +03:00
Fix various issues with ScheduledEventUpdateService
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
This commit is contained in:
parent
da2a88246c
commit
88c6533729
6 changed files with 190 additions and 112 deletions
|
@ -8,12 +8,20 @@ namespace Boyfriend.Data;
|
||||||
/// <remarks>This information is stored on disk as a JSON file.</remarks>
|
/// <remarks>This information is stored on disk as a JSON file.</remarks>
|
||||||
public sealed class ScheduledEventData
|
public sealed class ScheduledEventData
|
||||||
{
|
{
|
||||||
public ScheduledEventData(GuildScheduledEventStatus? status)
|
public ScheduledEventData(ulong id, string name, GuildScheduledEventStatus status,
|
||||||
|
DateTimeOffset scheduledStartTime)
|
||||||
{
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
Status = status;
|
Status = status;
|
||||||
|
ScheduledStartTime = scheduledStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ulong Id { get; }
|
||||||
|
public string Name { get; set; }
|
||||||
public bool EarlyNotificationSent { get; set; }
|
public bool EarlyNotificationSent { get; set; }
|
||||||
|
public DateTimeOffset ScheduledStartTime { get; set; }
|
||||||
public DateTimeOffset? ActualStartTime { get; set; }
|
public DateTimeOffset? ActualStartTime { get; set; }
|
||||||
public GuildScheduledEventStatus? Status { get; set; }
|
public GuildScheduledEventStatus? Status { get; set; }
|
||||||
|
public bool ScheduleOnStatusUpdated { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,21 @@ public class GuildLoadedResponder : IResponder<IGuildCreate>
|
||||||
data.GetOrCreateMemberData(member.User.Value.ID);
|
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))
|
if (!GuildSettings.ReceiveStartupMessages.Get(cfg))
|
||||||
{
|
{
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles sending a notification when a scheduled event has been cancelled
|
|
||||||
/// in a guild's <see cref="GuildSettings.EventNotificationChannel" /> if one is set.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEventDelete>
|
|
||||||
{
|
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
|
||||||
private readonly GuildDataService _guildData;
|
|
||||||
|
|
||||||
public GuildScheduledEventDeleteResponder(IDiscordRestChannelAPI channelApi, GuildDataService guildData)
|
|
||||||
{
|
|
||||||
_channelApi = channelApi;
|
|
||||||
_guildData = guildData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Result> 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);
|
|
||||||
}
|
|
||||||
}
|
|
32
src/Responders/ScheduledEventCreatedResponder.cs
Normal file
32
src/Responders/ScheduledEventCreatedResponder.cs
Normal file
|
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles adding a scheduled event to a guild's ScheduledEventData.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class ScheduledEventCreatedResponder : IResponder<IGuildScheduledEventCreate>
|
||||||
|
{
|
||||||
|
private readonly GuildDataService _guildData;
|
||||||
|
|
||||||
|
public ScheduledEventCreatedResponder(GuildDataService guildData)
|
||||||
|
{
|
||||||
|
_guildData = guildData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> 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();
|
||||||
|
}
|
||||||
|
}
|
30
src/Responders/ScheduledEventUpdatedResponder.cs
Normal file
30
src/Responders/ScheduledEventUpdatedResponder.cs
Normal file
|
@ -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<IGuildScheduledEventUpdate>
|
||||||
|
{
|
||||||
|
private readonly GuildDataService _guildData;
|
||||||
|
|
||||||
|
public ScheduledEventUpdatedResponder(GuildDataService guildData)
|
||||||
|
{
|
||||||
|
_guildData = guildData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,40 +61,56 @@ public sealed class ScheduledEventUpdateService : BackgroundService
|
||||||
return Result.FromError(eventsResult);
|
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.ScheduleOnStatusUpdated)
|
||||||
if (storedEvent.Status == scheduledEvent.Status)
|
|
||||||
{
|
{
|
||||||
var tickResult = await TickScheduledEventAsync(guildId, data, scheduledEvent, storedEvent, ct);
|
var tickResult = await TickScheduledEventAsync(guildId, data, scheduledEvent.Entity, storedEvent, ct);
|
||||||
failedResults.AddIfFailed(tickResult);
|
failedResults.AddIfFailed(tickResult);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusChangedResponseResult = scheduledEvent.Status switch
|
var statusUpdatedResponseResult = storedEvent.Status switch
|
||||||
{
|
{
|
||||||
GuildScheduledEventStatus.Scheduled =>
|
GuildScheduledEventStatus.Scheduled =>
|
||||||
await SendScheduledEventCreatedMessage(scheduledEvent, data.Settings, ct),
|
await SendScheduledEventCreatedMessage(scheduledEvent.Entity, data.Settings, ct),
|
||||||
GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed =>
|
GuildScheduledEventStatus.Canceled =>
|
||||||
await SendScheduledEventUpdatedMessage(scheduledEvent, data, ct),
|
await SendScheduledEventCancelledMessage(storedEvent, data, ct),
|
||||||
_ => new ArgumentOutOfRangeError(nameof(scheduledEvent.Status))
|
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();
|
return failedResults.AggregateErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Result<IGuildScheduledEvent> TryGetScheduledEvent(IEnumerable<IGuildScheduledEvent> from, ulong id)
|
||||||
|
{
|
||||||
|
var filtered = from.Where(schEvent => schEvent.ID == id);
|
||||||
|
var filteredArray = filtered.ToArray();
|
||||||
|
return filteredArray.Any()
|
||||||
|
? Result<IGuildScheduledEvent>.FromSuccess(filteredArray.Single())
|
||||||
|
: new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Result> TickScheduledEventAsync(
|
private async Task<Result> TickScheduledEventAsync(
|
||||||
Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData,
|
Snowflake guildId, GuildData data, IGuildScheduledEvent scheduledEvent, ScheduledEventData eventData,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
|
@ -240,63 +256,57 @@ public sealed class ScheduledEventUpdateService : BackgroundService
|
||||||
/// <param name="data">The data for the guild containing the scheduled event.</param>
|
/// <param name="data">The data for the guild containing the scheduled event.</param>
|
||||||
/// <param name="ct">The cancellation token for this operation</param>
|
/// <param name="ct">The cancellation token for this operation</param>
|
||||||
/// <returns>A reminder/notification sending result which may or may not have succeeded.</returns>
|
/// <returns>A reminder/notification sending result which may or may not have succeeded.</returns>
|
||||||
private async Task<Result> SendScheduledEventUpdatedMessage(
|
private async Task<Result> SendScheduledEventStartedMessage(
|
||||||
IGuildScheduledEvent scheduledEvent, GuildData data, CancellationToken ct = default)
|
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
|
var contentResult = await _utility.GetEventNotificationMentions(
|
||||||
{
|
scheduledEvent, data.Settings, ct);
|
||||||
GuildScheduledEventEntityType.StageInstance or GuildScheduledEventEntityType.Voice =>
|
if (!contentResult.IsDefined(out var content))
|
||||||
GetLocalEventStartedEmbedDescription(scheduledEvent),
|
{
|
||||||
GuildScheduledEventEntityType.External => GetExternalEventStartedEmbedDescription(scheduledEvent),
|
return Result.FromError(contentResult);
|
||||||
_ => 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<Result> SendScheduledEventCompletedMessage(ScheduledEventData eventData, GuildData data,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var completedEmbed = new EmbedBuilder().WithTitle(string.Format(Messages.EventCompleted, eventData.Name))
|
||||||
.WithDescription(
|
.WithDescription(
|
||||||
string.Format(
|
string.Format(
|
||||||
Messages.EventDuration,
|
Messages.EventDuration,
|
||||||
DateTimeOffset.UtcNow.Subtract(
|
DateTimeOffset.UtcNow.Subtract(
|
||||||
data.ScheduledEvents[scheduledEvent.ID.Value].ActualStartTime
|
eventData.ActualStartTime
|
||||||
?? scheduledEvent.ScheduledStartTime).ToString()))
|
?? eventData.ScheduledStartTime).ToString()))
|
||||||
.WithColour(ColorsList.Black)
|
.WithColour(ColorsList.Black)
|
||||||
.WithCurrentTimestamp()
|
.WithCurrentTimestamp()
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -306,9 +316,45 @@ public sealed class ScheduledEventUpdateService : BackgroundService
|
||||||
return Result.FromError(completedEmbed);
|
return Result.FromError(completedEmbed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
var createResult = (Result)await _channelApi.CreateMessageAsync(
|
||||||
GuildSettings.EventNotificationChannel.Get(data.Settings),
|
GuildSettings.EventNotificationChannel.Get(data.Settings),
|
||||||
embeds: new[] { completedBuilt }, ct: ct);
|
embeds: new[] { completedBuilt }, ct: ct);
|
||||||
|
if (createResult.IsSuccess)
|
||||||
|
{
|
||||||
|
data.ScheduledEvents.Remove(eventData.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Result> 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<string> GetLocalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent)
|
private static Result<string> GetLocalEventStartedEmbedDescription(IGuildScheduledEvent scheduledEvent)
|
||||||
|
|
Loading…
Add table
Reference in a new issue