diff --git a/locale/Messages.resx b/locale/Messages.resx index 84d7fa7..7d6b9e0 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -600,4 +600,43 @@ All settings have been reset + + Display Name + + + Not present + + + Information about {0} + + + Muted + + + Discord user since + + + Mentioned as + + + Banned + + + Punishments + + + Banned permanently + + + Not on the server + + + Muted with a timeout + + + Muted with a mute role + + + Until + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index abeaf88..56722cf 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -600,4 +600,43 @@ Все настройки были сброшены + + Отображаемое имя + + + Отсутствует + + + Информация о {0} + + + Заглушен + + + Пользователь Discord с + + + Упоминается как + + + Забанен + + + Наказания + + + Забанен навсегда + + + Не на сервере + + + Заглушен с помощью тайм-аута + + + Заглушен с помощью мут роли + + + До + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index fb01e72..5bce0c6 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -600,4 +600,43 @@ откатываемся к заводским... + + дисплейнейм + + + нету такого + + + деанон {0} + + + замучен + + + юзер Discord со времен + + + пинается как + + + забанен + + + приколы полученные по заслугам + + + забанен + + + вышел из сервера + + + замучен таймаутом + + + замучен ролькой + + + до + diff --git a/src/Boyfriend.cs b/src/Boyfriend.cs index 246869e..4ba43f8 100644 --- a/src/Boyfriend.cs +++ b/src/Boyfriend.cs @@ -102,7 +102,8 @@ public sealed class Boyfriend .WithCommandGroup() .WithCommandGroup() .WithCommandGroup() - .WithCommandGroup(); + .WithCommandGroup() + .WithCommandGroup(); var responderTypes = typeof(Boyfriend).Assembly .GetExportedTypes() .Where(t => t.IsResponder()); diff --git a/src/Commands/ToolsCommandGroup.cs b/src/Commands/ToolsCommandGroup.cs new file mode 100644 index 0000000..217528e --- /dev/null +++ b/src/Commands/ToolsCommandGroup.cs @@ -0,0 +1,175 @@ +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.Commands.Attributes; +using Remora.Discord.Commands.Contexts; +using Remora.Discord.Commands.Feedback.Services; +using Remora.Discord.Extensions.Embeds; +using Remora.Discord.Extensions.Formatting; +using Remora.Rest.Core; +using Remora.Results; + +namespace Boyfriend.Commands; + +[UsedImplicitly] +public class ToolsCommandGroup : CommandGroup +{ + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly GuildDataService _guildData; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly IDiscordRestUserAPI _userApi; + + public ToolsCommandGroup( + ICommandContext context, FeedbackService feedback, + GuildDataService guildData, IDiscordRestGuildAPI guildApi, + IDiscordRestUserAPI userApi, IDiscordRestChannelAPI channelApi) + { + _context = context; + _guildData = guildData; + _feedback = feedback; + _guildApi = guildApi; + _userApi = userApi; + } + + [Command("showinfo")] + [DiscordDefaultDMPermission(false)] + [Description("Shows info about user")] + [UsedImplicitly] + public async Task ExecuteShowInfoAsync( + [Description("Specific user or ID to show info about")] + IUser? target = null) + { + if (!_context.TryGetContextIDs(out var guildId, out _, out var userId)) + { + return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + + var userResult = await _userApi.GetUserAsync(userId, CancellationToken); + if (!userResult.IsDefined(out var user)) + { + return Result.FromError(userResult); + } + + var currentUserResult = await _userApi.GetCurrentUserAsync(CancellationToken); + if (!currentUserResult.IsDefined(out var currentUser)) + { + return Result.FromError(currentUserResult); + } + + var data = await _guildData.GetData(guildId, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + if (target is not null) + { + return await ShowUserInfoAsync(target, currentUser, data, guildId, CancellationToken); + } + + return await ShowUserInfoAsync(user, currentUser, data, guildId, CancellationToken); + } + + private async Task ShowUserInfoAsync( + IUser user, IUser currentUser, GuildData data, Snowflake guildId, CancellationToken ct = default) + { + var embedColor = ColorsList.Cyan; + + var memberData = data.GetOrCreateMemberData(user.ID); + + var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, user.ID, ct); + guildMemberResult.IsDefined(out var guildMember); + + var isMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) || + (guildMember is not null && guildMember.CommunicationDisabledUntil.IsDefined()); + + var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, user.ID, ct); + + var builder = new StringBuilder().AppendLine($"### <@{user.ID}>"); + + if (user.GlobalName is not null) + { + builder.Append("- ").AppendLine(Messages.ShowInfoDisplayName) + .Append(" - ").AppendLine(Markdown.Sanitize(user.GlobalName)); + } + + builder.Append("- ").AppendLine(Messages.ShowInfoDiscordUserSince) + .Append(" - ").AppendLine(Markdown.Timestamp(user.ID.Timestamp)); + + if (isMuted || existingBanResult.IsDefined()) + { + builder.Append("### ") + .AppendLine(Markdown.Bold(Messages.ShowInfoPunishments)); + } + + if (isMuted) + { + ShowInfoMutedUntilAsync(memberData, guildMember, builder); + + embedColor = ColorsList.Red; + } + + if (existingBanResult.IsDefined()) + { + ShowInfoBannedUntilAsync(memberData, builder); + + embedColor = ColorsList.Black; + } + + if (!guildMemberResult.IsSuccess && !existingBanResult.IsDefined()) + { + builder.Append("### ") + .AppendLine(Markdown.Bold(Messages.ShowInfoNotOnServer)); + + embedColor = ColorsList.Default; + } + + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.ShowInfoTitle, user.GetTag()), currentUser) + .WithDescription(builder.ToString()) + .WithColour(embedColor) + .WithLargeAvatar(user) + .WithFooter($"ID: {user.ID.ToString()}") + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct); + } + + private static void ShowInfoBannedUntilAsync(MemberData memberData, StringBuilder builder) + { + if (memberData.BannedUntil < DateTimeOffset.MaxValue) + { + builder.Append("- ").AppendLine(Messages.ShowInfoBanned) + .Append(" - ").Append(Messages.ShowInfoUntil).Append(' ') + .AppendLine(Markdown.Timestamp(memberData.BannedUntil.Value)); + } + + if (memberData.BannedUntil >= DateTimeOffset.MaxValue) + { + builder.Append("- ").AppendLine(Messages.ShowInfoBannedPermanently); + } + } + + private static void ShowInfoMutedUntilAsync( + MemberData memberData, IGuildMember? guildMember, StringBuilder builder) + { + builder.Append("- ").AppendLine(Messages.ShowInfoMuted); + if (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) + { + builder.Append(" - ").AppendLine(Messages.ShowInfoMutedWithMuteRole) + .Append(" - ").Append(Messages.ShowInfoUntil).Append(' ') + .AppendLine(Markdown.Timestamp(memberData.MutedUntil.Value)); + } + + if (guildMember is not null && guildMember.CommunicationDisabledUntil.IsDefined()) + { + builder.Append(" - ").AppendLine(Messages.ShowInfoMutedWithTimeout) + .Append(" - ").Append(Messages.ShowInfoUntil).Append(' ') + .AppendLine(Markdown.Timestamp(guildMember.CommunicationDisabledUntil.Value.Value)); + } + } +} diff --git a/src/Extensions.cs b/src/Extensions.cs index 9a05e80..1e30cf4 100644 --- a/src/Extensions.cs +++ b/src/Extensions.cs @@ -74,6 +74,23 @@ public static class Extensions return builder; } + /// + /// Adds a user avatar in the thumbnail field. + /// + /// The builder to add the thumbnail to. + /// The user whose avatar to use in the thumbnail field. + /// The builder with the added avatar in the thumbnail field. + public static EmbedBuilder WithLargeAvatar( + this EmbedBuilder builder, IUser avatarSource) + { + var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256); + var avatarUrl = avatarUrlResult.IsSuccess + ? avatarUrlResult.Entity + : CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity; + + return builder.WithThumbnailUrl(avatarUrl.AbsoluteUri); + } + /// /// Adds a footer representing that the action was performed in the . /// diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index b45ae70..29f4dc5 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1013,5 +1013,83 @@ namespace Boyfriend { return ResourceManager.GetString("AllSettingsReset", resourceCulture); } } + + internal static string NotPresent { + get { + return ResourceManager.GetString("NotPresent", resourceCulture); + } + } + + internal static string ShowInfoTitle { + get { + return ResourceManager.GetString("ShowInfoTitle", resourceCulture); + } + } + + internal static string ShowInfoMentionedAs { + get { + return ResourceManager.GetString("ShowInfoMentionedAs", resourceCulture); + } + } + + internal static string ShowInfoDisplayName { + get { + return ResourceManager.GetString("ShowInfoDisplayName", resourceCulture); + } + } + + internal static string ShowInfoDiscordUserSince { + get { + return ResourceManager.GetString("ShowInfoDiscordUserSince", resourceCulture); + } + } + + internal static string ShowInfoMuted { + get { + return ResourceManager.GetString("ShowInfoMuted", resourceCulture); + } + } + + internal static string ShowInfoBanned { + get { + return ResourceManager.GetString("ShowInfoBanned", resourceCulture); + } + } + + internal static string ShowInfoPunishments { + get { + return ResourceManager.GetString("ShowInfoPunishments", resourceCulture); + } + } + + internal static string ShowInfoBannedPermanently { + get { + return ResourceManager.GetString("ShowInfoBannedPermanently", resourceCulture); + } + } + + internal static string ShowInfoNotOnServer { + get { + return ResourceManager.GetString("ShowInfoNotOnServer", resourceCulture); + } + } + + internal static string ShowInfoMutedWithTimeout { + get { + return ResourceManager.GetString("ShowInfoMutedWithTimeout", resourceCulture); + } + } + + internal static string ShowInfoMutedWithMuteRole { + get { + return ResourceManager.GetString("ShowInfoMutedWithMuteRole", resourceCulture); + } + } + + internal static string ShowInfoUntil { + get { + return ResourceManager.GetString("ShowInfoUntil", resourceCulture); + } + } } }