Project restructure, Added .Net Core support, Fixed some bugs

This commit is contained in:
Brandon Smith
2015-08-17 18:57:44 -03:00
parent f9f31c3c04
commit 60f74d088a
45 changed files with 830 additions and 164 deletions

View File

@@ -0,0 +1,32 @@
using System;
using System.Threading.Tasks;
namespace Discord
{
public sealed class CommandBuilder
{
private readonly DiscordBotClient _client;
private readonly string _prefix;
private readonly bool _useWhitelist;
public CommandBuilder(DiscordBotClient client, string prefix, bool useWhitelist = false)
{
_client = client;
_prefix = prefix;
_useWhitelist = useWhitelist;
}
public void AddCommandGroup(string cmd, Action<CommandBuilder> config, bool useWhitelist = false)
{
config(new CommandBuilder(_client, _prefix + ' ' + cmd, useWhitelist));
}
public void AddCommand(string cmd, int minArgs, int maxArgs, Action<DiscordBotClient.CommandEventArgs> handler, bool? useWhitelist = null)
{
AddCommand(cmd, minArgs, maxArgs, e => { handler(e); return null; }, useWhitelist);
}
public void AddCommand(string cmd, int minArgs, int maxArgs, Func<DiscordBotClient.CommandEventArgs, Task> handler, bool? useWhitelist = null)
{
_client.AddCommand(cmd != "" ? _prefix + ' ' + cmd : _prefix, minArgs, maxArgs, handler, useWhitelist ?? _useWhitelist);
}
}
}

View File

