The "Massive Update" edition:

Mess:
 * Fixed file lookup to not depend on order in config file
Encyclopedia:
 * Lots of small fixes and a few additions
Bantracker:
 * Web-interface now has a special oper: keyword, logs can now be hidden too
 * Added new commands: 'updatebt': Syncs with the channel ban list,
   'coment': Reads or adds a comment for a ban,
   'togglemsg': Toggles /msg's from the bot,
   'banlink': Sends a direct link to the a log with a highlight
 * Updated several commands and made hostname lookup more intelligent
This commit is contained in:
Terence Simpson 2008-08-04 11:11:15 +01:00
parent 8f254c3ad5
commit f173b65390
5 changed files with 519 additions and 116 deletions

View File

@ -15,14 +15,16 @@
import sys
# This needs to be set to the location of the commoncgi.py file
sys.path.append('/var/www/ubotu.ubuntu-nl.org')
sys.path.append('/home/ubottu/bot/plugins/')
from commoncgi import *
### Variables
# Location of the bans database
db = '/home/ubotu/data/bans.db'
db = '/home/ubottu/data/bans.db'
# Number of bans to show per page
num_per_page = 100
### You shouldn't have to change anything under this line ###
con = sqlite.connect(db)
cur = con.cursor()
@ -31,12 +33,18 @@ error = ''
user = None
# Delete old sessions
## For some reason this fails, so deal with it
# FAIL!
try:
cur.execute("""DELETE FROM sessions WHERE time < %d""", int(time.time()) - 2592000 * 3)
cur.execute("DELETE FROM sessions WHERE time < %d", int(time.mktime(time.gmtime())) - 2592000 * 3)
con.commit()
con.close()
except:
con.commit()
pass
try:
con.commit()
except:
pass
finally:
con.close()
# Session handling
if form.has_key('sess'):
@ -44,34 +52,58 @@ if form.has_key('sess'):
if cookie.has_key('sess'):
try:
sess = cookie['sess'].value
cur.execute("""SELECT user FROM sessions WHERE session_id=%s""",sess)
con = sqlite.connect(db)
cur = con.cursor()
cur.execute("SELECT user FROM sessions WHERE session_id=%s",sess)
user = cur.fetchall()[0][0]
con.commit()
con.close()
except:
con.commit()
con.close()
pass
if not user:
print "Sorry, bantracker has been shut down for anonymous users due to server load"
print "Sorry, bantracker has been shut down for anonymous users due to server load<br>"
print "Join <a href=irc://irc.freenode.net/ubuntu-ops>#ubuntu-ops</a> on irc.freenode.net to descuss bans"
send_page('bans.tmpl')
# Log
if form.has_key('log'):
cur.execute("""SELECT log FROM bans WHERE id=%s""", form['log'].value)
con = sqlite.connect(db)
cur = con.cursor()
cur.execute("SELECT log FROM bans WHERE id=%s", form['log'].value)
log = cur.fetchall()
con.commit()
print q(log[0][0]).replace('\n', '<br />')
con.close()
if form.has_key('mark'):
marked = form['mark'].value
lines = log[0][0].splitlines()
for line in lines:
if marked.lower() in line.lower():
print '<font style="BACKGROUND-COLOR: yellow">%s</font><br>' % q(line)
else:
print "%s<br>" % q(line)
else:
print q(log[0][0]).replace('\n', '<br />')
send_page('empty.tmpl')
# Main page
# Process comments
if form.has_key('comment') and form.has_key('comment_id') and user:
cur.execute("""SELECT ban_id FROM comments WHERE ban_id=%s and comment=%s""", (form['comment_id'].value, form['comment'].value))
con = sqlite.connect(db)
cur = con.cursor()
cur.execute("SELECT ban_id FROM comments WHERE ban_id=%s and comment=%s", (form['comment_id'].value, form['comment'].value))
comm = cur.fetchall()
if not len(comm):
cur.execute("""INSERT INTO comments (ban_id, who, comment, time) VALUES (%s, %s, %s, %s)""",
(form['comment_id'].value,user,form['comment'].value,pickle.dumps(datetime.datetime.now(pytz.UTC))))
con.commit()
con.close()
if not len(comm):
con = sqlite.connect(db)
cur = con.cursor()
cur.execute("INSERT INTO comments (ban_id, who, comment, time) VALUES (%s, %s, %s, %s)",
(form['comment_id'].value,user,form['comment'].value,pickle.dumps(datetime.datetime.now(pytz.UTC))))
con.commit()
con.close()
# Write the page
print '<form action="bans.cgi" method="POST">'
@ -133,6 +165,10 @@ print '<input class="input" type="checkbox" name="mutes" '
if form.has_key('mutes') or not form.has_key('query'):
print 'checked="checked" '
print '/> Search in existing mutes<br />'
print '<input class="input" type="checkbox" name="floods" '
if form.has_key('floods') or not form.has_key('query'):
print 'checked="checked" '
print '/> Include FloodBots<br />'
print '</div>'
print '<div style="clear:both"><input class="submit" type="submit" value="search" /></div>'
@ -144,11 +180,14 @@ if not form.has_key('query'):
if form.has_key('sort'):
sort='&sort=' + form['sort'].value
print '<div style="clear: both">&middot;'
con = sqlite.connect(db)
cur = con.cursor()
cur.execute('SELECT COUNT(id) FROM bans')
nump = math.ceil(int(cur.fetchall()[0][0]) / float(num_per_page))
for i in range(nump):
for i in range(int(nump)):
print '<a href="bans.cgi?page=%d%s">%d</a> &middot;' % (i, sort, i+1)
print '</div>'
con.close()
# Empty log div, will be filled with AJAX
print '<div id="log" class="log">&nbsp;</div>'
@ -167,13 +206,20 @@ for h in [['Channel',0], ['Nick/Mask',1], ['Operator',2], ['Time',6]]:
print '<th>Log</th></tr>'
# Select and filter bans
con = sqlite.connect(db)
cur = con.cursor()
cur.execute("SELECT channel,mask,operator,time,removal,removal_op,id FROM bans ORDER BY id DESC")
bans = cur.fetchall()
con.close()
def myfilter(item, regex, kick, ban, oldban, mute, oldmute):
def myfilter(item, regex, kick, ban, oldban, mute, oldmute, floods, operator):
if operator:
if not operator.lower() in item[2].lower(): return False
if '!' not in item[1]:
if not kick: return False
elif item[1][0] == '%':
if not floods:
if 'floodbot' in item[2].lower(): return False
if item[1][0] == '%':
if item[4]:
if not oldmute: return False
else:
@ -183,17 +229,23 @@ def myfilter(item, regex, kick, ban, oldban, mute, oldmute):
if not oldban: return False
else:
if not ban: return False
return regex.search(item[1]) or regex.search(item[2]) or regex.search(item[0]) or (item[5] and regex.search(item[5]))
if operator:
return regex.search(item[0]) or regex.search(item[1]) or (item[5] and regex.search(item[5]))
return regex.search(item[0]) or regex.search(item[1]) or regex.search(item[2]) or (item[5] and regex.search(item[5]))
if form.has_key('query'):
k = b = ob = m = om = False
k = b = ob = m = om = fb = oper = False
if form.has_key('kicks'): k = True
if form.has_key('oldbans'): ob = True
if form.has_key('bans'): b = True
if form.has_key('oldmutes'): om = True
if form.has_key('mutes'): m = True
if form.has_key('floods'): fb = True
if form['query'].value.startswith("oper:"):
oper = form['query'].value[4:].split(':', 1)[1].split(None, 1)[0]
form['query'].value = form['query'].value[5+len(oper):].strip()
regex = re.compile(re.escape(form['query'].value).replace('\%','.*'), re.DOTALL | re.I)
bans = filter(lambda x: myfilter(x, regex, k, b, ob, m, om), bans)
bans = filter(lambda x: myfilter(x, regex, k, b, ob, m, om, fb, oper), bans)
start = 0; end = len(bans)
else:
page = 0
@ -230,7 +282,7 @@ for b in bans[start:end]:
i += 1
print '>'
# Channel
print '<td>%s %s</td>' % ('',b[0])
print '<td> %s</td>' % b[0]
# Mask
print '<td>%s' % b[1]
# Ban removal
@ -248,7 +300,7 @@ for b in bans[start:end]:
print '<br /><span class="removal">%s</span>' % pickle.loads(b[4]).astimezone(tz).strftime("%b %d %Y %H:%M:%S")
print '</td>'
# Log link
print """<td><span class="pseudolink" onclick="showlog('%s')">Show log</span></td>""" % b[6]
print '<td><span class="pseudolink" onclick="showlog(\'%s\')">Show/Hide log</span></td>' % b[6]
print '</tr>'
# Comments
@ -257,8 +309,11 @@ for b in bans[start:end]:
print ' class="bg2"'
print '>'
print '<td colspan="5" class="comment">'
cur.execute("""SELECT who, comment, time FROM comments WHERE ban_id = %s""" % b[6])
con = sqlite.connect(db)
cur = con.cursor()
cur.execute("SELECT who, comment, time FROM comments WHERE ban_id = %s" % b[6])
comments = cur.fetchall()
con.close()
if len(comments) == 0:
print '<span class="removal">(No comments) </span>'
else:
@ -267,14 +322,16 @@ for b in bans[start:end]:
print u' <span class="removal"><br />%s, %s</span><br />' % \
(c[0],pickle.loads(c[2]).astimezone(tz).strftime("%b %d %Y %H:%M:%S"))
if user:
print """<span class="pseudolink" onclick="toggle('%s','comment')">Add comment</span>""" % b[6]
print """<div class="invisible" id="comment_%s"><br />""" % b[6]
print """<form action="bans.cgi" method="POST"><textarea cols="50" rows="5" class="input" name="comment"></textarea><br />"""
print """<input type="hidden" name="comment_id" value="%s" />""" % b[6]
print """<input class="submit" type="submit" value="Send" /></form>"""
print '<span class="pseudolink" onclick="toggle(\'%s\',\'comment\')">Add comment</span>' % b[6]
print '<div class="invisible" id="comment_%s"><br />' % b[6]
print '<form action="bans.cgi" method="POST"><textarea cols="50" rows="5" class="input" name="comment"></textarea><br />'
print '<input type="hidden" name="comment_id" value="%s" />' % b[6]
print '<input class="submit" type="submit" value="Send" /></form>'
print '</td></tr>'
print '</table>'
if not bans and form.has_key('query'):
print "<center><u>No matches for:</u> &quot;%s&quot;</center>" % form['query'].value
# Aaaaaaaaaaaaaaaaand send!
send_page('bans.tmpl')

