Brian S. Stephan
55c1cf01a2
lets countdown.ircplugin use the regex handler and do less checking itself
196 lines
9.4 KiB
Python
196 lines
9.4 KiB
Python
"""Access to countdown items through bot commands."""
|
||
|
||
import logging
|
||
import re
|
||
import threading
|
||
import time
|
||
|
||
import parsedatetime as pdt
|
||
from dateutil.parser import parse
|
||
from dateutil.relativedelta import relativedelta
|
||
from django.utils import timezone
|
||
|
||
from countdown.models import CountdownItem
|
||
from ircbot.lib import Plugin, most_specific_message
|
||
|
||
log = logging.getLogger('countdown.ircplugin')
|
||
|
||
|
||
class Countdown(Plugin):
|
||
"""Report on countdown items."""
|
||
|
||
new_reminder_regex = (r'remind\s+(?P<who>[^\s]+)\s+(?P<when_type>at|in|on)\s+(?P<when>.*?)\s+'
|
||
r'(and\s+every\s+(?P<recurring_period>.*?)\s+)?'
|
||
r'(until\s+(?P<recurring_until>.*?)\s+)?'
|
||
r'(to|that|about)\s+(?P<text>.*)')
|
||
|
||
def __init__(self, bot, connection, event):
|
||
"""Initialize some stuff."""
|
||
self.running_reminders = []
|
||
self.send_reminders = True
|
||
|
||
t = threading.Thread(target=self.reminder_thread)
|
||
t.daemon = True
|
||
t.start()
|
||
|
||
super(Countdown, self).__init__(bot, connection, event)
|
||
|
||
def start(self):
|
||
"""Set up handlers."""
|
||
self.running_reminders = []
|
||
self.send_reminders = True
|
||
|
||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+list$',
|
||
self.handle_item_list, -20)
|
||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+(\S+)$',
|
||
self.handle_item_detail, -20)
|
||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], self.new_reminder_regex,
|
||
self.handle_new_reminder, -50)
|
||
|
||
super(Countdown, self).start()
|
||
|
||
def stop(self):
|
||
"""Tear down handlers."""
|
||
self.running_reminders = []
|
||
self.send_reminders = False
|
||
|
||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_item_list)
|
||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_item_detail)
|
||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_new_reminder)
|
||
|
||
super(Countdown, self).stop()
|
||
|
||
def reminder_thread(self):
|
||
"""After IRC is started up, begin sending reminders."""
|
||
# TODO: figure out if we need this sleep, which exists so we don't send reminders while still connecting to IRC
|
||
time.sleep(30)
|
||
while self.send_reminders:
|
||
reminders = CountdownItem.objects.filter(is_reminder=True, sent_reminder=False, at_time__lte=timezone.now())
|
||
|
||
for reminder in reminders:
|
||
log.debug("%s @ %s", reminder.reminder_message, reminder.at_time)
|
||
if reminder.at_time <= timezone.now():
|
||
log.info("sending %s to %s", reminder.reminder_message, reminder.reminder_target)
|
||
self.bot.reply(None, reminder.reminder_message, explicit_target=reminder.reminder_target)
|
||
|
||
# if recurring and not hit until, set a new at time, otherwise stop reminding
|
||
if reminder.recurring_until is not None and timezone.now() >= reminder.recurring_until:
|
||
reminder.sent_reminder = True
|
||
elif reminder.recurring_period != '':
|
||
calendar = pdt.Calendar()
|
||
when_t = calendar.parseDT(reminder.recurring_period, reminder.at_time,
|
||
tzinfo=reminder.at_time.tzinfo)[0]
|
||
if reminder.recurring_until is None or when_t <= reminder.recurring_until:
|
||
reminder.at_time = when_t
|
||
else:
|
||
reminder.sent_reminder = True
|
||
else:
|
||
reminder.sent_reminder = True
|
||
reminder.save()
|
||
time.sleep(1)
|
||
|
||
def handle_new_reminder(self, connection, event, match):
|
||
"""Watch IRC for requests to remind new things, create countdown items."""
|
||
if event.in_privmsg or event.addressed:
|
||
log.debug("%s is a new reminder request", most_specific_message(event))
|
||
who = match.group('who')
|
||
when_type = match.group('when_type')
|
||
when = match.group('when')
|
||
recurring_period = match.group('recurring_period')
|
||
recurring_until = match.group('recurring_until')
|
||
text = match.group('text')
|
||
log.debug("%s / %s / %s", who, when, text)
|
||
|
||
item_name = '{0:s}-{1:s}'.format(event.sender_nick, timezone.now().strftime('%s'))
|
||
|
||
# parse when to send the notification
|
||
if when_type == 'in':
|
||
# relative time
|
||
calendar = pdt.Calendar()
|
||
when_t = calendar.parseDT(when, timezone.localtime(timezone.now()),
|
||
tzinfo=timezone.get_current_timezone())[0]
|
||
else:
|
||
# absolute time
|
||
when_t = timezone.make_aware(parse(when))
|
||
|
||
# parse the person to address, if anyone, when sending the notification
|
||
if who == 'us' or who == event.sent_location or event.in_privmsg:
|
||
message = text
|
||
elif who == 'me':
|
||
message = '{0:s}: {1:s}'.format(event.sender_nick, text)
|
||
else:
|
||
message = '{0:s}: {1:s}'.format(who, text)
|
||
|
||
# replace pronouns and stuff
|
||
if who == 'me':
|
||
message = re.sub(r'\bI\b', r'you', message, flags=re.IGNORECASE)
|
||
message = re.sub(r'\bme\b', r'you', message, flags=re.IGNORECASE)
|
||
message = re.sub(r'\bmy\b', r'your', message, flags=re.IGNORECASE)
|
||
message = re.sub(r'\bmyself\b', r'yourself', message, flags=re.IGNORECASE)
|
||
elif who == 'us':
|
||
message = re.sub(r'\bwe\b', r'you', message, flags=re.IGNORECASE)
|
||
message = re.sub(r'\bus\b', r'you', message, flags=re.IGNORECASE)
|
||
message = re.sub(r'\bour\b', r'your', message, flags=re.IGNORECASE)
|
||
message = re.sub(r'\bourselves\b', r'yourselves', message, flags=re.IGNORECASE)
|
||
|
||
log.debug("%s / %s / %s", item_name, when_t, message)
|
||
|
||
countdown_item = CountdownItem.objects.create(name=item_name, at_time=when_t, is_reminder=True,
|
||
reminder_message=message, reminder_target=event.sent_location)
|
||
if recurring_period:
|
||
countdown_item.recurring_period = recurring_period
|
||
if recurring_until:
|
||
recurring_until_t = timezone.make_aware(parse(recurring_until))
|
||
countdown_item.recurring_until = recurring_until_t
|
||
countdown_item.save()
|
||
log.info("created countdown item %s", str(countdown_item))
|
||
|
||
if event.in_privmsg:
|
||
self.bot.reply(event, "ok, i'll message you at {0:s} 14[{1:s}]".format(str(when_t),
|
||
countdown_item.name))
|
||
else:
|
||
self.bot.reply(event, "ok, i'll message {0:s} at {1:s} 14[{2:s}]".format(event.sent_location,
|
||
str(when_t),
|
||
countdown_item.name))
|
||
|
||
return 'NO MORE'
|
||
|
||
def handle_item_detail(self, connection, event, match):
|
||
"""Provide the details of one countdown item."""
|
||
name = match.group(1)
|
||
|
||
if name != 'list':
|
||
try:
|
||
item = CountdownItem.objects.get(name=name)
|
||
rdelta = relativedelta(item.at_time, timezone.now())
|
||
relstr = "{0:s} will occur in ".format(name)
|
||
if rdelta.years != 0:
|
||
relstr += "{0:s} year{1:s} ".format(str(rdelta.years), "s" if abs(rdelta.years) != 1 else "")
|
||
if rdelta.months != 0:
|
||
relstr += "{0:s} month{1:s}, ".format(str(rdelta.months), "s" if abs(rdelta.months) != 1 else "")
|
||
if rdelta.days != 0:
|
||
relstr += "{0:s} day{1:s}, ".format(str(rdelta.days), "s" if abs(rdelta.days) != 1 else "")
|
||
if rdelta.hours != 0:
|
||
relstr += "{0:s} hour{1:s}, ".format(str(rdelta.hours), "s" if abs(rdelta.hours) != 1 else "")
|
||
if rdelta.minutes != 0:
|
||
relstr += "{0:s} minute{1:s}, ".format(str(rdelta.minutes), "s" if abs(rdelta.minutes) != 1 else "")
|
||
if rdelta.seconds != 0:
|
||
relstr += "{0:s} second{1:s}, ".format(str(rdelta.seconds), "s" if abs(rdelta.seconds) != 1 else "")
|
||
# remove trailing comma from output
|
||
reply = relstr[0:-2]
|
||
return self.bot.reply(event, reply)
|
||
except CountdownItem.DoesNotExist:
|
||
return self.bot.reply(event, "countdown item '{0:s}' not found".format(name))
|
||
|
||
def handle_item_list(self, connection, event, match):
|
||
"""List all countdown items."""
|
||
items = CountdownItem.objects.all()
|
||
if len(items) > 0:
|
||
reply = "countdown items: {0:s}".format(", ".join([x.name for x in items]))
|
||
return self.bot.reply(event, reply)
|
||
else:
|
||
return self.bot.reply(event, "no countdown items are configured")
|
||
|
||
|
||
plugin = Countdown
|