[TASK] Huge rewrite. Now with real reconnect support.

This commit is contained in:
Jan Philipp Timme 2014-07-14 22:57:25 +02:00
parent 33184fc612
commit 5be9cd4eb9
6 changed files with 157 additions and 209 deletions

View File

@ -1,4 +1,4 @@
run:
twistd -n monitorbot
python fdskun.py
install:
pip install -r requirements.txt

154
fdskun.py Normal file
View File

@ -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()

View File

View File

@ -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()

View File

@ -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

View File

@ -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()