feat: implement member proxy commands

This commit is contained in:
dusk 2025-09-04 01:22:34 +03:00
parent 4a7ee0deb0
commit 1196d87fe7
No known key found for this signature in database
3 changed files with 159 additions and 149 deletions

View file

@ -44,6 +44,11 @@ public partial class CommandTree
Commands.MemberServerKeepproxyShow(var param, _) => ctx.Execute<MemberEdit>(MemberServerKeepProxy, m => m.ShowServerKeepProxy(ctx, param.target)),
Commands.MemberServerKeepproxyUpdate(var param, _) => ctx.Execute<MemberEdit>(MemberServerKeepProxy, m => m.ChangeServerKeepProxy(ctx, param.target, param.value)),
Commands.MemberServerKeepproxyClear(var param, var flags) => ctx.Execute<MemberEdit>(MemberServerKeepProxy, m => m.ClearServerKeepProxy(ctx, param.target, flags.yes)),
Commands.MemberProxyShow(var param, _) => ctx.Execute<MemberProxy>(MemberProxy, m => m.ShowProxy(ctx, param.target)),
Commands.MemberProxyClear(var param, var flags) => ctx.Execute<MemberProxy>(MemberProxy, m => m.ClearProxy(ctx, param.target)),
Commands.MemberProxyAdd(var param, _) => ctx.Execute<MemberProxy>(MemberProxy, m => m.AddProxy(ctx, param.target, param.tag)),
Commands.MemberProxyRemove(var param, _) => ctx.Execute<MemberProxy>(MemberProxy, m => m.RemoveProxy(ctx, param.target, param.tag)),
Commands.MemberProxySet(var param, _) => ctx.Execute<MemberProxy>(MemberProxy, m => m.SetProxy(ctx, param.target, param.tags)),
Commands.MemberTtsShow(var param, _) => ctx.Execute<MemberEdit>(MemberTts, m => m.ShowTts(ctx, param.target)),
Commands.MemberTtsUpdate(var param, _) => ctx.Execute<MemberEdit>(MemberTts, m => m.ChangeTts(ctx, param.target, param.value)),
Commands.MemberAutoproxyShow(var param, _) => ctx.Execute<MemberEdit>(MemberAutoproxy, m => m.ShowAutoproxy(ctx, param.target)),
@ -430,9 +435,7 @@ public partial class CommandTree
private async Task HandleMemberCommandTargeted(Context ctx, PKMember target)
{
// Commands that have a member target (eg. pk;member <member> delete)
if (ctx.Match("proxy", "tags", "proxytags", "brackets"))
await ctx.Execute<MemberProxy>(MemberProxy, m => m.Proxy(ctx, target));
else if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic"))
if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic"))
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.Avatar(ctx, target));
else if (ctx.Match("proxyavatar", "proxypfp", "webhookavatar", "webhookpfp", "pa", "pavatar", "ppfp"))
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.WebhookAvatar(ctx, target));

View file

