'kickOnMode' allows bot to kick when someone else sets a ban, 'clonePermit' can check clones joins in a channel, 'resolveIp' to True with supybot.debug.threadAllCommands to True should prevent bot being stuck due to socket/dns timeout

This commit is contained in:
Nicolas Coevoet 2016-01-05 12:22:43 +01:00
parent 5b2e162f70
commit fd99d11acb
2 changed files with 3709 additions and 3641 deletions

View File

@ -154,6 +154,10 @@ conf.registerChannelValue(ChanTracker, 'kickMode',
registry.CommaSeparatedListOfStrings(['b'], """bot will kick affected users when mode is triggered, registry.CommaSeparatedListOfStrings(['b'], """bot will kick affected users when mode is triggered,
use if with caution, if an op bans *!*@*, bot will kick everyone on the channel""")) use if with caution, if an op bans *!*@*, bot will kick everyone on the channel"""))
conf.registerChannelValue(ChanTracker, 'kickOnMode',
registry.Boolean(False, """bot will kick affected users when kickMode is triggered by someone,
use if with caution"""))
conf.registerChannelValue(ChanTracker, 'kickMax', conf.registerChannelValue(ChanTracker, 'kickMax',
registry.Integer(-1,"""if > 0, disable kick if affected users > kickMax, avoid to cleanup entire channel with ban like *!*@*""")) registry.Integer(-1,"""if > 0, disable kick if affected users > kickMax, avoid to cleanup entire channel with ban like *!*@*"""))
@ -192,6 +196,16 @@ conf.registerChannelValue(ChanTracker, 'skynet',
# related to channel's protection # related to channel's protection
#clone
conf.registerChannelValue(ChanTracker, 'clonePermit',
registry.Integer(-1,"""Number of messages allowed , -1 to disable"""))
conf.registerChannelValue(ChanTracker, 'cloneMode',
registry.String('d',"""mode used by the bot when clone detection is triggered"""))
conf.registerChannelValue(ChanTracker, 'cloneDuration',
registry.PositiveInteger(60,"""punishment duration in seconds"""))
conf.registerChannelValue(ChanTracker, 'cloneComment',
registry.String('clone detected',"""comment added on mode changes database, empty for no comment"""))
# flood detection settings # flood detection settings
conf.registerChannelValue(ChanTracker, 'floodPermit', conf.registerChannelValue(ChanTracker, 'floodPermit',
registry.Integer(-1,"""Number of messages allowed during floodLife, -1 to disable""")) registry.Integer(-1,"""Number of messages allowed during floodLife, -1 to disable"""))

106
plugin.py
View File

@ -76,37 +76,32 @@ mcidr = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}/\d{1,2}$')
m6cidr = re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}$') m6cidr = re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}$')
def matchHostmask (pattern,n,resolve): def matchHostmask (pattern,n,resolve):
# return the machted pattern for Nick # return the matched pattern for Nick
if n.prefix == None or not ircutils.isUserHostmask(n.prefix): if n.prefix == None or not ircutils.isUserHostmask(n.prefix):
return None return None
(nick,ident,host) = ircutils.splitHostmask(n.prefix) (nick,ident,host) = ircutils.splitHostmask(n.prefix)
if host.find('/') != -1: if host.find('/') != -1:
# cloaks
if host.startswith('gateway/web/freenode/ip.'): if host.startswith('gateway/web/freenode/ip.'):
n.ip = cache[n.prefix] = host.split('ip.')[1] n.ip = cache[n.prefix] = host.split('ip.')[1]
else: #elif resolve:
# trying to get ip #if not n.prefix in cache:
if host in cache: #try:
n.ip = cache[n.prefix] #r = socket.getaddrinfo(host,None)
else: #if r != None:
if n.ip != None: #u = {}
cache[n.prefix] = n.ip #L = []
elif resolve: #for item in r:
try: #if not item[4][0] in u:
r = socket.getaddrinfo(host,None) #u[item[4][0]] = item[4][0]
if r != None: #L.append(item[4][0])
u = {} #if len(L) == 1:
L = [] #n.setIp(L[0])
for item in r: #except:
if not item[4][0] in u: #t = ''
u[item[4][0]] = item[4][0] #if n.ip != None:
L.append(item[4][0]) #cache[n.prefix] = n.ip
if len(L) == 1: #else:
cache[n.prefix] = L[0] #cache[n.prefix] = host
n.setIp(L[0])
cache[n.prefix] = n.ip
except:
cache[n.prefix] = n.prefix
try: try:
if n.ip != None and pattern.find('@') != -1 and mcidr.match(pattern.split('@')[1]) and IPAddress(u'%s' % n.ip) in IPNetwork(u'%s' % pattern.split('@')[1]): if n.ip != None and pattern.find('@') != -1 and mcidr.match(pattern.split('@')[1]) and IPAddress(u'%s' % n.ip) in IPNetwork(u'%s' % pattern.split('@')[1]):
if ircutils.hostmaskPatternEqual('%s@*' % pattern.split('@')[0],'%s!%s@%s' % (nick,ident,n.ip)): if ircutils.hostmaskPatternEqual('%s@*' % pattern.split('@')[0],'%s!%s@%s' % (nick,ident,n.ip)):
@ -1202,6 +1197,34 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
if self.registryValue('announceNagInterval') > 0: if self.registryValue('announceNagInterval') > 0:
schedule.addEvent(self.checkNag,time.time()+self.registryValue('announceNagInterval')) schedule.addEvent(self.checkNag,time.time()+self.registryValue('announceNagInterval'))
def resolve (self,irc,msg,args,nick):
"""internaly used by the bot if resolveIp is True and usefull with supybot.debug.threadAllCommands to True"""
irc.noReply();
if msg.prefix == irc.prefix:
i = self.getIrc(irc)
n = self.getNick(irc,nick)
if n:
(nick,ident,host) = ircutils.splitHostmask(n.prefix)
if not n.prefix in cache and host.find('/') == -1:
try:
r = socket.getaddrinfo(host,None)
if r != None:
u = {}
L = []
for item in r:
if not item[4][0] in u:
u[item[4][0]] = item[4][0]
L.append(item[4][0])
if len(L) == 1:
n.setIp(L[0])
except:
t = ''
if n.ip != None:
cache[n.prefix] = n.ip
else:
cache[n.prefix] = host
resolve = wrap(resolve,['text'])
def checkNag (self): def checkNag (self):
if world: if world:
if world.ircs: if world.ircs:
@ -1934,6 +1957,7 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
i = self._ircs[irc.network] = Ircd (irc,self.registryValue('logsSize')) i = self._ircs[irc.network] = Ircd (irc,self.registryValue('logsSize'))
# restore CAP, if needed, needed to track account (account-notify) ang gecos (extended-join) # restore CAP, if needed, needed to track account (account-notify) ang gecos (extended-join)
# see config of this plugin # see config of this plugin
if not hasattr(irc.state, 'capabilities_ack'):
irc.queueMsg(ircmsgs.IrcMsg('CAP LS')) irc.queueMsg(ircmsgs.IrcMsg('CAP LS'))
return self._ircs[irc.network] return self._ircs[irc.network]
@ -2352,6 +2376,13 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
self.forceTickle = True self.forceTickle = True
self._tickle(irc) self._tickle(irc)
return return
if self.registryValue('resolveIp') and msg.prefix.split('@')[1].find('/') == -1:
mp = ircmsgs.IrcMsg('',command='PRIVMSG')
mp.prefix = irc.prefix
mp.args = [irc.nick]
command = 'resolve %s' % msg.nick
tokens = callbacks.tokenize(command)
self.Proxy(irc.irc, mp, tokens)
for channel in channels: for channel in channels:
if ircutils.isChannel(channel) and channel in irc.state.channels: if ircutils.isChannel(channel) and channel in irc.state.channels:
best = getBestPattern(n,irc,self.registryValue('useIpForGateway',channel=channel),self.registryValue('resolveIp'))[0] best = getBestPattern(n,irc,self.registryValue('useIpForGateway',channel=channel),self.registryValue('resolveIp'))[0]
@ -2419,6 +2450,22 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
chan.action.enqueue(ircmsgs.IrcMsg('MODE %s %s' % (channel,self.registryValue('massJoinUnMode',channel=channel)))) chan.action.enqueue(ircmsgs.IrcMsg('MODE %s %s' % (channel,self.registryValue('massJoinUnMode',channel=channel))))
schedule.addEvent(unAttack,float(time.time()+self.registryValue('massJoinDuration',channel=channel))) schedule.addEvent(unAttack,float(time.time()+self.registryValue('massJoinDuration',channel=channel)))
self.forceTickle = True self.forceTickle = True
if not banned:
permit = self.registryValue('clonePermit',channel=channel)
if permit > -1:
clones = []
for nick in list(irc.state.channels[channel].users):
n = self.getNick(irc,nick)
m = match(best,n,irc,self.registryValue('resolveIp'))
if m:
clones.append(nick)
if len(clones) > permit:
if self.registryValue('cloneMode',channel=channel) == 'd':
self._logChan(irc,channel,'[%s] clones (%s) detected (%s)' % (ircutils.bold(channel),best,', '.join(clones)))
else:
r = self.getIrcdMode(irc,self.registryValue('cloneMode',channel=channel),best)
self._act(irc,channel,r[0],r[1],self.registryValue('cloneDuration',channel=channel),self.registryValue('cloneComment',channel))
self.forceTickle = True
if msg.nick == irc.nick: if msg.nick == irc.nick:
self.forceTickle = True self.forceTickle = True
@ -2736,8 +2783,15 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
if len(cap) != 1 and cap in i.caps['LS'] and not cap in i.caps['LIST']: if len(cap) != 1 and cap in i.caps['LS'] and not cap in i.caps['LIST']:
r.append(cap) r.append(cap)
if len(r): if len(r):
if not hasattr(irc.state, 'capabilities_ack'):
# apply missed caps # apply missed caps
irc.queueMsg(ircmsgs.IrcMsg('CAP REQ :%s' % ' '.join(r))) irc.queueMsg(ircmsgs.IrcMsg('CAP REQ :%s' % ' '.join(r)))
else:
r = []
self.log.debug('CAP :: %s :: %s :: %s' % (irc.state.capabilities_ack,irc.state.capabilities_ls,irc.state.capabilities_nak))
for cap in CAPS:
if len(cap) and cap in i.caps['LS'] and not cap in irc.state.capabilities_ack:
r.append(cap)
elif command == 'ACK' or command == 'NAK': elif command == 'ACK' or command == 'NAK':
# retrieve current caps # retrieve current caps
irc.queueMsg(ircmsgs.IrcMsg('CAP LIST')) irc.queueMsg(ircmsgs.IrcMsg('CAP LIST'))
@ -3208,7 +3262,7 @@ class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
tolift.append(active) tolift.append(active)
kicked = False kicked = False
if m in self.registryValue('kickMode',channel=channel) and not value.startswith('m:'): # and not value.startswith(self.getIrcdExtbans(irc)) works for unreal if m in self.registryValue('kickMode',channel=channel) and not value.startswith('m:'): # and not value.startswith(self.getIrcdExtbans(irc)) works for unreal
if msg.nick == irc.nick or msg.nick == 'ChanServ': if msg.nick == irc.nick or msg.nick == 'ChanServ' or self.registryValue('kickOnMode',channel=channel):
if self.registryValue('kickMax',channel=channel) < 0 or len(item.affects) < self.registryValue('kickMax',channel=channel): if self.registryValue('kickMax',channel=channel) < 0 or len(item.affects) < self.registryValue('kickMax',channel=channel):
if nick in irc.state.channels[channel].users and nick != irc.nick: if nick in irc.state.channels[channel].users and nick != irc.nick:
chan.action.enqueue(ircmsgs.kick(channel,nick,random.choice(self.registryValue('kickMessage',channel=channel)))) chan.action.enqueue(ircmsgs.kick(channel,nick,random.choice(self.registryValue('kickMessage',channel=channel))))