#!/usr/bin/env python3 """ Reminds of things. """ import os import re import time import sqlite3 import threading from datetime import datetime 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 len(trigger.args) == 1: return bot.msg("Missing arguments for reminder command.") if len(trigger.args) == 2: reminder = '' else: reminder = ' '.join(trigger.args[2:]) duration = 0 for n, group in enumerate(re.search(regex, trigger.args[1]).groups()): if not group: continue duration += int(group) * multiplier[n] create_reminder(bot, trigger, duration, reminder) @commands('at') @example('.at 2012-12-21 18:00:00 End the world.') def at(bot, trigger): """ Gives you a reminder at the given time and date. Datetime must be in YYYY-MM-DD HH:MM:SS format. Only the bot's timezone is used. """ if len(trigger.args) < 3: return bot.msg("Missing arguments for reminder command.") if len(trigger.args) < 4: reminder = '' else: reminder = ' '.join(trigger.args[3:]) try: at_time = datetime.strptime(' '.join(trigger.args[1:2]), '%Y-%m-%d %H:%M:%S') except ValueError: return bot.msg("Datetime improperly formatted.") diff = at_time - datetime.now() duration = diff.seconds create_reminder(bot, trigger, duration, reminder) 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.fromtimestamp(t) t_format = config.default_time_format timef = datetime.strftime(remind_at, t_format) bot.reply('Okay, will remind at %s' % timef) else: bot.reply('Okay, will remind in %s secs' % duration)