@@ -0,0 +1,154 @@
using System.Collections.Generic;
namespace Discord
{
public static class CommandParser
{
private enum CommandParserPart
{
None,
CommandName,
Parameter,
QuotedParameter,
DoubleQuotedParameter
}
public static bool Parse(string input, out string command, out string[] args)
{
return Parse(input, out command, out args, true);
}
public static bool ParseArgs(string input, out string[] args)
{
string ignored;
return Parse(input, out ignored, out args, false);
}
private static bool Parse(string input, out string command, out string[] args, bool parseCommand)
{
CommandParserPart currentPart = parseCommand ? CommandParserPart.CommandName : CommandParserPart.None;
int startPosition = 0;
int endPosition = 0;
int inputLength = input.Length;
bool isEscaped = false;
List<string> argList = new List<string>();
command = null;
args = null;
if (input == "")
return false;
while (endPosition < inputLength)
{
char currentChar = input[endPosition++];
if (isEscaped)
isEscaped = false;
else if (currentChar == '\\')
isEscaped = true;
switch (currentPart)
{
case CommandParserPart.CommandName:
if ((!isEscaped && currentChar == ' ') || endPosition >= inputLength)
{
int length = (currentChar == ' ' ? endPosition - 1 : endPosition) - startPosition;
string temp = input.Substring(startPosition, length);
if (temp == "")
startPosition = endPosition;
else
{
currentPart = CommandParserPart.None;
command = temp;
startPosition = endPosition;
}
}
break;
case CommandParserPart.None:
if ((!isEscaped && currentChar == '\"'))
{
currentPart = CommandParserPart.DoubleQuotedParameter;
startPosition = endPosition;
}
else if ((!isEscaped && currentChar == '\''))
{
currentPart = CommandParserPart.QuotedParameter;
startPosition = endPosition;
}
else if ((!isEscaped && currentChar == ' ') || endPosition >= inputLength)
{
int length = (currentChar == ' ' ? endPosition - 1 : endPosition) - startPosition;
string temp = input.Substring(startPosition, length);
if (temp == "")
startPosition = endPosition;
else
{
currentPart = CommandParserPart.None;
argList.Add(temp);
startPosition = endPosition;
}
}
break;
case CommandParserPart.QuotedParameter:
if ((!isEscaped && currentChar == '\''))
{
string temp = input.Substring(startPosition, endPosition - startPosition - 1);
currentPart = CommandParserPart.None;
argList.Add(temp);
startPosition = endPosition;
}
else if (endPosition >= inputLength)
return false;
break;
case CommandParserPart.DoubleQuotedParameter:
if ((!isEscaped && currentChar == '\"'))
{
string temp = input.Substring(startPosition, endPosition - startPosition - 1);
currentPart = CommandParserPart.None;
argList.Add(temp);
startPosition = endPosition;
}
else if (endPosition >= inputLength)
return false;
break;
}
}
if (parseCommand && (command == null || command == ""))
return false;
args = argList.ToArray();
return true;
}
public static bool ArgsEqual(string[] args, int expected)
{
return args.Length == expected;
}
public static bool ArgsAtLeast(string[] args, int expected)
{
return args.Length >= expected;
}
public static bool ArgsAtMost(string[] args, int expected)
{
return args.Length <= expected;
}
public static bool ArgsIn(string[] args, params int[] expected)
{
int count = args.Length;
for (int i = 0; i < expected.Length; i++)
{
if (count == expected[i])
return true;
}
return false;
}
public static bool ArgsBetween(string[] args, int min, int max)
{
return args.Length >= min && args.Length <= max;
}
public static bool NoArgs(string[] args, params int[] expected)
{
return args.Length == 0;
}
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>19793545-ef89-48f4-8100-3ebaad0a9141</ProjectGuid>
<RootNamespace>Discord.Net.Commands</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,50 @@
using System;
namespace Discord
{
public partial class DiscordBotClient : DiscordClient
{
public class CommandEventArgs
{
public readonly Message Message;
public readonly Command Command;
public readonly string[] Args;
public User User => Message.User;
public string UserId => Message.UserId;
public Channel Channel => Message.Channel;
public string ChannelId => Message.ChannelId;
public Server Server => Message.Channel.Server;
public string ServerId => Message.Channel.ServerId;
public CommandEventArgs(Message message, Command command, string[] args)
{
Message = message;
Command = command;
Args = args;
}
}
public class CommandErrorEventArgs : CommandEventArgs
{
public readonly Exception Exception;
public CommandErrorEventArgs(Message message, Command command, string[] args, Exception ex)
: base(message, command, args)
{
Exception = ex;
}
}
public event EventHandler<CommandEventArgs> RanCommand;
private void RaiseRanCommand(CommandEventArgs args)
{
if (RanCommand != null)
RanCommand(this, args);
}
public event EventHandler<CommandErrorEventArgs> CommandError;
private void RaiseCommandError(Message msg, Command command, string[] args, Exception ex)
{
if (CommandError != null)
CommandError(this, new CommandErrorEventArgs(msg, command, args, ex));
}
}
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Discord
{
public sealed class Command
{
public readonly string[] Text;
public readonly int MinArgs, MaxArgs;
public readonly bool UseWhitelist;
internal readonly Func<DiscordBotClient.CommandEventArgs, Task> Handler;
public Command(string[] text, int minArgs, int maxArgs, bool useWhitelist, Func<DiscordBotClient.CommandEventArgs, Task> handler)
{
Text = text;
MinArgs = minArgs;
MaxArgs = maxArgs;
UseWhitelist = useWhitelist;
Handler = handler;
}
}
/// <summary>
/// A Discord.Net client with extensions for handling common bot operations like text commands.
/// </summary>
public partial class DiscordBotClient : DiscordClient
{
private List<Command> _commands;
private List<string> _whitelist;
public IEnumerable<Command> Commands => _commands;
public char CommandChar { get; set; }
public bool UseCommandChar { get; set; }
public bool RequireCommandCharInPublic { get; set; }
public bool RequireCommandCharInPrivate { get; set; }
public bool AlwaysUseWhitelist { get; set; }
public DiscordBotClient()
{
_commands = new List<Command>();
_whitelist = new List<string>();
CommandChar = '~';
RequireCommandCharInPublic = true;
RequireCommandCharInPrivate = true;
AlwaysUseWhitelist = false;
MessageCreated += async (s, e) =>
{
//Ignore messages from ourselves
if (e.Message.UserId == UserId)
return;
//Check the global whitelist
if (AlwaysUseWhitelist && !_whitelist.Contains(e.Message.UserId))
return;
//Check for the command character
string msg = e.Message.Text;
if (UseCommandChar)
{
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;
}
string[] args;
if (!CommandParser.ParseArgs(msg, out args))
return;
for (int i = 0; i < _commands.Count; i++)
{
Command cmd = _commands[i];
//Check Command Parts
if (args.Length < cmd.Text.Length)
continue;
bool isValid = true;
for (int j = 0; j < cmd.Text.Length; j++)
{
if (!string.Equals(args[j], cmd.Text[j], StringComparison.OrdinalIgnoreCase))
{
isValid = false;
break;
}
}
if (!isValid)
continue;
//Check Whitelist
if (cmd.UseWhitelist && !_whitelist.Contains(e.Message.UserId))
continue;
//Check Arg Count
int argCount = args.Length - cmd.Text.Length;
if (argCount < cmd.MinArgs || argCount > cmd.MaxArgs)
continue;
//Run Command
string[] newArgs = new string[argCount];
for (int j = 0; j < newArgs.Length; j++)
newArgs[j] = args[j + cmd.Text.Length];
var eventArgs = new CommandEventArgs(e.Message, cmd, newArgs);
RaiseRanCommand(eventArgs);
try
{
var task = cmd.Handler(eventArgs);
if (task != null)
await task;
}
catch (Exception ex)
{
RaiseCommandError(e.Message, cmd, newArgs, ex);
}
break;
}
};
}
public void AddCommandGroup(string cmd, Action<CommandBuilder> config, bool useWhitelist = false)
{
config(new CommandBuilder(this, cmd, useWhitelist));
}
public void AddCommand(string cmd, int minArgs, int maxArgs, Action<CommandEventArgs> handler, bool useWhitelist = false)
{
AddCommand(cmd, minArgs, maxArgs, e => { handler(e); return null; }, useWhitelist);
}
public void AddCommand(string cmd, int minArgs, int maxArgs, Func<CommandEventArgs, Task> handler, bool useWhitelist = false)
{
_commands.Add(new Command(cmd.Split(' '), minArgs, maxArgs, useWhitelist, handler));
}
public void AddWhitelist(User user)
=> AddWhitelist(user.Id);
public void AddWhitelist(string userId)
{
_whitelist.Add(userId);
}
}
}

View File

@@ -0,0 +1,26 @@
{
"version": "0.2.0-*",
"description": "A small Discord.Net extension to make bot creation easier.",
"authors": [ "RogueException" ],
"tags": [ "discord", "discordapp" ],
"projectUrl": "https://github.com/RogueException/Discord.Net",
"licenseUrl": "http://opensource.org/licenses/MIT",
"repository": {
"type": "git",
"url": "git://github.com/RogueException/Discord.Net"
},
"compilationOptions": {
"warningsAsErrors": true
},
"dependencies": {
"Discord.Net": ""
},
"frameworks": {
"dotnet": {
"dependencies": {
"System.Runtime": "4.0.20",
"Microsoft.CSharp": "4.0.0"
}
}
}
}