mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-04-20 00:43: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>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace Boyfriend {
|
namespace Boyfriend.locale {
|
||||||
using System;
|
|
||||||
|
|
||||||
|
|
||||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
|
|
@ -56,11 +56,11 @@ public class Boyfriend {
|
||||||
| GatewayIntents.GuildMembers
|
| GatewayIntents.GuildMembers
|
||||||
| GatewayIntents.GuildScheduledEvents);
|
| GatewayIntents.GuildScheduledEvents);
|
||||||
services.Configure<CacheSettings>(
|
services.Configure<CacheSettings>(
|
||||||
settings => {
|
cSettings => {
|
||||||
settings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
|
cSettings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
|
||||||
settings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
|
cSettings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
|
||||||
settings.SetAbsoluteExpiration<IMessage>(TimeSpan.FromDays(7));
|
cSettings.SetAbsoluteExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||||
settings.SetSlidingExpiration<IMessage>(TimeSpan.FromDays(7));
|
cSettings.SetSlidingExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddTransient<IConfigurationBuilder, ConfigurationBuilder>()
|
services.AddTransient<IConfigurationBuilder, ConfigurationBuilder>()
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -51,8 +53,8 @@ public class AboutCommandGroup : CommandGroup {
|
||||||
if (!currentUserResult.IsDefined(out var currentUser))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var builder = new StringBuilder().AppendLine(Markdown.Bold(Messages.AboutTitleDevelopers));
|
var builder = new StringBuilder().AppendLine(Markdown.Bold(Messages.AboutTitleDevelopers));
|
||||||
foreach (var dev in Developers)
|
foreach (var dev in Developers)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -76,8 +78,8 @@ public class BanCommandGroup : CommandGroup {
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
||||||
var cfg = data.Configuration;
|
var cfg = data.Settings;
|
||||||
Messages.Culture = data.Culture;
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
|
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
|
||||||
if (existingBanResult.IsDefined()) {
|
if (existingBanResult.IsDefined()) {
|
||||||
|
@ -145,8 +147,10 @@ public class BanCommandGroup : CommandGroup {
|
||||||
string.Format(Messages.UserBanned, target.GetTag()), target)
|
string.Format(Messages.UserBanned, target.GetTag()), target)
|
||||||
.WithColour(ColorsList.Green).Build();
|
.WithColour(ColorsList.Green).Build();
|
||||||
|
|
||||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
|
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||||
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||||
string.Format(Messages.UserBanned, target.GetTag()), target)
|
string.Format(Messages.UserBanned, target.GetTag()), target)
|
||||||
.WithDescription(description)
|
.WithDescription(description)
|
||||||
|
@ -160,14 +164,14 @@ public class BanCommandGroup : CommandGroup {
|
||||||
|
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
// Not awaiting to reduce response time
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,8 +213,8 @@ public class BanCommandGroup : CommandGroup {
|
||||||
if (!currentUserResult.IsDefined(out var currentUser))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
|
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
|
||||||
if (!existingBanResult.IsDefined()) {
|
if (!existingBanResult.IsDefined()) {
|
||||||
|
@ -238,8 +242,10 @@ public class BanCommandGroup : CommandGroup {
|
||||||
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
||||||
.WithColour(ColorsList.Green).Build();
|
.WithColour(ColorsList.Green).Build();
|
||||||
|
|
||||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
|
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||||
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||||
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
string.Format(Messages.UserUnbanned, target.GetTag()), target)
|
||||||
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
||||||
|
@ -254,14 +260,14 @@ public class BanCommandGroup : CommandGroup {
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
|
|
||||||
// Not awaiting to reduce response time
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -64,8 +66,8 @@ public class ClearCommandGroup : CommandGroup {
|
||||||
if (!messagesResult.IsDefined(out var messages))
|
if (!messagesResult.IsDefined(out var messages))
|
||||||
return Result.FromError(messagesResult);
|
return Result.FromError(messagesResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var idList = new List<Snowflake>(messages.Count);
|
var idList = new List<Snowflake>(messages.Count);
|
||||||
var builder = new StringBuilder().AppendLine(Mention.Channel(channelId.Value)).AppendLine();
|
var builder = new StringBuilder().AppendLine(Mention.Channel(channelId.Value)).AppendLine();
|
||||||
|
@ -93,7 +95,8 @@ public class ClearCommandGroup : CommandGroup {
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var title = string.Format(Messages.MessagesCleared, amount.ToString());
|
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)
|
var logEmbed = new EmbedBuilder().WithSmallTitle(title, currentUser)
|
||||||
.WithDescription(description)
|
.WithDescription(description)
|
||||||
.WithActionFooter(user)
|
.WithActionFooter(user)
|
||||||
|
@ -105,9 +108,9 @@ public class ClearCommandGroup : CommandGroup {
|
||||||
return Result.FromError(logEmbed);
|
return Result.FromError(logEmbed);
|
||||||
|
|
||||||
// Not awaiting to reduce response time
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PrivateFeedbackChannel != channelId.Value)
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { logBuilt },
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { logBuilt },
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -71,8 +73,8 @@ public class KickCommandGroup : CommandGroup {
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
||||||
var cfg = data.Configuration;
|
var cfg = data.Settings;
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
|
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
|
||||||
if (!memberResult.IsSuccess) {
|
if (!memberResult.IsSuccess) {
|
||||||
|
@ -129,8 +131,10 @@ public class KickCommandGroup : CommandGroup {
|
||||||
string.Format(Messages.UserKicked, target.GetTag()), target)
|
string.Format(Messages.UserKicked, target.GetTag()), target)
|
||||||
.WithColour(ColorsList.Green).Build();
|
.WithColour(ColorsList.Green).Build();
|
||||||
|
|
||||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
|
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||||
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||||
string.Format(Messages.UserKicked, target.GetTag()), target)
|
string.Format(Messages.UserKicked, target.GetTag()), target)
|
||||||
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
||||||
|
@ -144,14 +148,14 @@ public class KickCommandGroup : CommandGroup {
|
||||||
|
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
// Not awaiting to reduce response time
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -93,8 +95,8 @@ public class MuteCommandGroup : CommandGroup {
|
||||||
return Result.FromError(interactionResult);
|
return Result.FromError(interactionResult);
|
||||||
|
|
||||||
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
var data = await _dataService.GetData(guildId.Value, CancellationToken);
|
||||||
var cfg = data.Configuration;
|
var cfg = data.Settings;
|
||||||
Messages.Culture = data.Culture;
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
Result<Embed> responseEmbed;
|
Result<Embed> responseEmbed;
|
||||||
if (interactionResult.Entity is not null) {
|
if (interactionResult.Entity is not null) {
|
||||||
|
@ -116,8 +118,10 @@ public class MuteCommandGroup : CommandGroup {
|
||||||
string.Format(Messages.UserMuted, target.GetTag()), target)
|
string.Format(Messages.UserMuted, target.GetTag()), target)
|
||||||
.WithColour(ColorsList.Green).Build();
|
.WithColour(ColorsList.Green).Build();
|
||||||
|
|
||||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
&& 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))
|
var builder = new StringBuilder().AppendLine(string.Format(Messages.DescriptionActionReason, reason))
|
||||||
.Append(
|
.Append(
|
||||||
string.Format(
|
string.Format(
|
||||||
|
@ -136,14 +140,14 @@ public class MuteCommandGroup : CommandGroup {
|
||||||
|
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
// Not awaiting to reduce response time
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,8 +189,8 @@ public class MuteCommandGroup : CommandGroup {
|
||||||
if (!currentUserResult.IsDefined(out var currentUser))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
|
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
|
||||||
if (!memberResult.IsSuccess) {
|
if (!memberResult.IsSuccess) {
|
||||||
|
@ -220,8 +224,10 @@ public class MuteCommandGroup : CommandGroup {
|
||||||
string.Format(Messages.UserUnmuted, target.GetTag()), target)
|
string.Format(Messages.UserUnmuted, target.GetTag()), target)
|
||||||
.WithColour(ColorsList.Green).Build();
|
.WithColour(ColorsList.Green).Build();
|
||||||
|
|
||||||
if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
|
if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||||
|| (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
|
&& GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
|
|| (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
|
||||||
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
|
||||||
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
var logEmbed = new EmbedBuilder().WithSmallTitle(
|
||||||
string.Format(Messages.UserUnmuted, target.GetTag()), target)
|
string.Format(Messages.UserUnmuted, target.GetTag()), target)
|
||||||
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
|
||||||
|
@ -236,14 +242,14 @@ public class MuteCommandGroup : CommandGroup {
|
||||||
var builtArray = new[] { logBuilt };
|
var builtArray = new[] { logBuilt };
|
||||||
|
|
||||||
// Not awaiting to reduce response time
|
// Not awaiting to reduce response time
|
||||||
if (cfg.PublicFeedbackChannel != channelId.Value)
|
if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
|
||||||
&& cfg.PrivateFeedbackChannel != channelId.Value)
|
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
|
||||||
_ = _channelApi.CreateMessageAsync(
|
_ = _channelApi.CreateMessageAsync(
|
||||||
cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
|
||||||
ct: CancellationToken);
|
ct: CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -53,8 +55,8 @@ public class PingCommandGroup : CommandGroup {
|
||||||
if (!currentUserResult.IsDefined(out var currentUser))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var latency = _client.Latency.TotalMilliseconds;
|
var latency = _client.Latency.TotalMilliseconds;
|
||||||
if (latency is 0) {
|
if (latency is 0) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Boyfriend.Data;
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
@ -57,8 +58,8 @@ public class RemindCommandGroup : CommandGroup {
|
||||||
|
|
||||||
(await _dataService.GetMemberData(guildId.Value, userId.Value, CancellationToken)).Reminders.Add(
|
(await _dataService.GetMemberData(guildId.Value, userId.Value, CancellationToken)).Reminders.Add(
|
||||||
new Reminder {
|
new Reminder {
|
||||||
RemindAt = remindAt,
|
At = remindAt,
|
||||||
Channel = channelId.Value,
|
Channel = channelId.Value.Value,
|
||||||
Text = message
|
Text = message
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Boyfriend.Data;
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.Data.Options;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
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.
|
/// Handles the commands to list and modify per-guild settings: /settings and /settings list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SettingsCommandGroup : CommandGroup {
|
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 ICommandContext _context;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
private readonly FeedbackService _feedbackService;
|
private readonly FeedbackService _feedbackService;
|
||||||
|
@ -36,7 +53,7 @@ public class SettingsCommandGroup : CommandGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A slash command that lists current per-guild settings.
|
/// A slash command that lists current per-guild GuildSettings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A feedback sending result which may or may not have succeeded.
|
/// 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))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
foreach (var setting in typeof(GuildConfiguration).GetProperties()) {
|
foreach (var option in AllOptions) {
|
||||||
builder.Append(Markdown.InlineCode(setting.Name))
|
builder.Append(Markdown.InlineCode(option.Name))
|
||||||
.Append(": ");
|
.Append(": ");
|
||||||
var something = setting.GetValue(cfg);
|
var something = option.GetAsObject(cfg);
|
||||||
if (something!.GetType() == typeof(List<GuildConfiguration.NotificationReceiver>)) {
|
builder.AppendLine(Markdown.InlineCode(something.ToString()!));
|
||||||
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 embed = new EmbedBuilder().WithSmallTitle(Messages.SettingsListTitle, currentUser)
|
var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingsListTitle, currentUser)
|
||||||
|
@ -77,7 +91,7 @@ public class SettingsCommandGroup : CommandGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A slash command that modifies per-guild settings.
|
/// A slash command that modifies per-guild GuildSettings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="setting">The setting to modify.</param>
|
/// <param name="setting">The setting to modify.</param>
|
||||||
/// <param name="value">The new value of the setting.</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))
|
if (!currentUserResult.IsDefined(out var currentUser))
|
||||||
return Result.FromError(currentUserResult);
|
return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
|
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
|
||||||
Messages.Culture = cfg.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
PropertyInfo? property = null;
|
var option = AllOptions.Single(
|
||||||
|
o => string.Equals(setting, o.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
try {
|
var setResult = option.Set(cfg, value);
|
||||||
foreach (var prop in typeof(GuildConfiguration).GetProperties())
|
if (!setResult.IsSuccess) {
|
||||||
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 failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, currentUser)
|
var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.SettingNotChanged, currentUser)
|
||||||
.WithDescription(e.Message)
|
.WithDescription(setResult.Error.Message)
|
||||||
.WithColour(ColorsList.Red)
|
.WithColour(ColorsList.Red)
|
||||||
.Build();
|
.Build();
|
||||||
if (!failedEmbed.IsDefined(out var failedBuilt)) return Result.FromError(failedEmbed);
|
if (!failedEmbed.IsDefined(out var failedBuilt)) return Result.FromError(failedEmbed);
|
||||||
|
@ -139,9 +129,9 @@ public class SettingsCommandGroup : CommandGroup {
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
builder.Append(Markdown.InlineCode(setting))
|
builder.Append(Markdown.InlineCode(option.Name))
|
||||||
.Append($" {Messages.SettingIsNow} ")
|
.Append($" {Messages.SettingIsNow} ")
|
||||||
.Append(Markdown.InlineCode(value));
|
.Append(Markdown.InlineCode(option.GetAsObject(cfg).ToString()!));
|
||||||
|
|
||||||
var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingSuccessfullyChanged, currentUser)
|
var embed = new EmbedBuilder().WithSmallTitle(Messages.SettingSuccessfullyChanged, currentUser)
|
||||||
.WithDescription(builder.ToString())
|
.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;
|
using Remora.Rest.Core;
|
||||||
|
|
||||||
namespace Boyfriend.Data;
|
namespace Boyfriend.Data;
|
||||||
|
@ -8,29 +8,26 @@ namespace Boyfriend.Data;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <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 class GuildData {
|
public class GuildData {
|
||||||
public readonly GuildConfiguration Configuration;
|
|
||||||
public readonly string ConfigurationPath;
|
|
||||||
|
|
||||||
public readonly Dictionary<ulong, MemberData> MemberData;
|
public readonly Dictionary<ulong, MemberData> MemberData;
|
||||||
public readonly string MemberDataPath;
|
public readonly string MemberDataPath;
|
||||||
|
|
||||||
public readonly Dictionary<ulong, ScheduledEventData> ScheduledEvents;
|
public readonly Dictionary<ulong, ScheduledEventData> ScheduledEvents;
|
||||||
public readonly string ScheduledEventsPath;
|
public readonly string ScheduledEventsPath;
|
||||||
|
public readonly JsonNode Settings;
|
||||||
|
public readonly string SettingsPath;
|
||||||
|
|
||||||
public GuildData(
|
public GuildData(
|
||||||
GuildConfiguration configuration, string configurationPath,
|
JsonNode settings, string settingsPath,
|
||||||
Dictionary<ulong, ScheduledEventData> scheduledEvents, string scheduledEventsPath,
|
Dictionary<ulong, ScheduledEventData> scheduledEvents, string scheduledEventsPath,
|
||||||
Dictionary<ulong, MemberData> memberData, string memberDataPath) {
|
Dictionary<ulong, MemberData> memberData, string memberDataPath) {
|
||||||
Configuration = configuration;
|
Settings = settings;
|
||||||
ConfigurationPath = configurationPath;
|
SettingsPath = settingsPath;
|
||||||
ScheduledEvents = scheduledEvents;
|
ScheduledEvents = scheduledEvents;
|
||||||
ScheduledEventsPath = scheduledEventsPath;
|
ScheduledEventsPath = scheduledEventsPath;
|
||||||
MemberData = memberData;
|
MemberData = memberData;
|
||||||
MemberDataPath = memberDataPath;
|
MemberDataPath = memberDataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CultureInfo Culture => Configuration.GetCulture();
|
|
||||||
|
|
||||||
public MemberData GetMemberData(Snowflake userId) {
|
public MemberData GetMemberData(Snowflake userId) {
|
||||||
if (MemberData.TryGetValue(userId.Value, out var existing)) return existing;
|
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;
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -13,6 +11,6 @@ public class MemberData {
|
||||||
|
|
||||||
public ulong Id { get; }
|
public ulong Id { get; }
|
||||||
public DateTimeOffset? BannedUntil { get; set; }
|
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();
|
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;
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
public struct Reminder {
|
public struct Reminder {
|
||||||
public DateTimeOffset RemindAt;
|
public DateTimeOffset At;
|
||||||
public string Text;
|
public string Text;
|
||||||
public Snowflake Channel;
|
public ulong Channel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Boyfriend.Data;
|
using Boyfriend.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Boyfriend.Services;
|
using Boyfriend.Services;
|
||||||
using DiffPlex.DiffBuilder;
|
using DiffPlex.DiffBuilder;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -19,7 +20,7 @@ namespace Boyfriend;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles sending a <see cref="Messages.Ready" /> message to a guild that has just initialized if that guild
|
/// 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>
|
/// </summary>
|
||||||
public class GuildCreateResponder : IResponder<IGuildCreate> {
|
public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
|
@ -42,16 +43,16 @@ public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||||
var guild = gatewayEvent.Guild.AsT0;
|
var guild = gatewayEvent.Guild.AsT0;
|
||||||
_logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
|
_logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
|
||||||
|
|
||||||
var guildConfig = await _dataService.GetConfiguration(guild.ID, ct);
|
var cfg = await _dataService.GetSettings(guild.ID, ct);
|
||||||
if (!guildConfig.ReceiveStartupMessages)
|
if (!GuildSettings.ReceiveStartupMessages.Get(cfg))
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
if (guildConfig.PrivateFeedbackChannel is 0)
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
|
|
||||||
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
|
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
|
||||||
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
|
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 i = Random.Shared.Next(1, 4);
|
||||||
|
|
||||||
var embed = new EmbedBuilder().WithSmallTitle(currentUser.GetTag(), currentUser)
|
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);
|
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
return (Result)await _channelApi.CreateMessageAsync(
|
||||||
guildConfig.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built }, ct: ct);
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built }, ct: ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles logging the contents of a deleted message and the user who deleted the message
|
/// 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>
|
/// </summary>
|
||||||
public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||||
private readonly IDiscordRestAuditLogAPI _auditLogApi;
|
private readonly IDiscordRestAuditLogAPI _auditLogApi;
|
||||||
|
@ -89,8 +90,8 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||||
public async Task<Result> RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) {
|
public async Task<Result> RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) {
|
||||||
if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess();
|
if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess();
|
||||||
|
|
||||||
var guildConfiguration = await _dataService.GetConfiguration(guildId, ct);
|
var cfg = await _dataService.GetSettings(guildId, ct);
|
||||||
if (guildConfiguration.PrivateFeedbackChannel is 0) return Result.FromSuccess();
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()) return Result.FromSuccess();
|
||||||
|
|
||||||
var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct);
|
var messageResult = await _channelApi.GetChannelMessageAsync(gatewayEvent.ChannelID, gatewayEvent.ID, ct);
|
||||||
if (!messageResult.IsDefined(out var message)) return Result.FromError(messageResult);
|
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);
|
if (!userResult.IsDefined(out user)) return Result.FromError(userResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
Messages.Culture = guildConfiguration.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithSmallTitle(
|
.WithSmallTitle(
|
||||||
|
@ -127,14 +128,14 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
return (Result)await _channelApi.CreateMessageAsync(
|
||||||
guildConfiguration.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built },
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built },
|
||||||
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles logging the difference between an edited message's old and new content
|
/// 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>
|
/// </summary>
|
||||||
public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||||
private readonly CacheService _cacheService;
|
private readonly CacheService _cacheService;
|
||||||
|
@ -154,8 +155,8 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||||
public async Task<Result> RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) {
|
public async Task<Result> RespondAsync(IMessageUpdate gatewayEvent, CancellationToken ct = default) {
|
||||||
if (!gatewayEvent.GuildID.IsDefined(out var guildId))
|
if (!gatewayEvent.GuildID.IsDefined(out var guildId))
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
var guildConfiguration = await _dataService.GetConfiguration(guildId, ct);
|
var cfg = await _dataService.GetSettings(guildId, ct);
|
||||||
if (guildConfiguration.PrivateFeedbackChannel is 0)
|
if (GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty())
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
if (!gatewayEvent.Content.IsDefined(out var newContent))
|
if (!gatewayEvent.Content.IsDefined(out var newContent))
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
|
@ -189,7 +190,7 @@ public class MessageEditedResponder : IResponder<IMessageUpdate> {
|
||||||
|
|
||||||
var diff = InlineDiffBuilder.Diff(message.Content, newContent);
|
var diff = InlineDiffBuilder.Diff(message.Content, newContent);
|
||||||
|
|
||||||
Messages.Culture = guildConfiguration.GetCulture();
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithSmallTitle(string.Format(Messages.CachedMessageEdited, message.Author.GetTag()), message.Author)
|
.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);
|
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
return (Result)await _channelApi.CreateMessageAsync(
|
||||||
guildConfiguration.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built },
|
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built },
|
||||||
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles sending a guild's <see cref="GuildConfiguration.WelcomeMessage" /> if one is set.
|
/// Handles sending a guild's <see cref="GuildSettings.WelcomeMessage" /> if one is set.
|
||||||
/// If <see cref="GuildConfiguration.ReturnRolesOnRejoin"/> is enabled, roles will be returned.
|
/// If <see cref="GuildSettings.ReturnRolesOnRejoin"/> is enabled, roles will be returned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="GuildConfiguration.WelcomeMessage" />
|
/// <seealso cref="GuildSettings.WelcomeMessage" />
|
||||||
public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
private readonly GuildDataService _dataService;
|
private readonly GuildDataService _dataService;
|
||||||
|
@ -227,19 +228,21 @@ public class GuildMemberAddResponder : IResponder<IGuildMemberAdd> {
|
||||||
if (!gatewayEvent.User.IsDefined(out var user))
|
if (!gatewayEvent.User.IsDefined(out var user))
|
||||||
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.User)));
|
return Result.FromError(new ArgumentNullError(nameof(gatewayEvent.User)));
|
||||||
var data = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
var data = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
||||||
var cfg = data.Configuration;
|
var cfg = data.Settings;
|
||||||
if (cfg.PublicFeedbackChannel is 0 || cfg.WelcomeMessage is "off" or "disable" or "disabled")
|
if (GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
|
||||||
|
|| GuildSettings.WelcomeMessage.Get(cfg) is "off" or "disable" or "disabled")
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
if (cfg.ReturnRolesOnRejoin) {
|
if (GuildSettings.ReturnRolesOnRejoin.Get(cfg)) {
|
||||||
var result = await _guildApi.ModifyGuildMemberAsync(
|
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);
|
if (!result.IsSuccess) return Result.FromError(result.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Messages.Culture = data.Culture;
|
Messages.Culture = GuildSettings.Language.Get(cfg);
|
||||||
var welcomeMessage = cfg.WelcomeMessage is "default" or "reset"
|
var welcomeMessage = GuildSettings.WelcomeMessage.Get(cfg) is "default" or "reset"
|
||||||
? Messages.DefaultWelcomeMessage
|
? Messages.DefaultWelcomeMessage
|
||||||
: cfg.WelcomeMessage;
|
: GuildSettings.WelcomeMessage.Get(cfg);
|
||||||
|
|
||||||
var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct);
|
var guildResult = await _guildApi.GetGuildAsync(gatewayEvent.GuildID, ct: ct);
|
||||||
if (!guildResult.IsDefined(out var guild)) return Result.FromError(guildResult);
|
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);
|
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
return (Result)await _channelApi.CreateMessageAsync(
|
||||||
cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { built },
|
GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: new[] { built },
|
||||||
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles sending a notification when a scheduled event has been cancelled
|
/// 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>
|
/// </summary>
|
||||||
public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEventDelete> {
|
public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEventDelete> {
|
||||||
private readonly IDiscordRestChannelAPI _channelApi;
|
private readonly IDiscordRestChannelAPI _channelApi;
|
||||||
|
@ -275,7 +278,7 @@ public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEven
|
||||||
var guildData = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
var guildData = await _dataService.GetData(gatewayEvent.GuildID, ct);
|
||||||
guildData.ScheduledEvents.Remove(gatewayEvent.ID.Value);
|
guildData.ScheduledEvents.Remove(gatewayEvent.ID.Value);
|
||||||
|
|
||||||
if (guildData.Configuration.EventNotificationChannel is 0)
|
if (GuildSettings.EventNotificationChannel.Get(guildData.Settings).Empty())
|
||||||
return Result.FromSuccess();
|
return Result.FromSuccess();
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
|
@ -288,7 +291,7 @@ public class GuildScheduledEventDeleteResponder : IResponder<IGuildScheduledEven
|
||||||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
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) {
|
public async Task<Result> RespondAsync(IGuildMemberUpdate gatewayEvent, CancellationToken ct = default) {
|
||||||
var memberData = await _dataService.GetMemberData(gatewayEvent.GuildID, gatewayEvent.User.ID, ct);
|
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();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Boyfriend.locale;
|
||||||
using DiffPlex.DiffBuilder.Model;
|
using DiffPlex.DiffBuilder.Model;
|
||||||
using Remora.Discord.API;
|
using Remora.Discord.API;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
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}";
|
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);
|
return DiscordSnowflake.New(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static TResult? MaxOrDefault<TSource, TResult>(
|
public static TResult? MaxOrDefault<TSource, TResult>(
|
||||||
this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
|
this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
|
||||||
var list = source.ToList();
|
var list = source.ToList();
|
||||||
|
@ -190,4 +192,8 @@ public static class Extensions {
|
||||||
&& context.TryGetChannelID(out channelId)
|
&& context.TryGetChannelID(out channelId)
|
||||||
&& context.TryGetUserID(out userId);
|
&& 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.Collections.Concurrent;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Boyfriend.Data;
|
using Boyfriend.Data;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Remora.Discord.API.Abstractions.Rest;
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
|
@ -36,8 +37,8 @@ public class GuildDataService : IHostedService {
|
||||||
private async Task SaveAsync(CancellationToken ct) {
|
private async Task SaveAsync(CancellationToken ct) {
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
foreach (var data in _datas.Values) {
|
foreach (var data in _datas.Values) {
|
||||||
await using var configStream = File.OpenWrite(data.ConfigurationPath);
|
await using var settingsStream = File.OpenWrite(data.SettingsPath);
|
||||||
tasks.Add(JsonSerializer.SerializeAsync(configStream, data.Configuration, cancellationToken: ct));
|
tasks.Add(JsonSerializer.SerializeAsync(settingsStream, data.Settings, cancellationToken: ct));
|
||||||
|
|
||||||
await using var eventsStream = File.OpenWrite(data.ScheduledEventsPath);
|
await using var eventsStream = File.OpenWrite(data.ScheduledEventsPath);
|
||||||
tasks.Add(JsonSerializer.SerializeAsync(eventsStream, data.ScheduledEvents, cancellationToken: ct));
|
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) {
|
private async Task<GuildData> InitializeData(Snowflake guildId, CancellationToken ct = default) {
|
||||||
var idString = $"{guildId}";
|
var idString = $"{guildId}";
|
||||||
var memberDataPath = $"{guildId}/MemberData";
|
var memberDataPath = $"{guildId}/MemberData";
|
||||||
var configurationPath = $"{guildId}/Configuration.json";
|
var settingsPath = $"{guildId}/Settings.json";
|
||||||
var scheduledEventsPath = $"{guildId}/ScheduledEvents.json";
|
var scheduledEventsPath = $"{guildId}/ScheduledEvents.json";
|
||||||
if (!Directory.Exists(idString)) Directory.CreateDirectory(idString);
|
if (!Directory.Exists(idString)) Directory.CreateDirectory(idString);
|
||||||
if (!Directory.Exists(memberDataPath)) Directory.CreateDirectory(memberDataPath);
|
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);
|
if (!File.Exists(scheduledEventsPath)) await File.WriteAllTextAsync(scheduledEventsPath, "{}", ct);
|
||||||
|
|
||||||
await using var configurationStream = File.OpenRead(configurationPath);
|
await using var settingsStream = File.OpenRead(settingsPath);
|
||||||
var configuration
|
var jsonSettings
|
||||||
= JsonSerializer.DeserializeAsync<GuildConfiguration>(
|
= JsonNode.Parse(settingsStream);
|
||||||
configurationStream, cancellationToken: ct);
|
|
||||||
|
|
||||||
await using var eventsStream = File.OpenRead(scheduledEventsPath);
|
await using var eventsStream = File.OpenRead(scheduledEventsPath);
|
||||||
var events
|
var events
|
||||||
|
@ -80,23 +80,23 @@ public class GuildDataService : IHostedService {
|
||||||
await using var dataStream = File.OpenRead(dataPath);
|
await using var dataStream = File.OpenRead(dataPath);
|
||||||
var data = await JsonSerializer.DeserializeAsync<MemberData>(dataStream, cancellationToken: ct);
|
var data = await JsonSerializer.DeserializeAsync<MemberData>(dataStream, cancellationToken: ct);
|
||||||
if (data is null) continue;
|
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)
|
if (memberResult.IsSuccess)
|
||||||
data.Roles = memberResult.Entity.Roles.ToList();
|
data.Roles = memberResult.Entity.Roles.ToList().ConvertAll(r => r.Value);
|
||||||
|
|
||||||
memberData.Add(data.Id, data);
|
memberData.Add(data.Id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalData = new GuildData(
|
var finalData = new GuildData(
|
||||||
await configuration ?? new GuildConfiguration(), configurationPath,
|
jsonSettings ?? new JsonObject(), settingsPath,
|
||||||
await events ?? new Dictionary<ulong, ScheduledEventData>(), scheduledEventsPath,
|
await events ?? new Dictionary<ulong, ScheduledEventData>(), scheduledEventsPath,
|
||||||
memberData, memberDataPath);
|
memberData, memberDataPath);
|
||||||
while (!_datas.ContainsKey(guildId)) _datas.TryAdd(guildId, finalData);
|
while (!_datas.ContainsKey(guildId)) _datas.TryAdd(guildId, finalData);
|
||||||
return finalData;
|
return finalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GuildConfiguration> GetConfiguration(Snowflake guildId, CancellationToken ct = default) {
|
public async Task<JsonNode> GetSettings(Snowflake guildId, CancellationToken ct = default) {
|
||||||
return (await GetData(guildId, ct)).Configuration;
|
return (await GetData(guildId, ct)).Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MemberData> GetMemberData(Snowflake guildId, Snowflake userId, CancellationToken ct = default) {
|
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.Data;
|
||||||
|
using Boyfriend.locale;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
@ -94,9 +96,9 @@ public class GuildUpdateService : BackgroundService {
|
||||||
/// This method does the following:
|
/// This method does the following:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item>Automatically unbans users once their ban period has expired.</item>
|
/// <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>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 start notifications.</item>
|
||||||
/// <item>Sends scheduled event completion notifications.</item>
|
/// <item>Sends scheduled event completion notifications.</item>
|
||||||
/// <item>Sends reminders to members.</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>
|
/// <param name="ct">The cancellation token for this operation.</param>
|
||||||
private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) {
|
private async Task TickGuildAsync(Snowflake guildId, CancellationToken ct = default) {
|
||||||
var data = await _dataService.GetData(guildId, ct);
|
var data = await _dataService.GetData(guildId, ct);
|
||||||
Messages.Culture = data.Culture;
|
Messages.Culture = GuildSettings.Language.Get(data.Settings);
|
||||||
var defaultRoleSnowflake = data.Configuration.DefaultRole.ToDiscordSnowflake();
|
var defaultRole = GuildSettings.DefaultRole.Get(data.Settings);
|
||||||
|
|
||||||
foreach (var memberData in data.MemberData.Values) {
|
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(
|
_ = _guildApi.AddGuildMemberRoleAsync(
|
||||||
guildId, userId, defaultRoleSnowflake, ct: ct);
|
guildId, userId, defaultRole, ct: ct);
|
||||||
|
|
||||||
if (DateTimeOffset.UtcNow > memberData.BannedUntil) {
|
if (DateTimeOffset.UtcNow > memberData.BannedUntil) {
|
||||||
var unbanResult = await _guildApi.RemoveGuildBanAsync(
|
var unbanResult = await _guildApi.RemoveGuildBanAsync(
|
||||||
|
@ -139,7 +141,7 @@ public class GuildUpdateService : BackgroundService {
|
||||||
|
|
||||||
for (var i = memberData.Reminders.Count - 1; i >= 0; i--) {
|
for (var i = memberData.Reminders.Count - 1; i >= 0; i--) {
|
||||||
var reminder = memberData.Reminders[i];
|
var reminder = memberData.Reminders[i];
|
||||||
if (DateTimeOffset.UtcNow < reminder.RemindAt) continue;
|
if (DateTimeOffset.UtcNow < reminder.At) continue;
|
||||||
|
|
||||||
var embed = new EmbedBuilder().WithSmallTitle(
|
var embed = new EmbedBuilder().WithSmallTitle(
|
||||||
string.Format(Messages.Reminder, user.GetTag()), user)
|
string.Format(Messages.Reminder, user.GetTag()), user)
|
||||||
|
@ -151,7 +153,7 @@ public class GuildUpdateService : BackgroundService {
|
||||||
if (!embed.IsDefined(out var built)) continue;
|
if (!embed.IsDefined(out var built)) continue;
|
||||||
|
|
||||||
var messageResult = await _channelApi.CreateMessageAsync(
|
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)
|
if (!messageResult.IsSuccess)
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Error in reminder send.\n{ErrorMessage}", messageResult.Error.Message);
|
"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);
|
var eventsResult = await _eventApi.ListScheduledEventsForGuildAsync(guildId, ct: ct);
|
||||||
if (!eventsResult.IsDefined(out var events)) return;
|
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) {
|
foreach (var scheduledEvent in events) {
|
||||||
if (!data.ScheduledEvents.ContainsKey(scheduledEvent.ID.Value)) {
|
if (!data.ScheduledEvents.ContainsKey(scheduledEvent.ID.Value)) {
|
||||||
|
@ -172,7 +174,7 @@ public class GuildUpdateService : BackgroundService {
|
||||||
var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value];
|
var storedEvent = data.ScheduledEvents[scheduledEvent.ID.Value];
|
||||||
if (storedEvent.Status == scheduledEvent.Status) {
|
if (storedEvent.Status == scheduledEvent.Status) {
|
||||||
if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) {
|
if (DateTimeOffset.UtcNow >= scheduledEvent.ScheduledStartTime) {
|
||||||
if (data.Configuration.AutoStartEvents
|
if (GuildSettings.AutoStartEvents.Get(data.Settings)
|
||||||
&& scheduledEvent.Status is not GuildScheduledEventStatus.Active) {
|
&& scheduledEvent.Status is not GuildScheduledEventStatus.Active) {
|
||||||
var startResult = await _eventApi.ModifyGuildScheduledEventAsync(
|
var startResult = await _eventApi.ModifyGuildScheduledEventAsync(
|
||||||
guildId, scheduledEvent.ID,
|
guildId, scheduledEvent.ID,
|
||||||
|
@ -182,10 +184,11 @@ public class GuildUpdateService : BackgroundService {
|
||||||
"Error in automatic scheduled event start request.\n{ErrorMessage}",
|
"Error in automatic scheduled event start request.\n{ErrorMessage}",
|
||||||
startResult.Error.Message);
|
startResult.Error.Message);
|
||||||
}
|
}
|
||||||
} else if (data.Configuration.EventEarlyNotificationOffset != TimeSpan.Zero
|
} else if (GuildSettings.EventEarlyNotificationOffset.Get(data.Settings) != TimeSpan.Zero
|
||||||
&& !storedEvent.EarlyNotificationSent
|
&& !storedEvent.EarlyNotificationSent
|
||||||
&& DateTimeOffset.UtcNow
|
&& DateTimeOffset.UtcNow
|
||||||
>= scheduledEvent.ScheduledStartTime - data.Configuration.EventEarlyNotificationOffset) {
|
>= scheduledEvent.ScheduledStartTime
|
||||||
|
- GuildSettings.EventEarlyNotificationOffset.Get(data.Settings)) {
|
||||||
var earlyResult = await SendScheduledEventUpdatedMessage(scheduledEvent, data, true, ct);
|
var earlyResult = await SendScheduledEventUpdatedMessage(scheduledEvent, data, true, ct);
|
||||||
if (earlyResult.IsSuccess)
|
if (earlyResult.IsSuccess)
|
||||||
storedEvent.EarlyNotificationSent = true;
|
storedEvent.EarlyNotificationSent = true;
|
||||||
|
@ -203,7 +206,7 @@ public class GuildUpdateService : BackgroundService {
|
||||||
|
|
||||||
var result = scheduledEvent.Status switch {
|
var result = scheduledEvent.Status switch {
|
||||||
GuildScheduledEventStatus.Scheduled =>
|
GuildScheduledEventStatus.Scheduled =>
|
||||||
await SendScheduledEventCreatedMessage(scheduledEvent, data.Configuration, ct),
|
await SendScheduledEventCreatedMessage(scheduledEvent, data.Settings, ct),
|
||||||
GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed =>
|
GuildScheduledEventStatus.Active or GuildScheduledEventStatus.Completed =>
|
||||||
await SendScheduledEventUpdatedMessage(scheduledEvent, data, false, ct),
|
await SendScheduledEventUpdatedMessage(scheduledEvent, data, false, ct),
|
||||||
_ => Result.FromError(new ArgumentOutOfRangeError(nameof(scheduledEvent.Status)))
|
_ => Result.FromError(new ArgumentOutOfRangeError(nameof(scheduledEvent.Status)))
|
||||||
|
@ -215,17 +218,17 @@ public class GuildUpdateService : BackgroundService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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,
|
/// set,
|
||||||
/// when a scheduled event is created
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="scheduledEvent">The scheduled event that has just been created.</param>
|
/// <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>
|
/// <param name="ct">The cancellation token for this operation.</param>
|
||||||
/// <returns>A notification sending result which may or may not have succeeded.</returns>
|
/// <returns>A notification sending result which may or may not have succeeded.</returns>
|
||||||
private async Task<Result> SendScheduledEventCreatedMessage(
|
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);
|
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
|
||||||
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
|
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
|
||||||
|
|
||||||
|
@ -281,8 +284,8 @@ public class GuildUpdateService : BackgroundService {
|
||||||
.Build();
|
.Build();
|
||||||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||||
|
|
||||||
var roleMention = config.EventNotificationRole is not 0
|
var roleMention = !GuildSettings.EventNotificationRole.Get(settings).Empty()
|
||||||
? Mention.Role(config.EventNotificationRole.ToDiscordSnowflake())
|
? Mention.Role(GuildSettings.EventNotificationRole.Get(settings))
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
var button = new ButtonComponent(
|
var button = new ButtonComponent(
|
||||||
|
@ -294,14 +297,14 @@ public class GuildUpdateService : BackgroundService {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
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);
|
components: new[] { new ActionRowComponent(new[] { button }) }, ct: ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="scheduledEvent">The scheduled event that is about to start, has started or completed.</param>
|
/// <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>
|
/// <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(
|
var contentResult = await _utility.GetEventNotificationMentions(
|
||||||
scheduledEvent, data.Configuration, ct);
|
scheduledEvent, data.Settings, ct);
|
||||||
if (!contentResult.IsDefined(out content))
|
if (!contentResult.IsDefined(out content))
|
||||||
return Result.FromError(contentResult);
|
return Result.FromError(contentResult);
|
||||||
|
|
||||||
|
@ -383,7 +386,7 @@ public class GuildUpdateService : BackgroundService {
|
||||||
if (!result.IsDefined(out var built)) return Result.FromError(result);
|
if (!result.IsDefined(out var built)) return Result.FromError(result);
|
||||||
|
|
||||||
return (Result)await _channelApi.CreateMessageAsync(
|
return (Result)await _channelApi.CreateMessageAsync(
|
||||||
data.Configuration.EventNotificationChannel.ToDiscordSnowflake(),
|
GuildSettings.EventNotificationChannel.Get(data.Settings),
|
||||||
content ?? default(Optional<string>), embeds: new[] { built }, ct: ct);
|
content ?? default(Optional<string>), embeds: new[] { built }, ct: ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Boyfriend.Data;
|
using Boyfriend.Data;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
@ -103,38 +104,37 @@ public class UtilityService : IHostedService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the guild configuration enables <see cref="GuildConfiguration.NotificationReceiver.Role" />, then the
|
/// If the guild settings enables <see cref="GuildSettings.NotificationReceiver.Role" />, then the
|
||||||
/// <see cref="GuildConfiguration.EventNotificationRole" /> will also be mentioned.
|
/// <see cref="GuildSettings.EventNotificationRole" /> will also be mentioned.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="scheduledEvent">
|
/// <param name="scheduledEvent">
|
||||||
/// The scheduled event whose subscribers will be mentioned if the guild configuration enables
|
/// The scheduled event whose subscribers will be mentioned if the guild settings enables
|
||||||
/// <see cref="GuildConfiguration.NotificationReceiver.Interested" />.
|
/// <see cref="GuildSettings.NotificationReceiver.Interested" />.
|
||||||
/// </param>
|
/// </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>
|
/// <param name="ct">The cancellation token for this operation.</param>
|
||||||
/// <returns>A result containing the string which may or may not have succeeded.</returns>
|
/// <returns>A result containing the string which may or may not have succeeded.</returns>
|
||||||
public async Task<Result<string>> GetEventNotificationMentions(
|
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 builder = new StringBuilder();
|
||||||
var receivers = config.EventStartedReceivers;
|
var role = GuildSettings.EventNotificationRole.Get(settings);
|
||||||
var role = config.EventNotificationRole.ToDiscordSnowflake();
|
|
||||||
var usersResult = await _eventApi.GetGuildScheduledEventUsersAsync(
|
var usersResult = await _eventApi.GetGuildScheduledEventUsersAsync(
|
||||||
scheduledEvent.GuildID, scheduledEvent.ID, withMember: true, ct: ct);
|
scheduledEvent.GuildID, scheduledEvent.ID, withMember: true, ct: ct);
|
||||||
if (!usersResult.IsDefined(out var users)) return Result<string>.FromError(usersResult);
|
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)} ");
|
builder.Append($"{Mention.Role(role)} ");
|
||||||
if (receivers.Contains(GuildConfiguration.NotificationReceiver.Interested))
|
|
||||||
builder = users.Where(
|
builder = users.Where(
|
||||||
user => {
|
user => {
|
||||||
if (!user.GuildMember.IsDefined(out var member)) return true;
|
if (!user.GuildMember.IsDefined(out var member)) return true;
|
||||||
return !member.Roles.Contains(role);
|
return !member.Roles.Contains(role);
|
||||||
})
|
})
|
||||||
.Aggregate(builder, (current, user) => current.Append($"{Mention.User(user.User)} "));
|
.Aggregate(builder, (current, user) => current.Append($"{Mention.User(user.User)} "));
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue