1
0
Fork 1
mirror of https://github.com/TeamOctolings/Octobot.git synced 2025-04-20 00:43:36 +03:00

Merge branch 'master' into 40-rename-users-who-attempt-to-hoist-themselves

This commit is contained in:
Macintxsh 2023-07-20 11:17:16 +03:00 committed by GitHub
commit 306c1702ba
Signed by: GitHub
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 66 deletions

View file

@ -31,5 +31,5 @@ jobs:
uses: muno92/resharper_inspectcode@1.7.1 uses: muno92/resharper_inspectcode@1.7.1
with: with:
solutionPath: ./Boyfriend.sln solutionPath: ./Boyfriend.sln
ignoreIssueType: InvertIf, ConvertIfStatementToReturnStatement, ConvertIfStatementToSwitchStatement ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement
solutionWideAnalysis: true solutionWideAnalysis: true

View file

@ -5,7 +5,6 @@
![GitHub License](https://img.shields.io/github/license/TeamOctolings/Boyfriend) ![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) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/TeamOctolings/Boyfriend/.github/workflows/resharper.yml?branch=master)
![GitHub last commit](https://img.shields.io/github/last-commit/TeamOctolings/Boyfriend) ![GitHub last commit](https://img.shields.io/github/last-commit/TeamOctolings/Boyfriend)
![CodeFactor](https://img.shields.io/codefactor/grade/github/TeamOctolings/Boyfriend)
Beep! I'm a general-purpose bot for moderation written by [@Octol1ttle](https://github.com/Octol1ttle) in C# and Beep! I'm a general-purpose bot for moderation written by [@Octol1ttle](https://github.com/Octol1ttle) in C# and
Remora.Discord Remora.Discord

View file

@ -152,7 +152,9 @@ public class BanCommandGroup : CommandGroup {
title, target) title, target)
.WithColour(ColorsList.Green).Build(); .WithColour(ColorsList.Green).Build();
_utility.LogActionAsync(cfg, channelId, title, target, description, user, CancellationToken); var logResult = _utility.LogActionAsync(cfg, channelId, user, title, description, target, CancellationToken);
if (!logResult.IsSuccess)
return Result.FromError(logResult.Error);
return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken); return await _feedbackService.SendContextualEmbedResultAsync(embed, CancellationToken);
} }
@ -222,7 +224,7 @@ public class BanCommandGroup : CommandGroup {
var title = string.Format(Messages.UserUnbanned, target.GetTag()); var title = string.Format(Messages.UserUnbanned, target.GetTag());
var description = string.Format(Messages.DescriptionActionReason, reason); var description = string.Format(Messages.DescriptionActionReason, reason);
var logResult = _utility.LogActionAsync(cfg, channelId, title, target, description, user, CancellationToken); var logResult = _utility.LogActionAsync(cfg, channelId, user, title, description, target, CancellationToken);
if (!logResult.IsSuccess) if (!logResult.IsSuccess)
return Result.FromError(logResult.Error); return Result.FromError(logResult.Error);

View file

@ -28,15 +28,17 @@ public class ClearCommandGroup : CommandGroup {
private readonly GuildDataService _dataService; private readonly GuildDataService _dataService;
private readonly FeedbackService _feedbackService; private readonly FeedbackService _feedbackService;
private readonly IDiscordRestUserAPI _userApi; private readonly IDiscordRestUserAPI _userApi;
private readonly UtilityService _utility;
public ClearCommandGroup( public ClearCommandGroup(
IDiscordRestChannelAPI channelApi, ICommandContext context, GuildDataService dataService, IDiscordRestChannelAPI channelApi, ICommandContext context, GuildDataService dataService,
FeedbackService feedbackService, IDiscordRestUserAPI userApi) { FeedbackService feedbackService, IDiscordRestUserAPI userApi, UtilityService utility) {
_channelApi = channelApi; _channelApi = channelApi;
_context = context; _context = context;
_dataService = dataService; _dataService = dataService;
_feedbackService = feedbackService; _feedbackService = feedbackService;
_userApi = userApi; _userApi = userApi;
_utility = utility;
} }
/// <summary> /// <summary>
@ -55,7 +57,7 @@ public class ClearCommandGroup : CommandGroup {
[RequireBotDiscordPermissions(DiscordPermission.ManageMessages)] [RequireBotDiscordPermissions(DiscordPermission.ManageMessages)]
[Description("Remove multiple messages")] [Description("Remove multiple messages")]
[UsedImplicitly] [UsedImplicitly]
public async Task<Result> ClearMessagesAsync( public async Task<Result> ExecuteClear(
[Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)] [Description("Number of messages to remove (2-100)")] [MinValue(2)] [MaxValue(100)]
int amount) { int amount) {
if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId)) if (!_context.TryGetContextIDs(out var guildId, out var channelId, out var userId))
@ -66,12 +68,25 @@ public class ClearCommandGroup : CommandGroup {
channelId.Value, limit: amount + 1, ct: CancellationToken); channelId.Value, limit: amount + 1, ct: CancellationToken);
if (!messagesResult.IsDefined(out var messages)) if (!messagesResult.IsDefined(out var messages))
return Result.FromError(messagesResult); return Result.FromError(messagesResult);
var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken);
if (!userResult.IsDefined(out var user))
return Result.FromError(userResult);
// The current user's avatar is used when sending messages
var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken);
if (!currentUserResult.IsDefined(out var currentUser))
return Result.FromError(currentUserResult);
var cfg = await _dataService.GetSettings(guildId.Value, CancellationToken); return await ClearMessagesAsync(amount, guildId.Value, channelId.Value, messages, user, currentUser);
}
private async Task<Result> ClearMessagesAsync(
int amount, Snowflake guildId, Snowflake channelId, IReadOnlyList<IMessage> messages,
IUser user, IUser currentUser) {
var cfg = await _dataService.GetSettings(guildId, CancellationToken);
Messages.Culture = GuildSettings.Language.Get(cfg); 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)).AppendLine();
for (var i = messages.Count - 1; i >= 1; i--) { // '>= 1' to skip last message ('Boyfriend is thinking...') for (var i = messages.Count - 1; i >= 1; i--) { // '>= 1' to skip last message ('Boyfriend is thinking...')
var message = messages[i]; var message = messages[i];
idList.Add(message.ID); idList.Add(message.ID);
@ -79,41 +94,18 @@ public class ClearCommandGroup : CommandGroup {
builder.Append(message.Content.InBlockCode()); builder.Append(message.Content.InBlockCode());
} }
var title = string.Format(Messages.MessagesCleared, amount.ToString());
var description = builder.ToString(); var description = builder.ToString();
var userResult = await _userApi.GetUserAsync(userId.Value, CancellationToken);
if (!userResult.IsDefined(out var user))
return Result.FromError(userResult);
var deleteResult = await _channelApi.BulkDeleteMessagesAsync( var deleteResult = await _channelApi.BulkDeleteMessagesAsync(
channelId.Value, idList, user.GetTag().EncodeHeader(), CancellationToken); channelId, idList, user.GetTag().EncodeHeader(), CancellationToken);
if (!deleteResult.IsSuccess) if (!deleteResult.IsSuccess)
return Result.FromError(deleteResult.Error); return Result.FromError(deleteResult.Error);
// The current user's avatar is used when sending messages var logResult = _utility.LogActionAsync(
var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); cfg, channelId, user, title, description, currentUser, CancellationToken);
if (!currentUserResult.IsDefined(out var currentUser)) if (!logResult.IsSuccess)
return Result.FromError(currentUserResult); return Result.FromError(logResult.Error);
var title = string.Format(Messages.MessagesCleared, amount.ToString());
if (!GuildSettings.PrivateFeedbackChannel.Get(cfg).Empty()
&& GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value) {
var logEmbed = new EmbedBuilder().WithSmallTitle(title, currentUser)
.WithDescription(description)
.WithActionFooter(user)
.WithCurrentTimestamp()
.WithColour(ColorsList.Red)
.Build();
if (!logEmbed.IsDefined(out var logBuilt))
return Result.FromError(logEmbed);
// Not awaiting to reduce response time
if (GuildSettings.PrivateFeedbackChannel.Get(cfg) != channelId.Value)
_ = _channelApi.CreateMessageAsync(
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { logBuilt },
ct: CancellationToken);
}
var embed = new EmbedBuilder().WithSmallTitle(title, currentUser) var embed = new EmbedBuilder().WithSmallTitle(title, currentUser)
.WithColour(ColorsList.Green).Build(); .WithColour(ColorsList.Green).Build();

