Initial checkin

This commit is contained in:
Dennis Kaarsemaker 2006-06-26 19:57:20 +02:00
commit aea38c93ff
43 changed files with 4193 additions and 0 deletions

2
.bzrignore Normal file
View File

@ -0,0 +1,2 @@
*tar.gz
*pyc

1
Bantracker/README.txt Normal file
View File

@ -0,0 +1 @@
Insert a description of your plugin here, with any notes, etc. about using it.

22
Bantracker/__init__.py Normal file
View File

@ -0,0 +1,22 @@
"""
This plugin can store all kick/ban/remove/mute actions
"""
import supybot
import supybot.world as world
__version__ = "0.2"
__author__ = supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net")
__contributors__ = {}
__url__ = 'https://bots.ubuntulinux.nl'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

349
Bantracker/bans.cgi Executable file
View File

@ -0,0 +1,349 @@
#!/usr/bin/python
import sys
sys.path.append('/home/dennis/public_html')
from commoncgi import *
import lp_auth
### Variables
db = '/home/dennis/ubugtu/data/bans.db'
lp_group = 'ubuntu-irc'
num_per_page = 100
con = sqlite.connect(db)
cur = con.cursor()
# Login check
person = None
error = ''
# Delete old sessions
cur.execute("""DELETE FROM sessions WHERE time < %d""", int(time.time()) - 86400)
# Registration?
if form.has_key('lpuser') and form.has_key('lpmail'):
cur.execute("""SELECT * FROM USERS WHERE username = %s""", form['lpuser'].value)
if len(cur.fetchall()):
error = """User is already registered"""
else:
import sha, commands, random
try:
newperson = lp_auth.LaunchpadPerson(nick=form['lpuser'].value, email=form['lpmail'].value)
except:
error = """Username incorrect. Your username is the $someone in
http://launchpad.net/people/$someone that is your
launchpad homepage"""
else:
mailsha = sha.new('mailto:%s' % form['lpmail'].value).hexdigest().lower()
if mailsha in newperson.mail_shasums:
if not newperson.key:
error = """Your launchpad account does not have a GPG key. Please
set a GPG key on launchpad"""
else:
chars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
password = ""
salt = ""
for i in xrange(8):
password += chars[random.randint(0,len(chars)-1)]
salt += chars[random.randint(0,len(chars)-1)]
os.system('gpg --homedir /tmp --keyserver hkp://subkeys.pgp.net --recv-keys %s' % newperson.key)
(infd, outfd) = os.popen2('gpg --homedir /tmp --encrypt --armor --trust-model always --recipient %s'
% newperson.key)
infd.write(password)
infd.close()
gpg = outfd.read()
outfd.close()
fd = os.popen('mail -a "From: Ubugtu <ubugtu@ubuntu-nl.org>" -s "Your bantracker account" %s'
% form['lpmail'].value.replace('ubuntu@sourceguru.net','mezzle@gmail.com'), 'w')
fd.write(gpg)
fd.close()
error = "Your password has been sent (encrypted) to your e-mail address"
cur.execute("""INSERT INTO users (username, salt, password) VALUES (%s, %s, %s)""",
(form['lpuser'].value, salt,
sha.new(salt + sha.new(password + salt).hexdigest().lower()).hexdigest().lower()))
con.commit()
else:
error = """Username and mailaddress don't match. Username is the $someone
in http://launchpad.net/people/$someone that is your
launchpad homepage"""
# Session handling
if cookie.has_key('sess'):
try:
sess = cookie['sess'].value
cur.execute("""SELECT user FROM sessions WHERE session_id=%s""",sess)
user = cur.fetchall()[0][0]
person = pickle.loads(user)
except:
con.commit()
pass
# Login
if not person and form.has_key('user') and form.has_key('pw'):
import sha
cur.execute("SELECT salt, password FROM users WHERE username = %s", form['user'].value)
data = cur.fetchall()
if data:
salt, password = data[0]
if password != sha.new(salt + sha.new(form['pw'].value + salt).hexdigest().lower()).hexdigest().lower():
error = "Username or password incorrect"
else:
try:
person = lp_auth.LaunchpadPerson(nick = form['user'].value)
except lp_auth.LaunchpadException:
person = None
error = 'An error occured while talking to launchpad'
person.authenticated = True
if person.check_group_membership(lp_group):
# Create a session
sessid = md5.new('%s%s%d' % (os.environ['REMOTE_ADDR'], time.time(), random.randint(1,100000))).hexdigest()
cookie['sess'] = sessid
try:
cur.execute("""INSERT INTO sessions (session_id, user, time) VALUES
(%s, %s, %d);""", (sessid, pickle.dumps(person), int(time.time())))
except:
con.commit()
raise
con.commit()
else:
person.authenticated = False
error = "You are not in the '%s' group on launchpad" % lp_group
# Not authenticated.
if not person or not person.authenticated:
if error:
print """<span style="color:red">%s</span>""" % error
print """<form action="/bans.cgi" method="post">
<b>The old launchpad based authentication system has been
disabled!</b><br /><br />
Login:<br />
<input class="input" type="text" name="user" /><br />
Password:<br />
<input class="input" type="password" name="pw" /><br /><br />
<input class="submit" type="submit" value="Log in" />
</form>
<form>
No account yet? Enter your launchpad name and mailaddress
here.<br /><br />
Name:<br />
<input class="input" type="text" name="lpuser" /><br />
Mail address:<br />
<input class="input" type="text" name="lpmail" /><br /><br />
<input class="submit" type="submit" value="Request password" />
</form>
"""
send_page('bans.tmpl')
# Log
if form.has_key('log'):
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 />')
send_page('empty.tmpl')
# Main page
# Process comments
if form.has_key('comment') and form.has_key('comment_id'):
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,person.name,form['comment'].value,pickle.dumps(datetime.datetime.now(pytz.UTC))))
con.commit()
# Write the page
print '<form action="bans.cgi" method="POST">'
# Personal data
print '<div class="pdata"> Logged in as: %s <br /> Timezone: ' % person.name
if form.has_key('tz') and form['tz'].value in pytz.common_timezones:
tz = form['tz'].value
elif cookie.has_key('tz') and cookie['tz'].value in pytz.common_timezones:
tz = cookie['tz'].value
else:
tz = 'UTC'
cookie['tz'] = tz
print '<select class="input" name="tz">'
for zone in pytz.common_timezones:
print '<option value="%s"' % zone
if zone == tz:
print ' selected="selected"'
print ">%s</option>" % zone
print '</select><input class="submit" type="submit" value="change" /></form></div>'
tz = pytz.timezone(tz)
# Search form
print '<div class="search">'
print '<form action="/bans.cgi" method="GET">'
print '<input class="input" type="text" name="query"'
if form.has_key('query'):
print 'value="%s" ' % form['query'].value
print '/> Search string (% is wildcard)<br />'
# Search fields
print '<div style="float:left">'
print '<input class="input" type="checkbox" name="kicks" '
if form.has_key('kicks') or not form.has_key('query'):
print 'checked="checked" '
print '/> Search in kicks<br />'
print '<input class="input" type="checkbox" name="oldbans" '
if form.has_key('oldbans') or not form.has_key('query'):
print 'checked="checked" '
print '/> Search in removed bans<br />'
print '<input class="input" type="checkbox" name="bans" '
if form.has_key('bans') or not form.has_key('query'):
print 'checked="checked" '
print '/> Search in existing bans<br />'
print '</div>'
print '<div style="float:left">'
print '<input class="input" type="checkbox" name="oldmutes" '
if form.has_key('oldmutes') or not form.has_key('query'):
print 'checked="checked" '
print '/> Search in removed mutes<br />'
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 '</div>'
print '<div style="clear:both"><input class="submit" type="submit" value="search" /></div>'
print '</form></div>'
# Pagination, only when not processing a search
if not form.has_key('query'):
sort = ''
if form.has_key('sort'):
sort='&sort=' + form['sort'].value
print '<div style="clear: both">&middot;'
cur.execute('SELECT COUNT(id) FROM bans')
nump = math.ceil(int(cur.fetchall()[0][0]) / float(num_per_page))
for i in range(nump):
print '<a href="bans.cgi?page=%d%s">%d</a> &middot;' % (i, sort, i+1)
print '</div>'
# Empty log div, will be filled with AJAX
print '<div id="log" class="log">&nbsp;</div>'
# Main bans table
# Table heading
print '<table cellspacing="0" ><tr>'
for h in [['Channel',0], ['Nick/Mask',1], ['Operator',2], ['Time',6]]:
# Negative integers for backwards searching
try:
v = int(form['sort'].value)
if v < 10: h[1] += 10
except:
pass
print '<th><a href="bans.cgi?sort=%s">%s</a></th>' % (h[1],h[0])
print '<th>Log</th></tr>'
# Select and filter bans
cur.execute("SELECT channel,mask,operator,time,removal,removal_op,id FROM bans ORDER BY id DESC")
bans = cur.fetchall()
def myfilter(item, regex, kick, ban, oldban, mute, oldmute):
if '!' not in item[1]:
if not kick: return False
elif item[1][0] == '%':
if item[4]:
if not oldmute: return False
else:
if not mute: return False
else:
if item[4]:
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 form.has_key('query'):
k = b = ob = m = om = 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
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)
start = 0; end = len(bans)
else:
page = 0
try:
page = int(form['page'].value)
except:
pass
start = page * num_per_page
end = (page+1) * num_per_page
# 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 form.has_key('sort'):
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()
# And finally, display them!
i = 0
for b in bans[start:end]:
print '<tr'
if i % 2:
print ' class="bg2"'
i += 1
print '>'
# Channel
print '<td>%s %s</td>' % ('',b[0])
# Mask
print '<td>%s' % b[1]
# Ban removal
if b[4]:
print '<br /><span class="removal">(Removed)</span>'
print'</td>'
# Operator
print '<td>%s' % b[2]
if b[4]: # Ban removal
print '<br /><span class="removal">%s</span>' % b[5]
print '</td>'
# Time
print '<td>%s' % pickle.loads(b[3]).astimezone(tz).strftime("%b %d %Y %H:%M:%S")
if b[4]: # Ban removal
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/Hide log</span></td>""" % b[6]
print '</tr>'
# Comments
print '<tr'
if not i % 2:
print ' class="bg2"'
print '>'
print '<td colspan="5" class="comment">'
cur.execute("""SELECT who, comment, time FROM comments WHERE ban_id = %s""" % b[6])
comments = cur.fetchall()
if len(comments) == 0:
print '<span class="removal">(No comments) </span>'
else:
for c in comments:
print '%s <span class="removal"><br />%s, %s</span><br />' % \
(q(c[1]),c[0],pickle.loads(c[2]).astimezone(tz).strftime("%b %d %Y %H:%M:%S"))
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>'
# Aaaaaaaaaaaaaaaaand send!
send_page('bans.tmpl')

68
Bantracker/bans.tmpl Normal file
View File

@ -0,0 +1,68 @@
<html>
<head>
<title>Ubugtu bantracker</title>
<link rel="stylesheet" href="/bot.css" />
<link rel="shortcut icon" href="favicon.ico" type="image/png" />
<script type="text/javascript">
var DHTML = (document.getElementById || document.all || document.layers);
function getObj(name) {
if (document.getElementById) {
this.obj = document.getElementById(name);
this.style = document.getElementById(name).style;
}
else if (document.all) {
this.obj = document.all[name];
this.style = document.all[name].style;
}
else if (document.layers) {
this.obj = document.layers[name];
this.style = document.layers[name];
}
}
function toggle(item,prefix) {
var c = new getObj(prefix + '_' + item);
if ( c.style.display == 'inline' ) {
c.style.display = 'none';
}
else {
c.style.display = 'inline';
}
}
function showlog(item) {
var c = new getObj('log');
if ( c.style.display == 'block' ) {
c.style.display = 'none';
}
else {
loadlog(item);
}
}
var r;
function loadlog(id) {
r = new XMLHttpRequest();
r.onreadystatechange = printlog;
r.open("GET",'https://bugbot.ubuntulinux.nl/bans.cgi?log=' + id, true);
r.send(null);
}
function printlog() {
if (r.readyState == 4) {
var c = new getObj('log');
c.obj.innerHTML = r.responseText;
c.style.display = 'block';
}
}
</script>
</head>
<body>
<div class="main">
<h1>Ubugtu Bantracker</h1>
<p>
%s
</p>
<p>&copy;2006 Dennis Kaarsemaker</p>
</div>
</body>
</html>

9
Bantracker/config.py Normal file
View File

@ -0,0 +1,9 @@
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
conf.registerPlugin('Bantracker', True)
Bantracker = conf.registerPlugin('Bantracker')
conf.registerChannelValue(conf.supybot.plugins.BanTracker, 'enabled',
registry.Boolean(False, """Enable the bantracker"""))

1
Bantracker/empty.tmpl Normal file
View File

@ -0,0 +1 @@
%s

91
Bantracker/lp_auth.py Executable file
View File

@ -0,0 +1,91 @@
#!/usr/bin/python
#
# Simple authentication against launchpad and group membership checking.
# Feel free to use, modify and distribute as you see fit.
# (c) 2006 Dennis Kaarsemaker <dennis@kaarsemaker.net>
import urllib,urllib2
import xml.dom.minidom as dom
import sha, re
_login_url = 'https://launchpad.net/+login'
_login_data = 'loginpage_email=%s&loginpage_password=%s&loginpage_submit_login=Log%%20In'
_login_re = re.compile('logged in as.*?a href=".*?/people/(.*?)"', re.DOTALL)
_urlopener = urllib2.build_opener(urllib2.HTTPCookieProcessor(), urllib2.HTTPRedirectHandler())
class LaunchpadException(Exception):
pass
class LaunchpadPerson:
def __init__(self, email = None, password = None, nick = None):
self.authenticated = False
if email and password:
self.email = email
_nick = self._login(password)
if _nick:
self.authenticated = True
nick = _nick
if nick:
self.get_data(nick)
def get_data(self, nick):
self.nick = nick
try:
u = urllib2.urlopen('http://launchpad.net/people/%s/+rdf' % urllib.quote(nick))
rdf = u.read()
rdf = dom.parseString(rdf)
except:
raise
raise LaunchpadException('Could not parse launchpad data')
self.mail_shasums = map(lambda x: x.childNodes[0].data.lower(), rdf.getElementsByTagName('foaf:mbox_sha1sum'))
self.name = rdf.getElementsByTagName('foaf:name')[0].childNodes[0].data
try:
self.img = rdf.getElementsByTagName('foaf:img')[0].getAttribute('rdf:resource')
except: # No image
self.img = None
try:
self.key = rdf.getElementsByTagName('wot:fingerprint')[0].childNodes[0].data
except: # No image
self.key = None
def check_group_membership(self, group):
try:
self.mail_shasums
except AttributeError:
raise LaunchpadException("Person not logged in and launchpad username not known")
try:
fd = urllib2.urlopen('http://launchpad.net/people/%s/+rdf' % urllib.quote(group))
rdf = fd.read()
rdf = dom.parseString(rdf)
except:
raise LaunchpadException('Could not parse launchpad data')
group_mail_shasums = map(lambda x: x.childNodes[0].data.lower(), rdf.getElementsByTagName('foaf:mbox_sha1sum'))
# If the intersection of shasums and shasums2 is not empty, the persons
# prefered mail address is in the group.
return len([x for x in self.mail_shasums if x in group_mail_shasums]) > 0
def _login(self, pw):
req = urllib2.Request(_login_url, _login_data % (urllib.quote(self.email),urllib.quote(pw)))
try:
fd = _urlopener.open(req)
data = fd.read().lower()
except: # Launchpad offline perhaps...
raise LaunchpadException('Could not parse launchpad data')
try:
return _login_re.search(data).group(1)
except:
return False
if __name__ == '__main__':
import sys
person = LaunchpadPerson(sys.argv[1], sys.argv[2])
print person.authenticated
try:
print person.nick
print person.name
print person.mail_shasums
print person.img
except:
raise
pass
print person.check_group_membership(sys.argv[3])

188
Bantracker/plugin.py Normal file
View File

@ -0,0 +1,188 @@
# Based on the standard log plugin
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.ircmsgs as ircmsgs
import supybot.conf as conf
import sqlite, pytz, cPickle, datetime, time
db = '/home/dennis/ubugtu/data/bans.db'
tz = 'Europe/Amsterdam'
def now():
return cPickle.dumps(datetime.datetime.now(pytz.timezone(tz)))
def db_run(query, parms, expect_result = False, expect_id = False):
con = sqlite.connect(db)
cur = con.cursor()
try:
cur.execute(query, parms)
except:
con.close()
raise
data = None
if expect_result: data = cur.fetchall()
if expect_id: data = con.insert_id()
con.commit()
con.close()
return data
class Bantracker(callbacks.Plugin):
"""This plugin has no commands"""
noIgnore = True
def __init__(self, irc):
self.__parent = super(Bantracker, self)
self.__parent.__init__(irc)
self.lastMsgs = {}
self.lastStates = {}
self.logs = {}
def __call__(self, irc, msg):
try:
# I don't know why I put this in, but it doesn't work, because it
# doesn't call doNick or doQuit.
# if msg.args and irc.isChannel(msg.args[0]):
super(self.__class__, self).__call__(irc, msg)
if irc in self.lastMsgs:
if irc not in self.lastStates:
self.lastStates[irc] = irc.state.copy()
self.lastStates[irc].addMsg(irc, self.lastMsgs[irc])
finally:
# We must make sure this always gets updated.
self.lastMsgs[irc] = msg
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
channel = ircutils.toLower(channel)
if channel not in self.logs.keys():
self.logs[channel] = []
format = conf.supybot.log.timestampFormat()
if format:
s = time.strftime(format) + " " + ircutils.stripFormatting(s)
self.logs[channel] = self.logs[channel][-199:] + [s.strip()]
def doKickban(self, irc, channel, nick, target, kickmsg = None):
print "DoKickban: %s - %s - %s - %s" % (channel, nick, target, kickmsg)
if not self.registryValue('enabled', channel):
return
n = now()
id = db_run("INSERT INTO bans (channel, mask, operator, time, log) values(%s, %s, %s, %s, %s)",
(channel, target, nick, n, '\n'.join(self.logs[channel])), expect_id=True)
if kickmsg and id and not (kickmsg == nick):
db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, kickmsg, n))
def doUnban(self, irc, channel, nick, mask):
print "DoUnban: %s - %s - %s" % (channel, nick, mask)
if not self.registryValue('enabled', channel):
return
data = 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):
db_run("UPDATE bans SET removal=%s , removal_op=%s WHERE id=%s", (now(), nick, int(data[0][0])))
def doPrivmsg(self, irc, msg):
(recipients, text) = msg.args
for channel in recipients.split(','):
if irc.isChannel(channel):
nick = msg.nick or irc.nick
if ircmsgs.isAction(msg):
self.doLog(irc, channel,
'* %s %s\n' % (nick, ircmsgs.unAction(msg)))
else:
self.doLog(irc, channel, '<%s> %s\n' % (nick, text))
def doNotice(self, irc, msg):
(recipients, text) = msg.args
for channel in recipients.split(','):
if irc.isChannel(channel):
self.doLog(irc, channel, '-%s- %s\n' % (msg.nick, text))
def doNick(self, irc, msg):
oldNick = msg.nick
newNick = msg.args[0]
for (channel, c) in irc.state.channels.iteritems():
if newNick in c.users:
self.doLog(irc, channel,
'*** %s is now known as %s\n' % (oldNick, newNick))
def doJoin(self, irc, msg):
for channel in msg.args[0].split(','):
self.doLog(irc, channel,
'*** %s has joined %s\n' % (msg.nick or msg.prefix, channel))
def doKick(self, irc, msg):
if len(msg.args) == 3:
(channel, target, kickmsg) = msg.args
else:
(channel, target) = msg.args
kickmsg = ''
if kickmsg:
self.doLog(irc, channel,
'*** %s was kicked by %s (%s)\n' % (target, msg.nick, kickmsg))
else:
self.doLog(irc, channel,
'*** %s was kicked by %s\n' % (target, msg.nick))
self.doKickban(irc, channel, msg.nick, target, kickmsg)
def doPart(self, irc, msg):
for channel in msg.args[0].split(','):
self.doLog(irc, channel, '*** %s has left %s %s\n' % (msg.nick, channel, msg.args[1]))
if msg.args[1].startswith('requested by'):
args = msg.args[1].split()
self.doKickban(irc, channel, args[2].replace(':',''), msg.nick, ' '.join(args[3:])[1:-1].strip())
def doMode(self, irc, msg):
channel = msg.args[0]
if irc.isChannel(channel) and msg.args[1:]:
self.doLog(irc, channel,
'*** %s sets mode: %s %s\n' %
(msg.nick or msg.prefix, msg.args[1],
' '.join(msg.args[2:])))
if 'b' in msg.args[1] or 'd' in msg.args[1]:
i = 2
plusmin = False
print msg.args
for c in msg.args[1]:
if c == '-': plusmin = False
elif c == '+': plusmin = True
else:
if c == 'b':
if plusmin: self.doKickban(irc, channel, msg.nick, msg.args[i])
else: self.doUnban(irc,channel, msg.nick, msg.args[i])
i += 1
if c == 'd':
if plusmin: self.doKickban(irc, channel, msg.nick, msg.args[i] + ' (realname)')
else: self.doUnban(irc,channel, msg.nick, msg.args[i] + ' (realname)')
i += 1
def doTopic(self, irc, msg):
if len(msg.args) == 1:
return # It's an empty TOPIC just to get the current topic.
channel = msg.args[0]
self.doLog(irc, channel,
'*** %s changes topic to "%s"\n' % (msg.nick, msg.args[1]))
def doQuit(self, irc, msg):
for (channel, chan) in self.lastStates[irc].channels.iteritems():
if msg.nick in chan.users:
self.doLog(irc, channel, '*** %s has quit IRC\n' % msg.nick)
def outFilter(self, irc, msg):
# Gotta catch my own messages *somehow* :)
# Let's try this little trick...
if msg.command in ('PRIVMSG', 'NOTICE'):
# Other messages should be sent back to us.
m = ircmsgs.IrcMsg(msg=msg, prefix=irc.prefix)
self(irc, m)
return msg
Class = Bantracker

4
Bantracker/test.py Normal file
View File

@ -0,0 +1,4 @@
from supybot.test import *
class BantrackerTestCase(PluginTestCase):
plugins = ('Bantracker',)

35
Bugtracker/README.txt Normal file
View File

@ -0,0 +1,35 @@
Copyright (c) 2005-2006, 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.
The syntax to add a tracker is weird, here are some examples:
@bugtracker add freedesktop bugzilla https://bugs.freedesktop.org Freedesktop
@bugtracker add malone malone http://launchpad.net/malone Malone
@bugtracker add debian debbugs http://bugs.debian.org Debian
@bugtracker add openoffice issuezilla http://www.openoffice.org/issues OpenOffice
@bugtracker add django trac http://code.djangoproject.com/ticket Django
@bugtracker add gaim sourceforge http://sourceforge.net/tracker/?group_id=235&atid=100235 Gaim
In general: @bugtracker add <name> <type> <baseurl> [description]
Bugtracker dialects (types) this plugin understands:
* Bugzilla
* Issuezilla (OpenOffice.org's tjernobyl transformation of bugzilla)
* Malone
* Debbugs (debbugs sucks donkeyballs - please fix debbugs)
* Trac (with not-too-buggered-up templates, it needs to do screenscraping)
* Sourceforge (needs atid and group_id in the url!)
To request a bug report, use this syntax:
bug 123
bug #123
supybot bug 123
bug 123, 4, 5
bug 1, 3 and 89

35
Bugtracker/__init__.py Normal file
View File

@ -0,0 +1,35 @@
###
# Copyright (c) 2005,2006 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.
#
###
"""
This plugin will display bug information when requested.
"""
import supybot
import supybot.world as world
__version__ = "2.0"
__author__ = supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net")
__contributors__ = {}
__url__ = 'http://bots.ubuntulinux.nl/'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

44
Bugtracker/config.py Normal file
View File

@ -0,0 +1,44 @@
###
# Copyright (c) 2005,2006 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 supybot.conf as conf
import supybot.registry as registry
import supybot.ircutils as ircutils
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Bugtracker', True)
Bugtracker = conf.registerPlugin('Bugtracker')
conf.registerChannelValue(conf.supybot.plugins.Bugtracker, 'bugSnarfer',
registry.Boolean(False, """Determines whether the bug snarfer will be
enabled, such that any Bugtracker URLs and bug ### seen in the channel
will have their information reported into the channel."""))
conf.registerChannelValue(conf.supybot.plugins.Bugtracker, 'replyNoBugtracker',
registry.String('I don\'t have a bugtracker %s.', """Determines the phrase
to use when notifying the user that there is no information about that
bugtracker site."""))
conf.registerChannelValue(conf.supybot.plugins.Bugtracker, 'snarfTarget',
registry.String('', """Determines the bugtracker to query when the
snarf command is triggered"""))
class Bugtrackers(registry.SpaceSeparatedListOfStrings):
List = ircutils.IrcSet
conf.registerGlobalValue(conf.supybot.plugins.Bugtracker, 'bugtrackers',
Bugtrackers([], """Determines what bugtrackers will be added to the bot when it starts."""))
conf.registerGlobalValue(conf.supybot.plugins.Bugtracker, 'replyWhenNotFound',
registry.Boolean(False, """Whether to send a message when a bug could not be
found"""))

577
Bugtracker/plugin.py Normal file
View File

@ -0,0 +1,577 @@
###
# Copyright (c) 2005,2006 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 supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.conf as conf
import supybot.registry as registry
import re
import xml.dom.minidom as minidom
from htmlentitydefs import entitydefs as entities
import email.FeedParser
def registerBugtracker(name, url='', description='', trackertype=''):
conf.supybot.plugins.Bugtracker.bugtrackers().add(name)
group = conf.registerGroup(conf.supybot.plugins.Bugtracker.bugtrackers, name)
URL = conf.registerGlobalValue(group, 'url', registry.String(url, ''))
DESC = conf.registerGlobalValue(group, 'description', registry.String(description, ''))
TRACKERTYPE = conf.registerGlobalValue(group, 'trackertype', registry.String(trackertype, ''))
if url:
URL.setValue(url)
if description:
DESC.setValue(description)
if trackertype:
if defined_bugtrackers.has_key(trackertype.lower()):
TRACKERTYPE.setValue(trackertype.lower())
else:
raise BugtrackerError("Unknown trackertype: %s" % trackertype)
entre = re.compile('&(\S*?);')
def _getnodetxt(node):
L = []
for childnode in node.childNodes:
if childnode.nodeType == childnode.TEXT_NODE:
L.append(childnode.data)
val = ''.join(L)
if node.hasAttribute('encoding'):
encoding = node.getAttribute('encoding')
if encoding == 'base64':
try:
val = val.decode('base64')
except:
val = 'Cannot convert bug data from base64.'
while entre.search(val):
entity = entre.search(val).group(1)
if entity in entities:
val = entre.sub(entities[entity], val)
else:
val = entre.sub('?', val)
return val
class BugtrackerError(Exception):
"""A bugtracker error"""
pass
class BugNotFoundError(Exception):
"""Pity, bug isn't there"""
pass
class Bugtracker(callbacks.PluginRegexp):
"""Show a link to a bug report with a brief description"""
threaded = True
callBefore = ['URL']
regexps = ['turlSnarfer', 'bugSnarfer', 'oopsSnarfer']
def __init__(self, irc):
callbacks.PluginRegexp.__init__(self, irc)
self.db = ircutils.IrcDict()
for name in self.registryValue('bugtrackers'):
registerBugtracker(name)
group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
if group.trackertype() in defined_bugtrackers.keys():
self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description())
else:
raise BugtrackerError("Unknown trackertype: %s" % group.trackertype())
self.shorthand = utils.abbrev(self.db.keys())
def add(self, irc, msg, args, name, trackertype, url, description):
"""<name> <type> <url> [<description>]
Add a bugtracker <url> to the list of defined bugtrackers. <type> is the
type of the tracker (currently only Malone, Debbugs, Bugzilla,
Issuezilla and Trac are known). <name> is the name that will be used to
reference the bugzilla in all commands. Unambiguous abbreviations of
<name> will be accepted also. <description> is the common name for the
bugzilla and will be listed with the bugzilla query; if not given, it
defaults to <name>.
"""
name = name.lower()
if not description:
description = name
if url[-1] == '/':
url = url[:-1]
trackertype = trackertype.lower()
if trackertype in defined_bugtrackers:
self.db[name] = defined_bugtrackers[trackertype](name,url,description)
else:
irc.error("Bugtrackers of type '%s' are not understood" % trackertype)
return
registerBugtracker(name, url, description, trackertype)
self.shorthand = utils.abbrev(self.db.keys())
irc.replySuccess()
add = wrap(add, ['something', 'something', 'url', additional('text')])
def remove(self, irc, msg, args, name):
"""<abbreviation>
Remove the bugtracker associated with <abbreviation> from the list of
defined bugtrackers.
"""
try:
name = self.shorthand[name.lower()]
del self.db[name]
self.registryValue('bugtrackers').remove(name)
self.shorthand = utils.abbrev(self.db.keys())
irc.replySuccess()
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0])
irc.error(s % name)
remove = wrap(remove, ['text'])
def list(self, irc, msg, args, name):
"""[abbreviation]
List defined bugtrackers. If [abbreviation] is specified, list the
information for that bugtracker.
"""
if name:
name = name.lower()
try:
name = self.shorthand[name]
(url, description, type) = (self.db[name].url, self.db[name].description,
self.db[name].__class__.__name__)
irc.reply('%s: %s, %s [%s]' % (name, description, url, type))
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0])
irc.error(s % name)
else:
if self.db:
L = self.db.keys()
L.sort()
irc.reply(utils.str.commaAndify(L))
else:
irc.reply('I have no defined bugtrackers.')
list = wrap(list, [additional('text')])
def bugSnarfer(self, irc, msg, match):
r"""\b(?P<bt>(([a-z]+)?\s+bugs?|[a-z]+))\s+#?(?P<bug>\d+(?!\d*\.\d+)((,|\s*(and|en|et|und))\s*#?\d+(?!\d*\.\d+))*)"""
if not self.registryValue('bugSnarfer', msg.args[0]):
return
# Don't double on commands
s = str(msg).split(':')[2]
if s[0] in str(conf.supybot.reply.whenAddressedBy.chars):
return
sure_bug = match.group('bt').endswith('bug') or match.group('bt').endswith('bug')
# FIXME dig into supybot docs/code
#if conf.supybot.reply.whenAddressedBy.strings:
# print type(conf.supybot.reply.whenAddressedBy.strings)
# for p in conf.supybot.reply.whenAddressedBy.strings:
# if s.startswith(str(p)):
# return
# Get tracker name
bugids = match.group('bug')
reps = ((' ',''),('#',''),('and',','),('en',','),('et',','),('und',','))
for r in reps:
bugids = bugids.replace(r[0],r[1])
bugids = bugids.split(',')[:5]
bt = map(lambda x: x.lower(), match.group('bt').split())
name = ''
if len(bt) == 1 and not (bt[0] in ['bug','bugs']):
try:
name = bt[0].lower()
tracker = self.db[name]
except:
return
elif len(bt) == 2:
try:
name = bt[0].lower()
tracker = self.db[name]
except:
name = ''
pass
if not name:
snarfTarget = self.registryValue('snarfTarget', msg.args[0])
if not snarfTarget:
return
try:
name = self.shorthand[snarfTarget.lower()]
except:
s = self.registryValue('replyNoBugtracker', name)
irc.error(s % name)
try:
tracker = self.db[name]
except KeyError:
s = self.registryValue('replyNoBugtracker', name)
irc.error(s % name)
else:
for bugid in bugids:
bugid = int(bugid)
try:
report = self.get_bug(tracker,bugid)
except BugtrackerError, e:
if not sure_bug and bugid < 30:
return
irc.error(str(e))
else:
irc.reply(report, prefixNick=False)
#show_bug.cgi?id=|bugreport.cgi?bug=|(bugs|+bug)/|ticket/|tracker/.*aid=
#&group_id=\d+&at_id=\d+
def turlSnarfer(self, irc, msg, match):
"(?P<tracker>https?://.*?)(show_bug.cgi\?id=|bugreport.cgi\?bug=|(bugs|\+bug)/|/ticket/|tracker/.*aid=)(?P<bug>\d+)(?P<sfurl>&group_id=\d+&at_id=\d+)?"
if not self.registryValue('bugSnarfer', msg.args[0]):
return
try:
tracker = self.get_tracker(match.group(0),match.group('sfurl'))
if not tracker:
return
report = self.get_bug(tracker,int(match.group('bug')), do_url = False)
except BugtrackerError, e:
irc.error(str(e))
else:
irc.reply(report, prefixNick=False)
turlSnarfer = urlSnarfer(turlSnarfer)
# Only useful for launchpad developers
def oopsSnarfer(self, irc, msg, match):
r"OOPS-(?P<oopsid>\d*[A-Z]\d+)"
oopsid = match.group(1)
irc.reply("https://chinstrap.ubuntu.com/~jamesh/oops.cgi/%s" % oopsid, prefixNick=False)
def get_tracker(self,snarfurl,sfdata):
for t in self.db.keys():
tracker = self.db[t]
url = tracker.url.replace('http://','').replace('https://','')
if 'sourceforge.net' in url:
# Try to find the correct sf tracker
if str(sfdata) in tracker.url:
return tracker
if '/' in url:
url = url[:url.index('/')]
if url in snarfurl:
return tracker
if 'sourceforge.net' in snarfurl:
return self.db['sourceforge']
# No tracker found, bummer. Let's try and add one
if 'show_bug.cgi' in snarfurl:
tracker = Bugzilla().get_tracker(snarfurl)
if tracker:
self.db[tracker.name] = tracker
self.shorthand = utils.abbrev(self.db.keys())
return tracker
return None
def get_bug(self, tracker, id, do_url = True):
(product, title, severity, status, url) = tracker.get_bug(id)
severity = severity[0].upper() + severity[1:].lower()
status = status[0].upper() + status[1:].lower()
if not do_url:
url = ''
if product:
return "%s bug %s in %s \"%s\" [%s,%s] %s" % (tracker.description, id, product,
title, severity, status, url)
return "%s bug %s \"%s\" [%s,%s] %s" % (tracker.description, id, title, severity, status, url)
# Define all bugtrackers
class IBugtracker:
def __init__(self, name=None, url=None, description=None):
self.name = name
self.url = url
self.description = description
def get_bug(self, id):
raise BugTrackerError("Bugtracker class does not implement get_bug")
def get_tracker(self, url):
raise BugTrackerError("Bugtracker class does not implement get_tracker")
class Bugzilla(IBugtracker):
def get_tracker(self, url):
url = url.replace('show_bug','xml')
try:
bugxml = utils.web.getUrl(url)
tree = minidom.parseString(bugxml)
url = str(tree.getElementsByTagName('bugzilla')[0].attributes['urlbase'].childNodes[0].data)
if url[-1] == '/':
url = url[:-1]
name = url[url.find('//') + 2:]
if '/' in name:
name = name[:name.find('/')]
desc = name
registerBugtracker(name, url, desc, 'bugzilla')
tracker = Bugzilla(name, url, desc)
return tracker
except:
return None
def get_bug(self, id):
url = "%s/xml.cgi?id=%d" % (self.url,id)
try:
bugxml = utils.web.getUrl(url)
zilladom = minidom.parseString(bugxml)
except Exception, e:
s = 'Could not parse XML returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
bug_n = zilladom.getElementsByTagName('bug')[0]
if bug_n.hasAttribute('error'):
errtxt = bug_n.getAttribute('error')
s = 'Error getting %s bug #%s: %s' % (self.description, id, errtxt)
raise BugtrackerError, s
try:
title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0])
status = _getnodetxt(bug_n.getElementsByTagName('bug_status')[0])
try:
status += ": " + _getnodetxt(bug_n.getElementsByTagName('resolution')[0])
except:
pass
component = _getnodetxt(bug_n.getElementsByTagName('component')[0])
severity = _getnodetxt(bug_n.getElementsByTagName('bug_severity')[0])
except Exception, e:
s = 'Could not parse XML returned by %s bugzilla: %s' % (self.description, e)
raise BugtrackerError, s
return (component, title, severity, status, "%s/show_bug.cgi?id=%d" % (self.url, id))
class Issuezilla(IBugtracker):
def get_bug(self, id):
url = "%s/xml.cgi?id=%d" % (self.url,id)
try:
bugxml = utils.web.getUrl(url)
zilladom = minidom.parseString(bugxml)
except Exception, e:
s = 'Could not parse XML returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
bug_n = zilladom.getElementsByTagName('issue')[0]
if not (bug_n.getAttribute('status_code') == '200'):
s = 'Error getting %s bug #%s: %s' % (self.description, id, bug_n.getAttribute('status_message'))
raise BugtrackerError, s
try:
title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0])
status = _getnodetxt(bug_n.getElementsByTagName('issue_status')[0])
try:
status += ": " + _getnodetxt(bug_n.getElementsByTagName('resolution')[0])
except:
pass
component = _getnodetxt(bug_n.getElementsByTagName('component')[0])
severity = _getnodetxt(bug_n.getElementsByTagName('issue_type')[0])
except Exception, e:
s = 'Could not parse XML returned by %s bugzilla: %s' % (self.description, e)
raise BugtrackerError, s
return (component, title, severity, status, "%s/show_bug.cgi?id=%d" % (self.url, id))
class Malone(IBugtracker):
def _parse(self, task):
parser = email.FeedParser.FeedParser()
parser.feed(task)
return parser.close()
def _sort(self, task1, task2):
# Status sort:
try:
statuses = ['Rejected', 'Fix Committed', 'Fix Released', 'Confirmed', 'In Progress', 'Needs Info', 'Unconfirmed']
severities = ['Wishlist', 'Minor', 'Normal', 'Major', 'Critical']
if task1['status'] not in statuses and task2['status'] in statuses: return 1
if task1['status'] in statuses and task2['status'] not in statuses: return -1
if task1['severity'] not in severities and task2['severity'] in severities: return 1
if task1['severity'] in severities and task2['severity'] not in severities: return -1
if not (task1['status'] == task2['status']):
if statuses.index(task1['status']) < statuses.index(task2['status']):
return -1
return 1
if not (task1['severity'] == task2['severity']):
if severities.index(task1['severity']) < severities.index(task2['severity']):
return -1
return 1
except: # Launchpad changed again?
return 0
return 0
def get_bug(self, id):
try:
bugdata = utils.web.getUrl("%s/%d/+text" % (self.url,id))
except Exception, e:
if '404' in str(e):
s = 'Error getting %s bug #%s: Bug does not exist' % (self.description, id)
raise BugtrackerError, s
s = 'Could not parse data returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
summary = {}
# Trap private bugs
if "<!-- 4a. didn't try to log in last time: -->" in bugdata:
raise BugtrackerError, "This bug is private"
try:
# Split bug data into separate pieces (bug data, task data)
data = bugdata.split('\n\n')
bugdata = data[0]
taskdata = data[1:]
parser = email.FeedParser.FeedParser()
parser.feed(bugdata)
bugdata = parser.close()
taskdata = map(self._parse, taskdata)
taskdata.sort(self._sort)
taskdata = taskdata[-1]
except Exception, e:
s = 'Could not parse data returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
t = taskdata['task']
if '(' in t:
t = t[:t.rfind('(') -1]
return (t, bugdata['title'], taskdata['importance'],
taskdata['status'], "%s/bugs/%s" % (self.url.replace('/malone',''), id))
# <rant>
# Debbugs sucks donkeyballs
# * HTML pages are inconsistent
# * Parsing mboxes gets incorrect with cloning perversions (eg with bug 330000)
# * No sane way of accessing bug reports in a machine readable way (bts2ldap has no search on bugid)
#
# So sometimes the plugin will return incorrect things - so what. Fix the
# damn bts before complaining.
# There's a patch against the thing since august 2003 for enabling machine
# readable output: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=207225
#
# It's not only releases that go slow in Debian, apparently the bugtracker
# development is even slower than that...
# </rant>
class Debbugs(IBugtracker):
def parse_mail(self, id, text, data):
(headers, text) = text.split("\n\n", 1)
for h in headers.split("\n"):
h2 = h.lower()
if h2.startswith('to') and ('%d-close' % id in h2 or '%d-done' % id in h2):
data['status'] = 'Closed'
if not data['title'] and h2.startswith('subject'):
data['title'] = h.strip()
infirstmail = False
for l in text.split("\n"):
l2 = l.lower().split()
if len(l2) == 0:
if infirstmail: return
continue
if l2[0] in ['quit', 'stop', 'thank', '--']:
return
elif l2[0] == 'package:':
data['package'] = l2[1]
infirstmail = True
elif l2[0] == 'severity:':
data['severity'] = l2[1]
try:
if len(l2) > 1:
if l2[0] in ['reassign', 'reopen', 'retitle', 'severity'] and not (int(l2[1]) == id):
continue
except ValueError: # Parsing to int failed, so not an integer
if l2[0] == 'reassign':
data['package'] = l2[2]
elif l2[0] == 'reopen':
data['status'] = 'Open'
elif l2[0] == 'retitle':
data['title'] = l.split(None,2)[2]
elif l2[0] == 'severity':
data['severity'] = ls[2]
def get_bug(self, id):
url = "%s/cgi-bin/bugreport.cgi?bug=%d;mbox=yes" % (self.url,id)
try:
bugdata = utils.web.getUrl(url)
except Exception, e:
s = 'Could not parse data returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
if '<p>There is no record of Bug' in bugdata:
raise BugtrackerError, "%s bug %d does not exist" % (self.description, id)
try:
data = {'package': None,'title': None,'severity':None,'status':'Open'}
for m in bugdata.split("\n\n\nFrom"):
self.parse_mail(id, m, data)
except Exception, e:
s = 'Could not parse data returned by %s bugtracker: %s' % (self.description, e)
raise BugtrackerError, s
return (data['package'], data['title'], data['severity'], data['status'], "%s/%s" % (self.url, id))
# For trac based trackers we also need to do some screenscraping - should be
# doable unless a certain track instance uses weird templates.
class Trac(IBugtracker):
def get_bug(self, id):
url = "%s/%d" % (self.url, id)
try:
bugdata = utils.web.getUrl(url)
except Exception, e:
s = 'Could not parse data returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
for l in bugdata.split("\n"):
if '<h1>Ticket' in l:
severity = l[l.find('(')+1:l.find(')')]
if 'class="summary"' in l:
title = l[l.find('>')+1:l.find('</')]
if 'class="status"' in l:
status = l[l.find('<strong>')+8:l.find('</strong>')]
if 'headers="h_component"' in l:
package = l[l.find('>')+1:l.find('</')]
if 'headers="h_severity"' in l:
severity = l[l.find('>')+1:l.find('</')]
return (package, title, severity, status, "%s/%s" % (self.url, id))
sfre = re.compile(r"""
.*?
<h2>\[.*?\]\s*(?P<title>.*?)</h2>
.*?
Priority.*?(?P<priority>\d+)
.*?
Status.*?<br>\s+(?P<status>\S+)
.*?
Resolution.*?<br>\s+(?P<resolution>\S+)
.*?
""", re.VERBOSE | re.DOTALL | re.I)
class Sourceforge(IBugtracker):
_sf_url = 'http://sf.net/support/tracker.php?aid=%d'
def get_bug(self, id):
url = self._sf_url % id
try:
bugdata = utils.web.getUrl(url)
except Exception, e:
s = 'Could not parse data returned by %s: %s' % (self.description, e)
raise BugtrackerError, s
try:
reo = sfre.search(bugdata)
status = reo.group('status')
resolution = reo.group('resolution')
if not (resolution.lower() == 'none'):
status += ' ' + resolution
return (None, reo.group('title'), "Pri: %s" % reo.group('priority'), status, self._sf_url % id)
except:
raise BugtrackerError, "Bug not found"
# Introspection is quite cool
defined_bugtrackers = {}
v = vars()
for k in v.keys():
if type(v[k]) == type(IBugtracker) and issubclass(v[k], IBugtracker) and not (v[k] == IBugtracker):
defined_bugtrackers[k.lower()] = v[k]
# Let's add a few bugtrackers by default
registerBugtracker('mozilla', 'http://bugzilla.mozilla.org', 'Mozilla', 'bugzilla')
registerBugtracker('ubuntu', 'http://bugzilla.ubuntu.com', 'Ubuntu', 'bugzilla')
registerBugtracker('gnome', 'http://bugzilla.gnome.org', 'Gnome', 'bugzilla')
registerBugtracker('gnome2', 'http://bugs.gnome.org', 'Gnome', 'bugzilla')
registerBugtracker('kde', 'http://bugs.kde.org', 'KDE', 'bugzilla')
registerBugtracker('ximian', 'http://bugzilla.ximian.com', 'Ximian', 'bugzilla')
registerBugtracker('freedesktop', 'http://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla')
registerBugtracker('freedesktop2', 'http://bugs.freedesktop.org', 'Freedesktop', 'bugzilla')
# Given that there is only one, let's add it by default
registerBugtracker('openoffice', 'http://openoffice.org/issues', 'OpenOffice.org', 'issuezilla')
# Given that there is only one, let's add it by default
registerBugtracker('malone', 'http://launchpad.net/malone', 'Malone', 'malone')
# Given that there is only one, let's add it by default
registerBugtracker('debian', 'http://bugs.debian.org', 'Debian', 'debbugs')
# Let's add a few bugtrackers by default
registerBugtracker('trac', 'http://projects.edgewall.com/trac/ticket', 'Trac', 'trac')
registerBugtracker('django', 'http://code.djangoproject.com/ticket', 'Django', 'trac')
# Let's add a few bugtrackers by default
registerBugtracker('supybot', 'http://sourceforge.net/tracker/?group_id=58965&atid=489447', 'Supybot', 'sourceforge')
# Special one, do NOT disable/delete
registerBugtracker('sourceforge', 'http://sourceforge.net/tracker/', 'Sourceforge', 'sourceforge')
Class = Bugtracker

