""" def footer(): """Standard footer used for all pages.""" return """
| Home | Coily :: a Spring Python IRC bot (powered by CherryPy) |
Hi, I\(aqm Coily! I\(aqm a bot used to manage the IRC channel #springpython.
If you visit the channel, you may find I have a lot of information to offer while you are there. If I seem to be missing some useful definitions, then you can help grow my knowledge.
Command
Description
!coily, what is xyz?
This is how you ask me for a definition of something.
!xyz
This is a shortcut way to ask the same question.
!coily, xyz is some definition for xyz
This is how you feed me a definition.
To save you from having to query me for every current definition I have, there is a link on this web site that lists all my current definitions. NOTE: These definitions can be set by other users.
""" + footer()
@cherrypy.expose
def listDefinitions(self):
results = header()
results += """
"
results += footer()
return results
.ft P
.fi
.SS Putting it all together
.sp
Well, so far, I have two useful classes. However, they need to get launched
inside a script. This means objects need to be instantiated. To do this, I have
decided to make this a Spring app and use \fBInversion of Control\fP.
.sp
So, I defined two contexts, one for the IRC bot and another for the web
application.
.SS IRC Bot\(aqs application context
.sp
.nf
.ft C
class CoilyIRCServer(PythonConfig):
"""This container represents the context of the IRC bot. It needs to
export information, so the web app can get it."""
def __init__(self):
super(CoilyIRCServer, self).__init__()
@Object
def remoteBot(self):
return DictionaryBot([("irc.ubuntu.com", 6667)], "#springpython",
ops=["Goldfisch"], nickname="coily",
realname="Coily the Spring Python assistant", logfile="springpython.log")
@Object
def bot(self):
exporter = PyroServiceExporter()
exporter.service_name = "bot"
exporter.service = self.remoteBot()
return exporter
.ft P
.fi
.SS Web App\(aqs application context
.sp
.nf
.ft C
class CoilyWebClient(PythonConfig):
"""
This container represents the context of the web application used to interact with the bot and present a
nice frontend to the user community about the channel and the bot.\e
"""
def __init__(self):
super(CoilyWebClient, self).__init__()
@Object
def root(self):
return CoilyView(self.bot())
@Object
def bot(self):
proxy = PyroProxyFactory()
proxy.service_url = "PYROLOC://localhost:7766/bot"
return proxy
.ft P
.fi
.SS Main runner
.sp
I fit all this into one executable. However, I quickly discovered that both
CherryPy web apps and irclib bots like to run in the main thread. This means
I need to launch two python shells, one running the web app, the other running
the ircbot, and I need the web app to be able to talk to the irc bot. This is
a piece of cake with Spring Python. All I need to utilize is a \fBremoting technology\fP:
.sp
.nf
.ft C
if __name__ == "__main__":
# Parse some launching options.
parser = OptionParser(usage="usage: %prog [\-h|\-\-help] [options]")
parser.add_option("\-w", "\-\-web", action="store_true", dest="web", default=False, help="Run the web server object.")
parser.add_option("\-i", "\-\-irc", action="store_true", dest="irc", default=False, help="Run the IRC\-bot object.")
parser.add_option("\-d", "\-\-debug", action="store_true", dest="debug", default=False, help="Turn up logging level to DEBUG.")
(options, args) = parser.parse_args()
if options.web and options.irc:
print "You cannot run both the web server and the IRC\-bot at the same time."
sys.exit(2)
if not options.web and not options.irc:
print "You must specify one of the objects to run."
sys.exit(2)
if options.debug:
logger = logging.getLogger("springpython")
loggingLevel = logging.DEBUG
logger.setLevel(loggingLevel)
ch = logging.StreamHandler()
ch.setLevel(loggingLevel)
formatter = logging.Formatter("%(asctime)s \- %(name)s \- %(levelname)s \- %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
if options.web:
# This runs the web application context of the application. It allows a nice web\-enabled view into
# the channel and the bot that supports it.
applicationContext = ApplicationContext(CoilyWebClient())
# Configure cherrypy programmatically.
conf = {"/": {"tools.staticdir.root": os.getcwd()},
"/images": {"tools.staticdir.on": True,
"tools.staticdir.dir": "images"},
"/html": {"tools.staticdir.on": True,
"tools.staticdir.dir": "html"},
"/styles": {"tools.staticdir.on": True,
"tools.staticdir.dir": "css"}
}
cherrypy.config.update({\(aqserver.socket_port\(aq: 9001})
cherrypy.tree.mount(applicationContext.get_object(name = "root"), \(aq/\(aq, config=conf)
cherrypy.engine.start()
cherrypy.engine.block()
if options.irc:
# This runs the IRC bot that connects to a channel and then responds to various events.
applicationContext = ApplicationContext(CoilyIRCServer())
coily = applicationContext.get_object("bot")
coily.service.start()
.ft P
.fi
.SS Releasing your CherryPy web app to the internet
.sp
Now that you have a CherryPy web app running, how about making it visible to
the internet?
.sp
If you already have an Apache web server running, and are using a Debian/Ubuntu
installation, you just need to create a file in \fI/etc/apache2/sites\-available\fP like
\fIcoily.conf\fP with the following lines:
.sp
.nf
.ft C
RedirectMatch ^/coily$ /coily/
ProxyPass /coily/ http://localhost:9001/
ProxyPassReverse /coily/ http://localhost:9001/
Keyword
Definition
"""
for key, value in self.bot.getDefinitions().items():
results += markup("""
""" % (value[0], value[1]))
results += "%s
%s