[Pymoul-svn] SF.net SVN: pymoul: [297] pymoul/trunk/src/moul/chatrelay
Status: Alpha
Brought to you by:
tiran
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. |