3
Bugtracker/test.py Normal file
View File

@ -0,0 +1,3 @@
from supybot.test import *
class BugtrackerTestCase(PluginTestCase):
plugins = ('Bugtracker',)

1
Changuard/README.txt Normal file
View File

@ -0,0 +1 @@
Insert a description of your plugin here, with any notes, etc. about using it.

21
Changuard/__init__.py Normal file
View File

@ -0,0 +1,21 @@
"""
Various channel protections
"""
import supybot
import supybot.world as world
__version__ = "0.1"
__author__ = supybot.Author('Dennis Kaarsemaker', 'Seveas', 'dennis@kaarsemaker.net')
__contributors__ = {}
__url__ = 'http://bots.ubuntulinux.nl'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

10
Changuard/config.py Normal file
View File

@ -0,0 +1,10 @@
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Changuard', True)
Changuard = conf.registerPlugin('Changuard')
conf.registerChannelValue(conf.supybot.plugins.Changuard, 'enabled',
registry.Boolean(False,"""Enable the guard plugin"""))

25
Changuard/plugin.py Normal file
View File

@ -0,0 +1,25 @@
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.ircmsgs as ircmsgs
class Changuard(callbacks.PluginRegexp):
"""Channel guard"""
regexps = ['theban','badwords']
def theban(self, irc, msg, match):
r"""((\S\S\S\S\S.*?)\2\2\2\2\2|nextpicturez)"""
if self.registryValue('enabled', msg.args[0]):
if msg.args[0][0] == "#":
irc.queueMsg(ircmsgs.IrcMsg(command='REMOVE', args=(msg.args[0], msg.nick, "No flooding please"), msg=msg))
irc.queueMsg(ircmsgs.ban(msg.args[0], '*!*@%s' % msg.host))
def badwords(self, irc, msg, match):
r"""http.*(sex|porn)"""
if self.registryValue('enabled', msg.args[0]):
if msg.args[0][0] == "#":
irc.queueMsg(ircmsgs.IrcMsg(command='REMOVE', args=(msg.args[0], msg.nick, "Watch your language!"), msg=msg))
Class = Changuard

