Split up Context class into more extension methods
This commit is contained in:
parent
53036da6a5
commit
7fef8c1dde
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using App.Metrics;
|
using App.Metrics;
|
||||||
@ -9,9 +8,7 @@ using Autofac;
|
|||||||
|
|
||||||
using DSharpPlus;
|
using DSharpPlus;
|
||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
using DSharpPlus.Exceptions;
|
|
||||||
|
|
||||||
using PluralKit.Bot.Utils;
|
|
||||||
using PluralKit.Core;
|
using PluralKit.Core;
|
||||||
|
|
||||||
namespace PluralKit.Bot
|
namespace PluralKit.Bot
|
||||||
@ -59,12 +56,11 @@ namespace PluralKit.Bot
|
|||||||
public DiscordRestClient Rest => _rest;
|
public DiscordRestClient Rest => _rest;
|
||||||
|
|
||||||
public PKSystem System => _senderSystem;
|
public PKSystem System => _senderSystem;
|
||||||
|
|
||||||
|
public Parameters Parameters => _parameters;
|
||||||
|
|
||||||
public string PopArgument() => _parameters.Pop();
|
// TODO: this is just here so the extension methods can access it; should it be public/private/?
|
||||||
public string PeekArgument() => _parameters.Peek();
|
internal IDataStore DataStore => _data;
|
||||||
public string RemainderOrNull(bool skipFlags = true) => _parameters.Remainder(skipFlags).Length == 0 ? null : _parameters.Remainder(skipFlags);
|
|
||||||
public bool HasNext(bool skipFlags = true) => RemainderOrNull(skipFlags) != null;
|
|
||||||
public string FullCommand => _parameters.FullCommand;
|
|
||||||
|
|
||||||
public Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
public Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
||||||
{
|
{
|
||||||
@ -76,42 +72,6 @@ namespace PluralKit.Bot
|
|||||||
throw new PKError("PluralKit does not have permission to send embeds in this channel. Please ensure I have the **Embed Links** permission enabled.");
|
throw new PKError("PluralKit does not have permission to send embeds in this channel. Please ensure I have the **Embed Links** permission enabled.");
|
||||||
return Channel.SendMessageFixedAsync(text, embed: embed, mentions: mentions);
|
return Channel.SendMessageFixedAsync(text, embed: embed, mentions: mentions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the next parameter is equal to one of the given keywords. Case-insensitive.
|
|
||||||
/// </summary>
|
|
||||||
public bool Match(ref string used, params string[] potentialMatches)
|
|
||||||
{
|
|
||||||
var arg = PeekArgument();
|
|
||||||
foreach (var match in potentialMatches)
|
|
||||||
{
|
|
||||||
if (arg.Equals(match, StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
used = PopArgument();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the next parameter is equal to one of the given keywords. Case-insensitive.
|
|
||||||
/// </summary>
|
|
||||||
public bool Match(params string[] potentialMatches)
|
|
||||||
{
|
|
||||||
string used = null; // Unused and unreturned, we just yeet it
|
|
||||||
return Match(ref used, potentialMatches);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MatchFlag(params string[] potentialMatches)
|
|
||||||
{
|
|
||||||
// Flags are *ALWAYS PARSED LOWERCASE*. This means we skip out on a "ToLower" call here.
|
|
||||||
// Can assume the caller array only contains lowercase *and* the set below only contains lowercase
|
|
||||||
|
|
||||||
var flags = _parameters.Flags();
|
|
||||||
return potentialMatches.Any(potentialMatch => flags.Contains(potentialMatch));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute<T>(Command commandDef, Func<T, Task> handler)
|
public async Task Execute<T>(Command commandDef, Func<T, Task> handler)
|
||||||
{
|
{
|
||||||
@ -137,109 +97,6 @@ namespace PluralKit.Bot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DiscordUser> MatchUser()
|
|
||||||
{
|
|
||||||
var text = PeekArgument();
|
|
||||||
if (text.TryParseMention(out var id))
|
|
||||||
return await Shard.GetUserAsync(id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MatchUserRaw(out ulong id)
|
|
||||||
{
|
|
||||||
id = 0;
|
|
||||||
|
|
||||||
var text = PeekArgument();
|
|
||||||
if (text.TryParseMention(out var mentionId))
|
|
||||||
id = mentionId;
|
|
||||||
|
|
||||||
return id != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<PKSystem> PeekSystem() => MatchSystemInner();
|
|
||||||
|
|
||||||
public async Task<PKSystem> MatchSystem()
|
|
||||||
{
|
|
||||||
var system = await MatchSystemInner();
|
|
||||||
if (system != null) PopArgument();
|
|
||||||
return system;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<PKSystem> MatchSystemInner()
|
|
||||||
{
|
|
||||||
var input = PeekArgument();
|
|
||||||
|
|
||||||
// System references can take three forms:
|
|
||||||
// - The direct user ID of an account connected to the system
|
|
||||||
// - A @mention of an account connected to the system (<@uid>)
|
|
||||||
// - A system hid
|
|
||||||
|
|
||||||
// Direct IDs and mentions are both handled by the below method:
|
|
||||||
if (input.TryParseMention(out var id))
|
|
||||||
return await _data.GetSystemByAccount(id);
|
|
||||||
|
|
||||||
// Finally, try HID parsing
|
|
||||||
var system = await _data.GetSystemByHid(input);
|
|
||||||
return system;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<PKMember> PeekMember()
|
|
||||||
{
|
|
||||||
var input = PeekArgument();
|
|
||||||
|
|
||||||
// Member references can have one of three forms, depending on
|
|
||||||
// whether you're in a system or not:
|
|
||||||
// - A member hid
|
|
||||||
// - A textual name of a member *in your own system*
|
|
||||||
// - a textual display name of a member *in your own system*
|
|
||||||
|
|
||||||
// First, if we have a system, try finding by member name in system
|
|
||||||
if (_senderSystem != null && await _data.GetMemberByName(_senderSystem, input) is PKMember memberByName)
|
|
||||||
return memberByName;
|
|
||||||
|
|
||||||
// Then, try member HID parsing:
|
|
||||||
if (await _data.GetMemberByHid(input) is PKMember memberByHid)
|
|
||||||
return memberByHid;
|
|
||||||
|
|
||||||
// And if that again fails, we try finding a member with a display name matching the argument from the system
|
|
||||||
if (_senderSystem != null && await _data.GetMemberByDisplayName(_senderSystem, input) is PKMember memberByDisplayName)
|
|
||||||
return memberByDisplayName;
|
|
||||||
|
|
||||||
// We didn't find anything, so we return null.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to pop a member descriptor from the stack, returning it if present. If a member could not be
|
|
||||||
/// resolved by the next word in the argument stack, does *not* touch the stack, and returns null.
|
|
||||||
/// </summary>
|
|
||||||
public async Task<PKMember> MatchMember()
|
|
||||||
{
|
|
||||||
// First, peek a member
|
|
||||||
var member = await PeekMember();
|
|
||||||
|
|
||||||
// If the peek was successful, we've used up the next argument, so we pop that just to get rid of it.
|
|
||||||
if (member != null) PopArgument();
|
|
||||||
|
|
||||||
// Finally, we return the member value.
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CreateMemberNotFoundError(string input)
|
|
||||||
{
|
|
||||||
// TODO: does this belong here?
|
|
||||||
if (input.Length == 5)
|
|
||||||
{
|
|
||||||
if (_senderSystem != null)
|
|
||||||
return $"Member with ID or name \"{input}\" not found.";
|
|
||||||
return $"Member with ID \"{input}\" not found."; // Accounts without systems can't query by name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_senderSystem != null)
|
|
||||||
return $"Member with name \"{input}\" not found. Note that a member ID is 5 characters long.";
|
|
||||||
return $"Member not found. Note that a member ID is 5 characters long.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public LookupContext LookupContextFor(PKSystem target) =>
|
public LookupContext LookupContextFor(PKSystem target) =>
|
||||||
System?.Id == target.Id ? LookupContext.ByOwner : LookupContext.ByNonOwner;
|
System?.Id == target.Id ? LookupContext.ByOwner : LookupContext.ByNonOwner;
|
||||||
|
|
||||||
@ -248,30 +105,7 @@ namespace PluralKit.Bot
|
|||||||
|
|
||||||
public LookupContext LookupContextFor(PKMember target) =>
|
public LookupContext LookupContextFor(PKMember target) =>
|
||||||
System?.Id == target.System ? LookupContext.ByOwner : LookupContext.ByNonOwner;
|
System?.Id == target.System ? LookupContext.ByOwner : LookupContext.ByNonOwner;
|
||||||
|
|
||||||
public async Task<DiscordChannel> MatchChannel()
|
|
||||||
{
|
|
||||||
if (!MentionUtils.TryParseChannel(PeekArgument(), out var channel))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var discordChannel = await _shard.GetChannelAsync(channel);
|
|
||||||
if (discordChannel.Type != ChannelType.Text) return null;
|
|
||||||
|
|
||||||
PopArgument();
|
|
||||||
return discordChannel;
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (UnauthorizedException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IComponentContext Services => _provider;
|
public IComponentContext Services => _provider;
|
||||||
}
|
}
|
||||||
}
|
}
|
59
PluralKit.Bot/CommandSystem/ContextArgumentsExt.cs
Normal file
59
PluralKit.Bot/CommandSystem/ContextArgumentsExt.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace PluralKit.Bot
|
||||||
|
{
|
||||||
|
public static class ContextArgumentsExt
|
||||||
|
{
|
||||||
|
public static string PopArgument(this Context ctx) =>
|
||||||
|
ctx.Parameters.Pop();
|
||||||
|
|
||||||
|
public static string PeekArgument(this Context ctx) =>
|
||||||
|
ctx.Parameters.Peek();
|
||||||
|
|
||||||
|
public static string RemainderOrNull(this Context ctx, bool skipFlags = true) =>
|
||||||
|
ctx.Parameters.Remainder(skipFlags).Length == 0 ? null : ctx.Parameters.Remainder(skipFlags);
|
||||||
|
|
||||||
|
public static bool HasNext(this Context ctx, bool skipFlags = true) =>
|
||||||
|
ctx.RemainderOrNull(skipFlags) != null;
|
||||||
|
|
||||||
|
public static string FullCommand(this Context ctx) =>
|
||||||
|
ctx.Parameters.FullCommand;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the next parameter is equal to one of the given keywords. Case-insensitive.
|
||||||
|
/// </summary>
|
||||||
|
public static bool Match(this Context ctx, ref string used, params string[] potentialMatches)
|
||||||
|
{
|
||||||
|
var arg = ctx.PeekArgument();
|
||||||
|
foreach (var match in potentialMatches)
|
||||||
|
{
|
||||||
|
if (arg.Equals(match, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
used = ctx.PopArgument();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the next parameter is equal to one of the given keywords. Case-insensitive.
|
||||||
|
/// </summary>
|
||||||
|
public static bool Match(this Context ctx, params string[] potentialMatches)
|
||||||
|
{
|
||||||
|
string used = null; // Unused and unreturned, we just yeet it
|
||||||
|
return ctx.Match(ref used, potentialMatches);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool MatchFlag(this Context ctx, params string[] potentialMatches)
|
||||||
|
{
|
||||||
|
// Flags are *ALWAYS PARSED LOWERCASE*. This means we skip out on a "ToLower" call here.
|
||||||
|
// Can assume the caller array only contains lowercase *and* the set below only contains lowercase
|
||||||
|
|
||||||
|
var flags = ctx.Parameters.Flags();
|
||||||
|
return potentialMatches.Any(potentialMatch => flags.Contains(potentialMatch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
using System.Threading.Channels;
|
using DSharpPlus;
|
||||||
|
|
||||||
using DSharpPlus;
|
|
||||||
|
|
||||||
using PluralKit.Core;
|
using PluralKit.Core;
|
||||||
|
|
||||||
namespace PluralKit.Bot
|
namespace PluralKit.Bot
|
||||||
{
|
{
|
||||||
public static class ContextChecks
|
public static class ContextChecksExt
|
||||||
{
|
{
|
||||||
public static Context CheckGuildContext(this Context ctx)
|
public static Context CheckGuildContext(this Context ctx)
|
||||||
{
|
{
|
140
PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs
Normal file
140
PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using DSharpPlus;
|
||||||
|
using DSharpPlus.Entities;
|
||||||
|
using DSharpPlus.Exceptions;
|
||||||
|
|
||||||
|
using PluralKit.Bot.Utils;
|
||||||
|
using PluralKit.Core;
|
||||||
|
|
||||||
|
namespace PluralKit.Bot
|
||||||
|
{
|
||||||
|
public static class ContextEntityArgumentsExt
|
||||||
|
{
|
||||||
|
public static async Task<DiscordUser> MatchUser(this Context ctx)
|
||||||
|
{
|
||||||
|
var text = ctx.PeekArgument();
|
||||||
|
if (text.TryParseMention(out var id))
|
||||||
|
return await ctx.Shard.GetUserAsync(id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool MatchUserRaw(this Context ctx, out ulong id)
|
||||||
|
{
|
||||||
|
id = 0;
|
||||||
|
|
||||||
|
var text = ctx.PeekArgument();
|
||||||
|
if (text.TryParseMention(out var mentionId))
|
||||||
|
id = mentionId;
|
||||||
|
|
||||||
|
return id != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<PKSystem> PeekSystem(this Context ctx) => ctx.MatchSystemInner();
|
||||||
|
|
||||||
|
public static async Task<PKSystem> MatchSystem(this Context ctx)
|
||||||
|
{
|
||||||
|
var system = await ctx.MatchSystemInner();
|
||||||
|
if (system != null) ctx.PopArgument();
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<PKSystem> MatchSystemInner(this Context ctx)
|
||||||
|
{
|
||||||
|
var input = ctx.PeekArgument();
|
||||||
|
|
||||||
|
// System references can take three forms:
|
||||||
|
// - The direct user ID of an account connected to the system
|
||||||
|
// - A @mention of an account connected to the system (<@uid>)
|
||||||
|
// - A system hid
|
||||||
|
|
||||||
|
// Direct IDs and mentions are both handled by the below method:
|
||||||
|
if (input.TryParseMention(out var id))
|
||||||
|
return await ctx.DataStore.GetSystemByAccount(id);
|
||||||
|
|
||||||
|
// Finally, try HID parsing
|
||||||
|
var system = await ctx.DataStore.GetSystemByHid(input);
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<PKMember> PeekMember(this Context ctx)
|
||||||
|
{
|
||||||
|
var input = ctx.PeekArgument();
|
||||||
|
|
||||||
|
// Member references can have one of three forms, depending on
|
||||||
|
// whether you're in a system or not:
|
||||||
|
// - A member hid
|
||||||
|
// - A textual name of a member *in your own system*
|
||||||
|
// - a textual display name of a member *in your own system*
|
||||||
|
|
||||||
|
// First, if we have a system, try finding by member name in system
|
||||||
|
if (ctx.System != null && await ctx.DataStore.GetMemberByName(ctx.System, input) is PKMember memberByName)
|
||||||
|
return memberByName;
|
||||||
|
|
||||||
|
// Then, try member HID parsing:
|
||||||
|
if (await ctx.DataStore.GetMemberByHid(input) is PKMember memberByHid)
|
||||||
|
return memberByHid;
|
||||||
|
|
||||||
|
// And if that again fails, we try finding a member with a display name matching the argument from the system
|
||||||
|
if (ctx.System != null && await ctx.DataStore.GetMemberByDisplayName(ctx.System, input) is PKMember memberByDisplayName)
|
||||||
|
return memberByDisplayName;
|
||||||
|
|
||||||
|
// We didn't find anything, so we return null.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to pop a member descriptor from the stack, returning it if present. If a member could not be
|
||||||
|
/// resolved by the next word in the argument stack, does *not* touch the stack, and returns null.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<PKMember> MatchMember(this Context ctx)
|
||||||
|
{
|
||||||
|
// First, peek a member
|
||||||
|
var member = await ctx.PeekMember();
|
||||||
|
|
||||||
|
// If the peek was successful, we've used up the next argument, so we pop that just to get rid of it.
|
||||||
|
if (member != null) ctx.PopArgument();
|
||||||
|
|
||||||
|
// Finally, we return the member value.
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateMemberNotFoundError(this Context ctx, string input)
|
||||||
|
{
|
||||||
|
// TODO: does this belong here?
|
||||||
|
if (input.Length == 5)
|
||||||
|
{
|
||||||
|
if (ctx.System != null)
|
||||||
|
return $"Member with ID or name \"{input}\" not found.";
|
||||||
|
return $"Member with ID \"{input}\" not found."; // Accounts without systems can't query by name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.System != null)
|
||||||
|
return $"Member with name \"{input}\" not found. Note that a member ID is 5 characters long.";
|
||||||
|
return $"Member not found. Note that a member ID is 5 characters long.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<DiscordChannel> MatchChannel(this Context ctx)
|
||||||
|
{
|
||||||
|
if (!MentionUtils.TryParseChannel(ctx.PeekArgument(), out var channel))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var discordChannel = await ctx.Shard.GetChannelAsync(channel);
|
||||||
|
if (discordChannel.Type != ChannelType.Text) return null;
|
||||||
|
|
||||||
|
ctx.PopArgument();
|
||||||
|
return discordChannel;
|
||||||
|
}
|
||||||
|
catch (NotFoundException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -327,7 +327,7 @@ namespace PluralKit.Bot
|
|||||||
{
|
{
|
||||||
var commandListStr = CreatePotentialCommandList(potentialCommands);
|
var commandListStr = CreatePotentialCommandList(potentialCommands);
|
||||||
await ctx.Reply(
|
await ctx.Reply(
|
||||||
$"{Emojis.Error} Unknown command `pk;{ctx.FullCommand}`. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see <https://pluralkit.me/commands>.");
|
$"{Emojis.Error} Unknown command `pk;{ctx.FullCommand()}`. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see <https://pluralkit.me/commands>.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PrintCommandExpectedError(Context ctx, params Command[] potentialCommands)
|
private async Task PrintCommandExpectedError(Context ctx, params Command[] potentialCommands)
|
||||||
|
Loading…
Reference in New Issue
Block a user