329 lines
9.8 KiB
Python
329 lines
9.8 KiB
Python
"""
|
|
Module - dr.botzo modular functionality base class
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
|
|
from ConfigParser import NoSectionError, NoOptionError
|
|
|
|
import logging
|
|
|
|
import MySQLdb as mdb
|
|
|
|
from extlib import irclib
|
|
|
|
class Module(object):
|
|
|
|
"""Provide module base class with convenience functionality/bootstrap."""
|
|
|
|
def priority(self):
|
|
return 50
|
|
|
|
def __init__(self, irc, config):
|
|
"""Construct a feature module.
|
|
|
|
Inheritors can do special things here, but should be sure to call
|
|
Module.__init__.
|
|
|
|
Args:
|
|
irc DrBotIRC object for the running bot
|
|
config ConfigParser object, the entire config file
|
|
|
|
"""
|
|
|
|
self.irc = irc
|
|
self.config = config
|
|
|
|
# reload logging config every time
|
|
logging.config.fileConfig('logging.cfg')
|
|
self.log = logging.getLogger('drbotzo.'+self.__class__.__name__.lower())
|
|
|
|
self.is_shutdown = False
|
|
|
|
# set up database for this module
|
|
self.db_init()
|
|
|
|
# set up XML-RPC for this module
|
|
self.xmlrpc_init()
|
|
|
|
self.log.info("Loaded " + self.__class__.__name__)
|
|
|
|
def register_handlers(self):
|
|
"""Hook handler functions into the IRC library.
|
|
|
|
This is called when the module is loaded. Classes with special stuff
|
|
to do could implement this and set up the appropriate handlers, e.g.:
|
|
|
|
self.irc.server.add_global_handler('welcome', self.on_connect)
|
|
|
|
By default, a module attaches to pubmsg/privmsg, which sets up some
|
|
common variables and then calls do(). You are free to implement do()
|
|
(see below), or override this and do whatever you want.
|
|
|
|
Modules interested in doing so might also register XML-RPC stuff here.
|
|
|
|
"""
|
|
|
|
self.irc.server.add_global_handler('pubmsg', self.on_pub_or_privmsg,
|
|
self.priority())
|
|
self.irc.server.add_global_handler('privmsg', self.on_pub_or_privmsg,
|
|
self.priority())
|
|
|
|
def unregister_handlers(self):
|
|
"""Unhook handler functions from the IRC library.
|
|
|
|
Inverse of the above. This is called by unload, to remove the
|
|
soon-to-be old object from the server global handlers (or whatever has
|
|
been added via register_handlers). Classes inheriting from Module
|
|
could reimplement this, e.g.:
|
|
|
|
self.irc.server.remove_global_handler('welcome', self.on_connect)
|
|
|
|
"""
|
|
|
|
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 on_pub_or_privmsg(self, connection, event):
|
|
"""Do a default thing on a pubmsg or privmsg.
|
|
|
|
Sets up a couple variables and then calls do(), which by default we
|
|
expect implementers to implement.
|
|
|
|
Args:
|
|
connection the source connection for this event
|
|
event the event to handle
|
|
|
|
Returns:
|
|
The results of handling the event.
|
|
|
|
"""
|
|
|
|
nick = irclib.nm_to_n(event.source())
|
|
userhost = irclib.nm_to_uh(event.source())
|
|
what = event.arguments()[0]
|
|
admin_unlocked = False
|
|
|
|
try:
|
|
if userhost == self.config.get('dr.botzo', 'admin_userhost'):
|
|
admin_unlocked = True
|
|
except NoOptionError: pass
|
|
|
|
return self.do(connection, event, nick, userhost, what, admin_unlocked)
|
|
|
|
def sendmsg(self, target, msg):
|
|
"""Send a privmsg over IRC to target.
|
|
|
|
Args:
|
|
target destination on the network
|
|
msg the message to send
|
|
|
|
"""
|
|
|
|
if msg is not None:
|
|
if target is not None:
|
|
msgs = msg.split('\n')
|
|
for line in msgs:
|
|
self.irc.server.privmsg(target, line)
|
|
|
|
def save(self):
|
|
"""Save whatever the module may need to save. Sync files, etc.
|
|
|
|
Implement this if you need it.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
def shutdown(self):
|
|
"""Do pre-deletion type cleanup.
|
|
|
|
Implement this to close databases, write to disk, etc. Note that
|
|
DrBotIRC will call save() before this, so implement appropriately.
|
|
|
|
"""
|
|
|
|
self.is_shutdown = True
|
|
self.log.info("Unloading " + self.__class__.__name__)
|
|
|
|
def remove_metaoptions(self, list_):
|
|
"""Remove metaoptions from provided list, which was probably from a
|
|
config file.
|
|
|
|
DEPRECATED
|
|
|
|
This is a convenience method that can go away once we get module stuff
|
|
out of the config file and into the database.
|
|
|
|
Args:
|
|
list_ the list to prune known keywords from
|
|
|
|
"""
|
|
|
|
list_.remove('debug')
|
|
|
|
def retransmit_event(self, event):
|
|
"""Pretend that some event the bot has generated is rather an incoming
|
|
IRC event.
|
|
|
|
DEPRECATED
|
|
|
|
Why one would do this is unclear, but I wrote it and then realized
|
|
I didn't need it.
|
|
|
|
Args:
|
|
event the event to replay
|
|
|
|
"""
|
|
|
|
self.irc.server._handle_event(event)
|
|
|
|
def get_db(self):
|
|
"""Get a database connection to mdb.
|
|
|
|
Once grabbed, it should be closed when work is done. Modules that
|
|
need a database connection should test for and create (or, eventually,
|
|
alter) required table structure in their __init__ IF that structure
|
|
does not already exist. Well-behaved modules should use a prefix in
|
|
their table names (eg "karma_log" rather than "log")
|
|
|
|
See also db_module_registered, below.
|
|
|
|
"""
|
|
|
|
dbhost = self.config.get('dr.botzo', 'dbhost')
|
|
dbuser = self.config.get('dr.botzo', 'dbuser')
|
|
dbpass = self.config.get('dr.botzo', 'dbpass')
|
|
dbname = self.config.get('dr.botzo', 'dbname')
|
|
db = mdb.connect(dbhost, dbuser, dbpass, dbname, charset='utf8',
|
|
use_unicode=True)
|
|
|
|
return db
|
|
|
|
def db_module_registered(self, modulename):
|
|
"""Ask the database for a version number for a module.
|
|
|
|
Return that version number if the module has registered, or None if
|
|
not.
|
|
|
|
Args:
|
|
modulename the name of the module to check
|
|
|
|
Returns:
|
|
The version number stored for the module in the database, as a int
|
|
|
|
"""
|
|
|
|
db = self.get_db()
|
|
version = None
|
|
try:
|
|
cur = db.cursor()
|
|
cur.execute("SELECT version FROM drbotzo_modules WHERE module = %s",
|
|
(modulename,))
|
|
version = cur.fetchone()
|
|
if (version != None):
|
|
version = version[0]
|
|
except mdb.Error as e:
|
|
self.log.error("database error during module registered check")
|
|
self.log.exception(e)
|
|
raise
|
|
finally: cur.close()
|
|
|
|
return version
|
|
|
|
def db_register_module_version(self, modulename, version):
|
|
"""Enter the given module name and version number into the database.
|
|
|
|
The primary want for this is to record what the current revision the
|
|
tables are, to do upgrades.
|
|
|
|
Args:
|
|
modulename the name of the module to update
|
|
version the version number to set as a int
|
|
|
|
"""
|
|
|
|
db = self.get_db()
|
|
try:
|
|
cur = db.cursor(mdb.cursors.DictCursor)
|
|
cur.execute('INSERT IGNORE INTO drbotzo_modules (version, module) VALUES (%s, %s)',
|
|
(version, modulename))
|
|
db.commit()
|
|
except mdb.Error as e:
|
|
db.rollback()
|
|
self.log.error("database error during register module version")
|
|
self.log.exception(e)
|
|
raise
|
|
finally: cur.close()
|
|
|
|
def db_init(self):
|
|
"""Set up the database tables and so on.
|
|
|
|
Modules interested in this should implement table setup here.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
def xmlrpc_init(self):
|
|
"""Set up XML-RPC handlers.
|
|
|
|
Modules interested in this should implement registration here.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
def do(self, connection, event, nick, userhost, what, admin):
|
|
"""Do the primary thing this module was intended to do, in most cases.
|
|
|
|
Implement this method in your subclass to have a fairly-automatic hook
|
|
into IRC functionality. This is called by DrBotIRC during pubmsg and
|
|
privmsg events.
|
|
|
|
Args:
|
|
connection the connection instance to handle
|
|
event the event to handle
|
|
nick the nick of the originator of the event
|
|
userhost the userhost of the originator of the event
|
|
what the message body of the event
|
|
admin if the event was created by an admin
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
def _unencode_xml(self, text):
|
|
"""Convert <, >, & to their real entities.
|
|
|
|
Convenience method for modules, I've been thinking about moving this.
|
|
|
|
Args:
|
|
text the text to clean up
|
|
|
|
Returns:
|
|
The provided text string, with the known entities replaced.
|
|
|
|
"""
|
|
|
|
text = text.replace('<', '<')
|
|
text = text.replace('>', '>')
|
|
text = text.replace('&', '&')
|
|
return text
|
|
|
|
# vi:tabstop=4:expandtab:autoindent
|
|
# kate: indent-mode python;indent-width 4;replace-tabs on;
|