diff --git a/locale/Messages.resx b/locale/Messages.resx index 84d7fa7..3d05722 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -600,4 +600,46 @@ All settings have been reset + + Display name + + + Information about {0} + + + Muted + + + Discord user since + + + Banned + + + Punishments + + + Banned permanently + + + Not in the guild + + + Muted by timeout + + + Muted by mute role + + + Guild member since + + + Nickname + + + Roles + + + Nitro booster since + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index abeaf88..3f20dd0 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -600,4 +600,46 @@ Все настройки были сброшены + + Отображаемое имя + + + Информация о {0} + + + Заглушен + + + Вступил в Discord + + + Забанен + + + Наказания + + + Забанен навсегда + + + Не на сервере + + + Заглушен с помощью тайм-аута + + + Заглушен с помощью роли мута + + + Вступил на сервер + + + Никнейм + + + Роли + + + Начал бустить сервер + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index fb01e72..a564e1c 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -600,4 +600,46 @@ откатываемся к заводским... + + дисплейнейм + + + деанон {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..3ac8b70 --- /dev/null +++ b/src/Commands/ToolsCommandGroup.cs @@ -0,0 +1,231 @@ +using System.ComponentModel; +using System.Drawing; +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; + +/// +/// Handles commands related to tools: /showinfo. +/// +[UsedImplicitly] +public class ToolsCommandGroup : CommandGroup +{ + private readonly ICommandContext _context; + private readonly FeedbackService _feedback; + private readonly IDiscordRestGuildAPI _guildApi; + private readonly GuildDataService _guildData; + 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; + } + + /// + /// A slash command that shows information about user. + /// + /// + /// Information in the output: + /// + /// Display name + /// Discord user since + /// Guild nickname + /// Guild member since + /// Nitro booster since + /// Guild roles + /// Active mute information + /// Active ban information + /// Is on guild status + /// + /// + /// The user to show info about. + /// + /// A feedback sending result which may or may not have succeeded. + /// + [Command("showinfo")] + [DiscordDefaultDMPermission(false)] + [Description("Shows info about user")] + [UsedImplicitly] + public async Task ExecuteShowInfoAsync( + [Description("User 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); + + return await ShowUserInfoAsync(target ?? user, currentUser, data, guildId, CancellationToken); + } + + private async Task ShowUserInfoAsync( + IUser user, IUser currentUser, GuildData data, Snowflake guildId, CancellationToken ct = default) + { + var builder = new StringBuilder().AppendLine($"### <@{user.ID}>"); + + if (user.GlobalName is not null) + { + builder.Append("- ").AppendLine(Messages.ShowInfoDisplayName) + .AppendLine(Markdown.InlineCode(user.GlobalName)); + } + + builder.Append("- ").AppendLine(Messages.ShowInfoDiscordUserSince) + .AppendLine(Markdown.Timestamp(user.ID.Timestamp)); + + var memberData = data.GetOrCreateMemberData(user.ID); + + var embedColor = ColorsList.Cyan; + + var guildMemberResult = await _guildApi.GetGuildMemberAsync(guildId, user.ID, ct); + DateTimeOffset? communicationDisabledUntil = null; + if (guildMemberResult.IsDefined(out var guildMember)) + { + communicationDisabledUntil = guildMember.CommunicationDisabledUntil.OrDefault(null); + + embedColor = AppendGuildInformation(embedColor, guildMember, builder); + } + + var isMuted = (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) || + communicationDisabledUntil is not null; + + var existingBanResult = await _guildApi.GetGuildBanAsync(guildId, user.ID, ct); + + if (isMuted || existingBanResult.IsDefined()) + { + builder.Append("### ") + .AppendLine(Markdown.Bold(Messages.ShowInfoPunishments)); + } + + if (isMuted) + { + AppendMuteInformation(memberData, communicationDisabledUntil, builder); + + embedColor = ColorsList.Red; + } + + if (existingBanResult.IsDefined()) + { + AppendBanInformation(memberData, builder); + + embedColor = ColorsList.Black; + } + + if (!guildMemberResult.IsSuccess && !existingBanResult.IsDefined()) + { + builder.Append("### ") + .AppendLine(Markdown.Bold(Messages.ShowInfoNotOnGuild)); + + 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 Color AppendGuildInformation(Color color, IGuildMember guildMember, StringBuilder builder) + { + if (guildMember.Nickname.IsDefined(out var nickname)) + { + builder.Append("- ").AppendLine(Messages.ShowInfoGuildNickname) + .AppendLine(Markdown.InlineCode(nickname)); + } + + builder.Append("- ").AppendLine(Messages.ShowInfoGuildMemberSince) + .AppendLine(Markdown.Timestamp(guildMember.JoinedAt)); + + if (guildMember.PremiumSince.IsDefined(out var premiumSince)) + { + builder.Append("- ").AppendLine(Messages.ShowInfoGuildMemberPremiumSince) + .AppendLine(Markdown.Timestamp(premiumSince.Value)); + color = ColorsList.Magenta; + } + + if (guildMember.Roles.Count > 0) + { + builder.Append("- ").AppendLine(Messages.ShowInfoGuildRoles); + for (var i = 0; i < guildMember.Roles.Count - 1; i++) + { + builder.Append($"<@&{guildMember.Roles[i]}>, "); + } + + builder.AppendLine($"<@&{guildMember.Roles[^1]}>"); + } + + return color; + } + + private static void AppendBanInformation(MemberData memberData, StringBuilder builder) + { + if (memberData.BannedUntil < DateTimeOffset.MaxValue) + { + builder.Append("- ").AppendLine(Messages.ShowInfoBanned) + .Append(" - ").AppendLine(string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.BannedUntil.Value))); + return; + } + + builder.Append("- ").AppendLine(Messages.ShowInfoBannedPermanently); + } + + private static void AppendMuteInformation( + MemberData memberData, DateTimeOffset? communicationDisabledUntil, StringBuilder builder) + { + builder.Append("- ").AppendLine(Messages.ShowInfoMuted); + if (memberData.MutedUntil is not null && DateTimeOffset.UtcNow <= memberData.MutedUntil) + { + builder.Append(" - ").AppendLine(Messages.ShowInfoMutedByMuteRole) + .Append(" - ").AppendLine(string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(memberData.MutedUntil.Value))); + } + + if (communicationDisabledUntil is not null) + { + builder.Append(" - ").AppendLine(Messages.ShowInfoMutedByTimeout) + .Append(" - ").AppendLine(string.Format( + Messages.DescriptionActionExpiresAt, Markdown.Timestamp(communicationDisabledUntil.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..884cfd4 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1013,5 +1013,89 @@ namespace Boyfriend { return ResourceManager.GetString("AllSettingsReset", resourceCulture); } } + + internal static string ShowInfoTitle { + get { + return ResourceManager.GetString("ShowInfoTitle", 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 ShowInfoNotOnGuild { + get { + return ResourceManager.GetString("ShowInfoNotOnGuild", resourceCulture); + } + } + + internal static string ShowInfoMutedByTimeout { + get { + return ResourceManager.GetString("ShowInfoMutedByTimeout", resourceCulture); + } + } + + internal static string ShowInfoMutedByMuteRole { + get { + return ResourceManager.GetString("ShowInfoMutedByMuteRole", resourceCulture); + } + } + + internal static string ShowInfoGuildMemberSince { + get { + return ResourceManager.GetString("ShowInfoGuildMemberSince", resourceCulture); + } + } + + internal static string ShowInfoGuildNickname { + get { + return ResourceManager.GetString("ShowInfoGuildNickname", resourceCulture); + } + } + + internal static string ShowInfoGuildRoles { + get { + return ResourceManager.GetString("ShowInfoGuildRoles", resourceCulture); + } + } + + internal static string ShowInfoGuildMemberPremiumSince { + get { + return ResourceManager.GetString("ShowInfoGuildMemberPremiumSince", resourceCulture); + } + } } }