[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. |