private async Task <EmbedBuilder> CreateMemberNameInfoEmbed(Context ctx, PKMember target) { var lcx = ctx.LookupContextFor(target); MemberGuildSettings memberGuildConfig = null; if (ctx.Guild != null) { memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id)); } var eb = new EmbedBuilder() .Title($"Member names") .Footer(new($"Member ID: {target.Hid} | Active name in bold. Server name overrides display name, which overrides base name.")); if (target.DisplayName == null && memberGuildConfig?.DisplayName == null) { eb.Field(new("Name", $"**{target.NameFor(ctx)}**")); } else { eb.Field(new("Name", target.NameFor(ctx))); } if (target.NamePrivacy.CanAccess(lcx)) { if (target.DisplayName != null && memberGuildConfig?.DisplayName == null) { eb.Field(new("Display Name", $"**{target.DisplayName}**")); } else { eb.Field(new("Display Name", target.DisplayName ?? "*(none)*")); } } if (ctx.Guild != null) { if (memberGuildConfig?.DisplayName != null) { eb.Field(new($"Server Name (in {ctx.Guild.Name})", $"**{memberGuildConfig.DisplayName}**")); } else { eb.Field(new($"Server Name (in {ctx.Guild.Name})", memberGuildConfig?.DisplayName ?? "*(none)*")); } } return(eb); }
private async Task AvatarShow(AvatarLocation location, Context ctx, PKMember target, MemberGuildSettings?guildData) { var currentValue = location == AvatarLocation.Member ? target.AvatarUrl : guildData?.AvatarUrl; var canAccess = location != AvatarLocation.Member || target.AvatarPrivacy.CanAccess(ctx.LookupContextFor(target)); if (string.IsNullOrEmpty(currentValue) || !canAccess) { if (location == AvatarLocation.Member) { if (target.System == ctx.System?.Id) { throw new PKSyntaxError("This member does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention."); } throw new PKError("This member does not have an avatar set."); } if (location == AvatarLocation.Server) { throw new PKError($"This member does not have a server avatar set. Type `pk;member {target.Hid} avatar` to see their global avatar."); } } var field = location == AvatarLocation.Server ? $"server avatar (for {ctx.Guild.Name})" : "avatar"; var cmd = location == AvatarLocation.Server ? "serveravatar" : "avatar"; var eb = new DiscordEmbedBuilder() .WithTitle($"{target.NameFor(ctx)}'s {field}") .WithImageUrl(currentValue); if (target.System == ctx.System?.Id) { eb.WithDescription($"To clear, use `pk;member {target.Hid} {cmd} clear`."); } await ctx.Reply(embed : eb.Build()); }
public async Task Soulscream(Context ctx, PKMember target) { // this is for a meme, please don't take this code seriously. :) var name = target.NameFor(ctx.LookupContextFor(target)); var encoded = HttpUtility.UrlEncode(name); var resp = await _client.GetAsync($"https://onomancer.sibr.dev/api/generateStats2?name={encoded}"); if (resp.StatusCode != HttpStatusCode.OK) { // lol return; } var data = JObject.Parse(await resp.Content.ReadAsStringAsync()); var scream = data["soulscream"] !.Value <string>(); var eb = new DiscordEmbedBuilder() .WithColor(DiscordColor.Red) .WithTitle(name) .WithUrl($"https://onomancer.sibr.dev/reflect?name={encoded}") .WithDescription($"*{scream}*"); await ctx.Reply(embed : eb.Build()); }
public async Task BannerImage(Context ctx, PKMember target) { ctx.CheckOwnMember(target); async Task ClearBannerImage() { await ctx.Repository.UpdateMember(target.Id, new MemberPatch { BannerImage = null }); await ctx.Reply($"{Emojis.Success} Member banner image cleared."); } async Task SetBannerImage(ParsedImage img) { await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true); await ctx.Repository.UpdateMember(target.Id, new MemberPatch { BannerImage = img.Url }); var msg = img.Source switch { AvatarSource.Url => $"{Emojis.Success} Member banner image changed to the image at the given URL.", AvatarSource.Attachment => $"{Emojis.Success} Member banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working.", AvatarSource.User => throw new PKError("Cannot set a banner image to an user's avatar."), _ => 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, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build()) : ctx.Reply(msg)); } async Task ShowBannerImage() { if ((target.BannerImage?.Trim() ?? "").Length > 0) { var eb = new EmbedBuilder() .Title($"{target.NameFor(ctx)}'s banner image") .Image(new Embed.EmbedImage(target.BannerImage)) .Description($"To clear, use `pk;member {target.Hid} banner clear`."); await ctx.Reply(embed : eb.Build()); } else { throw new PKSyntaxError( "This member does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL or @mention."); } } if (await ctx.MatchClear("this member's banner image")) { await ClearBannerImage(); } else if (await ctx.MatchImage() is { } img) { await SetBannerImage(img); }
public static JObject ToJson(this PKMember member, LookupContext ctx, bool needsLegacyProxyTags = false, string systemStr = null) { var includePrivacy = ctx == LookupContext.ByOwner; var o = new JObject(); o.Add("id", member.Hid); o.Add("uuid", member.Uuid.ToString()); if (systemStr != null) { o.Add("system", systemStr); } o.Add("name", member.NameFor(ctx)); o.Add("display_name", member.NamePrivacy.CanAccess(ctx) ? member.DisplayName : null); // o.Add("color", member.ColorPrivacy.CanAccess(ctx) ? member.Color : null); o.Add("color", member.Color); o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport()); o.Add("pronouns", member.PronounsFor(ctx)); o.Add("avatar_url", member.AvatarFor(ctx).TryGetCleanCdnUrl()); o.Add("banner", member.DescriptionPrivacy.Get(ctx, member.BannerImage).TryGetCleanCdnUrl()); o.Add("description", member.DescriptionFor(ctx)); o.Add("created", member.CreatedFor(ctx)?.FormatExport()); o.Add("keep_proxy", member.KeepProxy); var tagArray = new JArray(); foreach (var tag in member.ProxyTags) { tagArray.Add(new JObject { { "prefix", tag.Prefix }, { "suffix", tag.Suffix } }); } o.Add("proxy_tags", tagArray); if (includePrivacy) { var p = new JObject(); p.Add("visibility", member.MemberVisibility.ToJsonString()); p.Add("name_privacy", member.NamePrivacy.ToJsonString()); p.Add("description_privacy", member.DescriptionPrivacy.ToJsonString()); p.Add("birthday_privacy", member.BirthdayPrivacy.ToJsonString()); p.Add("pronoun_privacy", member.PronounPrivacy.ToJsonString()); p.Add("avatar_privacy", member.AvatarPrivacy.ToJsonString()); p.Add("metadata_privacy", member.MetadataPrivacy.ToJsonString()); o.Add("privacy", p); } else { o.Add("privacy", null); } return(o); }
private async Task AutoproxyMember(Context ctx, PKMember member) { ctx.CheckOwnMember(member); await UpdateAutoproxy(ctx, AutoproxyMode.Member, member.Id); await ctx.Reply($"{Emojis.Success} Autoproxy set to **{member.NameFor(ctx)}** in this server."); }
public async Task Pronouns(Context ctx, PKMember target) { if (await ctx.MatchClear("this member's pronouns")) { ctx.CheckOwnMember(target); var patch = new MemberPatch { Pronouns = Partial <string> .Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member pronouns cleared."); } else if (!ctx.HasNext()) { if (!target.PronounPrivacy.CanAccess(ctx.LookupContextFor(target.System))) { throw Errors.LookupNotAllowed; } if (target.Pronouns == null) { if (ctx.System?.Id == target.System) { await ctx.Reply($"This member does not have pronouns set. To set some, type `pk;member {target.Reference()} pronouns <pronouns>`."); } else { await ctx.Reply("This member does not have pronouns set."); } } else if (ctx.MatchFlag("r", "raw")) { await ctx.Reply($"```\n{target.Pronouns}\n```"); } else { await ctx.Reply($"**{target.NameFor(ctx)}**'s pronouns are **{target.Pronouns}**.\nTo print the pronouns with formatting, type `pk;member {target.Reference()} pronouns -raw`." + (ctx.System?.Id == target.System ? $" To clear them, type `pk;member {target.Reference()} pronouns -clear`." : "")); } } else { ctx.CheckOwnMember(target); var pronouns = ctx.RemainderOrNull().NormalizeLineEndSpacing(); if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) { throw Errors.MemberPronounsTooLongError(pronouns.Length); } var patch = new MemberPatch { Pronouns = Partial <string> .Present(pronouns) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member pronouns changed."); } }
private async Task AutoproxyMember(Context ctx, PKMember member) { ctx.CheckOwnMember(member); // todo: why does this not throw an error if the member is already set await UpdateAutoproxy(ctx, AutoproxyMode.Member, member.Id); await ctx.Reply($"{Emojis.Success} Autoproxy set to **{member.NameFor(ctx)}** in this server."); }
public DiscordEmbed CreateLoggedMessageEmbed(PKSystem system, PKMember member, ulong messageId, ulong originalMsgId, DiscordUser sender, string content, DiscordChannel channel) { // TODO: pronouns in ?-reacted response using this card var timestamp = DiscordUtils.SnowflakeToInstant(messageId); var name = member.NameFor(LookupContext.ByNonOwner); return(new DiscordEmbedBuilder() .WithAuthor($"#{channel.Name}: {name}", iconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarFor(LookupContext.ByNonOwner))) .WithThumbnail(member.AvatarFor(LookupContext.ByNonOwner)) .WithDescription(content?.NormalizeLineEndSpacing()) .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} | Sender: {sender.Username}#{sender.Discriminator} ({sender.Id}) | Message ID: {messageId} | Original Message ID: {originalMsgId}") .WithTimestamp(timestamp.ToDateTimeOffset()) .Build()); }
public async Task Pronouns(Context ctx, PKMember target) { if (MatchClear(ctx)) { CheckEditMemberPermission(ctx, target); target.Pronouns = null; await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member pronouns cleared."); } else if (!ctx.HasNext()) { if (!target.PronounPrivacy.CanAccess(ctx.LookupContextFor(target.System))) { throw Errors.LookupNotAllowed; } if (target.Pronouns == null) { if (ctx.System?.Id == target.System) { await ctx.Reply($"This member does not have pronouns set. To set some, type `pk;member {target.Hid} pronouns <pronouns>`."); } else { await ctx.Reply("This member does not have pronouns set."); } } else { await ctx.Reply($"**{target.NameFor(ctx)}**'s pronouns are **{target.Pronouns}**." + (ctx.System?.Id == target.System ? $" To clear them, type `pk;member {target.Hid} pronouns -clear`." : "")); } } else { CheckEditMemberPermission(ctx, target); var pronouns = ctx.RemainderOrNull().NormalizeLineEndSpacing(); if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) { throw Errors.MemberPronounsTooLongError(pronouns.Length); } target.Pronouns = pronouns; await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member pronouns changed."); } }
public Embed CreateEditedMessageEmbed(PKSystem system, PKMember member, ulong messageId, ulong originalMsgId, User sender, string content, string oldContent, Channel channel) { var timestamp = DiscordUtils.SnowflakeToInstant(messageId); var name = member.NameFor(LookupContext.ByNonOwner); return(new EmbedBuilder() .Author(new($"[Edited] #{channel.Name}: {name}", IconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarFor(LookupContext.ByNonOwner)))) .Thumbnail(new(member.AvatarFor(LookupContext.ByNonOwner))) .Field(new("Old message", oldContent?.NormalizeLineEndSpacing().Truncate(1000))) .Description(content?.NormalizeLineEndSpacing()) .Footer(new($"System ID: {system.Hid} | Member ID: {member.Hid} | Sender: {sender.Username}#{sender.Discriminator} ({sender.Id}) | Message ID: {messageId} | Original Message ID: {originalMsgId}")) .Timestamp(timestamp.ToDateTimeOffset().ToString("O")) .Build()); }
public static JObject ToJson(this PKMember member, LookupContext ctx) { var includePrivacy = ctx == LookupContext.ByOwner; var o = new JObject(); o.Add("id", member.Hid); o.Add("name", member.NameFor(ctx)); // o.Add("color", member.ColorPrivacy.CanAccess(ctx) ? member.Color : null); o.Add("color", member.Color); o.Add("display_name", member.NamePrivacy.CanAccess(ctx) ? member.DisplayName : null); o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport()); o.Add("pronouns", member.PronounsFor(ctx)); o.Add("avatar_url", member.AvatarFor(ctx)); o.Add("description", member.DescriptionFor(ctx)); var tagArray = new JArray(); foreach (var tag in member.ProxyTags) { tagArray.Add(new JObject { { "prefix", tag.Prefix }, { "suffix", tag.Suffix } }); } o.Add("proxy_tags", tagArray); o.Add("keep_proxy", member.KeepProxy); o.Add("privacy", includePrivacy ? (member.MemberVisibility.LevelName()) : null); o.Add("visibility", includePrivacy ? (member.MemberVisibility.LevelName()) : null); o.Add("name_privacy", includePrivacy ? (member.NamePrivacy.LevelName()) : null); o.Add("description_privacy", includePrivacy ? (member.DescriptionPrivacy.LevelName()) : null); o.Add("birthday_privacy", includePrivacy ? (member.BirthdayPrivacy.LevelName()) : null); o.Add("pronoun_privacy", includePrivacy ? (member.PronounPrivacy.LevelName()) : null); o.Add("avatar_privacy", includePrivacy ? (member.AvatarPrivacy.LevelName()) : null); // o.Add("color_privacy", ctx == LookupContext.ByOwner ? (member.ColorPrivacy.LevelName()) : null); o.Add("metadata_privacy", includePrivacy ? (member.MetadataPrivacy.LevelName()) : null); o.Add("created", member.CreatedFor(ctx)?.FormatExport()); if (member.ProxyTags.Count > 0) { // Legacy compatibility only, TODO: remove at some point o.Add("prefix", member.ProxyTags?.FirstOrDefault().Prefix); o.Add("suffix", member.ProxyTags?.FirstOrDefault().Suffix); } return(o); }
public async Task Delete(Context ctx, PKMember target) { ctx.CheckSystem().CheckOwnMember(target); await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.NameFor(ctx)}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__"); if (!await ctx.ConfirmWithReply(target.Hid)) { throw Errors.MemberDeleteCancelled; } await _db.Execute(conn => _repo.DeleteMember(conn, target.Id)); await ctx.Reply($"{Emojis.Success} Member deleted."); }
private async Task AvatarShow(AvatarLocation location, Context ctx, PKMember target, MemberGuildSettings?guildData) { // todo: this privacy code is really confusing // for now, we skip privacy flag/config parsing for this, but it would be good to fix that at some point var currentValue = location == AvatarLocation.Member ? target.AvatarUrl : guildData?.AvatarUrl; var canAccess = location != AvatarLocation.Member || target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.System)); if (string.IsNullOrEmpty(currentValue) || !canAccess) { if (location == AvatarLocation.Member) { if (target.System == ctx.System?.Id) { throw new PKSyntaxError( "This member does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention."); } throw new PKError("This member does not have an avatar set."); } if (location == AvatarLocation.Server) { throw new PKError( $"This member does not have a server avatar set. Type `pk;member {target.Reference(ctx)} avatar` to see their global avatar."); } } var field = location == AvatarLocation.Server ? $"server avatar (for {ctx.Guild.Name})" : "avatar"; var cmd = location == AvatarLocation.Server ? "serveravatar" : "avatar"; var eb = new EmbedBuilder() .Title($"{target.NameFor(ctx)}'s {field}") .Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl())); if (target.System == ctx.System?.Id) { eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {cmd} clear`."); } await ctx.Reply(embed : eb.Build()); }
public async Task Delete(Context ctx, PKMember target) { if (ctx.System == null) { throw Errors.NoSystemError; } if (target.System != ctx.System.Id) { throw Errors.NotOwnMemberError; } await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.NameFor(ctx)}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__"); if (!await ctx.ConfirmWithReply(target.Hid)) { throw Errors.MemberDeleteCancelled; } await _data.DeleteMember(target); await ctx.Reply($"{Emojis.Success} Member deleted."); }
private DiscordEmbed CreatePrivacyEmbed(Context ctx, PKMember member) { string PrivacyLevelString(PrivacyLevel level) => level switch { PrivacyLevel.Private => "**Private** (visible only when queried by you)", PrivacyLevel.Public => "**Public** (visible to everyone)", _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) }; var eb = new DiscordEmbedBuilder() .WithTitle($"Current privacy settings for {member.NameFor(ctx)}") .AddField("Name (replaces name with display name if member has one)", PrivacyLevelString(member.NamePrivacy)) .AddField("Description", PrivacyLevelString(member.DescriptionPrivacy)) .AddField("Avatar", PrivacyLevelString(member.AvatarPrivacy)) .AddField("Birthday", PrivacyLevelString(member.BirthdayPrivacy)) .AddField("Pronouns", PrivacyLevelString(member.PronounPrivacy)) // .AddField("Color", PrivacyLevelString(target.ColorPrivacy)) .AddField("Meta (message count, last front, last message)", PrivacyLevelString(member.MetadataPrivacy)) .AddField("Visibility", PrivacyLevelString(member.MemberVisibility)) .WithDescription("To edit privacy settings, use the command:\n`pk;member <member> privacy <subject> <level>`\n\n- `subject` is one of `name`, `description`, `avatar`, `birthday`, `pronouns`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`."); return(eb.Build()); }
public static string Reference(this PKMember member, Context ctx) => EntityReference(member.Hid, member.NameFor(ctx));
public async Task Privacy(Context ctx, PKMember target, PrivacyLevel?newValueFromCommand) { ctx.CheckSystem().CheckOwnMember(target); // Display privacy settings if (!ctx.HasNext() && newValueFromCommand == null) { await ctx.Reply(embed : new EmbedBuilder() .Title($"Current privacy settings for {target.NameFor(ctx)}") .Field(new("Name (replaces name with display name if member has one)", target.NamePrivacy.Explanation())) .Field(new("Description", target.DescriptionPrivacy.Explanation())) .Field(new("Avatar", target.AvatarPrivacy.Explanation())) .Field(new("Birthday", target.BirthdayPrivacy.Explanation())) .Field(new("Pronouns", target.PronounPrivacy.Explanation())) .Field(new("Meta (message count, last front, last message)", target.MetadataPrivacy.Explanation())) .Field(new("Visibility", target.MemberVisibility.Explanation())) .Description("To edit privacy settings, use the command:\n`pk;member <member> privacy <subject> <level>`\n\n- `subject` is one of `name`, `description`, `avatar`, `birthday`, `pronouns`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`.") .Build()); return; } // Get guild settings (mostly for warnings and such) MemberGuildSettings guildSettings = null; if (ctx.Guild != null) { guildSettings = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id)); } async Task SetAll(PrivacyLevel level) { await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch().WithAllPrivacy(level))); if (level == PrivacyLevel.Private) { await ctx.Reply($"{Emojis.Success} All {target.NameFor(ctx)}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see nothing on the member card."); } else { await ctx.Reply($"{Emojis.Success} All {target.NameFor(ctx)}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see everything on the member card."); } } async Task SetLevel(MemberPrivacySubject subject, PrivacyLevel level) { await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch().WithPrivacy(subject, level))); var subjectName = subject switch { MemberPrivacySubject.Name => "name privacy", MemberPrivacySubject.Description => "description privacy", MemberPrivacySubject.Avatar => "avatar privacy", MemberPrivacySubject.Pronouns => "pronoun privacy", MemberPrivacySubject.Birthday => "birthday privacy", MemberPrivacySubject.Metadata => "metadata privacy", MemberPrivacySubject.Visibility => "visibility", _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; var explanation = (subject, level) switch { (MemberPrivacySubject.Name, PrivacyLevel.Private) => "This member's name is now hidden from other systems, and will be replaced by the member's display name.", (MemberPrivacySubject.Description, PrivacyLevel.Private) => "This member's description is now hidden from other systems.", (MemberPrivacySubject.Avatar, PrivacyLevel.Private) => "This member's avatar is now hidden from other systems.", (MemberPrivacySubject.Birthday, PrivacyLevel.Private) => "This member's birthday is now hidden from other systems.", (MemberPrivacySubject.Pronouns, PrivacyLevel.Private) => "This member's pronouns are now hidden from other systems.", (MemberPrivacySubject.Metadata, PrivacyLevel.Private) => "This member's metadata (eg. created timestamp, message count, etc) is now hidden from other systems.", (MemberPrivacySubject.Visibility, PrivacyLevel.Private) => "This member is now hidden from member lists.", (MemberPrivacySubject.Name, PrivacyLevel.Public) => "This member's name is no longer hidden from other systems.", (MemberPrivacySubject.Description, PrivacyLevel.Public) => "This member's description is no longer hidden from other systems.", (MemberPrivacySubject.Avatar, PrivacyLevel.Public) => "This member's avatar is no longer hidden from other systems.", (MemberPrivacySubject.Birthday, PrivacyLevel.Public) => "This member's birthday is no longer hidden from other systems.", (MemberPrivacySubject.Pronouns, PrivacyLevel.Public) => "This member's pronouns are no longer hidden other systems.", (MemberPrivacySubject.Metadata, PrivacyLevel.Public) => "This member's metadata (eg. created timestamp, message count, etc) is no longer hidden from other systems.", (MemberPrivacySubject.Visibility, PrivacyLevel.Public) => "This member is no longer hidden from member lists.", _ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})") }; await ctx.Reply($"{Emojis.Success} {target.NameFor(ctx)}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}"); // Name privacy only works given a display name if (subject == MemberPrivacySubject.Name && level == PrivacyLevel.Private && target.DisplayName == null) { await ctx.Reply($"{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**."); } // Avatar privacy doesn't apply when proxying if no server avatar is set if (subject == MemberPrivacySubject.Avatar && level == PrivacyLevel.Private && guildSettings?.AvatarUrl == null) { await ctx.Reply($"{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `pk;member {target.Reference()} serveravatar`"); } } if (ctx.Match("all") || newValueFromCommand != null) { await SetAll(newValueFromCommand ?? ctx.PopPrivacyLevel()); } else { await SetLevel(ctx.PopMemberPrivacySubject(), ctx.PopPrivacyLevel()); } }
public async Task Pronouns(Context ctx, PKMember target) { var noPronounsSetMessage = "This member does not have pronouns set."; if (ctx.System?.Id == target.System) { noPronounsSetMessage += $"To set some, type `pk;member {target.Reference(ctx)} pronouns <pronouns>`."; } ctx.CheckSystemPrivacy(target.System, target.PronounPrivacy); if (ctx.MatchRaw()) { if (target.Pronouns == null) { await ctx.Reply(noPronounsSetMessage); } else { await ctx.Reply($"```\n{target.Pronouns}\n```"); } return; } if (!ctx.HasNext(false)) { if (target.Pronouns == null) { await ctx.Reply(noPronounsSetMessage); } else { await ctx.Reply( $"**{target.NameFor(ctx)}**'s pronouns are **{target.Pronouns}**.\nTo print the pronouns with formatting, type `pk;member {target.Reference(ctx)} pronouns -raw`." + (ctx.System?.Id == target.System ? $" To clear them, type `pk;member {target.Reference(ctx)} pronouns -clear`." : "")); } return; } ctx.CheckOwnMember(target); if (await ctx.MatchClear("this member's pronouns")) { var patch = new MemberPatch { Pronouns = Partial <string> .Null() }; await ctx.Repository.UpdateMember(target.Id, patch); await ctx.Reply($"{Emojis.Success} Member pronouns cleared."); } else { var pronouns = ctx.RemainderOrNull(false).NormalizeLineEndSpacing(); if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) { throw Errors.StringTooLongError("Pronouns", pronouns.Length, Limits.MaxPronounsLength); } var patch = new MemberPatch { Pronouns = Partial <string> .Present(pronouns) }; await ctx.Repository.UpdateMember(target.Id, patch); await ctx.Reply($"{Emojis.Success} Member pronouns changed."); } }
public async Task DisplayName(Context ctx, PKMember target) { async Task PrintSuccess(string text) { var successStr = text; if (ctx.Guild != null) { var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id)); if (memberGuildConfig.DisplayName != null) { successStr += $" However, this member has a server name set in this server ({ctx.Guild.Name}), and will be proxied using that name, \"{memberGuildConfig.DisplayName}\", here."; } } await ctx.Reply(successStr); } if (await ctx.MatchClear("this member's display name")) { ctx.CheckOwnMember(target); var patch = new MemberPatch { DisplayName = Partial <string> .Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await PrintSuccess($"{Emojis.Success} Member display name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\"."); } else if (!ctx.HasNext()) { // No perms check, display name isn't covered by member privacy var eb = await CreateMemberNameInfoEmbed(ctx, target); if (ctx.System?.Id == target.System) { eb.Description($"To change display name, type `pk;member {target.Reference()} displayname <display name>`.\nTo clear it, type `pk;member {target.Reference()} displayname -clear`."); } await ctx.Reply(embed : eb.Build()); } else { ctx.CheckOwnMember(target); var newDisplayName = ctx.RemainderOrNull(); var patch = new MemberPatch { DisplayName = Partial <string> .Present(newDisplayName) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await PrintSuccess($"{Emojis.Success} Member display name changed. This member will now be proxied using the name \"{newDisplayName}\"."); } }
public async Task ServerName(Context ctx, PKMember target) { ctx.CheckGuildContext(); if (await ctx.MatchClear("this member's server name")) { ctx.CheckOwnMember(target); var patch = new MemberGuildPatch { DisplayName = null }; await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch)); if (target.DisplayName != null) { await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName}\" in this server ({ctx.Guild.Name})."); } else { await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\" in this server ({ctx.Guild.Name})."); } } else if (!ctx.HasNext()) { // No perms check, server name isn't covered by member privacy var eb = await CreateMemberNameInfoEmbed(ctx, target); if (ctx.System?.Id == target.System) { eb.Description($"To change server name, type `pk;member {target.Reference()} servername <server name>`.\nTo clear it, type `pk;member {target.Reference()} servername -clear`."); } await ctx.Reply(embed : eb.Build()); } else { ctx.CheckOwnMember(target); var newServerName = ctx.RemainderOrNull(); var patch = new MemberGuildPatch { DisplayName = newServerName }; await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch)); await ctx.Reply($"{Emojis.Success} Member server name changed. This member will now be proxied using the name \"{newServerName}\" in this server ({ctx.Guild.Name})."); } }
public async Task DisplayName(Context ctx, PKMember target) { async Task PrintSuccess(string text) { var successStr = text; if (ctx.Guild != null) { var memberGuildConfig = await _db.Execute(c => c.QueryOrInsertMemberGuildConfig(ctx.Guild.Id, target.Id)); if (memberGuildConfig.DisplayName != null) { successStr += $" However, this member has a server name set in this server ({ctx.Guild.Name}), and will be proxied using that name, \"{memberGuildConfig.DisplayName}\", here."; } } await ctx.Reply(successStr); } if (MatchClear(ctx)) { CheckEditMemberPermission(ctx, target); target.DisplayName = null; await _data.SaveMember(target); await PrintSuccess($"{Emojis.Success} Member display name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\"."); } else if (!ctx.HasNext()) { // No perms check, display name isn't covered by member privacy var eb = await CreateMemberNameInfoEmbed(ctx, target); if (ctx.System?.Id == target.System) { eb.WithDescription($"To change display name, type `pk;member {target.Hid} displayname <display name>`.\nTo clear it, type `pk;member {target.Hid} displayname -clear`."); } await ctx.Reply(embed : eb.Build()); } else { CheckEditMemberPermission(ctx, target); var newDisplayName = ctx.RemainderOrNull(); target.DisplayName = newDisplayName; await _data.SaveMember(target); await PrintSuccess($"{Emojis.Success} Member display name changed. This member will now be proxied using the name \"{newDisplayName}\"."); } }
public async Task ServerName(Context ctx, PKMember target) { ctx.CheckGuildContext(); if (MatchClear(ctx)) { CheckEditMemberPermission(ctx, target); await _db.Execute(c => c.ExecuteAsync("update member_guild set display_name = null where member = @member and guild = @guild", new { member = target.Id, guild = ctx.Guild.Id })); if (target.DisplayName != null) { await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName}\" in this server ({ctx.Guild.Name})."); } else { await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\" in this server ({ctx.Guild.Name})."); } } else if (!ctx.HasNext()) { // No perms check, server name isn't covered by member privacy var eb = await CreateMemberNameInfoEmbed(ctx, target); if (ctx.System?.Id == target.System) { eb.WithDescription($"To change server name, type `pk;member {target.Hid} servername <server name>`.\nTo clear it, type `pk;member {target.Hid} servername -clear`."); } await ctx.Reply(embed : eb.Build()); } else { CheckEditMemberPermission(ctx, target); var newServerName = ctx.RemainderOrNull(); await _db.Execute(c => c.ExecuteAsync("update member_guild set display_name = @newServerName where member = @member and guild = @guild", new { member = target.Id, guild = ctx.Guild.Id, newServerName })); await ctx.Reply($"{Emojis.Success} Member server name changed. This member will now be proxied using the name \"{newServerName}\" in this server ({ctx.Guild.Name})."); } }
public async Task Privacy(Context ctx, PKMember target, PrivacyLevel?newValueFromCommand) { if (ctx.System == null) { throw Errors.NoSystemError; } if (target.System != ctx.System.Id) { throw Errors.NotOwnMemberError; } // Display privacy settings if (!ctx.HasNext() && newValueFromCommand == null) { await ctx.Reply(embed : CreatePrivacyEmbed(ctx, target)); return; } // Get guild settings (mostly for warnings and such) MemberGuildSettings guildSettings = null; if (ctx.Guild != null) { guildSettings = await _db.Execute(c => c.QueryOrInsertMemberGuildConfig(ctx.Guild.Id, target.Id)); } // Set Privacy Settings PrivacyLevel PopPrivacyLevel(string subjectName) { if (ctx.Match("public", "show", "shown", "visible")) { return(PrivacyLevel.Public); } if (ctx.Match("private", "hide", "hidden")) { return(PrivacyLevel.Private); } if (!ctx.HasNext()) { throw new PKSyntaxError($"You must pass a privacy level for `{subjectName}` (`public` or `private`)"); } throw new PKSyntaxError($"Invalid privacy level `{ctx.PopArgument()}` (must be `public` or `private`)."); } // See if we have a subject given PrivacyLevel newLevel; if (PrivacyUtils.TryParseMemberPrivacy(ctx.PeekArgument(), out var subject)) { // We peeked before, pop it now ctx.PopArgument(); // Read the privacy level from args newLevel = PopPrivacyLevel(subject.Name()); // Set the level on the given subject target.SetPrivacy(subject, newLevel); await _data.SaveMember(target); // Print response var explanation = (subject, newLevel) switch { (MemberPrivacySubject.Name, PrivacyLevel.Private) => "This member's name is now hidden from other systems, and will be replaced by the member's display name.", (MemberPrivacySubject.Description, PrivacyLevel.Private) => "This member's description is now hidden from other systems.", (MemberPrivacySubject.Avatar, PrivacyLevel.Private) => "This member's avatar is now hidden from other systems.", (MemberPrivacySubject.Birthday, PrivacyLevel.Private) => "This member's birthday is now hidden from other systems.", (MemberPrivacySubject.Pronouns, PrivacyLevel.Private) => "This member's pronouns are now hidden from other systems.", (MemberPrivacySubject.Metadata, PrivacyLevel.Private) => "This member's metadata (eg. created timestamp, message count, etc) is now hidden from other systems.", (MemberPrivacySubject.Visibility, PrivacyLevel.Private) => "This member is now hidden from member lists.", (MemberPrivacySubject.Name, PrivacyLevel.Public) => "This member's name is no longer hidden from other systems.", (MemberPrivacySubject.Description, PrivacyLevel.Public) => "This member's description is no longer hidden from other systems.", (MemberPrivacySubject.Avatar, PrivacyLevel.Public) => "This member's avatar is no longer hidden from other systems.", (MemberPrivacySubject.Birthday, PrivacyLevel.Public) => "This member's birthday is no longer hidden from other systems.", (MemberPrivacySubject.Pronouns, PrivacyLevel.Public) => "This member's pronouns are no longer hidden other systems.", (MemberPrivacySubject.Metadata, PrivacyLevel.Public) => "This member's metadata (eg. created timestamp, message count, etc) is no longer hidden from other systems.", (MemberPrivacySubject.Visibility, PrivacyLevel.Public) => "This member is no longer hidden from member lists.", _ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {newLevel})") }; await ctx.Reply($"{Emojis.Success} {target.NameFor(ctx)}'s {subject.Name()} has been set to **{newLevel.LevelName()}**. {explanation}"); } else if (ctx.Match("all") || newValueFromCommand != null) { newLevel = newValueFromCommand ?? PopPrivacyLevel("all"); target.SetAllPrivacy(newLevel); await _data.SaveMember(target); if (newLevel == PrivacyLevel.Private) { await ctx.Reply($"All {target.NameFor(ctx)}'s privacy settings have been set to **{newLevel.LevelName()}**. Other accounts will now see nothing on the member card."); } else { await ctx.Reply($"All {target.NameFor(ctx)}'s privacy settings have been set to **{newLevel.LevelName()}**. Other accounts will now see everything on the member card."); } } else { var subjectList = "`name`, `description`, `avatar`, `birthday`, `pronouns`, `metadata`, `visibility`, or `all`"; throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be {subjectList})."); } // Name privacy only works given a display name if (subject == MemberPrivacySubject.Name && newLevel == PrivacyLevel.Private && target.DisplayName == null) { await ctx.Reply($"{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**."); } // Avatar privacy doesn't apply when proxying if no server avatar is set if (subject == MemberPrivacySubject.Avatar && newLevel == PrivacyLevel.Private && guildSettings?.AvatarUrl == null) { await ctx.Reply($"{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `pk;member {target.Hid} serveravatar`"); } }
public static JObject ToJson(this PKMember member, LookupContext ctx, bool needsLegacyProxyTags = false, string systemStr = null, APIVersion v = APIVersion.V1) { var includePrivacy = ctx == LookupContext.ByOwner; var o = new JObject(); o.Add("id", member.Hid); if (v == APIVersion.V2) { o.Add("uuid", member.Uuid.ToString()); if (systemStr != null) { o.Add("system", systemStr); } } o.Add("name", member.NameFor(ctx)); // o.Add("color", member.ColorPrivacy.CanAccess(ctx) ? member.Color : null); o.Add("display_name", member.NamePrivacy.CanAccess(ctx) ? member.DisplayName : null); o.Add("color", member.Color); o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport()); o.Add("pronouns", member.PronounsFor(ctx)); o.Add("avatar_url", member.AvatarFor(ctx).TryGetCleanCdnUrl()); o.Add("banner", member.DescriptionPrivacy.Get(ctx, member.BannerImage).TryGetCleanCdnUrl()); o.Add("description", member.DescriptionFor(ctx)); o.Add("created", member.CreatedFor(ctx)?.FormatExport()); o.Add("keep_proxy", member.KeepProxy); var tagArray = new JArray(); foreach (var tag in member.ProxyTags) { tagArray.Add(new JObject { { "prefix", tag.Prefix }, { "suffix", tag.Suffix } }); } o.Add("proxy_tags", tagArray); switch (v) { case APIVersion.V1: { o.Add("privacy", includePrivacy ? member.MemberVisibility.LevelName() : null); o.Add("visibility", includePrivacy ? member.MemberVisibility.LevelName() : null); o.Add("name_privacy", includePrivacy ? member.NamePrivacy.LevelName() : null); o.Add("description_privacy", includePrivacy ? member.DescriptionPrivacy.LevelName() : null); o.Add("birthday_privacy", includePrivacy ? member.BirthdayPrivacy.LevelName() : null); o.Add("pronoun_privacy", includePrivacy ? member.PronounPrivacy.LevelName() : null); o.Add("avatar_privacy", includePrivacy ? member.AvatarPrivacy.LevelName() : null); // o.Add("color_privacy", ctx == LookupContext.ByOwner ? (member.ColorPrivacy.LevelName()) : null); o.Add("metadata_privacy", includePrivacy ? member.MetadataPrivacy.LevelName() : null); if (member.ProxyTags.Count > 0 && needsLegacyProxyTags) { // Legacy compatibility only, TODO: remove at some point o.Add("prefix", member.ProxyTags?.FirstOrDefault().Prefix); o.Add("suffix", member.ProxyTags?.FirstOrDefault().Suffix); } break; } case APIVersion.V2: { if (includePrivacy) { var p = new JObject(); p.Add("visibility", member.MemberVisibility.ToJsonString()); p.Add("name_privacy", member.NamePrivacy.ToJsonString()); p.Add("description_privacy", member.DescriptionPrivacy.ToJsonString()); p.Add("birthday_privacy", member.BirthdayPrivacy.ToJsonString()); p.Add("pronoun_privacy", member.PronounPrivacy.ToJsonString()); p.Add("avatar_privacy", member.AvatarPrivacy.ToJsonString()); p.Add("metadata_privacy", member.MetadataPrivacy.ToJsonString()); o.Add("privacy", p); } else { o.Add("privacy", null); } break; } } return(o); }
public async Task <Embed> CreateMemberEmbed(PKSystem system, PKMember member, Guild guild, LookupContext ctx, DateTimeZone zone) { // string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone)); var name = member.NameFor(ctx); if (system.Name != null) { name = $"{name} ({system.Name})"; } uint color; try { color = member.Color?.ToDiscordColor() ?? DiscordUtils.Gray; } catch (ArgumentException) { // Bad API use can cause an invalid color string // this is now fixed in the API, but might still have some remnants in the database // so we just default to a blank color, yolo color = DiscordUtils.Gray; } var guildSettings = guild != null ? await _repo.GetMemberGuild(guild.Id, member.Id) : null; var guildDisplayName = guildSettings?.DisplayName; var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx); var groups = await _repo.GetMemberGroups(member.Id) .Where(g => g.Visibility.CanAccess(ctx)) .OrderBy(g => g.Name, StringComparer.InvariantCultureIgnoreCase) .ToListAsync(); var eb = new EmbedBuilder() .Author(new Embed.EmbedAuthor(name, IconUrl: avatar.TryGetCleanCdnUrl(), Url: $"https://dash.pluralkit.me/profile/m/{member.Hid}")) // .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) .Color(color) .Footer(new Embed.EmbedFooter( $"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {member.Created.FormatZoned(zone)}" : "")}")); if (member.DescriptionPrivacy.CanAccess(ctx)) { eb.Image(new Embed.EmbedImage(member.BannerImage)); } var description = ""; if (member.MemberVisibility == PrivacyLevel.Private) { description += "*(this member is hidden)*\n"; } if (guildSettings?.AvatarUrl != null) { if (member.AvatarFor(ctx) != null) { description += $"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl.TryGetCleanCdnUrl()}) to see the global avatar)*\n"; } else { description += "*(this member has a server-specific avatar set)*\n"; } } if (description != "") { eb.Description(description); } if (avatar != null) { eb.Thumbnail(new Embed.EmbedThumbnail(avatar.TryGetCleanCdnUrl())); } if (!member.DisplayName.EmptyOrNull() && member.NamePrivacy.CanAccess(ctx)) { eb.Field(new Embed.Field("Display Name", member.DisplayName.Truncate(1024), true)); } if (guild != null && guildDisplayName != null) { eb.Field(new Embed.Field($"Server Nickname (for {guild.Name})", guildDisplayName.Truncate(1024), true)); } if (member.BirthdayFor(ctx) != null) { eb.Field(new Embed.Field("Birthdate", member.BirthdayString, true)); } if (member.PronounsFor(ctx) is { } pronouns&& !string.IsNullOrWhiteSpace(pronouns)) { eb.Field(new Embed.Field("Pronouns", pronouns.Truncate(1024), true)); } if (member.MessageCountFor(ctx) is { } count&& count > 0) { eb.Field(new Embed.Field("Message Count", member.MessageCount.ToString(), true)); } if (member.HasProxyTags) { eb.Field(new Embed.Field("Proxy Tags", member.ProxyTagsString("\n").Truncate(1024), true)); } // --- For when this gets added to the member object itself or however they get added // if (member.LastMessage != null && member.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last message:" FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))); // if (member.LastSwitchTime != null && m.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last switched in:", FormatTimestamp(member.LastSwitchTime.Value)); // if (!member.Color.EmptyOrNull() && member.ColorPrivacy.CanAccess(ctx)) eb.AddField("Color", $"#{member.Color}", true); if (!member.Color.EmptyOrNull()) { eb.Field(new Embed.Field("Color", $"#{member.Color}", true)); } if (groups.Count > 0) { // More than 5 groups show in "compact" format without ID var content = groups.Count > 5 ? string.Join(", ", groups.Select(g => g.DisplayName ?? g.Name)) : string.Join("\n", groups.Select(g => $"[`{g.Hid}`] **{g.DisplayName ?? g.Name}**")); eb.Field(new Embed.Field($"Groups ({groups.Count})", content.Truncate(1000))); } if (member.DescriptionFor(ctx) is { } desc) { eb.Field(new Embed.Field("Description", member.Description.NormalizeLineEndSpacing())); } return(eb.Build()); }
public async Task <DiscordEmbed> CreateMemberEmbed(PKSystem system, PKMember member, DiscordGuild guild, LookupContext ctx) { // string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone)); var name = member.NameFor(ctx); if (system.Name != null) { name = $"{name} ({system.Name})"; } DiscordColor color; try { color = member.Color?.ToDiscordColor() ?? DiscordUtils.Gray; } catch (ArgumentException) { // Bad API use can cause an invalid color string // TODO: fix that in the API // for now we just default to a blank color, yolo color = DiscordUtils.Gray; } await using var conn = await _db.Obtain(); var guildSettings = guild != null ? await conn.QueryOrInsertMemberGuildConfig(guild.Id, member.Id) : null; var guildDisplayName = guildSettings?.DisplayName; var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx); var groups = (await conn.QueryMemberGroups(member.Id)).Where(g => g.Visibility.CanAccess(ctx)).ToList(); var eb = new DiscordEmbedBuilder() // TODO: add URL of website when that's up .WithAuthor(name, iconUrl: DiscordUtils.WorkaroundForUrlBug(avatar)) // .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) .WithColor(color) .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {member.Created.FormatZoned(system)}":"")}"); var description = ""; if (member.MemberVisibility == PrivacyLevel.Private) { description += "*(this member is hidden)*\n"; } if (guildSettings?.AvatarUrl != null) { if (member.AvatarFor(ctx) != null) { description += $"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl}) to see the global avatar)*\n"; } else { description += "*(this member has a server-specific avatar set)*\n"; } } if (description != "") { eb.WithDescription(description); } if (avatar != null) { eb.WithThumbnail(avatar); } if (!member.DisplayName.EmptyOrNull() && member.NamePrivacy.CanAccess(ctx)) { eb.AddField("Display Name", member.DisplayName.Truncate(1024), true); } if (guild != null && guildDisplayName != null) { eb.AddField($"Server Nickname (for {guild.Name})", guildDisplayName.Truncate(1024), true); } if (member.BirthdayFor(ctx) != null) { eb.AddField("Birthdate", member.BirthdayString, true); } if (member.PronounsFor(ctx) is {} pronouns&& !string.IsNullOrWhiteSpace(pronouns)) { eb.AddField("Pronouns", pronouns.Truncate(1024), true); } if (member.MessageCountFor(ctx) is {} count&& count > 0) { eb.AddField("Message Count", member.MessageCount.ToString(), true); } if (member.HasProxyTags) { eb.AddField("Proxy Tags", member.ProxyTagsString("\n").Truncate(1024), true); } // --- For when this gets added to the member object itself or however they get added // if (member.LastMessage != null && member.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last message:" FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))); // if (member.LastSwitchTime != null && m.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last switched in:", FormatTimestamp(member.LastSwitchTime.Value)); // if (!member.Color.EmptyOrNull() && member.ColorPrivacy.CanAccess(ctx)) eb.AddField("Color", $"#{member.Color}", true); if (!member.Color.EmptyOrNull()) { eb.AddField("Color", $"#{member.Color}", true); } if (groups.Count > 0) { // More than 5 groups show in "compact" format without ID var content = groups.Count > 5 ? string.Join(", ", groups.Select(g => g.DisplayName ?? g.Name)) : string.Join("\n", groups.Select(g => $"[`{g.Hid}`] **{g.DisplayName ?? g.Name}**")); eb.AddField($"Groups ({groups.Count})", content.Truncate(1000)); } if (member.DescriptionFor(ctx) is {} desc) { eb.AddField("Description", member.Description.NormalizeLineEndSpacing(), false); } return(eb.Build()); }
public static string NameFor(this PKMember member, Context ctx) => member.NameFor(ctx.LookupContextFor(member));