[TASK] Huge rewrite. Now with real reconnect support.
This commit is contained in:
parent
33184fc612
commit
5be9cd4eb9
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
||||||
run:
|
run:
|
||||||
twistd -n monitorbot
|
python fdskun.py
|
||||||
install:
|
install:
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
|
|
@ -1,5 +1,6 @@
|
||||||
[irc]
|
[irc]
|
||||||
endpoint = tcp:host=irc.euirc.net:port=6667
|
host = irc.euirc.net
|
||||||
|
port = 6667
|
||||||
nickName = FDS-kun
|
nickName = FDS-kun
|
||||||
realName = bot: provides tracking of an ftp folder
|
realName = bot: provides tracking of an ftp folder
|
||||||
channel = #JPT
|
channel = #JPT
|
||||||
|
|
|
@ -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()
|
|
Loading…
Reference in New Issue