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); }
public static string Reference(this PKGroup group, Context ctx) => EntityReference(group.Hid, group.NameFor(ctx));
public static string Reference(this PKGroup group) => EntityReference(group.Hid, group.Name);
public static string NameFor(this PKGroup group, Context ctx) => group.NameFor(ctx.LookupContextFor(group.System));
public static string IconFor(this PKGroup group, Context ctx) => group.IconFor(ctx.LookupContextFor(group.System)).TryGetCleanCdnUrl();
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()); }
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())); }
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."); } }
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."); } }
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."); } }
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."); } }
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()); }