diff --git a/DeukBot4/Database/ReminderHandler.cs b/DeukBot4/Database/ReminderHandler.cs
new file mode 100644
index 0000000..8503d4d
--- /dev/null
+++ b/DeukBot4/Database/ReminderHandler.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Threading.Tasks;
+using Discord;
+using StackExchange.Redis;
+
+namespace DeukBot4.Database
+{
+ public class ReminderHandler
+ {
+ public static ReminderHandler Main = new ReminderHandler();
+ public static ConnectionMultiplexer Redis = ConnectionMultiplexer.Connect("127.0.0.1");
+
+
+ public void AddReminder(TimeSpan time, string message, ulong channel, ulong author, ulong recipient)
+ {
+ try
+ {
+ var db = Redis.GetDatabase();
+ var id = Guid.NewGuid().ToString();
+ var expectedTime = DateTime.UtcNow.Add(time);
+ db.SortedSetAdd("deukbot_reminders", (RedisValue)id, expectedTime.ToBinary());
+ db.HashSet((RedisKey) id, new[]
+ {
+ new HashEntry("channel", channel),
+ new HashEntry("message", message),
+ new HashEntry("author", author),
+ new HashEntry("recipient", recipient),
+ });
+ }
+ catch (Exception e)
+ {
+ Logger.Main.LogError(e);
+ }
+ }
+
+ public async Task CheckReminders()
+ {
+ var checkTime = TimeSpan.FromSeconds(70);
+ var startTime = DateTime.UtcNow;
+ var desiredTopScore = (startTime + checkTime).ToBinary();
+ var db = Redis.GetDatabase();
+ var reminders = db.SortedSetRangeByScoreWithScores("deukbot_reminders", stop: desiredTopScore);
+ foreach (var sortedSetEntry in reminders)
+ {
+ var val = sortedSetEntry.Element.ToString();
+ var timeLong = sortedSetEntry.Score;
+ var time = DateTime.FromBinary((long) timeLong);
+ var data = db.HashGetAll(val);
+ ulong channel = 0;
+ ulong author = 0;
+ ulong recipient = 0;
+ string message = null;
+ foreach (var hashEntry in data)
+ {
+ if (hashEntry.Name == "channel") channel = (ulong) hashEntry.Value;
+ else if (hashEntry.Name == "message") message = hashEntry.Value;
+ else if (hashEntry.Name == "author") author = (ulong) hashEntry.Value;
+ else if (hashEntry.Name == "recipient") recipient = (ulong) hashEntry.Value;
+ }
+ var diff = time - DateTime.UtcNow;
+ FireReminderAtTime((int) diff.TotalSeconds, channel, message, author, recipient);
+ db.KeyDelete(val);
+ }
+
+ db.SortedSetRemoveRangeByScore("deukbot_reminders", Double.MinValue, desiredTopScore);
+
+ await Task.Delay(checkTime);
+ await CheckReminders();
+ }
+
+ private async Task FireReminderAtTime(int seconds, ulong channelId, string message, ulong author, ulong recipient)
+ {
+ if (seconds > 0)
+ await Task.Delay(TimeSpan.FromSeconds(seconds));
+ await FireReminder(channelId, message, author, recipient);
+ }
+
+ private async Task FireReminder(ulong channelId, string message, ulong author, ulong recipient)
+ {
+ if (Program.Client.GetChannel(channelId) is ITextChannel channel)
+ {
+ if (author == recipient)
+ {
+ channel.SendMessageAsync($"Hey <@{recipient}>, don't forget to {message}.");
+ }
+ else
+ {
+ channel.SendMessageAsync($"Hey <@{recipient}>, <@{author}> asked me to remind you to {message}.");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeukBot4/DeukBot4.csproj b/DeukBot4/DeukBot4.csproj
index 40a67b8..e3acf92 100644
--- a/DeukBot4/DeukBot4.csproj
+++ b/DeukBot4/DeukBot4.csproj
@@ -9,5 +9,6 @@
+
\ No newline at end of file
diff --git a/DeukBot4/Logger.cs b/DeukBot4/Logger.cs
index c13247f..c74fab8 100644
--- a/DeukBot4/Logger.cs
+++ b/DeukBot4/Logger.cs
@@ -6,9 +6,12 @@ using Discord;
namespace DeukBot4
{
- public static class Logger
+
+ public class Logger
{
- private static readonly Dictionary Colors = new Dictionary
+ public static Logger Main = new Logger();
+
+ private readonly Dictionary Colors = new Dictionary
{
{LogSeverity.Info, ConsoleColor.Black},
{LogSeverity.Verbose, ConsoleColor.Black},
@@ -18,34 +21,33 @@ namespace DeukBot4
{LogSeverity.Critical, ConsoleColor.Red}
};
- public static async Task Log(object o, LogSeverity severity)
+ public async Task Log(object o, LogSeverity severity)
{
Console.ForegroundColor = Colors[severity];
Console.WriteLine($"[{severity}] {DateTime.UtcNow:u}: {o.ToString()}");
Console.ResetColor();
}
- public static async Task LogDiscord(LogMessage message)
+ public async Task LogDiscord(LogMessage message)
{
Console.ForegroundColor = Colors[message.Severity];
Console.WriteLine($"[{message.Severity}] {DateTime.UtcNow:u}: {message.Message}");
Console.ResetColor();
}
- public static async Task Log(object o)
+ public async Task Log(object o)
{
await Log(o, LogSeverity.Info);
}
- public static async Task LogWarning(object o)
+ public async Task LogWarning(object o)
{
await Log(o, LogSeverity.Warning);
}
- public static async Task LogError(object o)
+ public async Task LogError(object o)
{
await Log(o, LogSeverity.Error);
}
-
}
}
\ No newline at end of file
diff --git a/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs b/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs
index 8ae5f6b..a9422f1 100644
--- a/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs
+++ b/DeukBot4/MessageHandlers/CommandHandler/CommandHandler.cs
@@ -35,7 +35,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler
}
}
- Logger.Log(
+ Logger.Main.Log(
$"Loaded following commands for container {obj.Name}: {commands.Select(x => x.Name).Join(", ")}");
}
@@ -57,19 +57,19 @@ namespace DeukBot4.MessageHandlers.CommandHandler
switch (resultCode)
{
case CommandRequest.RequestCode.Invalid:
- await Logger.LogError("Invalid content: " + message.Content);
+ await Logger.Main.LogError("Invalid content: " + message.Content);
return;
case CommandRequest.RequestCode.InvalidParameters:
- await Logger.LogError("Invalid parameters: " + message.Content);
+ await Logger.Main.LogError("Invalid parameters: " + message.Content);
break;
case CommandRequest.RequestCode.Forbidden:
- await Logger.Log(
+ await Logger.Main.Log(
$"Unauthorized user tried to run command: {message.Author.Username} -> {message.Content}");
break;
case CommandRequest.RequestCode.OK:
if (!(message.Channel is IGuildChannel) && req.Item1.Command.ForbidInPm)
{
- await Logger.Log(
+ await Logger.Main.Log(
$"User is trying to use blocked command in PM: {message.Author.Username}");
return;
}
@@ -80,7 +80,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler
}
catch (Exception e)
{
- await Logger.Log("An error occured: \n" + e);
+ await Logger.Main.Log("An error occured: \n" + e);
}
break;
case CommandRequest.RequestCode.UnknownCommand:
diff --git a/DeukBot4/MessageHandlers/CommandHandler/Commands/GeneralCommands.cs b/DeukBot4/MessageHandlers/CommandHandler/Commands/GeneralCommands.cs
index e49975a..b0542e4 100644
--- a/DeukBot4/MessageHandlers/CommandHandler/Commands/GeneralCommands.cs
+++ b/DeukBot4/MessageHandlers/CommandHandler/Commands/GeneralCommands.cs
@@ -3,6 +3,7 @@ using System.Net;
using System.Threading.Tasks;
using System.Web;
using DeukBot4.APIHandlers;
+using DeukBot4.Database;
using DeukBot4.MessageHandlers.CommandHandler.RequestStructure;
using DeukBot4.MessageHandlers.Permissions;
using DeukBot4.Utilities;
@@ -140,6 +141,5 @@ namespace DeukBot4.MessageHandlers.CommandHandler
};
await request.SendMessageAsync("", embed: eb.Build());
}
-
}
}
\ No newline at end of file
diff --git a/DeukBot4/MessageHandlers/CommandHandler/Commands/ModeratorCommands.cs b/DeukBot4/MessageHandlers/CommandHandler/Commands/ModeratorCommands.cs
index d28effe..42f9341 100644
--- a/DeukBot4/MessageHandlers/CommandHandler/Commands/ModeratorCommands.cs
+++ b/DeukBot4/MessageHandlers/CommandHandler/Commands/ModeratorCommands.cs
@@ -178,7 +178,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler
span = TimeSpan.FromMinutes(minutes.Value);
break;
case ParameterMatcher.ParameterType.Timespan:
- var sp = TimespanParser.Parse(request.Parameters[1].AsString());
+ var sp = TimespanHelper.Parse(request.Parameters[1].AsString());
if (sp.HasValue)
{
span = sp.Value;
diff --git a/DeukBot4/MessageHandlers/CommandHandler/Commands/RolePermissionCommands.cs b/DeukBot4/MessageHandlers/CommandHandler/Commands/RolePermissionCommands.cs
index 3b4ea21..dfcd782 100644
--- a/DeukBot4/MessageHandlers/CommandHandler/Commands/RolePermissionCommands.cs
+++ b/DeukBot4/MessageHandlers/CommandHandler/Commands/RolePermissionCommands.cs
@@ -99,7 +99,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler
}
catch(Exception e)
{
- await Logger.LogError(e.Message);
+ await Logger.Main.LogError(e.Message);
}
}
diff --git a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs
index 67f280a..d1541c5 100644
--- a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs
+++ b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/CommandRequest.cs
@@ -59,7 +59,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
}
catch (Exception e)
{
- await Logger.LogError(e.Message);
+ await Logger.Main.LogError(e.Message);
return (null, RequestCode.Forbidden, null);
}
if (permission < command.Permission)
diff --git a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs
index 65ff88b..eeed654 100644
--- a/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs
+++ b/DeukBot4/MessageHandlers/CommandHandler/RequestStructure/ParameterMatcher.cs
@@ -47,7 +47,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
case ParameterType.User:
return $" *(?:<@!*(?<{index}>\\d+)>|(?<{index}>\\d+)(?:$| |\n))";
case ParameterType.Timespan:
- return $" *(?<{index}>\\d+\\.*d*[smhd])";
+ return $" *(?<{index}>\\d+\\.*\\d*[smhd])";
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
@@ -66,7 +66,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
}
catch (Exception e)
{
- Logger.LogError(e.ToString());
+ Logger.Main.LogError(e.ToString());
return command.RequireParameterMatch ? null : new RequestParameter[0];
}
if (matches.Success)
diff --git a/DeukBot4/MessageHandlers/ImageBackupHandler.cs b/DeukBot4/MessageHandlers/ImageBackupHandler.cs
index b59576b..4914c29 100644
--- a/DeukBot4/MessageHandlers/ImageBackupHandler.cs
+++ b/DeukBot4/MessageHandlers/ImageBackupHandler.cs
@@ -76,7 +76,7 @@ namespace DeukBot4.MessageHandlers
}
catch (Exception e)
{
- await Logger.LogError(e);
+ await Logger.Main.LogError(e);
}
}
}
diff --git a/DeukBot4/MessageHandlers/MainHandler.cs b/DeukBot4/MessageHandlers/MainHandler.cs
index 410a174..358c91d 100644
--- a/DeukBot4/MessageHandlers/MainHandler.cs
+++ b/DeukBot4/MessageHandlers/MainHandler.cs
@@ -1,8 +1,10 @@
using System;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using DeukBot4.APIHandlers;
+using DeukBot4.Database;
using DeukBot4.Utilities;
using Discord;
using Discord.WebSocket;
@@ -22,6 +24,7 @@ namespace DeukBot4.MessageHandlers
#pragma warning disable 4014
CommandHandler.CommandHandler.HandleMessage(message);
HandlePrivateMessage(message);
+ HandleReminder(message);
ImageBackupHandler.Backup(message);
JokeHandlers.DeltaHandler(message);
JokeHandlers.DadJokeHandler(message);
@@ -30,7 +33,7 @@ namespace DeukBot4.MessageHandlers
}
catch (Exception e)
{
- await Logger.LogError(e.ToString());
+ await Logger.Main.LogError(e.ToString());
}
}
@@ -39,7 +42,7 @@ namespace DeukBot4.MessageHandlers
{
if (message.Channel is IPrivateChannel)
{
- await Logger.Log(($"Private Message: {message.Author.Username}- {message.Content}"));
+ await Logger.Main.Log(($"Private Message: {message.Author.Username}- {message.Content}"));
if (_dmChannel == null)
{
_dmChannel = (ITextChannel) Program.Client.GetChannel(Program.Settings.DmChannel);
@@ -63,5 +66,70 @@ namespace DeukBot4.MessageHandlers
}
}
+ private static Regex ReminderMatcher =
+ new Regex(
+ @".*(remind\s*((?me)|<@!*(?\d*)>)\s*to)(?.*)(in\s*)(?\d)\s*(?\w*)",
+ RegexOptions.IgnoreCase);
+ private static async Task HandleReminder(SocketMessage message)
+ {
+ var match = ReminderMatcher.Match(message.Content);
+ if (!match.Success)
+ {
+ return;
+ }
+ var recipient = match.Groups["recipient"].Captures[0].Value;
+ var action = match.Groups["action"].Value.Trim();
+ if (string.IsNullOrWhiteSpace(action))
+ return;
+ var timeNumber = double.Parse(match.Groups["timeNum"].Value);
+ var timeIdentifier = match.Groups["timeId"].Value.Trim();
+ TimeSpan timespan;
+ if (timeIdentifier.ToLowerInvariant().StartsWith("minu"))
+ {
+ timespan = TimeSpan.FromMinutes(timeNumber);
+ }
+ else if (timeIdentifier.ToLowerInvariant().StartsWith("hour"))
+ {
+ timespan = TimeSpan.FromHours(timeNumber);
+ }
+ else if (timeIdentifier.ToLowerInvariant().StartsWith("day"))
+ {
+ timespan = TimeSpan.FromDays(timeNumber);
+ }
+ else if (timeIdentifier.ToLowerInvariant().StartsWith("month"))
+ {
+ var dest = DateTime.UtcNow.AddMonths((int) (timeNumber));
+ dest = dest.AddDays(timeNumber % 1 * 30);
+ timespan = dest - DateTime.UtcNow;
+ }
+ else if (timeIdentifier.ToLowerInvariant().StartsWith("year"))
+ {
+ var dest = DateTime.UtcNow.AddYears((int) (timeNumber));
+ dest = dest.AddDays(timeNumber % 1 * 365);
+ timespan = dest - DateTime.UtcNow;
+ }
+ else
+ {
+ Logger.Main.LogError("Unknown timespan identifier: " + timeIdentifier);
+ return;
+ }
+
+ if (timespan.TotalMinutes < 5)
+ {
+ message.Channel.SendMessageAsync("A reminder should be at least 5 minutes in the future");
+ return;
+ }
+
+ if (!ulong.TryParse(recipient, out var recip))
+ {
+ recip = message.Author.Id;
+ }
+
+ ReminderHandler.Main.AddReminder(timespan, action, message.Channel.Id, message.Author.Id, recip);
+ message.Channel.SendMessageAsync(
+ message.Author.Id == recip
+ ? $"Reminder set! I will remind you in {timespan.ToPrettyFormat()} to {action}"
+ : $"Reminder set! I will remind <@!{recip}> in {timespan.ToPrettyFormat()} to {action}");
+ }
}
}
\ No newline at end of file
diff --git a/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs b/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs
index eef3a43..477b056 100644
--- a/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs
+++ b/DeukBot4/MessageHandlers/Permissions/PermissionValidator.cs
@@ -38,7 +38,7 @@ namespace DeukBot4.MessageHandlers.Permissions
}
catch(Exception e)
{
- await Logger.LogError(e.ToString());
+ await Logger.Main.LogError(e.ToString());
return PermissionLevel.Everyone;
}
}
diff --git a/DeukBot4/Program.cs b/DeukBot4/Program.cs
index af3be50..b66d065 100644
--- a/DeukBot4/Program.cs
+++ b/DeukBot4/Program.cs
@@ -7,6 +7,7 @@ using DeukBot4.MessageHandlers.CommandHandler;
using Discord;
using Discord.Commands.Builders;
using Discord.WebSocket;
+using StackExchange.Redis;
namespace DeukBot4
{
@@ -21,10 +22,17 @@ namespace DeukBot4
MainAsync().GetAwaiter().GetResult();
}
+ private static async Task SetupScheduler()
+ {
+ ReminderHandler.Main.CheckReminders();
+ }
+
private static async Task MainAsync()
{
+
Settings = Settings.FromJsonFile("settings.json");
DatabaseConnection.ConnectionString = Settings.DatabaseConnectionString;
+ await SetupScheduler();
DatabaseInitializer.Initialize();
ServerSettingHandler.OnBotStartUp();
@@ -35,7 +43,7 @@ namespace DeukBot4
Client = new DiscordSocketClient();
- Client.Log += Logger.LogDiscord;
+ Client.Log += Logger.Main.LogDiscord;
Client.Ready += OnReady;
Client.MessageReceived += MainHandler.HandleMessage;
diff --git a/DeukBot4/Utilities/TimespanParser.cs b/DeukBot4/Utilities/TimespanHelper.cs
similarity index 55%
rename from DeukBot4/Utilities/TimespanParser.cs
rename to DeukBot4/Utilities/TimespanHelper.cs
index c8075d1..0e22e13 100644
--- a/DeukBot4/Utilities/TimespanParser.cs
+++ b/DeukBot4/Utilities/TimespanHelper.cs
@@ -1,9 +1,10 @@
using System;
using System.Linq;
+using System.Text;
namespace DeukBot4.Utilities
{
- public static class TimespanParser
+ public static class TimespanHelper
{
public static TimeSpan? Parse(string s)
{
@@ -27,6 +28,19 @@ namespace DeukBot4.Utilities
default:
return null;
}
+ }
+ public static string ToPrettyFormat(this TimeSpan span) {
+
+ if (span == TimeSpan.Zero) return "0 minutes";
+
+ var sb = new StringBuilder();
+ if (span.Days > 0)
+ sb.AppendFormat("{0} day{1} ", span.Days, span.Days > 1 ? "s" : String.Empty);
+ if (span.Hours > 0)
+ sb.AppendFormat("{0} hour{1} ", span.Hours, span.Hours > 1 ? "s" : String.Empty);
+ if (span.Minutes > 0)
+ sb.AppendFormat("{0} minute{1} ", span.Minutes, span.Minutes > 1 ? "s" : String.Empty);
+ return sb.ToString();
}
}