Esempio n. 1
0
    public async Task <Embed> CreateGroupEmbed(Context ctx, PKSystem system, PKGroup target)
    {
        var pctx = ctx.LookupContextFor(system.Id);

        var countctx = LookupContext.ByNonOwner;

        if (ctx.MatchFlag("a", "all"))
        {
            if (system.Id == ctx.System.Id)
            {
                countctx = LookupContext.ByOwner;
            }
            else
            {
                throw Errors.LookupNotAllowed;
            }
        }

        var memberCount = await _repo.GetGroupMemberCount(target.Id, countctx == LookupContext.ByOwner?null : PrivacyLevel.Public);

        var nameField = target.NamePrivacy.Get(pctx, target.Name, target.DisplayName ?? target.Name);

        if (system.Name != null)
        {
            nameField = $"{nameField} ({system.Name})";
        }

        uint color;

        try
        {
            color = target.Color?.ToDiscordColor() ?? DiscordUtils.Gray;
        }
        catch (ArgumentException)
        {
            // There's no API for group colors yet, but defaulting to a blank color regardless
            color = DiscordUtils.Gray;
        }

        var eb = new EmbedBuilder()
                 .Author(new Embed.EmbedAuthor(nameField, IconUrl: target.IconFor(pctx), Url: $"https://dash.pluralkit.me/profile/g/{target.Hid}"))
                 .Color(color);

        eb.Footer(new Embed.EmbedFooter($"System ID: {system.Hid} | Group ID: {target.Hid}{(target.MetadataPrivacy.CanAccess(pctx) ? $" | Created on {target.Created.FormatZoned(ctx.Zone)}" : "")}"));

        if (target.DescriptionPrivacy.CanAccess(pctx))
        {
            eb.Image(new Embed.EmbedImage(target.BannerImage));
        }

        if (target.NamePrivacy.CanAccess(pctx) && target.DisplayName != null)
        {
            eb.Field(new Embed.Field("Display Name", target.DisplayName, true));
        }

        if (!target.Color.EmptyOrNull())
        {
            eb.Field(new Embed.Field("Color", $"#{target.Color}", true));
        }

        if (target.ListPrivacy.CanAccess(pctx))
        {
            if (memberCount == 0 && pctx == LookupContext.ByOwner)
            {
                // Only suggest the add command if this is actually the owner lol
                eb.Field(new Embed.Field("Members (0)",
                                         $"Add one with `pk;group {target.Reference(ctx)} add <member>`!"));
            }
            else
            {
                var name = pctx == LookupContext.ByOwner
                    ? target.Reference(ctx)
                    : target.Hid;
                eb.Field(new Embed.Field($"Members ({memberCount})", $"(see `pk;group {name} list`)"));
            }
        }

        if (target.DescriptionFor(pctx) is { } desc)
        {
            eb.Field(new Embed.Field("Description", desc));
        }

        if (target.IconFor(pctx) is { } icon)
        {
            eb.Thumbnail(new Embed.EmbedThumbnail(icon.TryGetCleanCdnUrl()));
        }

        return(eb.Build());
    }
Esempio n. 2
0
    public Task <Embed> CreateFrontPercentEmbed(FrontBreakdown breakdown, PKSystem system, PKGroup group,
                                                DateTimeZone tz, LookupContext ctx, string embedTitle,
                                                bool ignoreNoFronters, bool showFlat)
    {
        var color = system.Color;

        if (group != null)
        {
            color = group.Color;
        }

        uint embedColor;

        try
        {
            embedColor = color?.ToDiscordColor() ?? DiscordUtils.Gray;
        }
        catch (ArgumentException)
        {
            embedColor = DiscordUtils.Gray;
        }

        var eb = new EmbedBuilder()
                 .Title(embedTitle)
                 .Color(embedColor);

        var footer =
            $"Since {breakdown.RangeStart.FormatZoned(tz)} ({(breakdown.RangeEnd - breakdown.RangeStart).FormatDuration()} ago)";

        Duration period;

        if (showFlat)
        {
            period  = Duration.FromTicks(breakdown.MemberSwitchDurations.Values.ToList().Sum(i => i.TotalTicks));
            footer += ". Showing flat list (percentages add up to 100%)";
            if (!ignoreNoFronters)
            {
                period += breakdown.NoFronterDuration;
            }
            else
            {
                footer += ", ignoring switch-outs";
            }
        }
        else if (ignoreNoFronters)
        {
            period  = breakdown.RangeEnd - breakdown.RangeStart - breakdown.NoFronterDuration;
            footer += ". Ignoring switch-outs";
        }
        else
        {
            period = breakdown.RangeEnd - breakdown.RangeStart;
        }

        eb.Footer(new Embed.EmbedFooter(footer));

        var maxEntriesToDisplay = 24; // max 25 fields allowed in embed - reserve 1 for "others"

        // We convert to a list of pairs so we can add the no-fronter value
        // Dictionary doesn't allow for null keys so we instead have a pair with a null key ;)
        var pairs = breakdown.MemberSwitchDurations.ToList();

        if (breakdown.NoFronterDuration != Duration.Zero && !ignoreNoFronters)
        {
            pairs.Add(new KeyValuePair <PKMember, Duration>(null, breakdown.NoFronterDuration));
        }

        var membersOrdered = pairs.OrderByDescending(pair => pair.Value).Take(maxEntriesToDisplay).ToList();

        foreach (var pair in membersOrdered)
        {
            var frac = pair.Value / period;
            eb.Field(new Embed.Field(pair.Key?.NameFor(ctx) ?? "*(no fronter)*",
                                     $"{frac * 100:F0}% ({pair.Value.FormatDuration()})"));
        }

        if (membersOrdered.Count > maxEntriesToDisplay)
        {
            eb.Field(new Embed.Field("(others)",
                                     membersOrdered.Skip(maxEntriesToDisplay)
                                     .Aggregate(Duration.Zero, (prod, next) => prod + next.Value)
                                     .FormatDuration(), true));
        }

        return(Task.FromResult(eb.Build()));
    }
Esempio n. 3
0
    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());
    }