dr.botzo/countdown/ircplugin.py

212 lines
10 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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