4
Changuard/test.py Normal file
View File

@ -0,0 +1,4 @@
from supybot.test import *
class ChanguardTestCase(PluginTestCase):
plugins = ('Changuard',)

1
Encyclopedia/README.txt Normal file
View File

@ -0,0 +1 @@
Insert a description of your plugin here, with any notes, etc. about using it.

42
Encyclopedia/__init__.py Normal file
View File

@ -0,0 +1,42 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
#
###
"""
Add a description of the plugin (to be presented to the user inside the wizard)
here. This should describe *what* the plugin does.
"""
import supybot
import supybot.world as world
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
__version__ = ""
# XXX Replace this with an appropriate author or supybot.Author instance.
__author__ = supybot.authors.unknown
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
# This is a url where the most recent plugin package can be downloaded.
__url__ = '' # 'http://supybot.com/Members/yourname/Factoid plugin/download'
import config
import plugin
reload(plugin) # In case we're being reloaded.
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
if world.testing:
import test
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

28
Encyclopedia/config.py Normal file
View File

@ -0,0 +1,28 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
#
###
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified himself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Encyclopedia', True)
Encyclopedia = conf.registerPlugin('Encyclopedia')
# This is where your configuration variables (if any) should go. For example:
# conf.registerGlobalValue(Factoid, 'someConfigVariableName',
# registry.Boolean(False, """Help for someConfigVariableName."""))
conf.registerChannelValue(Encyclopedia, 'database',
registry.String('', 'Name of database to use'))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

