public async Task PermCheckGuild(Context ctx) { Guild guild; GuildMemberPartial senderGuildUser = null; if (ctx.Guild != null && !ctx.HasNext()) { guild = ctx.Guild; senderGuildUser = ctx.Member; } 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.AsCode()} as an ID."); } try { guild = await _rest.GetGuild(guildId); } catch (ForbiddenException) { throw Errors.GuildNotFound(guildId); } if (guild != null) { senderGuildUser = await _rest.GetGuildMember(guildId, ctx.Author.Id); } if (guild == null || senderGuildUser == null) { throw Errors.GuildNotFound(guildId); } } var guildMember = await _rest.GetGuildMember(guild.Id, await _cache.GetOwnUser()); // Loop through every channel and group them by sets of permissions missing var permissionsMissing = new Dictionary <ulong, List <Channel> >(); var hiddenChannels = false; var missingEmojiPermissions = false; foreach (var channel in await _rest.GetGuildChannels(guild.Id)) { var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember); var webhookPermissions = PermissionExtensions.EveryonePermissions(guild, channel); var userPermissions = PermissionExtensions.PermissionsFor(guild, channel, ctx.Author.Id, senderGuildUser); if ((userPermissions & PermissionSet.ViewChannel) == 0) { // If the user can't see this channel, don't calculate permissions for it // (to prevent info-leaking, mostly) // Instead, show the user that some channels got ignored (so they don't get confused) hiddenChannels = true; 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 ((webhookPermissions & PermissionSet.UseExternalEmojis) == 0) { missingPermissionField |= (ulong)PermissionSet.UseExternalEmojis; missingEmojiPermissions = true; } // 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 <Channel>()); permissionsMissing[missingPermissionField].Add(channel); } } // Generate the output embed var eb = new EmbedBuilder() .Title($"Permission check for **{guild.Name}**"); if (permissionsMissing.Count == 0) { eb.Description("No errors found, all channels proxyable :)").Color(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 = ((PermissionSet)missingPermissionField).ToPermissionString(); var channelsList = string.Join("\n", channels .OrderBy(c => c.Position) .Select(c => $"#{c.Name}")); eb.Field(new Embed.Field($"Missing *{missingPermissionNames}*", channelsList.Truncate(1000))); eb.Color(DiscordUtils.Red); } } var footer = ""; if (hiddenChannels) { footer += "Some channels were ignored as you do not have view access to them."; } if (missingEmojiPermissions) { if (hiddenChannels) { footer += " | "; } footer += "Use External Emojis permissions must be granted to the @everyone role / Default Permissions."; } if (footer.Length > 0) { eb.Footer(new Embed.EmbedFooter(footer)); } // Send! :) await ctx.Reply(embed : eb.Build()); }
public async Task PermCheckChannel(Context ctx) { if (!ctx.HasNext()) { throw new PKSyntaxError("You need to specify a channel."); } var error = "Channel not found or you do not have permissions to access it."; // todo: this breaks if channel is not in cache and bot does not have View Channel permissions var channel = await ctx.MatchChannel(); if (channel == null || channel.GuildId == null) { throw new PKError(error); } var guild = await _rest.GetGuildOrNull(channel.GuildId.Value); if (guild == null) { throw new PKError(error); } var guildMember = await _rest.GetGuildMember(channel.GuildId.Value, await _cache.GetOwnUser()); if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel)) { throw new PKError(error); } var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember); var webhookPermissions = PermissionExtensions.EveryonePermissions(guild, channel); // We use a bitfield so we can set individual permission bits ulong missingPermissions = 0; foreach (var requiredPermission in requiredPermissions) { if ((botPermissions & requiredPermission) == 0) { missingPermissions |= (ulong)requiredPermission; } } if ((webhookPermissions & PermissionSet.UseExternalEmojis) == 0) { missingPermissions |= (ulong)PermissionSet.UseExternalEmojis; } // Generate the output embed var eb = new EmbedBuilder() .Title($"Permission check for **{channel.Name}**"); if (missingPermissions == 0) { eb.Description("No issues found, channel is proxyable :)"); } else { var missing = ""; foreach (var permission in requiredPermissions) { if (((ulong)permission & missingPermissions) == (ulong)permission) { missing += $"\n- **{permission.ToPermissionString()}**"; } } if (((ulong)PermissionSet.UseExternalEmojis & missingPermissions) == (ulong)PermissionSet.UseExternalEmojis) { missing += $"\n- **{PermissionSet.UseExternalEmojis.ToPermissionString()}**"; } eb.Description($"Missing permissions:\n{missing}"); } await ctx.Reply(embed : eb.Build()); }