View file

@ -27,9 +27,8 @@ public class LanguageOption : Option<CultureInfo> {
/// <inheritdoc /> /// <inheritdoc />
public override Result Set(JsonNode settings, string from) { public override Result Set(JsonNode settings, string from) {
if (!CultureInfoCache.ContainsKey(from.ToLowerInvariant())) return CultureInfoCache.ContainsKey(from.ToLowerInvariant())
return Result.FromError(new ArgumentInvalidError(nameof(from), Messages.LanguageNotSupported)); ? base.Set(settings, from.ToLowerInvariant())
: Result.FromError(new ArgumentInvalidError(nameof(from), Messages.LanguageNotSupported));
return base.Set(settings, from.ToLowerInvariant());
} }
} }

View file

@ -46,15 +46,13 @@ public class MessageDeletedResponder : IResponder<IMessageDelete> {
if (!auditLogResult.IsDefined(out var auditLogPage)) return Result.FromError(auditLogResult); if (!auditLogResult.IsDefined(out var auditLogPage)) return Result.FromError(auditLogResult);
var auditLog = auditLogPage.AuditLogEntries.Single(); var auditLog = auditLogPage.AuditLogEntries.Single();
if (!auditLog.Options.IsDefined(out var options))
return Result.FromError(new ArgumentNullError(nameof(auditLog.Options)));
var user = message.Author; var userResult = Result<IUser>.FromSuccess(message.Author);
if (options.ChannelID == gatewayEvent.ChannelID if (auditLog.Options.Value.ChannelID == gatewayEvent.ChannelID
&& DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2) { && DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2)
var userResult = await _userApi.GetUserAsync(auditLog.UserID!.Value, ct); userResult = await _userApi.GetUserAsync(auditLog.UserID!.Value, ct);
if (!userResult.IsDefined(out user)) return Result.FromError(userResult);
} if (!userResult.IsDefined(out var user)) return Result.FromError(userResult);
Messages.Culture = GuildSettings.Language.Get(cfg); Messages.Culture = GuildSettings.Language.Get(cfg);

View file

@ -64,13 +64,10 @@ public class UtilityService : IHostedService {
var currentUserResult = await _userApi.GetCurrentUserAsync(ct); var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
if (!currentUserResult.IsDefined(out var currentUser)) if (!currentUserResult.IsDefined(out var currentUser))
return Result<string?>.FromError(currentUserResult); return Result<string?>.FromError(currentUserResult);
if (currentUser.ID == targetId)
return Result<string?>.FromSuccess($"UserCannot{action}Bot".Localized());
var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct); var guildResult = await _guildApi.GetGuildAsync(guildId, ct: ct);
if (!guildResult.IsDefined(out var guild)) if (!guildResult.IsDefined(out var guild))
return Result<string?>.FromError(guildResult); return Result<string?>.FromError(guildResult);
if (targetId == guild.OwnerID) return Result<string?>.FromSuccess($"UserCannot{action}Owner".Localized());
var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct); var targetMemberResult = await _guildApi.GetGuildMemberAsync(guildId, targetId, ct);
if (!targetMemberResult.IsDefined(out var targetMember)) if (!targetMemberResult.IsDefined(out var targetMember))
@ -84,6 +81,25 @@ public class UtilityService : IHostedService {
if (!rolesResult.IsDefined(out var roles)) if (!rolesResult.IsDefined(out var roles))
return Result<string?>.FromError(rolesResult); return Result<string?>.FromError(rolesResult);
var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId, ct);
return interacterResult.IsDefined(out var interacter)
? CheckInteractions(action, guild, roles, targetMember, currentMember, interacter)
: Result<string?>.FromError(interacterResult);
}
private static Result<string?> CheckInteractions(
string action, IGuild guild, IReadOnlyList<IRole> roles, IGuildMember targetMember, IGuildMember currentMember,
IGuildMember interacter) {
if (!targetMember.User.IsDefined(out var targetUser))
return Result<string?>.FromError(new ArgumentNullError(nameof(targetMember.User)));
if (!interacter.User.IsDefined(out var interacterUser))
return Result<string?>.FromError(new ArgumentNullError(nameof(interacter.User)));
if (currentMember.User == targetMember.User)
return Result<string?>.FromSuccess($"UserCannot{action}Bot".Localized());
if (targetUser.ID == guild.OwnerID) return Result<string?>.FromSuccess($"UserCannot{action}Owner".Localized());
var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList(); var targetRoles = roles.Where(r => targetMember.Roles.Contains(r.ID)).ToList();
var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID)); var botRoles = roles.Where(r => currentMember.Roles.Contains(r.ID));
@ -91,20 +107,15 @@ public class UtilityService : IHostedService {
if (targetBotRoleDiff >= 0) if (targetBotRoleDiff >= 0)
return Result<string?>.FromSuccess($"BotCannot{action}Target".Localized()); return Result<string?>.FromSuccess($"BotCannot{action}Target".Localized());
if (interacterId == guild.OwnerID) if (interacterUser.ID == guild.OwnerID)
return Result<string?>.FromSuccess(null); return Result<string?>.FromSuccess(null);
var interacterResult = await _guildApi.GetGuildMemberAsync(guildId, interacterId, ct);
if (!interacterResult.IsDefined(out var interacter))
return Result<string?>.FromError(interacterResult);
var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID)); var interacterRoles = roles.Where(r => interacter.Roles.Contains(r.ID));
var targetInteracterRoleDiff var targetInteracterRoleDiff
= targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position); = targetRoles.MaxOrDefault(r => r.Position) - interacterRoles.MaxOrDefault(r => r.Position);
if (targetInteracterRoleDiff >= 0) return targetInteracterRoleDiff < 0
return Result<string?>.FromSuccess($"UserCannot{action}Target".Localized()); ? Result<string?>.FromSuccess(null)
: Result<string?>.FromSuccess($"UserCannot{action}Target".Localized());
return Result<string?>.FromSuccess(null);
} }
/// <summary> /// <summary>
@ -143,15 +154,15 @@ public class UtilityService : IHostedService {
/// </summary> /// </summary>
/// <param name="cfg">The guild configuration.</param> /// <param name="cfg">The guild configuration.</param>
/// <param name="channelId">The ID of the channel where the action was executed.</param> /// <param name="channelId">The ID of the channel where the action was executed.</param>
/// <param name="title">The title for the embed.</param>
/// <param name="avatar">The user whose avatar will be displayed next to the <paramref name="title" /> of the embed.</param>
/// <param name="description">The description of the embed.</param>
/// <param name="user">The user who performed the action.</param> /// <param name="user">The user who performed the action.</param>
/// <param name="title">The title for the embed.</param>
/// <param name="description">The description of the embed.</param>
/// <param name="avatar">The user whose avatar will be displayed next to the <paramref name="title" /> of the embed.</param>
/// <param name="ct">The cancellation token for this operation.</param> /// <param name="ct">The cancellation token for this operation.</param>
/// <returns></returns> /// <returns></returns>
public Result LogActionAsync( public Result LogActionAsync(
JsonNode cfg, Snowflake channelId, string title, IUser avatar, string description, JsonNode cfg, Snowflake channelId, IUser user, string title, string description, IUser avatar,
IUser user, CancellationToken ct = default) { CancellationToken ct = default) {
var publicChannel = GuildSettings.PublicFeedbackChannel.Get(cfg); var publicChannel = GuildSettings.PublicFeedbackChannel.Get(cfg);
var privateChannel = GuildSettings.PrivateFeedbackChannel.Get(cfg); var privateChannel = GuildSettings.PrivateFeedbackChannel.Get(cfg);
if (GuildSettings.PublicFeedbackChannel.Get(cfg).EmptyOrEqualTo(channelId) if (GuildSettings.PublicFeedbackChannel.Get(cfg).EmptyOrEqualTo(channelId)