commit 3a85a9f18fb548dba98d62a845ac997202c10aff Author: Deukhoofd Date: Thu Mar 29 01:34:48 2018 +0200 Initial work diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..553458b --- /dev/null +++ b/.gitignore @@ -0,0 +1,331 @@ +# Created by .ignore support plugin (hsz.mobi) +### VisualStudio template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +/DeukBot4/settings.json +*/.directory +**/.directory +.directory \ No newline at end of file diff --git a/DeukBot4.sln b/DeukBot4.sln new file mode 100644 index 0000000..5528007 --- /dev/null +++ b/DeukBot4.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeukBot4", "DeukBot4\DeukBot4.csproj", "{68999D7B-9A79-42D6-B9D1-6421B42742D9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {68999D7B-9A79-42D6-B9D1-6421B42742D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68999D7B-9A79-42D6-B9D1-6421B42742D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68999D7B-9A79-42D6-B9D1-6421B42742D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68999D7B-9A79-42D6-B9D1-6421B42742D9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/DeukBot4/DeukBot4.csproj b/DeukBot4/DeukBot4.csproj new file mode 100644 index 0000000..08f5001 --- /dev/null +++ b/DeukBot4/DeukBot4.csproj @@ -0,0 +1,11 @@ + + + Exe + netcoreapp2.0 + 7.1 + + + + + + \ No newline at end of file diff --git a/DeukBot4/DeukBot4.csproj.DotSettings b/DeukBot4/DeukBot4.csproj.DotSettings new file mode 100644 index 0000000..7646070 --- /dev/null +++ b/DeukBot4/DeukBot4.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DeukBot4/Logger.cs b/DeukBot4/Logger.cs new file mode 100644 index 0000000..9770d5e --- /dev/null +++ b/DeukBot4/Logger.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using Discord; + +namespace DeukBot4 +{ + public static class Logger + { + private static readonly Dictionary Colors = new Dictionary + { + {LogSeverity.Info, ConsoleColor.Black}, + {LogSeverity.Verbose, ConsoleColor.Black}, + {LogSeverity.Debug, ConsoleColor.Black}, + {LogSeverity.Warning, ConsoleColor.Yellow}, + {LogSeverity.Error, ConsoleColor.Red}, + {LogSeverity.Critical, ConsoleColor.Red} + }; + + public static async Task Log(object o, LogSeverity severity) + { + Console.ForegroundColor = Colors[severity]; + Console.WriteLine($"[{severity}] {DateTime.UtcNow.ToShortTimeString()}: {o.ToString()}"); + Console.ResetColor(); + } + + public static async Task LogDiscord(LogMessage message) + { + Console.ForegroundColor = Colors[message.Severity]; + Console.WriteLine($"[{message.Severity}] {DateTime.UtcNow.ToShortTimeString()}: {message.Message}"); + Console.ResetColor(); + } + + public static async Task Log(object o) + { + await Log(o, LogSeverity.Info); + } + + public static async Task LogWarning(object o) + { + await Log(o, LogSeverity.Warning); + } + + public static async Task LogError(object o) + { + await Log(o, LogSeverity.Error); + } + + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/Attributes/CommandAttribute.cs b/DeukBot4/MessageHandlers/Attributes/CommandAttribute.cs new file mode 100644 index 0000000..2c7e246 --- /dev/null +++ b/DeukBot4/MessageHandlers/Attributes/CommandAttribute.cs @@ -0,0 +1,17 @@ +using System; +using DeukBot4.MessageHandlers.Permissions; + +namespace DeukBot4.MessageHandlers +{ + public class CommandAttribute : Attribute + { + public string Command { get; } + public PermissionLevel Permission { get; } + + public CommandAttribute(string command, PermissionLevel permission) + { + Command = command; + Permission = permission; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/Attributes/CommandHelpAttribute.cs b/DeukBot4/MessageHandlers/Attributes/CommandHelpAttribute.cs new file mode 100644 index 0000000..2b869c3 --- /dev/null +++ b/DeukBot4/MessageHandlers/Attributes/CommandHelpAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace DeukBot4.MessageHandlers +{ + public class CommandHelpAttribute : Attribute + { + public string ShortHelp { get; } + public string LongHelp { get; } + + public CommandHelpAttribute(string shortHelp, string longHelp) + { + ShortHelp = shortHelp; + LongHelp = longHelp; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/Attributes/CommandParametersAttribute.cs b/DeukBot4/MessageHandlers/Attributes/CommandParametersAttribute.cs new file mode 100644 index 0000000..0789b2d --- /dev/null +++ b/DeukBot4/MessageHandlers/Attributes/CommandParametersAttribute.cs @@ -0,0 +1,16 @@ +using System; +using DeukBot4.MessageHandlers.CommandHandler.RequestStructure; + +namespace DeukBot4.MessageHandlers +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class CommandParametersAttribute : Attribute + { + public ParameterMatcher.ParameterType[] Types { get; } + + public CommandParametersAttribute(ParameterMatcher.ParameterType[] types) + { + Types = types; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/Command.cs b/DeukBot4/MessageHandlers/CommandHandler/Command.cs new file mode 100644 index 0000000..f0d58f2 --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/Command.cs @@ -0,0 +1,55 @@ +using System.Diagnostics; +using System.Reflection; +using System.Threading.Tasks; +using DeukBot4.MessageHandlers.CommandHandler.RequestStructure; +using DeukBot4.MessageHandlers.Permissions; +using Discord.WebSocket; + +namespace DeukBot4.MessageHandlers.CommandHandler +{ + public class Command + { + public Command(string name, PermissionLevel permission, string shortHelp, string longHelp, + ParameterMatcher.ParameterType[][] parameterTypes, MethodInfo function, CommandContainerBase commandContainer) + { + Name = name; + Permission = permission; + ShortHelp = shortHelp; + LongHelp = longHelp; + Function = function; + CommandContainer = commandContainer; + ParameterTypes = parameterTypes; + HasHelp = true; + } + + public Command(string name, PermissionLevel permission, ParameterMatcher.ParameterType[][] parameterTypes, + MethodInfo function, CommandContainerBase commandContainer) + { + Name = name; + Permission = permission; + Function = function; + CommandContainer = commandContainer; + ParameterTypes = parameterTypes; + HasHelp = false; + } + + public string Name { get; } + public PermissionLevel Permission { get; } + public string ShortHelp { get; } + public string LongHelp { get; } + public MethodInfo Function { get; } + public CommandContainerBase CommandContainer { get; } + public bool HasHelp { get; } + public ParameterMatcher.ParameterType[][] ParameterTypes { get; } + + private string[] _parameterMatchers; + + public string[] ParametersMatchers => + _parameterMatchers ?? (_parameterMatchers = ParameterMatcher.GenerateRegex(this)); + + public async Task Invoke(CommandRequest request) + { + await (Task) Function.Invoke(CommandContainer, new object[] {request}); + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/CommandContainerBase.cs b/DeukBot4/MessageHandlers/CommandHandler/CommandContainerBase.cs new file mode 100644 index 0000000..f84b7ed --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/CommandContainerBase.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using DeukBot4.MessageHandlers.CommandHandler.RequestStructure; + +namespace DeukBot4.MessageHandlers.CommandHandler +{ + public abstract class CommandContainerBase + { + public abstract string Name { get; } + + public Command[] GetCommands() + { + var funcs = GetType().GetMethods() + .Where(x => x.GetCustomAttributes(typeof(CommandAttribute), true).Length > 0); + var commands = new List(); + + foreach (var methodInfo in funcs) + { + var commandAttributes = methodInfo.GetCustomAttributes(typeof(CommandAttribute), true) + .Select(x => x as CommandAttribute); + + CommandHelpAttribute helpAttribute = null; + var helpAttributes = methodInfo.GetCustomAttributes(typeof(CommandHelpAttribute), true) + .Select(x => x as CommandHelpAttribute); + var commandHelpAttributes = helpAttributes as CommandHelpAttribute[] ?? helpAttributes.ToArray(); + if (commandHelpAttributes.Any()) + helpAttribute = commandHelpAttributes[0]; + + var parametersAttributes = methodInfo.GetCustomAttributes(typeof(CommandParametersAttribute), true) + .Select(x => x as CommandParametersAttribute); + var commandParametersAttributes = parametersAttributes as CommandParametersAttribute[] ?? + parametersAttributes.ToArray(); + var parameters = commandParametersAttributes.Select(x => x.Types).ToArray(); + + foreach (var commandAttribute in commandAttributes) + { + if (commandAttribute == null) + continue; + if (helpAttribute == null) + { + commands.Add(new Command(commandAttribute.Command, commandAttribute.Permission, parameters, + methodInfo, this)); + } + else + { + commands.Add(new Command(commandAttribute.Command, commandAttribute.Permission, + helpAttribute.ShortHelp, helpAttribute.LongHelp, parameters, methodInfo, this)); + + } + } + } + return commands.ToArray(); + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs b/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs new file mode 100644 index 0000000..b29163d --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using DeukBot4.MessageHandlers.CommandHandler.RequestStructure; +using DeukBot4.MessageHandlers.Permissions; +using DeukBot4.Utilities; +using Discord.WebSocket; + +namespace DeukBot4.MessageHandlers.CommandHandler +{ + public static class CommandHandler + { + public static Dictionary Commands { get; private set; } = new Dictionary(); + public const char CommandTrigger = '!'; + + public static void Build() + { + var commandContainers = typeof(CommandHandler).Assembly.GetTypes() + .Where(x => typeof(CommandContainerBase).IsAssignableFrom(x) && !x.IsAbstract); + + foreach (var commandContainer in commandContainers) + { + if (!(Activator.CreateInstance(commandContainer) is CommandContainerBase obj)) + continue; + var commands = obj.GetCommands(); + foreach (var command in commands) + { + Commands.Add(command.Name.ToLowerInvariant(), command); + } + + Logger.Log($"Loaded following commands for container {obj.Name}: {commands.Select(x => x.Name).Join(", ")}"); + + } + + } + + public static async Task HandleMessage(SocketMessage message) + { + if (message.Content[0] != CommandTrigger) return; + var req = CommandRequest.Create(message); + var resultCode = req.Item2; + if (resultCode == CommandRequest.RequestCode.Invalid) + { + await Logger.LogError("Invalid content: " + message.Content); + return; + } + else if (resultCode == CommandRequest.RequestCode.Forbidden) + { + await Logger.Log( + $"Unauthorized user tried to run command: {message.Author.Username} -> {message.Content}"); + + } + else if (resultCode == CommandRequest.RequestCode.OK) + { + await req.Item1.Command.Invoke(req.Item1); + + } + } + + public static Command GetCommand(string name) + { + return Commands.TryGetValue(name.ToLowerInvariant(), out var com) ? com : null; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/GeneralCommands.cs b/DeukBot4/MessageHandlers/CommandHandler/GeneralCommands.cs new file mode 100644 index 0000000..009c5fe --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/GeneralCommands.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using DeukBot4.MessageHandlers.CommandHandler.RequestStructure; +using DeukBot4.MessageHandlers.Permissions; + +namespace DeukBot4.MessageHandlers.CommandHandler +{ + public class GeneralCommands : CommandContainerBase + { + public override string Name => "General"; + + [Command("ping", PermissionLevel.Everyone)] + [CommandHelp("Simple Ping Pong Response", "Generates a simple Pong response when triggered")] + public async Task Ping(CommandRequest request) + { + await request.OriginalMessage.Channel.SendMessageAsync("Pong"); + } + + [Command("help", PermissionLevel.Everyone)] + [CommandParameters(new []{ParameterMatcher.ParameterType.Word})] + [CommandHelp("Displays a list of commands for the bot", +@"Allows you to see all commands you can use for your permission level, along with a description. +usage: +``help`` for a list of all commands useable for you. +``help`` [command name] for more detailed info on a specific command. Note that you need to be able to use the command to get this info." + )] + public async Task Help(CommandRequest request) + { + if (request.Parameters.Length == 0) + { + await request.OriginalMessage.Channel.SendMessageAsync( + HelpCommandGenerator.GenerateFullHelp(request.RequestPermissions)); + } + else + { + await request.OriginalMessage.Channel.SendMessageAsync( + HelpCommandGenerator.GenerateSpecificHelp(request.Parameters[0].AsString(), request.RequestPermissions)); + } + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/HelpCommandGenerator.cs b/DeukBot4/MessageHandlers/CommandHandler/HelpCommandGenerator.cs new file mode 100644 index 0000000..429a6f9 --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/HelpCommandGenerator.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Text; +using DeukBot4.MessageHandlers.Permissions; + +namespace DeukBot4.MessageHandlers.CommandHandler +{ + public static class HelpCommandGenerator + { + public static string GenerateFullHelp(PermissionLevel level) + { + var dic = new Dictionary>(); + foreach (var command in CommandHandler.Commands) + { + if (command.Value.Permission > level) + continue; + if (!command.Value.HasHelp) + continue; + if (!dic.TryGetValue(command.Value.CommandContainer.Name, out var entry)) + { + dic.Add(command.Value.CommandContainer.Name, new Dictionary()); + entry = dic[command.Value.CommandContainer.Name]; + } + entry.Add(command.Value.Name, command.Value.ShortHelp); + } + var sb = new StringBuilder(); + foreach (var entry in dic) + { + sb.Append($"**{entry.Key}**\n"); + foreach (var cmd in entry.Value) + { + sb.Append($"``{cmd.Key}`` - {cmd.Value}\n"); + } + } + + return sb.ToString(); + } + + public static string GenerateSpecificHelp(string command, PermissionLevel level) + { + if (!CommandHandler.Commands.TryGetValue(command, out var cmd)) + return null; + if (cmd.Permission > level) + return null; + if (!cmd.HasHelp) + return null; + + return $"**{cmd.Name}** - *{cmd.LongHelp}*"; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs new file mode 100644 index 0000000..5090494 --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using DeukBot4.MessageHandlers.Permissions; +using Discord.WebSocket; + +namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure +{ + public class CommandRequest + { + private const string CommandNamePattern = "!([a-zA-Z0-9_]*) *(.*)"; + private static readonly Regex CommandNameMatcher = new Regex(CommandNamePattern); + + public Command Command { get; } + //public string ParameterString { get; } + public SocketMessage OriginalMessage { get; } + public RequestParameter[] Parameters { get; private set; } + public PermissionLevel RequestPermissions { get; } + + private CommandRequest(SocketMessage message, Command command, PermissionLevel requestPermissions, RequestParameter[] parameters) + { + OriginalMessage = message; + Command = command; + RequestPermissions = requestPermissions; + Parameters = parameters; + } + + public enum RequestCode + { + OK, Invalid, Forbidden + } + + public static (CommandRequest, RequestCode) Create(SocketMessage message) + { + var originalMessage = message; + var content = message.Content; + var res = CommandNameMatcher.Match(content); + if (res.Groups.Count <= 2) + return (null, RequestCode.Invalid); + + var commandName = res.Groups[1].Value; + var command = CommandHandler.GetCommand(commandName); + if (command == null) + { + return (null, RequestCode.Invalid); + } + + var permission = PermissionValidator.GetUserPermissionLevel(message); + if (permission < command.Permission) + { + return (null, RequestCode.Forbidden); + } + var parameterString = res.Groups[2].Value; + var parameters = ParameterMatcher.GetParameterValues(command, parameterString); + return (new CommandRequest(originalMessage, command, permission, parameters), RequestCode.OK); + } + + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs new file mode 100644 index 0000000..aea5009 --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure +{ + public static class ParameterMatcher + { + public enum ParameterType + { + Word, + Number, + Remainder, + } + + public static string[] GenerateRegex(Command command) + { + var arr = new string[command.ParameterTypes.Length]; + for (var index = 0; index < command.ParameterTypes.Length; index++) + { + var commandParameterType = command.ParameterTypes[index]; + + var builder = new StringBuilder(); + builder.Append(GetParameterRegex(commandParameterType[index])); + arr[index] = builder.ToString(); + } + return arr; + } + + private static string GetParameterRegex(ParameterType type) + { + switch (type) + { + case ParameterType.Word: + return " *(\\w+)"; + case ParameterType.Number: + return " *(\\d+)"; + case ParameterType.Remainder: + return " *(.*)"; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + public static RequestParameter[] GetParameterValues(Command command, string parameterString) + { + foreach (var pattern in command.ParametersMatchers) + { + var matches = Regex.Match(parameterString, pattern); + if (matches.Success) + { + return matches.Groups.Skip(1).Select(x => new RequestParameter(x.Value)).ToArray(); + } + } + return new RequestParameter[0]; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/RequestParameter.cs b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/RequestParameter.cs new file mode 100644 index 0000000..3b77c6e --- /dev/null +++ b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/RequestParameter.cs @@ -0,0 +1,28 @@ +using System; + +namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure +{ + public class RequestParameter + { + private readonly string _value; + + public RequestParameter(string value) + { + _value = value; + } + + public int AsInt() + { + if (int.TryParse(_value, out var i)) + { + return i; + } + throw new ArgumentException(); + } + + public string AsString() + { + return _value; + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/MainHandler.cs b/DeukBot4/MessageHandlers/MainHandler.cs new file mode 100644 index 0000000..cb87857 --- /dev/null +++ b/DeukBot4/MessageHandlers/MainHandler.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using Discord.WebSocket; + +namespace DeukBot4.MessageHandlers +{ + public static class MainHandler + { + public static async Task HandleMessage(SocketMessage message) + { + await CommandHandler.CommandHandler.HandleMessage(message); + } + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/Permissions/PermissionLevels.cs b/DeukBot4/MessageHandlers/Permissions/PermissionLevels.cs new file mode 100644 index 0000000..1ce06e4 --- /dev/null +++ b/DeukBot4/MessageHandlers/Permissions/PermissionLevels.cs @@ -0,0 +1,13 @@ +namespace DeukBot4.MessageHandlers.Permissions +{ + public enum PermissionLevel : sbyte + { + Banned = -10, + Bot = -5, + Everyone = 0, + Helper = 20, + Moderator = 40, + Admin = 60, + Owner = 100 + } +} \ No newline at end of file diff --git a/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs b/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs new file mode 100644 index 0000000..aa6411d --- /dev/null +++ b/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs @@ -0,0 +1,27 @@ +using DeukBot4.MessageHandlers.CommandHandler.RequestStructure; +using Discord.WebSocket; + +namespace DeukBot4.MessageHandlers.Permissions +{ + public static class PermissionValidator + { + public static PermissionLevel GetUserPermissionLevel(SocketMessage message) + { + if (message.Author.Id == Program.Settings.OwnerId) + { + return PermissionLevel.Owner; + } + if (message.Author.IsBot) + { + return PermissionLevel.Bot; + } + return PermissionLevel.Everyone; + } + + public static bool CanUse(this CommandRequest req) + { + var level = GetUserPermissionLevel(req.OriginalMessage); + return level >= req.Command.Permission; + } + } +} \ No newline at end of file diff --git a/DeukBot4/Program.cs b/DeukBot4/Program.cs new file mode 100644 index 0000000..041a870 --- /dev/null +++ b/DeukBot4/Program.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using DeukBot4.MessageHandlers; +using DeukBot4.MessageHandlers.CommandHandler; +using Discord; +using Discord.Commands.Builders; +using Discord.WebSocket; + +namespace DeukBot4 +{ + class Program + { + public static DiscordSocketClient Client { get; private set; } + public static Settings Settings { get; private set; } + + private static void Main(string[] args) + { + MainAsync().GetAwaiter().GetResult(); + } + + private static async Task MainAsync() + { + Settings = Settings.FromJsonFile("settings.json"); + CommandHandler.Build(); + Client = new DiscordSocketClient(); + + Client.Log += Logger.LogDiscord; + Client.Ready += OnReady; + Client.MessageReceived += MainHandler.HandleMessage; + + await Client.LoginAsync(TokenType.Bot, Settings.Token); + await Client.StartAsync(); + + + // Block this task until the program is closed. + await Task.Delay(-1); + } + + private static async Task OnReady() + { + await Client.CurrentUser.ModifyAsync(properties => + { + properties.Username = Settings.Username; + }); + } + } +} \ No newline at end of file diff --git a/DeukBot4/Settings.cs b/DeukBot4/Settings.cs new file mode 100644 index 0000000..669c94d --- /dev/null +++ b/DeukBot4/Settings.cs @@ -0,0 +1,22 @@ +using System.IO; +using Newtonsoft.Json; + +namespace DeukBot4 +{ + public class Settings + { + [JsonProperty] + public string Token { get; private set; } + [JsonProperty] + public string Username { get; private set; } + [JsonProperty] + public ulong OwnerId { get; private set; } + + + public static Settings FromJsonFile(string filepath) + { + var s = File.ReadAllText(filepath); + return JsonConvert.DeserializeObject(s); + } + } +} \ No newline at end of file diff --git a/DeukBot4/Utilities/IEnumerableExtensions.cs b/DeukBot4/Utilities/IEnumerableExtensions.cs new file mode 100644 index 0000000..9b03bc7 --- /dev/null +++ b/DeukBot4/Utilities/IEnumerableExtensions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace DeukBot4.Utilities +{ + public static class IEnumerableExtensions + { + public static string Join(this IEnumerable arr, string sep) + { + return string.Join(sep, arr); + } + } +} \ No newline at end of file