85
Encyclopedia/factoids.cgi Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/python
import sqlite
import datetime
import cgi, cgitb
from math import ceil
import re
cgitb.enable
NUM_PER_PAGE=50.0
buf = ''
def out(txt):
global buf
buf += str(txt)
def link(match):
url = match.group('url')
txt = url
if len(txt) > 30:
txt = txt[:20] + '&hellip;' + txt[-10:]
return '<a href="%s">%s</a>' % (url, txt)
def q(txt):
txt = str(txt).replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;').replace('\n','<br />')
# linkify
rx = re.compile('(?P<url>(https?://\S+|www\S+))')
return rx.sub(link, txt)
database = 'ubuntu'
form = cgi.FieldStorage()
try:
page = int(form['page'].value)
except:
page = 0
order_by = 'added DESC'
try:
order_by = form['order'].value
if order_by not in ('added DESC', 'added ASC', 'name DESC', 'name ASC', 'popularity DESC','popularity ASC'):
order_by = 'added DESC'
except:
order_by = 'added DESC'
con = sqlite.connect('/home/dennis/ubugtu/data/facts/%s.db' % database)
cur = con.cursor()
cur.execute("""SELECT COUNT(*) FROM facts WHERE value NOT LIKE '<alias>%%'""")
num = cur.fetchall()[0][0]
npages = int(ceil(num / float(NUM_PER_PAGE)))
out('&middot;')
for i in range(npages):
out(' <a href="factoids.cgi?order=%s&page=%s">%d</a> &middot;' % (order_by, i, i+1))
out('<br />Order by<br />&middot;')
out(' <a href="factoids.cgi?order=%s&page=%d">%s</a> &middot;' % ('name ASC', page, 'Name +'))
out(' <a href="factoids.cgi?order=%s&page=%d">%s</a> &middot;' % ('name DESC', page, 'Name -'))
out(' <a href="factoids.cgi?order=%s&page=%d">%s</a> &middot;' % ('popularity ASC', page, 'Popularity +'))
out(' <a href="factoids.cgi?order=%s&page=%d">%s</a> &middot;' % ('popularity DESC', page, 'Popularity -'))
out(' <a href="factoids.cgi?order=%s&page=%d">%s</a> &middot;' % ('added ASC', page, 'Date added +'))
out(' <a href="factoids.cgi?order=%s&page=%d">%s</a> &middot;' % ('added DESC', page, 'Date added -'))
out('<table cellspacing="0"><tr><th>Factoid</th><th>Value</th><th>Author</th></tr>')
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE value NOT LIKE '<alias>%%' ORDER BY %s LIMIT %d, %d" % (order_by, page*NUM_PER_PAGE, NUM_PER_PAGE))
factoids = cur.fetchall()
i = 0
for f in factoids:
cur.execute("SELECT name FROM facts WHERE value LIKE %s", '<alias> ' + f[0])
f = list(f)
f[0] += '\n' + '\n'.join([x[0] for x in cur.fetchall()])
out('<tr')
if i % 2: out(' class="bg2"')
i += 1
out('><td>%s</td><td>%s</td><td>%s<br />Added on: %s<br />Requested %s times</td>' % tuple([q(x) for x in f]))
out('</table>')
print "Content-Type: text/html; charset=UTF-8"
print ""
fd = open('factoids.tmpl')
tmpl = fd.read()
fd.close()
print tmpl % (buf)

134
Encyclopedia/factoids.tmpl Normal file
View File

@ -0,0 +1,134 @@
<html>
<head>
<title>Ubotu factoids</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<style type="text/css">
body {
font-family: verdana, sans;
font-weight: bold;
background-color: #d9bb7a;
color: #980101;
font-size: 10px;
}
.submit {
font-size: 10px;
border: solid 1px #980101;
color: #980101;
font-weight: bold;
background-color: #fdff99;
}
.input {
font-size: 10px;
border: solid 1px #980101;
color: #980101;
font-weight: bold;
background-color: white;
}
th {
font-size: 10px;
border-bottom: solid 1px #980101;
text-align: center;
}
td,th {
font-size: 10px;
text-align: left;
vertical-align: top;
}
td.comment {
white-space: normal;
padding-left: 20px;
}
li {
list-style: none;
}
table {
width: 100%%;
padding-top: 1em;
clear: both;
}
div.main {
margin: 20px;
border: 2px solid #980101;
padding: 0px;
background-color: #fdd99b;
text-align: center;
}
div.pdata {
text-align: right;
padding-right: 10px;
float: right;
}
div.search {
float: left;
text-align: left;
padding-left: 10px;
}
a, span.pseudolink {
color: #d40000;
text-decoration: underline;
cursor: pointer;
}
span.removal {
color: #6699cc;
}
tr.bg2 {
background-color: #fdff99;
}
div.invisible {
display: none;
}
div.log {
display: none;
color: black;
width: 100%%;
font-family: monospace;
white-space: normal;
}
</style>
<script type="text/javascript">
var DHTML = (document.getElementById || document.all || document.layers);
function getObj(name) {
if (document.getElementById) {
this.obj = document.getElementById(name);
this.style = document.getElementById(name).style;
}
else if (document.all) {
this.obj = document.all[name];
this.style = document.all[name].style;
}
else if (document.layers) {
this.obj = document.layers[name];
this.style = document.layers[name];
}
}
function toggle(item,prefix) {
var c = new getObj(prefix + '_' + item);
if ( c.style.display == 'inline' ) {
c.style.display = 'none';
}
else {
if ( !c.innerHTML && prefix == 'log' ) {
loadlog(item);
}
else {
c.style.display = 'inline';
}
}
}
</script>
<body>
<div class="main">
<h1>Ubotu factoids</h1>
<p>
%s
</p>
<p>
&copy;2006 Dennis Kaarsemaker
</p>
</div>
</body>
</html>

442
Encyclopedia/plugin.py Normal file
View File

@ -0,0 +1,442 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
#
###
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.ircmsgs as ircmsgs
import supybot.callbacks as callbacks
import sqlite, datetime, time, apt_pkg, commands
import supybot.registry as registry
import supybot.ircdb as ircdb
from email import FeedParser
import re
import os
apt_pkg.init()
fallback = ('ubuntu', '#ubuntu')
datadir = '/home/dennis/ubugtu/data/facts'
def r(section):
if '/' in section:
return section[:section.find('/')]
return 'main'
class Factoid:
def __init__(self, name, value, author, added, popularity):
self.name = name; self.value = value
self.author = author; self.added = added
self.popularity = popularity
def get_factoid(db, name, channel):
cur = db.cursor()
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name = %s", '%s-%s' % (name, channel))
factoid = cur.fetchall()
if len(factoid):
f = factoid[0]
return Factoid(f[0],f[1],f[2],f[3],f[4])
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name = %s", name)
factoid = cur.fetchall()
if len(factoid):
f = factoid[0]
return Factoid(f[0],f[1],f[2],f[3],f[4])
return None
def resolve_alias(db,factoid,channel,loop=0):
if loop >= 10:
return Factoid('','Error: infinite <alias> loop detected','','',0)
if factoid.value.lower().startswith('<alias>'):
new_factoid = get_factoid(db,factoid.value[7:].lower().strip(),channel)
if not new_factoid:
return Factoid('','Error: unresolvable <alias>','','',0)
else:
return resolve_alias(db, new_factoid, channel, loop+1)
else:
return factoid
class Encyclopedia(callbacks.PluginRegexp):
"""!factoid: show factoid"""
threaded = True
regexps = ['showfactoid', 'addfactoid', 'deletefactoid','info','find','editfactoid','searchfactoid','seen']
def __init__(self, irc):
callbacks.PluginRegexp.__init__(self, irc)
self.databases = {}
self.times = {}
self.seens = {}
# Capability check
def _precheck(self, irc, msg, capability=None, timeout=None):
channel = msg.args[0].lower()
inchannel = channel.startswith('#')
excl = msg.args[1].startswith('!')
if inchannel and not excl:
return False
for c in irc.callbacks:
comm = msg.args[1].split()[0]
print c.isDisabled(comm)
if c.isCommandMethod(comm) and not c.isDisabled(comm):
return False
if capability:
try:
_ = ircdb.users.getUser(msg.prefix)
except KeyError:
irc.queueMsg(ircmsgs.privmsg('#ubuntu-ops', "In %s, %s said: %s" % (msg.args[0], msg.nick, msg.args[1])))
irc.reply("Your edit request has been forwarded to #ubuntu-ops. Thank you for your attention to detail",private=True)
return False
if not ircdb.checkCapability(msg.prefix, capability):
irc.queueMsg(ircmsgs.privmsg('#ubuntu-ops', "In %s, %s said: %s" % (msg.args[0], msg.nick, msg.args[1])))
irc.reply("Your edit request has been forwarded to #ubuntu-ops. Thank you for your attention to detail",private=True)
return False
if timeout:
for key in self.times.keys():
if self.times[key] < time.time() - 15:
self.times.pop(key)
if timeout in self.times:
return False
self.times[timeout] = time.time()
db = self.registryValue('database',channel)
if not db:
db,channel = fallback
if channel not in self.databases:
self.databases[channel] = sqlite.connect(os.path.join(datadir, '%s.db' % db))
return self.databases[channel]
def searchfactoid(self, irc, msg, match):
r"^!?search\s+(?P<query>.+)"
db = self._precheck(irc, msg, timeout=(msg.args[0],match.group('query')))
if not db: return
cur = db.cursor()
query = '%%%s%%' % match.group('query').replace('%','').replace('*','%')
try:
cur.execute("SELECT name FROM facts WHERE (value LIKE %s OR name LIKE %s ) AND value NOT LIKE '<alias>%%'", (query, query))
data = cur.fetchall()
all = [x[0] for x in data]
cur.execute("SELECT value FROM facts WHERE name LIKE %s AND value LIKE '<alias>%%'", query)
data = cur.fetchall()
all += [x[0][7:].strip() for x in data]
all = list(set(all))
if len(all) > 10:
irc.reply("Found: %s (and %d more)" % (', '.join(all[:10]), len(all)-10))
elif len(all):
irc.reply("Found: %s" % ', '.join(all))
else:
irc.reply("Found nothing")
except:
irc.error('An error occured (code 561)')
def showfactoid(self, irc, msg, match):
r"^(!|ubotu\S?\s)?(?P<noalias>-)?\s*(tell\s+(?P<nick>\S+)\s+about\s+)?(?P<factoid>\S.*?)(>\s*(?P<nick2>\S+))?$"
db = self._precheck(irc, msg, timeout=(msg.args[0], match.group('nick'), match.group('factoid'), match.group('nick2')))
if not db: return
to = channel = msg.args[0]
if channel[0] != '#':
to = msg.nick
cur = db.cursor()
retmsg = ''
noalias = match.group('noalias')
factoid = match.group('factoid').lower().strip()
if ' is ' in match.group(0) or \
'=~' in match.group(0) or \
'<sed>' in match.group(0) or \
factoid.startswith('forget ') or \
factoid.startswith('info ') or \
factoid.startswith('find ') or \
factoid.startswith('search ') or \
factoid.startswith('seen'):
return
#if channel.startswith('#'):
if True:
nick = match.group('nick')
if match.group('nick2'): nick = match.group('nick2')
if nick == 'me': nick = msg.nick
if nick:
for chan in irc.state.channels:
if nick in irc.state.channels[chan].users and\
msg.nick in irc.state.channels[chan].users:
retmsg = '%s wants you to know: ' % msg.nick
to = nick
break
else:
irc.error("That person could not be found in any channel you're in")
return
# Retrieve factoid
try:
factoid = get_factoid(db, factoid, channel)
if not factoid:
irc.reply('I know nothing about %s' % match.group('factoid'))
return
# Output factoid
if not noalias:
factoid = resolve_alias(db,factoid,channel)
else:
cur.execute("SELECT name FROM facts WHERE value = %s", '<alias> ' + factoid.name)
data = cur.fetchall()
if(len(data)):
irc.queueMsg(ircmsgs.privmsg(to, "%s aliases: %s" % (factoid.name, ', '.join([x[0].strip() for x in data]))))
return
# Do timing
if not self._precheck(irc, msg, timeout=(to,factoid.name)):
return
cur.execute("UPDATE FACTS SET popularity = %d WHERE name = %s", factoid.popularity+1, factoid.name)
db.commit()
if factoid.value.startswith('<reply>'):
irc.queueMsg(ircmsgs.privmsg(to, '%s%s' % (retmsg, factoid.value[7:].strip())))
else:
irc.queueMsg(ircmsgs.privmsg(to, '%s%s is %s' % (retmsg, factoid.name, factoid.value.strip())))
except:
raise
irc.error('An error occured (code 813)')
def addfactoid(self, irc, msg, match):
r"^!?(?P<no>no,?\s+)?(?P<factoid>\S.*?)\s+is\s+(?P<fact>\S.*)"
db = self._precheck(irc, msg, capability='editfactoids', timeout=(msg.args[0],match.group(0)))
if not db: return
channel = msg.args[0]
cur = db.cursor()
factoid = match.group('factoid').lower().strip()
fact = match.group('fact').strip()
if '<sed>' in match.group(0) or \
'=~' in match.group(0) or \
factoid.startswith('forget') or \
factoid.startswith('info') or \
factoid.startswith('find') or \
factoid.startswith('search'):
return
try:
# See if the alias exists and resolve it...
old_factoid = get_factoid(db, factoid, channel)
if old_factoid:
if not fact.startswith('<alias>'):
old_factoid = resolve_alias(db, old_factoid, channel)
# Unresolvable alias
if not old_factoid.name:
irc.reply(old_factoid.value)
return
if match.group('no'):
if fact.startswith('<alias>'):
cur.execute("SELECT COUNT(*) FROM facts WHERE value = %s", '<alias> ' + factoid)
num = cur.fetchall()[0][0]
if num:
irc.reply("Can't turn factoid with aliases into an alias")
return
alias_factoid = get_factoid(db, fact[7:].lower().strip(), channel)
if not alias_factoid:
alias_factoid = Factoid('','Error: unresolvable <alias>','','',0)
else:
alias_factoid = resolve_alias(db, alias_factoid, channel)
if not alias_factoid.name:
irc.reply(alias_factoid.value)
return
fact = '<alias> %s' % alias_factoid.name
fact = fact.lower()
cur.execute("""UPDATE facts SET value=%s, author=%s, added=%s WHERE name=%s""",
(fact, msg.prefix, str(datetime.datetime.now()), old_factoid.name))
db.commit()
irc.reply("I'll remember that")
else:
irc.reply('%s is already known...' % factoid)
else:
if fact.lower().startswith('<alias>'):
old_factoid = get_factoid(db, fact[7:].lower().strip(), channel)
if not old_factoid:
old_factoid = Factoid('','Error: unresolvable <alias>','','',0)
else:
old_factoid = resolve_alias(db, old_factoid, channel)
if not old_factoid.name:
irc.reply(old_factoid.value)
return
fact = '<alias> %s' % old_factoid.name
fact = fact.lower()
cur.execute("""INSERT INTO facts (name, value, author, added) VALUES
(%s, %s, %s, %s)""", (factoid, fact, msg.prefix, str(datetime.datetime.now())))
db.commit()
irc.reply("I'll remember that")
except:
irc.error('An error occured (code 735)')
def editfactoid(self, irc, msg, match):
r"^!?(?P<factoid>.*?)\s*(=~|(\s+is\s*)<sed>)\s*s?(?P<regex>.*)"
db = self._precheck(irc, msg, capability='editfactoids', timeout=(msg.args[0],match.group(0)))
if not db: return
channel = msg.args[0]
cur = db.cursor()
factoid = match.group('factoid').lower().strip()
regex = match.group('regex').strip()
if factoid.startswith('forget') or \
factoid.startswith('info') or \
factoid.startswith('find') or \
factoid.startswith('search'): return
# Store factoid if nonexistant or 'no' is given
try:
# See if the alias exists and resolve it...
factoid = get_factoid(db, factoid, channel)
if factoid:
factoid = resolve_alias(db, factoid, channel)
# Unresolvable alias
if not factoid.name:
irc.reply(old_factoid.value)
return
delim = regex[0]
if regex[-1] != delim:
irc.reply("Missing end delimiter")
return
data = regex.split(delim)[1:-1]
if len(data) != 2:
irc.reply("You used the delimiter too often. Maybe try another one?")
return
regex, change = data
if '<alias>' in change.lower():
irc.reply("Can't turn factoids into aliases this way")
return
try:
regex = re.compile(regex)
except:
irc.reply("Malformed regex")
return
newval = regex.sub(change, factoid.value, 1)
if newval != factoid.value:
cur.execute("""UPDATE facts SET value=%s, author=%s, added=%s WHERE name=%s""",
(newval, msg.prefix, str(datetime.datetime.now()), factoid.name))
db.commit()
irc.reply("I'll remember that")
else:
irc.reply("No changes, not saving")
else:
irc.reply('I know nothing about %s' % match.group('factoid'))
except:
irc.error('An error occured (code 735)')
def deletefactoid(self, irc, msg, match):
r"^!?forget\s+(?P<factoid>\S.*)"
db = self._precheck(irc, msg, capability='editfactoids', timeout=(msg.args[0],match.group('factoid')))
if not db: return
channel = msg.args[0]
cur = db.cursor()
try:
cur.execute("SELECT COUNT(*) FROM facts WHERE value = %s", '<alias> ' + match.group('factoid'))
num = cur.fetchall()[0][0]
if num:
irc.reply("Can't forget factoids with aliases")
else:
cur.execute("DELETE FROM facts WHERE name = %s", match.group('factoid'))
db.commit()
irc.reply("I've forgotten it")
except:
raise
irc.error('An error occured (code 124)')
aptcommand = """apt-cache\\
-o"Dir::State::Lists=/home/dennis/ubugtu/data/%s"\\
-o"Dir::etc::sourcelist=/home/dennis/ubugtu/data/%s.list"\\
-o"Dir::State::status=/home/dennis/ubugtu/data/%s.status"\\
-o"Dir::Cache=/home/dennis/ubugtu/data/cache"\\
%s %s"""
def info(self, irc, msg, match):
r"^!?info\s+(?P<package>\S+)(\s+(?P<distro>\S+))?"
if not self._precheck(irc, msg, timeout=(msg.args[0],match.group('package'), match.group('distro'))):
return
distro = 'dapper'
if (match.group('distro') in ('warty','hoary','breezy','dapper','edgy')):
distro = match.group('distro')
data = commands.getoutput(self.aptcommand % (distro, distro, distro, 'show', match.group('package')))
if not data or 'E: No packages found' in data:
irc.reply('Package %s does not exist in %s' % (match.group('package'), distro))
else:
maxp = {'Version': '0'}
packages = [x.strip() for x in data.split('\n\n')]
for p in packages:
if not p.strip():
continue
parser = FeedParser.FeedParser()
parser.feed(p)
p = parser.close()
if apt_pkg.VersionCompare(maxp['Version'], p['Version']) < 0:
maxp = p
del parser
irc.reply("%s: %s. In repository %s, is %s. Version %s (%s), package size %s kB, installed size %s kB" %
(maxp['Package'], maxp['Description'].split('\n')[0], r(maxp['Section']),
maxp['Priority'], maxp['Version'], distro, int(maxp['Size'])/1024, maxp['Installed-Size']))
def find(self, irc, msg, match):
r"^!?find\s+(?P<package>\S+)(\s+(?P<distro>\S+))?"
if not self._precheck(irc, msg, timeout=(msg.args[0],match.group('package'), match.group('distro'),2)):
return
distro = 'dapper'
if (match.group('distro') in ('warty','hoary','breezy','dapper','edgy')):
distro = match.group('distro')
data = commands.getoutput(self.aptcommand % (distro, distro, distro, 'search -n', match.group('package')))
if not data:
irc.reply("No packages matching '%s' could be found" % match.group('package'))
else:
pkgs = [x.split()[0] for x in data.split('\n')]
if len(pkgs) > 5:
irc.reply("Found: %s (and %d others)" % (', '.join(pkgs[:5]), len(pkgs) -5))
else:
irc.reply("Found: %s" % ', '.join(pkgs[:5]))
def seen(self, irc, msg, match):
r"^!?seen\s+(?P<nick>\S+)"
if not self._precheck(irc, msg, timeout=(msg.args[0],match.group('nick'))):
return
to = msg.args[0]
if msg.args[0][0] != '#':
to = msg.nick
self.seens[match.group('nick')] = (to, time.time())
irc.queueMsg(ircmsgs.privmsg('seenserv', "seen %s" % match.group('nick')))
def doNotice(self, irc, msg):
if msg.nick.lower() == 'seenserv':
resp = msg.args[1]
for n in self.seens.keys():
if self.seens[n][1] < time.time() - 10:
self.seens.pop(n)
for n in self.seens.keys():
if n.lower() in resp.lower():
irc.queueMsg(ircmsgs.privmsg(self.seens[n][0], resp))
self.seens.pop(n)
def addeditor(self, irc, msg, args, name):
self._precheck(irc, msg, capability='addeditors')
try:
u = ircdb.users.getUser(name)
except:
irc.error('User %s is not registered' % name)
else:
u.addCapability('editfactoids')
addeditor = wrap(addeditor, ['text'])
def editors(self, irc, msg, args):
irc.reply(', '.join([ircdb.users.getUser(u).name for u in ircdb.users.users \
if 'editfactoids' in ircdb.users.getUser(u).capabilities]))
editors = wrap(editors)
def moderators(self, irc, msg, args):
irc.reply(', '.join([ircdb.users.getUser(u).name for u in ircdb.users.users \
if 'addeditors' in ircdb.users.getUser(u).capabilities]))
moderators = wrap(moderators)
def removeeditor(self, irc, msg, args, name):
self._precheck(irc, msg, capability='addeditors')
try:
u = ircdb.users.getUser(name)
except:
irc.error('User %s is not registered' % name)
else:
u.removeCapability('editfactoids')
removeeditor = wrap(removeeditor, ['text'])
Class = Encyclopedia
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

