[Pymoul-svn] SF.net SVN: pymoul: [289] pymoul/trunk/src/moul
Status: Alpha
Brought to you by:
tiran
|
From: <ti...@us...> - 2007-05-24 00:30:39
|
Revision: 289
http://pymoul.svn.sourceforge.net/pymoul/?rev=289&view=rev
Author: tiran
Date: 2007-05-23 17:30:38 -0700 (Wed, 23 May 2007)
Log Message:
-----------
Adding first code of the chat relay IRC bot daemon.
Modified Paths:
--------------
pymoul/trunk/src/moul/time/podage.py
Added Paths:
-----------
pymoul/trunk/src/moul/chatrelay/
pymoul/trunk/src/moul/chatrelay/__init__.py
pymoul/trunk/src/moul/chatrelay/io.py
pymoul/trunk/src/moul/chatrelay/ircclient.py
Added: pymoul/trunk/src/moul/chatrelay/__init__.py
===================================================================
--- pymoul/trunk/src/moul/chatrelay/__init__.py (rev 0)
+++ pymoul/trunk/src/moul/chatrelay/__init__.py 2007-05-24 00:30:38 UTC (rev 289)
@@ -0,0 +1,22 @@
+# pyMoul - Python interface to Myst Online URU Live
+# Copyright (C) 2007 Christian Heimes <christian (at) cheimes (dot) de>
+
+# 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 2 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, write to the Free Software Foundation, Inc., 59
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""
+"""
+__author__ = "Christian Heimes"
+__version__ = "$Id: __init__.py 108 2007-01-31 14:46:54Z tiran $"
+__revision__ = "$Revision: 108 $"
Property changes on: pymoul/trunk/src/moul/chatrelay/__init__.py
___________________________________________________________________
Name: svn:keywords
+ 'Id Revision'
Name: svn:eol-style
+ native
Added: pymoul/trunk/src/moul/chatrelay/io.py
===================================================================
--- pymoul/trunk/src/moul/chatrelay/io.py (rev 0)
+++ pymoul/trunk/src/moul/chatrelay/io.py 2007-05-24 00:30:38 UTC (rev 289)
@@ -0,0 +1,91 @@
+# pyMoul - Python interface to Myst Online URU Live
+# Copyright (C) 2007 Christian Heimes <christian (at) cheimes (dot) de>
+
+# 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 2 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, write to the Free Software Foundation, Inc., 59
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""
+"""
+__author__ = "Christian Heimes"
+__version__ = "$Id: __init__.py 108 2007-01-31 14:46:54Z tiran $"
+__revision__ = "$Revision: 108 $"
+
+import os
+
+class NullFormatter(object):
+ """A formatter that doesn't change the msg
+ """
+ def format(self, msg):
+ return msg
+
+class LogFileReader(object):
+ """Read log file
+ """
+ def __init__(self, fname):
+ self._fname = fname
+ self._fd = None
+ self._fifo = []
+ self._incomplete = []
+
+ def exists(self):
+ return os.path.isfile(self._fname)
+
+ def open(self):
+ self._fd = open(self._fname, 'r')
+ self._fd.seek(0, 2) # eof
+
+ def close(self):
+ if self._fd:
+ self._fd.close()
+ self._fd = None
+ self._fifo = []
+ self._incomplete = []
+
+ def _read(self):
+ fd = self._fd
+ data = self.read()
+ if not data:
+ return
+ lines = data.split(os.linesep)
+ # XXX - KISS, don't check for imcomplete lines
+ for line in lines:
+ line = line.strip()
+ if line:
+ self._fifo.append(line)
+
+ def __iter__(self):
+ self._read()
+ return self
+
+ def next(self):
+ try:
+ return self._fifo.pop(0)
+ except IndexError:
+ raise StopIteration
+
+class MessageWriter(object):
+ """Write messages to a channel
+ """
+ maxlength = 80
+
+ def __init__(self, client, channel, formatter=None):
+ self._client = client
+ self._channel = channel
+ if formatter is None:
+ formatter = NullFormatter()
+ self._fmt = formatter
+
+ def log(self, msg):
+ msg = self._fmt.format(msg)
+ self._client.say(self._channel, msg, self.maxlength)
Property changes on: pymoul/trunk/src/moul/chatrelay/io.py
___________________________________________________________________
Name: svn:keywords
+ 'Id Revision'
Name: svn:eol-style
+ native
Added: pymoul/trunk/src/moul/chatrelay/ircclient.py
===================================================================
--- pymoul/trunk/src/moul/chatrelay/ircclient.py (rev 0)
+++ pymoul/trunk/src/moul/chatrelay/ircclient.py 2007-05-24 00:30:38 UTC (rev 289)
@@ -0,0 +1,285 @@
+# pyMoul - Python interface to Myst Online URU Live
+# Copyright (C) 2007 Christian Heimes <christian (at) cheimes (dot) de>
+
+# 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 2 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, write to the Free Software Foundation, Inc., 59
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""Chat relay using Twisted IRC
+
+Partly based on Buildbot's IRC support
+"""
+__author__ = "Christian Heimes"
+__version__ = "$Id: __init__.py 108 2007-01-31 14:46:54Z tiran $"
+__revision__ = "$Revision: 108 $"
+
+import sys
+
+from twisted.words.protocols import irc
+from twisted.internet import reactor, protocol
+from twisted.python import log, failure
+
+from moul.chatrelay.io import MessageWriter
+from moul.chatrelay.io import LogFileReader
+
+def requirePasswd(func):
+ """@decorator"""
+ func.requirePassword = True
+ return func
+
+def doesRequirePasswd(func):
+ return getattr(func, 'requirePassword', False)
+
+def usage(usage):
+ """@decorator"""
+ def wrapper(func):
+ func.usage = usage
+ return func
+ return wrapper
+
+class UsageError(ValueError):
+ def __init__(self, string = "Invalid usage", *more):
+ ValueError.__init__(self, string, *more)
+
+class InvalidPassword(UsageError):
+ def __init__(self, string = "Invalid password", *more):
+ ValueError.__init__(self, string, *more)
+
+class ChatRelayBot(irc.IRCClient):
+ """A chat relay bot"""
+ def __init__(self, nickname, channel, realname=None, username=None,
+ serverpasswd=None, nickpasswd=None, adminpasswd=None):
+ """
+ """
+ self.nickname = nickname
+ self.channel = channel
+ self.realname = realname
+ self.username = username
+ self.password = serverpasswd
+ self.nickpasswd = nickpasswd
+ self.adminpasswd = adminpasswd
+
+ def connectionMade(self):
+ irc.IRCClient.connectionMade(self)
+
+ def connectionLost(self, reason):
+ irc.IRCClient.connectionLost(self, reason)
+
+ # callbacks for events
+
+ def signedOn(self):
+ if self.nickpasswd:
+ self.msg("Nickserv", "IDENTIFY %s" % self.nickpasswd)
+ self.join(self.channel)
+
+ def joined(self, channel):
+ log.msg("I have joined", channel)
+
+ def left(self, channel):
+ log.msg("I have left", channel)
+
+ def kickedFrom(self, channel, kicker, message):
+ log.msg("I have been kicked from %s by %s: %s" %
+ (channel, kicker, message))
+
+ def privmsg(self, user, channel, message):
+ """This will get called when the bot receives a message."""
+ user = user.split('!', 1)[0]
+ channel = channel.lower()
+ if channel == self.nickname.lower():
+ # private message
+ message = "%s: %s" % (self.nickname, message)
+ reply = user
+ private = True
+ else:
+ reply = channel
+ private = False
+
+ if message.startswith("%s:" % self.nickname):
+ message = message[len("%s:" % self.nickname):]
+
+ message = message.lstrip()
+
+ parts = message.split(' ', 1)
+ if len(parts) == 1:
+ parts = parts + ['']
+ cmd, args = parts
+ log.msg("irc command", cmd)
+
+ error = None
+ try:
+ meth, args = self.getCommandMethod(cmd, args, private)
+ if not meth and message[-1] == '!':
+ meth = self.command_EXCITED
+
+ if meth:
+ meth(user, reply, args.strip())
+ except UsageError, e:
+ self.reply(reply, str(e))
+ except:
+ f = failure.Failure()
+ log.err(f)
+ error = "Something bad happened (see logs): %s" % f.type
+
+ if error:
+ try:
+ self.reply(reply, error)
+ except:
+ log.err()
+
+ #self.say(channel, "count %d" % self.counter)
+ #self.counter += 1
+
+ def reply(self, dest, message):
+ # maybe self.notice(dest, message) instead?
+ self.msg(dest, message)
+
+ def getCommandMethod(self, command, args, private):
+ meth = getattr(self, 'command_' + command.upper(), None)
+ if meth is not None:
+ if doesRequirePasswd(meth):
+ if not private:
+ raise UsageError("This command requires a password.")
+ parts = args.split(' ', 1)
+ if len(parts) == 1:
+ parts = parts + ['']
+ password, args = parts
+ if password != self.adminpasswd:
+ raise InvalidPassword()
+ return meth, args
+
+ @usage("Say hello")
+ def command_HELLO(self, user, reply, args):
+ self.reply(reply, "yes?")
+
+ @usage("Say something exciting")
+ def command_EXCITED(self, user, reply, args):
+ # like 'buildbot: destroy the sun!'
+ self.reply(reply, "What you say!")
+
+ def build_commands(self):
+ commands = []
+ for k, v in self.__class__.__dict__.items():
+ if k.startswith('command_'):
+ name = k[8:].lower()
+ if doesRequirePasswd(v):
+ name = name+'*'
+ commands.append(name)
+ commands.sort()
+ return commands
+
+ @usage("help <command> - Give help for <command>")
+ def command_HELP(self, user, reply, args):
+ args = args.split()
+ if len(args) == 0:
+ self.reply(reply, "Get help on what? (try 'help <foo>', or 'commands' for a command list)")
+ return
+ command = args[0]
+ meth = self.getCommandMethod(command)
+ if not meth:
+ raise UsageError, "no such command '%s'" % command
+ usage = getattr(meth, 'usage', None)
+ if usage:
+ self.reply(reply, "Usage: %s" % usage)
+ else:
+ self.reply(reply, "No usage info for '%s'" % command)
+
+ @usage("commands - List available commands")
+ def command_COMMANDS(self, user, reply, args):
+ commands = self.build_commands()
+ msg = "Chat Relay commands: " + ", ".join(commands)
+ self.reply(reply, msg)
+
+ @usage("Dance! Let's do the time warp!")
+ def command_DANCE(self, user, reply, args):
+ reactor.callLater(1.0, self.reply, reply, "0-<")
+ reactor.callLater(3.0, self.reply, reply, "0-/")
+ reactor.callLater(3.5, self.reply, reply, "0-\\")
+
+ @usage("Open the chatlog")
+ @requirePasswd
+ def command_OPENLOG(self, user, reply, args):
+ self.reply(reply, 'Opening chatlog')
+
+ @usage("Close the chatlog")
+ @requirePasswd
+ def command_CLOSELOG(self, user, reply, args):
+ self.reply(reply, 'Closing chatlog')
+
+class ThrottledClientFactory(protocol.ClientFactory):
+ lostDelay = 2
+ failedDelay = 60
+ def clientConnectionLost(self, connector, reason):
+ reactor.callLater(self.lostDelay, connector.connect)
+ def clientConnectionFailed(self, connector, reason):
+ reactor.callLater(self.failedDelay, connector.connect)
+
+class ChatRelayBotFactory(ThrottledClientFactory):
+ protocol = ChatRelayBot
+
+ shuttingDown = False
+ p = None
+
+ def __init__(self, nickname, channel, realname=None, username=None,
+ serverpasswd=None, nickpasswd=None, adminpasswd=None):
+ """
+ """
+ self.nickname = nickname
+ self.channel = channel
+ self.realname = realname
+ self.username = username
+ self.password = serverpasswd
+ self.nickpasswd = nickpasswd
+ self.adminpasswd = adminpasswd
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ del d['p']
+ return d
+
+ def shutdown(self):
+ self.shuttingDown = True
+ if self.p:
+ self.p.quit("Shutting down")
+
+ def buildProtocol(self, address):
+ p = self.protocol(self.nickname, self.channel, self.realname,
+ self.username, self.password, self.nickpasswd,
+ self.adminpasswd)
+ p.factory = self
+ self.p = p
+ return p
+
+ def clientConnectionLost(self, connector, reason):
+ if self.shuttingDown:
+ log.msg("not scheduling reconnection attempt")
+ return
+ ThrottledClientFactory.clientConnectionLost(self, connector, reason)
+
+ def clientConnectionFailed(self, connector, reason):
+ if self.shuttingDown:
+ log.msg("not scheduling reconnection attempt")
+ return
+ ThrottledClientFactory.clientConnectionFailed(self, connector, reason)
+
+def main():
+ log.startLogging(sys.stdout)
+ f = ChatRelayBotFactory('TiransRelay', '#urubot', adminpasswd="moul")
+ # connect factory to this host and port
+ reactor.connectTCP("tsunami.justirc.net", 6667, f)
+
+ # run bot
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
Property changes on: pymoul/trunk/src/moul/chatrelay/ircclient.py
___________________________________________________________________
Name: svn:keywords
+ 'Id Revision'
Name: svn:eol-style
+ native
Modified: pymoul/trunk/src/moul/time/podage.py
===================================================================
--- pymoul/trunk/src/moul/time/podage.py 2007-04-28 13:27:31 UTC (rev 288)
+++ pymoul/trunk/src/moul/time/podage.py 2007-05-24 00:30:38 UTC (rev 289)
@@ -303,9 +303,9 @@
fd.close()
if __name__ == '__main__':
- forumTimeTable(tzlist=('CET',))
- #forumTimeTable(tzlist=('UTC',))
+ #forumTimeTable(tzlist=('CET',))
+ forumTimeTable(tzlist=('UTC',))
#forumTimeTable(tzlist=('US/Pacific',))
#forumTimeTable()
- #allTzList()
+ allTzList()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|