@ -6,133 +6,120 @@ namespace PluralKit.Bot;
public class MemberProxy
{
public async Task Proxy(Context ctx, PKMember target)
public async Task ShowProxy(Context ctx, PKMember target)
{
if (target.ProxyTags.Count == 0)
await ctx.Reply("This member does not have any proxy tags.");
else
await ctx.Reply($"This member's proxy tags are:\n{target.ProxyTagsString("\n")}");
}
public async Task ClearProxy(Context ctx, PKMember target)
{
ctx.CheckSystem().CheckOwnMember(target);
ProxyTag ParseProxyTags(string exampleProxy)
// If we already have multiple tags, this would clear everything, so prompt that
if (target.ProxyTags.Count > 1)
{
// // Make sure there's one and only one instance of "text" in the example proxy given
var prefixAndSuffix = exampleProxy.Split("text");
if (prefixAndSuffix.Length == 1) prefixAndSuffix = prefixAndSuffix[0].Split("TEXT");
if (prefixAndSuffix.Length < 2) throw Errors.ProxyMustHaveText;
if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText;
return new ProxyTag(prefixAndSuffix[0], prefixAndSuffix[1]);
}
async Task<bool> WarnOnConflict(ProxyTag newTag)
{
var query = "select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix is not distinct from @Prefix and suffix is not distinct from @Suffix and id != @Existing";
var conflicts = (await ctx.Database.Execute(conn => conn.QueryAsync<PKMember>(query,
new { newTag.Prefix, newTag.Suffix, Existing = target.Id, system = target.System }))).ToList();
if (conflicts.Count <= 0) return true;
var conflictList = conflicts.Select(m => $"- **{m.NameFor(ctx)}**");
var msg = $"{Emojis.Warn} The following members have conflicting proxy tags:\n{string.Join('\n', conflictList)}\nDo you want to proceed anyway?";
return await ctx.PromptYesNo(msg, "Proceed");
}
// "Sub"command: clear flag
if (ctx.MatchClear())
{
// If we already have multiple tags, this would clear everything, so prompt that
if (target.ProxyTags.Count > 1)
{
var msg = $"{Emojis.Warn} You already have multiple proxy tags set: {target.ProxyTagsString()}\nDo you want to clear them all?";
if (!await ctx.PromptYesNo(msg, "Clear"))
throw Errors.GenericCancelled();
}
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(new ProxyTag[0]) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Proxy tags cleared.");
}
// "Sub"command: no arguments; will print proxy tags
else if (!ctx.HasNext(false))
{
if (target.ProxyTags.Count == 0)
await ctx.Reply("This member does not have any proxy tags.");
else
await ctx.Reply($"This member's proxy tags are:\n{target.ProxyTagsString("\n")}");
}
// Subcommand: "add"
else if (ctx.Match("add", "append"))
{
if (!ctx.HasNext(false))
throw new PKSyntaxError("You must pass an example proxy to add (eg. `[text]` or `J:text`).");
var tagToAdd = ParseProxyTags(ctx.RemainderOrNull(false).NormalizeLineEndSpacing());
if (tagToAdd.IsEmpty) throw Errors.EmptyProxyTags(target, ctx);
if (target.ProxyTags.Contains(tagToAdd))
throw Errors.ProxyTagAlreadyExists(tagToAdd, target);
if (tagToAdd.ProxyString.Length > Limits.MaxProxyTagLength)
throw new PKError(
$"Proxy tag too long ({tagToAdd.ProxyString.Length} > {Limits.MaxProxyTagLength} characters).");
if (!await WarnOnConflict(tagToAdd))
var msg = $"{Emojis.Warn} You already have multiple proxy tags set: {target.ProxyTagsString()}\nDo you want to clear them all?";
if (!await ctx.PromptYesNo(msg, "Clear"))
throw Errors.GenericCancelled();
var newTags = target.ProxyTags.ToList();
newTags.Add(tagToAdd);
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(newTags.ToArray()) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Added proxy tags {tagToAdd.ProxyString.AsCode()} (using {tagToAdd.ProxyString.Length}/{Limits.MaxProxyTagLength} characters).");
}
// Subcommand: "remove"
else if (ctx.Match("remove", "delete"))
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(new ProxyTag[0]) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Proxy tags cleared.");
}
public async Task AddProxy(Context ctx, PKMember target, string proxyString)
{
ctx.CheckSystem().CheckOwnMember(target);
var tagToAdd = ParseProxyTag(proxyString);
if (tagToAdd.IsEmpty) throw Errors.EmptyProxyTags(target, ctx);
if (target.ProxyTags.Contains(tagToAdd))
throw Errors.ProxyTagAlreadyExists(tagToAdd, target);
if (tagToAdd.ProxyString.Length > Limits.MaxProxyTagLength)
throw new PKError(
$"Proxy tag too long ({tagToAdd.ProxyString.Length} > {Limits.MaxProxyTagLength} characters).");
if (!await WarnOnConflict(ctx, target, tagToAdd))
throw Errors.GenericCancelled();
var newTags = target.ProxyTags.ToList();
newTags.Add(tagToAdd);
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(newTags.ToArray()) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Added proxy tags {tagToAdd.ProxyString.AsCode()} (using {tagToAdd.ProxyString.Length}/{Limits.MaxProxyTagLength} characters).");
}
public async Task RemoveProxy(Context ctx, PKMember target, string proxyString)
{
ctx.CheckSystem().CheckOwnMember(target);
var tagToRemove = ParseProxyTag(proxyString);
if (tagToRemove.IsEmpty) throw Errors.EmptyProxyTags(target, ctx);
if (!target.ProxyTags.Contains(tagToRemove))
throw Errors.ProxyTagDoesNotExist(tagToRemove, target);
var newTags = target.ProxyTags.ToList();
newTags.Remove(tagToRemove);
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(newTags.ToArray()) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Removed proxy tags {tagToRemove.ProxyString.AsCode()}.");
}
public async Task SetProxy(Context ctx, PKMember target, string proxyString)
{
ctx.CheckSystem().CheckOwnMember(target);
var requestedTag = ParseProxyTag(proxyString);
if (requestedTag.IsEmpty) throw Errors.EmptyProxyTags(target, ctx);
if (target.ProxyTags.Count > 1)
{
if (!ctx.HasNext(false))
throw new PKSyntaxError("You must pass a proxy tag to remove (eg. `[text]` or `J:text`).");
var remainder = ctx.RemainderOrNull(false);
var tagToRemove = ParseProxyTags(remainder.NormalizeLineEndSpacing());
if (tagToRemove.IsEmpty) throw Errors.EmptyProxyTags(target, ctx);
if (!target.ProxyTags.Contains(tagToRemove))
{
// Legacy support for when line endings weren't normalized
tagToRemove = ParseProxyTags(remainder);
if (!target.ProxyTags.Contains(tagToRemove))
throw Errors.ProxyTagDoesNotExist(tagToRemove, target);
}
var newTags = target.ProxyTags.ToList();
newTags.Remove(tagToRemove);
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(newTags.ToArray()) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Removed proxy tags {tagToRemove.ProxyString.AsCode()}.");
}
// Subcommand: bare proxy tag given
else
{
var requestedTag = ParseProxyTags(ctx.RemainderOrNull(false).NormalizeLineEndSpacing());
if (requestedTag.IsEmpty) throw Errors.EmptyProxyTags(target, ctx);
// This is mostly a legacy command, so it's gonna warn if there's
// already more than one proxy tag.
if (target.ProxyTags.Count > 1)
{
var msg = $"This member already has more than one proxy tag set: {target.ProxyTagsString()}\nDo you want to replace them?";
if (!await ctx.PromptYesNo(msg, "Replace"))
throw Errors.GenericCancelled();
}
if (requestedTag.ProxyString.Length > Limits.MaxProxyTagLength)
throw new PKError(
$"Proxy tag too long ({requestedTag.ProxyString.Length} > {Limits.MaxProxyTagLength} characters).");
if (!await WarnOnConflict(requestedTag))
var msg = $"This member already has more than one proxy tag set: {target.ProxyTagsString()}\nDo you want to replace them?";
if (!await ctx.PromptYesNo(msg, "Replace"))
throw Errors.GenericCancelled();
var newTags = new[] { requestedTag };
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(newTags) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Member proxy tags set to {requestedTag.ProxyString.AsCode()} (using {requestedTag.ProxyString.Length}/{Limits.MaxProxyTagLength} characters).");
}
if (requestedTag.ProxyString.Length > Limits.MaxProxyTagLength)
throw new PKError(
$"Proxy tag too long ({requestedTag.ProxyString.Length} > {Limits.MaxProxyTagLength} characters).");
if (!await WarnOnConflict(ctx, target, requestedTag))
throw Errors.GenericCancelled();
var newTags = new[] { requestedTag };
var patch = new MemberPatch { ProxyTags = Partial<ProxyTag[]>.Present(newTags) };
await ctx.Repository.UpdateMember(target.Id, patch);
await ctx.Reply($"{Emojis.Success} Member proxy tags set to {requestedTag.ProxyString.AsCode()} (using {requestedTag.ProxyString.Length}/{Limits.MaxProxyTagLength} characters).");
}
private ProxyTag ParseProxyTag(string proxyString)
{
// Make sure there's one and only one instance of "text" in the example proxy given
var prefixAndSuffix = proxyString.Split("text");
if (prefixAndSuffix.Length == 1) prefixAndSuffix = prefixAndSuffix[0].Split("TEXT");
if (prefixAndSuffix.Length < 2) throw Errors.ProxyMustHaveText;
if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText;
return new ProxyTag(prefixAndSuffix[0], prefixAndSuffix[1]);
}
private async Task<bool> WarnOnConflict(Context ctx, PKMember target, ProxyTag newTag)
{
var query = "select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix is not distinct from @Prefix and suffix is not distinct from @Suffix and id != @Existing";
var conflicts = (await ctx.Database.Execute(conn => conn.QueryAsync<PKMember>(query,
new { newTag.Prefix, newTag.Suffix, Existing = target.Id, system = target.System }))).ToList();
if (conflicts.Count <= 0) return true;
var conflictList = conflicts.Select(m => $"- **{m.NameFor(ctx)}**");
var msg = $"{Emojis.Warn} The following members have conflicting proxy tags:\n{string.Join('\n', conflictList)}\nDo you want to proceed anyway?";
return await ctx.PromptYesNo(msg, "Proceed");
}
}