--- /dev/null
+@include "json.awk"
+
+# Load saved at queue
+BEGIN {
+ json_load("var/remind.json", remind);
+}
+
+# Parse an at style time
+function atp(now, time, cmd, iso, arr, month) {
+ # Remove quotes if needed
+ gsub(/["']/, "", time);
+
+ # Normalize date
+ cmd = "atp " now " " time " 2>/dev/null";
+ cmd | getline iso;
+ close(cmd);
+
+ # Parse date
+ gsub(/^\w+ |:/, " ", iso);
+ if (!match(iso, /(\w+) +([0-9]+ [0-9]+ [0-9]+ [0-9]+) +([0-9]+)/, arr))
+ return -1;
+ month = (index("JanFebMarAprMayJunJulAugSepOctNovDec", arr[1])-1)/3+1;
+ return mktime(arr[3] " " month " " arr[2])
+}
+
+# Queue a new job
+function at(from, text, prefix, now, arr, desc, time, mesg, cmd) {
+ now = systime();
+
+ # Check input
+ if (!match(text, /^.(at|in) +([a-zA-Z0-9:+-]+|"[ a-zA-Z0-9:+-]+") +(.*)/, arr))
+ return reply("invalid time");
+ if (!arr[3])
+ return reply("no message");
+ desc = arr[2];
+ mesg = arr[3];
+
+ # Parse date
+ time = atp(now, prefix desc);
+ if (time < 0)
+ return reply("unparsable time: " desc);
+ if (time < now)
+ return reply("time is in the past");
+ if (time == now)
+ return reply("date is right now");
+
+ # Log message
+ id = length(remind);
+ remind[id]["time"] = time;
+ remind[id]["from"] = from;
+ remind[id]["mesg"] = mesg;
+ remind[id]["done"] = "pending";
+ json_save("var/remind.json", remind);
+
+ say("queued job " id ": " \
+ strftime("%Y-%m-%d %H:%M", time));
+}
+
+# Print the at queue
+function atq(all, i, from, time, done, desc, line) {
+ for (i = 0; i < length(remind); i++) {
+ from = remind[i]["from"];
+ time = remind[i]["time"];
+ done = remind[i]["done"];
+ if (!all && done != "pending")
+ continue;
+ desc = strftime("%Y-%m-%d %H:%M", time);
+ line = sprintf("%-3d %s %s (%s)", i, desc, from, done);
+ say(line);
+ }
+}
+
+# Print an at job
+function atc(id) {
+ if (id >= length(remind))
+ return reply("invalid job id");
+ say("job " id ": " remind[id]["mesg"]);
+}
+
+# Remove job from the queue
+function atrm(id) {
+ if (id >= length(remind))
+ return reply("invalid job id");
+ if (remind[id]["done"] != "pending")
+ return reply("job is not pending");
+ remind[id]["done"] = "canceled";
+ json_save("var/remind.json", remind);
+ say("canceled job " id);
+}
+
+# Run the at daemon
+function atd(now) {
+ now = systime();
+ for (i = 0; i < length(remind); i++) {
+ if (remind[i]["done"] != "pending")
+ continue;
+ if (remind[i]["time"] > now)
+ continue;
+ remind[i]["done"] = "finished";
+ say(CHANNEL, remind[i]["from"] ": " remind[i]["mesg"]);
+ }
+ json_save("var/remind.json", remind);
+}
+
+
+# At handlers
+/^\.at / {
+ at(FROM, $0, "");
+}
+
+/^\.in / {
+ at(FROM, $0, "now+");
+}
+
+/^\.atq/ {
+ atq($0 ~ /!/);
+}
+
+/^\.atc +[0-9]+$/ {
+ atc($2);
+}
+
+/^\.atrm +[0-9]+$/ {
+ atrm($2);
+}
+
+/^\.help$/ {
+ say(".help at -- queue reminders for later")
+}
+
+/^\.help at/ {
+ say(".at [time] [msg] -- record a message for the given time")
+ say(".in [len] [msg] -- same as .at \"now + len\"")
+ say(".atq -- list any pending job")
+ say(".atq! -- list all jobs in the queue")
+ say(".atc [n] -- print the given job")
+ say(".atrm [n] -- delete the given job")
+ next
+}
+
+// {
+ atd();
+}