PluralKit/PluralKit.Bot/Commands/MemberEdit.cs

604 lines
31 KiB
C#
Raw Normal View History

2020-03-04 17:13:36 +00:00
using System.Text.RegularExpressions;
2020-02-01 12:03:02 +00:00
using System.Threading.Tasks;
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
using System;
using System.Net.Http;
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
using Myriad.Builders;
2020-02-01 12:03:02 +00:00
using NodaTime;
2020-02-01 12:03:02 +00:00
using PluralKit.Core;
namespace PluralKit.Bot
2020-02-01 12:03:02 +00:00
{
public class MemberEdit
{
2020-06-13 17:36:43 +00:00
private readonly IDatabase _db;
2020-08-29 11:46:27 +00:00
private readonly ModelRepository _repo;
private readonly HttpClient _client;
2020-02-01 12:03:02 +00:00
public MemberEdit(IDatabase db, ModelRepository repo, HttpClient client)
2020-02-01 12:03:02 +00:00
{
_db = db;
2020-08-29 11:46:27 +00:00
_repo = repo;
_client = client;
2020-02-01 12:03:02 +00:00
}
2021-08-27 15:03:47 +00:00
public async Task Name(Context ctx, PKMember target)
{
2021-08-27 15:03:47 +00:00
ctx.CheckSystem().CheckOwnMember(target);
2020-02-01 12:03:02 +00:00
var newName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a new name for the member.");
// Hard name length cap
if (newName.Length > Limits.MaxMemberNameLength)
throw Errors.StringTooLongError("Member name", newName.Length, Limits.MaxMemberNameLength);
2020-02-01 12:03:02 +00:00
// Warn if there's already a member by this name
2020-08-29 11:46:27 +00:00
var existingMember = await _db.Execute(conn => _repo.GetMemberByName(conn, ctx.System.Id, newName));
2021-08-27 15:03:47 +00:00
if (existingMember != null && existingMember.Id != target.Id)
2020-07-28 17:52:57 +00:00
{
2020-07-21 00:10:26 +00:00
var msg = $"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.NameFor(ctx)}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?";
2021-07-02 10:40:40 +00:00
if (!await ctx.PromptYesNo(msg, "Rename")) throw new PKError("Member renaming cancelled.");
2020-02-01 12:03:02 +00:00
}
// Rename the member
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Name = Partial<string>.Present(newName) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2020-02-01 12:03:02 +00:00
await ctx.Reply($"{Emojis.Success} Member renamed.");
if (newName.Contains(" ")) await ctx.Reply($"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it.");
if (target.DisplayName != null) await ctx.Reply($"{Emojis.Note} Note that this member has a display name set ({target.DisplayName}), and will be proxied using that name instead.");
2020-02-01 12:03:02 +00:00
2021-01-31 15:16:52 +00:00
if (ctx.Guild != null)
2020-02-01 12:03:02 +00:00
{
2021-01-31 15:16:52 +00:00
var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
2020-02-01 12:03:02 +00:00
if (memberGuildConfig.DisplayName != null)
2021-01-31 15:16:52 +00:00
await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName}) in this server ({ctx.Guild.Name}), and will be proxied using that name here.");
2020-02-01 12:03:02 +00:00
}
}
2020-03-04 17:13:36 +00:00
2021-08-27 15:03:47 +00:00
public async Task Description(Context ctx, PKMember target)
{
var noDescriptionSetMessage = "This member does not have a description set.";
if (ctx.System?.Id == target.System)
noDescriptionSetMessage += $" To set one, type `pk;member {target.Reference()} description <description>`.";
if (!target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
throw Errors.LookupNotAllowed;
2021-09-13 05:22:40 +00:00
if (ctx.MatchRaw())
2020-03-04 17:13:36 +00:00
{
2020-02-27 23:23:54 +00:00
if (target.Description == null)
await ctx.Reply(noDescriptionSetMessage);
2020-02-27 23:23:54 +00:00
else
2021-09-13 05:22:40 +00:00
await ctx.Reply($"```\n{target.Description}\n```");
return;
2020-02-27 23:23:54 +00:00
}
2021-09-13 06:33:34 +00:00
if (!ctx.HasNext(false))
2020-03-04 17:13:36 +00:00
{
if (target.Description == null)
await ctx.Reply(noDescriptionSetMessage);
else
2021-09-13 05:22:40 +00:00
await ctx.Reply(embed: new EmbedBuilder()
.Title("Member description")
.Description(target.Description)
.Field(new("\u200B", $"To print the description with formatting, type `pk;member {target.Reference()} description -raw`."
+ (ctx.System?.Id == target.System ? $" To clear it, type `pk;member {target.Reference()} description -clear`." : "")))
.Build());
return;
}
ctx.CheckOwnMember(target);
2020-02-27 23:23:54 +00:00
if (await ctx.MatchClear("this member's description"))
{
var patch = new MemberPatch { Description = Partial<string>.Null() };
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
await ctx.Reply($"{Emojis.Success} Member description cleared.");
}
else
{
var description = ctx.RemainderOrNull(skipFlags: false).NormalizeLineEndSpacing();
2020-03-04 17:13:36 +00:00
if (description.IsLongerThan(Limits.MaxDescriptionLength))
throw Errors.StringTooLongError("Description", description.Length, Limits.MaxDescriptionLength);
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Description = Partial<string>.Present(description) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
await ctx.Reply($"{Emojis.Success} Member description changed.");
}
2020-02-01 12:03:02 +00:00
}
2021-08-27 15:03:47 +00:00
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()} pronouns <pronouns>`.";
if (!target.PronounPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
throw Errors.LookupNotAllowed;
2021-09-13 05:22:40 +00:00
if (ctx.MatchRaw())
2020-03-04 17:13:36 +00:00
{
if (target.Pronouns == null)
await ctx.Reply(noPronounsSetMessage);
2020-03-04 17:13:36 +00:00
else
2021-09-13 05:22:40 +00:00
await ctx.Reply($"```\n{target.Pronouns}\n```");
return;
2020-03-04 17:13:36 +00:00
}
2021-09-13 06:33:34 +00:00
if (!ctx.HasNext(false))
2020-03-04 17:13:36 +00:00
{
if (target.Pronouns == null)
await ctx.Reply(noPronounsSetMessage);
else
2021-09-13 05:22:40 +00:00
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`." : ""));
return;
}
2020-02-01 12:03:02 +00:00
ctx.CheckOwnMember(target);
if (await ctx.MatchClear("this member's pronouns"))
{
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
{
var pronouns = ctx.RemainderOrNull(skipFlags: false).NormalizeLineEndSpacing();
2020-03-04 17:13:36 +00:00
if (pronouns.IsLongerThan(Limits.MaxPronounsLength))
throw Errors.StringTooLongError("Pronouns", pronouns.Length, Limits.MaxPronounsLength);
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Pronouns = Partial<string>.Present(pronouns) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
await ctx.Reply($"{Emojis.Success} Member pronouns changed.");
}
2020-02-01 12:03:02 +00:00
}
2021-08-02 17:46:12 +00:00
public async Task BannerImage(Context ctx, PKMember target)
{
ctx.CheckOwnMember(target);
async Task ClearBannerImage()
{
2021-08-27 15:03:47 +00:00
await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch { BannerImage = null }));
2021-08-02 17:46:12 +00:00
await ctx.Reply($"{Emojis.Success} Member banner image cleared.");
}
async Task SetBannerImage(ParsedImage img)
{
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, isFullSizeImage: true);
2021-08-02 17:46:12 +00:00
2021-08-27 15:03:47 +00:00
await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch { BannerImage = img.Url }));
2021-08-02 17:46:12 +00:00
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;
2021-08-27 15:03:47 +00:00
await (hasEmbed
? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build())
2021-08-02 17:46:12 +00:00
: 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(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();
2021-08-27 15:03:47 +00:00
else if (await ctx.MatchImage() is { } img)
2021-08-02 17:46:12 +00:00
await SetBannerImage(img);
else
await ShowBannerImage();
}
2020-02-01 12:03:02 +00:00
public async Task Color(Context ctx, PKMember target)
{
var color = ctx.RemainderOrNull();
if (await ctx.MatchClear())
2020-02-01 12:03:02 +00:00
{
ctx.CheckOwnMember(target);
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Color = Partial<string>.Null() };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
await ctx.Reply($"{Emojis.Success} Member color cleared.");
2020-02-01 12:03:02 +00:00
}
2020-03-04 17:13:36 +00:00
else if (!ctx.HasNext())
{
// if (!target.ColorPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
// throw Errors.LookupNotAllowed;
2020-02-01 12:03:02 +00:00
2020-03-04 17:13:36 +00:00
if (target.Color == null)
if (ctx.System?.Id == target.System)
await ctx.Reply(
$"This member does not have a color set. To set one, type `pk;member {target.Reference()} color <color>`.");
2020-03-04 17:13:36 +00:00
else
await ctx.Reply("This member does not have a color set.");
else
await ctx.Reply(embed: new EmbedBuilder()
.Title("Member color")
2021-01-15 10:29:43 +00:00
.Color(target.Color.ToDiscordColor())
.Thumbnail(new($"https://fakeimg.pl/256x256/{target.Color}/?text=%20"))
.Description($"This member's color is **#{target.Color}**."
+ (ctx.System?.Id == target.System ? $" To clear it, type `pk;member {target.Reference()} color -clear`." : ""))
2020-03-04 17:13:36 +00:00
.Build());
}
else
{
ctx.CheckOwnMember(target);
2020-02-01 12:03:02 +00:00
2020-03-04 17:13:36 +00:00
if (color.StartsWith("#")) color = color.Substring(1);
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Color = Partial<string>.Present(color.ToLowerInvariant()) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2020-03-04 17:13:36 +00:00
await ctx.Reply(embed: new EmbedBuilder()
.Title($"{Emojis.Success} Member color changed.")
2021-01-15 10:29:43 +00:00
.Color(color.ToDiscordColor())
.Thumbnail(new($"https://fakeimg.pl/256x256/{color}/?text=%20"))
2020-03-04 17:13:36 +00:00
.Build());
}
2020-02-01 12:03:02 +00:00
}
public async Task Birthday(Context ctx, PKMember target)
{
if (await ctx.MatchClear("this member's birthday"))
2020-03-04 17:13:36 +00:00
{
ctx.CheckOwnMember(target);
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Birthday = Partial<LocalDate?>.Null() };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2020-03-04 17:13:36 +00:00
await ctx.Reply($"{Emojis.Success} Member birthdate cleared.");
2021-08-27 15:03:47 +00:00
}
2020-03-04 17:13:36 +00:00
else if (!ctx.HasNext())
2020-02-01 12:03:02 +00:00
{
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
if (!target.BirthdayPrivacy.CanAccess(ctx.LookupContextFor(target.System)))
throw Errors.LookupNotAllowed;
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
if (target.Birthday == null)
await ctx.Reply("This member does not have a birthdate set."
+ (ctx.System?.Id == target.System ? $" To set one, type `pk;member {target.Reference()} birthdate <birthdate>`." : ""));
2020-03-04 17:13:36 +00:00
else
await ctx.Reply($"This member's birthdate is **{target.BirthdayString}**."
+ (ctx.System?.Id == target.System ? $" To clear it, type `pk;member {target.Reference()} birthdate -clear`." : ""));
2020-03-04 17:13:36 +00:00
}
else
{
ctx.CheckOwnMember(target);
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
var birthdayStr = ctx.RemainderOrNull();
var birthday = DateUtils.ParseDate(birthdayStr, true);
if (birthday == null) throw Errors.BirthdayParseError(birthdayStr);
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { Birthday = Partial<LocalDate?>.Present(birthday) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2020-03-04 17:13:36 +00:00
await ctx.Reply($"{Emojis.Success} Member birthdate changed.");
2020-02-01 12:03:02 +00:00
}
}
2021-08-27 15:03:47 +00:00
private async Task<EmbedBuilder> CreateMemberNameInfoEmbed(Context ctx, PKMember target)
2020-03-04 17:13:36 +00:00
{
var lcx = ctx.LookupContextFor(target);
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
MemberGuildSettings memberGuildConfig = null;
2021-01-31 15:16:52 +00:00
if (ctx.Guild != null)
memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
2020-02-01 12:03:02 +00:00
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."));
2020-02-01 12:03:02 +00:00
2020-03-04 17:13:36 +00:00
if (target.DisplayName == null && memberGuildConfig?.DisplayName == null)
eb.Field(new("Name", $"**{target.NameFor(ctx)}**"));
2020-03-04 17:13:36 +00:00
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)*"));
}
2020-02-01 12:03:02 +00:00
2021-01-31 15:16:52 +00:00
if (ctx.Guild != null)
2020-02-01 12:03:02 +00:00
{
2020-03-04 17:13:36 +00:00
if (memberGuildConfig?.DisplayName != null)
2021-01-31 15:16:52 +00:00
eb.Field(new($"Server Name (in {ctx.Guild.Name})", $"**{memberGuildConfig.DisplayName}**"));
2020-03-04 17:13:36 +00:00
else
2021-01-31 15:16:52 +00:00
eb.Field(new($"Server Name (in {ctx.Guild.Name})", memberGuildConfig?.DisplayName ?? "*(none)*"));
2020-02-01 12:03:02 +00:00
}
2020-03-04 17:13:36 +00:00
return eb;
2020-02-01 12:03:02 +00:00
}
2020-03-04 17:13:36 +00:00
public async Task DisplayName(Context ctx, PKMember target)
2020-02-01 12:03:02 +00:00
{
2020-03-04 17:13:36 +00:00
async Task PrintSuccess(string text)
{
var successStr = text;
2021-01-31 15:16:52 +00:00
if (ctx.Guild != null)
2020-03-04 17:13:36 +00:00
{
2021-01-31 15:16:52 +00:00
var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
2020-03-04 17:13:36 +00:00
if (memberGuildConfig.DisplayName != null)
2021-01-31 15:16:52 +00:00
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.";
2020-03-04 17:13:36 +00:00
}
await ctx.Reply(successStr);
}
2021-08-27 15:03:47 +00:00
var noDisplayNameSetMessage = "This member does not have a display name set.";
if (ctx.System?.Id == target.System)
noDisplayNameSetMessage += $" To set one, type `pk;member {target.Reference()} displayname <display name>`.";
// No perms check, display name isn't covered by member privacy
2021-09-13 05:22:40 +00:00
if (ctx.MatchRaw())
{
if (target.DisplayName == null)
await ctx.Reply(noDisplayNameSetMessage);
else
await ctx.Reply($"```\n{target.DisplayName}\n```");
return;
}
2021-09-13 06:33:34 +00:00
if (!ctx.HasNext(false))
2020-03-04 17:13:36 +00:00
{
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>`."
+ $"To clear it, type `pk;member {target.Reference()} displayname -clear`."
+ $"To print the raw display name, type `pk;member {target.Reference()} displayname -raw`.");
await ctx.Reply(embed: eb.Build());
return;
}
2021-08-27 15:03:47 +00:00
ctx.CheckOwnMember(target);
if (await ctx.MatchClear("this member's display name"))
{
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { DisplayName = Partial<string>.Null() };
2020-08-29 11:46:27 +00:00
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)}\".");
2020-03-04 17:13:36 +00:00
}
else
{
var newDisplayName = ctx.RemainderOrNull(skipFlags: false).NormalizeLineEndSpacing();
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { DisplayName = Partial<string>.Present(newDisplayName) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2020-03-04 17:13:36 +00:00
await PrintSuccess($"{Emojis.Success} Member display name changed. This member will now be proxied using the name \"{newDisplayName}\".");
2020-03-04 17:13:36 +00:00
}
}
2021-08-27 15:03:47 +00:00
2020-03-04 17:13:36 +00:00
public async Task ServerName(Context ctx, PKMember target)
{
2020-02-01 12:03:02 +00:00
ctx.CheckGuildContext();
2021-08-27 15:03:47 +00:00
var noServerNameSetMessage = "This member does not have a server name set.";
if (ctx.System?.Id == target.System)
noServerNameSetMessage += $" To set one, type `pk;member {target.Reference()} servername <server name>`.";
// No perms check, display name isn't covered by member privacy
2021-09-13 05:22:40 +00:00
if (ctx.MatchRaw())
{
MemberGuildSettings memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
if (memberGuildConfig.DisplayName == null)
await ctx.Reply(noServerNameSetMessage);
else
await ctx.Reply($"```\n{memberGuildConfig.DisplayName}\n```");
return;
}
2021-09-13 06:33:34 +00:00
if (!ctx.HasNext(false))
2021-09-13 05:22:40 +00:00
{
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`.\nTo print the raw server name, type `pk;member {target.Reference()} servername -raw`.");
await ctx.Reply(embed: eb.Build());
return;
}
ctx.CheckOwnMember(target);
if (await ctx.MatchClear("this member's server name"))
{
2021-08-27 15:03:47 +00:00
var patch = new MemberGuildPatch { DisplayName = null };
2021-01-31 15:16:52 +00:00
await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch));
2020-02-01 12:03:02 +00:00
2020-03-04 17:13:36 +00:00
if (target.DisplayName != null)
2021-01-31 15:16:52 +00:00
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}).");
2020-03-04 17:13:36 +00:00
else
2021-01-31 15:16:52 +00:00
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}).");
2020-03-04 17:13:36 +00:00
}
2020-02-01 12:03:02 +00:00
else
2020-03-04 17:13:36 +00:00
{
var newServerName = ctx.RemainderOrNull(skipFlags: false).NormalizeLineEndSpacing();
2021-08-27 15:03:47 +00:00
var patch = new MemberGuildPatch { DisplayName = newServerName };
2021-01-31 15:16:52 +00:00
await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch));
2020-02-01 12:03:02 +00:00
2021-01-31 15:16:52 +00:00
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}).");
2020-03-04 17:13:36 +00:00
}
2020-02-01 12:03:02 +00:00
}
2021-08-27 15:03:47 +00:00
2020-02-01 12:03:02 +00:00
public async Task KeepProxy(Context ctx, PKMember target)
{
ctx.CheckSystem().CheckOwnMember(target);
2020-02-01 12:03:02 +00:00
bool newValue;
if (ctx.Match("on", "enabled", "true", "yes")) newValue = true;
else if (ctx.Match("off", "disabled", "false", "no")) newValue = false;
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
2020-03-04 17:13:36 +00:00
else
{
if (target.KeepProxy)
await ctx.Reply("This member has keepproxy **enabled**, which means proxy tags will be **included** in the resulting message when proxying.");
else
await ctx.Reply("This member has keepproxy **disabled**, which means proxy tags will **not** be included in the resulting message when proxying.");
return;
};
2020-02-01 12:03:02 +00:00
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { KeepProxy = Partial<bool>.Present(newValue) };
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
2021-08-27 15:03:47 +00:00
2020-02-01 12:03:02 +00:00
if (newValue)
await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying.");
else
await ctx.Reply($"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
}
public async Task MemberAutoproxy(Context ctx, PKMember target)
{
if (ctx.System == null) throw Errors.NoSystemError;
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
bool newValue;
if (ctx.Match("on", "enabled", "true", "yes") || ctx.MatchFlag("on", "enabled", "true", "yes")) newValue = true;
else if (ctx.Match("off", "disabled", "false", "no") || ctx.MatchFlag("off", "disabled", "false", "no")) newValue = false;
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
else
{
if (target.AllowAutoproxy)
await ctx.Reply("Latch/front autoproxy are **enabled** for this member. This member will be automatically proxied when autoproxy is set to latch or front mode.");
else
await ctx.Reply("Latch/front autoproxy are **disabled** for this member. This member will not be automatically proxied when autoproxy is set to latch or front mode.");
return;
};
2021-08-27 15:03:47 +00:00
var patch = new MemberPatch { AllowAutoproxy = Partial<bool>.Present(newValue) };
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
if (newValue)
await ctx.Reply($"{Emojis.Success} Latch / front autoproxy have been **enabled** for this member.");
else
await ctx.Reply($"{Emojis.Success} Latch / front autoproxy have been **disabled** for this member.");
}
public async Task Privacy(Context ctx, PKMember target, PrivacyLevel? newValueFromCommand)
2020-02-01 12:03:02 +00:00
{
ctx.CheckSystem().CheckOwnMember(target);
2020-02-01 12:03:02 +00:00
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
// Display privacy settings
if (!ctx.HasNext() && newValueFromCommand == null)
2020-03-04 17:13:36 +00:00
{
await ctx.Reply(embed: new EmbedBuilder()
.Title($"Current privacy settings for {target.NameFor(ctx)}")
2021-08-27 15:03:47 +00:00
.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()))
2021-08-27 15:03:47 +00:00
.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`.")
2021-08-27 15:03:47 +00:00
.Build());
2020-03-04 17:13:36 +00:00
return;
}
2021-08-27 15:03:47 +00:00
// Get guild settings (mostly for warnings and such)
MemberGuildSettings guildSettings = null;
2021-01-31 15:16:52 +00:00
if (ctx.Guild != null)
guildSettings = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
2020-02-01 12:03:02 +00:00
async Task SetAll(PrivacyLevel level)
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
{
2020-08-29 11:46:27 +00:00
await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch().WithAllPrivacy(level)));
2021-08-27 15:03:47 +00:00
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.");
2021-08-27 15:03:47 +00:00
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.");
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
}
async Task SetLevel(MemberPrivacySubject subject, PrivacyLevel level)
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
{
2020-08-29 11:46:27 +00:00
await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch().WithPrivacy(subject, level)));
2021-08-27 15:03:47 +00:00
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}")
};
2021-08-27 15:03:47 +00:00
var explanation = (subject, level) switch
2020-06-17 21:06:49 +00:00
{
(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.",
2020-06-20 14:00:50 +00:00
(MemberPrivacySubject.Avatar, PrivacyLevel.Private) => "This member's avatar is now hidden from other systems.",
2020-06-17 21:06:49 +00:00
(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.",
2021-08-27 15:03:47 +00:00
2020-06-17 21:06:49 +00:00
(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.",
2020-06-20 14:00:50 +00:00
(MemberPrivacySubject.Avatar, PrivacyLevel.Public) => "This member's avatar is no longer hidden from other systems.",
2020-06-17 21:06:49 +00:00
(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.",
2021-08-27 15:03:47 +00:00
_ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})")
2020-06-17 21:06:49 +00:00
};
2021-08-27 15:03:47 +00:00
await ctx.Reply($"{Emojis.Success} {target.NameFor(ctx)}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}");
2021-08-27 15:03:47 +00:00
// 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**.");
2021-08-27 15:03:47 +00:00
// 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`");
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
}
if (ctx.Match("all") || newValueFromCommand != null)
await SetAll(newValueFromCommand ?? ctx.PopPrivacyLevel());
2020-02-01 12:03:02 +00:00
else
await SetLevel(ctx.PopMemberPrivacySubject(), ctx.PopPrivacyLevel());
2020-02-01 12:03:02 +00:00
}
2021-08-27 15:03:47 +00:00
2020-02-01 12:03:02 +00:00
public async Task Delete(Context ctx, PKMember target)
{
ctx.CheckSystem().CheckOwnMember(target);
2021-08-27 15:03:47 +00:00
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!***__");
2020-02-01 12:03:02 +00:00
if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled;
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
await _db.Execute(conn => _repo.DeleteMember(conn, target.Id));
2021-08-27 15:03:47 +00:00
2020-02-01 12:03:02 +00:00
await ctx.Reply($"{Emojis.Success} Member deleted.");
}
}
2021-08-27 15:03:47 +00:00
}