From 635bf2660100dbc9a5c58c066b45bf81240c5021 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 16 Jun 2023 13:45:57 +0500 Subject: [PATCH] Implement some unused guild configuration options Signed-off-by: Octol1ttle --- Data/MemberData.cs | 3 +++ EventResponders.cs | 17 +++++++++++++ InteractionResponders.cs | 2 +- Services/Data/GuildDataService.cs | 18 +++++++++++--- Services/GuildUpdateService.cs | 40 ++++++++++++++++++++++++------- 5 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Data/MemberData.cs b/Data/MemberData.cs index e2bee2b..6cbf787 100644 --- a/Data/MemberData.cs +++ b/Data/MemberData.cs @@ -1,3 +1,5 @@ +using Remora.Rest.Core; + namespace Boyfriend.Data; public class MemberData { @@ -8,4 +10,5 @@ public class MemberData { public ulong Id { get; } public DateTimeOffset? BannedUntil { get; set; } + public List Roles { get; set; } = new(); } diff --git a/EventResponders.cs b/EventResponders.cs index 2efe194..0f095bb 100644 --- a/EventResponders.cs +++ b/EventResponders.cs @@ -288,3 +288,20 @@ public class GuildScheduledEventDeleteResponder : IResponder +/// Handles updating when a guild member is updated. +/// +public class GuildMemberUpdateResponder : IResponder { + private readonly GuildDataService _dataService; + + public GuildMemberUpdateResponder(GuildDataService dataService) { + _dataService = dataService; + } + + public async Task RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) { + var memberData = await _dataService.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct); + memberData.Roles = gatewayEvent.Roles.ToList(); + return Result.FromSuccess(); + } +} diff --git a/InteractionResponders.cs b/InteractionResponders.cs index 6d2b729..231df31 100644 --- a/InteractionResponders.cs +++ b/InteractionResponders.cs @@ -23,7 +23,7 @@ public class InteractionResponders : InteractionGroup { /// A button that will output an ephemeral embed containing the information about a scheduled event. /// /// The ID of the guild and scheduled event, encoded as "guildId:eventId". - /// A feedback sending result which may or may not have succeeded. + /// An ephemeral feedback sending result which may or may not have succeeded. [Button("scheduled-event-details")] public async Task OnStatefulButtonClicked(string? state = null) { if (state is null) return Result.FromError(new ArgumentNullError(nameof(state))); diff --git a/Services/Data/GuildDataService.cs b/Services/Data/GuildDataService.cs index 4c4aa88..4552597 100644 --- a/Services/Data/GuildDataService.cs +++ b/Services/Data/GuildDataService.cs @@ -1,6 +1,8 @@ using System.Text.Json; using Boyfriend.Data; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Remora.Discord.API.Abstractions.Rest; using Remora.Rest.Core; namespace Boyfriend.Services.Data; @@ -10,9 +12,14 @@ namespace Boyfriend.Services.Data; /// public class GuildDataService : IHostedService { private readonly Dictionary _datas = new(); + private readonly IDiscordRestGuildAPI _guildApi; + private readonly ILogger _logger; // https://github.com/dotnet/aspnetcore/issues/39139 - public GuildDataService(IHostApplicationLifetime lifetime) { + public GuildDataService( + IHostApplicationLifetime lifetime, IDiscordRestGuildAPI guildApi, ILogger logger) { + _guildApi = guildApi; + _logger = logger; lifetime.ApplicationStopping.Register(ApplicationStopping); } @@ -75,6 +82,11 @@ public class GuildDataService : IHostedService { await using var dataStream = File.OpenRead(dataPath); var data = await JsonSerializer.DeserializeAsync(dataStream, cancellationToken: ct); if (data is null) continue; + var memberResult = await _guildApi.GetGuildMemberAsync(guildId, data.Id.ToDiscordSnowflake(), ct); + if (memberResult.IsSuccess) + data.Roles = memberResult.Entity.Roles.ToList(); + else + _logger.LogWarning("Error in member retrieval.\n{ErrorMessage}", memberResult.Error.Message); memberData.Add(data.Id, data); } @@ -91,9 +103,9 @@ public class GuildDataService : IHostedService { return (await GetData(guildId, ct)).Configuration; } - /*public async Task GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) { + public async Task GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) { return (await GetData(guildId, ct)).GetMemberData(userId); - }*/ + } public IEnumerable GetGuildIds() { return _datas.Keys; diff --git a/Services/GuildUpdateService.cs b/Services/GuildUpdateService.cs index b977591..dcc4d1b 100644 --- a/Services/GuildUpdateService.cs +++ b/Services/GuildUpdateService.cs @@ -63,7 +63,9 @@ public class GuildUpdateService : BackgroundService { /// This method does the following: /// /// Automatically unbans users once their ban period has expired. + /// Automatically grants users the guild's if one is set. /// Sends reminders about an upcoming scheduled event. + /// Automatically starts scheduled events if is enabled. /// Sends scheduled event start notifications. /// Sends scheduled event completion notifications. /// @@ -73,7 +75,7 @@ public class GuildUpdateService : BackgroundService { /// 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 Discord API doesn't provide necessary information about scheduled event updates. /// /// /// The ID of the guild to update. @@ -81,17 +83,29 @@ public class GuildUpdateService : BackgroundService { private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) { var data = await _dataService.GetData(guildId, ct); Messages.Culture = data.Culture; + var defaultRoleSnowflake = data.Configuration.DefaultRole.ToDiscordSnowflake(); + + foreach (var memberData in data.MemberData.Values) { + var userIdSnowflake = memberData.Id.ToDiscordSnowflake(); + if (!memberData.Roles.Contains(defaultRoleSnowflake)) { + var defaultRoleResult = await _guildApi.AddGuildMemberRoleAsync( + guildId, userIdSnowflake, defaultRoleSnowflake, ct: ct); + if (!defaultRoleResult.IsSuccess) + _logger.LogWarning( + "Error in automatic default role add request.\n{ErrorMessage}", + defaultRoleResult.Error.Message); + } - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var memberData in data.MemberData.Values) if (DateTimeOffset.UtcNow > memberData.BannedUntil) { var unbanResult = await _guildApi.RemoveGuildBanAsync( - guildId, memberData.Id.ToDiscordSnowflake(), Messages.PunishmentExpired.EncodeHeader(), ct); + guildId, userIdSnowflake, Messages.PunishmentExpired.EncodeHeader(), ct); if (unbanResult.IsSuccess) memberData.BannedUntil = null; else - _logger.LogWarning("Error in member data update.\n{ErrorMessage}", unbanResult.Error.Message); + _logger.LogWarning( + "Error in automatic user unban request.\n{ErrorMessage}", unbanResult.Error.Message); } + } var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct); if (!eventsResult.IsDefined(out var events)) return; @@ -104,9 +118,19 @@ public class GuildUpdateService : BackgroundService { } else { var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value]; if (storedEvent.Status == scheduledEvent.Status) { - if (DateTimeOffset.UtcNow - >= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset - && !storedEvent.EarlyNotificationSent) { + if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) { + if (scheduledEvent.Status is not GuildScheduledEventStatus.Active) { + var startResult = await _eventApi.ModifyGuildScheduledEventAsync( + guildId, scheduledEvent.ID, + status: GuildScheduledEventStatus.Active, ct: ct); + if (!startResult.IsSuccess) + _logger.LogWarning( + "Error in automatic scheduled event start request.\n{ErrorMessage}", + startResult.Error.Message); + } + } else if (DateTimeOffset.UtcNow + >= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset + && !storedEvent.EarlyNotificationSent) { var earlyResult = await SendScheduledEventUpdatedMessage(scheduledEvent, data, true, ct); if (earlyResult.IsSuccess) storedEvent.EarlyNotificationSent = true;