remove old IRC bot entirely

the porting is complete, everything i care about has been moved to the
django-based codebase, and this old junk can finally go

IT IS A NEW ERA, one of maintainability and flexible changes. after
years of procrastinating, i have finally done this. the future is now
This commit is contained in:
Brian S. Stephan 2015-06-19 21:50:35 -05:00
parent 8c77780923
commit 2dc2b6a8a2
11 changed files with 1 additions and 3194 deletions

View File

@ -1,4 +0,0 @@
Python IRC library (python-irclib)
* http://python-irclib.sourceforge.net/
* 0.6.4
* LGPLv2

2
README
View File

@ -17,4 +17,4 @@ DEVELOPMENT
The bot is currently written to be fairly modular. No real documentation on
this yet, but those interested in developing more features should take a
look at the classes in dr.botzo.py.
look at any of the ircplugin.py files.

View File

@ -1,821 +0,0 @@
"""
DrBotIRC - customizations of irclib, for dr.botzo
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 <http://www.gnu.org/licenses/>.
"""
import bisect
import copy
from ConfigParser import NoOptionError, NoSectionError
import logging
import re
import signal
from SimpleXMLRPCServer import SimpleXMLRPCServer
import socket
import sys
import thread
from extlib import irclib
log = logging.getLogger('drbotzo')
class DrBotzoMethods:
"""Methods to expose to the XML-RPC server."""
def __init__(self, irc):
"""Store the same stuff the core module would, since we'll probably
need it.
Args:
irc the irc instance to save a reference to
"""
self.irc = irc
def say(self, target, message):
"""Say a message in a channel/to a nick.
Args:
target the nick/channel to privmsg
message the message to send
Returns:
"OK", since there's no other feedback to give.
"""
self.irc.server.privmsg(target, message)
return "OK"
def execute_module_method(self, modname, method, args):
"""Execute the method (with arguments) of the specified module.
Args:
modname the loaded module to retrieve
method the method to call from that module
args the arguments to provide to the method as a tuple
Returns:
An error string, or the outcome of the method call.
"""
for module in self.irc.modlist:
if modname == module.__class__.__name__:
try:
func = getattr(module, method)
except AttributeError:
return ("couldn't find '{0:s}' in found module "
"'{1:s}'".format(method, modname))
if hasattr(func, '__call__'):
return func(*args)
else:
return ("'{0:s}' in found module '{1:s}' is not "
"callable".format(method, modname))
return "couldn't find module '{0:s}'".format(modname)
class DrBotServerConnection(irclib.ServerConnection):
"""Subclass irclib's ServerConnection, in order to expand privmsg."""
nickmask = None
def __init__(self, irclibobj, nickname=None, username=None):
"""Instantiate the server connection.
Also start guessing at the nickmask and get ready to do on_welcome
stuff.
Args:
irclibobj the irclib instance to connect with
nickname the nickname to use in nickmask guess
username the username to use in nickmask guess
"""
irclib.ServerConnection.__init__(self, irclibobj)
# temporary. hopefully on_welcome() will set this, but this should be
# a pretty good guess if not
nick = nickname
user = username if username is not None else nick
host = socket.getfqdn()
self.nickmask = "{0:s}!~{1:s}@{2:s}".format(nick, user, host)
log.debug("guessing at nickmask '{0:s}'".format(self.nickmask))
self.add_global_handler('welcome', self.on_welcome, 1)
def on_welcome(self, connection, event):
"""Set the nickmask that the ircd tells us is us.
Args:
connection source connection
event incoming event
"""
what = event.arguments()[0]
log.debug("welcome: {0:s}".format(what))
match = re.search('(\S+!\S+@\S+)', what)
if match:
self.nickmask = match.group(1)
log.debug("setting nickmask: {0:s}".format(self.nickmask))
def privmsg(self, target, text):
"""Send a PRIVMSG command.
Args:
target the destination nick/channel
text the message to send
"""
log.debug("OUTGOING PRIVMSG: t[{0:s}] m[{1:s}]".format(target, text))
splitter = "..."
# split messages that are too long. Max length is 512.
# TODO: this does not properly handle when the nickmask has been
# masked by the ircd
# is the above still the case?
space = 512 - len('\r\n') - len(' PRIVMSG ') - len(target) - len(' :') - len(self.nickmask) - len(' :')
splitspace = space - (len(splitter) + 1)
if len(text) > space:
times = 1
while len(text) > splitspace:
splitpos = text.rfind(' ', 0, splitspace)
splittext = text[0:splitpos] + ' ' + splitter
text = splitter + ' ' + text[splitpos+1:]
self.send_raw("PRIVMSG {0:s} :{1:s}".format(target, splittext))
times = times + 1
if times >= 4:
# this is stupidly long, abort
return
# done splitting
self.send_raw("PRIVMSG {0:s} :{1:s}".format(target, text))
else:
self.send_raw("PRIVMSG {0:s} :{1:s}".format(target, text))
class DrBotIRC(irclib.IRC):
"""Implement a customized irclib IRC."""
modlist = []
config = None
server = None
def __init__(self, config):
"""Initialize XML-RPC interface and save references.
Args:
config the config structure to load stuff from
"""
irclib.IRC.__init__(self)
self.config = config
self.xmlrpc = None
self.regex_handlers = dict()
# handle SIGINT
signal.signal(signal.SIGINT, self.sigint_handler)
# load XML-RPC server
try:
if self.config.has_section('XMLRPC'):
host = self.config.get('XMLRPC', 'host')
port = self.config.getint('XMLRPC', 'port')
if host and port:
self.funcs = DrBotzoMethods(self)
self.xmlrpc = SimpleXMLRPCServer((host, port), logRequests=False)
self.xmlrpc.register_introspection_functions()
self.xmlrpc.register_instance(self.funcs)
thread.start_new_thread(self._xmlrpc_listen, ())
except NoOptionError: pass
def server(self):
"""Create a DrBotServerConnection.
Returns:
The newly created DrBotServerConnection.
"""
# get the nick and user name so we can provide it to
# DrBotServerConnection as a hint for the nickmask
user = None
nick = None
try:
if self.config.has_section('dr.botzo'):
user = self.config.get('dr.botzo', 'user')
nick = self.config.get('dr.botzo', 'nick')
except NoOptionError: pass
self.server = DrBotServerConnection(self, user, nick)
# though we only handle one connection, the underlying library supports
# multiple. append the new one though we intend no others
self.connections.append(self.server)
return self.server
def add_global_regex_handler(self, event, regex, handler, priority=0):
"""Adds a global handler function for a specific event type and regex.
The handler function is called whenever the specified event is
triggered in any of the connections and the regex matches.
See documentation for the Event class.
The handler functions are called in priority order (lowest
number is highest priority). If a handler function returns
"NO MORE", no more handlers will be called.
This is basically an extension of add_global_handler(), either may
work, though it turns out most modules probably want this one.
The provided method should take as arguments:
* nick the nick creating the event, or None
* userhost the userhost creating the event, or None
* event list of the raw IRC events, which may be None
* from_admin whether or not the event came from an admin
* groups list of match.groups(), from re.search()
Args:
event event type (a string), as in numeric_events
regex regex string to match before doing callback invocation
handler callback function to invoke
priority integer, the lower number, the higher priority
"""
if type(event) != list:
event = [event]
for ev in event:
if not ev in self.regex_handlers:
self.regex_handlers[ev] = []
bisect.insort(self.regex_handlers[ev], ((priority, regex, handler)))
def remove_global_regex_handler(self, event, regex, handler):
"""Removes a global regex handler function.
Args:
event list of event type (a string), as in numeric_events
regex regex string that was used in matching
handler callback function to remove
Returns:
1 on success, otherwise 0.
"""
if type(event) != list:
event = [event]
for ev in event:
if not ev in self.regex_handlers:
continue
for h in self.regex_handlers[ev]:
if regex == h[1] and handler == h[2]:
self.regex_handlers[ev].remove(h)
def _handle_event(self, connection, event):
"""Override event handler to do recursion and regex checking.
Args:
connection source connection
event incoming event
"""
log.debug("EVENT: e[{0:s}] s[{1:s}] t[{2:s}] "
"a[{3:s}]".format(event.eventtype(), event.source(),
event.target(), event.arguments()))
try:
nick = irclib.nm_to_n(event.source())
except (IndexError, AttributeError):
nick = ''
try:
if self.config.has_section('Ignore'):
alias = self.config.get('Ignore', nick.lower())
if alias:
log.debug("ignoring {0:s} as per config file".format(nick))
return
except NoOptionError: pass
self.try_alias_cmds(connection, event)
self.try_recursion(connection, event)
self.try_alias(connection, event)
nick = None
userhost = None
admin = False
if event.source() is not None:
nick = irclib.nm_to_n(event.source())
try:
userhost = irclib.nm_to_uh(event.source())
except IndexError: pass
try:
if userhost == self.config.get('dr.botzo', 'admin_userhost'):
admin = True
except NoOptionError: pass
# try regex handlers first, since they're more specific
rh = self.regex_handlers
trh = sorted(rh.get('all_events', []) + rh.get(event.eventtype(), []))
for handler in trh:
try:
prio, regex, method = handler
for line in event.arguments():
match = re.search(regex, line)
if match:
log.debug("pattern matched, calling "
"{0:s}".format(method))
# pattern matched, call method with pattern groups
ret = method(nick, userhost, event, admin,
match.groups())
if ret == 'NO MORE':
return
except Exception as e:
log.error("exception floated up to DrBotIrc!")
log.exception(e)
h = self.handlers
th = sorted(h.get('all_events', []) + h.get(event.eventtype(), []))
for handler in th:
try:
prio, method = handler
ret = method(connection, event)
if ret == 'NO MORE':
return
except Exception as e:
log.error("exception floated up to DrBotIrc!")
log.exception(e)
def xmlrpc_register_function(self, func, name):
"""Add a method to the XML-RPC interface.
Args:
func the method to register
name the name to expose the method as
"""
if func and self.xmlrpc:
if hasattr(func, '__call__'):
self.xmlrpc.register_function(func, name)
def _xmlrpc_listen(self):
"""Begin listening.
Hopefully this was called in a new thread.
"""
self.xmlrpc.serve_forever()
def try_to_replace_event_text_with_module_text(self, connection, event):
"""Do something very similar to _handle_event, but for recursion.
The intent here is that we replace [text] with whatever a module
provides to us.
Args:
connection source connection
event incoming event
"""
replies = []
nick = None
userhost = None
admin = False
if event.source() is not None:
nick = irclib.nm_to_n(event.source())
try:
userhost = irclib.nm_to_uh(event.source())
except IndexError: pass
try:
if userhost == self.config.get('dr.botzo', 'admin_userhost'):
admin = True
except NoOptionError: pass
# try regex handlers first, since they're more specific
rh = self.regex_handlers
trh = sorted(rh.get('all_events', []) + rh.get(event.eventtype(), []))
for handler in trh:
try:
prio, regex, method = handler
for line in event.arguments():
match = re.search(regex, line)
if match:
log.debug("pattern matched, calling "
"{0:s}".format(method))
# pattern matched, call method with pattern groups
ret = method(nick, userhost, event, admin,
match.groups())
if ret:
replies.append(ret)
except Exception as e:
log.error("exception floated up to DrBotIrc!")
log.exception(e)
h = self.handlers
th = sorted(h.get('all_events', []) + h.get(event.eventtype(), []))
for handler in th:
try:
prio, method = handler
ret = method(connection, event)
if ret:
replies.append(ret)
except Exception as e:
log.error("exception floated up to DrBotIrc!")
log.exception(e)
if len(replies):
event.arguments()[0] = '\n'.join(replies)
def try_alias_cmds(self, connection, event):
"""See if there is an alias ("!command") in the text, and if so
do alias manipulation before any other recursion or aliasing.
Args:
connection source connection
event incoming event
Returns:
The outcome of the alias command, if the text had one.
"""
try:
nick = irclib.nm_to_n(event.source())
except (IndexError, AttributeError):
nick = ''
try:
userhost = irclib.nm_to_uh(event.source())
except (IndexError, AttributeError):
userhost = ''
replypath = event.target()
try:
what = event.arguments()[0]
except (IndexError, AttributeError):
what = ''
admin_unlocked = False
# privmsg
if replypath == connection.get_nickname():
replypath = nick
try:
if userhost == self.config.get('dr.botzo', 'admin_userhost'):
admin_unlocked = True
except NoOptionError: pass
# first see if the aliases are being directly manipulated via add/remove
whats = what.split(' ')
if len(whats) <= 1:
return
try:
if whats[0] == '!alias' and whats[1] == 'add' and len(whats) >= 4:
if not self.config.has_section('Alias'):
self.config.add_section('Alias')
self.config.set('Alias', whats[2], ' '.join(whats[3:]))
replystr = "Added alias '{0:s}'.".format(whats[2])
return self.reply(event, replystr)
if whats[0] == '!alias' and whats[1] == 'remove' and len(whats) >= 3:
if not self.config.has_section('Alias'):
self.config.add_section('Alias')
if self.config.remove_option('Alias', whats[2]):
replystr = "Removed alias '{0:s}'.".format(whats[2])
return self.reply(event, replystr)
elif whats[0] == '!alias' and whats[1] == 'list':
try:
if len(whats) > 2:
alias = self.config.get('Alias', whats[2])
return self.reply(event, alias)
else:
alist = self.config.options('Alias')
alist.remove('debug')
alist.sort()
liststr = ', '.join(alist)
return self.reply(event, liststr)
except NoSectionError: pass
except NoOptionError: pass
except NoSectionError: pass
def try_alias(self, connection, event):
"""Try turning aliases into commands.
Args:
connection source connection
event incoming event
Returns:
The de-aliased event object.
"""
try:
what = event.arguments()[0]
alias_list = self.config.options('Alias')
for alias in alias_list:
alias_re = re.compile(alias, re.IGNORECASE)
if alias_re.search(what) and alias != 'debug':
# we found an alias for our given string, doing a replace
command = re.sub(alias, self.config.get('Alias', alias), what, flags=re.IGNORECASE)
event.arguments()[0] = command
# now we have to check it for recursions again
self.try_recursion(connection, event)
# i guess someone could have an alias of an alias... try again
return self.try_alias(connection, event)
except NoOptionError: pass
except NoSectionError: pass
except IndexError: pass
# if we got here, there are no matching aliases, so return what we got
return event
def try_recursion(self, connection, event):
"""Scan message for subcommands to execute and use as part of this command.
Upon seeing a line intended for this module, see if there are subcommands
that we should do what is basically a text replacement on. The intent is to
allow things like the following:
command arg1 [anothercommand arg1 arg2]
where the output of anothercommand is command's arg2..n.
Args:
connection source connection
event incoming event
"""
try:
# begin recursion search
attempt = event.arguments()[0]
start_idx = attempt.find('[')
subcmd = attempt[start_idx+1:]
end_idx = subcmd.rfind(']')
subcmd = subcmd[:end_idx]
if start_idx != -1 and end_idx != -1 and len(subcmd) > 0:
# found recursion candidate
# copy the event and see if IT has recursion to do
newevent = copy.deepcopy(event)
newevent.arguments()[0] = subcmd
newevent._recursing = True
self.try_recursion(connection, newevent)
# recursion over, check for aliases
self.try_alias(connection, newevent)
# now that we have a string that has been de-aliased and
# recursed all the way deeper into its text, see if any
# modules can do something with it. this calls the same
# event handlers in the same way as if this were a native
# event.
self.try_to_replace_event_text_with_module_text(connection, newevent)
# we have done all we can do with the sub-event. whatever
# the text of that event now is, we should replace the parent
# event's [] section with it.
oldtext = event.arguments()[0]
newtext = oldtext.replace('['+subcmd+']', newevent.arguments()[0])
event.arguments()[0] = newtext
# we have now resolved the []. recursion will unfold, replacing
# it further and further, until we eventually get back to the
# original irc event in _handle_event, which will do one
# last search on the text.
except IndexError: pass
def quit_irc(self, connection, msg):
"""Quit IRC, disconnect, shut everything down.
Args:
connection the connection to quit
msg the message to send while quitting
"""
for module in self.modlist:
module.save()
module.shutdown()
connection.quit(msg)
log.info(self.save_config())
self._xmlrpc_shutdown()
log.info("Bot shutting down.")
sys.exit()
def reply(self, event, replystr, stop=False):
"""Reply over IRC to replypath or return a string with the reply.
The primary utility for this is to properly handle recursion. The
recursion code in DrBotIRC will set up a couple hints that this method
picks up on and will appropriately send an IRC event or return a
string.
Unless you know what you are doing, the modules you write should use
this method rather than send a privmsg reply, as failing to call this
method will certainly have recursion do odd things with your module.
Args:
event incoming event
replystr the message to reply with
stop whether or not to let other handlers see this
Returns:
The replystr if the event is inside recursion, or, potentially,
"NO MORE" to stop other event handlers from acting.
"""
replypath = event.target()
# if this is a privmsg, reply to the sender
if replypath == self.server.get_nickname():
replypath = irclib.nm_to_n(event.source())
if replystr is not None:
if event._recursing:
return replystr
else:
replies = replystr.split('\n')
for reply in replies:
self.server.privmsg(replypath, reply)
if stop:
return "NO MORE"
def save_modules(self):
"""Call each module's save(), in case they have something to do."""
for module in self.modlist:
module.save()
def _xmlrpc_shutdown(self):
"""Shut down the XML-RPC server."""
if self.xmlrpc is not None:
self.xmlrpc.shutdown()
self.xmlrpc.server_close()
def save_config(self):
"""Write the config file to disk.
Returns:
Short string indicating success.
"""
with open('dr.botzo.cfg', 'w') as cfg:
self.config.write(cfg)
return "Saved config."
def load_module(self, modname):
"""Load a module (in both the python and dr.botzo sense) if not
already loaded.
Args:
modname the module to attempt to load
Returns:
String describing the outcome of the load attempt.
"""
for module in self.modlist:
if modname == module.__class__.__name__:
return "Module '{0:s}' is already loaded.".format(modname)
# not loaded, let's get to work
try:
modstr = 'modules.'+modname
__import__(modstr)
module = sys.modules[modstr]
botmod = eval('module.' + modname + '(self, self.config)')
self.modlist.append(botmod)
botmod.register_handlers()
# might as well add it to the list
modset = set(self.config.get('dr.botzo', 'module_list').split(','))
modset.add(modname)
self.config.set('dr.botzo', 'module_list', ','.join(modset))
return "Module '{0:s}' loaded.".format(modname)
except ImportError as e:
log.error("Error loading '{0:s}'".format(modname))
log.exception(e)
return "Module '{0:s}' could not be loaded.".format(modname)
def unload_module(self, modname):
"""Attempt to unload and del a module if it's loaded.
Args:
modname the module to attempt to unload
Returns:
String describing the outcome of the unload attempt.
"""
modstr = 'modules.'+modname
for module in self.modlist:
if modname == module.__class__.__name__:
# do anything the module needs to do to clean up
module.save()
module.shutdown()
# remove module references
self.modlist.remove(module)
module.unregister_handlers()
# del it
del(sys.modules[modstr])
del(module)
# might as well remove it from the list
modset = set(self.config.get('dr.botzo', 'module_list').split(','))
modset.remove(modname)
self.config.set('dr.botzo', 'module_list', ','.join(modset))
return "Module '{0:s}' unloaded.".format(modname)
# guess it was never loaded
return "Module '{0:s}' is not loaded.".format(modname)
def list_modules(self):
"""List loaded modules.
Returns:
A list of the loaded modules' names.
"""
modnames = []
for module in self.modlist:
modnames.append(module.__class__.__name__)
return modnames
def sigint_handler(self, signal, frame):
"""Cleanly shutdown on SIGINT."""
for module in self.modlist:
module.save()
module.shutdown()
log.info(self.save_config())
self._xmlrpc_shutdown()
sys.exit()
# vi:tabstop=4:expandtab:autoindent
# kate: indent-mode python;indent-width 4;replace-tabs on;

