diff --git a/locale/Messages.resx b/locale/Messages.resx
index 41bb6ef..47e7d4f 100644
--- a/locale/Messages.resx
+++ b/locale/Messages.resx
@@ -231,8 +231,11 @@
You cannot kick members from this guild!
-
- You cannot moderate members in this guild!
+
+ You cannot mute members in this guild!
+
+
+ You cannot unmute members in this guild!
You cannot manage this guild!
@@ -675,4 +678,7 @@
Open Octobot's Wiki
+
+ Moderator role
+
diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx
index 273338b..2eef257 100644
--- a/locale/Messages.ru.resx
+++ b/locale/Messages.ru.resx
@@ -228,8 +228,11 @@
Ты не можешь выгонять участников с этого сервера!
-
- Ты не можешь модерировать участников этого сервера!
+
+ Ты не можешь глушить участников этого сервера!
+
+
+ Ты не можешь разглушать участников этого сервера!
Ты не можешь настраивать этот сервер!
@@ -675,4 +678,7 @@
Открыть Octobot's Wiki
+
+ Роль модератора
+
diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx
index af2c94d..4e92a44 100644
--- a/locale/Messages.tt-ru.resx
+++ b/locale/Messages.tt-ru.resx
@@ -231,8 +231,11 @@
кик шизиков нельзя
-
- тебе нельзя управлять шизоидами
+
+ тебе нельзя мутить шизоидов
+
+
+ тебе нельзя раззамучивать шизоидов
тебе нельзя редактировать дурку
@@ -675,4 +678,7 @@
вики Octobot (жмак)
+
+ звание админа
+
diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs
index ef5e9a4..02a377a 100644
--- a/src/Commands/BanCommandGroup.cs
+++ b/src/Commands/BanCommandGroup.cs
@@ -28,6 +28,7 @@ namespace Octobot.Commands;
[UsedImplicitly]
public class BanCommandGroup : CommandGroup
{
+ private readonly AccessControlService _access;
private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context;
private readonly IFeedbackService _feedback;
@@ -36,16 +37,16 @@ public class BanCommandGroup : CommandGroup
private readonly IDiscordRestUserAPI _userApi;
private readonly Utility _utility;
- public BanCommandGroup(
- ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData,
- IFeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi,
- Utility utility)
+ public BanCommandGroup(AccessControlService access, IDiscordRestChannelAPI channelApi, ICommandContext context,
+ IFeedbackService feedback, IDiscordRestGuildAPI guildApi, GuildDataService guildData,
+ IDiscordRestUserAPI userApi, Utility utility)
{
- _context = context;
+ _access = access;
_channelApi = channelApi;
- _guildData = guildData;
+ _context = context;
_feedback = feedback;
_guildApi = guildApi;
+ _guildData = guildData;
_userApi = userApi;
_utility = utility;
}
@@ -65,10 +66,10 @@ public class BanCommandGroup : CommandGroup
///
///
[Command("ban", "бан")]
- [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
[DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
- [RequireDiscordPermission(DiscordPermission.BanMembers)]
+ [RequireDiscordPermission(DiscordPermission.ManageMessages)]
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
[Description("Ban user")]
[UsedImplicitly]
@@ -128,7 +129,8 @@ public class BanCommandGroup : CommandGroup
}
private async Task BanUserAsync(
- IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data, Snowflake channelId,
+ IUser executor, IUser target, string reason, TimeSpan? duration, IGuild guild, GuildData data,
+ Snowflake channelId,
IUser bot, CancellationToken ct = default)
{
var existingBanResult = await _guildApi.GetGuildBanAsync(guild.ID, target.ID, ct);
@@ -141,7 +143,7 @@ public class BanCommandGroup : CommandGroup
}
var interactionResult
- = await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct);
+ = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Ban", ct);
if (!interactionResult.IsSuccess)
{
return ResultExtensions.FromError(interactionResult);
@@ -155,7 +157,8 @@ public class BanCommandGroup : CommandGroup
return await _feedback.SendContextualEmbedResultAsync(errorEmbed, ct: ct);
}
- var builder = new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason));
+ var builder =
+ new StringBuilder().AppendBulletPointLine(string.Format(Messages.DescriptionActionReason, reason));
if (duration is not null)
{
builder.AppendBulletPoint(
@@ -221,10 +224,10 @@ public class BanCommandGroup : CommandGroup
///
///
[Command("unban")]
- [DiscordDefaultMemberPermissions(DiscordPermission.BanMembers)]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
[DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
- [RequireDiscordPermission(DiscordPermission.BanMembers)]
+ [RequireDiscordPermission(DiscordPermission.ManageMessages)]
[RequireBotDiscordPermissions(DiscordPermission.BanMembers)]
[Description("Unban user")]
[UsedImplicitly]
@@ -286,7 +289,8 @@ public class BanCommandGroup : CommandGroup
.WithColour(ColorsList.Green).Build();
var title = string.Format(Messages.UserUnbanned, target.GetTag());
- var description = new StringBuilder().AppendBulletPoint(string.Format(Messages.DescriptionActionReason, reason));
+ var description =
+ new StringBuilder().AppendBulletPoint(string.Format(Messages.DescriptionActionReason, reason));
_utility.LogAction(
data.Settings, channelId, executor, title, description.ToString(), target, ColorsList.Green, ct: ct);
diff --git a/src/Commands/KickCommandGroup.cs b/src/Commands/KickCommandGroup.cs
index 5149ad4..87b915a 100644
--- a/src/Commands/KickCommandGroup.cs
+++ b/src/Commands/KickCommandGroup.cs
@@ -24,6 +24,7 @@ namespace Octobot.Commands;
[UsedImplicitly]
public class KickCommandGroup : CommandGroup
{
+ private readonly AccessControlService _access;
private readonly IDiscordRestChannelAPI _channelApi;
private readonly ICommandContext _context;
private readonly IFeedbackService _feedback;
@@ -32,16 +33,16 @@ public class KickCommandGroup : CommandGroup
private readonly IDiscordRestUserAPI _userApi;
private readonly Utility _utility;
- public KickCommandGroup(
- ICommandContext context, IDiscordRestChannelAPI channelApi, GuildDataService guildData,
- IFeedbackService feedback, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi,
- Utility utility)
+ public KickCommandGroup(AccessControlService access, IDiscordRestChannelAPI channelApi, ICommandContext context,
+ IFeedbackService feedback, IDiscordRestGuildAPI guildApi, GuildDataService guildData,
+ IDiscordRestUserAPI userApi, Utility utility)
{
- _context = context;
+ _access = access;
_channelApi = channelApi;
- _guildData = guildData;
+ _context = context;
_feedback = feedback;
_guildApi = guildApi;
+ _guildData = guildData;
_userApi = userApi;
_utility = utility;
}
@@ -59,10 +60,10 @@ public class KickCommandGroup : CommandGroup
/// was kicked and vice-versa.
///
[Command("kick", "кик")]
- [DiscordDefaultMemberPermissions(DiscordPermission.KickMembers)]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
[DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
- [RequireDiscordPermission(DiscordPermission.KickMembers)]
+ [RequireDiscordPermission(DiscordPermission.ManageMessages)]
[RequireBotDiscordPermissions(DiscordPermission.KickMembers)]
[Description("Kick member")]
[UsedImplicitly]
@@ -115,7 +116,7 @@ public class KickCommandGroup : CommandGroup
CancellationToken ct = default)
{
var interactionResult
- = await _utility.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct);
+ = await _access.CheckInteractionsAsync(guild.ID, executor.ID, target.ID, "Kick", ct);
if (!interactionResult.IsSuccess)
{
return ResultExtensions.FromError(interactionResult);
@@ -134,7 +135,8 @@ public class KickCommandGroup : CommandGroup
{
var dmEmbed = new EmbedBuilder().WithGuildTitle(guild)
.WithTitle(Messages.YouWereKicked)
- .WithDescription(MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason)))
+ .WithDescription(
+ MarkdownExtensions.BulletPoint(string.Format(Messages.DescriptionActionReason, reason)))
.WithActionFooter(executor)
.WithCurrentTimestamp()
.WithColour(ColorsList.Red)
diff --git a/src/Commands/MuteCommandGroup.cs b/src/Commands/MuteCommandGroup.cs
index 8e79830..ce0a296 100644
--- a/src/Commands/MuteCommandGroup.cs
+++ b/src/Commands/MuteCommandGroup.cs
@@ -28,6 +28,7 @@ namespace Octobot.Commands;
[UsedImplicitly]
public class MuteCommandGroup : CommandGroup
{
+ private readonly AccessControlService _access;
private readonly ICommandContext _context;
private readonly IFeedbackService _feedback;
private readonly IDiscordRestGuildAPI _guildApi;
@@ -35,14 +36,14 @@ public class MuteCommandGroup : CommandGroup
private readonly IDiscordRestUserAPI _userApi;
private readonly Utility _utility;
- public MuteCommandGroup(
- ICommandContext context, GuildDataService guildData, IFeedbackService feedback,
- IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi, Utility utility)
+ public MuteCommandGroup(AccessControlService access, ICommandContext context, IFeedbackService feedback,
+ IDiscordRestGuildAPI guildApi, GuildDataService guildData, IDiscordRestUserAPI userApi, Utility utility)
{
+ _access = access;
_context = context;
- _guildData = guildData;
_feedback = feedback;
_guildApi = guildApi;
+ _guildData = guildData;
_userApi = userApi;
_utility = utility;
}
@@ -62,10 +63,10 @@ public class MuteCommandGroup : CommandGroup
///
///
[Command("mute", "мут")]
- [DiscordDefaultMemberPermissions(DiscordPermission.ModerateMembers)]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
[DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
- [RequireDiscordPermission(DiscordPermission.ModerateMembers)]
+ [RequireDiscordPermission(DiscordPermission.ManageMessages)]
[RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)]
[Description("Mute member")]
[UsedImplicitly]
@@ -127,7 +128,7 @@ public class MuteCommandGroup : CommandGroup
Snowflake channelId, IUser bot, CancellationToken ct = default)
{
var interactionResult
- = await _utility.CheckInteractionsAsync(
+ = await _access.CheckInteractionsAsync(
guildId, executor.ID, target.ID, "Mute", ct);
if (!interactionResult.IsSuccess)
{
@@ -239,10 +240,10 @@ public class MuteCommandGroup : CommandGroup
///
///
[Command("unmute", "размут")]
- [DiscordDefaultMemberPermissions(DiscordPermission.ModerateMembers)]
+ [DiscordDefaultMemberPermissions(DiscordPermission.ManageMessages)]
[DiscordDefaultDMPermission(false)]
[RequireContext(ChannelContext.Guild)]
- [RequireDiscordPermission(DiscordPermission.ModerateMembers)]
+ [RequireDiscordPermission(DiscordPermission.ManageMessages)]
[RequireBotDiscordPermissions(DiscordPermission.ModerateMembers)]
[Description("Unmute member")]
[UsedImplicitly]
@@ -290,7 +291,7 @@ public class MuteCommandGroup : CommandGroup
IUser bot, CancellationToken ct = default)
{
var interactionResult
- = await _utility.CheckInteractionsAsync(
+ = await _access.CheckInteractionsAsync(
guildId, executor.ID, target.ID, "Unmute", ct);
if (!interactionResult.IsSuccess)
{
diff --git a/src/Commands/SettingsCommandGroup.cs b/src/Commands/SettingsCommandGroup.cs
index f756e93..a39e9c7 100644
--- a/src/Commands/SettingsCommandGroup.cs
+++ b/src/Commands/SettingsCommandGroup.cs
@@ -51,6 +51,7 @@ public class SettingsCommandGroup : CommandGroup
GuildSettings.EventNotificationChannel,
GuildSettings.DefaultRole,
GuildSettings.MuteRole,
+ GuildSettings.ModeratorRole,
GuildSettings.EventNotificationRole,
GuildSettings.EventEarlyNotificationOffset
];
diff --git a/src/Data/GuildSettings.cs b/src/Data/GuildSettings.cs
index 518465b..a1d8d74 100644
--- a/src/Data/GuildSettings.cs
+++ b/src/Data/GuildSettings.cs
@@ -76,6 +76,7 @@ public static class GuildSettings
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 ModeratorRole = new("ModeratorRole");
public static readonly SnowflakeOption EventNotificationRole = new("EventNotificationRole");
///
diff --git a/src/Data/Options/AllOptionsEnum.cs b/src/Data/Options/AllOptionsEnum.cs
index 6932822..d9e0c13 100644
--- a/src/Data/Options/AllOptionsEnum.cs
+++ b/src/Data/Options/AllOptionsEnum.cs
@@ -26,6 +26,7 @@ public enum AllOptionsEnum
[UsedImplicitly] EventNotificationChannel,
[UsedImplicitly] DefaultRole,
[UsedImplicitly] MuteRole,
+ [UsedImplicitly] ModeratorRole,
[UsedImplicitly] EventNotificationRole,
[UsedImplicitly] EventEarlyNotificationOffset
}
diff --git a/src/Octobot.cs b/src/Octobot.cs
index a4871f4..065967e 100644
--- a/src/Octobot.cs
+++ b/src/Octobot.cs
@@ -88,8 +88,9 @@ public sealed class Octobot
.AddPreparationErrorEvent()
.AddPostExecutionEvent()
// Services
- .AddSingleton()
+ .AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddHostedService(provider => provider.GetRequiredService())
.AddHostedService()
.AddHostedService()
diff --git a/src/Services/AccessControlService.cs b/src/Services/AccessControlService.cs
new file mode 100644
index 0000000..84667c3
--- /dev/null
+++ b/src/Services/AccessControlService.cs
@@ -0,0 +1,176 @@
+using Octobot.Data;
+using Octobot.Extensions;
+using Remora.Discord.API.Abstractions.Objects;
+using Remora.Discord.API.Abstractions.Rest;
+using Remora.Discord.Commands.Conditions;
+using Remora.Discord.Commands.Results;
+using Remora.Rest.Core;
+using Remora.Results;
+
+namespace Octobot.Services;
+
+public sealed class AccessControlService
+{
+ private readonly GuildDataService _data;
+ private readonly IDiscordRestGuildAPI _guildApi;
+ private readonly RequireDiscordPermissionCondition _permission;
+ private readonly IDiscordRestUserAPI _userApi;
+
+ public AccessControlService(GuildDataService data, IDiscordRestGuildAPI guildApi, IDiscordRestUserAPI userApi,
+ RequireDiscordPermissionCondition permission)
+ {
+ _data = data;
+ _guildApi = guildApi;
+ _userApi = userApi;
+ _permission = permission;
+ }
+
+ private async Task> CheckPermissionAsync(GuildData data, Snowflake memberId, IGuildMember member,
+ DiscordPermission permission, CancellationToken ct = default)
+ {
+ var moderatorRole = GuildSettings.ModeratorRole.Get(data.Settings);
+ var result = await _permission.CheckAsync(new RequireDiscordPermissionAttribute([permission]), member, ct);
+
+ if (result.Error is not null and not PermissionDeniedError)
+ {
+ return Result.FromError(result);
+ }
+
+ var hasPermission = result.IsSuccess;
+ return hasPermission || (!moderatorRole.Empty() &&
+ data.GetOrCreateMemberData(memberId).Roles.Contains(moderatorRole.Value));
+ }
+
+ ///
+ /// Checks whether or not a member can interact with another member
+ ///
+ /// The ID of the guild in which an operation is being performed.
+ /// The executor of the operation.
+ /// The target of the operation.
+ /// The operation.
+ /// The cancellation token for this operation.
+ ///
+ ///
+ /// - A result which has succeeded with a null string if the member can interact with the target.
+ /// -
+ /// A result which has succeeded with a non-null string containing the error message if the member cannot
+ /// interact with the target.
+ ///
+ /// - A result which has failed if an error occurred during the execution of this method.
+ ///
+ ///
+ public async Task> CheckInteractionsAsync(
+ Snowflake guildId, Snowflake? interacterId, Snowflake targetId, string action, CancellationToken ct = default)
+ {
+ if (interacterId == targetId)
+ {
+ return Result.FromSuccess($"UserCannot{action}Themselves".Localized());
+ }
+
+ var botResult = await _userApi.GetCurrentUserAsync(ct);
+ if (!botResult.IsDefined(out var bot))
+ {
+ return Result.FromError(botResult);
+ }
+
+ var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct);
+ if (!guildResult.IsDefined(out var guild))
+ {
+ return Result.FromError(guildResult);
+ }
+
+ var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct);
+ if (!targetMemberResult.IsDefined(out var targetMember))
+ {
+ return Result.FromSuccess(null);
+ }
+
+ var currentMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct);
+ if (!currentMemberResult.IsDefined(out var currentMember))
+ {
+ return Result.FromError(currentMemberResult);
+ }
+
+ var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct);
+ if (!rolesResult.IsDefined(out var roles))
+ {
+ return Result.FromError(rolesResult);
+ }
+
+ if (interacterId is null)
+ {
+ return CheckInteractions(action, guild, roles, targetMember, currentMember, currentMember);
+ }
+
+ var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct);
+ if (!interacterResult.IsDefined(out var interacter))
+ {
+ return Result.FromError(interacterResult);
+ }
+
+ var data = await _data.GetData(guildId, ct);
+
+ var permissionResult = await CheckPermissionAsync(data, interacterId.Value, interacter,
+ action switch
+ {
+ "Ban" => DiscordPermission.BanMembers,
+ "Kick" => DiscordPermission.KickMembers,
+ "Mute" or "Unmute" => DiscordPermission.ModerateMembers,
+ _ => throw new Exception()
+ }, ct);
+ if (!permissionResult.IsDefined(out var hasPermission))
+ {
+ return Result.FromError(permissionResult);
+ }
+
+ return hasPermission
+ ? CheckInteractions(action, guild, roles, targetMember, currentMember, interacter)
+ : Result.FromSuccess($"UserCannot{action}Members".Localized());
+ }
+
+ private static Result CheckInteractions(
+ string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember currentMember,
+ IGuildMember interacter)
+ {
+ if (!targetMember.User.IsDefined(out var targetUser))
+ {
+ return new ArgumentNullError(nameof(targetMember.User));
+ }
+
+ if (!interacter.User.IsDefined(out var interacterUser))
+ {
+ return new ArgumentNullError(nameof(interacter.User));
+ }
+
+ if (currentMember.User == targetMember.User)
+ {
+ return Result.FromSuccess($"UserCannot{action}Bot".Localized());
+ }
+
+ if (targetUser.ID == guild.OwnerID)
+ {
+ return Result.FromSuccess($"UserCannot{action}Owner".Localized());
+ }
+
+ var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList();
+ var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID));
+
+ var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position);
+ if (targetBotRoleDiff >= 0)
+ {
+ return Result.FromSuccess($"BotCannot{action}Target".Localized());
+ }
+
+ if (interacterUser.ID == guild.OwnerID)
+ {
+ return Result.FromSuccess(null);
+ }
+
+ var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID));
+ var targetInteracterRoleDiff
+ = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position);
+ return targetInteracterRoleDiff < 0
+ ? Result.FromSuccess(null)
+ : Result.FromSuccess($"UserCannot{action}Target".Localized());
+ }
+}
diff --git a/src/Services/Update/MemberUpdateService.cs b/src/Services/Update/MemberUpdateService.cs
index 45d0476..e177fca 100644
--- a/src/Services/Update/MemberUpdateService.cs
+++ b/src/Services/Update/MemberUpdateService.cs
@@ -26,20 +26,20 @@ public sealed partial class MemberUpdateService : BackgroundService
"Torus", "Violet", "Vortex", "Vulture", "Wagon", "Whale", "Woodpecker", "Zebra", "Zigzag"
];
+ private readonly AccessControlService _access;
private readonly IDiscordRestChannelAPI _channelApi;
private readonly IDiscordRestGuildAPI _guildApi;
private readonly GuildDataService _guildData;
private readonly ILogger _logger;
- private readonly Utility _utility;
- public MemberUpdateService(IDiscordRestChannelAPI channelApi, IDiscordRestGuildAPI guildApi,
- GuildDataService guildData, ILogger logger, Utility utility)
+ public MemberUpdateService(AccessControlService access, IDiscordRestChannelAPI channelApi,
+ IDiscordRestGuildAPI guildApi, GuildDataService guildData, ILogger logger)
{
+ _access = access;
_channelApi = channelApi;
_guildApi = guildApi;
_guildData = guildData;
_logger = logger;
- _utility = utility;
}
protected override async Task ExecuteAsync(CancellationToken ct)
@@ -94,7 +94,7 @@ public sealed partial class MemberUpdateService : BackgroundService
}
var interactionResult
- = await _utility.CheckInteractionsAsync(guildId, null, id, "Update", ct);
+ = await _access.CheckInteractionsAsync(guildId, null, id, "Update", ct);
if (!interactionResult.IsSuccess)
{
return ResultExtensions.FromError(interactionResult);
diff --git a/src/Services/Utility.cs b/src/Services/Utility.cs
index ad06315..3b9ab19 100644
--- a/src/Services/Utility.cs
+++ b/src/Services/Utility.cs
@@ -21,129 +21,13 @@ public sealed class Utility
private readonly IDiscordRestChannelAPI _channelApi;
private readonly IDiscordRestGuildScheduledEventAPI _eventApi;
private readonly IDiscordRestGuildAPI _guildApi;
- private readonly IDiscordRestUserAPI _userApi;
public Utility(
- IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi,
- IDiscordRestUserAPI userApi)
+ IDiscordRestChannelAPI channelApi, IDiscordRestGuildScheduledEventAPI eventApi, IDiscordRestGuildAPI guildApi)
{
_channelApi = channelApi;
_eventApi = eventApi;
_guildApi = guildApi;
- _userApi = userApi;
- }
-
- ///
- /// Checks whether or not a member can interact with another member
- ///
- /// The ID of the guild in which an operation is being performed.
- /// The executor of the operation.
- /// The target of the operation.
- /// The operation.
- /// The cancellation token for this operation.
- ///
- ///
- /// - A result which has succeeded with a null string if the member can interact with the target.
- /// -
- /// A result which has succeeded with a non-null string containing the error message if the member cannot
- /// interact with the target.
- ///
- /// - A result which has failed if an error occurred during the execution of this method.
- ///
- ///
- public async Task> CheckInteractionsAsync(
- Snowflake guildId, Snowflake? interacterId, Snowflake targetId, string action, CancellationToken ct = default)
- {
- if (interacterId == targetId)
- {
- return Result.FromSuccess($"UserCannot{action}Themselves".Localized());
- }
-
- var botResult = await _userApi.GetCurrentUserAsync(ct);
- if (!botResult.IsDefined(out var bot))
- {
- return Result.FromError(botResult);
- }
-
- var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct);
- if (!guildResult.IsDefined(out var guild))
- {
- return Result.FromError(guildResult);
- }
-
- var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct);
- if (!targetMemberResult.IsDefined(out var targetMember))
- {
- return Result.FromSuccess(null);
- }
-
- var currentMemberResult = await _guildApi.GetGuildMemberAsync(guildId, bot.ID, ct);
- if (!currentMemberResult.IsDefined(out var currentMember))
- {
- return Result.FromError(currentMemberResult);
- }
-
- var rolesResult = await _guildApi.GetGuildRolesAsync(guildId, ct);
- if (!rolesResult.IsDefined(out var roles))
- {
- return Result.FromError(rolesResult);
- }
-
- if (interacterId is null)
- {
- return CheckInteractions(action, guild, roles, targetMember, currentMember, currentMember);
- }
-
- var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId.Value, ct);
- return interacterResult.IsDefined(out var interacter)
- ? CheckInteractions(action, guild, roles, targetMember, currentMember, interacter)
- : Result.FromError(interacterResult);
- }
-
- private static Result CheckInteractions(
- string action, IGuild guild, IReadOnlyList roles, IGuildMember targetMember, IGuildMember currentMember,
- IGuildMember interacter)
- {
- if (!targetMember.User.IsDefined(out var targetUser))
- {
- return new ArgumentNullError(nameof(targetMember.User));
- }
-
- if (!interacter.User.IsDefined(out var interacterUser))
- {
- return new ArgumentNullError(nameof(interacter.User));
- }
-
- if (currentMember.User == targetMember.User)
- {
- return Result.FromSuccess($"UserCannot{action}Bot".Localized());
- }
-
- if (targetUser.ID == guild.OwnerID)
- {
- return Result.FromSuccess($"UserCannot{action}Owner".Localized());
- }
-
- var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList();
- var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID));
-
- var targetBotRoleDiff = targetRoles.MaxOrDefault(r => r.Position) - botRoles.MaxOrDefault(r => r.Position);
- if (targetBotRoleDiff >= 0)
- {
- return Result.FromSuccess($"BotCannot{action}Target".Localized());
- }
-
- if (interacterUser.ID == guild.OwnerID)
- {
- return Result.FromSuccess(null);
- }
-
- var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID));
- var targetInteracterRoleDiff
- = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position);
- return targetInteracterRoleDiff < 0
- ? Result.FromSuccess(null)
- : Result.FromSuccess($"UserCannot{action}Target".Localized());
}
///