#!/usr/bin/env python3 """ Reminds of things. """ import os import re import time import sqlite3 import datetime import threading import collections from module import commands, example class MonitorThread(threading.Thread): """ A custom custom thread class for monitoring the time to announce reminders. It allows itself to be stopped when there are no reminders to look for. """ def __init__(self, bot): threading.Thread.__init__(self) self._bot = bot self.stop = threading.Event() def run(self): # while not self._bot.channels.keys(): while not self._bot.stillConnected(): time.sleep(1) # don't try to say anything if we're not fully connected yet while not self.stop.is_set(): now = int(time.time()) unixtimes = [int(key) for key in self._bot.memory["remind"].keys()] oldtimes = [t for t in unixtimes if t <= now] if oldtimes: for oldtime in oldtimes: for reminder in self._bot.memory["remind"][oldtime]: channel, nick, message = reminder if message: self._bot.msg(channel, nick + ': ' + message) else: self._bot.msg(channel, nick + '!') del self._bot.memory["remind"][oldtime] delete_reminder(self._bot, oldtime) if not self._bot.memory["remind"] or not self._bot.stillConnected(): self.stop.set() self.stop.wait(2.5) del self._bot.memory["remind_monitor"] def start_monitor(bot): """ Starts the monitor thread. Does nothing if one is already running. """ if bot.memory.get("remind_monitor"): return t = MonitorThread(bot) t.start() bot.memory["remind_monitor"] = t def load_database(bot): """ Loads all entries from the 'remind' table in the bot's database and stores them in memory """ data = {} reminds = bot.db.execute("SELECT * FROM remind").fetchall() for remind in reminds: unixtime, channel, nick, message = remind reminder = (channel, nick, message) try: data[unixtime].append(reminder) except KeyError: data[unixtime] = [reminder] return data def insert_reminder(bot, unixtime, reminder): """ Inserts a new reminder into the 'remind' table in the bot's database. reminder - a tuple containing (channel, nick, message) """ bot.db.execute("INSERT INTO remind (unixtime, channel, nick, message) " "VALUES(?,?,?,?)", (unixtime,) + reminder) def delete_reminder(bot, unixtime): """ Deletes a reminder from the 'remind' table in the bot's database, using unixtime as the key. """ bot.db.execute("DELETE FROM remind WHERE unixtime = ?", (unixtime,)) def setup(bot): con = bot.db.connect() cur = con.cursor() try: cur.execute("SELECT * FROM remind").fetchone() except sqlite3.OperationalError: cur.execute("CREATE TABLE remind(" "unixtime INTEGER DEFAULT (STRFTIME('%s', 'now'))," "channel TEXT," "nick TEXT," "message TEXT)") con.commit() con.close() bot.memory["remind"] = load_database(bot) start_monitor(bot) scaling = collections.OrderedDict([ ('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), ]) periods = '|'.join(scaling.keys()) @commands('remind') @example('.remind 3h45m Go to class') def remind(bot, trigger): """Gives you a reminder in the given amount of time.""" if not trigger.group(2): return bot.msg("Missing arguments for reminder command.") if trigger.group(3) and not trigger.group(4): return bot.msg("No message given for reminder.") duration = 0 message = filter(None, re.split(f"(\d+(?:\.\d+)? ?(?:(?i) {periods})) ?", trigger.group(2))[1:]) reminder = '' stop = False for piece in message: grp = re.match('(\d+(?:\.\d+)?) ?(.*) ?', piece) if grp and not stop: length = float(grp.group(1)) factor = scaling.get(grp.group(2).lower(), 60) duration += length * factor else: reminder = reminder + piece stop = True if duration == 0: return bot.reply("Sorry, didn't understand the input.") if duration % 1: duration = int(duration) + 1 else: duration = int(duration) create_reminder(bot, trigger, duration, reminder) @commands('at') @example('.at 13:47 Do your homework!') @example('.at 16:30UTC-5 Do cocaine') @example('.at 14:45:45 Remove dick from oven') def at(bot, trigger): """ Gives you a reminder at the given time. Takes hh:mm:ssUTC+/-## message. Timezone, if provided, must be in UTC format. 24 hour clock format only. """ if not trigger.group(2): return bot.msg("No arguments given for reminder command.") if trigger.group(3) and not trigger.group(4): return bot.msg("No message given for reminder.") regex = re.compile(r"(\d+):(\d+)(?::(\d+))?(?:UTC([+-]\d+))? (.*)") match = regex.match(trigger.group(2)) if not match: return bot.reply("Sorry, but I didn't understand your input.") hour, minute, second, tz, message = match.groups() if not second: second = '0' if tz: try: tz = int(tz.replace("UTC", "")) except ValueError: bot.msg("Invalid timezone. Using the bot's current timezone.") tz = None if tz: timezone = datetime.timezone(datetime.timedelta(hours=tz)) else: timezone = datetime.datetime.now().astimezone().tzinfo # current timezone the bot is in now = datetime.datetime.now(timezone) at_time = datetime.datetime(now.year, now.month, now.day, int(hour), int(minute), int(second), tzinfo=timezone) timediff = at_time - now duration = timediff.seconds if duration < 0: duration += 86400 create_reminder(bot, trigger, duration, message) def create_reminder(bot, trigger, duration, message): """ Inserts the reminder into the bot's memory and database so it can eventually announce for it. """ t = int(time.time()) + duration reminder = (trigger.channel, trigger.nick, message) try: bot.memory["remind"][t].append(reminder) except KeyError: bot.memory["remind"][t] = [reminder] start_monitor(bot) insert_reminder(bot, t, reminder) if duration >= 60: remind_at = datetime.datetime.fromtimestamp(t) t_format = bot.config.core.default_time_format timef = datetime.datetime.strftime(remind_at, t_format) bot.reply('Okay, will remind at %s' % timef) else: bot.reply('Okay, will remind in %s secs' % duration)