14
Encyclopedia/test.py Normal file
View File

@ -0,0 +1,14 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
#
###
from supybot.test import *
class Factoid pluginTestCase(PluginTestCase):
plugins = ('Factoid plugin',)
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

9
Makefile Normal file
View File

@ -0,0 +1,9 @@
ALL=$(shell find . -name '???*' -type d -printf '%p.tar.gz\n')
default: $(ALL)
%.tar.gz: %
tar zcf $@ $<
clean:
rm -f $(ALL)

1
Mess/README.txt Normal file
View File

@ -0,0 +1 @@
Insert a description of your plugin here, with any notes, etc. about using it.

21
Mess/__init__.py Normal file
View File

@ -0,0 +1,21 @@
"""
Random mess plugin
"""
import supybot
import supybot.world as world
__version__ = "0.5"
__author__ = supybot.Author('Dennis Kaarsemaker','Seveas','dennis@kaarsemaker.net')
__contributors__ = {}
__url__ = 'https://bots.ubuntulinux.nl'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

453
Mess/bofh.txt Normal file
View File

@ -0,0 +1,453 @@
clock speed
solar flares
electromagnetic radiation from satellite debris
static from nylon underwear
static from plastic slide rules
global warming
poor power conditioning
static buildup
doppler effect
hardware stress fractures
magnetic interference from money/credit cards
dry joints on cable plug
we're waiting for the phone company to fix that line
sounds like a Windows problem, try calling Microsoft support
temporary routing anomaly
somebody was calculating pi on the server
fat electrons in the lines
excess surge protection
floating point processor overflow
divide-by-zero error
POSIX compliance problem
monitor resolution too high
improperly oriented keyboard
network packets travelling uphill (use a carrier pigeon)
Decreasing electron flux
first Saturday after first full moon in Winter
radiosity depletion
CPU radiator broken
It works the way the Wang did, what's the problem
positron router malfunction
cellular telephone interference
techtonic stress
piezo-electric interference
(l)user error
working as designed
dynamic software linking table corrupted
heavy gravity fluctuation, move computer to floor rapidly
secretary plugged hairdryer into UPS
terrorist activities
not enough memory, go get system upgrade
interrupt configuration error
spaghetti cable cause packet failure
boss forgot system password
bank holiday - system operating credits not recharged
virus attack, luser responsible
waste water tank overflowed onto computer
Complete Transient Lockout
bad ether in the cables
Bogon emissions
Change in Earth's rotational speed
Cosmic ray particles crashed through the hard disk platter
Smell from unhygienic janitorial staff wrecked the tape heads
Little hamster in running wheel had coronary; waiting for replacement to be Fedexed from Wyoming
Evil dogs hypnotised the night shift
Plumber mistook routing panel for decorative wall fixture
Electricians made popcorn in the power supply
Groundskeepers stole the root password
high pressure system failure
failed trials, system needs redesigned
system has been recalled
not approved by the FCC
need to wrap system in aluminum foil to fix problem
not properly grounded, please bury computer
CPU needs recalibration
system needs to be rebooted
bit bucket overflow
descramble code needed from software company
only available on a need to know basis
knot in cables caused data stream to become twisted and kinked
nesting roaches shorted out the ether cable
The file system is full of it
Satan did it
Daemons did it
You're out of memory
There isn't any problem
Unoptimized hard drive
Typo in the code
Yes, yes, its called a design limitation
Look, buddy: Windows 3.1 IS A General Protection Fault.
That's a great computer you have there; have you considered how it would work as a BSD machine?
Please excuse me, I have to circuit an AC line through my head to get this database working.
Yeah, yo mama dresses you funny and you need a mouse to delete files.
Support staff hung over, send aspirin and come back LATER.
Someone is standing on the ethernet cable, causing a kink in the cable
Windows 95 undocumented "feature"
Runt packets
Password is too complex to decrypt
Boss' kid fucked up the machine
Electromagnetic energy loss
Budget cuts
Mouse chewed through power cable
Stale file handle (next time use Tupperware(tm)!)
Feature not yet implemented
Internet outage
Pentium FDIV bug
Vendor no longer supports the product
Small animal kamikaze attack on power supplies
The vendor put the bug there.
SIMM crosstalk.
IRQ dropout
Collapsed Backbone
Power company testing new voltage spike (creation) equipment
operators on strike due to broken coffee machine
backup tape overwritten with copy of system manager's favourite CD
UPS interrupted the server's power
The electrician didn't know what the yellow cable was so he yanked the ethernet out.
The keyboard isn't plugged in
The air conditioning water supply pipe ruptured over the machine room
The electricity substation in the car park blew up.
The rolling stones concert down the road caused a brown out
The salesman drove over the CPU board.
The monitor is plugged into the serial port
Root nameservers are out of sync
electro-magnetic pulses from French above ground nuke testing.
your keyboard's space bar is generating spurious keycodes.
the real ttys became pseudo ttys and vice-versa.
the printer thinks its a router.
the router thinks its a printer.
evil hackers from Serbia.
we just switched to FDDI.
halon system went off and killed the operators.
because Bill Gates is a Jehovah's witness and so nothing can work on St. Swithin's day.
user to computer ratio too high.
user to computer ration too low.
we just switched to Sprint.
it has Intel Inside
Sticky bits on disk.
Power Company having EMP problems with their reactor
The ring needs another token
new management
telnet: Unable to connect to remote host: Connection refused
SCSI Chain overterminated
It's not plugged in.
because of network lag due to too many people playing deathmatch
You put the disk in upside down.
Daemons loose in system.
User was distributing pornography on server; system seized by FBI.
BNC (brain not connected)
UBNC (user brain not connected)
LBNC (luser brain not connected)
disks spinning backwards - toggle the hemisphere jumper.
new guy cross-connected phone lines with ac power bus.
had to use hammer to free stuck disk drive heads.
Too few computrons available.
Flat tire on station wagon with tapes.
Communications satellite used by the military for star wars.
Party-bug in the Aloha protocol.
Insert coin for new game
Dew on the telephone lines.
Arcserve crashed the server again.
Some one needed the powerstrip, so they pulled the switch plug.
My pony-tail hit the on/off switch on the power strip.
Big to little endian conversion error
You can tune a file system, but you can't tune a fish
Dumb terminal
Zombie processes haunting the computer
Incorrect time synchronization
Defunct processes
Stubborn processes
non-redundant fan failure
monitor VLF leakage
bugs in the RAID
no "any" key on keyboard
root rot
Backbone Scoliosis
/pub/lunch
excessive collisions & not enough packet ambulances
le0: no carrier: transceiver cable problem?
broadcast packets on wrong frequency
popper unable to process jumbo kernel
NOTICE: alloc: /dev/null: filesystem full
pseudo-user on a pseudo-terminal
Recursive traversal of loopback mount points
Backbone adjustment
OS swapped to disk
vapors from evaporating sticky-note adhesives
sticktion
short leg on process table
multicasts on broken packets
ether leak
Atilla the Hub
endothermal recalibration
filesystem not big enough for Jumbo Kernel Patch
loop found in loop in redundant loopback
system consumed all the paper for paging
permission denied
Reformatting Page. Wait...
Either the disk or the processor is on fire.
SCSI's too wide.
Proprietary Information.
Just type 'mv * /dev/null'.
runaway cat on system.
Did you pay the new Support Fee?
We only support a 1200 bps connection.
We only support a 28000 bps connection.
Me no internet, only janitor, me just wax floors.
I'm sorry a pentium won't do, you need an SGI to connect with us.
Post-it Note Sludge leaked into the monitor.
the curls in your keyboard cord are losing electricity.
The monitor needs another box of pixels.
RPC_PMAP_FAILURE
kernel panic: write-only-memory (/dev/wom0) capacity exceeded.
Write-only-memory subsystem too slow for this machine. Contact your local dealer.
Just pick up the phone and give modem connect sounds. "Well you said we should get more lines so we don't have voice lines."
Quantum dynamics are affecting the transistors
Police are examining all internet packets in the search for a narco-net-trafficker
We are currently trying a new concept of using a live mouse. Unfortunately, one has yet to survive being hooked up to the computer.....please bear with us.
Your mail is being routed through Germany ... and they're censoring us.
Only people with names beginning with 'A' are getting mail this week (a la Microsoft)
We didn't pay the Internet bill and it's been cut off.
Lightning strikes.
Of course it doesn't work. We've performed a software upgrade.
Change your language to Finnish.
Fluorescent lights are generating negative ions. If turning them off doesn't work, take them out and put tin foil on the ends.
High nuclear activity in your area.
What office are you in? Oh, that one. Did you know that your building was built over the universities first nuclear research site? And wow, aren't you the lucky one, your office is right over where the core is buried!
The MGs ran out of gas.
The UPS doesn't have a battery backup.
Recursivity. Call back if it happens again.
Someone thought The Big Red Button was a light switch.
The mainframe needs to rest. It's getting old, you know.
I'm not sure. Try calling the Internet's head office -- it's in the book.
The lines are all busy (busied out, that is -- why let them in to begin with?).
Jan 9 16:41:27 huber su: 'su root' succeeded for .... on /dev/pts/1
It's those computer people in London. They keep stuffing things up.
A star wars satellite accidently blew up the WAN.
Fatal error right in front of screen
That function is not currently supported, but Bill Gates assures us it will be featured in the next upgrade.
wrong polarity of neutron flow
Lusers learning curve appears to be fractal
We had to turn off that service to comply with the CDA Bill.
Ionization from the air-conditioning
TCP/IP UDP alarm threshold is set too low.
Someone is broadcasting pygmy packets and the router doesn't know how to deal with them.
The new frame relay network hasn't bedded down the software loop transmitter yet.
Fanout dropping voltage too much, try cutting some of those little traces
Plate voltage too low on demodulator tube
You did wha... oh _dear_....
CPU needs bearings repacked
Too many little pins on CPU confusing it, bend back and forth until 10-20% are neatly removed. Do _not_ leave metal bits visible!
_Rosin_ core solder? But...
Software uses US measurements, but the OS is in metric...
The computer fleetly, mouse and all.
Your cat tried to eat the mouse.
The Borg tried to assimilate your system. Resistance is futile.
It must have been the lightning storm we had yesterday
Due to Federal Budget problems we have been forced to cut back on the number of users able to access the system at one time
Too much radiation coming from the soil.
Unfortunately we have run out of bits/bytes/whatever. Don't worry, the next supply will be coming next week.
Program load too heavy for processor to lift.
Processes running slowly due to weak power supply
Our ISP is having frame relay problems
We've run out of licenses
Interference from lunar radiation
Standing room only on the bus.
You need to install an RTFM interface.
That would be because the software doesn't work.
That's easy to fix, but I can't be bothered.
Someone's tie is caught in the printer, and if anything else gets printed, he'll be in it too.
We're upgrading /dev/null
The Usenet news is out of date
Our POP server was kidnapped by a weasel.
It's stuck in the Web.
Your modem doesn't speak English.
The mouse escaped.
All of the packets are empty.
The UPS is on strike.
Neutrino overload on the nameserver
Melting hard drives
Someone has messed up the kernel pointers
The kernel license has expired
Netscape has crashed
The cord jumped over and hit the power switch.
It was OK before you touched it.
Bit rot
U.S. Postal Service
Your Flux Capacitor has gone bad.
The Dilithium Crystals need to be rotated.
The static electricity routing is acting up...
Traceroute says that there is a routing problem in the backbone. It's not our problem.
The co-locator cannot verify the frame-relay gateway to the ISDN server.
High altitude condensation from U.S.A.F prototype aircraft has contaminated the primary subnet mask. Turn off your computer for 9 days to avoid damaging it.
Lawn mower blade in your fan need sharpening
Electrons on a bender
Telecommunications is upgrading.
Telecommunications is downgrading.
Telecommunications is downshifting.
Hard drive sleeping. Let it wake up on it's own...
Interference between the keyboard and the chair.
The CPU has shifted, and become decentralized.
Due to the CDA, we no longer have a root account.
We ran out of dial tone and we're and waiting for the phone company to deliver another bottle.
You must've hit the wrong any key.
PCMCIA slave driver
The Token fell out of the ring. Call us when you find it.
The hardware bus needs a new token.
Too many interrupts
Not enough interrupts
The data on your hard drive is out of balance.
Digital Manipulator exceeding velocity parameters
appears to be a Slow/Narrow SCSI-0 Interface problem
microelectronic Riemannian curved-space fault in write-only file system
fractal radiation jamming the backbone
routing problems on the neural net
IRQ-problems with the Un-Interruptible-Power-Supply
CPU-angle has to be adjusted because of vibrations coming from the nearby road
emissions from GSM-phones
CD-ROM server needs recalibration
firewall needs cooling
asynchronous inode failure
transient bus protocol violation
incompatible bit-registration operators
your process is not ISO 9000 compliant
You need to upgrade your VESA local bus to a MasterCard local bus.
The recent proliferation of Nuclear Testing
Elves on strike. (Why do they call EMAG Elf Magic)
Internet exceeded Luser level, please wait until a luser logs off before attempting to log back on.
Your EMAIL is now being delivered by the USPS.
Your computer hasn't been returning all the bits it gets from the Internet.
You've been infected by the Telescoping Hubble virus.
Scheduled global CPU outage
Your Pentium has a heating problem - try cooling it with ice cold water.(Do not turn of your computer, you do not want to cool down the Pentium Chip while he isn't working, do you?)
Your processor has processed too many instructions. Turn it off immediately, do not type any commands!!
Your packets were eaten by the terminator
Your processor does not develop enough heat.
We need a licensed electrician to replace the light bulbs in the computer room.
The POP server is out of Coke
Fiber optics caused gas main leak
Server depressed, needs Prozac
quantum decoherence
those damn raccoons!
suboptimal routing experience
A plumber is needed, the network drain is clogged
50% of the manual is in .pdf readme files
the AA battery in the wallclock sends magnetic interference
the xy axis in the trackball is coordinated with the summer solstice
the butane lighter causes the pincushioning
old inkjet cartridges emanate barium-based fumes
manager in the cable duct
Well fix that in the next (upgrade, update, patch release, service pack).
HTTPD Error 666 : BOFH was here
HTTPD Error 4004 : very old Intel cpu - insufficient processing power
The ATM board has run out of 10 pound notes. We are having a whip round to refill it, care to contribute ?
Network failure - call NBC
Having to manually track the satellite.
Your/our computer(s) had suffered a memory leak, and we are waiting for them to be topped up.
The rubber band broke
We're on Token Ring, and it looks like the token got loose.
Stray Alpha Particles from memory packaging caused Hard Memory Error on Server.
paradigm shift...without a clutch
PEBKAC (Problem Exists Between Keyboard And Chair)
The cables are not the same length.
Second-system effect.
Chewing gum on /dev/sd3c
Boredom in the Kernel.
the daemons! the daemons! the terrible daemons!
I'd love to help you -- it's just that the Boss won't let me near the computer.
struck by the Good Times virus
YOU HAVE AN I/O ERROR -> Incompetent Operator error
Your parity check is overdrawn and you're out of cache.
Communist revolutionaries taking over the server room and demanding all the computers in the building or they shoot the sysadmin. Poor misguided fools.
Plasma conduit breach
Out of cards on drive D:
Sand fleas eating the Internet cables
parallel processors running perpendicular today
ATM cell has no roaming feature turned on, notebooks can't connect
Webmasters kidnapped by evil cult.
Failure to adjust for daylight savings time.
Virus transmitted from computer to sysadmins.
Virus due to computers having unsafe sex.
Incorrectly configured static routes on the corerouters.
Forced to support NT servers; sysadmins quit.
Suspicious pointer corrupted virtual machine
It's the InterNIC's fault.
Root name servers corrupted.
Budget cuts forced us to sell all the power cords for the servers.
Someone hooked the twisted pair wires into the answering machine.
Operators killed by year 2000 bug bite.
We've picked COBOL as the language of choice.
Operators killed when huge stack of backup tapes fell over.
Robotic tape changer mistook operator's tie for a backup tape.
Someone was smoking in the computer room and set off the halon systems.
Your processor has taken a ride to Heaven's Gate on the UFO behind Hale-Bopp's comet.
it's an ID-10-T error
Dyslexics retyping hosts file on servers
The Internet is being scanned for viruses.
Your computer's union contract is set to expire at midnight.
Bad user karma.
/dev/clue was linked to /dev/null
Increased sunspot activity.
We already sent around a notice about that.
It's union rules. There's nothing we can do about it. Sorry.
Interference from the Van Allen Belt.
Jupiter is aligned with Mars.
Redundant ACLs.
Mail server hit by UniSpammer.
T-1's congested due to porn traffic to the news server.
Data for intranet got routed through the extranet and landed on the internet.
We are a 100% Microsoft Shop.
We are Microsoft. What you are experiencing is not a problem; it is an undocumented feature.
Sales staff sold a product we don't offer.
Secretary sent chain letter to all 5000 employees.
Sysadmin didn't hear pager go off due to loud music from bar-room speakers.
Sysadmin accidentally destroyed pager with a large hammer.
Sysadmins unavailable because they are in a meeting talking about why they are unavailable so much.
Bad cafeteria food landed all the sysadmins in the hospital.
Route flapping at the NAP.
Computers under water due to SYN flooding.
The vulcan-death-grip ping has been applied.
Electrical conduits in machine room are melting.
Traffic jam on the Information Superhighway.
Radial Telemetry Infiltration
Cow-tippers tipped a cow onto the server.
tachyon emissions overloading the system
Maintenance window broken
We're out of slots on the server
Computer room being moved. Our systems are down for the weekend.
Sysadmins busy fighting SPAM.
Repeated reboots of the system failed to solve problem
Feature was not beta tested
Domain controller not responding
Someone else stole your IP address, call the Internet detectives!
It's not RFC-822 compliant.
operation failed because: there is no message for this error (#1014)
stop bit received
internet is needed to catch the etherbunny
network down, IP packets delivered via UPS
Firmware update in the coffee machine
Temporal anomaly
Mouse has out-of-cheese-error
Borg implants are failing
Borg nanites have infested the server
error: one bad user found in front of screen
Please state the nature of the technical emergency
Internet shut down due to maintenance
Daemon escaped from pentagram
crop circles in the corn shell
sticky bit has come loose
Hot Java has gone cold
Cache miss - please take better aim next time
Hash table has woodworm
Trojan horse ran out of hay
Zombie processes detected, machine is haunted.
overflow error in /dev/null
Browser's cookie is corrupted -- someone's been nibbling on it.
Mailer-daemon is busy burning your message in hell.
According to Microsoft, it's by design
vi needs to be upgraded to vii
greenpeace free'd the mallocs
Terrorists crashed an airplane into the server room, have to remove /bin/laden. (rm -rf /bin/laden)
astropneumatic oscillations in the water-cooling
Somebody ran the operating system through a spelling checker.
Spider infestation in warm case parts

16
Mess/config.py Normal file
View File

@ -0,0 +1,16 @@
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Mess', True)
Mess = conf.registerPlugin('Mess')
conf.registerChannelValue(conf.supybot.plugins.Mess, 'enabled',
registry.Boolean(False,"""Enable all mess that Ubugtu can spit out in the
channel"""))
conf.registerChannelValue(conf.supybot.plugins.Mess, 'offensive',
registry.Boolean(False,"""Enable all possibly offensive mess that Ubugtu can spit out in the
channel"""))
conf.registerChannelValue(conf.supybot.plugins.Mess, 'delay',
registry.Integer(10,""" Minimum number of seconds between mess """))

243
Mess/plugin.py Normal file
View File

@ -0,0 +1,243 @@
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import random, re, time, commands, urllib2
import supybot.ircmsgs as ircmsgs
_bofhfile = '/home/dennis/ubugtu/plugins/Mess/bofh.txt'
_bofhdata = open(bofhfile).readlines()
class Mess(callbacks.PluginRegexp):
"""Random Mess plugin"""
threaded = True
regexps = ['hugme']
hugs = ["hugs %s","gives %s a big hug","gives %s a sloppy wet kiss",
"huggles %s","squeezes %s","humps %s"]
regex = re.compile('</h1>.*?<p>(.*?)</p>', re.DOTALL)
entre = re.compile('&(\S*?);')
jre1 = ('http://www.jackbauerfacts.com/index.php?rate_twenty_four',
re.compile('current-rating.*?width.*?<td>(.*?)</td>', re.DOTALL))
jre2 = ('http://www.notrly.com/jackbauer/',
re.compile('<p class="fact">(.*?)</p>'))
badwords = ['sex','masturbate','fuck','rape','dick','pussy','prostitute','hooker',
'orgasm','sperm','cunt','penis','shit','piss','urin','bitch','semen']
i = 0
time = {}
# WARNING: depends on an alteration in supybot/callbacks.py - don't do
# str(s) if s is unicode!
def dice(self, irc, msg, args, count):
if not self.ok(msg.args[0]): return
if not count: count = 1
if count > 5: count = 5
t = u' '.join([x.__call__([u"\u2680",u"\u2681",u"\u2682",u"\u2683",u"\u2684",u"\u2685"]) for x in [random.choice]*count])
print t
#print str(t)
irc.reply(t)
dice = wrap(dice, [additional('int')])
def hugme(self, irc, msg, match):
r""".*hug.*ubugtu"""
irc.queueMsg(ircmsgs.action(msg.args[0], self.hugs[random.randint(0,len(self.hugs)-1)] % msg.nick))
def ok(self, channel, offensive = False):
if not channel.startswith('#'):
delay = 5
else:
if not self.registryValue('enabled', channel):
return False
if offensive and not self.registryValue('offensive', channel):
return False
delay = self.registryValue('delay', channel)
if channel not in self.time.keys():
self.time[channel] = time.time()
return True
if self.time[channel] < time.time() - delay:
self.time[channel] = time.time()
return True
return False
def fact(self,who,count=0):
# The website is buggy, mysql errors rear their ugly head a lot. So we
# retry up to 5 times :)
if count > 5:
return
try:
fact = utils.web.getUrl('http://4q.cc/index.php?pid=fact&person=%s' % who)
reo = self.regex.search(fact)
val = reo.group(1)
while self.entre.search(val):
entity = self.entre.search(val).group(1)
if entity in entities:
val = self.entre.sub(entities[entity], val)
else:
val = self.entre.sub('?', val)
_val = val.lower()
for word in self.badwords:
if word in _val:
raise RuntimeError
return val
except:
time.sleep(1)
return self.fact(who,count+1)
def t(self, irc, msg, args):
""" Display a mr T. fact """
if not self.ok(msg.args[0]): return
f = self.fact('mrt')
if f: irc.reply(f)
t = wrap(t)
def chuck(self, irc, msg, args):
""" Display a Chuck Norris fact """
if not self.ok(msg.args[0]): return
f = self.fact('chuck')
if f: irc.reply(f)
chuck = wrap(chuck)
def vin(self, irc, msg, args):
""" Display a Vin Diesel fact """
if not self.ok(msg.args[0]): return
f = self.fact('vin')
if f: irc.reply(f)
vin = wrap(vin)
hre = re.compile('<font.*?<b>(.*?)</font>',re.DOTALL)
hre2 = re.compile('<.*?>')
def hamster(self, irc, msg, args):
""" Bob sez! """
if not self.ok(msg.args[0]): return
try:
data = utils.web.getUrl("http://hamsterrepublic.com/dyn/bobsez")
except:
return
# Find correct data
data = self.hre.search(data).group(1)
data = self.hre2.sub('',data)
irc.reply(data.strip())
hamster = wrap(hamster)
def fortune(self, irc, msg, args):
""" Display a fortune cookie """
if not self.ok(msg.args[0]): return
f = commands.getoutput('fortune -s')
f.replace('\t',' ')
f = f.split('\n')
for l in f:
if l:
irc.reply(l)
fortune = wrap(fortune)
def ofortune(self, irc, msg, args):
""" Display a possibly offensive fortune cookie """
if not self.ok(msg.args[0], True): return
f = commands.getoutput('fortune -so')
f.replace('\t',' ')
f = f.split('\n')
for l in f:
if l:
irc.reply(l)
ofortune = wrap(ofortune)
#def bash(self, irc, msg, args):
# """ Display a bash.org quote """
# if not self.ok(msg.args[0], True): return
# b = utils.web.getUrl('http://bash.org?random1')
# r = []
# infirst = False
# for line in b.split('\n'):
# if '#' in line and 'X' in line:
# if infirst:
# if len(r) < 6:
# bw = False
# for w in self.badwords:
# if w in ''.join(r):
# bw = True
# break
# if not bw:
# for l in r:
# if l:
# irc.reply(l)
# return
# r = []
# infirst = True
# elif infirst:
# r.append(line.strip())
# irc.reply('hmm, weird')
#bash = wrap(bash)
def bofh(self, irc, msg, args, num):
""" Display a BOFH excuse """
if not self.ok(msg.args[0]): return
if num and num >= 1 and num <= len(_bofhdata):
i = num
else:
i = random.randint(0,len(_bofhdata)-1)
irc.reply("BOFH excuse #%d: %s" % (i, _bofhdata[i]))
bofh = wrap(bofh, [additional('int')])
def bauer(self, irc, msg, args, count=0):
""" Display a Jack Bauer fact """
if not self.ok(msg.args[0]): return
f = self._bauer()
if f:
irc.reply(f)
bauer = wrap(bauer)
def futurama(self, irc, msg, args):
""" Display a futurama quote """
if not self.ok(msg.args[0]): return
u = urllib2.urlopen('http://slashdot.org')
h = [x for x in u.headers.headers if x.startswith('X') and not x.startswith('X-Powered-By')][0]
irc.reply(h[2:-2])
futurama = wrap(futurama)
def yourmom(self, irc, msg, args):
""" Your mom hates IRC """
if not self.ok(msg.args[0], True): return
data = utils.web.getUrl('http://pfa.php1h.com/')
irc.reply(data[data.find('<p>')+3:data.find('</p>')].strip())
yourmom = wrap(yourmom)
def bush(self, irc,msg,args):
"""Yes, bush needs help...."""
if not self.ok(msg.args[0], True): return
data = utils.web.getUrl('http://www.dubyaspeak.com/random.phtml')
data = data[data.find('<font'):data.find('</font')]
while '<' in data:
data = data[:data.find('<')] + data[data.find('>')+1:]
irc.reply(data.replace("\n",''))
bush = wrap(bush)
def _bauer(self,count=0):
# if self.i % 2 == 0:
# (url, re) = self.jre1
# else:
# (url, re) = self.jre2
# self.i += 1
(url, re) = self.jre2
if count > 5:
return
try:
fact = utils.web.getUrl(url)
reo = re.search(fact)
val = reo.group(1)
while self.entre.search(val):
entity = self.entre.search(val).group(1)
if entity in entities:
val = self.entre.sub(entities[entity], val)
else:
val = self.entre.sub('?', val)
_val = val.lower()
for word in self.badwords:
if word in _val:
raise RuntimeError
return val
except:
time.sleep(1)
return self._bauer(count+1)
Class = Mess

4
Mess/test.py Normal file
View File

@ -0,0 +1,4 @@
from supybot.test import *
class MessTestCase(PluginTestCase):
plugins = ('Mess',)

5
Webcal/README.txt Normal file
View File

@ -0,0 +1,5 @@
This plugin can update a topic given an iCal schedule. It's made to work for
#ubuntu-meeting on Freenode (and iCal from fridge.ubuntu.com) but in theory it
should work with other iCal feeds too. It's not hard to understand.
Requirements: included ical module and pytz

34
Webcal/__init__.py Normal file
View File

@ -0,0 +1,34 @@
###
# Copyright (c) 2005,2006 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.
#
###
"""
Update the topic according to an iCal schedule
"""
import supybot
import supybot.world as world
__version__ = "0.2"
__author__ = supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net")
__contributors__ = {}
__url__ = 'http://bugbot.ubuntulinux.nl/'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

28
Webcal/config.py Normal file
View File

@ -0,0 +1,28 @@
###
# Copyright (c) 2005,2006 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 supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Webcal', True)
Webcal = conf.registerPlugin('Webcal')
conf.registerChannelValue(conf.supybot.plugins.Webcal, 'url',
registry.String('',"""Webcal URL for the channel"""))
conf.registerChannelValue(conf.supybot.plugins.Webcal, 'topic',
registry.String('',"""Topic template"""))
conf.registerGlobalValue(conf.supybot.plugins.Webcal, 'defaultChannel',
registry.String('',"""Default channel for /msg replies"""))

209
Webcal/ical.py Normal file
View File

@ -0,0 +1,209 @@
#!/usr/bin/python
# Slightly modified version of the iCal module found at
# http://www.devoesquared.com/Software/iCal_Module
import os
import os.path
import re
import datetime
import time
import pytz # pytz can be found on http://pytz.sourceforge.net
SECONDS_PER_DAY=24*60*60
class ICalReader:
def __init__(self, data):
self.events = []
self.raw_data = data
self.readEvents()
def readEvents(self):
self.events = []
lines = self.raw_data.split('\n')
inEvent = False
eventLines = []
stRegex = re.compile("^BEGIN:VEVENT")
enRegex = re.compile("^END:VEVENT")
for line in lines:
if stRegex.match(line):
inEvent = True
eventLines = []
if inEvent:
eventLines.append(line)
if enRegex.match(line):
self.events.append(self.parseEvent(eventLines))
return self.events
def parseEvent(self, lines):
event = ICalEvent()
event.raw_data = "\n".join(lines)
startDate = None
rule = None
endDate = None
for line in lines:
if re.compile("^SUMMARY:(.*)").match(line):
event.summary = re.compile("^SUMMARY:(.*)").match(line).group(1)
elif re.compile("^DTSTART;.*:(.*).*").match(line):
startDate = self.parseDate(re.compile("^DTSTART;.*:(.*).*").match(line).group(1))
elif re.compile("^DTEND;.*:(.*).*").match(line):
endDate = self.parseDate(re.compile("^DTEND;.*:(.*).*").match(line).group(1))
elif re.compile("^EXDATE.*:(.*)").match(line):
event.addExceptionDate(parseDate(re.compile("^EXDATE.*:(.*)").match(line).group(1)))
elif re.compile("^RRULE:(.*)").match(line):
rule = re.compile("^RRULE:(.*)").match(line).group(1)
event.startDate = startDate
event.endDate = endDate
if rule:
event.addRecurrenceRule(rule)
return event
def parseDate(self, dateStr):
year = int(dateStr[0:4])
if year < 1970:
year = 1970
month = int(dateStr[4:4+2])
day = int(dateStr[6:6+2])
try:
hour = int(dateStr[9:9+2])
minute = int(dateStr[11:11+2])
except:
hour = 0
minute = 0
return datetime.datetime(year, month, day, hour, minute, tzinfo=pytz.UTC)
def selectEvents(self, selectFunction):
note = datetime.datetime.today()
self.events.sort()
events = filter(selectFunction, self.events)
return events
def todaysEvents(self, event):
return event.startsToday()
def tomorrowsEvents(self, event):
return event.startsTomorrow()
def eventsFor(self, date):
note = datetime.datetime.today()
self.events.sort()
ret = []
for event in self.events:
if event.startsOn(date):
ret.append(event)
return ret
class ICalEvent:
def __init__(self):
self.exceptionDates = []
self.dateSet = None
def __str__(self):
return self.summary
def __eq__(self, otherEvent):
return self.startDate == otherEvent.startDate
def addExceptionDate(self, date):
self.exceptionDates.append(date)
def addRecurrenceRule(self, rule):
self.dateSet = DateSet(self.startDate, self.endDate, rule)
def startsToday(self):
return self.startsOn(datetime.datetime.today())
def startsTomorrow(self):
tomorrow = datetime.datetime.fromtimestamp(time.time() + SECONDS_PER_DAY)
return self.startsOn(tomorrow)
def startsOn(self, date):
return (self.startDate.year == date.year and
self.startDate.month == date.month and
self.startDate.day == date.day or
(self.dateSet and self.dateSet.includes(date)))
def startTime(self):
return self.startDate
class DateSet:
def __init__(self, startDate, endDate, rule):
self.startDate = startDate
self.endDate = endDate
self.frequency = None
self.count = None
self.untilDate = None
self.byMonth = None
self.byDate = None
self.parseRecurrenceRule(rule)
def parseRecurrenceRule(self, rule):
if re.compile("FREQ=(.*?);").match(rule) :
self.frequency = re.compile("FREQ=(.*?);").match(rule).group(1)
if re.compile("COUNT=(\d*)").match(rule) :
self.count = int(re.compile("COUNT=(\d*)").match(rule).group(1))
if re.compile("UNTIL=(.*?);").match(rule) :
self.untilDate = DateParser.parse(re.compile("UNTIL=(.*?);").match(rule).group(1))
if re.compile("INTERVAL=(\d*)").match(rule) :
self.interval = int(re.compile("INTERVAL=(\d*)").match(rule).group(1))
if re.compile("BYMONTH=(.*?);").match(rule) :
self.byMonth = re.compile("BYMONTH=(.*?);").match(rule).group(1)
if re.compile("BYDAY=(.*?);").match(rule) :
self.byDay = re.compile("BYDAY=(.*?);").match(rule).group(1)
def includes(self, date):
if date == self.startDate:
return True
if self.untilDate and date > self.untilDate:
return False
if self.frequency == 'DAILY':
increment = 1
if self.interval:
increment = self.interval
d = self.startDate
counter = 0
while(d < date):
if self.count:
counter += 1
if counter >= self.count:
return False
d = d.replace(day=d.day+1)
if (d.day == date.day and
d.year == date.year and
d.month == date.month):
return True
elif self.frequency == 'WEEKLY':
if self.startDate.weekday() == date.weekday():
return True
else:
if self.endDate:
for n in range(0, self.endDate.day - self.startDate.day):
newDate = self.startDate.replace(day=self.startDate.day+n)
if newDate.weekday() == date.weekday():
return True
elif self.frequency == 'MONTHLY':
pass
elif self.frequency == 'YEARLY':
pass
return False

213
Webcal/plugin.py Normal file
View File

@ -0,0 +1,213 @@
###
# Copyright (c) 2005,2006 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 supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.schedule as schedule
import supybot.ircmsgs as ircmsgs
import pytz
import ical
import datetime
reload(ical)
def _event_to_string(event, timezone):
if not timezone:
return "%s UTC: %s" % (event.startDate.strftime("%d %b %H:%M"), event.summary)
return "%s: %s" % (event.startDate.astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M"), event.summary)
def diff(delta):
s = ''
if delta.days:
if delta.days > 1:
s = 's'
return '%d day%s' % (delta.days, s)
h = ''
if delta.seconds > 7200:
s = 's'
if delta.seconds > 3600:
h = '%d hour%s ' % (int(delta.seconds/3600),s)
s = ''
seconds = delta.seconds % 3600
if seconds > 120:
s = 's'
return '%s%d minute%s' % (h,(seconds/60),s)
class Webcal(callbacks.Plugin):
"""@schedule <timezone>: display the schedule in your timezone"""
threaded = True
def __init__(self, irc):
callbacks.Privmsg.__init__(self, irc)
self.irc = irc
schedule.addPeriodicEvent(self._refresh_cache, 60 * 20, name=self.name())
schedule.addPeriodicEvent(self._autotopics, 60, name=self.name() + 'b')
self.cache = {}
def die(self):
schedule.removeEvent(self.name())
schedule.removeEvent(self.name() + 'b')
self.cache.clear()
def reset(self):
self.cache.clear()
def _filter(self, event, channel, now):
#channel = '#ubuntu-meeting' # Testing hack
if channel.lower() not in event.raw_data.lower():
return False
delta = event.endDate - now
return delta.days >= 0 or (delta.days == -1 and abs(delta).seconds < 30 * 60)
def _gettopic(self, url, channel, do_update=False, only_update=False, timezone = None, no_topic=False):
if do_update or url not in self.cache.keys():
data = utils.web.getUrl(url)
parser = ical.ICalReader(data)
#parser = ical.ICalReader(ical.sample)
self.cache[url] = parser.events
if not only_update:
now = datetime.datetime.now(pytz.UTC)
events = filter(lambda x: self._filter(x,channel,now),self.cache[url])[:6]
preamble = ''
if len(events):
# The standard slack of 30 minutes after the meeting will be an
# error if there are 2 conscutive meetings.
if len(events) > 1 and events[1].startDate < now:
events = events[1:]
ev0 = events[0]
delta = abs(ev0.startDate - now)
if ev0.startDate < now or (delta.days == 0 and delta.seconds < 10 * 60):
preamble = 'Current meeting: %s | ' % ev0.summary.replace('Meeting','').strip()
events = events[1:]
events = map(lambda x: _event_to_string(x,timezone), events)
template = self.registryValue('topic', channel)
newtopic = ' | '.join(events).replace(' Meeting','')
if '%s' in template and not no_topic:
newtopic = template % str(newtopic)
return preamble + newtopic
def _nextmeeting(self, url, channel, timezone):
if url not in self.cache.keys():
data = utils.web.getUrl(url)
parser = ical.ICalReader(data)
#parser = ical.ICalReader(ical.sample)
self.cache[url] = parser.events
now = datetime.datetime.now(pytz.UTC)
events = filter(lambda x: self._filter(x,channel,now),self.cache[url])[:6]
preamble = ''
if len(events):
# The standard slack of 30 minutes after the meeting will be an
# error if there are 2 conscutive meetings.
if len(events) > 1 and events[1].startDate < now:
events = events[1:]
ev0 = events[0]
delta = abs(ev0.startDate - now)
if ev0.startDate < now or (delta.days == 0 and delta.seconds < 10 * 60):
return ' - Current meeting: %s ' % ev0.summary.replace('Meeting','').strip()
return ' - Next meeting: %s in %s' % (ev0.summary.replace('Meeting',''), diff(delta))
return ''
def _autotopics(self):
if not self.irc:
return
for c in self.irc.state.channels:
url = self.registryValue('url', c)
if url:
newtopic = self._gettopic(url, c)
if newtopic and not (newtopic.strip() == self.irc.state.getTopic(c).strip()):
self.irc.queueMsg(ircmsgs.topic(c, newtopic))
def _refresh_cache(self):
if not self.lastIrc:
return
for c in self.lastIrc.state.channels:
url = self.registryValue('url', c)
if url:
self._gettopic(url, c, True, true)
def topic(self, irc, msg, args):
url = self.registryValue('url', msg.args[0])
if not url:
return
newtopic = self._gettopic(url, msg.args[0], True)
if not (newtopic.strip() == irc.state.getTopic(msg.args[0]).strip()):
irc.queueMsg(ircmsgs.topic(msg.args[0], newtopic))
topic = wrap(topic)
def _tzfilter(self, tz, ud):
if tz == ud:
return True
pos = tz.find('/')
while not (pos == -1):
if tz[pos+1:] == ud:
return True
pos = tz.find('/',pos+1)
return False
def schedule(self, irc, msg, args, tz):
""" Retrieve the date/time of scheduled meetings in a specific timezone """
if not tz:
tz = 'utc'
if irc.isChannel(msg.args[0]):
c = msg.args[0]
else:
c = self.registryValue('defaultChannel')
if not c:
return
url = self.registryValue('url', c)
if not url:
return
tzs = filter(lambda x: self._tzfilter(x.lower(),tz.lower()), pytz.all_timezones)
if not tzs or 'gmt' in tz.lower():
irc.error('Unknown timezone: %s - Full list: http://bugbot.ubuntulinux.nl/timezones.html' % tz)
else:
irc.reply('Schedule for %s: %s' % (tzs[0],self._gettopic(url, c, timezone=tzs[0], no_topic=True)))
schedule = wrap(schedule, [additional('text')])
def now(self, irc, msg, args, tz):
""" Display the current time """
now = datetime.datetime.now(pytz.UTC)
if not tz:
tz = 'utc'
#irc.reply('Current time in UTC: %s' % now.strftime("%B %d %Y, %H:%M:%S"))
tzs = filter(lambda x: self._tzfilter(x.lower(),tz.lower()), pytz.all_timezones)
if not tzs or 'gmt' in tz.lower():
irc.error('Unknown timezone: %s - Full list: http://bugbot.ubuntulinux.nl/timezones.html' % tz)
else:
if irc.isChannel(msg.args[0]):
c = msg.args[0]
else:
c = self.registryValue('defaultChannel')
if not c:
return
#c = "#ubuntu-meeting"
url = self.registryValue('url', c)
print c, url
if not url:
meeting = ''
else:
meeting = self._nextmeeting(url, c, tzs[0])
irc.reply('Current time in %s: %s%s' % (tzs[0],now.astimezone(pytz.timezone(tzs[0])).strftime("%B %d %Y, %H:%M:%S"),meeting))
now = wrap(now, [additional('text')])
time = now
def doTopic(self, irc, msg):
url = self.registryValue('url', msg.args[0])
if not url:
return
irc.queueMsg(ircmsgs.privmsg(msg.nick, "The topic of %s is managed by me and filled with the contents of %s - please don't change manually" % (msg.args[0],url)))
Class = Webcal

3
Webcal/test.py Normal file
View File

@ -0,0 +1,3 @@
from supybot.test import *
class WebcalTestCase(PluginTestCase):
plugins = ('Webcal',)

592
Webcal/timezones.html Normal file
View File

@ -0,0 +1,592 @@
<html>
<head>
<title>Ubugtu and Ubotu</title>
<link rel="stylesheet" href="/bot.css" />
</head>
<body>
<div class="home">
<h1>Timezones <img src="xchat.png" alt="Hi!" /></h1>
<p>
These are all timezones known by Ubugtu. You don't need to use the full
zone name, the last part is enough. When this is ambiguous, the
alphabetic first timezone will be used. Timezones are not case sensitive
</p>
<p>&alefsym;</p>
<p>
Examples:<br />
<br />
For Europe/Amsterdam:<br />
@schedule amsterdam<br />
For America/North_Dakota/Center:<br />
@schedule North_Dakota/Center<br />
</p>
<p>&alefsym;</p>
<h1>All timezones</h1>
<p>
Africa/Abidjan<br />
Africa/Accra<br />
Africa/Addis_Ababa<br />
Africa/Algiers<br />
Africa/Asmera<br />
Africa/Bamako<br />
Africa/Bangui<br />
Africa/Banjul<br />
Africa/Bissau<br />
Africa/Blantyre<br />
Africa/Brazzaville<br />
Africa/Bujumbura<br />
Africa/Cairo<br />
Africa/Casablanca<br />
Africa/Ceuta<br />
Africa/Conakry<br />
Africa/Dakar<br />
Africa/Dar_es_Salaam<br />
Africa/Djibouti<br />
Africa/Douala<br />
Africa/El_Aaiun<br />
Africa/Freetown<br />
Africa/Gaborone<br />
Africa/Harare<br />
Africa/Johannesburg<br />
Africa/Kampala<br />
Africa/Khartoum<br />
Africa/Kigali<br />
Africa/Kinshasa<br />
Africa/Lagos<br />
Africa/Libreville<br />
Africa/Lome<br />
Africa/Luanda<br />
Africa/Lubumbashi<br />
Africa/Lusaka<br />
Africa/Malabo<br />
Africa/Maputo<br />
Africa/Maseru<br />
Africa/Mbabane<br />
Africa/Mogadishu<br />
Africa/Monrovia<br />
Africa/Nairobi<br />
Africa/Ndjamena<br />
Africa/Niamey<br />
Africa/Nouakchott<br />
Africa/Ouagadougou<br />
Africa/Porto-Novo<br />
Africa/Sao_Tome<br />
Africa/Timbuktu<br />
Africa/Tripoli<br />
Africa/Tunis<br />
Africa/Windhoek
</p>
<p>&alefsym;</p>
<p>
America/Adak<br />
America/Anchorage<br />
America/Anguilla<br />
America/Antigua<br />
America/Araguaina<br />
America/Argentina/Buenos_Aires<br />
America/Argentina/Catamarca<br />
America/Argentina/ComodRivadavia<br />
America/Argentina/Cordoba<br />
America/Argentina/Jujuy<br />
America/Argentina/La_Rioja<br />
America/Argentina/Mendoza<br />
America/Argentina/Rio_Gallegos<br />
America/Argentina/San_Juan<br />
America/Argentina/Tucuman<br />
America/Argentina/Ushuaia<br />
America/Aruba<br />
America/Asuncion<br />
America/Atka<br />
America/Bahia<br />
America/Barbados<br />
America/Belem<br />
America/Belize<br />
America/Boa_Vista<br />
America/Bogota<br />
America/Boise<br />
America/Buenos_Aires<br />
America/Cambridge_Bay<br />
America/Campo_Grande<br />
America/Cancun<br />
America/Caracas<br />
America/Catamarca<br />
America/Cayenne<br />
America/Cayman<br />
America/Chicago<br />
America/Chihuahua<br />
America/Coral_Harbour<br />
America/Cordoba<br />
America/Costa_Rica<br />
America/Cuiaba<br />
America/Curacao<br />
America/Danmarkshavn<br />
America/Dawson<br />
America/Dawson_Creek<br />
America/Denver<br />
America/Detroit<br />
America/Dominica<br />
America/Edmonton<br />
America/Eirunepe<br />
America/El_Salvador<br />
America/Ensenada<br />
America/Fort_Wayne<br />
America/Fortaleza<br />
America/Glace_Bay<br />
America/Godthab<br />
America/Goose_Bay<br />
America/Grand_Turk<br />
America/Grenada<br />
America/Guadeloupe<br />
America/Guatemala<br />
America/Guayaquil<br />
America/Guyana<br />
America/Halifax<br />
America/Havana<br />
America/Hermosillo<br />
America/Indiana/Indianapolis<br />
America/Indiana/Knox<br />
America/Indiana/Marengo<br />
America/Indiana/Vevay<br />
America/Indianapolis<br />
America/Inuvik<br />
America/Iqaluit<br />
America/Jamaica<br />
America/Jujuy<br />
America/Juneau<br />
America/Kentucky/Louisville<br />
America/Kentucky/Monticello<br />
America/Knox_IN<br />
America/La_Paz<br />
America/Lima<br />
America/Los_Angeles<br />
America/Louisville<br />
America/Maceio<br />
America/Managua<br />
America/Manaus<br />
America/Martinique<br />
America/Mazatlan<br />
America/Mendoza<br />
America/Menominee<br />
America/Merida<br />
America/Mexico_City<br />
America/Miquelon<br />
America/Monterrey<br />
America/Montevideo<br />
America/Montreal<br />
America/Montserrat<br />
America/Nassau<br />
America/New_York<br />
America/Nipigon<br />
America/Nome<br />
America/Noronha<br />
America/North_Dakota/Center<br />
America/Panama<br />
America/Pangnirtung<br />
America/Paramaribo<br />
America/Phoenix<br />
America/Port-au-Prince<br />
America/Port_of_Spain<br />
America/Porto_Acre<br />
America/Porto_Velho<br />
America/Puerto_Rico<br />
America/Rainy_River<br />
America/Rankin_Inlet<br />
America/Recife<br />
America/Regina<br />
America/Rio_Branco<br />
America/Rosario<br />
America/Santiago<br />
America/Santo_Domingo<br />
America/Sao_Paulo<br />
America/Scoresbysund<br />
America/Shiprock<br />
America/St_Johns<br />
America/St_Kitts<br />
America/St_Lucia<br />
America/St_Thomas<br />
America/St_Vincent<br />
America/Swift_Current<br />
America/Tegucigalpa<br />
America/Thule<br />
America/Thunder_Bay<br />
America/Tijuana<br />
America/Toronto<br />
America/Tortola<br />
America/Vancouver<br />
America/Virgin<br />
America/Whitehorse<br />
America/Winnipeg<br />
America/Yakutat<br />
America/Yellowknife
</p>
<p>&alefsym;</p>
<p>
Antarctica/Casey<br />
Antarctica/Davis<br />
Antarctica/DumontDUrville<br />
Antarctica/Mawson<br />
Antarctica/McMurdo<br />
Antarctica/Palmer<br />
Antarctica/Rothera<br />
Antarctica/South_Pole<br />
Antarctica/Syowa<br />
Antarctica/Vostok
</p>
<p>&alefsym;</p>
<p>
Arctic/Longyearbyen
</p>
<p>&alefsym;</p>
</p>
Asia/Aden<br />
Asia/Almaty<br />
Asia/Amman<br />
Asia/Anadyr<br />
Asia/Aqtau<br />
Asia/Aqtobe<br />
Asia/Ashgabat<br />
Asia/Ashkhabad<br />
Asia/Baghdad<br />
Asia/Bahrain<br />
Asia/Baku<br />
Asia/Bangkok<br />
Asia/Beirut<br />
Asia/Bishkek<br />
Asia/Brunei<br />
Asia/Calcutta<br />
Asia/Choibalsan<br />
Asia/Chongqing<br />
Asia/Chungking<br />
Asia/Colombo<br />
Asia/Dacca<br />
Asia/Damascus<br />
Asia/Dhaka<br />
Asia/Dili<br />
Asia/Dubai<br />
Asia/Dushanbe<br />
Asia/Gaza<br />
Asia/Harbin<br />
Asia/Hong_Kong<br />
Asia/Hovd<br />
Asia/Irkutsk<br />
Asia/Istanbul<br />
Asia/Jakarta<br />
Asia/Jayapura<br />
Asia/Jerusalem<br />
Asia/Kabul<br />
Asia/Kamchatka<br />
Asia/Karachi<br />
Asia/Kashgar<br />
Asia/Katmandu<br />
Asia/Krasnoyarsk<br />
Asia/Kuala_Lumpur<br />
Asia/Kuching<br />
Asia/Kuwait<br />
Asia/Macao<br />
Asia/Macau<br />
Asia/Magadan<br />
Asia/Makassar<br />
Asia/Manila<br />
Asia/Muscat<br />
Asia/Nicosia<br />
Asia/Novosibirsk<br />
Asia/Omsk<br />
Asia/Oral<br />
Asia/Phnom_Penh<br />
Asia/Pontianak<br />
Asia/Pyongyang<br />
Asia/Qatar<br />
Asia/Qyzylorda<br />
Asia/Rangoon<br />
Asia/Riyadh<br />
Asia/Saigon<br />
Asia/Sakhalin<br />
Asia/Samarkand<br />
Asia/Seoul<br />
Asia/Shanghai<br />
Asia/Singapore<br />
Asia/Taipei<br />
Asia/Tashkent<br />
Asia/Tbilisi<br />
Asia/Tehran<br />
Asia/Tel_Aviv<br />
Asia/Thimbu<br />
Asia/Thimphu<br />
Asia/Tokyo<br />
Asia/Ujung_Pandang<br />
Asia/Ulaanbaatar<br />
Asia/Ulan_Bator<br />
Asia/Urumqi<br />
Asia/Vientiane<br />
Asia/Vladivostok<br />
Asia/Yakutsk<br />
Asia/Yekaterinburg<br />
Asia/Yerevan
</p>
<p>&alefsym;</p>
<p>
Atlantic/Azores<br />
Atlantic/Bermuda<br />
Atlantic/Canary<br />
Atlantic/Cape_Verde<br />
Atlantic/Faeroe<br />
Atlantic/Jan_Mayen<br />
Atlantic/Madeira<br />
Atlantic/Reykjavik<br />
Atlantic/South_Georgia<br />
Atlantic/St_Helena<br />
Atlantic/Stanley
</p>
<p>&alefsym;</p>
<p>
Australia/ACT<br />
Australia/Adelaide<br />
Australia/Brisbane<br />
Australia/Broken_Hill<br />
Australia/Canberra<br />
Australia/Currie<br />
Australia/Darwin<br />
Australia/Hobart<br />
Australia/LHI<br />
Australia/Lindeman<br />
Australia/Lord_Howe<br />
Australia/Melbourne<br />
Australia/NSW<br />
Australia/North<br />
Australia/Perth<br />
Australia/Queensland<br />
Australia/South<br />
Australia/Sydney<br />
Australia/Tasmania<br />
Australia/Victoria<br />
Australia/West<br />
Australia/Yancowinna
</p>
<p>&alefsym;</p>
<p>
Brazil/Acre<br />
Brazil/DeNoronha<br />
Brazil/East<br />
Brazil/West
</p>
<p>&alefsym;</p>
<p>
CET<br />
CST6CDT
</p>
<p>&alefsym;</p>
<p>
Canada/Atlantic<br />
Canada/Central<br />
Canada/East-Saskatchewan<br />
Canada/Eastern<br />
Canada/Mountain<br />
Canada/Newfoundland<br />
Canada/Pacific<br />
Canada/Saskatchewan<br />
Canada/Yukon<br />
Chile/Continental<br />
Chile/EasterIsland
</p>
<p>&alefsym;</p>
<p>
Cuba<br />
EET<br />
EST<br />
EST5EDT<br />
Egypt<br />
Eire
</p>
<p>&alefsym;</p>
<p>
Etc/Greenwich<br />
Etc/UCT<br />
Etc/UTC<br />
Etc/Universal<br />
Etc/Zulu
</p>
<p>&alefsym;</p>
Europe/Amsterdam<br />
Europe/Andorra<br />
Europe/Athens<br />
Europe/Belfast<br />
Europe/Belgrade<br />
Europe/Berlin<br />
Europe/Bratislava<br />
Europe/Brussels<br />
Europe/Bucharest<br />
Europe/Budapest<br />
Europe/Chisinau<br />
Europe/Copenhagen<br />
Europe/Dublin<br />
Europe/Gibraltar<br />
Europe/Helsinki<br />
Europe/Istanbul<br />
Europe/Kaliningrad<br />
Europe/Kiev<br />
Europe/Lisbon<br />
Europe/Ljubljana<br />
Europe/London<br />
Europe/Luxembourg<br />
Europe/Madrid<br />
Europe/Malta<br />
Europe/Mariehamn<br />
Europe/Minsk<br />
Europe/Monaco<br />
Europe/Moscow<br />
Europe/Nicosia<br />
Europe/Oslo<br />
Europe/Paris<br />
Europe/Prague<br />
Europe/Riga<br />
Europe/Rome<br />
Europe/Samara<br />
Europe/San_Marino<br />
Europe/Sarajevo<br />
Europe/Simferopol<br />
Europe/Skopje<br />
Europe/Sofia<br />
Europe/Stockholm<br />
Europe/Tallinn<br />
Europe/Tirane<br />
Europe/Tiraspol<br />
Europe/Uzhgorod<br />
Europe/Vaduz<br />
Europe/Vatican<br />
Europe/Vienna<br />
Europe/Vilnius<br />
Europe/Warsaw<br />
Europe/Zagreb<br />
Europe/Zaporozhye<br />
Europe/Zurich
</p>
<p>&alefsym;</p>
<p>
GB<br />
GB-Eire<br />
Greenwich<br />
HST<br />
Hongkong<br />
Iceland
</p>
<p>&alefsym;</p>
<p>
Indian/Antananarivo<br />
Indian/Chagos<br />
Indian/Christmas<br />
Indian/Cocos<br />
Indian/Comoro<br />
Indian/Kerguelen<br />
Indian/Mahe<br />
Indian/Maldives<br />
Indian/Mauritius<br />
Indian/Mayotte<br />
Indian/Reunion
</p>
<p>&alefsym;</p>
<p>
Iran<br />
Israel<br />
Jamaica<br />
Japan<br />
Kwajalein<br />
Libya<br />
MET<br />
MST<br />
MST7MDT
</p>
<p>&alefsym;</p>
<p>
Mexico/BajaNorte<br />
Mexico/BajaSur<br />
Mexico/General
</p>
<p>&alefsym;</p>
<p>
NZ<br />
NZ-CHAT<br />
Navajo<br />
PRC<br />
PST8PDT
</p>
<p>&alefsym;</p>
<p>
Pacific/Apia<br />
Pacific/Auckland<br />
Pacific/Chatham<br />
Pacific/Easter<br />
Pacific/Efate<br />
Pacific/Enderbury<br />
Pacific/Fakaofo<br />
Pacific/Fiji<br />
Pacific/Funafuti<br />
Pacific/Galapagos<br />
Pacific/Gambier<br />
Pacific/Guadalcanal<br />
Pacific/Guam<br />
Pacific/Honolulu<br />
Pacific/Johnston<br />
Pacific/Kiritimati<br />
Pacific/Kosrae<br />
Pacific/Kwajalein<br />
Pacific/Majuro<br />
Pacific/Marquesas<br />
Pacific/Midway<br />
Pacific/Nauru<br />
Pacific/Niue<br />
Pacific/Norfolk<br />
Pacific/Noumea<br />
Pacific/Pago_Pago<br />
Pacific/Palau<br />
Pacific/Pitcairn<br />
Pacific/Ponape<br />
Pacific/Port_Moresby<br />
Pacific/Rarotonga<br />
Pacific/Saipan<br />
Pacific/Samoa<br />
Pacific/Tahiti<br />
Pacific/Tarawa<br />
Pacific/Tongatapu<br />
Pacific/Truk<br />
Pacific/Wake<br />
Pacific/Wallis<br />
Pacific/Yap
</p>
<p>&alefsym;</p>
<p>
Poland<br />
Portugal<br />
ROC<br />
ROK<br />
Singapore<br />
Turkey<br />
UCT
</p>
<p>&alefsym;</p>
<p>
US/Alaska<br />
US/Aleutian<br />
US/Arizona<br />
US/Central<br />
US/East-Indiana<br />
US/Eastern<br />
US/Hawaii<br />
US/Indiana-Starke<br />
US/Michigan<br />
US/Mountain<br />
US/Pacific<br />
US/Pacific-New<br />
US/Samoa
</p>
<p>&alefsym;</p>
<p>
UTC<br />
Universal<br />
W-SU<br />
WET<br />
Zulu<br />
posixrules
</p>
</div>
</body>
</html>

93
bot.css Normal file
View File

@ -0,0 +1,93 @@
body {
font-family: verdana, sans;
font-weight: bold;
background-color: #d9bb7a;
color: #980101;
font-size: 10px;
}
div.home {
margin: 20px auto;
width: 300px;
border: 2px solid #980101;
padding: 0px 10px;
background-color: #fdd99b;
text-align: center;
}
.submit {
font-size: 10px;
border: solid 1px #980101;
color: #980101;
font-weight: bold;
background-color: #fdff99;
}
.input {
font-size: 10px;
border: solid 1px #980101;
color: #980101;
font-weight: bold;
background-color: white;
}
th {
border-bottom: solid 1px #980101;
text-align: center;
}
td,th {
font-size: 10px;
text-align: left;
vertical-align: top;
white-space: nowrap;
}
td.comment {
white-space: normal;
padding-left: 20px;
}
li {
list-style: none;
}
table {
width: 100%%;
padding-top: 1em;
clear: both;
}
div.main {
margin: 20px;
border: 2px solid #980101;
padding: 0px;
background-color: #fdd99b;
text-align: center;
}
div.pdata {
text-align: right;
padding-right: 10px;
float: right;
}
div.search {
float: left;
text-align: left;
padding-left: 10px;
}
a, span.pseudolink {
color: #d40000;
text-decoration: underline;
cursor: pointer;
}
span.removal {
color: #6699cc;
}
tr.bg2 {
background-color: #fdff99;
}
div.invisible {
display: none;
}
div.log {
display: none;
color: black;
width: 100%%;
font-family: monospace;
white-space: normal;
text-align: left;
font-weight: normal;
}

33
commoncgi.py Normal file
View File

@ -0,0 +1,33 @@
import cgi, cgitb, re, sys, math, os, md5, sqlite, random, time, datetime, pytz, Cookie, StringIO
import cPickle as pickle
cgitb.enable()
form = cgi.FieldStorage()
cookie = Cookie.SimpleCookie()
if os.environ.has_key('HTTP_COOKIE'):
cookie.load(os.environ['HTTP_COOKIE'])
if cookie.has_key('sess'):
cookie['sess']['max-age'] = 2592000
cookie['sess']['version'] = 1
if cookie.has_key('tz'):
cookie['tz']['max-age'] = 2592000
cookie['tz']['version'] = 1
sys.stdout = StringIO.StringIO()
def send_page(template):
data = sys.stdout.getvalue()
sys.stdout = sys.__stdout__
print "Content-Type: text/html"
print cookie
print ""
fd = open(template)
tmpl = fd.read()
fd.close()
print tmpl % data
sys.exit(0)
def q(txt):
return txt.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')