Added reminders

This commit is contained in:
Deukhoofd 2018-08-13 18:41:59 +02:00
parent 56f1e2740e
commit b2e3b3dec8
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
14 changed files with 212 additions and 26 deletions

View File

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

View File

@ -9,5 +9,6 @@
<PackageReference Include="Microsoft.NETCore.App" Version="2.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Npgsql" Version="4.0.2" />
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
</ItemGroup>
</Project>

View File

@ -6,9 +6,12 @@ using Discord;
namespace DeukBot4
{
public static class Logger
public class Logger
{
private static readonly Dictionary<LogSeverity, ConsoleColor> Colors = new Dictionary<LogSeverity, ConsoleColor>
public static Logger Main = new Logger();
private readonly Dictionary<LogSeverity, ConsoleColor> Colors = new Dictionary<LogSeverity, ConsoleColor>
{
{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);
}
}
}

View File

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

View File

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

View File

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

View File

@ -99,7 +99,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler
}
catch(Exception e)
{
await Logger.LogError(e.Message);
await Logger.Main.LogError(e.Message);
}
}

View File

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

View File

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

View File

@ -76,7 +76,7 @@ namespace DeukBot4.MessageHandlers
}
catch (Exception e)
{
await Logger.LogError(e);
await Logger.Main.LogError(e);
}
}
}

View File

@ -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*((?<recipient>me)|<@!*(?<recipient>\d*)>)\s*to)(?<action>.*)(in\s*)(?<timeNum>\d)\s*(?<timeId>\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}");
}
}
}

View File

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

View File

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

View File

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