RelayNext: yet another crappy antiflood mechanism

Also, catch errors in outFilter so they don't block all of the bot's output.
This commit is contained in:
GLolol 2015-01-15 17:31:38 -08:00
parent d0ff6c9a16
commit f62c6eef73
2 changed files with 92 additions and 34 deletions

View File

@ -61,6 +61,20 @@ conf.registerChannelValue(RelayNext, 'noHighlight',
registry.Boolean(False, _("""Determines whether the bot should prefix nicks
with a hyphen (-) to prevent excess highlights (in PRIVMSGs and actions).""")))
conf.registerGroup(RelayNext, 'antiflood')
conf.registerChannelValue(RelayNext.antiflood, 'enable',
registry.Boolean(False, _("""Determines whether flood prevention should be enabled
for the given channel.""")))
conf.registerChannelValue(RelayNext.antiflood, 'seconds',
registry.PositiveInteger(20, _("""Determines how many seconds messages should be queued
for flood protection.""")))
conf.registerChannelValue(RelayNext.antiflood, 'maximum',
registry.PositiveInteger(15, _("""Determines the maximum amount of incoming messages
the bot will allow from a relay channel before flood protection is triggered.""")))
conf.registerChannelValue(RelayNext.antiflood, 'timeout',
registry.PositiveInteger(60, _("""Determines the amount of time in seconds the bot should
block messages if flood protection is triggered.""")))
conf.registerGroup(RelayNext, 'events')
_events = ('quit', 'join', 'part', 'nick', 'mode', 'kick')

View File

@ -31,6 +31,7 @@
from copy import deepcopy
import pickle
import re
import traceback
import supybot.world as world
import supybot.irclib as irclib
@ -41,6 +42,7 @@ from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
from supybot.utils.structures import TimeoutQueue
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('RelayNext')
@ -91,6 +93,10 @@ class RelayNext(callbacks.Plugin):
self.ircstates = {}
self.lastmsg = {}
# This part facilitates flood protection
self.msgcounters = {}
self.floodTriggered = False
self.db = {}
self.loadDB()
world.flushers.append(self.exportDB)
@ -144,7 +150,7 @@ class RelayNext(callbacks.Plugin):
results.append(cn[0])
return results
def _format(self, irc, msg):
def _format(self, irc, msg, announcement=False):
s = ''
nick = msg.nick
userhost = ''
@ -166,39 +172,48 @@ class RelayNext(callbacks.Plugin):
userhost = ' (%s)' % msg.prefix.split('!', 1)[1]
except:
pass
if msg.command == 'NICK':
newnick = msg.args[0]
if color:
newnick = self.simpleHash(newnick)
s = '- %s is now known as %s' % (nick, newnick)
elif msg.command == 'PRIVMSG':
text = msg.args[1]
if re.match('^\x01ACTION .*\x01$', text):
text = text[8:-1]
s = '* %s %s' % (nick, text)
else:
s = '<%s> %s' % (nick, msg.args[1])
elif msg.command == 'JOIN':
s = '- %s%s has joined %s' % (nick, userhost, channel)
elif msg.command == 'PART':
# Part message isn't a required field and can be empty sometimes
try:
partmsg = ' (%s)' % msg.args[1]
except:
partmsg = ''
s = '- %s%s has left %s%s' % (nick, userhost, channel, partmsg)
elif msg.command == 'QUIT':
s = '- %s has quit (%s)' % (nick, msg.args[0])
elif msg.command == 'MODE':
modes = ' '.join(msg.args[1:])
s = '- %s%s set mode %s on %s' % (nick, userhost, modes, channel)
if announcement:
# Announcements use a special syntax
s = '*** %s' % announcement
else:
if msg.command == 'NICK':
newnick = msg.args[0]
if color:
newnick = self.simpleHash(newnick)
s = '- %s is now known as %s' % (nick, newnick)
elif msg.command == 'PRIVMSG':
text = msg.args[1]
if re.match('^\x01ACTION .*\x01$', text):
text = text[8:-1]
s = '* %s %s' % (nick, text)
else:
s = '<%s> %s' % (nick, msg.args[1])
elif msg.command == 'JOIN':
s = '- %s%s has joined %s' % (nick, userhost, channel)
elif msg.command == 'PART':
# Part message isn't a required field and can be empty sometimes
try:
partmsg = ' (%s)' % msg.args[1]
except:
partmsg = ''
s = '- %s%s has left %s%s' % (nick, userhost, channel, partmsg)
elif msg.command == 'QUIT':
s = '- %s has quit (%s)' % (nick, msg.args[0])
elif msg.command == 'MODE':
modes = ' '.join(msg.args[1:])
s = '- %s%s set mode %s on %s' % (nick, userhost, modes, channel)
if s: # Add the network name and some final touch-ups
s = "\x02[%s]\x02 %s" % (netname, s)
s = s.replace("- -", "-", 1)
return s
def checkFlood(self, channel, source, command):
maximum = self.registryValue("antiflood.maximum", channel)
enabled = self.registryValue("antiflood.enable", channel)
if enabled and len(self.msgcounters[(source, command)]) > maximum:
return True
def relay(self, irc, msg, channel=None):
channel = channel or msg.args[0]
# Get the source channel
@ -206,6 +221,29 @@ class RelayNext(callbacks.Plugin):
source = source.lower()
out_s = self._format(irc, msg)
if out_s:
### Begin Flood checking clause
timeout = self.registryValue("antiflood.timeout", channel)
seconds = self.registryValue("antiflood.seconds", channel)
maximum = self.registryValue("antiflood.maximum", channel)
try:
self.msgcounters[(source, msg.command)].enqueue(msg.prefix)
except KeyError:
self.msgcounters[(source, msg.command)] = TimeoutQueue(seconds)
if self.checkFlood(channel, source, msg.command):
self.log.debug("RelayNext (%s): message from %s blocked by "
"flood preotection.", irc.network, channel)
if self.floodTriggered:
return
c = msg.command.lower()
e = format("Flood detected on %s (%s %ss/%s seconds), "
"not relaying %ss for %s seconds!", channel,
maximum, c, seconds, c, timeout)
out_s = self._format(irc, msg, announcement=e)
self.log.info("RelayNext (%s): %s", irc.network, e)
self.floodTriggered = True
else:
self.floodTriggered = False
### End Flood checking clause
for relay in self.db.values():
if source in relay: # If our channel is in a relay
# Remove ourselves so we don't get duplicated messages
@ -255,12 +293,18 @@ class RelayNext(callbacks.Plugin):
def outFilter(self, irc, msg):
# Catch our own messages and send them into the relay (this is
# useful because Supybot is often a multi-purpose bot!)
if msg.command == 'PRIVMSG' and not msg.relayedMsg:
if msg.args[0] in self._getAllRelaysForNetwork(irc):
new_msg = deepcopy(msg)
new_msg.nick = irc.nick
self.relay(irc, new_msg, channel=msg.args[0])
return msg
try:
if msg.command == 'PRIVMSG' and not msg.relayedMsg:
if msg.args[0] in self._getAllRelaysForNetwork(irc):
new_msg = deepcopy(msg)
new_msg.nick = irc.nick
self.relay(irc, new_msg, channel=msg.args[0])
except Exception as e:
# We want to log errors, but not block the bot's output
traceback.print_exc()
log.error(str(e))
finally:
return msg
### User commands