1
0
mirror of https://gitlab.com/Deukhoofd/DeukBot4.git synced 2025-10-29 01:40:05 +00:00

Initial work

This commit is contained in:
2018-03-29 01:34:48 +02:00
commit 3a85a9f18f
22 changed files with 1008 additions and 0 deletions

11
DeukBot4/DeukBot4.csproj Normal file
View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="2.0.0-beta" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=messagehandlers_005Cattributes/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

51
DeukBot4/Logger.cs Normal file
View File

@@ -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<LogSeverity, ConsoleColor> Colors = new Dictionary<LogSeverity, ConsoleColor>
{
{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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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});
}
}
}

View File

@@ -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<Command>();
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();
}
}
}

View File

@@ -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<string, Command> Commands { get; private set; } = new Dictionary<string, Command>();
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;
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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<string, Dictionary<string, string>>();
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<string, string>());
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}*";
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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];
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

47
DeukBot4/Program.cs Normal file
View File

@@ -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;
});
}
}
}

22
DeukBot4/Settings.cs Normal file
View File

@@ -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<Settings>(s);
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace DeukBot4.Utilities
{
public static class IEnumerableExtensions
{
public static string Join(this IEnumerable<string> arr, string sep)
{
return string.Join(sep, arr);
}
}
}