mirror of
https://github.com/ncoevoet/ChanTracker.git
synced 2025-04-25 20:41:19 -05:00
1773 lines
59 KiB
Python
1773 lines
59 KiB
Python
###
|
|
# Copyright (c) 2013, nicolas coevoet
|
|
# Copyright (c) 2010, Daniel Folkinshteyn - taken some ideas about threading database ( MessageParser )
|
|
# Copyright (c) 2004, Jeremiah Fincher - taken duration parser from plugin Time
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions, and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions, and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the author of this software nor the name of
|
|
# contributors to this software may be used to endorse or promote products
|
|
# derived from this software without specific prior written consent.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
###
|
|
|
|
import os
|
|
import time
|
|
import supybot.utils as utils
|
|
from supybot.commands import *
|
|
import supybot.commands as commands
|
|
import supybot.plugins as plugins
|
|
import supybot.ircutils as ircutils
|
|
import supybot.ircmsgs as ircmsgs
|
|
import supybot.callbacks as callbacks
|
|
import supybot.ircdb as ircdb
|
|
import supybot.log as log
|
|
from string import Template
|
|
from sets import Set
|
|
import socket
|
|
import re
|
|
import sqlite3
|
|
|
|
_isip4 = re.compile("\.".join(["([01]?\d\d?|2[0-4]\d|25[0-5])"]*4))
|
|
_isip6 = re.compile("(\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,6}\Z)|(\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\Z)|(\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\Z)|(\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\Z)|(\A([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\Z)|(\A([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4}){1,1}\Z)|(\A(([0-9a-f]{1,4}:){1,7}|:):\Z)|(\A:(:[0-9a-f]{1,4}){1,7}\Z)|(\A((([0-9a-f]{1,4}:){6})(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})\Z)|(\A(([0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})\Z)|(\A([0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,3}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,2}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,1}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A(([0-9a-f]{1,4}:){1,5}|:):(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A:(:[0-9a-f]{1,4}){1,5}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)")
|
|
|
|
def isip(s):
|
|
if _isip4.match(s) or _isip6.match(s):
|
|
return True
|
|
return False
|
|
|
|
def matchHostmask (pattern,n):
|
|
if n.prefix == None or not ircutils.isUserHostmask(n.prefix):
|
|
return None
|
|
(nick,ident,host) = ircutils.splitHostmask(n.prefix)
|
|
# TODO needs to implement CIDR masks
|
|
if host.find('/') != -1:
|
|
# cloaks
|
|
if n.ip == None and host.startswith('gateway/') and host.find('ip.') != -1:
|
|
n.setIp(host.split('ip.')[1])
|
|
else:
|
|
# trying to get ip
|
|
if n.ip == None and not isip(host):
|
|
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:
|
|
# when more than one ip is returned for the domain,
|
|
# don't use ip, otherwise it could not match
|
|
n.setIp(L[0])
|
|
else:
|
|
n.setIp(None)
|
|
except:
|
|
n.setIp(None)
|
|
if n.ip != None and ircutils.hostmaskPatternEqual(pattern,'%s!%s@%s' % (nick,ident,n.ip)):
|
|
return '%s!%s@%s' % (nick,ident,n.ip)
|
|
if ircutils.hostmaskPatternEqual(pattern,n.prefix):
|
|
return n.prefix
|
|
return None
|
|
|
|
def matchAccount (pattern,pat,negate,n):
|
|
if negate:
|
|
if len(pat):
|
|
log.error('%s unknown pattern' % pattern)
|
|
else:
|
|
if n.account == None:
|
|
return n.prefix
|
|
else:
|
|
if len(pat):
|
|
if n.account != None and ircutils.hostmaskPatternEqual('*!*@%s' % pat, '*!*@%s' % n.account):
|
|
return '$a:'+n.account
|
|
else:
|
|
if n.account != None:
|
|
return '$a:'+n.account
|
|
return None
|
|
|
|
def matchRealname (pattern,pat,negate,n):
|
|
if n.realname == None:
|
|
return None
|
|
if negate:
|
|
if len(pat):
|
|
if not ircutils.hostmaskPatternEqual('*!*@%s' % pat, '*!*@%s' % n.realname):
|
|
return '$r:'+n.realname
|
|
else:
|
|
if len(pat):
|
|
if ircutils.hostmaskPatternEqual('*!*@%s' % pat, '*!*@%s' % n.realname):
|
|
return '$r:'+n.realname
|
|
return None
|
|
|
|
def matchGecos (pattern,pat,negate,n):
|
|
if n.realname == None:
|
|
return None
|
|
tests = []
|
|
(nick,ident,host) = ircutils.splitHostmask(n.prefix)
|
|
tests.append(n.prefix)
|
|
if n.ip != None:
|
|
tests.append('%s!%s@%s' % (nick,ident,n.ip))
|
|
for test in tests:
|
|
test = '%s#%s' % (test,n.realname)
|
|
if negate:
|
|
if not ircutils.hostmaskPatternEqual(pat,test):
|
|
return test
|
|
else:
|
|
if ircutils.hostmaskPatternEqual(pat,test):
|
|
return test
|
|
return None
|
|
|
|
def match (pattern,n):
|
|
if pattern.startswith('$'):
|
|
p = pattern[1:]
|
|
negate = p[0] == '~'
|
|
if negate:
|
|
p = p[1:]
|
|
t = p[0]
|
|
p = p[1:]
|
|
if len(p):
|
|
# remove ':'
|
|
p = p[1:]
|
|
if t == 'a':
|
|
return matchAccount (pattern,p,negate,n)
|
|
elif t == 'r':
|
|
return matchRealname (pattern,p,negate,n)
|
|
elif t == 'x':
|
|
return matchGecos (pattern,p,negate,n)
|
|
else:
|
|
log.error('%s unknown pattern' % pattern)
|
|
else:
|
|
if ircutils.isUserHostmask(pattern):
|
|
return matchHostmask(pattern,n)
|
|
else:
|
|
if pattern.find('$'):
|
|
# channel forwards
|
|
pattern = pattern.split('$')[0]
|
|
if ircutils.isUserHostmask(pattern):
|
|
return matchHostmask(pattern,n)
|
|
else:
|
|
log.error('%s unknown pattern' % pattern)
|
|
else:
|
|
log.error('%s unknown pattern' % pattern)
|
|
return None
|
|
|
|
def getBestPattern (n):
|
|
# return best pattern for a given Nick
|
|
results = []
|
|
(nick,ident,host) = ircutils.splitHostmask(n.prefix)
|
|
if ident.startswith('~'):
|
|
ident = '*'
|
|
else:
|
|
if host.startswith('gateway/web/') and host.find('ip.') != -1:
|
|
# uneeded to keep the hexip, otherwise keep identd
|
|
ident = '*'
|
|
if host.startswith('gateway/tor-sasl/'):
|
|
# don't trust tor
|
|
ident = '*'
|
|
if n.ip != None:
|
|
if n.ip.find('::') > 4:
|
|
# large ipv6
|
|
a = n.ip.split(':')
|
|
m = a[0]+':'+a[1]+':'+a[2]+':'+a[3]+':*'
|
|
results.append('*!%s@%s' % (ident,m))
|
|
else:
|
|
results.append('*!%s@%s' % (ident,n.ip))
|
|
if host.find('/') != -1:
|
|
# cloaks
|
|
if host.startswith('gateway/'):
|
|
h = host.split('/')
|
|
# gateway/type/(domain|account) ?/random
|
|
p = ''
|
|
if len(h) > 3:
|
|
p = '/*'
|
|
h = h[:2]
|
|
host = '%s%s' % ('/'.join(h),p)
|
|
elif host.startswith('nat/'):
|
|
h = host.replace('nat/','')
|
|
if h.find('/') != -1:
|
|
host = 'nat/%s/*' % h.split('/')[0]
|
|
if not ircutils.userFromHostmask(n.prefix).startswith('~'):
|
|
ident = ircutils.userFromHostmask(n.prefix)
|
|
if host.find('gateway/') != -1 and host.find('/x-'):
|
|
# uneeded random chars
|
|
host = '%s/*' % host.split('/x-')[0]
|
|
results.append('*!%s@%s' % (ident,host))
|
|
if n.account:
|
|
results.append('$a:%s' % n.account)
|
|
if n.realname:
|
|
results.append('$r:%s' % n.realname.replace(' ','?'))
|
|
return results
|
|
|
|
def clearExtendedBanPattern (pattern):
|
|
if pattern.startswith('$'):
|
|
pattern = pattern[1:]
|
|
if pattern.startswith('~'):
|
|
pattern = pattern[1:]
|
|
pattern = pattern[1:]
|
|
if pattern.startswith(':'):
|
|
pattern = pattern[1:]
|
|
return pattern
|
|
|
|
def floatToGMT (t):
|
|
f = None
|
|
try:
|
|
f = float(t)
|
|
except:
|
|
return None
|
|
return time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(f))
|
|
|
|
class Ircd (object):
|
|
# define an ircd, keeps Chan and Nick items
|
|
def __init__(self,irc,logsSize):
|
|
object.__init__(self)
|
|
self.irc = irc
|
|
self.name = irc.network
|
|
self.channels = {}
|
|
self.nicks = {}
|
|
self.caps = {}
|
|
# contains IrcMsg
|
|
self.queue = utils.structures.smallqueue()
|
|
self.logsSize = logsSize
|
|
|
|
def getItem (self,irc,uid):
|
|
# return active item
|
|
if not irc or not uid:
|
|
return None
|
|
for channel in self.channels:
|
|
chan = self.getChan(irc,channel)
|
|
items = chan.getItems()
|
|
for type in items:
|
|
for value in items[type]:
|
|
item = items[type][value]
|
|
if item.uid == uid:
|
|
return item
|
|
return None
|
|
|
|
def info (self,irc,uid,prefix,db):
|
|
# return mode changes summary
|
|
if not uid or not prefix:
|
|
return []
|
|
c = db.cursor()
|
|
c.execute("""SELECT channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if not len(L):
|
|
c.close()
|
|
return []
|
|
(channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = L[0]
|
|
if not ircdb.checkCapability(prefix, '%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
c.close()
|
|
return []
|
|
results = []
|
|
current = time.time()
|
|
results.append('[%s][%s], %s sets +%s %s' % (channel,floatToGMT(begin_at),oper,kind,mask))
|
|
if not removed_at:
|
|
if begin_at == end_at:
|
|
results.append('setted forever')
|
|
else:
|
|
results.append('setted for %s' % utils.timeElapsed(end_at-begin_at))
|
|
results.append('with %s more' % utils.timeElapsed(end_at-current))
|
|
results.append('ends at [%s]' % floatToGMT(end_at))
|
|
else:
|
|
results.append('was active %s and ended on [%s]' % (utils.timeElapsed(removed_at-begin_at),floatToGMT(removed_at)))
|
|
results.append('was setted for %s' % utils.timeElapsed(end_at-begin_at))
|
|
c.execute("""SELECT oper, comment, at FROM comments WHERE ban_id=? ORDER BY at DESC""",(uid,))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
for com in L:
|
|
(oper,comment,at) = com
|
|
results.append('"%s" by %s on %s' % (comment,oper,floatToGMT(at)))
|
|
c.execute("""SELECT ban_id,full FROM nicks WHERE ban_id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
results.append('targeted:')
|
|
for affected in L:
|
|
(uid,mask) = affected
|
|
results.append('- %s' % mask)
|
|
c.close()
|
|
return results
|
|
|
|
def pending(self,irc,channel,mode,prefix,pattern,db):
|
|
# returns active items for a channel mode
|
|
if not channel or not mode or not prefix:
|
|
return []
|
|
if not ircdb.checkCapability(prefix, '%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
return []
|
|
chan = self.getChan(irc,channel)
|
|
items = chan.getItemsFor(mode)
|
|
results = []
|
|
r = []
|
|
c = db.cursor()
|
|
if len(items):
|
|
for item in items:
|
|
item = items[item]
|
|
r.append([item.uid,item.mode,item.value,item.by,item.when,item.expire])
|
|
r.sort(reverse=True)
|
|
if len(r):
|
|
for item in r:
|
|
(uid,mode,value,by,when,expire) = item
|
|
if pattern != None and not ircutils.hostmaskPatternEqual(pattern,by):
|
|
continue
|
|
c.execute("""SELECT oper, comment, at FROM comments WHERE ban_id=? ORDER BY at DESC LIMIT 1""",(uid,))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
(oper,comment,at) = L[0]
|
|
message = '"%s" by %s' % (comment,oper)
|
|
else:
|
|
message = ''
|
|
if expire and expire != when:
|
|
results.append('[#%s +%s %s by %s expires at %s] %s' % (uid,mode,value,by,floatToGMT(expire),message))
|
|
else:
|
|
results.append('[#%s +%s %s by %s setted on %s] %s' % (uid,mode,value,by,floatToGMT(when),message))
|
|
c.close()
|
|
return results
|
|
|
|
def log (self,irc,uid,prefix,db):
|
|
# return log of affected users by a mode change
|
|
if not uid or not prefix:
|
|
return []
|
|
c = db.cursor()
|
|
c.execute("""SELECT channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if not len(L):
|
|
c.close()
|
|
return []
|
|
(channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = L[0]
|
|
if not ircdb.checkCapability(prefix, '%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
c.close()
|
|
return []
|
|
results = []
|
|
c.execute("""SELECT full,log FROM nicks WHERE ban_id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
for item in L:
|
|
(full,log) = item
|
|
results.append('for %s' % full)
|
|
for line in log.split('\n'):
|
|
results.append(line)
|
|
else:
|
|
results.append('no log found')
|
|
c.close()
|
|
return results
|
|
|
|
def add (self,irc,channel,mode,value,seconds,prefix,db,logFunction):
|
|
# add new eIqb item
|
|
if not ircdb.checkCapability(prefix,'%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
return False
|
|
if not channel or not mode or not value or not prefix:
|
|
return False
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,oper FROM bans WHERE channel=? AND kind=? AND mask=? AND removed_at is NULL ORDER BY id LIMIT 1""",(channel,mode,value))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
# item exists, so edit it
|
|
c.close()
|
|
return self.edit(irc,channel,mode,value,seconds,prefix,db,logFunction)
|
|
else:
|
|
if channel in self.channels:
|
|
chan = self.getChan(irc,channel)
|
|
item = chan.getItem(mode,value)
|
|
if not item:
|
|
hash = '%s%s' % (mode,value)
|
|
# prepare item update after being set ( we don't have id yet )
|
|
chan.update[hash] = [mode,value,seconds,prefix]
|
|
# enqueue mode changes
|
|
chan.queue.enqueue(('+%s' % mode,value))
|
|
return True
|
|
return False
|
|
|
|
def mark (self,irc,uid,message,prefix,db,logFunction):
|
|
# won't use channel,mode,value, because Item may be removed already
|
|
if not prefix or not message:
|
|
return False
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,channel,kind,mask FROM bans WHERE id=?""",(uid,))
|
|
L = c.fetchall()
|
|
b = False
|
|
if len(L):
|
|
(uid,channel,kind,mask) = L[0]
|
|
if not ircdb.checkCapability(prefix,'%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
c.close()
|
|
return False
|
|
current = time.time()
|
|
c.execute("""INSERT INTO comments VALUES (?, ?, ?, ?)""",(uid,prefix,current,message))
|
|
db.commit()
|
|
logFunction(irc,channel,'[%s][#%s +%s %s] marked by %s: %s' % (channel,uid,kind,mask,prefix.split('!')[0],message))
|
|
b = True
|
|
c.close()
|
|
return b
|
|
|
|
def search (self,irc,pattern,prefix,db):
|
|
# deep search inside database,
|
|
# results filtered depending prefix capability
|
|
c = db.cursor()
|
|
bans = {}
|
|
results = []
|
|
isOwner = ircdb.checkCapability(prefix, 'owner') or prefix == irc.prefix
|
|
glob = '*%s*' % pattern
|
|
like = '%'+pattern+'%'
|
|
if pattern.startswith('$'):
|
|
pattern = clearExtendedBanPattern(pattern)
|
|
glob = '*%s*' % pattern
|
|
like = '%'+pattern+'%'
|
|
elif ircutils.isUserHostmask(pattern):
|
|
# or pattern.startswith('$') ... todo
|
|
(n,i,h) = ircutils.splitHostmask(pattern)
|
|
if n == '*':
|
|
n = None
|
|
if i == '*':
|
|
i = None
|
|
if h == '*':
|
|
h = None
|
|
items = [n,i,h]
|
|
subpattern = ''
|
|
for item in items:
|
|
if item:
|
|
subpattern = subpattern + '*' + item
|
|
glob = '*%s*' % subpattern
|
|
like = '%'+subpattern+'%'
|
|
c.execute("""SELECT id, mask FROM bans ORDER BY id DESC""")
|
|
items = c.fetchall()
|
|
if len(items):
|
|
for item in items:
|
|
(uid,mask) = item
|
|
if ircutils.hostmaskPatternEqual(pattern,mask):
|
|
bans[uid] = uid
|
|
c.execute("""SELECT ban_id, full FROM nicks ORDER BY ban_id DESC""")
|
|
items = c.fetchall()
|
|
if len(items):
|
|
for item in items:
|
|
(uid,full) = item
|
|
if ircutils.hostmaskPatternEqual(pattern,full):
|
|
bans[uid] = uid
|
|
c.execute("""SELECT ban_id, full FROM nicks WHERE full GLOB ? OR full LIKE ? ORDER BY ban_id DESC""",(glob,like))
|
|
items = c.fetchall()
|
|
if len(items):
|
|
for item in items:
|
|
(uid,full) = item
|
|
bans[uid] = uid
|
|
c.execute("""SELECT id, mask FROM bans WHERE mask GLOB ? OR mask LIKE ? ORDER BY id DESC""",(glob,like))
|
|
items = c.fetchall()
|
|
if len(items):
|
|
for item in items:
|
|
(uid,full) = item
|
|
bans[uid] = uid
|
|
c.execute("""SELECT ban_id, comment FROM comments WHERE comment GLOB ? OR comment LIKE ? ORDER BY ban_id DESC""",(glob,like))
|
|
items = c.fetchall()
|
|
if len(items):
|
|
for item in items:
|
|
(uid,full) = item
|
|
bans[uid] = uid
|
|
if len(bans):
|
|
for uid in bans:
|
|
c.execute("""SELECT id, mask, kind, channel FROM bans WHERE id=? ORDER BY id DESC LIMIT 1""",(uid,))
|
|
items = c.fetchall()
|
|
for item in items:
|
|
(uid,mask,kind,channel) = item
|
|
if isOwner or ircdb.checkCapability(prefix, '%s,op' % channel) or prefix != irc.prefix:
|
|
results.append([uid,mask,kind,channel])
|
|
if len(results):
|
|
results.sort(reverse=True)
|
|
i = 0
|
|
msgs = []
|
|
while i < len(results):
|
|
(uid,mask,kind,channel) = results[i]
|
|
if isOwner:
|
|
msgs.append('[#%s +%s %s in %s]' % (uid,kind,mask,channel))
|
|
else:
|
|
msgs.append('[#%s +%s %s]' % (uid,kind,mask))
|
|
i = i+1
|
|
return ', '.join(msgs)
|
|
return 'nothing found'
|
|
|
|
def submark (self,irc,channel,mode,value,message,prefix,db,logFunction):
|
|
# add mark to an item which is not already in lists
|
|
if not channel or not mode or not value or not prefix:
|
|
return False
|
|
if not ircdb.checkCapability(prefix,'%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
return False
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,oper FROM bans WHERE channel=? AND kind=? AND mask=? AND removed_at is NULL ORDER BY id LIMIT 1""",(channel,mode,value))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
# item exists, so edit it
|
|
(uid,oper) = L[0]
|
|
c.close()
|
|
return self.mark(irc,uid,message,prefix,db,logFunction)
|
|
else:
|
|
if channel in self.channels:
|
|
chan = self.getChan(irc,channel)
|
|
item = chan.getItem(mode,value)
|
|
if not item:
|
|
hash = '%s%s' % (mode,value)
|
|
# prepare item update after being set ( we don't have id yet )
|
|
chan.mark[hash] = [mode,value,message,prefix]
|
|
return True
|
|
return False
|
|
|
|
def affect (self,irc,uid,prefix,db):
|
|
# return affected users by a mode change
|
|
if not uid or not prefix:
|
|
return []
|
|
c = db.cursor()
|
|
c.execute("""SELECT channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if not len(L):
|
|
c.close()
|
|
return []
|
|
(channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = L[0]
|
|
if not ircdb.checkCapability(prefix, '%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
c.close()
|
|
return []
|
|
results = []
|
|
c.execute("""SELECT full,log FROM nicks WHERE ban_id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
for item in L:
|
|
(full,log) = item
|
|
results.append(full)
|
|
else:
|
|
results.append('nobody affected')
|
|
c.close()
|
|
return results
|
|
|
|
|
|
def edit (self,irc,channel,mode,value,seconds,prefix,db,logFunction,massremoval):
|
|
# edit eIqb duration
|
|
log.debug('ircd.edit %s %s %s %s %s' % (channel,mode,value,seconds,prefix))
|
|
if not channel or not mode or not value or not prefix:
|
|
return False
|
|
if not ircdb.checkCapability(prefix,'%s,op' % channel):
|
|
if prefix != irc.prefix:
|
|
return False
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,channel,kind,mask,begin_at,end_at FROM bans WHERE channel=? AND kind=? AND mask=? AND removed_at is NULL ORDER BY id LIMIT 1""",(channel,mode,value))
|
|
L = c.fetchall()
|
|
b = False
|
|
if len(L):
|
|
(uid,channel,kind,mask,begin_at,end_at) = L[0]
|
|
chan = self.getChan(irc,channel)
|
|
current = time.time()
|
|
if begin_at == end_at:
|
|
text = 'was forever'
|
|
else:
|
|
text = 'ended [%s] for %s' % (floatToGMT(end_at),utils.timeElapsed(end_at-begin_at))
|
|
if seconds < 0:
|
|
newEnd = begin_at
|
|
reason = 'never expires'
|
|
else:
|
|
newEnd = current+seconds
|
|
reason = 'expires at [%s], for %s in total' % (floatToGMT(newEnd),utils.timeElapsed(newEnd-begin_at))
|
|
text = '%s, now %s' % (text,reason)
|
|
c.execute("""INSERT INTO comments VALUES (?, ?, ?, ?)""",(uid,prefix,current,text))
|
|
c.execute("""UPDATE bans SET end_at=? WHERE id=?""", (newEnd,int(uid)))
|
|
db.commit()
|
|
if not massremoval:
|
|
logFunction(irc,channel,'[%s][#%s +%s %s] edited by %s: %s' % (channel,uid,kind,mask,prefix.split('!')[0],reason))
|
|
i = chan.getItem(kind,mask)
|
|
if i:
|
|
if newEnd == begin_at:
|
|
i.expire = None
|
|
else:
|
|
i.expire = newEnd
|
|
b = True
|
|
c.close()
|
|
return b
|
|
|
|
def resync (self,irc,channel,mode,db,logFunction):
|
|
# here sync mode lists, if items were removed when bot was offline, mark records as removed
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,channel,mask FROM bans WHERE channel=? AND kind=?AND removed_at is NULL ORDER BY id""",(channel,mode))
|
|
L = c.fetchall()
|
|
current = time.time()
|
|
commits = 0
|
|
msgs = []
|
|
if len(L):
|
|
current = time.time()
|
|
if channel in irc.state.channels:
|
|
chan = self.getChan(irc,channel)
|
|
if mode in chan.dones:
|
|
for record in L:
|
|
(uid,channel,mask) = record
|
|
item = chan.getItem(mode,mask)
|
|
if not item:
|
|
c.execute("""UPDATE bans SET removed_at=?, removed_by=? WHERE id=?""", (current,'offline!offline@offline',int(uid)))
|
|
commits = commits + 1
|
|
msgs.append('[#%s %s]' % (uid,mask))
|
|
if commits > 0:
|
|
db.commit()
|
|
logFunction(irc,channel,'[%s][%s] %s removed: %s' % (channel,mode,commits, ' '.join(msgs)))
|
|
c.close()
|
|
|
|
def getChan (self,irc,channel):
|
|
if not channel or not irc:
|
|
return None
|
|
self.irc = irc
|
|
if not channel in self.channels:
|
|
self.channels[channel] = Chan (self,channel)
|
|
return self.channels[channel]
|
|
|
|
def getNick (self,irc,nick):
|
|
if not nick or not irc:
|
|
return None
|
|
self.irc = irc
|
|
if not nick in self.nicks:
|
|
self.nicks[nick] = Nick(self.logsSize)
|
|
return self.nicks[nick]
|
|
|
|
class Chan (object):
|
|
# in memory and in database stores +eIqb list -ov
|
|
# no user action from here
|
|
def __init__(self,ircd,name):
|
|
object.__init__(self)
|
|
self.ircd = ircd
|
|
self.name = name
|
|
self._lists = {}
|
|
# queue contains (mode,valueOrNone) - ircutils.joinModes
|
|
self.queue = utils.structures.smallqueue()
|
|
# contains [modevalue] = [mode,value,seconds,prefix]
|
|
self.update = {}
|
|
# contains [modevalue] = [mode,value,message,prefix]
|
|
self.mark = {}
|
|
# contains IrcMsg ( mostly kick / fpart )
|
|
self.action = utils.structures.smallqueue()
|
|
# looking for eqIb list ends
|
|
self.dones = []
|
|
self.syn = False
|
|
self.opAsked = False
|
|
self.deopAsked = False
|
|
|
|
def getItems (self):
|
|
# [X][Item.value] is Item
|
|
return self._lists
|
|
|
|
def getItemsFor (self,mode):
|
|
if not mode in self._lists:
|
|
self._lists[mode] = {}
|
|
return self._lists[mode]
|
|
|
|
def addItem (self,mode,value,by,when,db):
|
|
# eqIb(+*) (-ov) pattern prefix when
|
|
# mode : eqIb -ov + ?
|
|
l = self.getItemsFor(mode)
|
|
if not value in l:
|
|
i = Item()
|
|
i.channel = self.name
|
|
i.mode = mode
|
|
i.value = value
|
|
uid = None
|
|
expire = None
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,oper,begin_at,end_at FROM bans WHERE channel=? AND kind=? AND mask=? AND removed_at is NULL ORDER BY id LIMIT 1""",(self.name,mode,value))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
# restoring stored informations, due to netsplit server's values may be wrong
|
|
(uid,by,when,expire) = L[0]
|
|
c.execute("""SELECT ban_id,full FROM nicks WHERE ban_id=?""",(uid,))
|
|
L = c.fetchall()
|
|
if len(L):
|
|
for item in L:
|
|
(uid,full) = item
|
|
i.affects.append(full)
|
|
else:
|
|
# if begin_at == end_at --> that means forever
|
|
c.execute("""INSERT INTO bans VALUES (NULL, ?, ?, ?, ?, ?, ?,NULL, NULL)""", (self.name,by,mode,value,when,when))
|
|
db.commit()
|
|
uid = c.lastrowid
|
|
# leave channel's users list management to supybot
|
|
ns = []
|
|
if self.name in self.ircd.irc.state.channels:
|
|
for nick in self.ircd.irc.state.channels[self.name].users:
|
|
if nick in self.ircd.nicks:
|
|
n = self.ircd.getNick(self.ircd.irc,nick)
|
|
m = match(value,n)
|
|
if m:
|
|
i.affects.append(n.prefix)
|
|
# insert logs
|
|
index = 0
|
|
logs = []
|
|
logs.append('%s matched by %s' % (n,m))
|
|
for line in n.logs:
|
|
(ts,target,message) = n.logs[index]
|
|
index += 1
|
|
if target == self.name or target == 'ALL':
|
|
logs.append('[%s] %s' % (floatToGMT(ts),message))
|
|
c.execute("""INSERT INTO nicks VALUES (?, ?, ?, ?)""",(uid,value,n.prefix,'\n'.join(logs)))
|
|
ns.append([n,m])
|
|
if len(ns):
|
|
db.commit()
|
|
c.close()
|
|
i.uid = uid
|
|
i.by = by
|
|
i.when = when
|
|
i.expire = expire
|
|
l[value] = i
|
|
return l[value]
|
|
|
|
def getItem (self,mode,value):
|
|
if mode in self._lists:
|
|
if value in self._lists[mode]:
|
|
return self._lists[mode][value]
|
|
return None
|
|
|
|
def removeItem (self,mode,value,by,db):
|
|
# flag item as removed in database
|
|
c = db.cursor()
|
|
c.execute("""SELECT id,oper,begin_at,end_at FROM bans WHERE channel=? AND kind=? AND mask=? AND removed_at is NULL ORDER BY id LIMIT 1""",(self.name,mode,value))
|
|
L = c.fetchall()
|
|
removed_at = time.time()
|
|
if len(L):
|
|
(uid,by,when,expire) = L[0]
|
|
c.execute("""UPDATE bans SET removed_at=?, removed_by=? WHERE id=?""", (removed_at,by,int(uid)))
|
|
db.commit()
|
|
c.close()
|
|
i = self.getItem(mode,value)
|
|
# item can be None, if someone typoed a -eqbI value
|
|
if i:
|
|
self._lists[mode].pop(value)
|
|
i.removed_by = by
|
|
i.removed_at = removed_at
|
|
return i
|
|
|
|
class Item (object):
|
|
def __init__(self):
|
|
object.__init__(self)
|
|
self.channel = None
|
|
self.mode = None
|
|
self.value = None
|
|
self.by = None
|
|
self.when = None
|
|
self.uid = None
|
|
self.expire = None
|
|
self.removed_at = None
|
|
self.removed_by = None
|
|
self.asked = False
|
|
self.affects = []
|
|
|
|
def __repr__(self):
|
|
end = self.expire
|
|
if self.when == self.expire:
|
|
end = None
|
|
return 'Item(%s [%s][%s] by %s on %s, expire on %s, removed %s by %s)' % (self.uid,self.mode,self.value,self.by,floatToGMT(self.when),floatToGMT(end),floatToGMT(self.removed_at),self.removed_by)
|
|
|
|
class Nick (object):
|
|
def __init__(self,logSize):
|
|
object.__init__(self)
|
|
self.prefix = None
|
|
self.ip = None
|
|
self.realname = None
|
|
self.account = None
|
|
self.logs = utils.structures.MaxLengthQueue(logSize)
|
|
# log format :
|
|
# target can be a channel, or 'ALL' when it's related to nick itself ( account changes, nick changes, host changes, etc )
|
|
# [float(timestamp),target,message]
|
|
|
|
def setPrefix (self,prefix):
|
|
if not prefix == self.prefix:
|
|
self.prefix = prefix
|
|
# recompute ip
|
|
if self.prefix:
|
|
matchHostmask(self.prefix,self)
|
|
getBestPattern(self)
|
|
return self
|
|
|
|
def setIp (self,ip):
|
|
if not ip == self.ip and not ip == '255.255.255.255':
|
|
self.ip = ip
|
|
return self
|
|
|
|
def setAccount (self,account):
|
|
self.account = account
|
|
return self
|
|
|
|
def setRealname (self,realname):
|
|
self.realname = realname
|
|
return self
|
|
|
|
def addLog (self,target,message):
|
|
self.logs.enqueue([time.time(),target,message])
|
|
return self
|
|
|
|
def __repr__(self):
|
|
return '%s ip:%s $a:%s $r:%s' % (self.prefix,self.ip,self.account,self.realname)
|
|
|
|
# Taken from plugins.Time.seconds
|
|
def getTs (irc, msg, args, state):
|
|
"""[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]
|
|
|
|
Returns the number of seconds in the number of <years>, <weeks>,
|
|
<days>, <hours>, <minutes>, and <seconds> given. An example usage is
|
|
"seconds 2h 30m", which would return 9000, which is '3600*2 + 30*60'.
|
|
Useful for scheduling events at a given number of seconds in the
|
|
future.
|
|
"""
|
|
# here there is some glich / ugly hack to allow any('getTs'), with rest('test') after ...
|
|
# TODO checks that bot can't kill itself with loop
|
|
seconds = -1
|
|
for arg in args:
|
|
if not arg or arg[-1] not in 'ywdhms':
|
|
try:
|
|
n = int(args[0])
|
|
state.args.append(n)
|
|
args.pop(0)
|
|
except:
|
|
if len(args):
|
|
state.args.append(float(seconds))
|
|
raise callbacks.ArgumentError
|
|
return
|
|
(s, kind) = arg[:-1], arg[-1]
|
|
try:
|
|
i = int(s)
|
|
except ValueError:
|
|
raise callbacks.ArgumentError
|
|
return
|
|
if kind == 'y':
|
|
seconds += i*31536000
|
|
elif kind == 'w':
|
|
seconds += i*604800
|
|
elif kind == 'd':
|
|
seconds += i*86400
|
|
elif kind == 'h':
|
|
seconds += i*3600
|
|
elif kind == 'm':
|
|
seconds += i*60
|
|
elif kind == 's':
|
|
seconds += i
|
|
args.pop(0)
|
|
state.args.append(float(seconds))
|
|
|
|
addConverter('getTs', getTs)
|
|
|
|
class maybe(commands.any):
|
|
def __init__(self, spec, continueOnError=False):
|
|
self.__parent = super(commands.any, self)
|
|
self.__parent.__init__(spec)
|
|
self.continueOnError = continueOnError
|
|
|
|
def __call__(self, irc, msg, args, state):
|
|
st = state.essence()
|
|
n = 0
|
|
try:
|
|
while args:
|
|
self.__parent.__call__(irc, msg, args, st)
|
|
n = n + 1
|
|
except IndexError:
|
|
pass
|
|
except (callbacks.ArgumentError, callbacks.Error), e:
|
|
if not self.continueOnError:
|
|
raise
|
|
else:
|
|
pass
|
|
state.args.append(st.args[n:])
|
|
|
|
|
|
import threading
|
|
import supybot.world as world
|
|
|
|
def getDuration (seconds):
|
|
if not seconds or not len(seconds):
|
|
return -1
|
|
return seconds[0]
|
|
|
|
class ChanTracker(callbacks.Plugin,plugins.ChannelDBHandler):
|
|
"""This plugin keeps records of channel mode changes and permits to manage them over time"""
|
|
threaded = True
|
|
noIgnore = True
|
|
|
|
def __init__(self, irc):
|
|
self.__parent = super(ChanTracker, self)
|
|
self.__parent.__init__(irc)
|
|
callbacks.Plugin.__init__(self, irc)
|
|
plugins.ChannelDBHandler.__init__(self)
|
|
self.lastTickle = time.time()-self.registryValue('pool')
|
|
self.forceTickle = True
|
|
self._ircs = {}
|
|
self.getIrc(irc)
|
|
|
|
def edit (self,irc,msg,args,user,ids,seconds):
|
|
"""<id> [,<id>] [<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] [<-1>] means forever\n\nchange expiration of an active ban/quiet/exempt/Invite item"""
|
|
i = self.getIrc(irc)
|
|
b = True
|
|
for id in ids:
|
|
item = i.getItem(irc,id)
|
|
if item:
|
|
b = b and i.edit(irc,item.channel,item.mode,item.value,getDuration(seconds),msg.prefix,self.getDb(irc.network),self._logChan,False)
|
|
else:
|
|
b = False
|
|
if b:
|
|
irc.replySuccess()
|
|
else:
|
|
irc.error('no item found or not enough rights')
|
|
self.forceTickle = True
|
|
self._tickle(irc)
|
|
edit = wrap(edit,['user',commalist('int'),any('getTs')])
|
|
|
|
def info (self,irc,msg,args,user,id):
|
|
"""<id>\n\nsummary of a mode change"""
|
|
i = self.getIrc(irc)
|
|
results = i.info(irc,id,msg.prefix,self.getDb(irc.network))
|
|
if len(results):
|
|
for line in results:
|
|
irc.queueMsg(ircmsgs.privmsg(msg.nick,line))
|
|
else:
|
|
irc.error('no item found or not enough rights')
|
|
self._tickle(irc)
|
|
info = wrap(info,['user','int'])
|
|
|
|
def detail (self,irc,msg,args,user,uid):
|
|
"""<id>\n\nlogs of a mode change"""
|
|
i = self.getIrc(irc)
|
|
results = i.log (irc,uid,msg.prefix,self.getDb(irc.network))
|
|
if len(results):
|
|
for line in results:
|
|
irc.queueMsg(ircmsgs.privmsg(msg.nick,line))
|
|
else:
|
|
irc.error('no item found or not enough rights')
|
|
self._tickle(irc)
|
|
detail = wrap(detail,['user','int'])
|
|
|
|
def affect (self,irc,msg,args,user,uid):
|
|
"""<id>\n\nlist users affected by a mode change"""
|
|
i = self.getIrc(irc)
|
|
results = i.affect (irc,uid,msg.prefix,self.getDb(irc.network))
|
|
if len(results):
|
|
for line in results:
|
|
irc.queueMsg(ircmsgs.privmsg(msg.nick,line))
|
|
else:
|
|
irc.error('no item found or not enough rights')
|
|
self._tickle(irc)
|
|
affect = wrap(affect, ['user','int'])
|
|
|
|
def mark(self,irc,msg,args,user,ids,message):
|
|
"""<id> [,<id>]\n\nadd comment on a mode change"""
|
|
i = self.getIrc(irc)
|
|
b = True
|
|
for id in ids:
|
|
b = b and i.mark(irc,id,message,msg.prefix,self.getDb(irc.network),self._logChan)
|
|
if b:
|
|
irc.replySuccess()
|
|
else:
|
|
irc.error('item not found or not enough rights')
|
|
self.forceTickle = True
|
|
self._tickle(irc)
|
|
mark = wrap(mark,['user',commalist('int'),'text'])
|
|
|
|
def query (self,irc,msg,args,user,text):
|
|
"""<pattern|hostmask>\n\nreturns matched items"""
|
|
# method renamed for conflict with Config.search
|
|
i = self.getIrc(irc)
|
|
irc.reply(i.search(irc,text,msg.prefix,self.getDb(irc.network)))
|
|
query = wrap(query,['user','text'])
|
|
|
|
def pending (self, irc, msg, args, op, channel, mode, pattern):
|
|
"""[<channel>] [<mode>] [<hostmask>]\n\nreturns active items for mode if given otherwise all modes are returned, if hostmask given, filtered by oper"""
|
|
i = self.getIrc(irc)
|
|
if not mode:
|
|
results = []
|
|
modes = self.registryValue('modesToAskWhenOpped') + self.registryValue('modesToAsk')
|
|
for m in modes:
|
|
log.debug('pending for %s' % m)
|
|
r = i.pending(irc,channel,m,msg.prefix,pattern,self.getDb(irc.network))
|
|
if len(r):
|
|
for line in r:
|
|
results.append(line)
|
|
else:
|
|
results = i.pending(irc,channel,mode,msg.prefix,pattern,self.getDb(irc.network))
|
|
if len(results):
|
|
for line in results:
|
|
irc.queueMsg(ircmsgs.privmsg(msg.nick,line))
|
|
else:
|
|
irc.error('no results')
|
|
pending = wrap(pending,['op','channel',additional('letter'),additional('hostmask')])
|
|
|
|
#def todo (self,irc,msg,args,channel,text):
|
|
#"""[<channel>] <modes> [<values>] sets modes for channels"""
|
|
## -bb+o-i values
|
|
#try:
|
|
#items = ircutils.separateModes(text.split(' '))
|
|
#chan = self.getChan(irc,channel)
|
|
#if items and len(items):
|
|
#for item in items:
|
|
#chan.queue.enqueue((item[0],item[1]))
|
|
#except:
|
|
#irc.error()
|
|
#self.forceTickle = True
|
|
#self._tickle(irc)
|
|
#todo = wrap(todo,['op','channel','text'])
|
|
|
|
def do (self,irc,msg,args,op,channel,mode,items,seconds,reason):
|
|
"""[<channel>] <nick|hostmask>[,<nick|hostmask>] [<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] [<-1> or empty means forever] <reason>\n\n +mode targets for duration reason is mandatory"""
|
|
if mode in self.registryValue('modesToAsk') or mode in self.registryValue('modesToAskWhenOpped'):
|
|
b = self._adds(irc,msg,args,channel,mode,items,getDuration(seconds),reason)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item already active or not enough rights')
|
|
else:
|
|
irc.error('selected mode is not supported by config')
|
|
do = wrap(do,['op','channel','letter',commalist('something'),any('getTs',True),rest('text')])
|
|
|
|
def q (self,irc,msg,args,op,channel,items,seconds,reason):
|
|
"""[<channel>] <nick|hostmask>[,<nick|hostmask>] [<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] [<-1> or empty means forever] <reason>\n\n+q targets for duration reason is mandatory"""
|
|
b = self._adds(irc,msg,args,channel,'q',items,getDuration(seconds),reason)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item already active or not enough rights')
|
|
q = wrap(q,['op','channel',commalist('something'),any('getTs',True),rest('text')])
|
|
|
|
def b (self, irc, msg, args, op, channel, items, seconds,reason):
|
|
"""[<channel>] <nick|hostmask>[,<nick|hostmask>] [<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] [<-1> or empty means forever] <reason>\n\n+b targets for duration reason is mandatory"""
|
|
b = self._adds(irc,msg,args,channel,'b',items,getDuration(seconds),reason)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item already active or not enough rights')
|
|
b = wrap(b,['op','channel',commalist('something'),any('getTs',True),rest('text')])
|
|
|
|
def i (self, irc, msg, args, op, channel, items, seconds):
|
|
"""[<channel>] <nick|hostmask>[,<nick|hostmask>] [<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] [<-1> or empty means forever] <reason>\n\n+I targets for duration reason is mandatory"""
|
|
b = self._adds(irc,msg,args,channel,'I',items,getDuration(seconds),reason)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item already active or not enough rights')
|
|
i = wrap(i,['op','channel',commalist('something'),any('getTs',True),rest('text')])
|
|
|
|
def e (self, irc, msg, args, op, channel, items,seconds,reason):
|
|
"""[<channel>] <nick|hostmask>[,<nick|hostmask>] [<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] [<-1> or empty means forever] <reason>\n\n+e targets for duration reason is mandatory"""
|
|
b = self._adds(irc,msg,args,channel,'e',items,getDuration(seconds),reason)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item already active or not enough rights')
|
|
e = wrap(e,['op','channel',commalist('something'),any('getTs'),rest('text')])
|
|
|
|
def undo (self, irc, msg, args, op, channel, mode, items):
|
|
"""[<channel>] <nick|hostmask|*> [<nick|hostmask|*>]\n\nsets -q on them, * remove them all"""
|
|
if mode in self.registryValue('modesToAsk') or mode in self.registryValue('modesToAskWhenOpped'):
|
|
b = self._removes(irc,msg,args,channel,mode,items)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item not found or not enough rights')
|
|
else:
|
|
irc.error('selected mode is not supported by config')
|
|
undo = wrap(undo,['op','channel','letter',many('something')])
|
|
|
|
def uq (self, irc, msg, args, op, channel, items):
|
|
"""[<channel>] <nick|hostmask|*> [<nick|hostmask|*>]\n\nsets -q on them, * remove them all"""
|
|
b = self._removes(irc,msg,args,channel,'q',items)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item not found or not enough rights')
|
|
uq = wrap(uq,['op','channel',many('something')])
|
|
|
|
def ub (self, irc, msg, args, op, channel, items):
|
|
"""[<channel>] <nick|hostmask|*> [<nick|hostmask>]\n\nsets -b on them, * remove them all"""
|
|
b = self._removes(irc,msg,args,channel,'b',items)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item not found or not enough rights')
|
|
ub = wrap(ub,['op','channel',many('something')])
|
|
|
|
def ui (self, irc, msg, args, op, channel, items):
|
|
"""[<channel>] <nick|hostmask|*> [<nick|hostmask|*>]\n\nsets -I on them, * remove them all"""
|
|
b = self._removes(irc,msg,args,channel,'I',items)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item not found or not enough rights')
|
|
ui = wrap(ui,['op','channel',many('something')])
|
|
|
|
def ue (self, irc, msg, args, op, channel, items):
|
|
"""[<channel>] <nick|hostmask|*> [<nick|hostmask|*>]\n\nsets -e on them, * remove them all"""
|
|
b = self._removes(irc,msg,args,channel,'e',items)
|
|
if b:
|
|
irc.replySuccess()
|
|
return
|
|
irc.error('item not found or not enough rights')
|
|
ue = wrap(ue,['op','channel',many('something')])
|
|
|
|
def check (self,irc,msg,args,op,channel,pattern):
|
|
"""[<channel>] <pattern> returns a list of affected users by a pattern"""
|
|
# returns affected users by the given pattern
|
|
if ircutils.isUserHostmask(pattern) or pattern.startswith('$'):
|
|
results = []
|
|
i = self.getIrc(irc)
|
|
for nick in irc.state.channels[channel].users:
|
|
if nick in i.nicks:
|
|
n = self.getNick(irc,nick)
|
|
m = match(pattern,n)
|
|
if m:
|
|
results.append(nick)
|
|
if len(results):
|
|
irc.reply(', '.join(results))
|
|
else:
|
|
irc.error('nothing found')
|
|
else:
|
|
irc.error('invalid pattern')
|
|
check = wrap (check,['op','channel','text'])
|
|
|
|
def getmask (self,irc,msg,args,nick):
|
|
"""<nick> returns a list of pattern, best first, mostly used for debug"""
|
|
# returns patterns for a given nick
|
|
i = self.getIrc(irc)
|
|
if nick in i.nicks:
|
|
irc.reply(', '.join(getBestPattern(self.getNick(irc,nick))))
|
|
else:
|
|
irc.error('nick not found')
|
|
getmask = wrap(getmask,['owner','nick'])
|
|
|
|
def _adds (self,irc,msg,args,channel,mode,items,duration,reason):
|
|
i = self.getIrc(irc)
|
|
targets = []
|
|
for item in items:
|
|
if ircutils.isUserHostmask(item) or item.startswith('$'):
|
|
targets.append(item)
|
|
elif channel in irc.state.channels and item in irc.state.channels[channel].users:
|
|
n = self.getNick(irc,item)
|
|
targets.append(getBestPattern(n)[0])
|
|
n = 0
|
|
for item in targets:
|
|
if i.add(irc,channel,mode,item,duration,msg.prefix,self.getDb(irc.network),self._logChan):
|
|
if reason:
|
|
i.submark(irc,channel,mode,item,reason,msg.prefix,self.getDb(irc.network),self._logChan)
|
|
n = n+1
|
|
self.forceTickle = True
|
|
self._tickle(irc)
|
|
return len(items) == n
|
|
|
|
def _removes (self,irc,msg,args,channel,mode,items):
|
|
i = self.getIrc(irc)
|
|
chan = self.getChan(irc,channel)
|
|
targets = []
|
|
massremove = False
|
|
for item in items:
|
|
if ircutils.isUserHostmask(item) or item.startswith('$'):
|
|
targets.append(item)
|
|
elif channel in irc.state.channels and item in irc.state.channels[channel].users:
|
|
n = self.getNick(irc,item)
|
|
L = chan.getItemsFor(mode)
|
|
# here we check active items against Nick and add everything pattern which matchs him
|
|
for pattern in L:
|
|
m = match(pattern,n)
|
|
if m:
|
|
targets.append(pattern)
|
|
elif item == '*':
|
|
massremove = True
|
|
if channel in irc.state.channels:
|
|
L = chan.getItemsFor(mode)
|
|
for pattern in L:
|
|
targets.append(pattern)
|
|
n = 0
|
|
for item in targets:
|
|
if i.edit(irc,channel,mode,item,0,msg.prefix,self.getDb(irc.network),self._logChan,massremove):
|
|
n = n + 1
|
|
self.forceTickle = True
|
|
self._tickle(irc)
|
|
return len(items) == n or massremove
|
|
|
|
def getIrc (self,irc):
|
|
# init irc db
|
|
if not irc in self._ircs:
|
|
i = self._ircs[irc] = Ircd (irc,self.registryValue('logsSize'))
|
|
# restore CAP, if needed, needed to track account (account-notify) ang gecos (extended-join)
|
|
# see config of this plugin
|
|
irc.queueMsg(ircmsgs.IrcMsg('CAP LS'))
|
|
return self._ircs[irc]
|
|
|
|
def getChan (self,irc,channel):
|
|
i = self.getIrc(irc)
|
|
if not channel in i.channels:
|
|
# restore channel state, loads lists
|
|
modesToAsk = ''.join(self.registryValue('modesToAsk'))
|
|
modesWhenOpped = ''.join(self.registryValue('modesToAskWhenOpped'))
|
|
if channel in irc.state.channels:
|
|
if irc.nick in irc.state.channels[channel].ops and len(modesWhenOpped):
|
|
i.queue.enqueue(ircmsgs.IrcMsg('MODE %s %s' % (channel,modesWhenOpped)))
|
|
if len(modesToAsk):
|
|
i.queue.enqueue(ircmsgs.IrcMsg('MODE %s %s' % (channel,modesToAsk)))
|
|
# loads extended who
|
|
i.queue.enqueue(ircmsgs.IrcMsg('WHO ' + channel +' %tnuhiar,42'))
|
|
# fallback, TODO maybe uneeded as supybot do it by itself, but necessary on plugin reload ...
|
|
i.queue.enqueue(ircmsgs.IrcMsg('WHO %s' % channel))
|
|
return i.getChan (irc,channel)
|
|
|
|
def getNick (self,irc,nick):
|
|
return self.getIrc (irc).getNick (irc,nick)
|
|
|
|
def makeDb(self, filename):
|
|
"""Create a database and connect to it."""
|
|
if os.path.exists(filename):
|
|
db = sqlite3.connect(filename)
|
|
db.text_factory = str
|
|
return db
|
|
db = sqlite3.connect(filename)
|
|
db.text_factory = str
|
|
c = db.cursor()
|
|
c.execute("""CREATE TABLE bans (
|
|
id INTEGER PRIMARY KEY,
|
|
channel VARCHAR(100) NOT NULL,
|
|
oper VARCHAR(1000) NOT NULL,
|
|
kind VARCHAR(1) NOT NULL,
|
|
mask VARCHAR(1000) NOT NULL,
|
|
begin_at TIMESTAMP NOT NULL,
|
|
end_at TIMESTAMP NOT NULL,
|
|
removed_at TIMESTAMP,
|
|
removed_by VARCHAR(1000)
|
|
)""")
|
|
c.execute("""CREATE TABLE nicks (
|
|
ban_id INTEGER,
|
|
ban VARCHAR(1000) NOT NULL,
|
|
full VARCHAR(1000) NOT NULL,
|
|
log TEXT NOT NULL
|
|
)""")
|
|
c.execute("""CREATE TABLE comments (
|
|
ban_id INTEGER,
|
|
oper VARCHAR(1000) NOT NULL,
|
|
at TIMESTAMP NOT NULL,
|
|
comment TEXT NOT NULL
|
|
)""")
|
|
db.commit()
|
|
return db
|
|
|
|
def getDb(self, irc):
|
|
"""Use this to get a database for a specific irc."""
|
|
currentThread = threading.currentThread()
|
|
if irc not in self.dbCache and currentThread == world.mainThread:
|
|
self.dbCache[irc] = self.makeDb(self.makeFilename(irc))
|
|
if currentThread != world.mainThread:
|
|
db = self.makeDb(self.makeFilename(irc))
|
|
else:
|
|
db = self.dbCache[irc]
|
|
db.isolation_level = None
|
|
return db
|
|
|
|
def doPong (self,irc,msg):
|
|
self._tickle(irc)
|
|
|
|
def doPing (self,irc,msg):
|
|
self._tickle(irc)
|
|
|
|
def _sendModes (self, irc, modes, f):
|
|
numModes = irc.state.supported.get('modes', 1)
|
|
ircd = self.getIrc(irc)
|
|
for i in range(0, len(modes), numModes):
|
|
ircd.queue.enqueue(f(modes[i:i + numModes]))
|
|
|
|
def _tickle (self,irc):
|
|
# Called each time messages are received from irc, it avoid using schedulers which can fail silency
|
|
# For performance, that may be change in future ...
|
|
t = time.time()
|
|
if not self.forceTickle:
|
|
pool = self.registryValue('pool')
|
|
if pool > 0:
|
|
if self.lastTickle+pool > t:
|
|
return
|
|
self.lastTickle = t
|
|
i = self.getIrc(irc)
|
|
retickle = False
|
|
# send waiting msgs, here we mostly got kick messages
|
|
while len(i.queue):
|
|
# sendMsg vs queueMsg
|
|
irc.sendMsg(i.queue.dequeue())
|
|
def f(L):
|
|
return ircmsgs.modes(channel,L)
|
|
for channel in irc.state.channels:
|
|
chan = self.getChan(irc,channel)
|
|
# check expired items
|
|
for mode in chan.getItems():
|
|
for value in chan._lists[mode]:
|
|
item = chan._lists[mode][value]
|
|
if item.expire != None and item.expire != item.when and not item.asked and item.expire <= t:
|
|
chan.queue.enqueue(('-'+item.mode,item.value))
|
|
# avoid adding it multi times until servers returns changes
|
|
item.asked = True
|
|
retickle = True
|
|
# check items to update - duration
|
|
# that allows to set mode, and apply duration to Item created after mode changes
|
|
# otherwise, we should create db records before applying mode changes ... which, well don't do that :p
|
|
if len(chan.update):
|
|
overexpire = self.registryValue('autoExpire',channel=channel)
|
|
if overexpire > 0:
|
|
# won't override duration pushed by someone else if default is forever
|
|
# [mode,value,seconds,prefix]
|
|
L = []
|
|
for update in chan.update:
|
|
L.append(chan.update[update])
|
|
o = {}
|
|
index = 0
|
|
for k in L:
|
|
(m,value,expire,prefix) = L[index]
|
|
if expire == -1 or expire == None:
|
|
if overexpire != expire:
|
|
chan.update['%s%s' % (m,value)] = [m,value,overexpire,irc.prefix]
|
|
index = index + 1
|
|
L = []
|
|
for update in chan.update:
|
|
L.append(chan.update[update])
|
|
for update in L:
|
|
(m,value,expire,prefix) = update
|
|
item = chan.getItem(m,value)
|
|
if item and item.expire != expire:
|
|
b = i.edit(irc,item.channel,item.mode,item.value,expire,prefix,self.getDb(irc.network),self._logChan,False)
|
|
key = '%s%s' % (m,value)
|
|
del chan.update[key]
|
|
# update marks
|
|
if len(chan.mark):
|
|
L = []
|
|
for mark in chan.mark:
|
|
L.append(chan.mark[mark])
|
|
for mark in L:
|
|
(m,value,reason,prefix) = mark
|
|
item = chan.getItem(m,value)
|
|
if item:
|
|
i.mark(irc,item.uid,reason,prefix,self.getDb(irc.network),self._logChan)
|
|
key = '%s%s' % (item.mode,value)
|
|
del chan.mark[key]
|
|
|
|
# dequeue pending actions
|
|
if not irc.nick in irc.state.channels[channel].ops and not chan.opAsked and self.registryValue('keepOp',channel=channel) and chan.syn:
|
|
# chan.syn is necessary, otherwise, bot can't call owner if rights missed ( see doNotice )
|
|
chan.opAsked = True
|
|
irc.sendMsg(ircmsgs.IrcMsg(self.registryValue('opCommand') % (channel,irc.nick)))
|
|
retickle = True
|
|
if len(chan.queue):
|
|
if not irc.nick in irc.state.channels[channel].ops and not chan.opAsked:
|
|
# pending actions and not opped
|
|
chan.opAsked = True
|
|
irc.sendMsg(ircmsgs.IrcMsg(self.registryValue('opCommand') % (channel,irc.nick)))
|
|
retickle = True
|
|
elif irc.nick in irc.state.channels[channel].ops:
|
|
L = []
|
|
while len(chan.queue):
|
|
L.append(chan.queue.pop())
|
|
# remove duplicates ( should not happens but .. )
|
|
S = Set(L)
|
|
r = []
|
|
for item in L:
|
|
r.append(item)
|
|
if len(r):
|
|
# create IrcMsg
|
|
self._sendModes(irc,r,f)
|
|
if not len(chan.queue) and irc.nick in irc.state.channels[channel].ops and not self.registryValue('keepOp',channel=channel) and not chan.deopAsked:
|
|
# no more actions, no op needed
|
|
chan.deopAsked = True
|
|
chan.queue.enqueue(('-o',irc.nick))
|
|
retickle = True
|
|
# send waiting msgs
|
|
while len(i.queue):
|
|
# sendMsg vs queueMsg
|
|
irc.sendMsg(i.queue.dequeue())
|
|
if retickle:
|
|
self.forceTickle = True
|
|
else:
|
|
self.forceTickle = False
|
|
|
|
def _addChanModeItem (self,irc,channel,mode,value,prefix,date):
|
|
# bqeI* -ov
|
|
if irc.isChannel(channel) and channel in irc.state.channels:
|
|
chan = self.getChan(irc,channel)
|
|
chan.addItem(mode,value,prefix,date,self.getDb(irc.network))
|
|
|
|
def _endList (self,irc,msg,channel,mode):
|
|
if irc.isChannel(channel) and channel in irc.state.channels:
|
|
chan = self.getChan(irc,channel)
|
|
b = False
|
|
if not mode in chan.dones:
|
|
chan.dones.append(mode)
|
|
b = True
|
|
i = self.getIrc(irc)
|
|
i.resync(irc,channel,mode,self.getDb(irc.network),self._logChan)
|
|
if b:
|
|
self._logChan(irc,channel,"[%s][%s] %s items parsed, ready %s" % (channel,mode,len(chan.getItemsFor(mode)),''.join(chan.dones)))
|
|
self._tickle(irc)
|
|
|
|
def do346 (self,irc,msg):
|
|
# /mode #channel I
|
|
self._addChanModeItem(irc,msg.args[1],'I',msg.args[2],msg.args[3],msg.args[4])
|
|
|
|
def do347 (self,irc,msg):
|
|
# end of I list
|
|
self._endList(irc,msg,msg.args[1],'I')
|
|
|
|
def do348 (self,irc,msg):
|
|
# /mode #channel e
|
|
self._addChanModeItem(irc,msg.args[1],'e',msg.args[2],msg.args[3],msg.args[4])
|
|
|
|
def do349 (self,irc,msg):
|
|
# end of e list
|
|
self._endList(irc,msg,msg.args[1],'e')
|
|
|
|
def do367 (self,irc,msg):
|
|
# /mode #channel b
|
|
self._addChanModeItem(irc,msg.args[1],'b',msg.args[2],msg.args[3],msg.args[4])
|
|
|
|
def do368 (self,irc,msg):
|
|
# end of b list
|
|
self._endList(irc,msg,msg.args[1],'b')
|
|
|
|
def do728 (self,irc,msg):
|
|
# extended channel's list ( q atm )
|
|
self._addChanModeItem(irc,msg.args[1],msg.args[2],msg.args[3],msg.args[4],msg.args[5])
|
|
|
|
def do729 (self,irc,msg):
|
|
# end of extended list ( q )
|
|
self._endList(irc,msg,msg.args[1],msg.args[2])
|
|
|
|
def do352(self, irc, msg):
|
|
# WHO $channel
|
|
(nick, ident, host) = (msg.args[5], msg.args[2], msg.args[3])
|
|
n = self.getNick(irc,nick)
|
|
n.setPrefix('%s!%s@%s' % (nick,ident,host))
|
|
# channel = msg.args[1]
|
|
|
|
def do329 (self,irc,msg):
|
|
# channel timestamp
|
|
channel = msg.args[1]
|
|
self._tickle(irc)
|
|
|
|
def do354 (self,irc,msg):
|
|
# WHO $channel %tnuhiar,42
|
|
# irc.nick 42 ident ip host nick account realname
|
|
if len(msg.args) == 8 and msg.args[1] == '42':
|
|
(garbage,digit,ident,ip,host,nick,account,realname) = msg.args
|
|
if account == '0':
|
|
account = None
|
|
n = self.getNick(irc,nick)
|
|
n.setPrefix('%s!%s@%s' % (nick,ident,host))
|
|
n.setIp(ip)
|
|
n.setAccount(account)
|
|
n.setRealname(realname)
|
|
#channel = msg.args[1]
|
|
self._tickle(irc)
|
|
|
|
def do315 (self,irc,msg):
|
|
# end of extended WHO $channel
|
|
channel = msg.args[1]
|
|
if irc.isChannel(channel) and channel in irc.state.channels:
|
|
chan = self.getChan(irc,channel)
|
|
if not chan.syn:
|
|
# this flag is mostly used to wait for the full sync before moaming on owners when something wrong happened
|
|
# like not enough rights to take op
|
|
chan.syn = True
|
|
self._tickle(irc)
|
|
|
|
def _logChan (self,irc,channel,message):
|
|
if channel in irc.state.channels:
|
|
logChannel = self.registryValue('logChannel',channel=channel)
|
|
if logChannel in irc.state.channels:
|
|
irc.queueMsg(ircmsgs.privmsg(logChannel,message))
|
|
|
|
def doJoin (self,irc,msg):
|
|
isBot = msg.nick == irc.nick
|
|
channels = msg.args[0].split(',')
|
|
n = self.getNick(irc,msg.nick)
|
|
i = self.getIrc(irc)
|
|
n.setPrefix(msg.prefix)
|
|
if 'LIST' in i.caps and 'extended-join' in i.caps['LIST'] and len(msg.args) == 3:
|
|
n.setRealname(msg.args[2])
|
|
n.setAccount(msg.args[1])
|
|
for channel in channels:
|
|
if ircutils.isChannel(channel) and channel in irc.state.channels:
|
|
chan = self.getChan(irc,channel)
|
|
n.addLog(channel,'has joined')
|
|
self._tickle(irc)
|
|
|
|
def doPart (self,irc,msg):
|
|
isBot = msg.nick == irc.nick
|
|
channels = msg.args[0].split(',')
|
|
i = self.getIrc(irc)
|
|
n = self.getNick(irc,msg.nick)
|
|
reason = ''
|
|
if len(msg.args) == 2:
|
|
reason = msg.args[1].lstrip().rstrip()
|
|
for channel in channels:
|
|
if ircutils.isChannel(channel):
|
|
if isBot and channel in i.channels:
|
|
del i.channels[channel]
|
|
continue
|
|
if len(reason):
|
|
n.addLog(channel,'has left [%s]' % (reason))
|
|
if reason.startswith('requested by'):
|
|
self._logChan(irc,channel,'[%s] %s has left %s' % (channel,msg.prefix,reason))
|
|
else:
|
|
n.addLog(channel,'has left')
|
|
self._tickle(irc)
|
|
|
|
def doKick (self,irc,msg):
|
|
if len(msg.args) == 3:
|
|
(channel,target,reason) = msg.args
|
|
else:
|
|
(channel,target) = msg.args
|
|
reason = ''
|
|
isBot = target == irc.nick
|
|
if isBot:
|
|
if ircutils.isChannel(channel):
|
|
if isBot and channel in i.channels:
|
|
del i.channels[channel]
|
|
return
|
|
n = self.getNick(irc,target)
|
|
n.addLog(channel,'kicked by %s (%s)' % (msg.prefix,reason))
|
|
self._logChan(irc,channel,'[%s] %s kicked by %s (%s)' % (channel,target,msg.prefix,reason))
|
|
self._tickle(irc)
|
|
|
|
def doQuit (self,irc,msg):
|
|
isBot = msg.nick == irc.nick
|
|
reason = None
|
|
if len(msg.args) == 1:
|
|
reason = msg.args[0].lstrip().rstrip()
|
|
if not isBot:
|
|
n = self.getNick(irc,msg.nick)
|
|
if reason:
|
|
n.addLog('ALL','has quit [%s]' % reason)
|
|
else:
|
|
n.addLog('ALL','has quit')
|
|
if reason and reason == 'Changing host':
|
|
# recloak
|
|
log.debug('%s recloaked' % irc.prefix)
|
|
else:
|
|
i = self.getIrc(irc)
|
|
if msg.nick in i.nicks:
|
|
del i.nicks[msg.nick]
|
|
self._tickle(irc)
|
|
|
|
def doPrivmsg (self,irc,msg):
|
|
isCtcp = ircmsgs.isCtcp(msg)
|
|
(recipients, text) = msg.args
|
|
isAction = ircmsgs.isAction(msg)
|
|
if isAction:
|
|
text = ircmsgs.unAction(msg)
|
|
n = None
|
|
if ircutils.isUserHostmask(msg.prefix):
|
|
n = self.getNick(irc,msg.nick)
|
|
if not n:
|
|
# server msgs
|
|
self.log.warn("%s isn't a valid sender" % msg.prefix)
|
|
self._tickle(irc)
|
|
return
|
|
for channel in recipients.split(','):
|
|
if irc.isChannel(channel) and channel in irc.state.channels:
|
|
message = text
|
|
if isCtcp and not isAction:
|
|
message = 'CTCP | %s' % text
|
|
self._logChan(irc,channel,'[%s] %s ctcps "%s"' % (channel,msg.prefix,text))
|
|
elif isAction:
|
|
message = '- %s -' % text
|
|
n.addLog(channel,message)
|
|
self._tickle(irc)
|
|
|
|
def doNick (self,irc,msg):
|
|
oldNick = msg.prefix.split('!')[0]
|
|
newNick = msg.args[0]
|
|
i = self.getIrc (irc)
|
|
n = None
|
|
if oldNick in i.nicks:
|
|
n = self.getNick(irc,oldNick)
|
|
i.nicks.pop(oldNick)
|
|
if n.prefix:
|
|
prefixNew = '%s!%s' % (newNick,n.prefix.split('!')[1:])
|
|
n.setPrefix(prefixNew)
|
|
i.nicks[newNick] = n
|
|
n = self.getNick(irc,newNick)
|
|
n.addLog('ALL','%s is now known as %s' % (oldNick,newNick))
|
|
self._tickle(irc)
|
|
|
|
def doCap (self,irc,msg):
|
|
# handles CAP messages
|
|
i = self.getIrc(irc)
|
|
command = msg.args[1]
|
|
l = msg.args[2].split(' ')
|
|
if command == 'LS':
|
|
# retreived supported CAP
|
|
i.caps['LS'] = l
|
|
# checking actives CAP, reload, etc
|
|
irc.queueMsg(ircmsgs.IrcMsg('CAP LIST'))
|
|
elif command == 'LIST':
|
|
i.caps['LIST'] = l
|
|
if 'LS' in i.caps:
|
|
r = []
|
|
# 'identify-msg' removed due to unability for default supybot's drivers to handles it correctly
|
|
# ['account-notify','extended-join']
|
|
# targeted caps
|
|
CAPS = self.registryValue('caps')
|
|
for cap in CAPS:
|
|
if cap in i.caps['LS'] and not cap in i.caps['LIST']:
|
|
r.append(cap)
|
|
if len(r):
|
|
# apply missed caps
|
|
irc.queueMsg(ircmsgs.IrcMsg('CAP REQ :%s' % ' '.join(r)))
|
|
elif command == 'ACK' or command == 'NAK':
|
|
# retrieve current caps
|
|
irc.queueMsg(ircmsgs.IrcMsg('CAP LIST'))
|
|
self._tickle(irc)
|
|
|
|
def doAccount (self,irc,msg):
|
|
# update nick's model
|
|
if ircutils.isUserHostmask(msg.prefix):
|
|
nick = ircutils.nickFromHostmask(msg.prefix)
|
|
n = self.getNick(irc,nick)
|
|
old = n.account;
|
|
acc = msg.args[0]
|
|
if acc == '*':
|
|
acc = None
|
|
n.setAccount(acc)
|
|
n.addLog('ALL','%s is now identified as %s' % (old,acc))
|
|
self._tickle(irc)
|
|
|
|
def doNotice (self,irc,msg):
|
|
(targets, text) = msg.args
|
|
if targets == irc.nick:
|
|
b = False
|
|
if text == 'You are not authorized to perform this operation.':
|
|
b = True
|
|
if b:
|
|
i = self.getIrc(irc)
|
|
for nick in i.nicks:
|
|
n = i.getNick(irc,nick)
|
|
if n.prefix and ircdb.checkCapability(n.prefix, 'owner') and n.prefix != irc.prefix:
|
|
irc.queueMsg(ircmsgs.privmsg(n.prefix.split('!')[0],'Warning got %s notice: %s' % (msg.prefix,text)))
|
|
break
|
|
#if text.startswith('*** Message to ') and text.endswith(' throttled due to flooding'):
|
|
# as bot floods, todo schedule info to owner
|
|
else:
|
|
n = self.getNick(irc,msg.nick)
|
|
for channel in targets.split(','):
|
|
if irc.isChannel(channel) and channel in irc.state.channels:
|
|
n.addLog(channel,'NOTICE | %s' % text)
|
|
self._logChan(irc,channel,'[%s] %s notices "%s"' % (channel,msg.prefix,text))
|
|
self._tickle(irc)
|
|
|
|
def doTopic(self, irc, msg):
|
|
if len(msg.args) == 1:
|
|
return
|
|
channel = msg.args[0]
|
|
n = irc.getNick(irc,msg.nick)
|
|
if channel in irc.state.channels:
|
|
n.addLog(channel,'sets topic "%s"' % msg.args[1])
|
|
self._logChan(irc,channel,'[%s] %s sets topic "%s"' % (channel,msg.prefix,msg.args[1]))
|
|
|
|
def doMode(self, irc, msg):
|
|
channel = msg.args[0]
|
|
now = time.time()
|
|
n = None
|
|
i = self.getIrc(irc)
|
|
if ircutils.isUserHostmask(msg.prefix):
|
|
# prevent server.netsplit to create a Nick
|
|
n = self.getNick(irc,msg.nick)
|
|
n.setPrefix(msg.prefix)
|
|
# umode otherwise
|
|
if irc.isChannel(channel) and msg.args[1:] and channel in irc.state.channels:
|
|
modes = ircutils.separateModes(msg.args[1:])
|
|
chan = self.getChan(irc,channel)
|
|
msgs = []
|
|
overexpire = self.registryValue('autoExpire',channel=channel)
|
|
for change in modes:
|
|
(mode,value) = change
|
|
if value:
|
|
value = value.lstrip().rstrip()
|
|
item = None
|
|
if '+' in mode:
|
|
m = mode[1:]
|
|
if m in self.registryValue('modesToAskWhenOpped') or m in self.registryValue('modesToAsk'):
|
|
item = chan.addItem(m,value,msg.prefix,now,self.getDb(irc.network))
|
|
if overexpire > 0:
|
|
# overwrite expires
|
|
if msg.nick != irc.nick:
|
|
# an op do something, and over expires is enabled, announce or not ? currently not. change last flag
|
|
i.edit(irc,channel,m,value,overexpire,irc.prefix,self.getDb(irc.network),self._logChan,True)
|
|
self.forceTickle = True
|
|
# not sure i will keep this "feature" as the plugin is a bantracker plugin, and should be only that
|
|
if m in self.registryValue('kickMode',channel=channel):
|
|
if item and len(item.affects):
|
|
for affected in item.affects:
|
|
nick = affected.split('!')[0]
|
|
if nick in irc.state.channels[channel].users:
|
|
i.queue.enqueue(ircmsgs.kick(channel,affected.split('!')[0],self.registryValue('kickMessage')))
|
|
if m == 'o' and value == irc.nick:
|
|
chan.opAsked = False
|
|
ms = ''
|
|
asked = self.registryValue('modesToAskWhenOpped')
|
|
for k in asked:
|
|
if not k in chan.dones:
|
|
ms = ms + k
|
|
if len(ms):
|
|
# update missed list, using sendMsg, as the bot may ask for -o just after
|
|
irc.sendMsg(ircmsgs.IrcMsg('MODE %s %s' % (channel,ms)))
|
|
# flush pending queue
|
|
self.forceTickle = True
|
|
else:
|
|
m = mode[1:]
|
|
if m == 'o' and value == irc.nick:
|
|
chan.deopAsked = False
|
|
if m in self.registryValue('modesToAskWhenOpped') or m in self.registryValue('modesToAsk'):
|
|
item = chan.removeItem(m,value,msg.prefix,self.getDb(irc.network))
|
|
if n:
|
|
n.addLog(channel,'sets %s %s' % (mode,value))
|
|
if item:
|
|
if '+' in mode:
|
|
if len(item.affects) != 1:
|
|
msgs.append('[#%s %s %s - %s users]' % (item.uid,mode,value,len(item.affects)))
|
|
else:
|
|
msgs.append('[#%s %s %s - %s]' % (item.uid,mode,value,item.affects[0]))
|
|
else:
|
|
if len(item.affects) != 1:
|
|
# something odds appears during tests, when channel is not sync, and there is some removal, item.remove_at or item.when aren't Float
|
|
# TODO check before string format maybe
|
|
# left as it is, trying to reproduce
|
|
msgs.append('[#%s %s %s - %s users, %s]' % (item.uid,mode,value,len(item.affects),utils.timeElapsed(item.removed_at-item.when)))
|
|
else:
|
|
msgs.append('[#%s %s %s - %s, %s]' % (item.uid,mode,value,item.affects[0],utils.timeElapsed(item.removed_at-item.when)))
|
|
else:
|
|
msgs.append('[%s %s]' % (mode,value))
|
|
else:
|
|
if n:
|
|
n.addLog(channel,'sets %s' % mode)
|
|
msgs.append(mode)
|
|
if irc.nick in irc.state.channels[channel].ops and not self.registryValue('keepOp',channel=channel):
|
|
self.forceTickle = True
|
|
self._tickle(irc)
|
|
self._logChan(irc,channel,'[%s] %s sets %s' % (channel,msg.prefix,' '.join(msgs)))
|
|
|
|
def do478(self,irc,msg):
|
|
# message when ban list is full after adding something to eqIb list
|
|
(nick,channel,ban,info) = msg.args
|
|
if info == 'Channel ban list is full':
|
|
self._logChan(irc,channel,'[%s] %s' % (channel,info.upper()))
|
|
self._tickle(irc)
|
|
|
|
|
|
Class = ChanTracker
|
|
|
|
|
|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|