Revamped CommandsPlugin

This uses a dictionary for the commands list, if a command has a max
args set it'll only get that amount, will call the UnkownCommand event,
and now has a built in help command that can be optionally enabled.
CommandChar is now a list, but a single character can still be used.
Externally, not much should have changed, but commands can be hidden
from the help command and a description can be set. There's probably
more that I've forgotten about.
This commit is contained in:
Googie2149
2015-10-28 23:11:15 -04:00
parent affcc806db
commit db4e8a1ec6
5 changed files with 229 additions and 98 deletions

View File

@@ -30,6 +30,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Modules", "src\
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "src\Discord.Net.Modules.Net45\Discord.Net.Modules.csproj", "{3091164F-66AE-4543-A63D-167C1116241D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "src\Discord.Net.Modules.Net45\Discord.Net.Modules.csproj", "{3091164F-66AE-4543-A63D-167C1116241D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestingProject", "TestingProject\TestingProject.csproj", "{6CD8116D-6749-4174-81EB-C4EB4B1F185B}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -79,6 +81,12 @@ Global
{3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU
{6CD8116D-6749-4174-81EB-C4EB4B1F185B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CD8116D-6749-4174-81EB-C4EB4B1F185B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CD8116D-6749-4174-81EB-C4EB4B1F185B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{6CD8116D-6749-4174-81EB-C4EB4B1F185B}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{6CD8116D-6749-4174-81EB-C4EB4B1F185B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CD8116D-6749-4174-81EB-C4EB4B1F185B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -93,5 +101,6 @@ Global
{1B5603B4-6F8F-4289-B945-7BAAE523D740} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} {1B5603B4-6F8F-4289-B945-7BAAE523D740} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{01584E8A-78DA-486F-9EF9-A894E435841B} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} {01584E8A-78DA-486F-9EF9-A894E435841B} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35}
{3091164F-66AE-4543-A63D-167C1116241D} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} {3091164F-66AE-4543-A63D-167C1116241D} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{6CD8116D-6749-4174-81EB-C4EB4B1F185B} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@@ -9,13 +9,15 @@ namespace Discord.Commands
public int? MinArgs { get; internal set; } public int? MinArgs { get; internal set; }
public int? MaxArgs { get; internal set; } public int? MaxArgs { get; internal set; }
public int MinPerms { get; internal set; } public int MinPerms { get; internal set; }
internal readonly string[] Parts; public bool IsHidden { get; internal set; }
public string Description { get; internal set; }
internal Func<CommandEventArgs, Task> Handler; internal Func<CommandEventArgs, Task> Handler;
internal Command(string text) internal Command(string text)
{ {
Text = text; Text = text;
Parts = text.ToLowerInvariant().Split(' '); IsHidden = false; // Set false by default to avoid null error
Description = "No description set for this command.";
} }
} }
} }

View File

