mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-04-19 16:33:36 +03:00
Made guild settings code 10x better
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
This commit is contained in:
parent
c00585e639
commit
bbddb3790a
26 changed files with 452 additions and 303 deletions
5
locale/Messages.Designer.cs
generated
5
locale/Messages.Designer.cs
generated
|
@ -7,10 +7,7 @@
|
|||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Boyfriend {
|
||||
using System;
|
||||
|
||||
|
||||
namespace Boyfriend.locale {
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
|
|
|
@ -56,11 +56,11 @@ public class Boyfriend {
|
|||
| GatewayIntents.GuildMembers
|
||||
| GatewayIntents.GuildScheduledEvents);
|
||||
services.Configure<CacheSettings>(
|
||||
settings => {
|
||||
settings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
|
||||
settings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
|
||||
settings.SetAbsoluteExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||
settings.SetSlidingExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||
cSettings => {
|
||||
cSettings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
|
||||
cSettings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
|
||||
cSettings.SetAbsoluteExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||
cSettings.SetSlidingExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||
});
|
||||
|
||||
services.AddTransient<IConfigurationBuilder, ConfigurationBuilder>()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -51,8 +53,8 @@ public class AboutCommandGroup : CommandGroup {
|
|||
if (!currentUserResult.IsDefined(out var currentUser))
|
||||
return Result.FromError(currentUserResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var builder = new StringBuilder().AppendLine(Markdown.Bold(Messages.AboutTitleDevelopers));
|
||||
foreach (var dev in Developers)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -76,8 +78,8 @@ public class BanCommandGroup : CommandGroup {
|
|||
return Result.FromError(currentUserResult);
|
||||
|
||||
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
||||
var cfg = data.Configuration;
|
||||
Messages.Culture = data.Culture;
|
||||
var cfg = data.Settings;
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
|
||||
if (existingBanResult.IsDefined()) {
|
||||
|
@ -145,8 +147,10 @@ public class BanCommandGroup : CommandGroup {
|
|||
string.Format(Messages.UserBanned, target.GetTag()), target)
|
||||
.WithColour(ColorsList.Green).Build();
|
||||
|
||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
||||
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||
string.Format(Messages.UserBanned, target.GetTag()), target)
|
||||
.WithDescription(description)
|
||||
|
@ -160,14 +164,14 @@ public class BanCommandGroup : CommandGroup {
|
|||
|
||||
var builtArray = new[] { logBuilt };
|
||||
// Not awaiting to reduce response time
|
||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -209,8 +213,8 @@ public class BanCommandGroup : CommandGroup {
|
|||
if (!currentUserResult.IsDefined(out var currentUser))
|
||||
return Result.FromError(currentUserResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
|
||||
if (!existingBanResult.IsDefined()) {
|
||||
|
@ -238,8 +242,10 @@ public class BanCommandGroup : CommandGroup {
|
|||
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
||||
.WithColour(ColorsList.Green).Build();
|
||||
|
||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
||||
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
||||
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
||||
|
@ -254,14 +260,14 @@ public class BanCommandGroup : CommandGroup {
|
|||
var builtArray = new[] { logBuilt };
|
||||
|
||||
// Not awaiting to reduce response time
|
||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -64,8 +66,8 @@ public class ClearCommandGroup : CommandGroup {
|
|||
if (!messagesResult.IsDefined(out var messages))
|
||||
return Result.FromError(messagesResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var idList = new List<Snowflake>(messages.Count);
|
||||
var builder = new StringBuilder().AppendLine(Mention.Channel(channelId.Value)).AppendLine();
|
||||
|
@ -93,7 +95,8 @@ public class ClearCommandGroup : CommandGroup {
|
|||
return Result.FromError(currentUserResult);
|
||||
|
||||
var title = string.Format(Messages.MessagesCleared, amount.ToString());
|
||||
if (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value) {
|
||||
if (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value) {
|
||||
var logEmbed = new EmbedBuilder().WithSmallTitle(title, currentUser)
|
||||
.WithDescription(description)
|
||||
.WithActionFooter(user)
|
||||
|
@ -105,9 +108,9 @@ public class ClearCommandGroup : CommandGroup {
|
|||
return Result.FromError(logEmbed);
|
||||
|
||||
// Not awaiting to reduce response time
|
||||
if (cfg.PrivateFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { logBuilt },
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { logBuilt },
|
||||
ct: CancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.ComponentModel;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -71,8 +73,8 @@ public class KickCommandGroup : CommandGroup {
|
|||
return Result.FromError(currentUserResult);
|
||||
|
||||
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
||||
var cfg = data.Configuration;
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = data.Settings;
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
|
||||
if (!memberResult.IsSuccess) {
|
||||
|
@ -129,8 +131,10 @@ public class KickCommandGroup : CommandGroup {
|
|||
string.Format(Messages.UserKicked, target.GetTag()), target)
|
||||
.WithColour(ColorsList.Green).Build();
|
||||
|
||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
||||
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||
string.Format(Messages.UserKicked, target.GetTag()), target)
|
||||
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
||||
|
@ -144,14 +148,14 @@ public class KickCommandGroup : CommandGroup {
|
|||
|
||||
var builtArray = new[] { logBuilt };
|
||||
// Not awaiting to reduce response time
|
||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -93,8 +95,8 @@ public class MuteCommandGroup : CommandGroup {
|
|||
return Result.FromError(interactionResult);
|
||||
|
||||
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
||||
var cfg = data.Configuration;
|
||||
Messages.Culture = data.Culture;
|
||||
var cfg = data.Settings;
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
Result<Embed> responseEmbed;
|
||||
if (interactionResult.Entity is not null) {
|
||||
|
@ -116,8 +118,10 @@ public class MuteCommandGroup : CommandGroup {
|
|||
string.Format(Messages.UserMuted, target.GetTag()), target)
|
||||
.WithColour(ColorsList.Green).Build();
|
||||
|
||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
||||
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||
var builder = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason))
|
||||
.Append(
|
||||
string.Format(
|
||||
|
@ -136,14 +140,14 @@ public class MuteCommandGroup : CommandGroup {
|
|||
|
||||
var builtArray = new[] { logBuilt };
|
||||
// Not awaiting to reduce response time
|
||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -185,8 +189,8 @@ public class MuteCommandGroup : CommandGroup {
|
|||
if (!currentUserResult.IsDefined(out var currentUser))
|
||||
return Result.FromError(currentUserResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
|
||||
if (!memberResult.IsSuccess) {
|
||||
|
@ -220,8 +224,10 @@ public class MuteCommandGroup : CommandGroup {
|
|||
string.Format(Messages.UserUnmuted, target.GetTag()), target)
|
||||
.WithColour(ColorsList.Green).Build();
|
||||
|
||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
||||
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||
string.Format(Messages.UserUnmuted, target.GetTag()), target)
|
||||
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
||||
|
@ -236,14 +242,14 @@ public class MuteCommandGroup : CommandGroup {
|
|||
var builtArray = new[] { logBuilt };
|
||||
|
||||
// Not awaiting to reduce response time
|
||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||
_ = _channelApi.CreateMessageAsync(
|
||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||
ct: CancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.ComponentModel;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -53,8 +55,8 @@ public class PingCommandGroup : CommandGroup {
|
|||
if (!currentUserResult.IsDefined(out var currentUser))
|
||||
return Result.FromError(currentUserResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var latency = _client.Latency.TotalMilliseconds;
|
||||
if (latency is 0) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.ComponentModel;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -57,8 +58,8 @@ public class RemindCommandGroup : CommandGroup {
|
|||
|
||||
(await _dataService.GetMemberData(guildId.Value, userId.Value, CancellationToken)).Reminders.Add(
|
||||
new Reminder {
|
||||
RemindAt = remindAt,
|
||||
Channel = channelId.Value,
|
||||
At = remindAt,
|
||||
Channel = channelId.Value.Value,
|
||||
Text = message
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.Data.Options;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
|
@ -21,6 +22,22 @@ namespace Boyfriend.Commands;
|
|||
/// Handles the commands to list and modify per-guild settings: /settings and /settings list.
|
||||
/// </summary>
|
||||
public class SettingsCommandGroup : CommandGroup {
|
||||
private static readonly IOption[] AllOptions = {
|
||||
GuildSettings.Language,
|
||||
GuildSettings.WelcomeMessage,
|
||||
GuildSettings.ReceiveStartupMessages,
|
||||
GuildSettings.RemoveRolesOnMute,
|
||||
GuildSettings.ReturnRolesOnRejoin,
|
||||
GuildSettings.AutoStartEvents,
|
||||
GuildSettings.PublicFeedbackChannel,
|
||||
GuildSettings.PrivateFeedbackChannel,
|
||||
GuildSettings.EventNotificationChannel,
|
||||
GuildSettings.DefaultRole,
|
||||
GuildSettings.MuteRole,
|
||||
GuildSettings.EventNotificationRole,
|
||||
GuildSettings.EventEarlyNotificationOffset
|
||||
};
|
||||
|
||||
private readonly ICommandContext _context;
|
||||
private readonly GuildDataService _dataService;
|
||||
private readonly FeedbackService _feedbackService;
|
||||
|
@ -36,7 +53,7 @@ public class SettingsCommandGroup : CommandGroup {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A slash command that lists current per-guild settings.
|
||||
/// A slash command that lists current per-guild GuildSettings.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A feedback sending result which may or may not have succeeded.
|
||||
|
@ -52,19 +69,16 @@ public class SettingsCommandGroup : CommandGroup {
|
|||
if (!currentUserResult.IsDefined(out var currentUser))
|
||||
return Result.FromError(currentUserResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
foreach (var setting in typeof(GuildConfiguration).GetProperties()) {
|
||||
builder.Append(Markdown.InlineCode(setting.Name))
|
||||
foreach (var option in AllOptions) {
|
||||
builder.Append(Markdown.InlineCode(option.Name))
|
||||
.Append(": ");
|
||||
var something = setting.GetValue(cfg);
|
||||
if (something!.GetType() == typeof(List<GuildConfiguration.NotificationReceiver>)) {
|
||||
var list = (something as List<GuildConfiguration.NotificationReceiver>);
|
||||
builder.AppendLine(string.Join(", ", list!.Select(v => Markdown.InlineCode(v.ToString()))));
|
||||
} else { builder.AppendLine(Markdown.InlineCode(something.ToString()!)); }
|
||||
var something = option.GetAsObject(cfg);
|
||||
builder.AppendLine(Markdown.InlineCode(something.ToString()!));
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingsListTitle, currentUser)
|
||||
|
@ -77,7 +91,7 @@ public class SettingsCommandGroup : CommandGroup {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A slash command that modifies per-guild settings.
|
||||
/// A slash command that modifies per-guild GuildSettings.
|
||||
/// </summary>
|
||||
/// <param name="setting">The setting to modify.</param>
|
||||
/// <param name="value">The new value of the setting.</param>
|
||||
|
@ -96,40 +110,16 @@ public class SettingsCommandGroup : CommandGroup {
|
|||
if (!currentUserResult.IsDefined(out var currentUser))
|
||||
return Result.FromError(currentUserResult);
|
||||
|
||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
||||
Messages.Culture = cfg.GetCulture();
|
||||
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
PropertyInfo? property = null;
|
||||
var option = AllOptions.Single(
|
||||
o => string.Equals(setting, o.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
try {
|
||||
foreach (var prop in typeof(GuildConfiguration).GetProperties())
|
||||
if (string.Equals(setting, prop.Name, StringComparison.CurrentCultureIgnoreCase))
|
||||
property = prop;
|
||||
if (property == null || !property.CanWrite)
|
||||
throw new ApplicationException(Messages.SettingDoesntExist);
|
||||
var type = property.PropertyType;
|
||||
|
||||
if (value is "reset" or "default") { property.SetValue(cfg, null); } else if (type == typeof(string)) {
|
||||
if (setting == "language" && value is not ("ru" or "en" or "mctaylors-ru"))
|
||||
throw new ApplicationException(Messages.LanguageNotSupported);
|
||||
property.SetValue(cfg, value);
|
||||
} else {
|
||||
try {
|
||||
if (type == typeof(bool))
|
||||
property.SetValue(cfg, Convert.ToBoolean(value));
|
||||
|
||||
if (type == typeof(ulong)) {
|
||||
var id = Convert.ToUInt64(value);
|
||||
|
||||
property.SetValue(cfg, id);
|
||||
}
|
||||
} catch (Exception e) when (e is FormatException or OverflowException) {
|
||||
throw new ApplicationException(Messages.InvalidSettingValue);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
var setResult = option.Set(cfg, value);
|
||||
if (!setResult.IsSuccess) {
|
||||
var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, currentUser)
|
||||
.WithDescription(e.Message)
|
||||
.WithDescription(setResult.Error.Message)
|
||||
.WithColour(ColorsList.Red)
|
||||
.Build();
|
||||
if (!failedEmbed.IsDefined(out var failedBuilt)) return Result.FromError(failedEmbed);
|
||||
|
@ -139,9 +129,9 @@ public class SettingsCommandGroup : CommandGroup {
|
|||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append(Markdown.InlineCode(setting))
|
||||
builder.Append(Markdown.InlineCode(option.Name))
|
||||
.Append($" {Messages.SettingIsNow} ")
|
||||
.Append(Markdown.InlineCode(value));
|
||||
.Append(Markdown.InlineCode(option.GetAsObject(cfg).ToString()!));
|
||||
|
||||
var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingSuccessfullyChanged, currentUser)
|
||||
.WithDescription(builder.ToString())
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
using System.Globalization;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
||||
namespace Boyfriend.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Stores per-guild settings that can be set by a member
|
||||
/// with <see cref="DiscordPermission.ManageGuild" /> using the /settings command
|
||||
/// </summary>
|
||||
public class GuildConfiguration {
|
||||
/// <summary>
|
||||
/// Represents a scheduled event notification receiver.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used to selectively mention guild members when a scheduled event has started or is about to start.
|
||||
/// </remarks>
|
||||
public enum NotificationReceiver {
|
||||
Interested,
|
||||
Role
|
||||
}
|
||||
|
||||
public static readonly Dictionary<string, CultureInfo> CultureInfoCache = new() {
|
||||
{ "en", new CultureInfo("en-US") },
|
||||
{ "ru", new CultureInfo("ru-RU") },
|
||||
{ "mctaylors-ru", new CultureInfo("tt-RU") }
|
||||
};
|
||||
|
||||
public string Language { get; set; } = "en";
|
||||
|
||||
/// <summary>
|
||||
/// Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a new member joins the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>No message will be sent if set to "off", "disable" or "disabled".</item>
|
||||
/// <item><see cref="Messages.DefaultWelcomeMessage" /> will be sent if set to "default" or "reset"</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GuildMemberAddResponder" />
|
||||
public string WelcomeMessage { get; set; } = "default";
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not the <see cref="Messages.Ready" /> message should be sent
|
||||
/// in <see cref="PrivateFeedbackChannel" /> on startup.
|
||||
/// </summary>
|
||||
/// <seealso cref="GuildCreateResponder" />
|
||||
public bool ReceiveStartupMessages { get; set; }
|
||||
|
||||
public bool RemoveRolesOnMute { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not a guild member's roles are returned if he/she leaves and then joins back.
|
||||
/// </summary>
|
||||
/// <remarks>Roles will not be returned if the member left the guild because of /ban or /kick.</remarks>
|
||||
public bool ReturnRolesOnRejoin { get; set; }
|
||||
|
||||
public bool AutoStartEvents { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls what channel should all public messages be sent to.
|
||||
/// </summary>
|
||||
public ulong PublicFeedbackChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls what channel should all private, moderator-only messages be sent to.
|
||||
/// </summary>
|
||||
public ulong PrivateFeedbackChannel { get; set; }
|
||||
|
||||
public ulong EventNotificationChannel { get; set; }
|
||||
public ulong DefaultRole { get; set; }
|
||||
public ulong MuteRole { get; set; }
|
||||
public ulong EventNotificationRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls what guild members should be mentioned when a scheduled event has started or is about to start.
|
||||
/// </summary>
|
||||
/// <seealso cref="NotificationReceiver" />
|
||||
public List<NotificationReceiver> EventStartedReceivers { get; set; }
|
||||
= new() { NotificationReceiver.Interested, NotificationReceiver.Role };
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of time before a scheduled event to send a reminder in <see cref="EventNotificationChannel" />.
|
||||
/// </summary>
|
||||
public TimeSpan EventEarlyNotificationOffset { get; set; } = TimeSpan.Zero;
|
||||
|
||||
// Do not convert this to a property, else serialization will be attempted
|
||||
public CultureInfo GetCulture() {
|
||||
return CultureInfoCache[Language];
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Boyfriend.Data;
|
||||
|
@ -8,29 +8,26 @@ namespace Boyfriend.Data;
|
|||
/// </summary>
|
||||
/// <remarks>This information is stored on disk as a JSON file.</remarks>
|
||||
public class GuildData {
|
||||
public readonly GuildConfiguration Configuration;
|
||||
public readonly string ConfigurationPath;
|
||||
|
||||
public readonly Dictionary<ulong, MemberData> MemberData;
|
||||
public readonly string MemberDataPath;
|
||||
|
||||
public readonly Dictionary<ulong, ScheduledEventData> ScheduledEvents;
|
||||
public readonly string ScheduledEventsPath;
|
||||
public readonly JsonNode Settings;
|
||||
public readonly string SettingsPath;
|
||||
|
||||
public GuildData(
|
||||
GuildConfiguration configuration, string configurationPath,
|
||||
JsonNode settings, string settingsPath,
|
||||
Dictionary<ulong, ScheduledEventData> scheduledEvents, string scheduledEventsPath,
|
||||
Dictionary<ulong, MemberData> memberData, string memberDataPath) {
|
||||
Configuration = configuration;
|
||||
ConfigurationPath = configurationPath;
|
||||
Settings = settings;
|
||||
SettingsPath = settingsPath;
|
||||
ScheduledEvents = scheduledEvents;
|
||||
ScheduledEventsPath = scheduledEventsPath;
|
||||
MemberData = memberData;
|
||||
MemberDataPath = memberDataPath;
|
||||
}
|
||||
|
||||
public CultureInfo Culture => Configuration.GetCulture();
|
||||
|
||||
public MemberData GetMemberData(Snowflake userId) {
|
||||
if (MemberData.TryGetValue(userId.Value, out var existing)) return existing;
|
||||
|
||||
|
|
63
src/Data/GuildSettings.cs
Normal file
63
src/Data/GuildSettings.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using Boyfriend.Data.Options;
|
||||
using Boyfriend.locale;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
||||
namespace Boyfriend.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Contains all per-guild settings that can be set by a member
|
||||
/// with <see cref="DiscordPermission.ManageGuild" /> using the /settings command
|
||||
/// </summary>
|
||||
public static class GuildSettings {
|
||||
public static readonly LanguageOption Language = new("Language", "en");
|
||||
|
||||
/// <summary>
|
||||
/// Controls what message should be sent in <see cref="PublicFeedbackChannel" /> when a new member joins the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>No message will be sent if set to "off", "disable" or "disabled".</item>
|
||||
/// <item><see cref="Messages.DefaultWelcomeMessage" /> will be sent if set to "default" or "reset"</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GuildMemberAddResponder" />
|
||||
public static readonly Option<string> WelcomeMessage = new("WelcomeMessage", "default");
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not the <see cref="Messages.Ready" /> message should be sent
|
||||
/// in <see cref="PrivateFeedbackChannel" /> on startup.
|
||||
/// </summary>
|
||||
/// <seealso cref="GuildCreateResponder" />
|
||||
public static readonly BoolOption ReceiveStartupMessages = new("ReceiveStartupMessages", false);
|
||||
|
||||
public static readonly BoolOption RemoveRolesOnMute = new("RemoveRolesOnMute", false);
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not a guild member's roles are returned if he/she leaves and then joins back.
|
||||
/// </summary>
|
||||
/// <remarks>Roles will not be returned if the member left the guild because of /ban or /kick.</remarks>
|
||||
public static readonly BoolOption ReturnRolesOnRejoin = new("ReturnRolesOnRejoin", false);
|
||||
|
||||
public static readonly BoolOption AutoStartEvents = new("AutoStartEvents", false);
|
||||
|
||||
/// <summary>
|
||||
/// Controls what channel should all public messages be sent to.
|
||||
/// </summary>
|
||||
public static readonly SnowflakeOption PublicFeedbackChannel = new("PublicFeedbackChannel");
|
||||
|
||||
/// <summary>
|
||||
/// Controls what channel should all private, moderator-only messages be sent to.
|
||||
/// </summary>
|
||||
public static readonly SnowflakeOption PrivateFeedbackChannel = new("PrivateFeedbackChannel");
|
||||
|
||||
public static readonly SnowflakeOption EventNotificationChannel = new("EventNotificationChannel");
|
||||
public static readonly SnowflakeOption DefaultRole = new("DefaultRole");
|
||||
public static readonly SnowflakeOption MuteRole = new("MuteRole");
|
||||
public static readonly SnowflakeOption EventNotificationRole = new("EventNotificationRole");
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of time before a scheduled event to send a reminder in <see cref="EventNotificationChannel" />.
|
||||
/// </summary>
|
||||
public static readonly TimeSpanOption EventEarlyNotificationOffset = new(
|
||||
"EventEarlyNotificationOffset", TimeSpan.Zero);
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
using Remora.Rest.Core;
|
||||
|
||||
namespace Boyfriend.Data;
|
||||
|
||||
/// <summary>
|
||||
|
@ -13,6 +11,6 @@ public class MemberData {
|
|||
|
||||
public ulong Id { get; }
|
||||
public DateTimeOffset? BannedUntil { get; set; }
|
||||
public List<Snowflake> Roles { get; set; } = new();
|
||||
public List<ulong> Roles { get; set; } = new();
|
||||
public List<Reminder> Reminders { get; } = new();
|
||||
}
|
||||
|
|
31
src/Data/Options/BoolOption.cs
Normal file
31
src/Data/Options/BoolOption.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.locale;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend.Data.Options;
|
||||
|
||||
public class BoolOption : Option<bool> {
|
||||
public BoolOption(string name, bool defaultValue) : base(name, defaultValue) { }
|
||||
|
||||
public override Result Set(JsonNode settings, string from) {
|
||||
if (!TryParseBool(from, out var value))
|
||||
return Result.FromError(new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue));
|
||||
|
||||
settings[Name] = value;
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
|
||||
private static bool TryParseBool(string from, out bool value) {
|
||||
value = false;
|
||||
switch (from) {
|
||||
case "1" or "y" or "yes" or "д" or "да":
|
||||
value = true;
|
||||
return true;
|
||||
case "0" or "n" or "no" or "н" or "не" or "нет":
|
||||
value = false;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
10
src/Data/Options/IOption.cs
Normal file
10
src/Data/Options/IOption.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend.Data.Options;
|
||||
|
||||
public interface IOption {
|
||||
string Name { get; init; }
|
||||
object GetAsObject(JsonNode settings);
|
||||
Result Set(JsonNode settings, string from);
|
||||
}
|
31
src/Data/Options/LanguageOption.cs
Normal file
31
src/Data/Options/LanguageOption.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.locale;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend.Data.Options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class LanguageOption : Option<CultureInfo> {
|
||||
private static readonly Dictionary<string, CultureInfo> CultureInfoCache = new() {
|
||||
{ "en", new CultureInfo("en-US") },
|
||||
{ "ru", new CultureInfo("ru-RU") },
|
||||
{ "mctaylors-ru", new CultureInfo("tt-RU") }
|
||||
};
|
||||
|
||||
public LanguageOption(string name, string defaultValue) : base(name, CultureInfoCache[defaultValue]) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override CultureInfo Get(JsonNode settings) {
|
||||
var property = settings[Name];
|
||||
return property != null ? CultureInfoCache[property.GetValue<string>()] : DefaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Result Set(JsonNode settings, string from) {
|
||||
if (!CultureInfoCache.ContainsKey(from.ToLowerInvariant()))
|
||||
return Result.FromError(new ArgumentInvalidError(nameof(from), Messages.LanguageNotSupported));
|
||||
|
||||
return base.Set(settings, from.ToLowerInvariant());
|
||||
}
|
||||
}
|
45
src/Data/Options/Option.cs
Normal file
45
src/Data/Options/Option.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend.Data.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an per-guild option.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the option.</typeparam>
|
||||
public class Option<T> : IOption {
|
||||
internal readonly T DefaultValue;
|
||||
|
||||
public Option(string name, T defaultValue) {
|
||||
Name = name;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public Type Type { get; set; } = typeof(T);
|
||||
public string Name { get; init; }
|
||||
|
||||
public object GetAsObject(JsonNode settings) {
|
||||
return Get(settings)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the option from a <see cref="string" /> to the provided JsonNode.
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="JsonNode" /> to set the value to.</param>
|
||||
/// <param name="from">The string from which the new value of the option will be parsed.</param>
|
||||
/// <returns>A value setting result which may or may not have succeeded.</returns>
|
||||
public virtual Result Set(JsonNode settings, string from) {
|
||||
settings[Name] = from;
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the option from the provided <paramref name="settings" />.
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="JsonNode" /> to get the value from.</param>
|
||||
/// <returns>The value of the option.</returns>
|
||||
public virtual T Get(JsonNode settings) {
|
||||
var property = settings[Name];
|
||||
return property != null ? property.GetValue<T>() : DefaultValue;
|
||||
}
|
||||
}
|
23
src/Data/Options/SnowflakeOption.cs
Normal file
23
src/Data/Options/SnowflakeOption.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.locale;
|
||||
using Remora.Rest.Core;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend.Data.Options;
|
||||
|
||||
public class SnowflakeOption : Option<Snowflake> {
|
||||
public SnowflakeOption(string name) : base(name, 0UL.ToSnowflake()) { }
|
||||
|
||||
public override Snowflake Get(JsonNode settings) {
|
||||
var property = settings[Name];
|
||||
return property != null ? property.GetValue<ulong>().ToSnowflake() : DefaultValue;
|
||||
}
|
||||
|
||||
public override Result Set(JsonNode settings, string from) {
|
||||
if (!ulong.TryParse(from, out var parsed))
|
||||
return Result.FromError(new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue));
|
||||
|
||||
settings[Name] = parsed;
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
20
src/Data/Options/TimeSpanOption.cs
Normal file
20
src/Data/Options/TimeSpanOption.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.locale;
|
||||
using Remora.Commands.Parsers;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend.Data.Options;
|
||||
|
||||
public class TimeSpanOption : Option<TimeSpan> {
|
||||
private static readonly TimeSpanParser Parser = new();
|
||||
|
||||
public TimeSpanOption(string name, TimeSpan defaultValue) : base(name, defaultValue) { }
|
||||
|
||||
public override Result Set(JsonNode settings, string from) {
|
||||
if (!Parser.TryParseAsync(from).Result.IsDefined(out var span))
|
||||
return Result.FromError(new ArgumentInvalidError(nameof(from), Messages.InvalidSettingValue));
|
||||
|
||||
settings[Name] = span.ToString();
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
using Remora.Rest.Core;
|
||||
|
||||
namespace Boyfriend.Data;
|
||||
|
||||
public struct Reminder {
|
||||
public DateTimeOffset RemindAt;
|
||||
public DateTimeOffset At;
|
||||
public string Text;
|
||||
public Snowflake Channel;
|
||||
public ulong Channel;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Boyfriend.Services;
|
||||
using DiffPlex.DiffBuilder;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -19,7 +20,7 @@ namespace Boyfriend;
|
|||
|
||||
/// <summary>
|
||||
/// Handles sending a <see cref="Messages.Ready" /> message to a guild that has just initialized if that guild
|
||||
/// has <see cref="GuildConfiguration.ReceiveStartupMessages" /> enabled
|
||||
/// has <see cref="GuildSettings.ReceiveStartupMessages" /> enabled
|
||||
/// </summary>
|
||||
public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||
private readonly IDiscordRestChannelAPI _channelApi;
|
||||
|
@ -42,16 +43,16 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
|
|||
var guild = gatewayEvent.Guild.AsT0;
|
||||
_logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
|
||||
|
||||
var guildConfig = await _dataService.GetConfiguration(guild.ID, ct);
|
||||
if (!guildConfig.ReceiveStartupMessages)
|
||||
var cfg = await _dataService.GetSettings(guild.ID, ct);
|
||||
if (!GuildSettings.ReceiveStartupMessages.Get(cfg))
|
||||
return Result.FromSuccess();
|
||||
if (guildConfig.PrivateFeedbackChannel is 0)
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
|
||||
return Result.FromSuccess();
|
||||
|
||||
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
|
||||
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
|
||||
|
||||
Messages.Culture = guildConfig.GetCulture();
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
var i = Random.Shared.Next(1, 4);
|
||||
|
||||
var embed = new EmbedBuilder().WithSmallTitle(currentUser.GetTag(), currentUser)
|
||||
|
@ -63,13 +64,13 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
|
|||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
guildConfig.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built }, ct: ct);
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built }, ct: ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles logging the contents of a deleted message and the user who deleted the message
|
||||
/// to a guild's <see cref="GuildConfiguration.PrivateFeedbackChannel" /> if one is set.
|
||||
/// to a guild's <see cref="GuildSettings.PrivateFeedbackChannel" /> if one is set.
|
||||
/// </summary>
|
||||
public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||
private readonly IDiscordRestAuditLogAPI _auditLogApi;
|
||||
|
@ -89,8 +90,8 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
|||
public async Task<Result> RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) {
|
||||
if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess();
|
||||
|
||||
var guildConfiguration = await _dataService.GetConfiguration(guildId, ct);
|
||||
if (guildConfiguration.PrivateFeedbackChannel is 0) return Result.FromSuccess();
|
||||
var cfg = await _dataService.GetSettings(guildId, ct);
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) return Result.FromSuccess();
|
||||
|
||||
var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct);
|
||||
if (!messageResult.IsDefined(out var message)) return Result.FromError(messageResult);
|
||||
|
@ -111,7 +112,7 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
|||
if (!userResult.IsDefined(out user)) return Result.FromError(userResult);
|
||||
}
|
||||
|
||||
Messages.Culture = guildConfiguration.GetCulture();
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithSmallTitle(
|
||||
|
@ -127,14 +128,14 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
|||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
guildConfiguration.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built },
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built },
|
||||
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles logging the difference between an edited message's old and new content
|
||||
/// to a guild's <see cref="GuildConfiguration.PrivateFeedbackChannel" /> if one is set.
|
||||
/// to a guild's <see cref="GuildSettings.PrivateFeedbackChannel" /> if one is set.
|
||||
/// </summary>
|
||||
public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||
private readonly CacheService _cacheService;
|
||||
|
@ -154,8 +155,8 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
|||
public async Task<Result> RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) {
|
||||
if (!gatewayEvent.GuildID.IsDefined(out var guildId))
|
||||
return Result.FromSuccess();
|
||||
var guildConfiguration = await _dataService.GetConfiguration(guildId, ct);
|
||||
if (guildConfiguration.PrivateFeedbackChannel is 0)
|
||||
var cfg = await _dataService.GetSettings(guildId, ct);
|
||||
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
|
||||
return Result.FromSuccess();
|
||||
if (!gatewayEvent.Content.IsDefined(out var newContent))
|
||||
return Result.FromSuccess();
|
||||
|
@ -189,7 +190,7 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
|||
|
||||
var diff = InlineDiffBuilder.Diff(message.Content, newContent);
|
||||
|
||||
Messages.Culture = guildConfiguration.GetCulture();
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithSmallTitle(string.Format(Messages.CachedMessageEdited, message.Author.GetTag()), message.Author)
|
||||
|
@ -201,16 +202,16 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
|||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
guildConfiguration.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built },
|
||||
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built },
|
||||
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending a guild's <see cref="GuildConfiguration.WelcomeMessage" /> if one is set.
|
||||
/// If <see cref="GuildConfiguration.ReturnRolesOnRejoin"/> is enabled, roles will be returned.
|
||||
/// Handles sending a guild's <see cref="GuildSettings.WelcomeMessage" /> if one is set.
|
||||
/// If <see cref="GuildSettings.ReturnRolesOnRejoin"/> is enabled, roles will be returned.
|
||||
/// </summary>
|
||||
/// <seealso cref="GuildConfiguration.WelcomeMessage" />
|
||||
/// <seealso cref="GuildSettings.WelcomeMessage" />
|
||||
public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
||||
private readonly IDiscordRestChannelAPI _channelApi;
|
||||
private readonly GuildDataService _dataService;
|
||||
|
@ -227,19 +228,21 @@ public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
|||
if (!gatewayEvent.User.IsDefined(out var user))
|
||||
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.User)));
|
||||
var data = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
||||
var cfg = data.Configuration;
|
||||
if (cfg.PublicFeedbackChannel is 0 || cfg.WelcomeMessage is "off" or "disable" or "disabled")
|
||||
var cfg = data.Settings;
|
||||
if (GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||
|| GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled")
|
||||
return Result.FromSuccess();
|
||||
if (cfg.ReturnRolesOnRejoin) {
|
||||
if (GuildSettings.ReturnRolesOnRejoin.Get(cfg)) {
|
||||
var result = await _guildApi.ModifyGuildMemberAsync(
|
||||
gatewayEvent.GuildID, user.ID, roles: data.GetMemberData(user.ID).Roles, ct: ct);
|
||||
gatewayEvent.GuildID, user.ID,
|
||||
roles: data.GetMemberData(user.ID).Roles.ConvertAll(r => r.ToSnowflake()), ct: ct);
|
||||
if (!result.IsSuccess) return Result.FromError(result.Error);
|
||||
}
|
||||
|
||||
Messages.Culture = data.Culture;
|
||||
var welcomeMessage = cfg.WelcomeMessage is "default" or "reset"
|
||||
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||
var welcomeMessage = GuildSettings.WelcomeMessage.Get(cfg) is "default" or "reset"
|
||||
? Messages.DefaultWelcomeMessage
|
||||
: cfg.WelcomeMessage;
|
||||
: GuildSettings.WelcomeMessage.Get(cfg);
|
||||
|
||||
var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct);
|
||||
if (!guildResult.IsDefined(out var guild)) return Result.FromError(guildResult);
|
||||
|
@ -253,14 +256,14 @@ public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
|||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built },
|
||||
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: new[] { built },
|
||||
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending a notification when a scheduled event has been cancelled
|
||||
/// in a guild's <see cref="GuildConfiguration.EventNotificationChannel" /> if one is set.
|
||||
/// in a guild's <see cref="GuildSettings.EventNotificationChannel" /> if one is set.
|
||||
/// </summary>
|
||||
public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEventDelete> {
|
||||
private readonly IDiscordRestChannelAPI _channelApi;
|
||||
|
@ -275,7 +278,7 @@ public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEven
|
|||
var guildData = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
||||
guildData.ScheduledEvents.Remove(gatewayEvent.ID.Value);
|
||||
|
||||
if (guildData.Configuration.EventNotificationChannel is 0)
|
||||
if (GuildSettings.EventNotificationChannel.Get(guildData.Settings).Empty())
|
||||
return Result.FromSuccess();
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
|
@ -288,7 +291,7 @@ public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEven
|
|||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
guildData.Configuration.EventNotificationChannel.ToDiscordSnowflake(), embeds: new[] { built }, ct: ct);
|
||||
GuildSettings.EventNotificationChannel.Get(guildData.Settings), embeds: new[] { built }, ct: ct);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,7 +307,7 @@ public class GuildMemberUpdateResponder : IResponder<IGuildMemberUpdate> {
|
|||
|
||||
public async Task<Result> RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) {
|
||||
var memberData = await _dataService.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct);
|
||||
memberData.Roles = gatewayEvent.Roles.ToList();
|
||||
memberData.Roles = gatewayEvent.Roles.ToList().ConvertAll(r => r.Value);
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Boyfriend.locale;
|
||||
using DiffPlex.DiffBuilder.Model;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
@ -170,10 +171,11 @@ public static class Extensions {
|
|||
return user.Discriminator is 0000 ? $"@{user.Username}" : $"{user.Username}#{user.Discriminator:0000}";
|
||||
}
|
||||
|
||||
public static Snowflake ToDiscordSnowflake(this ulong id) {
|
||||
public static Snowflake ToSnowflake(this ulong id) {
|
||||
return DiscordSnowflake.New(id);
|
||||
}
|
||||
|
||||
|
||||
public static TResult? MaxOrDefault<TSource, TResult>(
|
||||
this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
|
||||
var list = source.ToList();
|
||||
|
@ -190,4 +192,8 @@ public static class Extensions {
|
|||
&& context.TryGetChannelID(out channelId)
|
||||
&& context.TryGetUserID(out userId);
|
||||
}
|
||||
|
||||
public static bool Empty(this Snowflake snowflake) {
|
||||
return snowflake.Value is 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.Data;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Remora.Discord.API.Abstractions.Rest;
|
||||
|
@ -36,8 +37,8 @@ public class GuildDataService : IHostedService {
|
|||
private async Task SaveAsync(CancellationToken ct) {
|
||||
var tasks = new List<Task>();
|
||||
foreach (var data in _datas.Values) {
|
||||
await using var configStream = File.OpenWrite(data.ConfigurationPath);
|
||||
tasks.Add(JsonSerializer.SerializeAsync(configStream, data.Configuration, cancellationToken: ct));
|
||||
await using var settingsStream = File.OpenWrite(data.SettingsPath);
|
||||
tasks.Add(JsonSerializer.SerializeAsync(settingsStream, data.Settings, cancellationToken: ct));
|
||||
|
||||
await using var eventsStream = File.OpenWrite(data.ScheduledEventsPath);
|
||||
tasks.Add(JsonSerializer.SerializeAsync(eventsStream, data.ScheduledEvents, cancellationToken: ct));
|
||||
|
@ -58,17 +59,16 @@ public class GuildDataService : IHostedService {
|
|||
private async Task<GuildData> InitializeData(Snowflake guildId, CancellationToken ct = default) {
|
||||
var idString = $"{guildId}";
|
||||
var memberDataPath = $"{guildId}/MemberData";
|
||||
var configurationPath = $"{guildId}/Configuration.json";
|
||||
var settingsPath = $"{guildId}/Settings.json";
|
||||
var scheduledEventsPath = $"{guildId}/ScheduledEvents.json";
|
||||
if (!Directory.Exists(idString)) Directory.CreateDirectory(idString);
|
||||
if (!Directory.Exists(memberDataPath)) Directory.CreateDirectory(memberDataPath);
|
||||
if (!File.Exists(configurationPath)) await File.WriteAllTextAsync(configurationPath, "{}", ct);
|
||||
if (!File.Exists(settingsPath)) await File.WriteAllTextAsync(settingsPath, "{}", ct);
|
||||
if (!File.Exists(scheduledEventsPath)) await File.WriteAllTextAsync(scheduledEventsPath, "{}", ct);
|
||||
|
||||
await using var configurationStream = File.OpenRead(configurationPath);
|
||||
var configuration
|
||||
= JsonSerializer.DeserializeAsync<GuildConfiguration>(
|
||||
configurationStream, cancellationToken: ct);
|
||||
await using var settingsStream = File.OpenRead(settingsPath);
|
||||
var jsonSettings
|
||||
= JsonNode.Parse(settingsStream);
|
||||
|
||||
await using var eventsStream = File.OpenRead(scheduledEventsPath);
|
||||
var events
|
||||
|
@ -80,23 +80,23 @@ public class GuildDataService : IHostedService {
|
|||
await using var dataStream = File.OpenRead(dataPath);
|
||||
var data = await JsonSerializer.DeserializeAsync<MemberData>(dataStream, cancellationToken: ct);
|
||||
if (data is null) continue;
|
||||
var memberResult = await _guildApi.GetGuildMemberAsync(guildId, data.Id.ToDiscordSnowflake(), ct);
|
||||
var memberResult = await _guildApi.GetGuildMemberAsync(guildId, data.Id.ToSnowflake(), ct);
|
||||
if (memberResult.IsSuccess)
|
||||
data.Roles = memberResult.Entity.Roles.ToList();
|
||||
data.Roles = memberResult.Entity.Roles.ToList().ConvertAll(r => r.Value);
|
||||
|
||||
memberData.Add(data.Id, data);
|
||||
}
|
||||
|
||||
var finalData = new GuildData(
|
||||
await configuration ?? new GuildConfiguration(), configurationPath,
|
||||
jsonSettings ?? new JsonObject(), settingsPath,
|
||||
await events ?? new Dictionary<ulong, ScheduledEventData>(), scheduledEventsPath,
|
||||
memberData, memberDataPath);
|
||||
while (!_datas.ContainsKey(guildId)) _datas.TryAdd(guildId, finalData);
|
||||
return finalData;
|
||||
}
|
||||
|
||||
public async Task<GuildConfiguration> GetConfiguration(Snowflake guildId, CancellationToken ct = default) {
|
||||
return (await GetData(guildId, ct)).Configuration;
|
||||
public async Task<JsonNode> GetSettings(Snowflake guildId, CancellationToken ct = default) {
|
||||
return (await GetData(guildId, ct)).Settings;
|
||||
}
|
||||
|
||||
public async Task<MemberData> GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.Data;
|
||||
using Boyfriend.locale;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
@ -94,9 +96,9 @@ public class GuildUpdateService : BackgroundService {
|
|||
/// This method does the following:
|
||||
/// <list type="bullet">
|
||||
/// <item>Automatically unbans users once their ban period has expired.</item>
|
||||
/// <item>Automatically grants members the guild's <see cref="GuildConfiguration.DefaultRole"/> if one is set.</item>
|
||||
/// <item>Automatically grants members the guild's <see cref="GuildSettings.DefaultRole"/> if one is set.</item>
|
||||
/// <item>Sends reminders about an upcoming scheduled event.</item>
|
||||
/// <item>Automatically starts scheduled events if <see cref="GuildConfiguration.AutoStartEvents"/> is enabled.</item>
|
||||
/// <item>Automatically starts scheduled events if <see cref="GuildSettings.AutoStartEvents"/> is enabled.</item>
|
||||
/// <item>Sends scheduled event start notifications.</item>
|
||||
/// <item>Sends scheduled event completion notifications.</item>
|
||||
/// <item>Sends reminders to members.</item>
|
||||
|
@ -114,15 +116,15 @@ public class GuildUpdateService : BackgroundService {
|
|||
/// <param name="ct">The cancellation token for this operation.</param>
|
||||
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();
|
||||
Messages.Culture = GuildSettings.Language.Get(data.Settings);
|
||||
var defaultRole = GuildSettings.DefaultRole.Get(data.Settings);
|
||||
|
||||
foreach (var memberData in data.MemberData.Values) {
|
||||
var userId = memberData.Id.ToDiscordSnowflake();
|
||||
var userId = memberData.Id.ToSnowflake();
|
||||
|
||||
if (defaultRoleSnowflake.Value is not 0 && !memberData.Roles.Contains(defaultRoleSnowflake))
|
||||
if (defaultRole.Value is not 0 && !memberData.Roles.Contains(defaultRole.Value))
|
||||
_ = _guildApi.AddGuildMemberRoleAsync(
|
||||
guildId, userId, defaultRoleSnowflake, ct: ct);
|
||||
guildId, userId, defaultRole, ct: ct);
|
||||
|
||||
if (DateTimeOffset.UtcNow > memberData.BannedUntil) {
|
||||
var unbanResult = await _guildApi.RemoveGuildBanAsync(
|
||||
|
@ -139,7 +141,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
|
||||
for (var i = memberData.Reminders.Count - 1; i >= 0; i--) {
|
||||
var reminder = memberData.Reminders[i];
|
||||
if (DateTimeOffset.UtcNow < reminder.RemindAt) continue;
|
||||
if (DateTimeOffset.UtcNow < reminder.At) continue;
|
||||
|
||||
var embed = new EmbedBuilder().WithSmallTitle(
|
||||
string.Format(Messages.Reminder, user.GetTag()), user)
|
||||
|
@ -151,7 +153,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
if (!embed.IsDefined(out var built)) continue;
|
||||
|
||||
var messageResult = await _channelApi.CreateMessageAsync(
|
||||
reminder.Channel, Mention.User(user), embeds: new[] { built }, ct: ct);
|
||||
reminder.Channel.ToSnowflake(), Mention.User(user), embeds: new[] { built }, ct: ct);
|
||||
if (!messageResult.IsSuccess)
|
||||
_logger.LogWarning(
|
||||
"Error in reminder send.\n{ErrorMessage}", messageResult.Error.Message);
|
||||
|
@ -163,7 +165,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct);
|
||||
if (!eventsResult.IsDefined(out var events)) return;
|
||||
|
||||
if (data.Configuration.EventNotificationChannel is 0) return;
|
||||
if (GuildSettings.EventNotificationChannel.Get(data.Settings).Empty()) return;
|
||||
|
||||
foreach (var scheduledEvent in events) {
|
||||
if (!data.ScheduledEvents.ContainsKey(scheduledEvent.ID.Value)) {
|
||||
|
@ -172,7 +174,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value];
|
||||
if (storedEvent.Status == scheduledEvent.Status) {
|
||||
if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) {
|
||||
if (data.Configuration.AutoStartEvents
|
||||
if (GuildSettings.AutoStartEvents.Get(data.Settings)
|
||||
&& scheduledEvent.Status is not GuildScheduledEventStatus.Active) {
|
||||
var startResult = await _eventApi.ModifyGuildScheduledEventAsync(
|
||||
guildId, scheduledEvent.ID,
|
||||
|
@ -182,10 +184,11 @@ public class GuildUpdateService : BackgroundService {
|
|||
"Error in automatic scheduled event start request.\n{ErrorMessage}",
|
||||
startResult.Error.Message);
|
||||
}
|
||||
} else if (data.Configuration.EventEarlyNotificationOffset != TimeSpan.Zero
|
||||
} else if (GuildSettings.EventEarlyNotificationOffset.Get(data.Settings) != TimeSpan.Zero
|
||||
&& !storedEvent.EarlyNotificationSent
|
||||
&& DateTimeOffset.UtcNow
|
||||
>= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset) {
|
||||
>= scheduledEvent.ScheduledStartTime
|
||||
- GuildSettings.EventEarlyNotificationOffset.Get(data.Settings)) {
|
||||
var earlyResult = await SendScheduledEventUpdatedMessage(scheduledEvent, data, true, ct);
|
||||
if (earlyResult.IsSuccess)
|
||||
storedEvent.EarlyNotificationSent = true;
|
||||
|
@ -203,7 +206,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
|
||||
var result = scheduledEvent.Status switch {
|
||||
GuildScheduledEventStatus.Scheduled =>
|
||||
await SendScheduledEventCreatedMessage(scheduledEvent, data.Configuration, ct),
|
||||
await SendScheduledEventCreatedMessage(scheduledEvent, data.Settings, ct),
|
||||
GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed =>
|
||||
await SendScheduledEventUpdatedMessage(scheduledEvent, data, false, ct),
|
||||
_ => Result.FromError(new ArgumentOutOfRangeError(nameof(scheduledEvent.Status)))
|
||||
|
@ -215,17 +218,17 @@ public class GuildUpdateService : BackgroundService {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending a notification, mentioning the <see cref="GuildConfiguration.EventNotificationRole" /> if one is
|
||||
/// Handles sending a notification, mentioning the <see cref="GuildSettings.EventNotificationRole" /> if one is
|
||||
/// set,
|
||||
/// when a scheduled event is created
|
||||
/// in a guild's <see cref="GuildConfiguration.EventNotificationChannel" /> if one is set.
|
||||
/// in a guild's <see cref="GuildSettings.EventNotificationChannel" /> if one is set.
|
||||
/// </summary>
|
||||
/// <param name="scheduledEvent">The scheduled event that has just been created.</param>
|
||||
/// <param name="config">The configuration of the guild containing the scheduled event.</param>
|
||||
/// <param name="settings">The settings of the guild containing the scheduled event.</param>
|
||||
/// <param name="ct">The cancellation token for this operation.</param>
|
||||
/// <returns>A notification sending result which may or may not have succeeded.</returns>
|
||||
private async Task<Result> SendScheduledEventCreatedMessage(
|
||||
IGuildScheduledEvent scheduledEvent, GuildConfiguration config, CancellationToken ct = default) {
|
||||
IGuildScheduledEvent scheduledEvent, JsonNode settings, CancellationToken ct = default) {
|
||||
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
|
||||
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
|
||||
|
||||
|
@ -281,8 +284,8 @@ public class GuildUpdateService : BackgroundService {
|
|||
.Build();
|
||||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
var roleMention = config.EventNotificationRole is not 0
|
||||
? Mention.Role(config.EventNotificationRole.ToDiscordSnowflake())
|
||||
var roleMention = !GuildSettings.EventNotificationRole.Get(settings).Empty()
|
||||
? Mention.Role(GuildSettings.EventNotificationRole.Get(settings))
|
||||
: string.Empty;
|
||||
|
||||
var button = new ButtonComponent(
|
||||
|
@ -294,14 +297,14 @@ public class GuildUpdateService : BackgroundService {
|
|||
);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
config.EventNotificationChannel.ToDiscordSnowflake(), roleMention, embeds: new[] { built },
|
||||
GuildSettings.EventNotificationChannel.Get(settings), roleMention, embeds: new[] { built },
|
||||
components: new[] { new ActionRowComponent(new[] { button }) }, ct: ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles sending a notification, mentioning the <see cref="GuildConfiguration.EventStartedReceivers" />s,
|
||||
/// Handles sending a notification, mentioning the <see cref="GuildSettings.EventStartedReceivers" />s,
|
||||
/// when a scheduled event is about to start, has started or completed
|
||||
/// in a guild's <see cref="GuildConfiguration.EventNotificationChannel" /> if one is set.
|
||||
/// in a guild's <see cref="GuildSettings.EventNotificationChannel" /> if one is set.
|
||||
/// </summary>
|
||||
/// <param name="scheduledEvent">The scheduled event that is about to start, has started or completed.</param>
|
||||
/// <param name="data">The data for the guild containing the scheduled event.</param>
|
||||
|
@ -353,7 +356,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
}
|
||||
|
||||
var contentResult = await _utility.GetEventNotificationMentions(
|
||||
scheduledEvent, data.Configuration, ct);
|
||||
scheduledEvent, data.Settings, ct);
|
||||
if (!contentResult.IsDefined(out content))
|
||||
return Result.FromError(contentResult);
|
||||
|
||||
|
@ -383,7 +386,7 @@ public class GuildUpdateService : BackgroundService {
|
|||
if (!result.IsDefined(out var built)) return Result.FromError(result);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
data.Configuration.EventNotificationChannel.ToDiscordSnowflake(),
|
||||
GuildSettings.EventNotificationChannel.Get(data.Settings),
|
||||
content ?? default(Optional<string>), embeds: new[] { built }, ct: ct);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using Boyfriend.Data;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
@ -103,38 +104,37 @@ public class UtilityService : IHostedService {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string mentioning all <see cref="GuildConfiguration.NotificationReceiver" />s related to a scheduled
|
||||
/// Gets the string mentioning all <see cref="GuildSettings.NotificationReceiver" />s related to a scheduled
|
||||
/// event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the guild configuration enables <see cref="GuildConfiguration.NotificationReceiver.Role" />, then the
|
||||
/// <see cref="GuildConfiguration.EventNotificationRole" /> will also be mentioned.
|
||||
/// If the guild settings enables <see cref="GuildSettings.NotificationReceiver.Role" />, then the
|
||||
/// <see cref="GuildSettings.EventNotificationRole" /> will also be mentioned.
|
||||
/// </remarks>
|
||||
/// <param name="scheduledEvent">
|
||||
/// The scheduled event whose subscribers will be mentioned if the guild configuration enables
|
||||
/// <see cref="GuildConfiguration.NotificationReceiver.Interested" />.
|
||||
/// The scheduled event whose subscribers will be mentioned if the guild settings enables
|
||||
/// <see cref="GuildSettings.NotificationReceiver.Interested" />.
|
||||
/// </param>
|
||||
/// <param name="config">The configuration of the guild containing the scheduled event</param>
|
||||
/// <param name="settings">The settings of the guild containing the scheduled event</param>
|
||||
/// <param name="ct">The cancellation token for this operation.</param>
|
||||
/// <returns>A result containing the string which may or may not have succeeded.</returns>
|
||||
public async Task<Result<string>> GetEventNotificationMentions(
|
||||
IGuildScheduledEvent scheduledEvent, GuildConfiguration config, CancellationToken ct = default) {
|
||||
IGuildScheduledEvent scheduledEvent, JsonNode settings, CancellationToken ct = default) {
|
||||
var builder = new StringBuilder();
|
||||
var receivers = config.EventStartedReceivers;
|
||||
var role = config.EventNotificationRole.ToDiscordSnowflake();
|
||||
var role = GuildSettings.EventNotificationRole.Get(settings);
|
||||
var usersResult = await _eventApi.GetGuildScheduledEventUsersAsync(
|
||||
scheduledEvent.GuildID, scheduledEvent.ID, withMember: true, ct: ct);
|
||||
if (!usersResult.IsDefined(out var users)) return Result<string>.FromError(usersResult);
|
||||
|
||||
if (receivers.Contains(GuildConfiguration.NotificationReceiver.Role) && role.Value is not 0)
|
||||
if (role.Value is not 0)
|
||||
builder.Append($"{Mention.Role(role)} ");
|
||||
if (receivers.Contains(GuildConfiguration.NotificationReceiver.Interested))
|
||||
builder = users.Where(
|
||||
user => {
|
||||
if (!user.GuildMember.IsDefined(out var member)) return true;
|
||||
return !member.Roles.Contains(role);
|
||||
})
|
||||
.Aggregate(builder, (current, user) => current.Append($"{Mention.User(user.User)} "));
|
||||
|
||||
builder = users.Where(
|
||||
user => {
|
||||
if (!user.GuildMember.IsDefined(out var member)) return true;
|
||||
return !member.Roles.Contains(role);
|
||||
})
|
||||
.Aggregate(builder, (current, user) => current.Append($"{Mention.User(user.User)} "));
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue