pyircbot/pyircbot/modules/Remind.py

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,
}