#!/usr/bin/env python ### # Copyright (c) 2005-2007 Dennis Kaarsemaker # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ### import sys import time, datetime, pytz import ConfigParser import sqlite import cPickle as pickle CONFIG_FILENAME = "bantracker.conf" config = ConfigParser.RawConfigParser() config.add_section('webpage') # set default values config.set('webpage', 'database', '/home/bot/data/bans.db') config.set('webpage', 'results_per_page', '100') config.set('webpage', 'anonymous_access', 'True') config.set('webpage', 'PLUGIN_PATH', '/var/www/bot') config.set('webpage', 'irc_network', 'irc.freenode.net') config.set('webpage', 'irc_channel', '#ubuntu-ops') try: config.readfp(open(CONFIG_FILENAME)) except IOError: try: config.write(open(CONFIG_FILENAME, 'w')) except IOError: pass # This needs to be set to the location of the commoncgi.py file PLUGIN_PATH = config.get('webpage', 'PLUGIN_PATH') if PLUGIN_PATH: sys.path.append(PLUGIN_PATH) try: from commoncgi import * except: print "Content-Type: text/html" print print "

Failed to load the module commoncgi

" print "

Check that the configured option PLUGIN_PATH is correct.

" sys.exit(-1) db = config.get('webpage', 'database') num_per_page = config.getint('webpage', 'results_per_page') anonymous_access = config.getboolean('webpage', 'anonymous_access') irc_network = config.get('webpage', 'irc_network') irc_channel = config.get('webpage', 'irc_channel') t1 = time.time() try: con = sqlite.connect(db) cur = con.cursor() except sqlite.DatabaseError: print >> sys.stderr, "Unable to connect to to database '%s'" % db send_page('bans.tmpl') def db_execute(query, args): try: cur.execute(query, args) return cur except sqlite.OperationalError: print >> sys.stderr, "The database is locked, wait a bit and try again." send_page('bans.tmpl') # Login check error = '' user = None # Delete old sessions try: session_timeout = int(time.time()) - (2592000 * 3) cur.execute('DELETE FROM sessions WHERE time < %d', (session_timeout,)) except: pass # Session handling if 'sess' in form: cookie['sess'] = form['sess'].value if 'sess' in cookie: sess = cookie['sess'].value try: cur.execute('SELECT user FROM sessions WHERE session_id=%s',(sess,)) user = cur.fetchall()[0][0] except: con.commit() pass if not user and not anonymous_access: print "Sorry, bantracker is not available for anonymous users
" print 'Join %s on %s to discuss bans.' % (irc_network, irc_channel[1:], irc_channel, irc_network) send_page('bans.tmpl') haveQuery = False def urlencode(**kwargs): """Return the url options as a string, inserting additional ones if given.""" d = dict([ (i.name, i.value) for i in form.list ]) d.update(kwargs) return utils.web.urlencode(d.items()) def isTrue(value): """Returns True if the form value is one of "1", "true", "yes", or "on", case insensitive""" if not value: return False return value.lower() in ('1', 'true', 'yes', 'on') def isFalse(value): """Returns True if the form value is one of "0", "false", "no", or "off", case insensitive""" if not value: return False return value.lower() in ('0', 'false', 'no', 'off') def isOn(k): global haveQuery default = not haveQuery if not k in form: return default if isTrue(form[k].value): return True if isFalse(form[k].value): return False return default # Log if 'log' in form: log_id = form['log'].value plain = False mark = False mark_value = '' regex = False regex_value = '' if 'plain' in form and isTrue(form['plain'].value): plain = True if 'mark' in form: mark = True mark_value = form['mark'].value if 'regex' in form and isTrue(form['regex'].value): regex = True regex_value = 'checked="checked"' log = db_execute("SELECT log FROM bans WHERE id=%s", log_id).fetchall() if not log or not log[0] or not log[0][0]: if plain: print >> sys.stderr, '
No such log with ID: %s' % q(log_id) send_page('empty.tmpl') else: print >> sys.stderr, 'No such log with ID: %s' % q(log_id) send_page('log.tmpl') log = log[0][0] if not plain: print '
' print '
' print '
' print ' ' % q(log_id) print ' ' print ' ' % q(mark_value) print ' ' % regex_value print ' ' print '
' print ' ' print '
' print '
' pad = '
' if plain: pad = '' print '
'
    else:
        print '
' if mark: if regex: try: mark = re.compile(mark_value, re.I) except: print >> sys.stderr, "Malformed regex %r" % mark_value mark = False else: escaped = re.escape(mark_value).replace('%', '.*') mark = re.compile(escaped, re.I) lines = log.splitlines() for line in lines: if plain: print q(line) elif mark: if mark.search(line): print ' %s%s' % (q(line), pad) else: print " %s%s" % (q(line), pad) else: print ' %s%s' % (q(line), pad) if plain: print '
' send_page('empty.tmpl') print '

' print '
' print '
' print '
' print ' Add a comment' print '
' print ' ' % log_id print ' ' print '
' print '
' print '
' send_page('log.tmpl') # Main page # Process comments if 'comment' in form and 'comment_id' in form and user: 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): try: 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() except sqlite.DatabaseError: con.rollback() print >> sys.stderr, "Sorry, failed to submit comment to the database. Please try again later." # Write the page print '
' # Personal data print '
' if user: print 'Logged in as: %s
' % user print 'Timezone: ' if 'tz' in form and form['tz'].value in pytz.common_timezones: tz = form['tz'].value elif 'tz' in cookie and cookie['tz'].value in pytz.common_timezones: tz = cookie['tz'].value else: tz = 'UTC' cookie['tz'] = tz print '' print '' print '
' print '
' tz = pytz.timezone(tz) haveQuery = 'query' in form or 'channel' in form or 'operator' in form def makeInput(name, label, before=False, type="checkbox", extra=''): if before: print '' % (name, label) value = '' if type == "checkbox": if isOn(name): value = ' checked="checked"' else: if name in form: value = ' value="%s"' % form[name].value, print ' %s' \ % (type, name, name, value, extra) if not before: print '' % (name, label) print '
' # Search form print '' if not haveQuery: # sqlite2 sucks, getting the last bans takes a lot of time. # so lets disable that so at least the page loads quickly. ## Maybe we should include a link on the main page for those who do want ## to list the latest bans? --tsimpson print '
' send_page('bans.tmpl') # Select and filter bans def getBans(id=None, mask=None, kicks=True, oldbans=True, bans=True, marks=True, floodbots=True, operator=None, channel=None, limit=None, offset=0, withCount=False): sql = "SELECT channel, mask, operator, time, removal, removal_op, id FROM bans" args = [] where = [] if id: where.append("id=%s") args.append(id) if mask: where.append("mask LIKE %s") args.append('%' + mask + '%') if not floodbots: where.append("operator NOT LIKE 'floodbot%%'") if operator: where.append("operator LIKE %s") args.append(operator) if channel: where.append("channel LIKE %s") args.append(channel) if not marks: where.append("id NOT IN (SELECT ban_id FROM comments WHERE comment LIKE '**MARK**%%')") if not kicks: where.append("mask LIKE '%%!%%'") if not (oldbans or bans): where.append("mask NOT LIKE '%%!%%'") else: if kicks: s = "(mask NOT LIKE '%%%%!%%%%' OR (mask LIKE '%%%%!%%%%' AND %s))" else: s = "%s" if not oldbans: where.append(s % "removal IS NULL") elif not bans: where.append(s % "removal IS NOT NULL") if where: where = " WHERE " + " AND ".join(where) else: where = '' sql += where sql += " ORDER BY id DESC" if limit: sql += " LIMIT %s OFFSET %s" % (limit, offset) #print sql, "
" #print args, "
" # Things seems faster if we do the query BEFORE counting. Due to caches probably. bans = db_execute(sql, args).fetchall() count = None if withCount: sql_count = "SELECT count(*) FROM bans%s" % where count = int(db_execute(sql_count, args).fetchone()[0]) return bans, count return bans def filterMutes(item): if item[1][0] == '%': return False return True def getQueryTerm(query, term): if term[-1] != ':': term += ':' if term in query: idx = query.index(term) + len(term) ret = query[idx:].split(None, 1)[0] query = query.replace(term + ret, '', 1).strip() return (query, ret) return (query, None) page = 0 if 'page' in form: page = int(form['page'].value) bans = [] ban_count = 0 query = oper = chan = None if 'query' in form: query = form['query'].value if query and query.isdigit(): bans = getBans(id=int(query)) ban_count = len(bans) if not bans: if 'channel' in form: chan = form['channel'].value if 'operator' in form: oper = form['operator'].value filter_mutes = not (isOn('mutes') or isOn('oldmutes')) filter_bans = not (isOn('bans') or isOn('oldbans')) filter_bans_or_mutes = bool(filter_bans) ^ bool(filter_mutes) if filter_bans_or_mutes: # we are going to filter the mutes from ban list, this sucks, because then we can't # paginate correctly using SQL with LIMIT and OFFSET, so we *have* to get all bans and # paginate manually. limit = offset = None else: limit = num_per_page offset = num_per_page * page bans, ban_count = getBans(mask=query, kicks=isOn('kicks'), oldbans=isOn('oldbans') or isOn('oldmutes'), bans=isOn('bans') or isOn('mutes'), marks=isOn('marks'), floodbots=isOn('floodbots'), operator=oper, channel=chan, limit=limit, offset=offset, withCount=True) #print 'filtering', filter_bans_or_mutes, '
' #print "total count", ban_count, "bans", len(bans), '
' if filter_bans_or_mutes: if filter_mutes: bans = filter(lambda x: filterMutes(x), bans) elif filter_bans: bans = filter(lambda x: not filterMutes(x), bans) ban_count = len(bans) # pick the bans for current page, since we didn't do it with SQL if ban_count > num_per_page: bans = bans[page * num_per_page: (page + 1) * num_per_page] #print "total count after filtering", ban_count, '
' # Sort the bans def _sortf(x1,x2,field): if x1[field] < x2[field]: return -1 if x1[field] > x2[field]: return 1 return 0 if 'sort' in form: try: field = int(form['sort'].value) except: pass else: if field in (0,1,2,6,10,11,12,16): bans.sort(lambda x1,x2: _sortf(x1,x2,field%10)) if field >= 10: bans.reverse() if haveQuery: if not ban_count: print '
Nothing found.
' elif ban_count == 1: print '
Found one match.
' else: print '
Found %s matches.
' % ban_count # Pagination if bans: pagination = '
\n·\n' num_pages = int(math.ceil(ban_count / float(num_per_page))) for i in range(num_pages): if page == i: pagination += '[%d] ·\n' % (urlencode(page=i), i + 1) else: pagination += '%d ·\n' % (urlencode(page=i), i + 1) pagination += '
\n' print pagination else: # nothign to show print '
' # if I don't print this the page is messed up. send_page('bans.tmpl') # Empty log div, will be filled with AJAX print '
 
' # Main bans table # Table heading print '
' print '' print '' print '' for h in [ ('Channel', 0, 45), ('Nick/Mask', 1, 25), ('Operator', 2, 0), ('Time', 6, 15) ]: # Negative integers for backwards searching try: v = int(form['sort'].value) if v < 10: h[1] += 10 except: pass #print '' % (h[2], h[1], h[0]) print '' % (h[2], h[0]) print '' print '' print '' print '' print '' # And finally, display them! i = 0 for b in bans: if i % 2: print '' else: print "" # Channel print '' % (b[6],'',b[0]) # Mask print '' # Operator print '' # Time print '' # Log link print """""" % (b[6], b[6], b[6]) # ID print '' % (b[6], b[6]) print "" # Comments if i % 2: print '' else: print "" db_execute('SELECT who, comment, time FROM comments WHERE ban_id=%d', (b[6],)) comments = cur.fetchall() if len(comments) == 0: print '' i += 1 print '
%s%sLogID
%s %s%s' % (b[6], b[1]) # Ban removal if b[4]: print '
(Removed)' print'
%s' % (b[6], b[2]) if b[4]: # Ban removal print u'
%s' % b[5] print '
%s' % (b[6], pickle.loads(b[3]).astimezone(tz).strftime("%b %d %Y %H:%M:%S")) if b[4]: # Ban removal print '
%s' % pickle.loads(b[4]).astimezone(tz).strftime("%b %d %Y %H:%M:%S") print '
Show log inline | full %d
' print '' % b[6] print '(No comments) ' else: print '' % b[6] print '' % b[6] for c in comments: print q(c[1]).replace('\n', '
') print u'
%s, %s

' % \ (c[0],pickle.loads(c[2]).astimezone(tz).strftime("%b %d %Y %H:%M:%S")) if user: print """Add comment""" % b[6] print """""" print '
' if bans: print pagination t2 = time.time() print "" % (t2 - t1) # Aaaaaaaaaaaaaaaaand send! send_page('bans.tmpl')