From 4eb9ee601da4c51f1e87a2222cbf0c150e2e1f1a Mon Sep 17 00:00:00 2001 From: Ske Date: Fri, 26 Jul 2019 12:05:09 +0200 Subject: [PATCH] Add command to check channel permissions for proxying --- PluralKit.Bot/Commands/MiscCommands.cs | 77 ++++++++++++++++++++++++++ PluralKit.Bot/Errors.cs | 3 + 2 files changed, 80 insertions(+) diff --git a/PluralKit.Bot/Commands/MiscCommands.cs b/PluralKit.Bot/Commands/MiscCommands.cs index 8cf76336..3d7d4c14 100644 --- a/PluralKit.Bot/Commands/MiscCommands.cs +++ b/PluralKit.Bot/Commands/MiscCommands.cs @@ -1,8 +1,11 @@ +using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using App.Metrics; using Discord; using Discord.Commands; +using Humanizer; namespace PluralKit.Bot.Commands { public class MiscCommands: ModuleBase { @@ -51,5 +54,79 @@ namespace PluralKit.Bot.Commands { .AddField("Proxy success rate", $"{proxySuccessRate.Percent/100:P1}") .Build()); } + + [Command("permcheck")] + [Summary("permcheck [guild]")] + public async Task PermCheckGuild(ulong guildId) + { + // TODO: will this call break for sharding if you try to request a guild on a different bot instance? + var guild = Context.Client.GetGuild(guildId) as IGuild; + if (guild == null) + throw Errors.GuildNotFound(guildId); + + var requiredPermissions = new [] + { + ChannelPermission.ViewChannel, // Manage Messages automatically grants Send and Add Reactions, but not Read + ChannelPermission.ManageMessages, + ChannelPermission.ManageWebhooks + }; + + // Loop through every channel and group them by sets of permissions missing + var permissionsMissing = new Dictionary>(); + foreach (var channel in await guild.GetTextChannelsAsync()) + { + // TODO: do we need to hide channels here to prevent info-leaking? + var perms = await channel.PermissionsIn(); + + // We use a bitfield so we can set individual permission bits in the loop + ulong missingPermissionField = 0; + foreach (var requiredPermission in requiredPermissions) + if (!perms.Has(requiredPermission)) + missingPermissionField |= (ulong) requiredPermission; + + // If we're not missing any permissions, don't bother adding it to the dict + // This means we can check if the dict is empty to see if all channels are proxyable + if (missingPermissionField != 0) + { + permissionsMissing.TryAdd(missingPermissionField, new List()); + permissionsMissing[missingPermissionField].Add(channel); + } + } + + // Generate the output embed + var eb = new EmbedBuilder() + .WithTitle($"Permission check for **{guild.Name}**"); + + if (permissionsMissing.Count == 0) + { + eb.WithDescription($"No errors found, all channels proxyable :)").WithColor(Color.Green); + } + else + { + foreach (var (missingPermissionField, channels) in permissionsMissing) + { + // Each missing permission field can have multiple missing channels + // so we extract them all and generate a comma-separated list + var missingPermissionNames = string.Join(", ", new ChannelPermissions(missingPermissionField) + .ToList() + .Select(perm => perm.Humanize().Transform(To.TitleCase))); + + var channelsList = string.Join("\n", channels + .OrderBy(c => c.Position) + .Select(c => $"#{c.Name}")); + eb.AddField($"Missing *{missingPermissionNames}*", channelsList); + eb.WithColor(Color.Red); + } + } + + // Send! :) + await Context.Channel.SendMessageAsync(embed: eb.Build()); + } + + [Command("permcheck")] + [Summary("permcheck [guild]")] + [RequireContext(ContextType.Guild, ErrorMessage = + "When running this command in DMs, you must pass a guild ID.")] + public Task PermCheckGuild() => PermCheckGuild(Context.Guild.Id); } } \ No newline at end of file diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index a1c7cb99..6a258061 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -70,5 +71,7 @@ namespace PluralKit.Bot { public static PKError DurationParseError(string durationStr) => new PKError($"Could not parse '{durationStr.Sanitize()}' as a valid duration. Try a format such as `30d`, `1d3h` or `20m30s`."); public static PKError FrontPercentTimeInFuture => new PKError("Cannot get the front percent between now and a time in the future."); + + public static PKError GuildNotFound(ulong guildId) => new PKError($"Guild with ID {guildId} not found."); } } \ No newline at end of file