modified a lot how channel's protection are handled and repeat / massRepeat behaviour

This commit is contained in:
Nicolas Coevoet 2014-01-17 14:38:56 +01:00
parent e0b3761f43
commit c2b4200eae

247
plugin.py
View File

@ -87,7 +87,6 @@ def matchHostmask (pattern,n):
cache[host] = None cache[host] = None
except: except:
cache[host] = None cache[host] = None
log.debug('checking %s / %s' % (pattern,n))
if n.ip != None and ircutils.hostmaskPatternEqual(pattern,'%s!%s@%s' % (nick,ident,n.ip)): if n.ip != None and ircutils.hostmaskPatternEqual(pattern,'%s!%s@%s' % (nick,ident,n.ip)):
return '%s!%s@%s' % (nick,ident,n.ip) return '%s!%s@%s' % (nick,ident,n.ip)
if ircutils.hostmaskPatternEqual(pattern,n.prefix): if ircutils.hostmaskPatternEqual(pattern,n.prefix):
@ -269,69 +268,6 @@ def floatToGMT (t):
return None return None
return time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(f)) return time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(f))
class SpamQueue(object):
timeout = 0
def __init__(self, timeout=None, queues=None):
if timeout is not None:
self.timeout = timeout
if queues is None:
queues = ircutils.IrcDict()
self.queues = queues
def __repr__(self):
return 'SpamQueue(timeout=%r,queues=%s)' % (self.timeout,repr(self.queues))
def reset (self,data):
q = self._getQueue(data,insert=False)
if q is not None:
q.reset()
key = self.key(data)
self.queues[key] = q
def key (self,data):
return data[0]
def getTimeout(self):
if isinstance(self.timeout, collections.Callable):
return self.timeout()
else:
return self.timeout
def _getQueue(self,data,insert=True):
try:
return self.queues[self.key(data)]
except KeyError:
if insert:
getTimeout = lambda : self.getTimeout()
q = utils.structures.TimeoutQueue(getTimeout)
self.queues[self.key(data)] = q
return q
else:
return None
def enqueue(self,data,what=None):
if what is None:
what = data
q = self._getQueue(data)
q.enqueue(what)
def len (self,data):
q = self._getQueue(data,insert=False)
if q is not None:
return len(q)
else:
return 0
def has (self,data,what):
q = self._getQueue(data,insert=False)
if q is not None:
if what is None:
what = data
for elt in q:
if elt == what:
return True
return False
class Ircd (object): class Ircd (object):
# define an ircd, keeps Chan and Nick items # define an ircd, keeps Chan and Nick items
def __init__(self,irc,logsSize): def __init__(self,irc,logsSize):
@ -853,12 +789,11 @@ class Chan (object):
# now stuff here is related to protection # now stuff here is related to protection
self.spam = ircutils.IrcDict() self.spam = ircutils.IrcDict()
self.repeatLogs = ircutils.IrcDict() self.repeatLogs = ircutils.IrcDict()
self.massPattern = ircutils.IrcDict()
self.nicks = ircutils.IrcDict() self.nicks = ircutils.IrcDict()
def isWrong (self,pattern): def isWrong (self,pattern):
if 'bad' in self.spam: if 'bad' in self.spam and pattern in self.spam['bad']:
if self.spam['bad'].len([pattern]) > 0: if len(self.spam['bad'][pattern]) > 0:
return True return True
return False return False
@ -1372,23 +1307,16 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
remove = wrap(remove,['op','nickInChannel',additional('text')]) remove = wrap(remove,['op','nickInChannel',additional('text')])
def match (self,irc,msg,args,channel,prefix): def match (self,irc,msg,args,channel,prefix):
"""[<channel>] <nick|pattern> """[<channel>] <nick>
returns active mode that targets pattern or nick given""" returns active mode that targets nick given, nick must be in a channel shared by with the bot"""
i = self.getIrc(irc) i = self.getIrc(irc)
n = None n = None
if prefix in i.nicks: if prefix in i.nicks:
n = self.getNick(irc,prefix) n = self.getNick(irc,prefix)
else: else:
n = Nick(0) irc.reply('unknow nick')
if prefix.find('#') != -1: return
a = prefix.split('#')
username = a[1]
prefix = a[0]
n.setPrefix(prefix)
n.setUsername(username)
else:
n.setPrefix(prefix)
results = i.against(irc,channel,n,msg.prefix,self.getDb(irc.network)) results = i.against(irc,channel,n,msg.prefix,self.getDb(irc.network))
if len(results): if len(results):
irc.reply(' '.join(results), private=True) irc.reply(' '.join(results), private=True)
@ -2089,6 +2017,9 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
del chan.nicks[nick] del chan.nicks[nick]
if best in chan.repeatLogs: if best in chan.repeatLogs:
del chan.repeatLogs[best] del chan.repeatLogs[best]
for k in chan.spam:
if best in chan.spam[k]:
del chan.spam[k][best]
schedule.addEvent(nrm,time.time()+self.registryValue('cycleLife')+10) schedule.addEvent(nrm,time.time()+self.registryValue('cycleLife')+10)
def doQuit (self,irc,msg): def doQuit (self,irc,msg):
@ -2300,45 +2231,30 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
n.addLog(channel,'NOTICE | %s' % text) n.addLog(channel,'NOTICE | %s' % text)
isVip = self._isVip(irc,channel,n) isVip = self._isVip(irc,channel,n)
if not isVip: if not isVip:
isPattern = False isNotice = self._isSomething(irc,channel,best,'notice')
for pattern in chan.massPattern: isMass = self._isMassRepeat(irc,channel,text)
if self._strcompare(chan.massPattern[pattern],text) >= self.registryValue('massRepeatPercent',channel=channel): isBad = False
kind = 'massRepeat' if isMass:
mode = self.registryValue('%sMode' % kind,channel=channel) kind = 'massRepeat'
duration = self.registryValue('%sDuration' % kind,channel=channel) mode = self.registryValue('%sMode' % kind,channel=channel)
comment = self.registryValue('%sComment' % kind,channel=channel) duration = self.registryValue('%sDuration' % kind,channel=channel)
self._act(irc,channel,mode,best,duration,comment) comment = self.registryValue('%sComment' % kind,channel=channel)
self._isBad(irc,channel,best) # increment bad self._act(irc,channel,mode,best,duration,comment)
self.forceTickle = True self._isBad(irc,channel,best)
isPattern = True self.forceTickle = True
if not isPattern: if isNotice:
isNotice = self._isSomething(irc,channel,best,'notice') isBad = self._isSomething(irc,channel,best,'bad')
isMass = False if isNotice or isBad:
isBad = False kind = None
if len(text) >= self.registryValue('massRepeatChars',channel=channel): if isBad:
isMass = self._isSomething(irc,channel,text,'massRepeat') kind = 'bad'
if isMass: else:
if not text in chan.massPattern: kind = 'notice'
chan.massPattern[text] = text mode = self.registryValue('%sMode' % kind,channel=channel)
def unpattern (): duration = self.registryValue('%sDuration' % kind,channel=channel)
if text in chan.massPattern: comment = self.registryValue('%sComment' % kind,channel=channel)
del chan.massPattern[text] self._act(irc,channel,mode,best,duration,comment)
schedule.addEvent(unpattern,time.time()+self.registryValue('massRepeatDuration',channel=channel)) self.forceTickle = True
if isNotice:
isBad = self._isSomething(irc,channel,best,'bad')
if isNotice or isBad:
kind = None
if isBad:
kind = 'bad'
else:
kind = 'notice'
mode = self.registryValue('%sMode' % kind,channel=channel)
if len(mode) > 1:
mode = mode[0]
duration = self.registryValue('%sDuration' % kind,channel=channel)
comment = self.registryValue('%sComment' % kind,channel=channel)
self._act(irc,channel,mode,best,duration,comment)
self.forceTickle = True
if self.registryValue('announceNotice',channel=channel): if self.registryValue('announceNotice',channel=channel):
if not chan.isWrong(best): if not chan.isWrong(best):
self._logChan(irc,channel,'[%s] %s notice "%s"' % (channel,msg.prefix,text)) self._logChan(irc,channel,'[%s] %s notice "%s"' % (channel,msg.prefix,text))
@ -2411,18 +2327,6 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
isVip = self._isVip(irc,channel,n) isVip = self._isVip(irc,channel,n)
# checking if message matchs living massRepeatPattern # checking if message matchs living massRepeatPattern
if not isVip: if not isVip:
for pattern in chan.massPattern:
if self._strcompare(chan.massPattern[pattern],text) >= self.registryValue('massRepeatPercent',channel=channel):
kind = 'massRepeat'
mode = self.registryValue('%sMode' % kind,channel=channel)
duration = self.registryValue('%sDuration' % kind,channel=channel)
comment = self.registryValue('%sComment' % kind,channel=channel)
self._act(irc,channel,mode,best,duration,comment)
self._isBad(irc,channel,best) # increment bad user count
self.forceTickle = True
# no needs to check others protection
continue
isCtcp = False isCtcp = False
if isCtcpMsg and not isAction: if isCtcpMsg and not isAction:
isCtcp = self._isSomething(irc,channel,best,'ctcp') isCtcp = self._isSomething(irc,channel,best,'ctcp')
@ -2431,16 +2335,15 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
isRepeat = self._isRepeat(irc,channel,best,text) isRepeat = self._isRepeat(irc,channel,best,text)
isHilight = self._isHilight(irc,channel,best,text) isHilight = self._isHilight(irc,channel,best,text)
isCap = self._isCap(irc,channel,best,text) isCap = self._isCap(irc,channel,best,text)
isMass = False isMass = self._isMassRepeat(irc,channel,text)
if len(text) >= self.registryValue('massRepeatChars',channel=channel): if isMass:
isMass = self._isSomething(irc,channel,text,'massRepeat') kind = 'massRepeat'
if isMass: mode = self.registryValue('%sMode' % kind,channel=channel)
if not text in chan.massPattern: duration = self.registryValue('%sDuration' % kind,channel=channel)
chan.massPattern[text] = text comment = self.registryValue('%sComment' % kind,channel=channel)
def unpattern (): self._act(irc,channel,mode,best,duration,comment)
if text in chan.massPattern: self._isBad(irc,channel,best)
del chan.massPattern[text] self.forceTickle = True
schedule.addEvent(unpattern,time.time()+self.registryValue('massRepeatDuration',channel=channel))
if isFlood or isHilight or isRepeat or isCap or isCtcp or isLowFlood: if isFlood or isHilight or isRepeat or isCap or isCtcp or isLowFlood:
isBad = self._isBad(irc,channel,best) isBad = self._isBad(irc,channel,best)
kind = None kind = None
@ -2756,13 +2659,14 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
return False return False
chan = self.getChan(irc,channel) chan = self.getChan(irc,channel)
life = self.registryValue('%sLife' % prop,channel=channel) life = self.registryValue('%sLife' % prop,channel=channel)
if not prop in chan.spam or chan.spam[prop].timeout != life: if not prop in chan.spam:
# reset queue if config has changed chan.spam[prop] = {}
chan.spam[prop] = SpamQueue(life) if not key in chan.spam[prop] or chan.spam[prop][key].timeout != life:
chan.spam[prop].enqueue([key]) chan.spam[prop][key] = utils.structures.TimeoutQueue(life)
if chan.spam[prop].len([key]) > limit: chan.spam[prop][key].enqueue(key)
if len(chan.spam[prop][key]) > limit:
log.debug('[%s] %s is detected as %s' % (channel,key,prop)) log.debug('[%s] %s is detected as %s' % (channel,key,prop))
chan.spam[prop].reset([key]) chan.spam[prop][key].reset()
return True return True
return False return False
@ -2795,31 +2699,43 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
return count > limit return count > limit
def _isRepeat(self,irc,channel,key,message): def _isRepeat(self,irc,channel,key,message):
limit = self.registryValue('repeatPermit',channel=channel) if self.registryValue('repeatPermit',channel=channel) < 0:
if limit < 0:
return False return False
chan = self.getChan(irc,channel) chan = self.getChan(irc,channel)
## needed to compare with previous text timeout = self.registryValue('repeatLife',channel=channel)
if limit == 0: if not key in chan.repeatLogs or chan.repeatLogs[key].timeout != timeout:
max = 2 chan.repeatLogs[key] = utils.structures.TimeoutQueue(timeout)
else:
max = limit*2
if not key in chan.repeatLogs:
chan.repeatLogs[key] = utils.structures.MaxLengthQueue(max)
logs = chan.repeatLogs[key] logs = chan.repeatLogs[key]
if max != logs.length:
logs = chan.repeatLogs[key] = utils.structures.MaxLengthQueue(max)
trigger = self.registryValue('repeatPercent',channel=channel) trigger = self.registryValue('repeatPercent',channel=channel)
result = False result = False
if len(logs) > 0: flag = False
flag = False for msg in logs:
for entry in logs: if self._strcompare(message,msg) >= trigger:
if self._strcompare(message,entry) >= trigger: flag = True
flag = True break
break if flag:
if flag: result = self._isSomething(irc,channel,key,'repeat')
result = self._isSomething(irc,channel,key,'repeat') chan.repeatLogs[key].enqueue(message)
logs.enqueue(message) return result
def _isMassRepeat(self,irc,channel,message):
if self.registryValue('massRepeatPermit',channel=channel) < 0 or len(message) < self.registryValue('massRepeatChars',channel=channel):
return False
chan = self.getChan(irc,channel)
life = self.registryValue('massRepeatLife',channel=channel)
if not channel in chan.repeatLogs or chan.repeatLogs[channel].timeout != life:
chan.repeatLogs[channel] = utils.structures.TimeoutQueue(life)
logs = chan.repeatLogs[channel]
trigger = self.registryValue('massRepeatPercent',channel=channel)
result = False
flag = False
for msg in logs:
if self._strcompare(message,msg) >= trigger:
flag = True
break
if flag:
result = self._isSomething(irc,channel,channel,'massRepeat')
chan.repeatLogs[channel].enqueue(message)
return result return result
def _isCap(self,irc,channel,key,message): def _isCap(self,irc,channel,key,message):
@ -2851,3 +2767,4 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
Class = ChanTracker Class = ChanTracker