View File

@ -1,6 +1,6 @@
<html>
<head>
<title>Ubugtu bantracker</title>
<title>Ubottu bantracker</title>
<link rel="stylesheet" href="bot.css" />
<link rel="shortcut icon" href="favicon.ico" type="image/png" />
<script type="text/javascript">
@ -31,9 +31,19 @@
}
var s = 0;
function showlog(item) {
loadlog(item);
if (s == item) {
c = new getObj('log');
if( c.style.display == 'block' ) {
c.style.display = 'none';
} else {
c.style.diaply = 'block';
}
s = 0;
} else {
loadlog(item);
}
}
var r;
function loadlog(id) {
@ -41,6 +51,7 @@
r.onreadystatechange = printlog;
r.open("GET",'bans.cgi?log=' + id, true);
r.send(null);
s = id;
}
function printlog() {
if (r.readyState == 4) {
@ -53,11 +64,12 @@
</head>
<body>
<div class="main">
<h1>Ubotu Bantracker</h1>
<h1>Ubottu Bantracker</h1>
<p>
%s
</p>
<p>&copy;2006-2007 Dennis Kaarsemaker</p>
<p>&copy;2006 Dennis Kaarsemaker<br>
Edited by Terence Simpson</p>
</div>
</body>
</html>

View File

@ -57,16 +57,77 @@ def now():
def capab(user, capability):
try:
if capability in user.capabilities:
if capability in list(user.capabilities):
return True
else:
return False
except:
return False
def hostmaskPatternEqual(pattern, hostmask):
if pattern.count('!') not in (1, 2) or pattern.count('@') != 1:
return False
if pattern.count('!') == 2:
pattern = "!".join(pattern.split('!')[:-1])
return ircutils.hostmaskPatternEqual(pattern, hostmask)
def dequeue(parent, irc):
global queue
queue.dequeue(parent, irc)
class MsgQueue(object):
def __init__(self):
self.msgcache = []
def queue(self, msg):
if msg not in self.msgcache:
self.msgcache.append(msg)
def clear(self):
self.msgcache = []
def dequeue(self, parent, irc):
parent.thread_timer.cancel()
parent.thread_timer = threading.Timer(30.0, dequeue, args=(parent, irc))
if len(self.msgcache) == 0:
parent.thread_timer.start()
return
msg = self.msgcache.pop(0)
irc.queueMsg(msg)
parent.thread_timer.start()
queue = MsgQueue()
class Ban(object):
"""Hold my bans"""
def __init__(self, args=None, **kwargs):
object.__init__(self)
if args:
self.mask = args[2]
self.who = args[3]
self.when = args[4]
else:
self.mask = kwargs['mask']
self.who = kwargs['who']
self.when = kwargs['when']
self.ascwhen = time.asctime(time.gmtime(float(self.when)))
def __tuple__(self):
return (self.mask, self.who, self.ascwhen)
def __iter__(self):
return self.__tuple__().__iter__()
def __str__(self):
return "%s by %s on %s" % tuple(self)
def __repr__(self):
return '<%s object "%s" at 0x%x>' % (self.__class__.__name__, self, id(self))
class Bantracker(callbacks.Plugin):
"""Use @mark to add a bantracker entry manually and
@btlogin to log into the bantracker"""
"""Plugin to manage bans.
Use 'mark' to add a bantracker entry manually,
'btlogin' to log into the bantracker,
'bansearch' to search the bantracker for bans and
'banlog' print the last 5 lines for ban entries
See '@list Bantracker' and '@help <command>' for other commands"""
noIgnore = True
def __init__(self, irc):
@ -75,18 +136,96 @@ class Bantracker(callbacks.Plugin):
self.lastMsgs = {}
self.lastStates = {}
self.logs = {}
self.nicks = {}
self.bans = {}
self.thread_timer = threading.Timer(30.0, dequeue, args=(self,irc))
self.thread_timer.start()
db = self.registryValue('database')
if db:
self.db = sqlite.connect(db)
else:
self.db = None
self.get_bans(irc)
self.get_nicks(irc)
def get_nicks(self, irc):
for (channel, c) in irc.state.channels.iteritems():
for nick in list(c.users):
if not nick in self.nicks:
self.nicks[nick] = self.nick_to_host(irc, nick)
def get_bans(self, irc):
global queue
for channel in irc.state.channels.keys():
if channel not in self.bans:
self.bans[channel] = []
queue.queue(ircmsgs.mode(channel, 'b'))
def sendWhois(self, irc, nick):
irc.queueMsg(ircmsgs.whois(nick, nick))
def do311(self, irc, msg):
"""/whois"""
nick = msg.args[1].lower()
mask = "%s!%s@%s" % (nick, msg.args[2].lower(), msg.args[3].lower())
self.nicks[nick] = mask
def do314(self, irc, msg):
"""/whowas"""
nick = msg.args[1].lower()
mask = "%s!%s@%s" % (nick, msg.args[2].lower(), msg.args[3].lower())
if not nick in self.nicks:
self.nicks[nick] = mask
def do401(self, irc, msg):
"""/whois faild"""
irc.queueMsg(ircmsgs.IrcMsg(prefix="", command='WHOWAS', args=(msg.args[1],), msg=msg))
def do406(self, irc, msg):
"""/whowas faild"""
self.log.info("Host lookup faild")
def do367(self, irc, msg):
"""Got ban"""
if msg.args[1] not in self.bans.keys():
self.bans[msg.args[1]] = []
self.bans[msg.args[1]].append(Ban(msg.args))
def nick_to_host(self, irc, target):
target = target.lower()
if ircutils.isUserHostmask(target):
return target
elif target in self.nicks:
return self.nicks[target]
else:
try:
return irc.state.nickToHostmask(target)
except:
self.sendWhois(irc, target)
if target in self.nicks:
return self.nicks[target]
else:
return "%s!*@*" % target
def die(self):
self.db.close()
global queue
if self.db:
self.db.close()
self.thread_timer.cancel()
queue.clear()
def reset(self):
self.db.close()
global queue
if self.db:
self.db.close()
queue.clear()
self.logs.clear()
self.lastMsgs.clear()
self.lastStates.clear()
self.nicks.clear()
def __call__(self, irc, msg):
try:
@ -99,6 +238,9 @@ class Bantracker(callbacks.Plugin):
self.lastMsgs[irc] = msg
def db_run(self, query, parms, expect_result = False, expect_id = False):
if not self.db:
self.log.error("Bantracker database not open")
return
n_tries = 0
try:
cur = self.db.cursor()
@ -115,11 +257,6 @@ class Bantracker(callbacks.Plugin):
self.db.commit()
return data
def reset(self):
self.logs.clear()
self.lastMsgs.clear()
self.lastStates.clear()
def doLog(self, irc, channel, s):
if not self.registryValue('enabled', channel):
return
@ -128,7 +265,7 @@ class Bantracker(callbacks.Plugin):
self.logs[channel] = []
format = conf.supybot.log.timestampFormat()
if format:
s = time.strftime(format) + " " + ircutils.stripFormatting(s)
s = time.strftime(format, time.gmtime()) + " " + ircutils.stripFormatting(s)
self.logs[channel] = self.logs[channel][-199:] + [s.strip()]
def doKickban(self, irc, channel, nick, target, kickmsg = None):
@ -139,6 +276,9 @@ class Bantracker(callbacks.Plugin):
(channel, target, nick, n, '\n'.join(self.logs[channel])), expect_id=True)
if kickmsg and id and not (kickmsg == nick):
self.db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, kickmsg, n))
if channel not in self.bans:
self.bans[channel] = []
self.bans[channel].append(Ban(mask=target, who=nick, when=time.mktime(time.gmtime())))
def doUnban(self, irc, channel, nick, mask):
if not self.registryValue('enabled', channel):
@ -146,6 +286,15 @@ class Bantracker(callbacks.Plugin):
data = self.db_run("SELECT MAX(id) FROM bans where channel=%s and mask=%s", (channel, mask), expect_result=True)
if len(data) and not (data[0][0] == None):
self.db_run("UPDATE bans SET removal=%s , removal_op=%s WHERE id=%s", (now(), nick, int(data[0][0])))
if not channel in self.bans:
self.bans[channel] = []
idx = None
for ban in self.bans[channel]:
if ban.mask == mask:
idx = self.bans[channel].index(ban)
break
if idx != None:
del self.bans[channel][idx]
def doPrivmsg(self, irc, msg):
(recipients, text) = msg.args
@ -171,10 +320,22 @@ class Bantracker(callbacks.Plugin):
if newNick in c.users:
self.doLog(irc, channel,
'*** %s is now known as %s\n' % (oldNick, newNick))
if oldNick in self.nicks:
del self.nicks[oldNick]
nick = newNick.lower()
hostmask = nick + "!".join(msg.prefix.lower().split('!')[1:])
self.nicks[nick] = hostmask
def doJoin(self, irc, msg):
global queue
for channel in msg.args[0].split(','):
self.doLog(irc, channel,
'*** %s has joined %s\n' % (msg.nick or msg.prefix, channel))
if not channel in self.bans.keys():
self.bans[channel] = []
queue.queue(ircmsgs.mode(channel, 'b'))
nick = msg.nick.lower() or msg.prefix.lower().split('!', 1)[0]
self.nicks[nick] = msg.prefix.lower()
def doKick(self, irc, msg):
if len(msg.args) == 3:
@ -241,7 +402,7 @@ class Bantracker(callbacks.Plugin):
self(irc, m)
return msg
def check_auth(self, irc, msg, args):
def check_auth(self, irc, msg, args, cap='bantracker'):
if not msg.tagged('identified'):
irc.error(conf.supybot.replies.incorrectAuthentication())
return False
@ -251,8 +412,8 @@ class Bantracker(callbacks.Plugin):
irc.error(conf.supybot.replies.incorrectAuthentication())
return False
if not capab(user, 'bantracker'):
irc.error(conf.supybot.replies.noCapability() % 'bantracker')
if not capab(user, cap):
irc.error(conf.supybot.replies.noCapability() % cap)
return False
return user
@ -274,14 +435,14 @@ class Bantracker(callbacks.Plugin):
irc.error("No bansite set, please set supybot.plugins.Bantracker.bansite")
return
sessid = md5.new('%s%s%d' % (msg.prefix, time.time(), random.randint(1,100000))).hexdigest()
self.db_run("INSERT INTO sessions (session_id, user, time) VALUES (%s, %s, %d);",
(sessid, msg.prefix[:msg.prefix.find('!')], int(time.mktime(time.gmtime()))))
irc.reply('Log in at %s/bans/cgi?sess=%s' % (self.registryValue('bansite'), sessid), private=True)
self.db_run("INSERT INTO sessions (session_id, user, time) VALUES (%s, %s, %d);",
( sessid, msg.prefix[:msg.prefix.find('!')], int(time.mktime(time.gmtime())) ) )
irc.reply('Log in at %s/bans.cgi?sess=%s' % (self.registryValue('bansite'), sessid), private=True)
btlogin = wrap(btlogin)
def mark(self, irc, msg, args, channel, target, kickmsg):
"""<nick|hostmask> [<channel>] [<comment>]
"""[<channel>] <nick|hostmask> [<comment>]
Creates an entry in the Bantracker as if <nick|hostmask> was kicked from <channel> with the comment <comment>,
if <comment> is given it will be uses as the comment on the Bantracker, <channel> is only needed when send in /msg
@ -289,14 +450,12 @@ class Bantracker(callbacks.Plugin):
user = self.check_auth(irc, msg, args)
if not user:
return
user.addAuth(msg.prefix)
ircdb.users.setUser(user, flush=False)
if not channel:
irc.error('<channel> must be given if not in a channel')
return
channels = []
for (chan, c) in irc.state.channels.iteritems():
for chan in irc.state.channels.keys():
channels.append(chan)
if not channel in channels:
@ -307,14 +466,7 @@ class Bantracker(callbacks.Plugin):
kickmsg = '**MARK**'
else:
kickmsg = "**MARK** - %s" % kickmsg
if ircutils.isUserHostmask(target):
hostmask = target
else:
try:
hostmask = irc.state.nickToHostmask(target)
except:
irc.reply('Could not get hostmask, using nick instead')
hostmask = target
hostmask = self.nick_to_host(irc, target)
self.doLog(irc, channel.lower(), '*** %s requested a mark for %s\n' % (msg.nick, target))
self.doKickban(irc, channel.lower(), msg.nick, hostmask, kickmsg)
@ -322,25 +474,45 @@ class Bantracker(callbacks.Plugin):
mark = wrap(mark, [optional('channel'), 'something', additional('text')])
def nick_to_host(irc, target):
if ircutils.isUserHostmask(target):
return target
else:
try:
return irc.state.nickToHostmask(target)
except:
return "%s!*@*" % target
nick_to_host = staticmethod(nick_to_host)
def sort_bans(self, channel=None):
data = self.db_run("SELECT mask, removal, channel, id FROM bans", (), expect_result=True)
if channel:
data = [i for i in data if i[2] == channel]
data = [i for i in data if i[1] == None]
mutes = [(i[0][1:], i[3]) for i in data if i[0][0] == '%']
bans = [(i[0], i[3]) for i in data if i[0][0] != '%']
bans = [(i[0], i[3]) for i in data if i[1] == None and '%' not in i[0]]
mutes = [(i[0], i[3]) for i in data if i[1] == None and '%' in i[0]]
return mutes + bans
def get_banId(self, mask, channel):
data = self.db_run("SELECT MAX(id) FROM bans WHERE mask=%s AND channel=%s", (mask, channel), True)[0]
if not data[0]:
return
return int(data[0])
def getBans(self, hostmask, channel):
match = []
if channel:
if channel in self.bans and self.bans[channel]:
for b in self.bans[channel]:
if hostmaskPatternEqual(b.mask, hostmask):
match.append((b.mask, self.get_banId(b.mask,channel)))
data = self.sort_bans(channel)
for e in data:
if hostmaskPatternEqual(e[0], hostmask):
if (e[0], e[1]) not in match:
match.append((e[0], e[1]))
else:
for c in self.bans:
for b in self.bans[c]:
if hostmaskPatternEqual(b.mask, hostmask):
match.append((b.mask, self.get_banId(b.mask,c)))
data = self.sort_bans()
for e in data:
if hostmaskPatternEqual(e[0], hostmask):
if (e[0], e[1]) not in match:
match.append((e[0], e[1]))
return match
def bansearch(self, irc, msg, args, target, channel):
"""<nick|hostmask> [<channel>]
@ -353,16 +525,18 @@ class Bantracker(callbacks.Plugin):
ret.append(t)
return tuple(ret)
if not self.check_auth(irc, msg, args):
user = self.check_auth(irc, msg, args)
if not user:
return
hostmask = self.nick_to_host(irc, target)
data = self.sort_bans(channel)
if 'owner' in list(user.capabilities) or 'admin' in list(user.capabilities):
if len(queue.msgcache) > 0:
irc.reply("Warning: still syncing (%i)" % len(queue.msgcache))
match = []
for e in data:
if ircutils.hostmaskPatternEqual(e[0], hostmask):
match.append((e[0], e[1]))
hostmask = self.nick_to_host(irc, target)
match = self.getBans(hostmask, channel)
if not match:
irc.reply("No matches found for %s in %s" % (hostmask, True and channel or "any channel"))
@ -370,8 +544,27 @@ class Bantracker(callbacks.Plugin):
ret = []
for m in match:
ret.append(format_entry(self.db_run("SELECT mask, operator, channel, time FROM bans WHERE id=%d", m[1], expect_result=True)[0]))
if m[1]:
ret.append((format_entry(self.db_run("SELECT mask, operator, channel, time FROM bans WHERE id=%d", m[1], expect_result=True)[0]), m[1]))
if not ret:
done = []
for c in self.bans:
for b in self.bans[c]:
for m in match:
if m[0] == b.mask:
if not c in done:
irc.reply("Match %s in %s" % (b, c))
done.append(c)
return
for i in ret:
irc.reply("Match: %s by %s in %s on %s" % i)
irc.reply("Match: %s by %s in %s on %s (ID: %s)" % (i[0] + (i[1],)))
if 'botmsg' in list(user.capabilities):
if target.split('!', 1)[0] != '*':
irc.reply("%s/bans.cgi?log=%s&mark=%s" % (self.registryValue('bansite'), i[1], target.split('!')[0]), private=True)
else:
irc.reply("%s/bans.cgi?log=%s" % (self.registryValue('bansite'), i[1]), private=True)
bansearch = wrap(bansearch, ['something', optional('anything', default=None)])
@ -385,34 +578,144 @@ class Bantracker(callbacks.Plugin):
if not self.check_auth(irc, msg, args):
return
if len(queue.msgcache) > 0:
irc.reply("Warning: still syncing (%i)" % len(queue.msgcache))
hostmask = self.nick_to_host(irc, target)
target = hostmask.split('!')[0]
data = self.sort_bans(channel)
match = []
for e in data:
if ircutils.hostmaskPatternEqual(e[0], hostmask):
match.append((e[0], e[1]))
sent = []
target = target.split('!', 1)[0]
match = self.getBans(hostmask, channel)
if not match:
irc.reply("No matches found for %s (%s) in %s" % (target, hostmask, True and channel or "any channel"))
return
ret = []
for m in match:
ret.append(self.db_run("SELECT log FROM bans WHERE id=%d", m[1], expect_result=True))
if m[1]:
ret.append((self.db_run("SELECT log, channel FROM bans WHERE id=%d", m[1], expect_result=True), m[1]))
for log in ret:
lines = [i for i in log[0][0].split('\n') if "<%s>" % target.lower() in i.lower() and i[21:21+len(target)].lower() == target.low$
sent = []
if not ret:
irc.reply("No matches in tracker")
for logs in ret:
log = logs[0]
id = logs[1]
lines = ["%s: %s" % (log[0][1], i) for i in log[0][0].split('\n') if "<%s>" % target.lower() in i.lower() and i[21:21+len(target)].lower() == target.lower()]
show_sep = False
if not lines:
irc.error("No log for %s available" % target)
for l in lines[:5]:
if l not in sent:
irc.reply(l)
sent.append(l)
irc.reply('--')
show_sep = False
irc.error("No log for ID %s available" % id)
else:
for l in lines[:5]:
if l not in sent:
show_sep = True
irc.reply(l)
sent.append(l)
if show_sep:
irc.reply('--')
banlog = wrap(banlog, ['something', optional('anything', default=None)])
def updatebt(self, irc, msg, args, channel):
"""[<channel>]
Update bans in the tracker from channel ban list,
if channel is not given then run in all channels
"""
def getBans(chan):
data = self.db_run("SELECT mask, removal FROM bans WHERE channel=%s", chan, expect_result=True)
return [i[0] for i in data if i[1] == None and "!" in i[0]]
def remBans(chan):
bans = getBans(chan)
old_bans = bans[:]
new_bans = [i.mask for i in self.bans[chan]]
remove_bans = []
for ban in old_bans:
if ban not in new_bans:
remove_bans.append(ban)
bans.remove(ban)
for ban in remove_bans:
self.log.info("Removing ban %s from %s" % (ban, channel))
self.doUnban(irc, channel, "Automated-Removal", ban)
return len(remove_bans)
if not self.check_auth(irc, msg, args, 'owner'):
return
res = 0
if len(queue.msgcache) > 0:
irc.reply("Error: still syncing (%i)" % len(queue.msgcache))
return
try:
if channel:
res += remBans(channel)
else:
for channel in irc.state.channels.keys():
if channel not in self.bans:
self.bans[channel] = []
res += remBans(channel)
except KeyError, e:
irc.error("%s, Please wait longer" % e)
return
irc.reply("Cleared %i obsolete bans" % res)
updatebt = wrap(updatebt, [optional('anything', default=None)])
def comment(self, irc, msg, args, id, kickmsg):
"""<id> [<comment>]
Reads or adds the <comment> for the ban with <id>,
use @bansearch to find the id of a ban"""
def addComment(id, nick, msg):
n = now()
self.db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, msg, n))
def readComment(id):
return self.db_run("SELECT who, comment, time FROM comments WHERE ban_id=%i", (id,), True)
nick = msg.nick
if kickmsg:
addComment(id, nick, kickmsg)
irc.replySuccess()
else:
data = readComment(id)
if data:
for c in data:
irc.reply("%s %s: %s" % (cPickle.loads(c[2]).astimezone(pytz.timezone('UTC')).strftime("%b %d %Y %H:%M:%S"), c[0], c[1]) )
else:
irc.error("No comments recorded for ban %i" % id)
comment = wrap(comment, ['id', optional('text')])
def togglemsg(self, irc, msg, args):
"""takes no arguments
Enable/Disable private mssages from the bot
"""
user = self.check_auth(irc, msg, args)
if not user:
return
if 'botmsg' in list(user.capabilities):
user.removeCapability('botmsg')
irc.reply("Disabled")
else:
user.addCapability('botmsg')
irc.reply("Enabled")
togglemsg = wrap(togglemsg)
def banlink(self, irc, msg, args, id, highlight):
"""<id> [<highlight>]
Returns a direct link to the log of kick/ban <id>
if <highlight> is given, lines containing that will be highlighted
"""
if not self.check_auth(irc, msg, args):
return
if not highlight:
irc.reply("%s/bans.cgi?log=%s" % (self.registryValue('bansite'), id), private=True)
else:
irc.reply("%s/bans.cgi?log=%s&mark=%s" % (self.registryValue('bansite'), id, highlight), private=True)
banlink = wrap(banlink, ['id', optional('somethingWithoutSpaces')])
Class = Bantracker

View File

@ -69,7 +69,7 @@ def capab(prefix, capability):
else:
user = prefix
try:
if 'editfactoids' in ircdb.users.getUser(prefix).capabilities:
if 'editfactoids' in list(ircdb.users.getUser(prefix).capabilities):
return True
else:
return False
@ -148,6 +148,11 @@ class Encyclopedia(callbacks.Plugin):
if text.startswith('tell '):
text = ' ' + text
if '|' in text:
if not retmsg:
retmsg = text[text.find('|')+1:].strip() + ': '
text = text[:text.find('|')].strip()
if '>' in text:
target = text[text.rfind('>')+1:].strip().split()[0]
text = text[:text.rfind('>')].strip()
@ -158,11 +163,6 @@ class Encyclopedia(callbacks.Plugin):
text = text[text.find(' about ')+7:].strip()
retmsg = "%s wants you to know: " % nick
if '|' in text:
if not retmsg:
retmsg = text[text.find('|')+1:].strip() + ': '
text = text[:text.find('|')].strip()
if target == 'me':
target = nick
if target.lower() != orig_target.lower() and target.startswith('#'):
@ -178,7 +178,7 @@ class Encyclopedia(callbacks.Plugin):
def get_db(self, channel):
db = self.registryValue('database',channel)
if channel in self.databases:
if self.databases[channel].time < time.time() - 3600:
if self.databases[channel].time < time.time() - 3600 or self.databases[channel].name != db:
self.databases[channel].close()
self.databases.pop(channel)
if channel not in self.databases:
@ -339,11 +339,24 @@ class Encyclopedia(callbacks.Plugin):
lower_text = text.lower()
ret = ''
retmsg = ''
if lower_text[:4] not in ('info','find'):
term = self.get_target(msg.nick, orig_text, target)
if term[0] == "info":
ret = "Retrieve information on a package: !info <package>"
retmsg = term[2]
elif term[0] == "find":
ret = "Search for a pacakge or a file: !find <term/file>"
retmsg = term[2]
elif term[0] == "search":
ret = "Search factoids for term: !search <term>"
retmsg = term[2]
elif term[0] == "seen":
ret = "I have no seen command"
retmsg = term[2]
elif lower_text[:4] not in ('info ','find '):
# Lookup, search or edit?
if lower_text.startswith('search '):
ret = self.search_factoid(lower_text[7:].strip(), channel)
elif (' is ' in lower_text and text[:3] in ('no ', 'no,')) or '<sed>' in lower_text or '=~' in lower_text \
elif (' is ' in lower_text and lower_text[:3] in ('no ', 'no,')) or '<sed>' in lower_text or '=~' in lower_text \
or '~=' in lower_text or '<alias>' in lower_text or lower_text.startswith('forget') or lower_text.startswith('unforget'):
if not capab(msg.prefix, 'editfactoids'):
irc.reply("Your edit request has been forwarded to %s. Thank you for your attention to detail" %
@ -352,7 +365,18 @@ class Encyclopedia(callbacks.Plugin):
(msg.args[0], msg.nick, msg.args[1])))
return
ret = self.factoid_edit(text, channel, msg.prefix)
elif ' is ' in lower_text and (' is ' in lower_text and '|' in lower_text and lower_text.index('|') > lower_text.index(' is ')):
elif ' is ' in lower_text and '|' in lower_text and lower_text.index('|') > lower_text.index(' is '):
if not capab(msg.prefix, 'editfactoids'):
if len(text[:text.find('is')]) > 15:
irc.error("I am only a bot, please don't think I'm intelligent :)")
else:
irc.reply("Your edit request has been forwarded to %s. Thank you for your attention to detail" %
self.registryValue('relaychannel',channel),private=True)
irc.queueMsg(ircmsgs.privmsg(self.registryValue('relaychannel',channel), "In %s, %s said: %s" %
(msg.args[0], msg.nick, msg.args[1])))
return
ret = self.factoid_add(text, channel, msg.prefix)
elif ' is ' in lower_text:
if not capab(msg.prefix, 'editfactoids'):
if len(text[:text.find('is')]) > 15:
irc.error("I am only a bot, please don't think I'm intelligent :)")
@ -390,7 +414,7 @@ class Encyclopedia(callbacks.Plugin):
retmsg = ''
ret = self.registryValue('notfoundmsg')
if ret.count('%') == ret.count('%s') == 1:
ret = ret % text
ret = ret % repr(text)
if not target.startswith('#') and not channel.lower() == irc.nick.lower():
queue(irc, channel, "%s, please see my private message" % target)
if type(ret) != list:
@ -513,7 +537,7 @@ class Encyclopedia(callbacks.Plugin):
ret = self.check_aliases(channel, factoid)
if ret:
return ret
cs.execute("""INSERT INTO facts (name, value, author, added) VALUES (%s, %s, %s, %s)""",
cs.execute("INSERT INTO facts (name, value, author, added) VALUES (%s, %s, %s, %s)",
(name, value, editor, str(datetime.datetime.now(pytz.timezone("UTC")))))
db.commit()
return "I'll remember that, %s" % editor[:editor.find('!')]
@ -554,14 +578,20 @@ class Encyclopedia(callbacks.Plugin):
cur.execute("SELECT name,value FROM facts WHERE name LIKE '%%%s%%' OR VAlUE LIKE '%%%s%%'" % (k, k))
res = cur.fetchall()
for r in res:
val = r[1]
d = r[1].startswith('<deleted>')
a = r[1].startswith('<alias>')
r = r[0]
if d:
r += '*'
if a:
r += '@' + val[7:].strip()
try:
ret[r] += 1
except:
ret[r] = 1
if not ret:
return "None found"
return 'Found: %s' % ', '.join(sorted(ret.keys(), lambda x, y: cmp(ret[x], ret[y]))[:10])
def sync(self, irc, msg, args):

View File

@ -21,6 +21,7 @@ import random, re, time, commands, urllib2
import supybot.ircmsgs as ircmsgs
import supybot.conf as conf
import threading
import os
mess = {
't': ('Mr. T facts', 'http://4q.cc/?pid=fact&person=mrt', r'<div id="factbox">\s*(?P<fact>.*?)\s*</div>', False),
@ -41,10 +42,10 @@ mess = {
'vmjg59': ('Virtual Matthew Garrett', 'http://www.rjek.com/vmjg59.cgi', r'<body>(?P<fact>.*?)<p>', True),
'shakespeare': ('Shakespeare quotes', 'http://www.pangloss.com/seidel/Shaker/', r'<font.*?>(?P<fact>.*?)</font>', False),
'lugradio': ('Lugradio facts', 'http://planet.lugradio.org/facts/', r'<h2>\s*(?P<fact>.*?)</h2>', False),
'bofh': ('BOFH excuses', '%s/Mess/bofh.txt' % conf.supybot.directories.plugins()[1], 'BOFH Excuse #%d: ', False),
'42': ('HHGTTG quotes', '%s/Mess/42.txt' % conf.supybot.directories.plugins()[1], '', False),
'magic8ball': ('The magic 8ball', '%s/Mess/ball.txt' % conf.supybot.directories.plugins()[1], '', False),
'ferengi': ('Ferengi rules of acquisition', '%s/Mess/ferengi.txt' % conf.supybot.directories.plugins()[1], 'Ferengi rule of acquisition ', False),
'bofh': ('BOFH excuses', '%s/bofh.txt' % os.path.split(os.path.abspath(__file__))[0], 'BOFH Excuse #%d: ', False),
'42': ('HHGTTG quotes', '%s/42.txt' % os.path.split(os.path.abspath(__file__))[0], '', False),
'magic8ball': ('The magic 8ball', '%s/ball.txt' % os.path.split(os.path.abspath(__file__))[0], '', False),
'ferengi': ('Ferengi rules of acquisition', '%s/ferengi.txt' % os.path.split(os.path.abspath(__file__))[0], 'Ferengi rule of acquisition ', False),
}
data = {}
for m in mess.keys():