diff --git a/.github/workflows/resharper.yml b/.github/workflows/resharper.yml
index 82b2562..b0ec1a3 100644
--- a/.github/workflows/resharper.yml
+++ b/.github/workflows/resharper.yml
@@ -4,10 +4,12 @@ concurrency:
cancel-in-progress: true
on:
- push:
- branches: [ "master" ]
pull_request:
branches: [ "master" ]
+ merge_group:
+ types: [checks_requested]
+ push:
+ branches: [ "master" ]
jobs:
inspect-code:
diff --git a/Boyfriend.csproj b/Boyfriend.csproj
index f6cc7dc..a40b9a8 100644
--- a/Boyfriend.csproj
+++ b/Boyfriend.csproj
@@ -21,8 +21,12 @@
+
-
+
+
+
+
diff --git a/docs/README.md b/docs/README.md
index 21ffbbf..0cde8ef 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,12 +1,6 @@
-
+
+
+
![GitHub License](https://img.shields.io/github/license/TeamOctolings/Boyfriend)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/TeamOctolings/Boyfriend/.github/workflows/resharper.yml?branch=master)
diff --git a/docs/assets/boyfriend.png b/docs/assets/boyfriend.png
new file mode 100644
index 0000000..a8a5d16
Binary files /dev/null and b/docs/assets/boyfriend.png differ
diff --git a/src/Boyfriend.cs b/src/Boyfriend.cs
index 6af4326..0731ccd 100644
--- a/src/Boyfriend.cs
+++ b/src/Boyfriend.cs
@@ -56,23 +56,28 @@ public class Boyfriend {
| GatewayIntents.GuildMembers
| GatewayIntents.GuildScheduledEvents);
services.Configure(
- settings => {
- settings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
- settings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
- settings.SetAbsoluteExpiration(TimeSpan.FromDays(7));
- settings.SetSlidingExpiration(TimeSpan.FromDays(7));
+ cSettings => {
+ cSettings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
+ cSettings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
+ cSettings.SetAbsoluteExpiration(TimeSpan.FromDays(7));
+ cSettings.SetSlidingExpiration(TimeSpan.FromDays(7));
});
services.AddTransient()
+ // Init
.AddDiscordCaching()
.AddDiscordCommands(true)
- .AddPreparationErrorEvent()
- .AddPostExecutionEvent()
+ // Interactions
.AddInteractivity()
.AddInteractionGroup()
+ // Slash command event handlers
+ .AddPreparationErrorEvent()
+ .AddPostExecutionEvent()
+ // Services
.AddSingleton()
.AddSingleton()
.AddHostedService()
+ // Slash commands
.AddCommandTree()
.WithCommandGroup()
.WithCommandGroup()
diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs
index 5ff757e..edb34d2 100644
--- a/src/Commands/AboutCommandGroup.cs
+++ b/src/Commands/AboutCommandGroup.cs
@@ -1,6 +1,8 @@
using System.ComponentModel;
using System.Text;
+using Boyfriend.Data;
using Boyfriend.Services;
+using JetBrains.Annotations;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Rest;
@@ -10,14 +12,12 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting;
using Remora.Results;
-// ReSharper disable ClassNeverInstantiated.Global
-// ReSharper disable UnusedMember.Global
-
namespace Boyfriend.Commands;
///
/// Handles the command to show information about this bot: /about.
///
+[UsedImplicitly]
public class AboutCommandGroup : CommandGroup {
private static readonly string[] Developers = { "Octol1ttle", "mctaylors", "neroduckale" };
private readonly ICommandContext _context;
@@ -42,6 +42,7 @@ public class AboutCommandGroup : CommandGroup {
///
[Command("about")]
[Description("Shows Boyfriend's developers")]
+ [UsedImplicitly]
public async Task SendAboutBotAsync() {
if (!_context.TryGetContextIDs(out var guildId, out _, out _))
return Result.FromError(
@@ -51,8 +52,8 @@ public class AboutCommandGroup : CommandGroup {
if (!currentUserResult.IsDefined(out var currentUser))
return Result.FromError(currentUserResult);
- var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
- Messages.Culture = cfg.GetCulture();
+ var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
+ Messages.Culture = GuildSettings.Language.Get(cfg);
var builder = new StringBuilder().AppendLine(Markdown.Bold(Messages.AboutTitleDevelopers));
foreach (var dev in Developers)
@@ -65,8 +66,7 @@ public class AboutCommandGroup : CommandGroup {
var embed = new EmbedBuilder().WithSmallTitle(Messages.AboutBot, currentUser)
.WithDescription(builder.ToString())
.WithColour(ColorsList.Cyan)
- .WithImageUrl(
- "https://media.discordapp.net/attachments/837385840946053181/1125009665592393738/boyfriend.png")
+ .WithImageUrl("https://cdn.upload.systems/uploads/JFAaX5vr.png")
.Build();
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs
index 02e0fa2..d2c1c76 100644
--- a/src/Commands/BanCommandGroup.cs
+++ b/src/Commands/BanCommandGroup.cs
@@ -1,11 +1,14 @@
using System.ComponentModel;
using System.Text;
+using Boyfriend.Data;
using Boyfriend.Services;
+using JetBrains.Annotations;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects;
+using Remora.Discord.Commands.Attributes;
using Remora.Discord.Commands.Conditions;
using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Feedback.Services;
@@ -13,14 +16,12 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting;
using Remora.Results;
-// ReSharper disable ClassNeverInstantiated.Global
-// ReSharper disable UnusedMember.Global
-
namespace Boyfriend.Commands;
///
/// Handles commands related to ban management: /ban and /unban.
///
+[UsedImplicitly]
public class BanCommandGroup : CommandGroup {
private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context;
@@ -58,10 +59,13 @@ public class BanCommandGroup : CommandGroup {
///
///
[Command("ban", "бан")]
+ [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)]
+ [DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
[RequireDiscordPermission(DiscordPermission.BanMembers)]
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
[Description("Ban user")]
+ [UsedImplicitly]
public async Task BanUserAsync(
[Description("User to ban")] IUser target,
[Description("Ban reason")] string reason,
@@ -76,8 +80,8 @@ public class BanCommandGroup : CommandGroup {
return Result.FromError(currentUserResult);
var data = await _dataService.GetData(guildId.Value, CancellationToken);
- var cfg = data.Configuration;
- Messages.Culture = data.Culture;
+ var cfg = data.Settings;
+ Messages.Culture = GuildSettings.Language.Get(cfg);
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
if (existingBanResult.IsDefined()) {
@@ -145,8 +149,10 @@ public class BanCommandGroup : CommandGroup {
string.Format(Messages.UserBanned, target.GetTag()), target)
.WithColour(ColorsList.Green).Build();
- if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
- || (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
+ if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
+ || (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
var logEmbed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.UserBanned, target.GetTag()), target)
.WithDescription(description)
@@ -160,14 +166,14 @@ public class BanCommandGroup : CommandGroup {
var builtArray = new[] { logBuilt };
// Not awaiting to reduce response time
- if (cfg.PublicFeedbackChannel != channelId.Value)
+ if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
+ GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
ct: CancellationToken);
- if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
- && cfg.PrivateFeedbackChannel != channelId.Value)
+ if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
+ GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
ct: CancellationToken);
}
}
@@ -193,10 +199,13 @@ public class BanCommandGroup : CommandGroup {
///
///
[Command("unban")]
+ [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)]
+ [DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
[RequireDiscordPermission(DiscordPermission.BanMembers)]
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
[Description("Unban user")]
+ [UsedImplicitly]
public async Task UnbanUserAsync(
[Description("User to unban")] IUser target,
[Description("Unban reason")] string reason) {
@@ -209,8 +218,8 @@ public class BanCommandGroup : CommandGroup {
if (!currentUserResult.IsDefined(out var currentUser))
return Result.FromError(currentUserResult);
- var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
- Messages.Culture = cfg.GetCulture();
+ var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
+ Messages.Culture = GuildSettings.Language.Get(cfg);
var existingBanResult = await _guildApi.GetGuildBanAsync(guildId.Value, target.ID, CancellationToken);
if (!existingBanResult.IsDefined()) {
@@ -238,8 +247,10 @@ public class BanCommandGroup : CommandGroup {
string.Format(Messages.UserUnbanned, target.GetTag()), target)
.WithColour(ColorsList.Green).Build();
- if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
- || (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
+ if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
+ || (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
var logEmbed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.UserUnbanned, target.GetTag()), target)
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
@@ -254,14 +265,14 @@ public class BanCommandGroup : CommandGroup {
var builtArray = new[] { logBuilt };
// Not awaiting to reduce response time
- if (cfg.PublicFeedbackChannel != channelId.Value)
+ if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
+ GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
ct: CancellationToken);
- if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
- && cfg.PrivateFeedbackChannel != channelId.Value)
+ if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
+ GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
ct: CancellationToken);
}
diff --git a/src/Commands/ClearCommandGroup.cs b/src/Commands/ClearCommandGroup.cs
index de44fbb..ede4d0b 100644
--- a/src/Commands/ClearCommandGroup.cs
+++ b/src/Commands/ClearCommandGroup.cs
@@ -1,6 +1,8 @@
using System.ComponentModel;
using System.Text;
+using Boyfriend.Data;
using Boyfriend.Services;
+using JetBrains.Annotations;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
@@ -14,14 +16,12 @@ using Remora.Discord.Extensions.Formatting;
using Remora.Rest.Core;
using Remora.Results;
-// ReSharper disable ClassNeverInstantiated.Global
-// ReSharper disable UnusedMember.Global
-
namespace Boyfriend.Commands;
///
/// Handles the command to clear messages in a channel: /clear.
///
+[UsedImplicitly]
public class ClearCommandGroup : CommandGroup {
private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context;
@@ -48,10 +48,13 @@ public class ClearCommandGroup : CommandGroup {
/// were cleared and vice-versa.
///
[Command("clear", "очистить")]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
+ [DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
[RequireDiscordPermission(DiscordPermission.ManageMessages)]
[RequireBotDiscordPermissions(DiscordPermission.ManageMessages)]
[Description("Remove multiple messages")]
+ [UsedImplicitly]
public async Task ClearMessagesAsync(
[Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)]
int amount) {
@@ -64,8 +67,8 @@ public class ClearCommandGroup : CommandGroup {
if (!messagesResult.IsDefined(out var messages))
return Result.FromError(messagesResult);
- var cfg = await _dataService.GetConfiguration(guildId.Value, CancellationToken);
- Messages.Culture = cfg.GetCulture();
+ var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken);
+ Messages.Culture = GuildSettings.Language.Get(cfg);
var idList = new List(messages.Count);
var builder = new StringBuilder().AppendLine(Mention.Channel(channelId.Value)).AppendLine();
@@ -93,7 +96,8 @@ public class ClearCommandGroup : CommandGroup {
return Result.FromError(currentUserResult);
var title = string.Format(Messages.MessagesCleared, amount.ToString());
- if (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value) {
+ if (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value) {
var logEmbed = new EmbedBuilder().WithSmallTitle(title, currentUser)
.WithDescription(description)
.WithActionFooter(user)
@@ -105,9 +109,9 @@ public class ClearCommandGroup : CommandGroup {
return Result.FromError(logEmbed);
// Not awaiting to reduce response time
- if (cfg.PrivateFeedbackChannel != channelId.Value)
+ if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: new[] { logBuilt },
+ GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { logBuilt },
ct: CancellationToken);
}
diff --git a/src/Commands/ErrorLoggingEvents.cs b/src/Commands/ErrorLoggingEvents.cs
index 30869b4..c5eba21 100644
--- a/src/Commands/ErrorLoggingEvents.cs
+++ b/src/Commands/ErrorLoggingEvents.cs
@@ -1,15 +1,16 @@
+using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Remora.Discord.Commands.Contexts;
+using Remora.Discord.Commands.Extensions;
using Remora.Discord.Commands.Services;
using Remora.Results;
-// ReSharper disable ClassNeverInstantiated.Global
-
namespace Boyfriend.Commands;
///
/// Handles error logging for slash commands that couldn't be successfully prepared.
///
+[UsedImplicitly]
public class ErrorLoggingPreparationErrorEvent : IPreparationErrorEvent {
private readonly ILogger _logger;
@@ -27,8 +28,11 @@ public class ErrorLoggingPreparationErrorEvent : IPreparationErrorEvent {
/// A result which has succeeded.
public Task PreparationFailed(
IOperationContext context, IResult preparationResult, CancellationToken ct = default) {
- if (!preparationResult.IsSuccess)
+ if (!preparationResult.IsSuccess && !preparationResult.Error.IsUserOrEnvironmentError()) {
_logger.LogWarning("Error in slash command preparation.\n{ErrorMessage}", preparationResult.Error.Message);
+ if (preparationResult.Error is ExceptionError exerr)
+ _logger.LogError(exerr.Exception, "An exception has been thrown");
+ }
return Task.FromResult(Result.FromSuccess());
}
@@ -37,6 +41,7 @@ public class ErrorLoggingPreparationErrorEvent : IPreparationErrorEvent {
///
/// Handles error logging for slash command groups.
///
+[UsedImplicitly]
public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent {
private readonly ILogger _logger;
@@ -54,8 +59,11 @@ public class ErrorLoggingPostExecutionEvent : IPostExecutionEvent {
/// A result which has succeeded.
public Task AfterExecutionAsync(
ICommandContext context, IResult commandResult, CancellationToken ct = default) {
- if (!commandResult.IsSuccess)
+ if (!commandResult.IsSuccess && !commandResult.Error.IsUserOrEnvironmentError()) {
_logger.LogWarning("Error in slash command execution.\n{ErrorMessage}", commandResult.Error.Message);
+ if (commandResult.Error is ExceptionError exerr)
+ _logger.LogError(exerr.Exception, "An exception has been thrown");
+ }
return Task.FromResult(Result.FromSuccess());
}
diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs
index da7b2c5..5809677 100644
--- a/src/Commands/KickCommandGroup.cs
+++ b/src/Commands/KickCommandGroup.cs
@@ -1,24 +1,25 @@
using System.ComponentModel;
+using Boyfriend.Data;
using Boyfriend.Services;
+using JetBrains.Annotations;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects;
+using Remora.Discord.Commands.Attributes;
using Remora.Discord.Commands.Conditions;
using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Feedback.Services;
using Remora.Discord.Extensions.Embeds;
using Remora.Results;
-// ReSharper disable ClassNeverInstantiated.Global
-// ReSharper disable UnusedMember.Global
-
namespace Boyfriend.Commands;
///
/// Handles the command to kick members of a guild: /kick.
///
+[UsedImplicitly]
public class KickCommandGroup : CommandGroup {
private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context;
@@ -54,10 +55,13 @@ public class KickCommandGroup : CommandGroup {
/// was kicked and vice-versa.
///
[Command("kick", "кик")]
+ [DiscordDefaultMemberPermissions(DiscordPermission.KickMembers)]
+ [DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
[RequireDiscordPermission(DiscordPermission.KickMembers)]
[RequireBotDiscordPermissions(DiscordPermission.KickMembers)]
[Description("Kick member")]
+ [UsedImplicitly]
public async Task KickUserAsync(
[Description("Member to kick")] IUser target,
[Description("Kick reason")] string reason) {
@@ -71,8 +75,8 @@ public class KickCommandGroup : CommandGroup {
return Result.FromError(currentUserResult);
var data = await _dataService.GetData(guildId.Value, CancellationToken);
- var cfg = data.Configuration;
- Messages.Culture = cfg.GetCulture();
+ var cfg = data.Settings;
+ Messages.Culture = GuildSettings.Language.Get(cfg);
var memberResult = await _guildApi.GetGuildMemberAsync(guildId.Value, target.ID, CancellationToken);
if (!memberResult.IsSuccess) {
@@ -129,8 +133,10 @@ public class KickCommandGroup : CommandGroup {
string.Format(Messages.UserKicked, target.GetTag()), target)
.WithColour(ColorsList.Green).Build();
- if ((cfg.PublicFeedbackChannel is not 0 && cfg.PublicFeedbackChannel != channelId.Value)
- || (cfg.PrivateFeedbackChannel is not 0 && cfg.PrivateFeedbackChannel != channelId.Value)) {
+ if ((!GuildSettings.PublicFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
+ || (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)) {
var logEmbed = new EmbedBuilder().WithSmallTitle(
string.Format(Messages.UserKicked, target.GetTag()), target)
.WithDescription(string.Format(Messages.DescriptionActionReason, reason))
@@ -144,14 +150,14 @@ public class KickCommandGroup : CommandGroup {
var builtArray = new[] { logBuilt };
// Not awaiting to reduce response time
- if (cfg.PublicFeedbackChannel != channelId.Value)
+ if (GuildSettings.PublicFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PublicFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
+ GuildSettings.PublicFeedbackChannel.Get(cfg), embeds: builtArray,
ct: CancellationToken);
- if (cfg.PrivateFeedbackChannel != cfg.PublicFeedbackChannel
- && cfg.PrivateFeedbackChannel != channelId.Value)
+ if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != GuildSettings.PublicFeedbackChannel.Get(cfg)
+ && GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
- cfg.PrivateFeedbackChannel.ToDiscordSnowflake(), embeds: builtArray,
+ GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: builtArray,
ct: CancellationToken);
}
}
diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs
index 764b4f4..4338d09 100644
--- a/src/Commands/MuteCommandGroup.cs
+++ b/src/Commands/MuteCommandGroup.cs
@@ -1,11 +1,14 @@
using System.ComponentModel;
using System.Text;
+using Boyfriend.Data;
using Boyfriend.Services;
+using JetBrains.Annotations;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects;
+using Remora.Discord.Commands.Attributes;
using Remora.Discord.Commands.Conditions;
using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Feedback.Services;
@@ -13,14 +16,12 @@ using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Extensions.Formatting;
using Remora.Results;
-// ReSharper disable ClassNeverInstantiated.Global
-// ReSharper disable UnusedMember.Global
-
namespace Boyfriend.Commands;
///
/// Handles commands related to mute management: /mute and /unmute.
///
+[UsedImplicitly]
public class MuteCommandGroup : CommandGroup {
private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context;
@@ -58,10 +59,13 @@ public class MuteCommandGroup : CommandGroup {
///
///
[Command("mute", "мут")]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ModerateMembers)]
+ [DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
[RequireDiscordPermission(DiscordPermission.ModerateMembers)]
[RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)]
[Description("Mute member")]
+ [UsedImplicitly]
public async Task MuteUserAsync(
[Description("Member to mute")] IUser target,
[Description("Mute reason")] string reason,
@@ -93,8 +97,8 @@ public class MuteCommandGroup : CommandGroup {
return Result.FromError(interactionResult);
var data = await _dataService.GetData(guildId.Value, CancellationToken);
- var cfg = data.Configuration;
- Messages.Culture = data.Culture;
+ var cfg = data.Settings;
+ Messages.Culture = GuildSettings.Language.Get(cfg);
Result