@@ -54,6 +54,18 @@ namespace Discord.Commands
return this; return this;
} }
public CommandBuilder Desc(string desc)
{
_command.Description = desc;
return this;
}
public CommandBuilder IsHidden()
{
_command.IsHidden = true;
return this;
}
public CommandBuilder Do(Func<CommandEventArgs, Task> func) public CommandBuilder Do(Func<CommandEventArgs, Task> func)
{ {
_command.Handler = func; _command.Handler = func;

View File

@@ -3,10 +3,12 @@
namespace Discord.Commands namespace Discord.Commands
{ {
public class PermissionException : Exception { public PermissionException() : base("User does not have permission to run this command.") { } } public class PermissionException : Exception { public PermissionException() : base("User does not have permission to run this command.") { } }
public class ArgumentException : Exception { public ArgumentException() : base("This command requires more arguments.") { } }
public class CommandEventArgs public class CommandEventArgs
{ {
public Message Message { get; } public Message Message { get; }
public Command Command { get; } public Command Command { get; }
public string MessageText { get; }
public string CommandText { get; } public string CommandText { get; }
public string ArgText { get; } public string ArgText { get; }
public int? Permissions { get; } public int? Permissions { get; }
@@ -16,10 +18,11 @@ namespace Discord.Commands
public Channel Channel => Message.Channel; public Channel Channel => Message.Channel;
public Server Server => Message.Channel.Server; public Server Server => Message.Channel.Server;
public CommandEventArgs(Message message, Command command, string commandText, string argText, int? permissions, string[] args) public CommandEventArgs(Message message, Command command, string messageText, string commandText, string argText, int? permissions, string[] args)
{ {
Message = message; Message = message;
Command = command; Command = command;
MessageText = messageText;
CommandText = commandText; CommandText = commandText;
ArgText = argText; ArgText = argText;
Permissions = permissions; Permissions = permissions;
@@ -31,7 +34,7 @@ namespace Discord.Commands
public Exception Exception { get; } public Exception Exception { get; }
public CommandErrorEventArgs(CommandEventArgs baseArgs, Exception ex) public CommandErrorEventArgs(CommandEventArgs baseArgs, Exception ex)
: base(baseArgs.Message, baseArgs.Command, baseArgs.CommandText, baseArgs.ArgText, baseArgs.Permissions, baseArgs.Args) : base(baseArgs.Message, baseArgs.Command, baseArgs.MessageText, baseArgs.CommandText, baseArgs.ArgText, baseArgs.Permissions, baseArgs.Args)
{ {
Exception = ex; Exception = ex;
} }

View File

@@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Discord.Commands namespace Discord.Commands
{ {
@@ -7,132 +10,234 @@ namespace Discord.Commands
public partial class CommandsPlugin public partial class CommandsPlugin
{ {
private readonly DiscordClient _client; private readonly DiscordClient _client;
private List<Command> _commands;
private Func<User, int> _getPermissions; private Func<User, int> _getPermissions;
public IEnumerable<Command> Commands => _commands; private Dictionary<string, Command> _commands;
public char CommandChar { get; set; } public Dictionary<string, Command> Commands => _commands;
public char CommandChar { get { return CommandChars[0]; } set { CommandChars = new List<char> { value }; } } // This could possibly be removed entirely. Not sure.
public List<char> CommandChars { get; set; }
public bool UseCommandChar { get; set; } public bool UseCommandChar { get; set; }
public bool RequireCommandCharInPublic { get; set; } public bool RequireCommandCharInPublic { get; set; }
public bool RequireCommandCharInPrivate { get; set; } public bool RequireCommandCharInPrivate { get; set; }
public bool HelpInPublic { get; set; }
public CommandsPlugin(DiscordClient client, Func<User, int> getPermissions = null) public CommandsPlugin(DiscordClient client, Func<User, int> getPermissions = null, bool builtInHelp = false)
{ {
_client = client; _client = client; // Wait why is this even set
_getPermissions = getPermissions; _getPermissions = getPermissions;
_commands = new List<Command>();
CommandChar = '/'; _commands = new Dictionary<string, Command>();
UseCommandChar = false;
RequireCommandCharInPublic = true;
RequireCommandCharInPrivate = true;
client.MessageReceived += async (s, e) => CommandChar = '!'; // Kept around to keep from possibly throwing an error. Might not be necessary.
{ CommandChars = new List<char> { '!', '?', '/' };
//If commands aren't being used, don't bother processing them UseCommandChar = true;
if (_commands.Count == 0) RequireCommandCharInPublic = true;
return; RequireCommandCharInPrivate = true;
HelpInPublic = true;
//Ignore messages from ourselves if (builtInHelp)
if (e.Message.User == client.CurrentUser) {
return; CreateCommand("help")
.ArgsBetween(0, 1)
.IsHidden()
.Desc("Returns information about commands.")
.Do(async e =>
{
if (e.Command.Text != "help")
{
await Reply(e, CommandDetails(e.Command));
}
else
{
if (e.Args == null)
{
StringBuilder output = new StringBuilder();
bool first = true;
output.AppendLine("These are the commands you can use:");
output.Append("`");
int permissions = getPermissions(e.User);
foreach (KeyValuePair<string, Command> k in _commands)
{
if (permissions >= k.Value.MinPerms && !k.Value.IsHidden)
if (first)
{
output.Append(k.Key);
first = false;
}
else
output.Append($", {k.Key}");
}
output.Append("`");
//Check for the command character if (CommandChars.Count == 1)
string msg = e.Message.Text; output.AppendLine($"{Environment.NewLine}You can use `{CommandChars[0]}` to call a command.");
if (UseCommandChar) else
{ output.AppendLine($"{Environment.NewLine}You can use `{String.Join(" ", CommandChars.Take(CommandChars.Count - 1))}` and `{CommandChars.Last()}` to call a command.");
if (msg.Length == 0)
return;
bool isPrivate = e.Message.Channel.IsPrivate;
bool hasCommandChar = msg[0] == CommandChar;
if (hasCommandChar)
msg = msg.Substring(1);
if (!isPrivate && RequireCommandCharInPublic && !hasCommandChar)
return;
if (isPrivate && RequireCommandCharInPrivate && !hasCommandChar)
return;
}
CommandPart[] args; output.AppendLine("`help <command>` can tell you more about how to use a command.");
if (!CommandParser.ParseArgs(msg, out args))
return;
for (int i = 0; i < _commands.Count; i++) await Reply(e, output.ToString());
{ }
Command cmd = _commands[i]; else
{
if (_commands.ContainsKey(e.Args[0]))
await Reply(e, CommandDetails(_commands[e.Args[0]]));
else
await Reply(e, $"`{e.Args[0]}` is not a valid command.");
}
}
});
//Check Command Parts }
if (args.Length < cmd.Parts.Length)
continue;
bool isValid = true; client.MessageReceived += async (s, e) =>
for (int j = 0; j < cmd.Parts.Length; j++) {
{ // This will need to be changed once a built in help command is made
if (!string.Equals(args[j].Value, cmd.Parts[j], StringComparison.OrdinalIgnoreCase)) if (_commands.Count == 0)
{ return;
isValid = false;
break;
}
}
if (!isValid)
continue;
//Check Arg Count if (e.Message.IsAuthor)
int argCount = args.Length - cmd.Parts.Length; return;
if (argCount < cmd.MinArgs || argCount > cmd.MaxArgs)
continue;
//Clean Args string msg = e.Message.Text;
string[] newArgs = new string[argCount];
for (int j = 0; j < newArgs.Length; j++)
newArgs[j] = args[j + cmd.Parts.Length].Value;
//Get ArgText if (msg.Length == 0)
string argText; return;
if (argCount == 0)
argText = "";
else
argText = msg.Substring(args[cmd.Parts.Length].Index);
//Check Permissions if (UseCommandChar)
int permissions = _getPermissions != null ? _getPermissions(e.Message.User) : 0; {
var eventArgs = new CommandEventArgs(e.Message, cmd, msg, argText, permissions, newArgs); bool isPrivate = e.Message.Channel.IsPrivate;
if (permissions < cmd.MinPerms) bool hasCommandChar = CommandChars.Contains(msg[0]);
{ if (hasCommandChar)
RaiseCommandError(eventArgs, new PermissionException()); msg = msg.Substring(1);
return;
}
//Run Command if (isPrivate && RequireCommandCharInPrivate && !hasCommandChar)
return; // If private, and command char is required, and it doesn't have it, ignore it.
if (!isPrivate && RequireCommandCharInPublic && !hasCommandChar)
return; // Same, but public.
}
string cmd;
CommandPart[] args;
if (!CommandParser.Parse(msg, out cmd, out args))
return;
if (_commands.ContainsKey(cmd))
{
Command comm = _commands[cmd];
//Get ArgText
int argCount = args.Length;
string argText;
if (argCount == 0)
argText = "";
else
argText = msg.Substring(args[0].Index);
//Clean Args
string[] newArgs = null;
if (comm.MaxArgs != null && argCount > 0)
{
newArgs = new string[(int)comm.MaxArgs];
for (int j = 0; j < newArgs.Length; j++)
newArgs[j] = args[j].Value;
}
// Check permissions here
int permissions = _getPermissions != null ? _getPermissions(e.Message.User) : 0;
var eventArgs = new CommandEventArgs(e.Message, comm, msg, cmd, argText, permissions, newArgs);
if (permissions < comm.MinPerms)
{
RaiseCommandError(eventArgs, new PermissionException());
return;
}
//Check Arg Count
if (argCount < comm.MinArgs)
{
RaiseCommandError(eventArgs, new ArgumentException());
if (builtInHelp)
await _commands["help"].Handler(eventArgs);
return;
}
// Actually run the command here
RaiseRanCommand(eventArgs); RaiseRanCommand(eventArgs);
try try
{ {
var task = cmd.Handler(eventArgs); var task = comm.Handler(eventArgs);
if (task != null) if (task != null)
await task.ConfigureAwait(false); await task.ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
RaiseCommandError(eventArgs, ex); RaiseCommandError(eventArgs, ex);
} }
break; }
} else
}; {
} CommandEventArgs eventArgs = new CommandEventArgs(e.Message, null, msg, cmd, null, null, null);
RaiseUnknownCommand(eventArgs);
if (builtInHelp)
await Reply(eventArgs, $"Command `cmd` does not exist.");
return;
}
};
}
internal string CommandDetails(Command comm)
{
StringBuilder output = new StringBuilder();
output.Append($"`{comm.Text}`");
if (comm.MinArgs != null && comm.MaxArgs != null)
{
if (comm.MinArgs == comm.MaxArgs)
{
if (comm.MaxArgs != 0)
output.Append($" {comm.MinArgs.ToString()} Args");
}
else
output.Append($" {comm.MinArgs.ToString()} - {comm.MaxArgs.ToString()} Args");
}
else if (comm.MinArgs != null && comm.MaxArgs == null)
{
output.Append($" ≥{comm.MinArgs.ToString()} Args");
}
else if (comm.MinArgs == null && comm.MaxArgs != null)
{
output.Append($" ≤{comm.MaxArgs.ToString()} Args");
}
output.Append($": {comm.Description}");
return output.ToString();
}
internal async Task Reply(CommandEventArgs e, string message)
{
if (HelpInPublic)
await _client.SendMessage(e.Channel, message);
else
await _client.SendPrivateMessage(e.User, message);
}
public void CreateCommandGroup(string cmd, Action<CommandGroupBuilder> config = null) public void CreateCommandGroup(string cmd, Action<CommandGroupBuilder> config = null)
=> config(new CommandGroupBuilder(this, cmd, 0)); => config(new CommandGroupBuilder(this, cmd, 0));
public CommandBuilder CreateCommand(string cmd) public CommandBuilder CreateCommand(string cmd)
{ {
var command = new Command(cmd); var command = new Command(cmd);
_commands.Add(command); _commands.Add(cmd, command);
return new CommandBuilder(command); return new CommandBuilder(command);
} }
internal void AddCommand(Command command) internal void AddCommand(Command command)
{ {
_commands.Add(command); _commands.Add(command.Text, command);
} }
} }
} }