Thread: [Pymoul-svn] SF.net SVN: pymoul: [290] pymoul/trunk/src/moul/chatrelay
Status: Alpha
Brought to you by:
tiran
From: <ti...@us...> - 2007-05-24 14:00:54
|
Revision: 290 http://pymoul.svn.sourceforge.net/pymoul/?rev=290&view=rev Author: tiran Date: 2007-05-24 07:00:41 -0700 (Thu, 24 May 2007) Log Message: ----------- First working version Modified Paths: -------------- pymoul/trunk/src/moul/chatrelay/io.py pymoul/trunk/src/moul/chatrelay/ircclient.py Modified: pymoul/trunk/src/moul/chatrelay/io.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/io.py 2007-05-24 00:30:38 UTC (rev 289) +++ pymoul/trunk/src/moul/chatrelay/io.py 2007-05-24 14:00:41 UTC (rev 290) @@ -34,45 +34,48 @@ """ def __init__(self, fname): self._fname = fname - self._fd = None + 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 + self._fd.seek(0, 2) # eof def close(self): if self._fd: - self._fd.close() + self._fd.close() self._fd = None self._fifo = [] - self._incomplete = [] - + self._incomplete = [] + + @property + def isOpen(self): + return self._fd is not None + 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) + data = self._fd.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 + self._read() + return self def next(self): try: - return self._fifo.pop(0) - except IndexError: - raise StopIteration + return self._fifo.pop(0) + except IndexError: + raise StopIteration class MessageWriter(object): """Write messages to a channel @@ -81,11 +84,11 @@ def __init__(self, client, channel, formatter=None): self._client = client - self._channel = channel - if formatter is None: - formatter = NullFormatter() - self._fmt = formatter + 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) + self._client.say(self._channel, msg, self.maxlength) Modified: pymoul/trunk/src/moul/chatrelay/ircclient.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/ircclient.py 2007-05-24 00:30:38 UTC (rev 289) +++ pymoul/trunk/src/moul/chatrelay/ircclient.py 2007-05-24 14:00:41 UTC (rev 290) @@ -24,14 +24,21 @@ __revision__ = "$Revision: 108 $" import sys +import os from twisted.words.protocols import irc from twisted.internet import reactor, protocol +from twisted.internet.task import LoopingCall from twisted.python import log, failure from moul.chatrelay.io import MessageWriter from moul.chatrelay.io import LogFileReader +from moul.osdependent import getMoulUserDataDir +datadir = getMoulUserDataDir() +chatlog = os.path.join(datadir, 'Log', 'chat.0.log') +print chatlog + def requirePasswd(func): """@decorator""" func.requirePassword = True @@ -57,6 +64,10 @@ class ChatRelayBot(irc.IRCClient): """A chat relay bot""" + reader = None + writer = None + interval = 5 + def __init__(self, nickname, channel, realname=None, username=None, serverpasswd=None, nickpasswd=None, adminpasswd=None): """ @@ -68,13 +79,34 @@ self.password = serverpasswd self.nickpasswd = nickpasswd self.adminpasswd = adminpasswd - + + self.loop = LoopingCall(self.checkLogfile) + self.identified = [] + def connectionMade(self): irc.IRCClient.connectionMade(self) def connectionLost(self, reason): irc.IRCClient.connectionLost(self, reason) + # core method - check log file + + def checkLogfile(self): + for line in iter(self.reader): + self.writer.log(line) + + def start(self): + #if not self.reader.isOpen: + # return + if not self.reader.exists(): + return "File does not exist" + self.reader.open() + self.loop.start(self.interval) + + def stop(self): + self.loop.stop() + self.reader.close() + # callbacks for events def signedOn(self): @@ -206,15 +238,22 @@ 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') + #@requirePasswd + @usage("Open the chatlog and start relaying") + def command_STARTLOG(self, user, reply, args): + self.reply(reply, 'Opening chatlog ...') + msg = self.start() + if msg is not None: + self.reply(reply, 'Error: %s' % msg) + return + self.msg(self.channel, "*** I'm relaying chat ***") - @usage("Close the chatlog") - @requirePasswd - def command_CLOSELOG(self, user, reply, args): - self.reply(reply, 'Closing chatlog') + #@requirePasswd + @usage("Stop relaying") + def command_STOPLOG(self, user, reply, args): + self.stop() + self.reply(reply, 'chatlog closed') + self.msg(self.channel, "*** I've stopped to relay chat ***") class ThrottledClientFactory(protocol.ClientFactory): lostDelay = 2 @@ -229,6 +268,8 @@ shuttingDown = False p = None + readerClass = LogFileReader + writerClass = MessageWriter def __init__(self, nickname, channel, realname=None, username=None, serverpasswd=None, nickpasswd=None, adminpasswd=None): @@ -257,6 +298,8 @@ self.username, self.password, self.nickpasswd, self.adminpasswd) p.factory = self + p.reader = self.readerClass(chatlog) + p.writer = self.writerClass(p, self.channel) self.p = p return p This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ti...@us...> - 2007-05-30 14:49:34
|
Revision: 297 http://pymoul.svn.sourceforge.net/pymoul/?rev=297&view=rev Author: tiran Date: 2007-05-30 07:49:24 -0700 (Wed, 30 May 2007) Log Message: ----------- Created configuration, main application and new bot Modified Paths: -------------- pymoul/trunk/src/moul/chatrelay/filter.py pymoul/trunk/src/moul/chatrelay/interfaces.py Added Paths: ----------- pymoul/trunk/src/moul/chatrelay/app.py pymoul/trunk/src/moul/chatrelay/chatrelay.ini pymoul/trunk/src/moul/chatrelay/config.py pymoul/trunk/src/moul/chatrelay/ircrelay.py pymoul/trunk/src/moul/chatrelay/service.py Added: pymoul/trunk/src/moul/chatrelay/app.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/app.py (rev 0) +++ pymoul/trunk/src/moul/chatrelay/app.py 2007-05-30 14:49:24 UTC (rev 297) @@ -0,0 +1,19 @@ +from twisted.application import internet, service +from twisted.internet import reactor + +from moul.chatrelay.service import PollingFileAppendWatchService +from moul.chatrelay.config import config +from moul.chatrelay.ircrelay import IRCChatRelayFactory + +application = service.Application("MOUL Chat Relay") + +appendWatchService = PollingFileAppendWatchService(config.watch_interval) +appendWatchService.setName("AppendWatch") +appendWatchService.setServiceParent(application) + +ircFactory = IRCChatRelayFactory() +ircFactory.config = config +ircRelayService = internet.TCPClient(config.network, config.port, + ircFactory) +ircRelayService.setName("IRC Relay") +ircRelayService.setServiceParent(application) Added: pymoul/trunk/src/moul/chatrelay/chatrelay.ini =================================================================== --- pymoul/trunk/src/moul/chatrelay/chatrelay.ini (rev 0) +++ pymoul/trunk/src/moul/chatrelay/chatrelay.ini 2007-05-30 14:49:24 UTC (rev 297) @@ -0,0 +1,9 @@ +[ircrelay] +nickname=Tiran +channel=#urubot +network=tsunami.justirc.net +port=6667 +adminpasswd=moul +#serverpassd= +#nickpasswd= +#defaultprofil= \ No newline at end of file Added: pymoul/trunk/src/moul/chatrelay/config.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/config.py (rev 0) +++ pymoul/trunk/src/moul/chatrelay/config.py 2007-05-30 14:49:24 UTC (rev 297) @@ -0,0 +1,70 @@ +import os +from ConfigParser import SafeConfigParser +from ConfigParser import NoOptionError + +_marker = object() +required = object() + +dirname = os.path.dirname(__file__) +defaultcfg = os.path.join(dirname, 'chatrelay.ini') + +class RequiredError(ValueError): + pass + +class DefaultConfigParser(SafeConfigParser): + + def get(self, sec, name, default=_marker): + try: + return SafeConfigParser.get(self, sec, name) + except NoOptionError: + if default is not _marker: + return default + else: + raise + +class Configuration(object): + """Config object + """ + options = ['nickname', 'channel', 'network', 'port', 'adminpasswd', + 'serverpasswd', 'nickpasswd', 'defaultprofile'] + + nickname = required + channel = required + network = required + port = 6667 + adminpasswd = required + serverpasswd = None + nickpasswd = None + defaultprofile = None + # internal, DO NOT MESS AROUND + realname = "Tiran's MOUL relay bot" + username = "TiransRelay" + nicksuffix = "[Relay]" + watch_interval = 5 # seconds + + def __init__(self): + self.profiles = [] + +class FileConfiguration(Configuration): + """Load configuration from a file + """ + + def __init__(self, cfgfile): + super(FileConfiguration, self).__init__() + cfg = DefaultConfigParser() + cfg.readfp(open(cfgfile)) + self._loadcfg(cfg) + + def _loadcfg(self, cfg): + sec = 'ircrelay' + for name in self.options: + default = getattr(self, name) + value = cfg.get(sec, name, default=default) + if value is required: + raise RequiredError(name) + setattr(self, name, value) + + self.port = int(self.port) + self.nickname = self.nickname + self.nicksuffix + +config = FileConfiguration(defaultcfg) Modified: pymoul/trunk/src/moul/chatrelay/filter.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/filter.py 2007-05-29 13:44:29 UTC (rev 296) +++ pymoul/trunk/src/moul/chatrelay/filter.py 2007-05-30 14:49:24 UTC (rev 297) @@ -3,6 +3,7 @@ from moul.chatrelay.interfaces import ILineFilter from moul.chatrelay.interfaces import ISnoopyLineFilter from moul.chatrelay.interfaces import IConfigureableFilter +from moul.chatrelay.interfaces import IFilterProfile from moul.file.chatlog import (CHAT_UNKNOWN, CHAT_PRIVMSG, CHAT_PRIVMSGTO, CHAT_PRIVMSGFROM, CHAT_ACTION, CHAT_ERROR, CHAT_MSG) @@ -17,6 +18,23 @@ def filter(self, line, **kwargs): return line, kwargs +class FilterProfile(object): + # TODO: support ISnoopy and IConfigurable + name = None + + def __init__(self, name): + self.name = name + self.filters = [] + + def registerFilters(self, *filters): + for filter in self.filters: + self.filters.append(filter) + + def filter(self, line, **kwargs): + for filter in self.filters: + line, kwargs = filter.filter(line, **kwargs) + return line, kwargs + class HightlightImportantFilter(object): """Highlight important people """ Modified: pymoul/trunk/src/moul/chatrelay/interfaces.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/interfaces.py 2007-05-29 13:44:29 UTC (rev 296) +++ pymoul/trunk/src/moul/chatrelay/interfaces.py 2007-05-30 14:49:24 UTC (rev 297) @@ -112,6 +112,15 @@ """List commands XXX: finish me """ +class IFilterProfile(ILineFilter): + """A filter profile + """ + name = Attribute("Filter profile name") + + def registerFilters(*filters): + """Register one or more filters + """ + class IOutputFormatter(Interface): """A line formatter """ Added: pymoul/trunk/src/moul/chatrelay/ircrelay.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/ircrelay.py (rev 0) +++ pymoul/trunk/src/moul/chatrelay/ircrelay.py 2007-05-30 14:49:24 UTC (rev 297) @@ -0,0 +1,116 @@ +# 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: ircclient.py 295 2007-05-28 16:53:00Z tiran $" +__revision__ = "$Revision: 295 $" + +import sys +import os + +from twisted.words.protocols import irc +from twisted.internet import reactor, protocol +from twisted.internet.task import LoopingCall +from twisted.python import log, failure + +from moul.chatrelay.io import MessageWriter +from moul.chatrelay.io import LogFileReader +from moul.chatrelay.io import MOULLogFormatter +from moul.osdependent import getMoulUserDataDir + +datadir = getMoulUserDataDir() +chatlog = os.path.join(datadir, 'Log', 'chat.0.log') + +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 IRCChatRelay(irc.IRCClient): + """A chat relay bot""" + + @property + def config(self): + return self.factory.config + + @property + def nickname(self): + return self.factory.config.nickname + @property + def password(self): + return self.factory.config.serverpasswd + @property + def realname(self): + return self.factory.config.realname + @property + def username(self): + return self.factory.config.username + + def connectionMade(self): + irc.IRCClient.connectionMade(self) + + def connectionLost(self, reason): + irc.IRCClient.connectionLost(self, reason) + + # callbacks for events + + def signedOn(self): + if self.config.nickpasswd: + self.msg("Nickserv", "IDENTIFY %s" % self.config.nickpasswd) + self.join(self.config.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)) + +class IRCChatRelayFactory(protocol.ReconnectingClientFactory): + protocol = IRCChatRelay + config = None + + def buildProtocol(self, address): + self.resetDelay() + p = self.protocol() + p.factory = self + return p Added: pymoul/trunk/src/moul/chatrelay/service.py =================================================================== --- pymoul/trunk/src/moul/chatrelay/service.py (rev 0) +++ pymoul/trunk/src/moul/chatrelay/service.py 2007-05-30 14:49:24 UTC (rev 297) @@ -0,0 +1,112 @@ +from zope.interface import implements + +from moul.chatrelay.interfaces import IFileAppendWatchService + +from twisted.python import log +from twisted.application import service +from twisted.application import internet +from twisted.internet import task + +class PollingFileAppendWatchService(internet.TimerService): + """Watch log files for new lines + + @ivar _files: mapping of file names -> (callback, errback) + @ivar _paused: mapping of paused file -> position + @ivar _fps: mapping of file names -> open file descriptors + + Based on twisted.mail.mail.FileMonitorService + """ + implements(IFileAppendWatchService) + + _volatile = ['_fds', '_loop'] + default_interval = 5 + filemode = 'r' + + def __init__(self, interval=None): + self._files = {} + self._paused = {} + self._fps = {} + self._call = None + self._interval = interval if interval else self.default_interval + + def startService(self): + service.Service.startService(self) + self._setupMonitor() + + def stopService(self): + self._stopMonitor() + return service.Service.stopService(self) + + def monitorFile(self, path, callback, errback=None): + if path in self._files: + raise ValueError("File '%s' is already registered" % path) + errback = errback if errback else callback + self._files[path] = (callback, errback) + + def unmonitorFile(self, path): + del self._files[path] + if path in self._fps: + self._fps[path].close() + del self._fps[path] + if path in self._paused: + del self._paused[path] + + def pauseFile(self, path): + if path in self._paused: + raise ValueError("Already paused: '%s'" % path) + if path in self._fps: + fp = self._fps[path] + pos = fp.tell() + fp.close() + del self._fps[path] + else: + pos = None + self._paused[path] = pos + + def resumeFile(self, path, fromEnd=True): + if path not in self._paused: + raise ValueError("Not paused: '%s'" % path) + pos = None if fromEnd else self._paused[path] + del self._paused[path] + self._getFile(path, pos) + + def _getFile(self, path, pos=None): + if path in self._paused: + return None + try: + return self._fds[path] + except KeyError: + pass + try: + fp = open(path, self.filemode) + except IOError, err: + log.err() + self._files[path][1](path, err) + return + if pos is None: + fp.seek(0, 2) + else: + try: + fp.seek(pos, 0) + except IOError, err: + log.err() + self._files[path][1](path, err) + fp.seek(0, 2) + self._fps[path] = fp + return fp + + def _setupMonitor(self): + self._loop = task.LoopingCall(self._monitor) + self._loop.start(self._interval, now=True).addErrback(self._failed) + + def _stopMonitor(self): + if self._loop.running: + self._loop.stop() + self._loop = None + + def _monitor(self): + for path, funcs in self._files.items(): + fp = self._getFile(path) + data = fp.read() + if data: + funcs[0](path, data) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |