diff --git a/Catalogger.Backend/Bot/Commands/ChannelCommands.cs b/Catalogger.Backend/Bot/Commands/ChannelCommands.cs index 5f24b2d..e11178c 100644 --- a/Catalogger.Backend/Bot/Commands/ChannelCommands.cs +++ b/Catalogger.Backend/Bot/Commands/ChannelCommands.cs @@ -14,6 +14,7 @@ // along with this program. If not, see . using System.ComponentModel; +using Catalogger.Backend.Cache; using Catalogger.Backend.Cache.InMemoryCache; using Catalogger.Backend.Database; using Catalogger.Backend.Database.Queries; @@ -21,14 +22,14 @@ using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Remora.Commands.Attributes; using Remora.Commands.Groups; +using Remora.Discord.API; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Objects; using Remora.Discord.Commands.Attributes; -using Remora.Discord.Commands.Contexts; -using Remora.Discord.Commands.Extensions; using Remora.Discord.Commands.Feedback.Messages; using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Commands.Services; +using Remora.Discord.Extensions.Embeds; using Remora.Discord.Interactivity; using Remora.Discord.Interactivity.Services; using Remora.Rest.Core; @@ -40,16 +41,26 @@ namespace Catalogger.Backend.Bot.Commands; public class ChannelCommands( ILogger logger, + Config config, DatabaseContext db, GuildCache guildCache, ChannelCache channelCache, + IMemberCache memberCache, IFeedbackService feedbackService, ContextInjectionService contextInjection, - InMemoryDataService dataService + InMemoryDataService dataService, + PermissionResolverService permissionResolver ) : CommandGroup { private readonly ILogger _logger = logger.ForContext(); + private static readonly DiscordPermission[] RequiredGuildPermissions = + [ + DiscordPermission.ManageGuild, + DiscordPermission.ViewAuditLog, + ]; + + // TODO: i hate this [Command("check-permissions")] [Description( "Check for any permission issues that would prevent Catalogger from sending logs." @@ -57,7 +68,134 @@ public class ChannelCommands( [DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)] public async Task CheckPermissionsAsync() { - throw new NotImplementedException(); + var (userId, guildId) = contextInjection.GetUserAndGuild(); + if (!guildCache.TryGet(guildId, out var guild)) + throw new CataloggerError("Guild not in cache"); + + var embed = new EmbedBuilder().WithTitle($"Permission check for {guild.Name}"); + + var botUser = await memberCache.TryGetAsync( + guildId, + DiscordSnowflake.New(config.Discord.ApplicationId) + ); + var currentUser = await memberCache.TryGetAsync(guildId, userId); + if (botUser == null || currentUser == null) + throw new CataloggerError("Bot member or invoking member not found in cache"); + + // We don't want to check categories or threads + var guildChannels = channelCache + .GuildChannels(guildId) + .Where(c => + c.Type + is not ( + ChannelType.GuildCategory + or ChannelType.PublicThread + or ChannelType.PrivateThread + or ChannelType.AnnouncementThread + ) + ) + .ToList(); + + // We'll only check channels the user can see, to not leak any information. + var checkChannels = guildChannels + .Where(c => + { + var perms = permissionResolver.GetChannelPermissions(guildId, currentUser, c); + return perms.HasPermission(DiscordPermission.ViewChannel) + || perms.HasPermission(DiscordPermission.Administrator); + }) + .ToList(); + + var ignoredChannels = guildChannels.Count - checkChannels.Count; + if (ignoredChannels != 0) + embed = embed.WithFooter( + $"{ignoredChannels} channel(s) were ignored as you do not have access to them" + ); + + var guildPerms = permissionResolver.GetGuildPermissions(guildId, botUser); + // If the bot has admin perms, we can ignore the rest--we'll never get permission errors + if (guildPerms.HasPermission(DiscordPermission.Administrator)) + { + return await feedbackService.ReplyAsync( + embeds: + [ + embed + .WithColour(DiscordUtils.Green) + .WithDescription("No issues found, all channels can be logged to and from!") + .Build() + .GetOrThrow(), + ] + ); + } + + var missingGuildPerms = string.Join( + ", ", + RequiredGuildPermissions.Where(p => !guildPerms.HasPermission(p)) + ); + if (!string.IsNullOrWhiteSpace(missingGuildPerms)) + embed.AddField("Server-level permissions", missingGuildPerms); + + var missingManageChannel = new List(); + var missingSendMessages = new List(); + var missingViewChannel = new List(); + var missingReadMessageHistory = new List(); + var missingManageWebhooks = new List(); + + foreach (var channel in checkChannels) + { + var channelPerms = permissionResolver.GetChannelPermissions(guildId, botUser, channel); + if (!channelPerms.HasPermission(DiscordPermission.ManageChannels)) + missingManageChannel.Add(channel.ID); + if (!channelPerms.HasPermission(DiscordPermission.SendMessages)) + missingSendMessages.Add(channel.ID); + if (!channelPerms.HasPermission(DiscordPermission.ViewChannel)) + missingViewChannel.Add(channel.ID); + if (!channelPerms.HasPermission(DiscordPermission.ReadMessageHistory)) + missingReadMessageHistory.Add(channel.ID); + if (!channelPerms.HasPermission(DiscordPermission.ManageWebhooks)) + missingManageWebhooks.Add(channel.ID); + } + + if (missingManageChannel.Count != 0) + embed.AddField( + "Manage Channel", + string.Join("\n", missingManageChannel.Select(id => $"<#{id}>")) + ); + if (missingSendMessages.Count != 0) + embed.AddField( + "Send Messages", + string.Join("\n", missingSendMessages.Select(id => $"<#{id}>")) + ); + if (missingReadMessageHistory.Count != 0) + embed.AddField( + "Read Message History", + string.Join("\n", missingReadMessageHistory.Select(id => $"<#{id}>")) + ); + if (missingViewChannel.Count != 0) + embed.AddField( + "View Channel", + string.Join("\n", missingViewChannel.Select(id => $"<#{id}>")) + ); + if (missingManageWebhooks.Count != 0) + embed.AddField( + "Manage Webhooks", + string.Join("\n", missingManageWebhooks.Select(id => $"<#{id}>")) + ); + + if (embed.Fields.Count == 0) + { + embed = embed + .WithColour(DiscordUtils.Green) + .WithDescription("No issues found, all channels can be logged to and from!"); + } + else + { + embed = embed + .WithColour(DiscordUtils.Red) + .WithDescription("Permission issues found, please fix them and try again."); + } + + return await feedbackService.ReplyAsync(embeds: [embed.Build().GetOrThrow()]); } [Command("configure-channels")]