mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-12 08:40:11 +00:00
feat: better parameters handling, implement multi-token matching
This commit is contained in:
parent
b29c51f103
commit
482c923507
14 changed files with 521 additions and 251 deletions
|
|
@ -26,11 +26,9 @@ public class Context
|
|||
private readonly IMetrics _metrics;
|
||||
private readonly CommandMessageService _commandMessageService;
|
||||
|
||||
private Command? _currentCommand;
|
||||
|
||||
public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message,
|
||||
int commandParseOffset, PKSystem senderSystem, SystemConfig config,
|
||||
GuildConfig? guildConfig, string[] prefixes)
|
||||
GuildConfig? guildConfig, string[] prefixes, Parameters parameters)
|
||||
{
|
||||
Message = (Message)message;
|
||||
ShardId = shardId;
|
||||
|
|
@ -50,6 +48,7 @@ public class Context
|
|||
DefaultPrefix = prefixes[0];
|
||||
Rest = provider.Resolve<DiscordApiClient>();
|
||||
Cluster = provider.Resolve<Cluster>();
|
||||
Parameters = parameters;
|
||||
}
|
||||
|
||||
public readonly IDiscordCache Cache;
|
||||
|
|
@ -75,6 +74,7 @@ public class Context
|
|||
|
||||
public readonly string CommandPrefix;
|
||||
public readonly string DefaultPrefix;
|
||||
public readonly Parameters Parameters;
|
||||
|
||||
internal readonly IDatabase Database;
|
||||
internal readonly ModelRepository Repository;
|
||||
|
|
@ -111,8 +111,6 @@ public class Context
|
|||
|
||||
public async Task Execute<T>(Command? commandDef, Func<T, Task> handler, bool deprecated = false)
|
||||
{
|
||||
_currentCommand = commandDef;
|
||||
|
||||
if (deprecated && commandDef != null)
|
||||
{
|
||||
await Reply($"{Emojis.Warn} Server configuration has moved to `{DefaultPrefix}serverconfig`. The command you are trying to run is now `{DefaultPrefix}{commandDef.Key}`.");
|
||||
|
|
@ -153,8 +151,8 @@ public class Context
|
|||
|
||||
public LookupContext LookupContextFor(SystemId systemId)
|
||||
{
|
||||
var hasPrivateOverride = this.MatchFlag("private", "priv");
|
||||
var hasPublicOverride = this.MatchFlag("public", "pub");
|
||||
var hasPrivateOverride = Parameters.HasFlag("private", "priv");
|
||||
var hasPublicOverride = Parameters.HasFlag("public", "pub");
|
||||
|
||||
if (hasPrivateOverride && hasPublicOverride)
|
||||
throw new PKError("Cannot match both public and private flags at the same time.");
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public static class ContextEntityArgumentsExt
|
|||
return null;
|
||||
}
|
||||
|
||||
public static async Task<PKMember> ParseMember(this Context ctx, Parameters parameters, string input, SystemId? restrictToSystem = null)
|
||||
public static async Task<PKMember> ParseMember(this Context ctx, string input, bool byId, SystemId? restrictToSystem = null)
|
||||
{
|
||||
// Member references can have one of three forms, depending on
|
||||
// whether you're in a system or not:
|
||||
|
|
@ -69,7 +69,7 @@ public static class ContextEntityArgumentsExt
|
|||
|
||||
// Skip name / display name matching if the user does not have a system
|
||||
// or if they specifically request by-HID matching
|
||||
if (ctx.System != null && !parameters.HasFlag("id", "by-id"))
|
||||
if (ctx.System != null && !byId)
|
||||
{
|
||||
// First, try finding by member name in system
|
||||
if (await ctx.Repository.GetMemberByName(ctx.System.Id, input) is PKMember memberByName)
|
||||
|
|
@ -169,9 +169,9 @@ public static class ContextEntityArgumentsExt
|
|||
return group;
|
||||
}
|
||||
|
||||
public static string CreateNotFoundError(this Context ctx, Parameters parameters, string entity, string input)
|
||||
public static string CreateNotFoundError(this Context ctx, string entity, string input, bool byId = false)
|
||||
{
|
||||
var isIDOnlyQuery = ctx.System == null || parameters.HasFlag("id", "by-id");
|
||||
var isIDOnlyQuery = ctx.System == null || byId;
|
||||
var inputIsHid = HidUtils.ParseHid(input) != null;
|
||||
|
||||
if (isIDOnlyQuery)
|
||||
|
|
|
|||
64
PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs
Normal file
64
PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot;
|
||||
|
||||
public static class ContextParametersExt
|
||||
{
|
||||
public static async Task<string?> ParamResolveOpaque(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter(
|
||||
ctx, param_name,
|
||||
param => (param as Parameter.Opaque)?.value
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<PKMember?> ParamResolveMember(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter(
|
||||
ctx, param_name,
|
||||
param => (param as Parameter.MemberRef)?.member
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<PKSystem?> ParamResolveSystem(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter(
|
||||
ctx, param_name,
|
||||
param => (param as Parameter.SystemRef)?.system
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<MemberPrivacySubject?> ParamResolveMemberPrivacyTarget(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter(
|
||||
ctx, param_name,
|
||||
param => (param as Parameter.MemberPrivacyTarget)?.target
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<string?> ParamResolvePrivacyLevel(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter(
|
||||
ctx, param_name,
|
||||
param => (param as Parameter.PrivacyLevel)?.level
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<bool?> ParamResolveToggle(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter(
|
||||
ctx, param_name,
|
||||
param => (param as Parameter.Toggle)?.value
|
||||
);
|
||||
}
|
||||
|
||||
// this can never really be false (either it's present and is true or it's not present)
|
||||
// but we keep it nullable for consistency with the other methods
|
||||
public static async Task<bool?> ParamResolveReset(this Context ctx, string param_name)
|
||||
{
|
||||
return await ctx.Parameters.ResolveParameter<bool?>(
|
||||
ctx, param_name,
|
||||
param => param is Parameter.Reset
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,27 @@
|
|||
using System.Diagnostics;
|
||||
using PluralKit.Core;
|
||||
using uniffi.commands;
|
||||
|
||||
namespace PluralKit.Bot;
|
||||
|
||||
// corresponds to the ffi Paramater type, but with stricter types (also avoiding exposing ffi types!)
|
||||
public abstract record Parameter()
|
||||
{
|
||||
public record MemberRef(PKMember member): Parameter;
|
||||
public record SystemRef(PKSystem system): Parameter;
|
||||
public record MemberPrivacyTarget(MemberPrivacySubject target): Parameter;
|
||||
public record PrivacyLevel(string level): Parameter;
|
||||
public record Toggle(bool value): Parameter;
|
||||
public record Opaque(string value): Parameter;
|
||||
public record Reset(): Parameter;
|
||||
}
|
||||
|
||||
public class Parameters
|
||||
{
|
||||
private string _cb { get; init; }
|
||||
private List<string> _args { get; init; }
|
||||
private Dictionary<string, string?> _flags { get; init; }
|
||||
private Dictionary<string, Parameter> _params { get; init; }
|
||||
private Dictionary<string, uniffi.commands.Parameter> _params { get; init; }
|
||||
|
||||
// just used for errors, temporarily
|
||||
public string FullCommand { get; init; }
|
||||
|
|
@ -31,68 +44,61 @@ public class Parameters
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<ResolvedParameters> ResolveParameters(Context ctx)
|
||||
{
|
||||
var parsed_members = await MemberParams().ToAsyncEnumerable().ToDictionaryAwaitAsync(async item => item.Key, async item =>
|
||||
await ctx.ParseMember(this, item.Value) ?? throw new PKError(ctx.CreateNotFoundError(this, "Member", item.Value))
|
||||
);
|
||||
var parsed_systems = await SystemParams().ToAsyncEnumerable().ToDictionaryAwaitAsync(async item => item.Key, async item =>
|
||||
await ctx.ParseSystem(item.Value) ?? throw new PKError(ctx.CreateNotFoundError(this, "System", item.Value))
|
||||
);
|
||||
return new ResolvedParameters(this, parsed_members, parsed_systems);
|
||||
}
|
||||
|
||||
public string Callback()
|
||||
{
|
||||
return _cb;
|
||||
}
|
||||
|
||||
public IDictionary<string, string> Flags()
|
||||
public bool HasFlag(params string[] potentialMatches)
|
||||
{
|
||||
return _flags;
|
||||
return potentialMatches.Any(_flags.ContainsKey);
|
||||
}
|
||||
|
||||
private Dictionary<string, string> Params(Func<ParameterKind, bool> filter)
|
||||
// resolves a single parameter
|
||||
private async Task<Parameter?> ResolveParameter(Context ctx, string param_name)
|
||||
{
|
||||
return _params.Where(item => filter(item.Value.@kind)).ToDictionary(item => item.Key, item => item.Value.@raw);
|
||||
if (!_params.ContainsKey(param_name)) return null;
|
||||
switch (_params[param_name])
|
||||
{
|
||||
case uniffi.commands.Parameter.MemberRef memberRef:
|
||||
var byId = HasFlag("id", "by-id");
|
||||
return new Parameter.MemberRef(
|
||||
await ctx.ParseMember(memberRef.member, byId)
|
||||
?? throw new PKError(ctx.CreateNotFoundError("Member", memberRef.member, byId))
|
||||
);
|
||||
case uniffi.commands.Parameter.SystemRef systemRef:
|
||||
// todo: do we need byId here?
|
||||
return new Parameter.SystemRef(
|
||||
await ctx.ParseSystem(systemRef.system)
|
||||
?? throw new PKError(ctx.CreateNotFoundError("System", systemRef.system))
|
||||
);
|
||||
case uniffi.commands.Parameter.MemberPrivacyTarget memberPrivacyTarget:
|
||||
// this should never really fail...
|
||||
// todo: we shouldn't have *three* different MemberPrivacyTarget types (rust, ffi, c#) syncing the cases will be annoying...
|
||||
if (!MemberPrivacyUtils.TryParseMemberPrivacy(memberPrivacyTarget.target, out var target))
|
||||
throw new PKError($"Invalid member privacy target {memberPrivacyTarget.target}");
|
||||
return new Parameter.MemberPrivacyTarget(target);
|
||||
case uniffi.commands.Parameter.PrivacyLevel privacyLevel:
|
||||
return new Parameter.PrivacyLevel(privacyLevel.level);
|
||||
case uniffi.commands.Parameter.Toggle toggle:
|
||||
return new Parameter.Toggle(toggle.toggle);
|
||||
case uniffi.commands.Parameter.OpaqueString opaque:
|
||||
return new Parameter.Opaque(opaque.raw);
|
||||
case uniffi.commands.Parameter.Reset _:
|
||||
return new Parameter.Reset();
|
||||
}
|
||||
// this should also never happen
|
||||
throw new PKError($"Unknown parameter type for parameter {param_name}");
|
||||
}
|
||||
|
||||
public IDictionary<string, string> Params()
|
||||
public async Task<T> ResolveParameter<T>(Context ctx, string param_name, Func<Parameter, T?> extract_func)
|
||||
{
|
||||
return Params(_ => true);
|
||||
var param = await ResolveParameter(ctx, param_name);
|
||||
// todo: i think this should return null for everything...?
|
||||
if (param == null) return default;
|
||||
return extract_func(param)
|
||||
// this should never really happen (hopefully!), but in case the parameter names dont match up (typos...) between rust <-> c#...
|
||||
// (it would be very cool to have this statically checked somehow..?)
|
||||
?? throw new PKError($"Parameter {param_name.AsCode()} was not found for command {Callback().AsCode()} -- this is a bug!!");
|
||||
}
|
||||
|
||||
public IDictionary<string, string> MemberParams()
|
||||
{
|
||||
return Params(kind => kind == ParameterKind.MemberRef);
|
||||
}
|
||||
|
||||
public IDictionary<string, string> SystemParams()
|
||||
{
|
||||
return Params(kind => kind == ParameterKind.SystemRef);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: im not really sure if this should be the way to go
|
||||
public class ResolvedParameters
|
||||
{
|
||||
public readonly Parameters Raw;
|
||||
public readonly Dictionary<string, PKMember> MemberParams;
|
||||
public readonly Dictionary<string, PKSystem> SystemParams;
|
||||
|
||||
public ResolvedParameters(Parameters parameters, Dictionary<string, PKMember> member_params, Dictionary<string, PKSystem> system_params)
|
||||
{
|
||||
Raw = parameters;
|
||||
MemberParams = member_params;
|
||||
SystemParams = system_params;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this to another file (?)
|
||||
public static class ParametersExt
|
||||
{
|
||||
public static bool HasFlag(this Parameters parameters, params string[] potentialMatches)
|
||||
{
|
||||
return potentialMatches.Any(parameters.Flags().ContainsKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue