Esempio n. 1
0
        public async Task GroupIcon(Context ctx, PKGroup target)
        {
            async Task ClearIcon()
            {
                ctx.CheckOwnGroup(target);

                await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {
                    Icon = null
                }));

                await ctx.Reply($"{Emojis.Success} Group icon cleared.");
            }

            async Task SetIcon(ParsedImage img)
            {
                ctx.CheckOwnGroup(target);

                if (img.Url.Length > Limits.MaxUriLength)
                {
                    throw Errors.InvalidUrl(img.Url);
                }
                await AvatarUtils.VerifyAvatarOrThrow(img.Url);

                await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {
                    Icon = img.Url
                }));

                var msg = img.Source switch
                {
                    AvatarSource.User => $"{Emojis.Success} Group icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the group icon will need to be re-set.",
                    AvatarSource.Url => $"{Emojis.Success} Group icon changed to the image at the given URL.",
                    AvatarSource.Attachment => $"{Emojis.Success} Group icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the group icon will stop working.",
                    _ => throw new ArgumentOutOfRangeException()
                };

                // The attachment's already right there, no need to preview it.
                var hasEmbed = img.Source != AvatarSource.Attachment;

                await(hasEmbed
                    ? ctx.Reply(msg, embed: new DiscordEmbedBuilder().WithImageUrl(img.Url).Build())
                    : ctx.Reply(msg));
            }

            async Task ShowIcon()
            {
                if ((target.Icon?.Trim() ?? "").Length > 0)
                {
                    var eb = new DiscordEmbedBuilder()
                             .WithTitle("Group icon")
                             .WithImageUrl(target.Icon);

                    if (target.System == ctx.System?.Id)
                    {
                        eb.WithDescription($"To clear, use `pk;group {target.Reference()} icon -clear`.");
                    }

                    await ctx.Reply(embed : eb.Build());
                }
                else
                {
                    throw new PKSyntaxError("This group does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
                }
            }

            if (await ctx.MatchClear("this group's icon"))
            {
                await ClearIcon();
            }
            else if (await ctx.MatchImage() is {} img)
            {
                await SetIcon(img);
            }
Esempio n. 2
0
 public static string Reference(this PKGroup group, Context ctx) => EntityReference(group.Hid, group.NameFor(ctx));
Esempio n. 3
0
 public static string Reference(this PKGroup group) => EntityReference(group.Hid, group.Name);
Esempio n. 4
0
 public static string NameFor(this PKGroup group, Context ctx) =>
 group.NameFor(ctx.LookupContextFor(group.System));
Esempio n. 5
0
 public static string IconFor(this PKGroup group, Context ctx) =>
 group.IconFor(ctx.LookupContextFor(group.System)).TryGetCleanCdnUrl();
Esempio n. 6
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)))
                 .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. 7
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. 8
0
        public async Task GroupDescription(Context ctx, PKGroup target)
        {
            if (await ctx.MatchClear("this group's description"))
            {
                ctx.CheckOwnGroup(target);

                var patch = new GroupPatch {
                    Description = Partial <string> .Null()
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group description cleared.");
            }
            else if (!ctx.HasNext())
            {
                if (!target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
                {
                    throw Errors.LookupNotAllowed;
                }

                if (target.Description == null)
                {
                    if (ctx.System?.Id == target.System)
                    {
                        await ctx.Reply($"This group does not have a description set. To set one, type `pk;group {target.Reference()} description <description>`.");
                    }
                    else
                    {
                        await ctx.Reply("This group does not have a description set.");
                    }
                }
                else if (ctx.MatchFlag("r", "raw"))
                {
                    await ctx.Reply($"```\n{target.Description}\n```");
                }
                else
                {
                    await ctx.Reply(embed : new EmbedBuilder()
                                    .Title("Group description")
                                    .Description(target.Description)
                                    .Field(new("\u200B", $"To print the description with formatting, type `pk;group {target.Reference()} description -raw`."
                                               + (ctx.System?.Id == target.System ? $" To clear it, type `pk;group {target.Reference()} description -clear`." : "")))
                                    .Build());
                }
            }
            else
            {
                ctx.CheckOwnGroup(target);

                var description = ctx.RemainderOrNull().NormalizeLineEndSpacing();
                if (description.IsLongerThan(Limits.MaxDescriptionLength))
                {
                    throw Errors.DescriptionTooLongError(description.Length);
                }

                var patch = new GroupPatch {
                    Description = Partial <string> .Present(description)
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group description changed.");
            }
        }
Esempio n. 9
0
        public async Task GroupDisplayName(Context ctx, PKGroup target)
        {
            if (await ctx.MatchClear("this group's display name"))
            {
                ctx.CheckOwnGroup(target);

                var patch = new GroupPatch {
                    DisplayName = Partial <string> .Null()
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group display name cleared.");
            }
            else if (!ctx.HasNext())
            {
                // No perms check, display name isn't covered by member privacy
                if (ctx.MatchFlag("r", "raw"))
                {
                    if (target.DisplayName == null)
                    {
                        if (ctx.System?.Id == target.System)
                        {
                            await ctx.Reply($"This group does not have a display name set. To set one, type `pk;group {target.Reference()} displayname <display name>`.");
                        }
                        else
                        {
                            await ctx.Reply("This group does not have a display name set.");
                        }
                    }
                    else
                    {
                        await ctx.Reply($"```\n{target.DisplayName}\n```");
                    }
                }
                else
                {
                    var eb = new EmbedBuilder()
                             .Field(new("Name", target.Name))
                             .Field(new("Display Name", target.DisplayName ?? "*(none)*"));

                    if (ctx.System?.Id == target.System)
                    {
                        eb.Description($"To change display name, type `pk;group {target.Reference()} displayname <display name>`.\nTo clear it, type `pk;group {target.Reference()} displayname -clear`.\nTo print the raw display name, type `pk;group {target.Reference()} displayname -raw`.");
                    }

                    await ctx.Reply(embed : eb.Build());
                }
            }
            else
            {
                ctx.CheckOwnGroup(target);

                var newDisplayName = ctx.RemainderOrNull();

                var patch = new GroupPatch {
                    DisplayName = Partial <string> .Present(newDisplayName)
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group display name changed.");
            }
        }
Esempio n. 10
0
    public async Task GroupDescription(Context ctx, PKGroup target)
    {
        ctx.CheckSystemPrivacy(target.System, target.DescriptionPrivacy);

        var noDescriptionSetMessage = "This group does not have a description set.";

        if (ctx.System?.Id == target.System)
        {
            noDescriptionSetMessage +=
                $" To set one, type `pk;group {target.Reference(ctx)} description <description>`.";
        }

        if (ctx.MatchRaw())
        {
            if (target.Description == null)
            {
                await ctx.Reply(noDescriptionSetMessage);
            }
            else
            {
                await ctx.Reply($"```\n{target.Description}\n```");
            }
            return;
        }

        if (!ctx.HasNext(false))
        {
            if (target.Description == null)
            {
                await ctx.Reply(noDescriptionSetMessage);
            }
            else
            {
                await ctx.Reply(embed : new EmbedBuilder()
                                .Title("Group description")
                                .Description(target.Description)
                                .Field(new Embed.Field("\u200B",
                                                       $"To print the description with formatting, type `pk;group {target.Reference(ctx)} description -raw`."
                                                       + (ctx.System?.Id == target.System
                            ? $" To clear it, type `pk;group {target.Reference(ctx)} description -clear`."
                            : "")))
                                .Build());
            }
            return;
        }

        ctx.CheckOwnGroup(target);

        if (await ctx.MatchClear("this group's description"))
        {
            var patch = new GroupPatch {
                Description = Partial <string> .Null()
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group description cleared.");
        }
        else
        {
            var description = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
            if (description.IsLongerThan(Limits.MaxDescriptionLength))
            {
                throw Errors.StringTooLongError("Description", description.Length, Limits.MaxDescriptionLength);
            }

            var patch = new GroupPatch {
                Description = Partial <string> .Present(description)
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group description changed.");
        }
    }
Esempio n. 11
0
    public async Task GroupDisplayName(Context ctx, PKGroup target)
    {
        var noDisplayNameSetMessage = "This group does not have a display name set.";

        if (ctx.System?.Id == target.System)
        {
            noDisplayNameSetMessage +=
                $" To set one, type `pk;group {target.Reference(ctx)} displayname <display name>`.";
        }

        // No perms check, display name isn't covered by member privacy

        if (ctx.MatchRaw())
        {
            if (target.DisplayName == null)
            {
                await ctx.Reply(noDisplayNameSetMessage);
            }
            else
            {
                await ctx.Reply($"```\n{target.DisplayName}\n```");
            }
            return;
        }

        if (!ctx.HasNext(false))
        {
            if (target.DisplayName == null)
            {
                await ctx.Reply(noDisplayNameSetMessage);
            }
            else
            {
                var eb = new EmbedBuilder()
                         .Field(new Embed.Field("Name", target.Name))
                         .Field(new Embed.Field("Display Name", target.DisplayName));

                var reference = target.Reference(ctx);

                if (ctx.System?.Id == target.System)
                {
                    eb.Description(
                        $"To change display name, type `pk;group {reference} displayname <display name>`."
                        + $"To clear it, type `pk;group {reference} displayname -clear`."
                        + $"To print the raw display name, type `pk;group {reference} displayname -raw`.");
                }

                await ctx.Reply(embed : eb.Build());
            }

            return;
        }

        ctx.CheckOwnGroup(target);

        if (await ctx.MatchClear("this group's display name"))
        {
            var patch = new GroupPatch {
                DisplayName = Partial <string> .Null()
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group display name cleared.");

            if (target.NamePrivacy == PrivacyLevel.Private)
            {
                await ctx.Reply($"{Emojis.Warn} Since this group no longer has a display name set, their name privacy **can no longer take effect**.");
            }
        }
        else
        {
            var newDisplayName = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();

            var patch = new GroupPatch {
                DisplayName = Partial <string> .Present(newDisplayName)
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group display name changed.");
        }
    }
Esempio n. 12
0
        public async Task <DiscordEmbed> CreateGroupEmbed(Context ctx, PKSystem system, PKGroup target)
        {
            await using var conn = await _db.Obtain();

            var pctx        = ctx.LookupContextFor(system);
            var memberCount = ctx.MatchPrivateFlag(pctx) ? await _repo.GetGroupMemberCount(conn, target.Id, PrivacyLevel.Public) : await _repo.GetGroupMemberCount(conn, target.Id);

            var nameField = target.Name;

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

            var eb = new DiscordEmbedBuilder()
                     .WithAuthor(nameField, iconUrl: DiscordUtils.WorkaroundForUrlBug(target.IconFor(pctx)))
                     .WithFooter($"System ID: {system.Hid} | Group ID: {target.Hid} | Created on {target.Created.FormatZoned(system)}");

            if (target.DisplayName != null)
            {
                eb.AddField("Display Name", target.DisplayName);
            }

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

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

            if (target.IconFor(pctx) is {} icon)
            {
                eb.WithThumbnail(icon);
            }

            return(eb.Build());
        }