""" Countdown - track and display time until events Copyright (C) 2010 Brian S. Stephan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from datetime import datetime from dateutil.parser import * from dateutil.relativedelta import * from dateutil.tz import * import MySQLdb as mdb from extlib import irclib from Module import Module class Countdown(Module): """Track when events will happen.""" def db_init(self): """Set up the database tables, if they don't exist.""" version = self.db_module_registered(self.__class__.__name__) if (version == None): db = self.get_db() try: version = 1 cur = db.cursor(mdb.cursors.DictCursor) cur.execute(''' CREATE TABLE countdown_item ( name VARCHAR(255) NOT NULL, source VARCHAR(255) NOT NULL, time TIMESTAMP NOT NULL, PRIMARY KEY (name, source) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ''') db.commit() self.db_register_module_version(self.__class__.__name__, version) except mdb.Error as e: db.rollback() self.log.error("database error trying to create tables") self.log.exception(e) raise finally: cur.close() def register_handlers(self): """Hook handler functions into the IRC library.""" # register IRC regex handlers self.irc.add_global_regex_handler('pubmsg', r'^!countdown\s+add\s+(\S+)\s+(.*)$', self.add_item) self.irc.add_global_regex_handler('privmsg', r'^!countdown\s+add\s+(\S+)\s+(.*)$', self.add_item) self.irc.add_global_regex_handler('pubmsg', r'^!countdown\s+remove\s+(\S+)$', self.remove_item) self.irc.add_global_regex_handler('privmsg', r'^!countdown\s+remove\s+(\S+)$', self.remove_item) self.irc.add_global_regex_handler('pubmsg', r'^!countdown\s+list$', self.list_items) self.irc.add_global_regex_handler('privmsg', r'^!countdown\s+list$', self.list_items) self.irc.add_global_regex_handler('pubmsg', r'^!countdown\s+(\S+)$', self.item_detail) self.irc.add_global_regex_handler('privmsg', r'^!countdown\s+(\S+)$', self.item_detail) def unregister_handlers(self): """Unhook handler functions from the IRC library.""" # register IRC regex handlers self.irc.remove_global_regex_handler('pubmsg', r'^!countdown\s+add\s+(\S+)\s+(.*)$', self.add_item) self.irc.remove_global_regex_handler('privmsg', r'^!countdown\s+add\s+(\S+)\s+(.*)$', self.add_item) self.irc.remove_global_regex_handler('pubmsg', r'^!countdown\s+remove\s+(\S+)$', self.remove_item) self.irc.remove_global_regex_handler('privmsg', r'^!countdown\s+remove\s+(\S+)$', self.remove_item) self.irc.remove_global_regex_handler('pubmsg', r'^!countdown\s+list$', self.list_items) self.irc.remove_global_regex_handler('privmsg', r'^!countdown\s+list$', self.list_items) self.irc.remove_global_regex_handler('pubmsg', r'^!countdown\s+(\S+)$', self.item_detail) self.irc.remove_global_regex_handler('privmsg', r'^!countdown\s+(\S+)$', self.item_detail) def add_item(self, nick, userhost, event, from_admin, groups): """Add a new item to track, and the datetime it occurs. Args: nick source nickname (unused) userhost source userhost (unused) event IRC event, target used to associate item to a source from_admin whether or not the event came from an admin (unused) groups tuple length 2, the item name and the time of the item """ try: name, time_str = groups time = parse(time_str, default=datetime.now().replace(tzinfo=tzlocal())) # determine if source is a channel of a privmsg source = event.target() if source == self.irc.server.get_nickname(): source = irclib.nm_to_n(event.source()) self._add_countdown_item_to_database(name, source, time.astimezone(tzutc())) self.log.debug("added countdown item '{0:s}' at " "{1:s}".format(name, time.isoformat())) replystr = "added countdown item '{0:s}'".format(name) return self.irc.reply(event, replystr) except Exception as e: self.log.error("could not add countdown item") self.log.exception(e) return self.irc.reply(event, ("could not add countdown item: " "{0:s}".format(str(e)))) def remove_item(self, nick, userhost, event, from_admin, groups): """State when the provided item will occur. Args: nick source nickname (unused) userhost source userhost (unused) event IRC event, target used to filter item names from_admin whether or not the event came from an admin (unused) groups tuple length 1, the item name to remove """ try: name = groups[0] # determine if source is a channel of a privmsg source = event.target() if source == self.irc.server.get_nickname(): source = irclib.nm_to_n(event.source()) self._remove_countdown_item_from_database(name, source) replystr = "removed countdown item '{0:s}'".format(name) return self.irc.reply(event, replystr) except Exception: pass def list_items(self, nick, userhost, event, from_admin, groups): """State when the provided item will occur. Args: nick source nickname (unused) userhost source userhost (unused) event IRC event, target used to filter item names from_admin whether or not the event came from an admin (unused) groups empty tuple (unused) """ try: # determine if source is a channel of a privmsg source = event.target() if source == self.irc.server.get_nickname(): source = irclib.nm_to_n(event.source()) cdlist = self._list_countdown_items(source) print(cdlist) liststr = "countdown items: {0:s}".format(", ".join(cdlist)) return self.irc.reply(event, liststr) except Exception: pass def item_detail(self, nick, userhost, event, from_admin, groups): """State when the provided item will occur. Args: nick source nickname (unused) userhost source userhost (unused) event IRC event, target used to filter item names from_admin whether or not the event came from an admin (unused) groups tuple length 1, the item name to look up """ try: name = groups[0] # determine if source is a channel of a privmsg source = event.target() if source == self.irc.server.get_nickname(): source = irclib.nm_to_n(event.source()) time = self._get_countdown_item_time(name, source) if time: rdelta = relativedelta(time, datetime.now().replace(tzinfo=tzlocal())) relstr = "{0:s} will occur in ".format(name) if rdelta.years != 0: relstr += "{0:s} year{1:s} ".format(str(rdelta.years), "s" if rdelta.years != 1 else "") if rdelta.months != 0: relstr += "{0:s} month{1:s}, ".format(str(rdelta.months), "s" if rdelta.months != 1 else "") if rdelta.days != 0: relstr += "{0:s} day{1:s}, ".format(str(rdelta.days), "s" if rdelta.days != 1 else "") if rdelta.hours != 0: relstr += "{0:s} hour{1:s}, ".format(str(rdelta.hours), "s" if rdelta.hours != 1 else "") if rdelta.minutes != 0: relstr += "{0:s} minute{1:s}, ".format(str(rdelta.minutes), "s" if rdelta.minutes != 1 else "") if rdelta.seconds != 0: relstr += "{0:s} second{1:s}, ".format(str(rdelta.seconds), "s" if rdelta.seconds != 1 else "") # remove trailing comma from output return self.irc.reply(event, relstr[0:-2]) except mdb.Error: pass def _add_countdown_item_to_database(self, name, source, time): """Add the given countdown item from the given source to the database. Args: name the name of the item to add source the source (nick or channel) this item is from time the datetime of the item to add """ db = self.get_db() try: cur = db.cursor(mdb.cursors.DictCursor) statement = ''' INSERT INTO countdown_item (name, source, time) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE time=%s ''' cur.execute(statement, (name, source, time, time)) db.commit() return cur.lastrowid except mdb.Error as e: db.rollback() self.log.error("database error during adding countdown item") self.log.exception(e) raise finally: cur.close() def _remove_countdown_item_from_database(self, name, source): """Add the given countdown item from the given source to the database. Args: name the name of the item to remove source the source (nick or channel) this item is from """ db = self.get_db() try: cur = db.cursor(mdb.cursors.DictCursor) statement = ''' DELETE FROM countdown_item WHERE name = %s AND source = %s ''' cur.execute(statement, (name, source)) db.commit() return except mdb.Error as e: db.rollback() self.log.error("database error during removing countdown item") self.log.exception(e) raise finally: cur.close() def _list_countdown_items(self, source): """Retrieve the names of all countdown items in the database. Args: source the source (nick or channel) to retrieve items for Returns: The names of the items, as a list of strings. """ items = [] db = self.get_db() try: cur = db.cursor(mdb.cursors.DictCursor) query = ''' SELECT name FROM countdown_item WHERE source = %s ''' cur.execute(query, (source,)) results = cur.fetchall() for result in results: items.append(result['name']) return items except mdb.Error as e: self.log.error("database error while getting item time") self.log.exception(e) raise finally: cur.close() def _get_countdown_item_time(self, name, source): """Add the given countdown item from the given source to the database. Args: name the name of the item to add source the source (nick or channel) this item is from Returns: The datetime for the item in local time. """ db = self.get_db() try: cur = db.cursor(mdb.cursors.DictCursor) query = ''' SELECT time FROM countdown_item WHERE name = %s AND source = %s ''' cur.execute(query, (name, source)) result = cur.fetchone() if result: return result['time'].replace(tzinfo=tzutc()) except mdb.Error as e: self.log.error("database error while getting item time") self.log.exception(e) raise finally: cur.close() # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on;