'" % (keyword, self._nickname, keyword))
def _set_definition(self, connection, keyword, definition):
"""Function to store a definition in cache and to disk in the bot's dictionary."""
self.definition[keyword.lower()] = (keyword, definition)
connection.privmsg(self.channel, "Got it! %s is %s" % self.definition[keyword.lower()])
f = open(self.datastore, "w")
cPickle.dump(self.definition, f)
f.close()
I have trimmed out the instantiation of this bot class, because that part isn't
relevant. You can go and immediately reuse this bot to manage any channel you have.
Web App
>>>>>>>
Well, after getting an IRC bot working that quickly, I want a nice interface to
see what it is up to. For that, I will use Spring Python and build a
Spring-based web app::
def header():
"""Standard header used for all pages"""
return """
Coily :: An IRC bot used to manage the #springpython irc channel (powered by CherryPy/Spring Python)
"""
def footer():
"""Standard footer used for all pages."""
return """
"""
def markup(text):
"""Convert any http://xyz references into real web links."""
httpR = re.compile(r"(http://[\w.:/?-]*\w)")
alteredText = httpR.sub(r'\1', text)
return alteredText
class CoilyView:
"""Presentation layer of the web application."""
def __init__(self, bot = None):
"""Inject a controller object in order to fetch live data."""
self.bot = bot
@cherrypy.expose
def index(self):
"""CherryPy will call this method for the root URI ("/") and send
its return value to the client."""
return header() + """
Welcome
Hi, I'm Coily! I'm 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.
List current definitions
""" + footer()
@cherrypy.expose
def listDefinitions(self):
results = header()
results += """
Keyword |
Definition |
"""
for key, value in self.bot.getDefinitions().items():
results += markup("""
%s |
%s |
""" % (value[0], value[1]))
results += "
"
results += footer()
return results
Putting it all together
>>>>>>>>>>>>>>>>>>>>>>>
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 :doc:`Inversion of Control `.
So, I defined two contexts, one for the IRC bot and another for the web
application.
IRC Bot's application context
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
::
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
Web App's application context
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
::
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.\
"""
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
Main runner
<<<<<<<<<<<
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 :doc:`remoting technology `::
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({'server.socket_port': 9001})
cherrypy.tree.mount(applicationContext.get_object(name = "root"), '/', 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()
Releasing your CherryPy web app to the internet
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Now that you have a CherryPy web app running, how about making it visible to
the internet?
.. highlight:: apache
If you already have an Apache web server running, and are using a Debian/Ubuntu
installation, you just need to create a file in */etc/apache2/sites-available* like
*coily.conf* with the following lines::
RedirectMatch ^/coily$ /coily/
ProxyPass /coily/ http://localhost:9001/
ProxyPassReverse /coily/ http://localhost:9001/
Order allow,deny
Allow from all
.. highlight:: bash
Now need to softlink this into /etc/apache2/sites-enabled::
% cd /etc/apache2/sites-enabled
% sudo ln -s /etc/apache2/sites-available/coily.conf 001-coily
This requires that mod_proxy be enabled::
% sudo a2enmod proxy proxy_http
Finally, restart Apache::
% sudo /etc/init.d/apache2 --force-reload
It should be visible on the site now.
Come and visit Coily
>>>>>>>>>>>>>>>>>>>>
If you haven't figured it out yet, I use this code to run my own bot, Coily.
Unfortunately, at this time, I don't have a mechanism to make it run persistently.
External Links
++++++++++++++
`See this article reported in LinuxToday `_