#!/usr/bin/env python3 """ Reminds of things. """ import os import re import time import sqlite3 import datetime import threading import collections import config 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.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) regex = ( "(?=\d+[ywdhms])" "(?:(\d+)y)?" "(?:(\d+)w)?" "(?:(\d+)d)?" "(?:(\d+)h)?" "(?:(\d+)m)?" "(?:(\d+)s)?" ) multiplier = [ 60*60*24*365, 60*60*24*7, 60*60*24, 60*60, 60, 1 ] @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): reminder = '' else: reminder = ' '.join(trigger.group[4:]) duration = 0 for n, group in enumerate(re.search(regex, trigger.group(3)).groups()): if not group: continue duration += int(group) * multiplier[n] 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 = config.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)