From 5be9cd4eb937a2274b23231c82fa2c4754828dc0 Mon Sep 17 00:00:00 2001 From: Jan Philipp Timme Date: Mon, 14 Jul 2014 22:57:25 +0200 Subject: [PATCH] [TASK] Huge rewrite. Now with real reconnect support. --- Makefile | 2 +- fdskun.py | 154 +++++++++++++++++++++++++++ monitor/__init__.py | 0 monitor/bot.py | 77 -------------- settings.ini.example | 3 +- twisted/plugins/monitorbot_plugin.py | 130 ---------------------- 6 files changed, 157 insertions(+), 209 deletions(-) create mode 100644 fdskun.py delete mode 100644 monitor/__init__.py delete mode 100644 monitor/bot.py delete mode 100644 twisted/plugins/monitorbot_plugin.py diff --git a/Makefile b/Makefile index 7257a2e..ae3ddb7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ run: - twistd -n monitorbot + python fdskun.py install: pip install -r requirements.txt diff --git a/fdskun.py b/fdskun.py new file mode 100644 index 0000000..98d28cb --- /dev/null +++ b/fdskun.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +from twisted.words.protocols import irc +from twisted.internet import reactor, protocol +from twisted.python import usage, log +from twisted.internet import inotify +from twisted.python import filepath +from ConfigParser import ConfigParser + +import time, sys + +class MonitorBot(irc.IRCClient): + """A (FTP)-FileSystem Monitoring IRC bot.""" + + def __init__(self, nickname): + self.nickname = nickname + + def connectionMade(self): + irc.IRCClient.connectionMade(self) + print("[connected at %s]" % time.asctime(time.localtime(time.time()))) + + def connectionLost(self, reason): + irc.IRCClient.connectionLost(self, reason) + print("[disconnected at %s]" % time.asctime(time.localtime(time.time()))) + + # callbacks for events + def signedOn(self): + """Called when bot has succesfully signed on to server.""" + self.join(self.factory.channel) + + def joined(self, channel): + """This will get called when the bot joins the channel.""" + print("[I have joined %s]" % channel) + + def privmsg(self, user, channel, msg): + """This will get called when the bot receives a message.""" + self.quit() + pass + +class MonitorBotFactory(protocol.ClientFactory): + """A factory for MonitorBots. + + A new protocol instance will be created each time we connect to the server. + """ + + def __init__(self, nickname, channel, fsmon): + self.nickname = nickname + self.channel = channel + self.fsmon = fsmon + + def buildProtocol(self, addr): + p = MonitorBot(self.nickname) + self.fsmon.setBot(p) + p.factory = self + return p + + def clientConnectionLost(self, connector, reason): + """If we get disconnected, reconnect to server.""" + connector.connect() + + def clientConnectionFailed(self, connector, reason): + print "connection failed:", reason + reactor.stop() + +class Options(usage.Options): + optParameters = [ + ['config', 'c', 'settings.ini', 'Configuration file.'], + ] + +class FSMonitor(): + def __init__(self, path, channel): + self._messages = [] + self._callid = None + self._bot = None + self._channel = channel + self._watch_path = filepath.FilePath(path) + + self._watchMask = ( inotify.IN_MODIFY + | inotify.IN_CREATE + | inotify.IN_DELETE + | inotify.IN_MOVED_FROM + | inotify.IN_MOVED_TO + ) + + + notifier = inotify.INotify() + notifier.startReading() + notifier.watch(self._watch_path, autoAdd=True, recursive=True, callbacks=[self.fsnotify], mask=self._watchMask) + + def setBot(self, bot): + self._bot = bot + + def humanReadableMask(self, mask): + flags_to_human = [ + (inotify.IN_MODIFY, 'geändert'), + (inotify.IN_CREATE, 'erstellt'), + (inotify.IN_DELETE, 'gelöscht'), + (inotify.IN_MOVED_FROM, 'umbenannt von'), + (inotify.IN_MOVED_TO, 'umbenannt nach') + ] + + """In Maske enthaltene Flags als String zusammenbauen""" + s = [] + for k, v in flags_to_human: + if k & mask: + s.append(v) + return s + + def fsnotify(self, ignored, filepath, mask): + """Actually called by the notifier in case of any event.""" + if self._callid != None and self._callid.active(): + self._callid.cancel() + path_segments = filepath.segmentsFrom(self._watch_path) + new_path = '/'.join(path_segments) + msg = "ftp> /%s (%s)" % (new_path, ', '.join(self.humanReadableMask(mask))) + if msg not in self._messages: + self._messages.append(msg) + self._callid = reactor.callLater(10.0, self.sendQueuedMessages) + + def sendQueuedMessages(self): + if self._bot == None: + print("No Bot given, dropping messages!") + return + if len(self._messages) > 3: + self._bot.msg(self._channel, "ftp> %i Events übersprungen. Letzter Event:" % (len(self._messages)-1)) + self._bot.msg(self._channel, self._messages[len(self._messages)-1]) + else: + for msg in self._messages: + self._bot.msg(self._channel, msg) + self._messages = [] + + +if __name__ == '__main__': + options = Options() + config = ConfigParser() + config.read([options['config']]) + + host = config.get('irc', 'host') + port = int(config.get('irc', 'port')) + channel = config.get('irc', 'channel') + nickname = config.get('irc', 'nickname') + realname = config.get('irc', 'realname') + path = config.get('fsmonitor', 'path') + + fsmon = FSMonitor(path, channel) + + # create factory protocol and application + f = MonitorBotFactory(nickname, channel, fsmon) + + # connect factory to this host and port + reactor.connectTCP(host, port, f) + + # run bot + reactor.run() diff --git a/monitor/__init__.py b/monitor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/monitor/bot.py b/monitor/bot.py deleted file mode 100644 index 6f02a03..0000000 --- a/monitor/bot.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- - -from twisted.internet import protocol -from twisted.python import log -from twisted.words.protocols import irc - - -class MonitorBot(irc.IRCClient): - def connectionMade(self): - """Called when a connection is made.""" - self.nickname = self.factory.nickname - self.realname = self.factory.realname - irc.IRCClient.connectionMade(self) - log.msg("connectionMade") - - def connectionLost(self, reason): - """Called when a connection is lost.""" - irc.IRCClient.connectionLost(self, reason) - log.msg("connectionLost {!r}".format(reason)) - - # callbacks for events - - def signedOn(self): - """Called when bot has successfully signed on to server.""" - log.msg("Signed on") - if self.nickname != self.factory.nickname: - log.msg('Your nickname was already occupied, actual nickname is "{}".'.format(self.nickname)) - self.join(self.factory.channel) - - def joined(self, channel): - """Called when the bot joins the channel.""" - log.msg("[{nick} has joined {channel}]".format(nick=self.nickname, channel=self.factory.channel,)) - - def privmsg(self, user, channel, msg): - """Called when the bot receives a message.""" - pass - """ - sendTo = None - prefix = '' - senderNick = user.split('!', 1)[0] - if channel == self.nickname: - # Reply back in the query / privmsg - sendTo = senderNick - elif msg.startswith(self.nickname): - # Reply back on the channel - sendTo = channel - prefix = senderNick + ': ' #Mark message so people know what is going on - else: - msg = msg.lower() - if msg in ['!hi']: - sendTo = channel - prefix = senderNick + ': ' - if sendTo: - reply = "Hello." - self.msg(sendTo, prefix + reply) - log.msg( - "sent message to {receiver}, triggered by {sender}:\n\t{reply}" - .format(receiver=sendTo, sender=senderNick, reply=reply) - ) - """ - -class MonitorBotFactory(protocol.ClientFactory): - protocol = MonitorBot - - def __init__(self, channel, nickname, realname): - """Initialize the bot factory with our settings.""" - self.channel = channel - self.nickname = nickname - self.realname = realname - - def clientConnectionLost(self, connector, reason): - """If we get disconnected, reconnect to server.""" - connector.connect() - - def clientConnectionFailed(self, connector, reason): - print "connection failed:", reason - reactor.stop() diff --git a/settings.ini.example b/settings.ini.example index 7135b12..bb5e8ee 100644 --- a/settings.ini.example +++ b/settings.ini.example @@ -1,5 +1,6 @@ [irc] -endpoint = tcp:host=irc.euirc.net:port=6667 +host = irc.euirc.net +port = 6667 nickName = FDS-kun realName = bot: provides tracking of an ftp folder channel = #JPT diff --git a/twisted/plugins/monitorbot_plugin.py b/twisted/plugins/monitorbot_plugin.py deleted file mode 100644 index 15e5c07..0000000 --- a/twisted/plugins/monitorbot_plugin.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- - -from ConfigParser import ConfigParser - -from twisted.application.service import IServiceMaker, Service -from twisted.internet.endpoints import clientFromString -from twisted.plugin import IPlugin -from twisted.python import usage, log -from zope.interface import implementer - -from twisted.internet import inotify -from twisted.python import filepath - -from monitor.bot import MonitorBotFactory - -class MonitorBotService(Service): - _bot = None - - def __init__(self, endpoint, channel, nickname, realname, path): - self._endpoint = endpoint - self._channel = channel - self._nickname = nickname - self._realname = realname - self._watch_path = filepath.FilePath(path) - self._messages = [] - self._callid = None - - def startService(self): - """Construct a client & connect to server.""" - from twisted.internet import reactor - - """Define callbacks.""" - def connected(bot): - self._bot = bot - - def failure(err): - log.err(err, _why='Could not connect to specified server.') - reactor.stop() - - client = clientFromString(reactor, self._endpoint) - factory = MonitorBotFactory( - self._channel, - self._nickname, - self._realname - ) - - def humanReadableMask(mask): - flags_to_human = [ - (inotify.IN_MODIFY, 'geändert'), - (inotify.IN_CREATE, 'erstellt'), - (inotify.IN_DELETE, 'gelöscht'), - (inotify.IN_MOVED_FROM, 'umbenannt von'), - (inotify.IN_MOVED_TO, 'umbenannt nach') - ] - - s = [] - for k, v in flags_to_human: - if k & mask: - s.append(v) - return s - - def fsnotify(ignored, filepath, mask): - if self._callid != None and self._callid.active(): - self._callid.cancel() - path_segments = filepath.segmentsFrom(self._watch_path) - new_path = '/'.join(path_segments) - msg = "ftp> /%s (%s)" % (new_path, ', '.join(humanReadableMask(mask))) - if msg not in self._messages: - self._messages.append(msg) - self._callid = reactor.callLater(10.0, sendQueuedMessages) - - def sendQueuedMessages(): - if len(self._messages) > 3: - self._bot.msg(self._channel, "ftp> %i Events übersprungen. Letzter Event:" % (len(self._messages)-1)) - self._bot.msg(self._channel, self._messages[len(self._messages)-1]) - else: - for msg in self._messages: - self._bot.msg(self._channel, msg) - self._messages = [] - - watchMask = ( inotify.IN_MODIFY - | inotify.IN_CREATE - | inotify.IN_DELETE - | inotify.IN_MOVED_FROM - | inotify.IN_MOVED_TO - ) - - notifier = inotify.INotify() - notifier.startReading() - notifier.watch(self._watch_path, autoAdd=True, recursive=True, callbacks=[fsnotify], mask=watchMask) - - """Attach defined callbacks.""" - return client.connect(factory).addCallbacks(connected, failure) - - def stopService(self): - """Disconnect.""" - if self._bot and self._bot.transport.connected: - self._bot.transport.loseConnection() - - -class Options(usage.Options): - optParameters = [ - ['config', 'c', 'settings.ini', 'Configuration file.'], - ] - - -@implementer(IServiceMaker, IPlugin) -class BotServiceMaker(object): - tapname = "monitorbot" - description = "IRC bot that provides verbose monitoring of an fs path." - options = Options - - def makeService(self, options): - """Read the config and construct the monitorbot service.""" - config = ConfigParser() - config.read([options['config']]) - - return MonitorBotService( - endpoint=config.get('irc', 'endpoint'), - channel=config.get('irc', 'channel'), - nickname=config.get('irc', 'nickname'), - realname=config.get('irc', 'realname'), - path=config.get('fsmonitor', 'path'), - ) - -# Now construct an object which *provides* the relevant interfaces -# The name of this variable is irrelevant, as long as there is *some* -# name bound to a provider of IPlugin and IServiceMaker. - -serviceMaker = BotServiceMaker()