public async Task PermCheckGuild(Context ctx) { DiscordGuild guild; DiscordMember senderGuildUser = null; if (ctx.Guild != null && !ctx.HasNext()) { guild = ctx.Guild; senderGuildUser = (DiscordMember)ctx.Author; } else { var guildIdStr = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a server ID or run this command in a server."); if (!ulong.TryParse(guildIdStr, out var guildId)) { throw new PKSyntaxError($"Could not parse `{guildIdStr}` as an ID."); } guild = ctx.Client.GetGuild(guildId); if (guild != null) { senderGuildUser = await guild.GetMember(ctx.Author.Id); } if (guild == null || senderGuildUser == null) { throw Errors.GuildNotFound(guildId); } } var requiredPermissions = new [] { Permissions.AccessChannels, Permissions.SendMessages, Permissions.AddReactions, Permissions.AttachFiles, Permissions.EmbedLinks, Permissions.ManageMessages, Permissions.ManageWebhooks }; // Loop through every channel and group them by sets of permissions missing var permissionsMissing = new Dictionary <ulong, List <DiscordChannel> >(); var hiddenChannels = 0; foreach (var channel in await guild.GetChannelsAsync()) { var botPermissions = channel.BotPermissions(); var userPermissions = senderGuildUser.PermissionsIn(channel); if ((userPermissions & Permissions.AccessChannels) == 0) { // If the user can't see this channel, don't calculate permissions for it // (to prevent info-leaking, mostly) // Instead, count how many hidden channels and show the user (so they don't get confused) hiddenChannels++; continue; } // We use a bitfield so we can set individual permission bits in the loop // TODO: Rewrite with proper bitfield math ulong missingPermissionField = 0; foreach (var requiredPermission in requiredPermissions) { if ((botPermissions & requiredPermission) == 0) { 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 <DiscordChannel>()); permissionsMissing[missingPermissionField].Add(channel); } } // Generate the output embed var eb = new DiscordEmbedBuilder() .WithTitle($"Permission check for **{guild.Name}**"); if (permissionsMissing.Count == 0) { eb.WithDescription($"No errors found, all channels proxyable :)").WithColor(DiscordUtils.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 = ((Permissions)missingPermissionField).ToPermissionString(); var channelsList = string.Join("\n", channels .OrderBy(c => c.Position) .Select(c => $"#{c.Name}")); eb.AddField($"Missing *{missingPermissionNames}*", channelsList.Truncate(1000)); eb.WithColor(DiscordUtils.Red); } } if (hiddenChannels > 0) { eb.WithFooter($"{"channel".ToQuantity(hiddenChannels)} were ignored as you do not have view access to them."); } // Send! :) await ctx.Reply(embed : eb.Build()); }
public async Task PermCheckGuild(Context ctx) { IGuild guild; if (ctx.Guild != null && !ctx.HasNext()) { guild = ctx.Guild; } else { var guildIdStr = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a server ID or run this command as ."); if (!ulong.TryParse(guildIdStr, out var guildId)) { throw new PKSyntaxError($"Could not parse `{guildIdStr.SanitizeMentions()}` as an ID."); } // TODO: will this call break for sharding if you try to request a guild on a different bot instance? guild = ctx.Client.GetGuild(guildId); if (guild == null) { throw Errors.GuildNotFound(guildId); } } var requiredPermissions = new [] { ChannelPermission.ViewChannel, ChannelPermission.SendMessages, ChannelPermission.AddReactions, ChannelPermission.AttachFiles, ChannelPermission.EmbedLinks, ChannelPermission.ManageMessages, ChannelPermission.ManageWebhooks }; // Loop through every channel and group them by sets of permissions missing var permissionsMissing = new Dictionary <ulong, List <ITextChannel> >(); 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 <ITextChannel>()); permissionsMissing[missingPermissionField].Add(channel); } } // Generate the output embed var eb = new EmbedBuilder() .WithTitle($"Permission check for **{guild.Name.SanitizeMentions()}**"); 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.Truncate(1000)); eb.WithColor(Color.Red); } } // Send! :) await ctx.Reply(embed : eb.Build()); }