Mercurial > public > sg101
view tools/sg101Bot.py @ 505:a5d11471d031
Refactor the logic in the rate limiter decorator. Check to see if the request was ajax, as the ajax view always returns 200. Have to decode the JSON response to see if an error occurred or not.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sat, 03 Dec 2011 19:13:38 +0000 |
parents | b871892264f2 |
children |
line wrap: on
line source
#! /usr/bin/env python """sg101Bot.py IRC Bot for SurfGuitar101.com's IRC server. This bot watches who is in the given channel and updates a MySQL database accordingly. The database is read by the website to display who is in the channel. Brian Neal <brian@surfguitar101.com> """ import re import sys import random import logging from optparse import OptionParser import ConfigParser import MySQLdb import irclib from daemon import Daemon #irclib.DEBUG = True class QuoteDb(object): """QuoteDb is a class that reads a quote file and provdes a random quote when asked""" def __init__(self, filename): random.seed() f = open(filename, 'r') self.quotes = [] for line in f: self.quotes.append(line.strip()) f.close() def get(self): return random.choice(self.quotes) class Sg101Table(object): """Sg101Table class for abstracting the database table that is used to store who is in the channel""" def __init__(self, dbHost, dbName, dbUser, dbPassword, tableName): self.dbArgs = {'host' : dbHost, 'user' : dbUser, 'passwd' : dbPassword, 'db' : dbName} self.table = tableName self.dbRefCnt = 0 def empty(self): self.__dbOpen() """empties the table of channel users""" logging.debug('emptying the table') self.cursor.execute('TRUNCATE TABLE ' + self.table) self.__dbClose() def add(self, nick): self.__dbOpen() """adds the nickname to the table""" logging.debug('adding to the table: ' + nick) self.cursor.execute("INSERT INTO " + self.table + " (name, last_update) VALUES (%s, NOW())", nick) self.__dbClose() def remove(self, nick): self.__dbOpen() """removes the nickname from the table""" logging.debug('removing from the table: ' + nick) self.cursor.execute("DELETE FROM " + self.table + " WHERE `name` = %s", nick) self.__dbClose() def set(self, nicks): self.__dbOpen() """empties and then adds all the nicknames in the nicks list to the table""" self.empty() logging.debug('setting the table: ' + ', '.join(nicks)) for nick in nicks: self.add(nick) self.__dbClose() def __dbOpen(self): if self.dbRefCnt <= 0: self.connection = MySQLdb.connect( host = self.dbArgs['host'], user = self.dbArgs['user'], passwd = self.dbArgs['passwd'], db = self.dbArgs['db']) self.cursor = self.connection.cursor() self.dbRefCnt = 1 else: self.dbRefCnt = self.dbRefCnt + 1 def __dbClose(self): self.dbRefCnt = self.dbRefCnt - 1 if self.dbRefCnt <= 0: self.cursor.close() self.connection.commit() self.connection.close() self.dbRefCnt = 0 class Sg101Bot(irclib.SimpleIRCClient): """Sg101Bot class for monitoring a given channel and updating a database table accordingly""" def __init__(self, channel, nickname, server, port, password, db, quotes, quoteMod = 5): """__init__ function for Sg101Bot""" irclib.SimpleIRCClient.__init__(self) self.channel = channel self.nickname = nickname self.server = server self.port = port self.password = password self.users = [] self.db = db self.quotes = quotes self.quoteMod = quoteMod self.pingCount = 0; def start(self): """call this function to start the bot""" self.db.empty() self.__connect() irclib.SimpleIRCClient.start(self) def on_nicknameinuse(self, c, e): """called if nick is in use; attempts to change nick""" c.nick(c.get_nickname() + "_") def on_welcome(self, c, e): """called when welcomed to the IRC server; attempts to join channel""" c.join(self.channel) def on_namreply(self, c, e): """called when we hear a namreply; we scan the names and if different than our info of who is in the channel we update the database""" me = c.get_nickname() users = [re.sub('^[@\+]', '', u) for u in e.arguments()[2].split(' ') if u != me] users.sort() if (self.users != users): logging.debug('on_namreply: detecting user list difference') self.users = users self.db.set(users) self.__printUsers() def on_join(self, c, e): """called after we or someone else joins the channel; update our list of users and db""" nick = irclib.nm_to_n(e.source()) if nick != c.get_nickname() and nick not in self.users: self.__addUser(nick) self.__printUsers() def on_part(self, c, e): """called after we or someone else parts the channel; update our list of users and db""" nick = irclib.nm_to_n(e.source()) if nick != c.get_nickname(): self.__delUser(nick) self.__printUsers() def on_kick(self, c, e): """called after someone is kicked; update our list of users and db""" nick = e.arguments()[0] if nick != c.get_nickname(): self.__delUser(nick) self.__printUsers() def on_quit(self, c, e): """called after someone quits; update our list of users and db""" nick = irclib.nm_to_n(e.source()) if nick != c.get_nickname(): self.__delUser(nick) self.__printUsers() def on_ping(self, c, e): """called when we are pinged; we use this opportunity to ask who is in the channel via NAMES""" c.names() self.pingCount = self.pingCount + 1 if (self.quoteMod > 0) and len(self.users) > 0 and (self.pingCount % self.quoteMod) == 0: c.privmsg(self.channel, self.quotes.get()) def on_privmsg(self, c, e): self.doCommand(e, e.arguments()[0], e.source()) def on_pubmsg(self, c, e): a = e.arguments()[0].split(':', 1) if len(a) > 1 and irclib.irc_lower(a[0]) == irclib.irc_lower(c.get_nickname()): self.doCommand(e, a[1].strip(), e.target()) def on_disconnect(self, c, e): logging.critical('received on_disconnect()') logging.debug('scheduling a routine in 30 secs') self.users = [] self.db.empty() self.connection.execute_delayed(30, self.__connectedChecker) def __connectedChecker(): logging.debug('__connectedChecker()') if not self.connection.is_connected(): logging.debug('rescheduling __connectedChecker in 30 secs') self.connection.execute_delayed(30, self._connected_checker) self.__connect() def doCommand(self, e, cmd, reply): if cmd == 'yow': self.connection.privmsg(reply, self.quotes.get()) def __connect(self): """our internal connect function""" self.connect(self.server, self.port, self.nickname, self.password) def __printUsers(self): """internal print users command""" msg = 'My users are: (' + ', '.join(self.users) + ')' logging.debug(msg) def __addUser(self, nick): """adds a user to our list and db""" self.users.append(nick) self.users.sort() self.db.add(nick) def __delUser(self, nick): """removes a user from our list and db""" if nick in self.users: self.users.remove(nick) self.db.remove(nick) class BotDaemon(Daemon): def __init__(self, filename, options, db_config): Daemon.__init__(self, filename) self.options = options self.db_config = db_config def run(self): logging.basicConfig(level = logging.DEBUG, format = '%(asctime)s %(levelname)s %(message)s', filename = '/home/brian/irc/sg101Bot.log', filemode = 'w') logging.info('sg101Bot starting') server = self.options.server port = self.options.port channel = self.options.channel nickname = self.options.nick password = self.options.password try: quotes = QuoteDb('/home/brian/irc/zippy.txt') db = Sg101Table(self.db_config['host'], self.db_config['database'], self.db_config['user'], self.db_config['password'], self.db_config['table']) bot = Sg101Bot(channel, nickname, server, port, password, db, quotes, 0) bot.start() except MySQLdb.OperationalError, message: logging.exception('MySQLdb.OperationalError: %d %s' % (message[0], message[1])) except MySQLdb.ProgrammingError, message: logging.exception('MySQLdb.ProgrammingError: %d %s' % (message[0], message[1])) except: print "Got an exception:", sys.exc_info()[0] logging.exception('Exception received ' + str(sys.exc_info())) logging.info('sg101Bot ending') if __name__ == "__main__": parser = OptionParser(usage='usage: %prog [options] start|stop|restart', description="""\ SG101 IRC Bot. Monitors who is in the specified channel and updates a database table accordingly for display by the website. """) parser.add_option("-s", "--server", default="localhost", help="server address") parser.add_option("-p", "--port", type="int", default=6667, help="port number") parser.add_option("-c", "--channel", default="#ShallowEnd", help="channel name") parser.add_option("-n", "--nick", default="bot", help="bot nickname") parser.add_option("-w", "--password", default="morereverb", help="channel password") parser.add_option("-q", "--quote", default="/home/brian/irc/zippy.txt", help="Quote file") (options, args) = parser.parse_args() commands = ('start', 'stop', 'restart') if len(args) != 1 and args[0] not in commands: parser.print_help() sys.exit("Please provide a single command: start, stop, or restart.") config = ConfigParser.ConfigParser() config.read('/home/brian/irc/sg101Bot.ini') db_config = dict(config.items('Database')) daemon = BotDaemon('/tmp/sg101Bot.pid', options, db_config) cmd = args[0] if cmd == 'start': daemon.start() elif cmd == 'stop': daemon.stop() elif cmd == 'restart': daemon.restart()