212 lines
10 KiB
Python
212 lines
10 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
|
||
from ircbot.models import IrcChannel
|
||
|
||
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|with)\s+(?P<text>.*?)'
|
||
r'(?=\s+\("(?P<name>.*)"\)|$)')
|
||
|
||
def __init__(self, bot, connection, event):
|
||
"""Initialize some stuff."""
|
||
self.running_reminders = []
|
||
self.send_reminders = True
|
||
self.server_config = connection.server_config
|
||
|
||
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+(.+)$',
|
||
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(),
|
||
reminder_target_new__server=self.server_config)
|
||
|
||
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_new.name)
|
||
self.bot.reply(None, reminder.reminder_message, explicit_target=reminder.reminder_target_new.name)
|
||
|
||
# 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(f'in one {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')
|
||
name = match.group('name')
|
||
log.debug("%s / %s / %s", who, when, text)
|
||
|
||
if not name:
|
||
item_name = '{0:s}-{1:s}'.format(event.sender_nick, timezone.now().strftime('%s'))
|
||
else:
|
||
if CountdownItem.objects.filter(name=name).count() > 0:
|
||
self.bot.reply(event, "item with name '{0:s}' already exists".format(name))
|
||
return 'NO MORE'
|
||
item_name = name
|
||
|
||
# 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)
|
||
|
||
# get the IrcChannel to send to
|
||
reminder_target, _ = IrcChannel.objects.get_or_create(name=event.sent_location,
|
||
server=connection.server_config)
|
||
|
||
countdown_item = CountdownItem.objects.create(name=item_name, at_time=when_t, is_reminder=True,
|
||
reminder_message=message, reminder_target_new=reminder_target)
|
||
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
|