diff --git a/ircbot/modules/Achievements.py b/ircbot/modules/Achievements.py deleted file mode 100644 index 25f50a4..0000000 --- a/ircbot/modules/Achievements.py +++ /dev/null @@ -1,470 +0,0 @@ -""" -Achievements - gamifying IRC -Copyright (C) 2011 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 . -""" - -import re -import thread -import time - -import MySQLdb as mdb - -from Module import Module - -from extlib import irclib - -__author__ = "Brian S. Stephan" -__copyright__ = "Copyright 2011, Brian S. Stephan" -__credits__ = ["Brian S. Stephan", "#lh"] -__license__ = "GPL" -__version__ = "0.1" -__maintainer__ = "Brian S. Stephan" -__email__ = "bss@incorporeal.org" -__status__ = "Development" - -class Achievements(Module): - - """Give out achievements for doing stuff on IRC, because why not.""" - - class AchievementsSettings(): - - """Track system settings.""" - - pass - - def __init__(self, irc, config): - """Set up trigger regexes.""" - - # TODO - joinpattern = '^!achievements\s+join$' - leavepattern = '^!achievements\s+leave$' - infopattern = '^!achievements\s+info\s+(.*)$' - rankpattern = '^!achievements\s+rank\s+(.*)$' - - self.joinre = re.compile(joinpattern) - self.leavere = re.compile(leavepattern) - self.infore = re.compile(infopattern) - self.rankre = re.compile(rankpattern) - - Module.__init__(self, irc, config) - - self.next_achievements_scan = 0 - thread.start_new_thread(self.thread_do, ()) - - 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): - # have to create the database tables - db = self.get_db() - try: - version = 1 - cur = db.cursor(mdb.cursors.DictCursor) - cur.execute(''' - CREATE TABLE achievements_player ( - id SERIAL, - nick VARCHAR(64) NOT NULL UNIQUE, - userhost VARCHAR(256) NOT NULL DEFAULT '', - is_playing INTEGER NOT NULL DEFAULT 0, - last_seen_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - cur.execute(''' - CREATE TABLE achievements_event ( - id SERIAL, - player_id BIGINT(20) UNSIGNED NOT NULL, - event VARCHAR(64) NOT NULL, - target VARCHAR(64), - msg_len INTEGER, - event_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(player_id) REFERENCES achievements_player(id) - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - cur.execute(''' - CREATE TABLE achievements_achievement ( - id SERIAL, - name VARCHAR(256) NOT NULL, - description VARCHAR(256) NOT NULL, - query VARCHAR(1024) NOT NULL - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - cur.execute(''' - CREATE TABLE achievements_log ( - id SERIAL, - player_id BIGINT(20) UNSIGNED NOT NULL, - achievement_id BIGINT(20) UNSIGNED NOT NULL, - event_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(player_id) REFERENCES achievements_player(id), - FOREIGN KEY(achievement_id) REFERENCES achievements_achievement(id) - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - cur.execute(''' - CREATE TABLE achievements_config ( - channel TEXT NOT NULL - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - cur.execute(''' - CREATE TABLE achievements_filter ( - id SERIAL, - filter VARCHAR(256) NOT NULL - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - cur.execute(''' - CREATE TABLE achievements_filter_log ( - id SERIAL, - filter_id BIGINT(20) UNSIGNED NOT NULL, - event_id BIGINT(20) UNSIGNED NOT NULL, - FOREIGN KEY(filter_id) REFERENCES achievements_filter(id), - FOREIGN KEY(event_id) REFERENCES achievements_event(id) - ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin - ''') - 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): - """Handle all sorts of things to track.""" - - self.irc.server.add_global_handler('pubmsg', self.track_irc_event) - self.irc.server.add_global_handler('pubnotice', self.track_irc_event) - self.irc.server.add_global_handler('privmsg', self.track_irc_event) - self.irc.server.add_global_handler('privnotice', self.track_irc_event) - self.irc.server.add_global_handler('join', self.track_irc_event) - self.irc.server.add_global_handler('kick', self.track_irc_event) - self.irc.server.add_global_handler('mode', self.track_irc_event) - self.irc.server.add_global_handler('part', self.track_irc_event) - self.irc.server.add_global_handler('quit', self.track_irc_event) - self.irc.server.add_global_handler('invite', self.track_irc_event) - self.irc.server.add_global_handler('action', self.track_irc_event) - self.irc.server.add_global_handler('topic', self.track_irc_event) - - self.irc.server.add_global_handler('pubmsg', self.on_pub_or_privmsg) - self.irc.server.add_global_handler('privmsg', self.on_pub_or_privmsg) - - def unregister_handlers(self): - self.irc.server.remove_global_handler('pubmsg', self.track_irc_event) - self.irc.server.remove_global_handler('pubnotice', self.track_irc_event) - self.irc.server.remove_global_handler('privmsg', self.track_irc_event) - self.irc.server.remove_global_handler('privnotice', self.track_irc_event) - self.irc.server.remove_global_handler('join', self.track_irc_event) - self.irc.server.remove_global_handler('kick', self.track_irc_event) - self.irc.server.remove_global_handler('mode', self.track_irc_event) - self.irc.server.remove_global_handler('part', self.track_irc_event) - self.irc.server.remove_global_handler('quit', self.track_irc_event) - self.irc.server.remove_global_handler('invite', self.track_irc_event) - self.irc.server.remove_global_handler('action', self.track_irc_event) - self.irc.server.remove_global_handler('topic', self.track_irc_event) - - self.irc.server.remove_global_handler('pubmsg', self.on_pub_or_privmsg) - self.irc.server.remove_global_handler('privmsg', self.on_pub_or_privmsg) - - def track_irc_event(self, connection, event): - """Put events in the database.""" - - if event.source(): - if event.source().find('!') >= 0: - nick = irclib.nm_to_n(event.source()) - userhost = irclib.nm_to_uh(event.source()) - self.log.debug('good: ' + nick + ' ' + userhost + ' ' + event.eventtype() + ' ' + str(event.target())) - if event.arguments(): - msg = event.arguments()[0] - msg_len = len(event.arguments()[0]) - else: - msg = '' - msg_len = 0 - - player_id = self._get_or_add_player(nick, userhost) - self._add_event(player_id, event.eventtype(), event.target(), msg, msg_len) - else: - self.log.debug('bad: ' + event.source() + ' ' + event.eventtype()) - else: - self.log.debug('really bad: ' + event.eventtype()) - - def do(self, connection, event, nick, userhost, what, admin_unlocked): - """Do stuff when commanded.""" - - if self.joinre.search(what): - return self.irc.reply(event, self._join_system(nick)) - elif self.leavere.search(what): - return self.irc.reply(event, self._leave_system(nick)) - elif self.infore.search(what): - match = self.infore.search(what) - achievement = match.group(1) - desc = self._get_achievement_info(achievement) - if desc: - return self.irc.reply(event, achievement + ': ' + desc) - elif self.rankre.search(what): - match = self.rankre.search(what) - player = match.group(1) - achievements = self._get_player_achievements(player) - if len(achievements): - return self.irc.reply(event, player + ' has obtained ' + ', '.join(achievements)) - - def thread_do(self): - """Do the scan for achievements and other miscellaneous tasks.""" - - while not self.is_shutdown: - self._do_achievement_scan() - time.sleep(1) - - def _get_or_add_player(self, nick, userhost): - """Add a player to the database, or update the existing one, and return the id.""" - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - statement = ''' - INSERT IGNORE INTO achievements_player (nick) VALUES (%s)''' - cur.execute(statement, (nick,)) - statement = ''' - UPDATE achievements_player SET userhost = %s, last_seen_time = CURRENT_TIMESTAMP - WHERE nick = %s''' - cur.execute(statement, (userhost, nick)) - db.commit() - statement = '''SELECT id FROM achievements_player WHERE nick = %s''' - cur.execute(statement, (nick,)) - result = cur.fetchone() - return result['id'] - except mdb.Error as e: - self.log.error("database error getting or adding player") - self.log.exception(e) - raise - finally: cur.close() - - def _add_event(self, player_id, event, target, msg, msg_len): - """Add an event to the log.""" - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - statement = ''' - INSERT INTO achievements_event ( - player_id, event, target, msg_len - ) VALUES (%s, %s, %s, %s) - ''' - cur.execute(statement, (player_id, event, target, msg_len)) - db.commit() - event_id = cur.lastrowid - - # now see if the event matched any filters - query = ''' - SELECT id FROM achievements_filter WHERE %s REGEXP filter - ''' - cur.execute(query, (msg,)) - results = cur.fetchall() - for result in results: - cur = db.cursor(mdb.cursors.DictCursor) - statement = ''' - INSERT INTO achievements_filter_log (filter_id, event_id) - VALUES (%s, %s) - ''' - cur.execute(statement, (result['id'], event_id)) - db.commit() - - return event_id - except mdb.Error as e: - self.log.error("database error adding event") - self.log.exception(e) - raise - finally: cur.close() - - def _get_achievements_settings(self): - """Get the report settings.""" - - db = self.get_db() - try: - # get the settings - cur = db.cursor(mdb.cursors.DictCursor) - query = 'SELECT channel FROM achievements_config' - cur.execute(query) - result = cur.fetchone() - if result: - settings = self.AchievementsSettings() - settings.channel = result['channel'] - - return settings - else: - return None - except mdb.Error as e: - self.log.error("database error getting settings") - self.log.exception(e) - raise - except AttributeError as e: - self.log.error("could not get channel settings, probably unset") - self.log.exception(e) - return None - finally: cur.close() - - def _join_system(self, nick): - """Add the appropriate nick to the system.""" - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - statement = 'UPDATE achievements_player SET is_playing = 1 WHERE nick = %s' - cur.execute(statement, (nick,)) - db.commit() - return nick + ' joined.' - except mdb.Error as e: - self.log.error("database error joining the system") - self.log.exception(e) - raise - finally: cur.close() - - def _leave_system(self, nick): - """Remove the appropriate nick from the system.""" - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - statement = 'UPDATE achievements_player SET is_playing = 0 WHERE nick = %s' - cur.execute(statement, (nick,)) - db.commit() - return nick + ' left.' - except mdb.Error as e: - self.log.error("database error leaving the system") - self.log.exception(e) - raise - finally: cur.close() - - def _add_player_to_achievement_log(self, player_id, achievement_id): - """Log the success of a player.""" - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - statement = 'INSERT INTO achievements_log (player_id, achievement_id) VALUES (%s, %s)' - cur.execute(statement, (player_id, achievement_id)) - db.commit() - return - except mdb.Error as e: - self.log.error("database error adding player to achievement log") - self.log.exception(e) - raise - finally: cur.close() - - def _get_achievement_info(self, achievement): - """Return the description of a given achievement.""" - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - query = 'SELECT description FROM achievements_achievement WHERE name = %s' - cur.execute(query, (achievement,)) - result = cur.fetchone() - if result: - return result['description'] - except mdb.Error as e: - self.log.error("database error getting achievement info") - self.log.exception(e) - raise - finally: cur.close() - - def _get_player_achievements(self, nick): - """Return the achievements the nick has.""" - - achievements = [] - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - query = ''' - SELECT a.name FROM achievements_achievement a - INNER JOIN achievements_log l ON l.achievement_id = a.id - INNER JOIN achievements_player p ON p.id = l.player_id - WHERE p.nick = %s - ''' - cur.execute(query, (nick,)) - results = cur.fetchall() - for result in results: - achievements.append(result['name']) - - return achievements - except mdb.Error as e: - self.log.error("database error getting player achievements") - self.log.exception(e) - raise - finally: cur.close() - - def _do_achievement_scan(self): - """Run the queries in the database, seeing if anyone new has an achievement.""" - - # don't do anything the first time - if self.next_achievements_scan == 0: - self.next_achievements_scan = time.time() + 300 - - if self.next_achievements_scan < time.time(): - self.next_achievements_scan = time.time() + 300 - - self.log.debug('in achievement scan') - settings = self._get_achievements_settings() - if settings is not None: - channel = settings.channel - achievers = self._query_for_new_achievers() - for achiever in achievers: - self.sendmsg(channel, achiever[0] + ' achieved ' + achiever[1] + '!') - - def _query_for_new_achievers(self): - """Get new achievement earners for each achievement.""" - - achievers = [] - self.log.debug('checking achievements') - - db = self.get_db() - try: - cur = db.cursor(mdb.cursors.DictCursor) - query = 'SELECT id, name, query FROM achievements_achievement' - cur.execute(query) - achievements = cur.fetchall() - - for achievement in achievements: - self.log.debug('checking achievement:[' + achievement['name'] + ']') - query = ''' - SELECT p.id, p.nick FROM achievements_player p WHERE - p.nick IN ( - ''' - query = query + achievement['query'] - query = query + ''' - ) AND p.is_playing = 1 - AND p.id NOT IN ( - SELECT player_id FROM achievements_log l - INNER JOIN achievements_achievement a - ON a.id = l.achievement_id - WHERE a.name = %s - ) - ''' - cur.execute(query, (achievement['name'],)) - ach_achievers = cur.fetchall() - - for ach_achiever in ach_achievers: - self.log.debug('name:[' + ach_achiever['nick'] + '] achievement:[' + achievement['name'] + ']') - self._add_player_to_achievement_log(ach_achiever['id'], achievement['id']) - achievers.append((ach_achiever['nick'], achievement['name'])) - - return achievers - except mdb.Error as e: - self.log.error("database error scanning new achievers") - self.log.exception(e) - raise - finally: cur.close() - -# vi:tabstop=4:expandtab:autoindent