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