Do tons of work. It's functional.... almost :P
Oops - I should have comitted earlier :P
This commit is contained in:
parent
f030fcf5e0
commit
0b73e2f1c7
9 changed files with 360 additions and 49 deletions
69
RhinoReminds/AIRecogniser.cs
Normal file
69
RhinoReminds/AIRecogniser.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Recognizers.Text;
|
||||||
|
using Microsoft.Recognizers.Text.DateTime;
|
||||||
|
using SBRL.Geometry;
|
||||||
|
|
||||||
|
namespace RhinoReminds
|
||||||
|
{
|
||||||
|
|
||||||
|
public static class AIRecogniser
|
||||||
|
{
|
||||||
|
public static (DateTime, TimeSpan) RecogniseDateTimeRange(string source, out string rawString)
|
||||||
|
{
|
||||||
|
List<ModelResult> aiResults = DateTimeRecognizer.RecognizeDateTime(source, Culture.English);
|
||||||
|
if (aiResults.Count == 0)
|
||||||
|
throw new AIException("Error: Couldn't recognise any time ranges in that source string.");
|
||||||
|
|
||||||
|
/* Example contents of the below dictionary:
|
||||||
|
[0]: {[timex, 2018-11-11T06:15]}
|
||||||
|
[1]: {[type, datetime]}
|
||||||
|
[2]: {[value, 2018-11-11 06:15:00]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
rawString = aiResults[0].Text;
|
||||||
|
Dictionary<string, string> aiResult = unwindResult(aiResults[0]);
|
||||||
|
foreach (KeyValuePair<string, string> kvp in aiResult)
|
||||||
|
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
|
||||||
|
string type = aiResult["type"];
|
||||||
|
|
||||||
|
if (type != "datetimerange")
|
||||||
|
throw new AIException($"Error: An invalid type of {type} was encountered ('datetimerange' expected).");
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
DateTime.Parse(aiResult["start"]),
|
||||||
|
DateTime.Parse(aiResult["end"]) - DateTime.Parse(aiResult["start"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTime RecogniseDateTime(string source, out string rawString)
|
||||||
|
{
|
||||||
|
List<ModelResult> aiResults = DateTimeRecognizer.RecognizeDateTime(source, Culture.English);
|
||||||
|
if (aiResults.Count == 0)
|
||||||
|
throw new AIException("Error: Couldn't recognise any dates or times in that source string.");
|
||||||
|
|
||||||
|
/* Example contents of the below dictionary:
|
||||||
|
[0]: {[timex, 2018-11-11T06:15]}
|
||||||
|
[1]: {[type, datetime]}
|
||||||
|
[2]: {[value, 2018-11-11 06:15:00]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
rawString = aiResults[0].Text;
|
||||||
|
Dictionary<string, string> aiResult = unwindResult(aiResults[0]);
|
||||||
|
string type = aiResult["type"];
|
||||||
|
if (!(new string[] { "datetime", "date", "time", "datetimerange", "daterange", "timerange" }).Contains(type))
|
||||||
|
throw new AIException($"Error: An invalid type of {type} was encountered ('datetime' expected).");
|
||||||
|
|
||||||
|
|
||||||
|
string result = type == "datetimerange" ? aiResult["start"] : aiResult["value"];
|
||||||
|
return DateTime.Parse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Dictionary<string, string> unwindResult(ModelResult modelResult) {
|
||||||
|
return (modelResult.Resolution["values"] as List<Dictionary<string, string>>)[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +1,49 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Recognizers.Text;
|
|
||||||
using Microsoft.Recognizers.Text.DateTime;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using S22.Xmpp;
|
using S22.Xmpp;
|
||||||
using S22.Xmpp.Client;
|
using S22.Xmpp.Client;
|
||||||
using S22.Xmpp.Im;
|
using S22.Xmpp.Im;
|
||||||
|
using SBRL.Geometry;
|
||||||
|
|
||||||
namespace RhinoReminds
|
namespace RhinoReminds
|
||||||
{
|
{
|
||||||
public class ClientListener
|
public class ClientListener
|
||||||
{
|
{
|
||||||
|
public bool Debug { get; set; } = false;
|
||||||
|
|
||||||
public readonly string Jid;
|
public readonly string Jid;
|
||||||
public string Username => Jid.Split('@')[0];
|
public string Username => Jid.Split('@')[0];
|
||||||
public string Hostname => Jid.Split('@')[1];
|
public string Hostname => Jid.Split('@')[1];
|
||||||
private readonly string password;
|
private readonly string password;
|
||||||
|
|
||||||
|
public string ReminderFilePath { get; set; } = "./Reminders.json";
|
||||||
private ReminderList reminders = new ReminderList();
|
private ReminderList reminders = new ReminderList();
|
||||||
|
|
||||||
private XmppClient client;
|
private XmppClient client;
|
||||||
|
|
||||||
public ClientListener(string inJid, string inPassword) {
|
public ClientListener(string inJid, string inPassword)
|
||||||
|
{
|
||||||
Jid = inJid;
|
Jid = inJid;
|
||||||
password = inPassword;
|
password = inPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start()
|
public async Task Start()
|
||||||
{
|
{
|
||||||
|
if (File.Exists(ReminderFilePath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Rhino/Startup] Loading reminders list from {ReminderFilePath}");
|
||||||
|
reminders = JsonConvert.DeserializeObject<ReminderList>(File.ReadAllText(ReminderFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
client = new XmppClient(Hostname, Username, password);
|
client = new XmppClient(Hostname, Username, password);
|
||||||
client.Error += errorHandler;
|
client.Error += errorHandler;
|
||||||
client.Message += messageHandler;
|
client.Message += messageHandlerRoot;
|
||||||
client.SubscriptionRequest += subscriptionRequestHandler;
|
client.SubscriptionRequest += subscriptionRequestHandler;
|
||||||
|
|
||||||
// Connect to the server. This starts it's own thread that doesn't block the program exiting, apparently
|
// Connect to the server. This starts it's own thread that doesn't block the program exiting, apparently
|
||||||
|
@ -46,63 +56,181 @@ namespace RhinoReminds
|
||||||
|
|
||||||
//client.SetStatus(Availability.Online);
|
//client.SetStatus(Availability.Online);
|
||||||
|
|
||||||
await Task.Delay(Timeout.Infinite);
|
await watchForReminders();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool subscriptionRequestHandler(Jid from) {
|
#region XMPP Event Handling
|
||||||
|
private bool subscriptionRequestHandler(Jid from)
|
||||||
|
{
|
||||||
Console.WriteLine($"[Rhino/SubscriptionRequest] Approving subscription from {from}");
|
Console.WriteLine($"[Rhino/SubscriptionRequest] Approving subscription from {from}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void errorHandler(object sender, ErrorEventArgs e) {
|
private void errorHandler(object sender, S22.Xmpp.Im.ErrorEventArgs e)
|
||||||
|
{
|
||||||
Console.Error.WriteLine($"Error {e.Reason}: {e.Exception}");
|
Console.Error.WriteLine($"Error {e.Reason}: {e.Exception}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void messageHandler(object sender, MessageEventArgs eventArgs) {
|
private void messageHandlerRoot(object sender, MessageEventArgs eventArgs)
|
||||||
|
{
|
||||||
Console.WriteLine($"[Rhino/Reciever] [Message] {eventArgs.Jid} | {eventArgs.Message.Body}");
|
Console.WriteLine($"[Rhino/Reciever] [Message] {eventArgs.Jid} | {eventArgs.Message.Body}");
|
||||||
|
|
||||||
string message = eventArgs.Message.Body;
|
try
|
||||||
string[] parts = Regex.Split(eventArgs.Message.Body.Trim(), @"\s+");
|
{
|
||||||
string instruction = parts[0].ToLower();
|
messageHandler(eventArgs.Message);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(error);
|
||||||
|
sendChatReply(eventArgs.Message, "Oops! I encountered an error. Please report this to my operator!");
|
||||||
|
sendChatReply(eventArgs.Message, $"Technical details: {WebUtility.HtmlEncode(error.ToString())} (stack trace available in server log) ");
|
||||||
|
|
||||||
if (parts.Select((string nextPart) => nextPart.ToLower()).Contains("remind")) {
|
|
||||||
Console.WriteLine("[Rhino/Reciever] Identified remind request");
|
|
||||||
|
|
||||||
List<ModelResult> dateAiResult = DateTimeRecognizer.RecognizeDateTime(message, Culture.English);
|
|
||||||
if (dateAiResult.Count == 0) {
|
|
||||||
sendChatReply(eventArgs.Message, "Sorry, but I didn't recognise any date or time in that message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//DateTime dateTime = dateAiResult[0].Resolution["values"]
|
|
||||||
|
|
||||||
sendChatReply(
|
|
||||||
eventArgs.Message,
|
|
||||||
"#1: " + (dateAiResult[0].Resolution["values"] as List<object>)[0].ToString() + "\n" +
|
|
||||||
"JSON: " + JsonConvert.SerializeObject(
|
|
||||||
dateAiResult[0].Resolution["values"]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void messageHandler(Message message)
|
||||||
|
{
|
||||||
|
string messageText = message.Body;
|
||||||
|
string[] parts = Regex.Split(messageText.Trim(), @"\s+");
|
||||||
|
string instruction = parts[0].ToLower();
|
||||||
|
|
||||||
|
|
||||||
|
switch (parts[0].ToLower())
|
||||||
|
{
|
||||||
|
case "list":
|
||||||
|
case "show":
|
||||||
|
if (parts.Select((n) => n.ToLower()).Contains("all")) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Identify number
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "remind":
|
||||||
|
Console.WriteLine("[Rhino/Reciever] Identified remind request");
|
||||||
|
|
||||||
|
DateTime dateTime; string rawDateTimeString;
|
||||||
|
try {
|
||||||
|
dateTime = AIRecogniser.RecogniseDateTime(messageText, out rawDateTimeString);
|
||||||
|
} catch (AIException error) {
|
||||||
|
sendChatReply(message, "Sorry, I had trouble figuring out when you wanted reminding about that!");
|
||||||
|
sendChatReply(message, $"(Technical details: {error.Message})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Range dateStringLocation = new Range(
|
||||||
|
messageText.IndexOf(rawDateTimeString, StringComparison.OrdinalIgnoreCase),
|
||||||
|
messageText.IndexOf(rawDateTimeString, StringComparison.OrdinalIgnoreCase) + rawDateTimeString.Length
|
||||||
|
);
|
||||||
|
string reminder = Regex.Replace(
|
||||||
|
messageText.Remove(dateStringLocation.Min, dateStringLocation.Stride),
|
||||||
|
@"^remind\s+(?:me\s+)?", "",
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
).Replace(@"\s{2,}", " ").Trim();
|
||||||
|
|
||||||
|
if (Debug)
|
||||||
|
{
|
||||||
|
sendChatReply(message, $"[debug] Raw date identified: [{rawDateTimeString}]");
|
||||||
|
sendChatReply(message, $"[debug] Time identified at {dateStringLocation}");
|
||||||
|
sendChatReply(message, $"[debug] Transforming message - phase #1 [{reminder}]");
|
||||||
|
sendChatReply(message, $"[debug] Transforming message - phase #2 [{reminder}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendChatReply(message, $"Ok! I'll remind you {reminder} at {dateTime}.");
|
||||||
|
|
||||||
|
Reminder newReminder = reminders.CreateReminder(message.From, dateTime, reminder);
|
||||||
|
reminders.Save(ReminderFilePath);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendChatReply(message, "I don't understand that. Try rephrasing it or asking for help.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendChatMessage(Jid to, string message) {
|
||||||
|
Console.WriteLine($"[Rhino/Send/Chat] Sending {message} -> {to}");
|
||||||
|
client.SendMessage(
|
||||||
|
to, message,
|
||||||
|
null, null, MessageType.Chat
|
||||||
|
);
|
||||||
|
}
|
||||||
private void sendChatReply(Message originalMessage, string reply)
|
private void sendChatReply(Message originalMessage, string reply)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"[Rhino/Send/Reply] Sending {reply} -> {originalMessage.From}");
|
||||||
client.SendMessage(
|
client.SendMessage(
|
||||||
originalMessage.From, reply,
|
originalMessage.From, reply,
|
||||||
null, originalMessage.Thread, MessageType.Chat
|
null, originalMessage.Thread, MessageType.Chat
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool isContact(Jid jid)
|
#endregion
|
||||||
|
|
||||||
|
#region Reminder Listening
|
||||||
|
|
||||||
|
private async Task watchForReminders()
|
||||||
{
|
{
|
||||||
foreach (RosterItem nextContact in client.GetRoster()) {
|
CancellationTokenSource cancellationSource = new CancellationTokenSource();
|
||||||
if (nextContact.Jid == jid)
|
CancellationToken cancellationToken = cancellationSource.Token;
|
||||||
return true;
|
Reminder nextReminder = reminders.GetNextReminder();
|
||||||
|
reminders.OnReminderListUpdate += (object sender, Reminder newReminder) => {
|
||||||
|
Reminder newNextReminder = reminders.GetNextReminder();
|
||||||
|
Console.WriteLine("[Rhino/Reminderd/Canceller] Reminder added - comparing.");
|
||||||
|
Console.WriteLine($"[Rhino/Reminderd/Canceller] {nextReminder} / {newNextReminder}");
|
||||||
|
if (nextReminder != newNextReminder) {
|
||||||
|
Console.WriteLine($"[Rhino/Reminderd/Canceller] Cancelling");
|
||||||
|
nextReminder = newNextReminder;
|
||||||
|
cancellationSource.Cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
TimeSpan nextWaitingTime;
|
||||||
|
try {
|
||||||
|
if (nextReminder != null) {
|
||||||
|
nextWaitingTime = DateTime.Now - nextReminder.Time;
|
||||||
|
if (DateTime.Now < nextReminder.Time) {
|
||||||
|
Console.WriteLine($"[Rhino/Reminderd] Sleeping for {nextWaitingTime}");
|
||||||
|
await Task.Delay(nextWaitingTime, cancellationToken);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Console.WriteLine("[Rhino/Reminderd] Sleeping until interrupted");
|
||||||
|
await Task.Delay(Timeout.Infinite, cancellationToken);
|
||||||
|
}
|
||||||
|
} catch (TaskCanceledException) {
|
||||||
|
Console.WriteLine("[Rhino/Reminderd] Sleep interrupted, recalculating");
|
||||||
|
cancellationSource = new CancellationTokenSource();
|
||||||
|
cancellationToken = cancellationSource.Token;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested) {
|
||||||
|
Console.WriteLine("[Rhino/Reminderd] Sleep interrupted, recalculating (but no exception thrown)");
|
||||||
|
cancellationSource = new CancellationTokenSource();
|
||||||
|
cancellationToken = cancellationSource.Token;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[Rhino/Reminderd] Sending notification {nextReminder}");
|
||||||
|
|
||||||
|
sendChatMessage(
|
||||||
|
nextReminder.Jid,
|
||||||
|
$"Hello! You asked me to remind you {nextReminder.Message} at {nextReminder.Time}.".Replace(@"\s{2,}", " ").Trim()
|
||||||
|
);
|
||||||
|
if (nextWaitingTime.TotalMilliseconds < 0) {
|
||||||
|
sendChatMessage(
|
||||||
|
nextReminder.Jid,
|
||||||
|
"(Sorry I'm late reminding you! I might not have been running at the time, " +
|
||||||
|
"or you might have scheduled a reminder for the past)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
reminders.DeleteReminder(nextReminder);
|
||||||
|
reminders.Save(ReminderFilePath);
|
||||||
|
nextReminder = reminders.GetNextReminder();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
RhinoReminds/Exceptions.cs
Normal file
16
RhinoReminds/Exceptions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
namespace RhinoReminds
|
||||||
|
{
|
||||||
|
[Serializable()]
|
||||||
|
public class AIException : System.Exception
|
||||||
|
{
|
||||||
|
public AIException() : base() { }
|
||||||
|
public AIException(string message) : base(message) { }
|
||||||
|
public AIException(string message, System.Exception inner) : base(message, inner) { }
|
||||||
|
|
||||||
|
// A constructor is needed for serialization when an
|
||||||
|
// exception propagates from a remoting server to the client.
|
||||||
|
protected AIException(System.Runtime.Serialization.SerializationInfo info,
|
||||||
|
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using S22.Xmpp;
|
||||||
using S22.Xmpp.Client;
|
using S22.Xmpp.Client;
|
||||||
using S22.Xmpp.Im;
|
using S22.Xmpp.Im;
|
||||||
|
|
||||||
|
@ -7,6 +9,7 @@ namespace RhinoReminds
|
||||||
{
|
{
|
||||||
public class ProgramSettings
|
public class ProgramSettings
|
||||||
{
|
{
|
||||||
|
public string Filepath = "./reminders.json";
|
||||||
public string Jid = null;
|
public string Jid = null;
|
||||||
|
|
||||||
public string Password = null;
|
public string Password = null;
|
||||||
|
@ -37,6 +40,7 @@ namespace RhinoReminds
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Options:");
|
Console.WriteLine("Options:");
|
||||||
Console.WriteLine(" -h --help Show this message");
|
Console.WriteLine(" -h --help Show this message");
|
||||||
|
Console.WriteLine($" -f --file Specify where to save reminders (default: {settings.Filepath})");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Environment Variables:");
|
Console.WriteLine("Environment Variables:");
|
||||||
Console.WriteLine(" XMPP_JID The JID to login to");
|
Console.WriteLine(" XMPP_JID The JID to login to");
|
||||||
|
@ -59,7 +63,9 @@ namespace RhinoReminds
|
||||||
|
|
||||||
public static void Run()
|
public static void Run()
|
||||||
{
|
{
|
||||||
ClientListener client = new ClientListener(settings.Jid, settings.Password);
|
ClientListener client = new ClientListener(settings.Jid, settings.Password) {
|
||||||
|
ReminderFilePath = settings.Filepath
|
||||||
|
};
|
||||||
client.Start().Wait();
|
client.Start().Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using S22.Xmpp;
|
||||||
|
|
||||||
namespace RhinoReminds
|
namespace RhinoReminds
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Reminder
|
public class Reminder
|
||||||
{
|
{
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
|
public string Jid { get; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public Jid JidObj => new Jid(Jid);
|
||||||
public DateTime Time { get; }
|
public DateTime Time { get; }
|
||||||
public string Message { get; }
|
public string Message { get; }
|
||||||
|
|
||||||
public Reminder(int inId, DateTime inTime, string inMessage)
|
public Reminder(int inId, string inJid, DateTime inTime, string inMessage)
|
||||||
{
|
{
|
||||||
Id = inId;
|
Id = inId;
|
||||||
|
Jid = inJid;
|
||||||
Time = inTime;
|
Time = inTime;
|
||||||
Message = inMessage;
|
Message = inMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[Reminder Id={Id}, Jid={Jid}, Time={Time}, Message={Message}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using S22.Xmpp;
|
||||||
|
|
||||||
namespace RhinoReminds
|
namespace RhinoReminds
|
||||||
{
|
{
|
||||||
|
@ -8,19 +11,43 @@ namespace RhinoReminds
|
||||||
public class ReminderList
|
public class ReminderList
|
||||||
{
|
{
|
||||||
private int nextId = 0;
|
private int nextId = 0;
|
||||||
|
private object saveLock = new object();
|
||||||
public SortedList<DateTime, Reminder> Reminders = new SortedList<DateTime, Reminder>();
|
public SortedList<DateTime, Reminder> Reminders = new SortedList<DateTime, Reminder>();
|
||||||
|
|
||||||
public event OnReminderListUpdateHandler OnReminderListUpdate;
|
public event OnReminderListUpdateHandler OnReminderListUpdate;
|
||||||
|
|
||||||
|
|
||||||
public ReminderList() {
|
public ReminderList() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Reminder CreateReminder(DateTime time, string message) {
|
public Reminder CreateReminder(Jid inJid, DateTime time, string message) {
|
||||||
Reminder result = new Reminder(nextId++, time, message);
|
Reminder result = new Reminder(nextId++, $"{inJid.Node}@{inJid.Domain}", time, message);
|
||||||
|
Console.WriteLine($"[Rhino/ReminderList] Created reminder {result}");
|
||||||
Reminders.Add(time, result);
|
Reminders.Add(time, result);
|
||||||
OnReminderListUpdate(this, result);
|
OnReminderListUpdate(this, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Save(string filename)
|
||||||
|
{
|
||||||
|
// Make sure that the reminder thread doesn't try to save the reminders at the exact same time
|
||||||
|
// we receive a request for a new reminder
|
||||||
|
lock (saveLock) { // FUTURE: We could go lockless here with some work, but it's not worth it for the teeny chance & low overhead
|
||||||
|
File.WriteAllText(filename, JsonConvert.SerializeObject(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reminder GetNextReminder()
|
||||||
|
{
|
||||||
|
if (Reminders.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Reminders.Values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteReminder(Reminder nextReminder) {
|
||||||
|
Reminders.Remove(nextReminder.Time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,12 +36,6 @@
|
||||||
<Reference Include="Newtonsoft.Json">
|
<Reference Include="Newtonsoft.Json">
|
||||||
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System.Collections.Immutable">
|
|
||||||
<HintPath>..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System.ValueTuple">
|
|
||||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\net47\System.ValueTuple.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="mscorlib" />
|
<Reference Include="mscorlib" />
|
||||||
<Reference Include="Microsoft.Recognizers.Definitions">
|
<Reference Include="Microsoft.Recognizers.Definitions">
|
||||||
<HintPath>..\packages\Microsoft.Recognizers.Text.1.1.2\lib\net462\Microsoft.Recognizers.Definitions.dll</HintPath>
|
<HintPath>..\packages\Microsoft.Recognizers.Text.1.1.2\lib\net462\Microsoft.Recognizers.Definitions.dll</HintPath>
|
||||||
|
@ -58,6 +52,12 @@
|
||||||
<Reference Include="Microsoft.Recognizers.Text.DateTime">
|
<Reference Include="Microsoft.Recognizers.Text.DateTime">
|
||||||
<HintPath>..\packages\Microsoft.Recognizers.Text.DateTime.1.1.2\lib\net462\Microsoft.Recognizers.Text.DateTime.dll</HintPath>
|
<HintPath>..\packages\Microsoft.Recognizers.Text.DateTime.1.1.2\lib\net462\Microsoft.Recognizers.Text.DateTime.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="System.Collections.Immutable">
|
||||||
|
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.ValueTuple">
|
||||||
|
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
|
@ -65,9 +65,15 @@
|
||||||
<Compile Include="ClientListener.cs" />
|
<Compile Include="ClientListener.cs" />
|
||||||
<Compile Include="Reminder.cs" />
|
<Compile Include="Reminder.cs" />
|
||||||
<Compile Include="ReminderList.cs" />
|
<Compile Include="ReminderList.cs" />
|
||||||
|
<Compile Include="AIRecogniser.cs" />
|
||||||
|
<Compile Include="Exceptions.cs" />
|
||||||
|
<Compile Include="Utilities\Range.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Utilities\" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
47
RhinoReminds/Utilities/Range.cs
Normal file
47
RhinoReminds/Utilities/Range.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SBRL.Geometry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single 1-dimensional range.
|
||||||
|
/// </summary>
|
||||||
|
public class Range
|
||||||
|
{
|
||||||
|
public int Min { get; set; }
|
||||||
|
public int Max { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The difference between the minimum and the maximum number in this range.
|
||||||
|
/// </summary>
|
||||||
|
public int Stride {
|
||||||
|
get {
|
||||||
|
return Max - Min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Range(int min, int max)
|
||||||
|
{
|
||||||
|
Min = min;
|
||||||
|
Max = max;
|
||||||
|
|
||||||
|
if (Max < Min)
|
||||||
|
throw new ArgumentException("Error: The maximum provided is greater than the minimum!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shift(int amount)
|
||||||
|
{
|
||||||
|
Min += amount;
|
||||||
|
Max += amount;
|
||||||
|
}
|
||||||
|
public void Multiply(int multiplier)
|
||||||
|
{
|
||||||
|
Min *= multiplier;
|
||||||
|
Max *= multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[Range Min={Min}, Max={Max}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,6 @@
|
||||||
<package id="Microsoft.Recognizers.Text.NumberWithUnit" version="1.1.2" targetFramework="net47" />
|
<package id="Microsoft.Recognizers.Text.NumberWithUnit" version="1.1.2" targetFramework="net47" />
|
||||||
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net47" />
|
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net47" />
|
||||||
<package id="S22.Xmpp" version="1.0.0.0" targetFramework="net47" />
|
<package id="S22.Xmpp" version="1.0.0.0" targetFramework="net47" />
|
||||||
<package id="System.Collections.Immutable" version="1.4.0" targetFramework="net47" />
|
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net47" />
|
||||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net47" />
|
<package id="System.ValueTuple" version="4.5.0" targetFramework="net47" />
|
||||||
</packages>
|
</packages>
|
Loading…
Reference in a new issue