View File

@ -1,328 +0,0 @@
"""
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 &lt;, &gt;, &amp; 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('&lt;', '<')
text = text.replace('&gt;', '>')
text = text.replace('&amp;', '&')
return text
# vi:tabstop=4:expandtab:autoindent
# kate: indent-mode python;indent-width 4;replace-tabs on;

View File

@ -1,23 +0,0 @@
[dr.botzo]
server = irc.foonetic.net
port = 6667
nick = dr_devzo
name = dr. devzo
usermode = -x
debug = true
admin_userhost = bss@ayu.incorporeal.org
module_list = IrcAdmin
dbhost = localhost
dbuser = dr_botzo
dbpass = password
dbname = dr_botzo
ssl = no
ipv6 = yes
[IrcAdmin]
autojoin = #bss
sleep = 30
automsg = nickserv identify foo
[Karma]
meta.pubmsg_needs_bot_prefix = false

View File

@ -1,129 +0,0 @@
"""
dr.botzo - a pluggable IRC bot written in Python
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/>.
"""
import django
from django.conf import settings
from ConfigParser import ConfigParser, NoSectionError, NoOptionError
import logging
import logging.config
import os
import sys
import MySQLdb as mdb
import DrBotIRC
# gross hack to get to django
sys.path.insert(0, '../dr_botzo')
django.setup()
config_file = 'dr.botzo.cfg'
# check argv
if len(sys.argv) == 2:
config_file = sys.argv[1]
# read config file
config = ConfigParser({'debug': 'false'})
config.read(os.path.expanduser(config_file))
logging.config.fileConfig('logging.cfg')
log = logging.getLogger('drbotzo')
try:
dbhost = config.get('dr.botzo', 'dbhost')
dbuser = config.get('dr.botzo', 'dbuser')
dbpass = config.get('dr.botzo', 'dbpass')
dbname = config.get('dr.botzo', 'dbname')
db = mdb.connect(dbhost, dbuser, dbpass, dbname, charset='utf8',
use_unicode=True)
try:
cur = db.cursor()
# need to create the drbotzo_modules table if it doesn't exist
query = """
SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = %s
AND table_name = %s
"""
cur.execute(query, (dbname, 'drbotzo_modules'))
row = cur.fetchone()
if row[0] == 0:
query = """
CREATE TABLE IF NOT EXISTS drbotzo_modules (
module VARCHAR(64) PRIMARY KEY,
version INTEGER
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin
"""
cur.execute(query)
db.commit()
finally: cur.close()
except NoOptionError as e:
sys.exit("Aborted due to error with necessary configuration: "
"{0:s}".format(str(e)))
# get some optional parameters
use_ssl = False
try:
use_ssl = config.getboolean('dr.botzo', 'ssl')
except NoOptionError:
pass
if use_ssl:
log.info("SSL support enabled")
else:
log.debug("SSL not requested")
use_ipv6 = False
try:
use_ipv6 = config.getboolean('dr.botzo', 'ipv6')
except NoOptionError:
pass
if use_ipv6:
log.info("IPv6 support enabled")
else:
log.debug("IPv6 not requested")
# start up the IRC bot
# create IRC and server objects and connect
irc = DrBotIRC.DrBotIRC(config)
server = irc.server().connect(settings.IRC_SERVER,
settings.IRC_PORT,
settings.IRC_NICK,
settings.IRC_PASS,
settings.IRC_USER,
settings.IRC_NAME,
ssl=use_ssl,
ipv6=use_ipv6)
# load features
try:
cfgmodlist = config.get('dr.botzo', 'module_list')
mods = cfgmodlist.split(',')
for mod in mods:
irc.load_module(mod)
except NoOptionError as e:
log.warning("You seem to be missing a module_list config option, which "
"you probably wanted.")
# loop forever
irc.process_forever()
# vi:tabstop=4:expandtab:autoindent
# kate: indent-mode python;indent-width 4;replace-tabs on;

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
[loggers]
keys = root,drbotzo,irclib,dr_botzo.markov,drbotzo.weather,drbotzo.dispatch
[handlers]
keys = stdout,logfile
[formatters]
keys = verbose
[logger_root]
level = INFO
handlers = stdout,logfile
[logger_irclib]
level = INFO
handlers = stdout,logfile
propagate = 0
qualname = irclib
[logger_drbotzo]
level = INFO
handlers = stdout,logfile
propagate = 0
qualname = drbotzo
[logger_dr_botzo.markov]
level = DEBUG
handlers = stdout,logfile
propagate = 0
qualname = dr_botzo.markov
[logger_drbotzo.dispatch]
level = DEBUG
handlers = stdout,logfile
propagate = 0
qualname = drbotzo.dispatch
[logger_drbotzo.weather]
level = DEBUG
handlers = stdout,logfile
propagate = 0
qualname = drbotzo.weather
[handler_stdout]
class = StreamHandler
level = DEBUG
formatter = verbose
args = ''
[handler_logfile]
class = FileHandler
level = DEBUG
formatter = verbose
args = ('dr.botzo.irc.log', 'a')
[formatter_verbose]
format = [%(levelname)-8s %(asctime)s %(name)s] %(message)s
datefmt =

View File

@ -1,61 +0,0 @@
"""
factfiles-to-facts - insert facts from flat file to database
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/>.
"""
import argparse
import getpass
import os
import sqlite3
import socket
import sys
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--factfile')
parser.add_argument('-c', '--category')
args = parser.parse_args()
if args.factfile == None or args.category == None:
print('script needs both -c (category) and -f (filename)')
sys.exit(2)
# database
dbfile = 'dr.botzo.data'
db = sqlite3.connect(dbfile)
# open file
if os.path.isfile(args.factfile):
with open(args.factfile, 'r') as file:
lines = file.readlines()
for line in lines:
try:
line = line.rstrip()
print('inserting \'' + line + '\'')
db.execute('''INSERT INTO facts_facts (category, fact, who, userhost)
VALUES (?,?,?,?)''', (args.category, line.decode('utf-8', 'ignore'), getpass.getuser(), getpass.getuser() + '@' + socket.getfqdn()))
except sqlite3.Error as e:
db.rollback()
print("sqlite error: " + str(e))
sys.exit(2)
db.commit()
else:
print('filename \'' + whats[0] + '\' doesn\'t exist')
# vi:tabstop=4:expandtab:autoindent
# kate: indent-mode python;indent-width 4;replace-tabs on;

View File

@ -1,58 +0,0 @@
"""
import-file-into-markov_chain.py
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 <http://www.gnu.org/licenses/>.
"""
import argparse
import os
import sqlite3
import sys
parser = argparse.ArgumentParser(description='Import lines into the specified context_id.')
parser.add_argument('context_id', metavar='CONTEXT', type=int, nargs=1)
args = parser.parse_args()
print(args.context_id[0])
db = sqlite3.connect('dr.botzo.data')
db.row_factory = sqlite3.Row
cur = db.cursor()
statement = 'INSERT INTO markov_chain (k1, k2, v, context_id) VALUES (?, ?, ?, ?)'
for line in sys.stdin:
# set up the head of the chain
w1 = '__start1'
w2 = '__start2'
# for each word pair, add the next word to the dictionary
for word in line.split():
try:
cur.execute(statement, (w1.decode('utf-8', 'replace'), w2.decode('utf-8', 'replace'), word.decode('utf-8', 'replace'), args.context_id[0]))
except sqlite3.Error as e:
db.rollback()
print("sqlite error: " + str(e))
raise
w1, w2 = w2, word
try:
cur.execute(statement, (w1.decode('utf-8', 'replace'), w2.decode('utf-8', 'replace'), '__stop', args.context_id[0]))
db.commit()
except sqlite3.Error as e:
db.rollback()
print("sqlite error: " + str(e))
raise
# vi:tabstop=4:expandtab:autoindent