273 lines
9.4 KiB
Python
273 lines
9.4 KiB
Python
"""
|
|
.. module:: Remind
|
|
:synopsis: A module to support reminders
|
|
|
|
.. moduleauthor:: Dave Pedu <dave@davepedu.com>
|
|
|
|
"""
|
|
|
|
from pyircbot.modulebase import ModuleBase, command
|
|
from datetime import datetime, timedelta
|
|
from threading import Thread
|
|
from time import sleep
|
|
import re
|
|
import pytz
|
|
from pyircbot.modules.ModInfo import info
|
|
|
|
|
|
class Remind(ModuleBase):
|
|
def __init__(self, bot, moduleName):
|
|
ModuleBase.__init__(self, bot, moduleName)
|
|
|
|
self.db = None
|
|
serviceProviders = self.bot.getmodulesbyservice("sqlite")
|
|
if not serviceProviders:
|
|
self.log.error("Remind: Could not find a valid sqlite service provider")
|
|
else:
|
|
self.log.info("Remind: Selecting sqlite service provider: %s" % serviceProviders[0])
|
|
self.db = serviceProviders[0].opendb("remind.db")
|
|
|
|
if not self.db.tableExists("reminders"):
|
|
self.log.info("Remind: Creating table: reminders")
|
|
c = self.db.query("""CREATE TABLE IF NOT EXISTS `reminders` (
|
|
`id` INTEGER PRIMARY KEY,
|
|
`sender` varchar(64),
|
|
`senderch` varchar(64),
|
|
`when` timestamp,
|
|
`message` varchar(2048)
|
|
) ;""")
|
|
c.close()
|
|
|
|
self.disabled = False
|
|
|
|
# Start monitor thread
|
|
self.t = Thread(target=self.monitor_thread)
|
|
self.t.daemon = True
|
|
self.t.start()
|
|
|
|
def monitor_thread(self):
|
|
while True:
|
|
sleep(self.config["precision"])
|
|
if self.disabled:
|
|
break
|
|
self.monitor()
|
|
|
|
def monitor(self):
|
|
remindPeople = self.db.query("SELECT * FROM `reminders` WHERE `when` < ?", (datetime.now(),))
|
|
reminders = remindPeople.fetchall()
|
|
remindPeople.close()
|
|
|
|
byrecip = {}
|
|
|
|
for reminder in reminders:
|
|
if not reminder["sender"] in byrecip:
|
|
byrecip[reminder["sender"]] = []
|
|
|
|
byrecip[reminder["sender"]].append(reminder)
|
|
|
|
reminders_bych = {}
|
|
|
|
for recip in byrecip:
|
|
reminders_pm = []
|
|
|
|
for reminder in byrecip[recip]:
|
|
if reminder["senderch"] == "":
|
|
reminders_pm.append(reminder)
|
|
else:
|
|
if not reminder["senderch"] in reminders_bych:
|
|
reminders_bych[reminder["senderch"]] = []
|
|
reminders_bych[reminder["senderch"]].append(reminder)
|
|
self.sendReminders(reminders_pm, recip, recip)
|
|
|
|
for channel in reminders_bych:
|
|
channelpms_bysender = {}
|
|
|
|
for chreminder in reminders_bych[channel]:
|
|
if not chreminder["sender"] in channelpms_bysender:
|
|
channelpms_bysender[chreminder["sender"]] = []
|
|
channelpms_bysender[chreminder["sender"]].append(chreminder)
|
|
|
|
for recip in channelpms_bysender:
|
|
self.sendReminders(channelpms_bysender[recip], channel, recip)
|
|
|
|
# Delete now that it's sent
|
|
for item in reminders:
|
|
self.db.query("DELETE FROM `reminders` WHERE `id`=?", (item["id"],)).close()
|
|
|
|
def sendReminders(self, reminders, target, nick):
|
|
" Send a set of reminders of the same recipient, to them. Collapse down into one message."
|
|
reminder_str = []
|
|
for reminder in reminders:
|
|
reminder_str.append(reminder["message"])
|
|
reminder_str = ", ".join(reminder_str)
|
|
if len(reminder_str) > 0:
|
|
self.bot.act_PRIVMSG(target, "%s: Reminder: %s" % (nick, reminder_str))
|
|
|
|
def ondisable(self):
|
|
self.disabled = True
|
|
|
|
@info("remind <time>", "have the bot remind you", cmds=["remind", "at"])
|
|
@command("remind", "at", allow_private=True)
|
|
def remindat(self, msg, cmd):
|
|
regex = re.compile(r'(\d+):(\d+)(?::(\d+))?([^\s\d]+)? (.*)')
|
|
match = regex.match(cmd.args_str)
|
|
replyTo = msg.args[0]
|
|
try:
|
|
hour, minute, second, tz, message = match.groups()
|
|
message = message.strip()
|
|
|
|
assert not message == ""
|
|
|
|
hour = int(hour)
|
|
minute = int(minute)
|
|
if second is not None:
|
|
second = int(second)
|
|
|
|
except:
|
|
self.bot.act_PRIVMSG(replyTo, "%s: .at - Remind at a time. Example: .at 20:45EST Do your homework!" %
|
|
msg.prefix.nick)
|
|
return
|
|
|
|
now = datetime.now()
|
|
remindAt = datetime.now()
|
|
|
|
# if there was timezone, make sure the time we're reminding them at is relative to their timezone
|
|
if tz is not None:
|
|
try:
|
|
theirzone = pytz.timezone(Remind.translateZoneStr(tz))
|
|
except:
|
|
self.bot.act_PRIVMSG(replyTo, "%s: I don't recognize the timezone '%s'." % (msg.prefix.nick, tz))
|
|
return
|
|
remindAt = theirzone.localize(remindAt, is_dst=Remind.is_dst(theirzone))
|
|
|
|
# Set the hour and minute we'll remind them at today.
|
|
# If the ends up being in the past, we'll add a day alter
|
|
remindAt = remindAt.replace(hour=hour).replace(minute=minute).replace(microsecond=0)
|
|
|
|
# Set seconds
|
|
if second is None:
|
|
remindAt = remindAt.replace(second=0)
|
|
else:
|
|
remindAt = remindAt.replace(second=second)
|
|
|
|
# if there was timezone, convert remindAt to our zone
|
|
if tz is not None:
|
|
try:
|
|
theirzone = pytz.timezone(Remind.translateZoneStr(tz))
|
|
except:
|
|
self.bot.act_PRIVMSG(replyTo, "%s: I don't recognize the timezone '%s'." % (msg.prefix.nick, tz))
|
|
return
|
|
remindAt = remindAt.astimezone(pytz.timezone(self.config["mytimezone"])).replace(tzinfo=None)
|
|
|
|
# Advance it a day if the time would have been earlier today
|
|
while remindAt < now:
|
|
remindAt += timedelta(days=1)
|
|
|
|
timediff = remindAt - datetime.now()
|
|
# self.bot.act_PRIVMSG(replyTo, "Time: %s" % str(remindAt))
|
|
# self.bot.act_PRIVMSG(replyTo, "Diff: %s" % (timediff))
|
|
|
|
# Save the reminder
|
|
c = self.db.query("INSERT INTO `reminders` (`sender`, `senderch`, `when`, `message`) VALUES (?, ?, ?, ?)", (
|
|
msg.prefix.nick,
|
|
msg.args[0] if "#" in msg.args[0] else "",
|
|
remindAt,
|
|
message
|
|
))
|
|
c.close()
|
|
|
|
diffHours = int(timediff.seconds / 60 / 60)
|
|
diffMins = int((timediff.seconds - diffHours * 60 * 60) / 60)
|
|
|
|
self.bot.act_PRIVMSG(replyTo, "%s: Ok, will do. Approx %sh%sm to go." %
|
|
(msg.prefix.nick, diffHours, diffMins))
|
|
|
|
@staticmethod
|
|
def is_dst(tz):
|
|
now = pytz.utc.localize(datetime.utcnow())
|
|
return now.astimezone(tz).dst() != timedelta(0)
|
|
|
|
@staticmethod
|
|
def translateZoneStr(zonestr):
|
|
translations = {
|
|
"EDT": "US/Eastern",
|
|
"PDT": "America/Los_Angeles"
|
|
}
|
|
if zonestr in translations:
|
|
return translations[zonestr]
|
|
else:
|
|
return zonestr
|
|
|
|
@info("after <duration> <reminder>", "have the bot remind after", cmds=["after", "in"])
|
|
@command("after", "in", allow_private=True)
|
|
def remindin(self, msg, cmd):
|
|
replyTo = msg.args[0]
|
|
|
|
if not cmd.args:
|
|
self.bot.act_PRIVMSG(replyTo, "%s: .in - Remind after x amount of time. Example: .in 1week5d2h1m Go "
|
|
"fuck yourself" % msg.prefix.nick)
|
|
return
|
|
|
|
timepieces = re.compile(r'([0-9]+)([a-zA-Z]+)').findall(cmd.args[0])
|
|
if not timepieces:
|
|
self.bot.act_PRIVMSG(replyTo, "%s: .in - Remind after x amount of time. Example: .in 1week5d2h1m Go "
|
|
"fuck yourself" % msg.prefix.nick)
|
|
return
|
|
|
|
delaySeconds = 0
|
|
for match in timepieces:
|
|
# ('30', 'm')
|
|
if not match[1] in Remind.scaling:
|
|
self.bot.act_PRIVMSG(replyTo, "%s: Sorry, I don't understand the time unit '%s'" %
|
|
(msg.prefix.nick, match[1]))
|
|
return
|
|
|
|
delaySeconds += (Remind.scaling[match[1]] * int(match[0]))
|
|
|
|
remindAt = datetime.now() + timedelta(seconds=delaySeconds)
|
|
|
|
self.db.query("INSERT INTO `reminders` (`sender`, `senderch`, `when`, `message`) VALUES (?, ?, ?, ?)", (
|
|
msg.prefix.nick,
|
|
msg.args[0] if "#" in msg.args[0] else "",
|
|
remindAt,
|
|
cmd.args_str[len(cmd.args[0]):].strip()
|
|
)).close()
|
|
|
|
hours = int(delaySeconds / 60 / 60)
|
|
minutes = int((delaySeconds - (hours * 60 * 60)) / 60)
|
|
|
|
self.bot.act_PRIVMSG(replyTo, "%s: Ok, talk to you in approx %sh%sm" % (msg.prefix.nick, hours, minutes))
|
|
|
|
scaling = {
|
|
"years": 365.25 * 24 * 3600,
|
|
"year": 365.25 * 24 * 3600,
|
|
"yrs": 365.25 * 24 * 3600,
|
|
"y": 365.25 * 24 * 3600,
|
|
"months": 29.53059 * 24 * 3600,
|
|
"month": 29.53059 * 24 * 3600,
|
|
"mo": 29.53059 * 24 * 3600,
|
|
"weeks": 7 * 24 * 3600,
|
|
"week": 7 * 24 * 3600,
|
|
"wks": 7 * 24 * 3600,
|
|
"wk": 7 * 24 * 3600,
|
|
"w": 7 * 24 * 3600,
|
|
"days": 24 * 3600,
|
|
"day": 24 * 3600,
|
|
"d": 24 * 3600,
|
|
"hours": 3600,
|
|
"hour": 3600,
|
|
"hrs": 3600,
|
|
"hr": 3600,
|
|
"h": 3600,
|
|
"minutes": 60,
|
|
"minute": 60,
|
|
"mins": 60,
|
|
"min": 60,
|
|
"m": 60,
|
|
"seconds": 1,
|
|
"second": 1,
|
|
"secs": 1,
|
|
"sec": 1,
|
|
"s": 1,
|
|
}
|