Remove old, unported, and unused plugins.

* Bantracker
* IRCLogin
* Lart
* Mess
* Webcal
This commit is contained in:
Krytarik Raido 2021-12-04 04:04:04 +01:00
parent e9306925c0
commit 2c8bc43bf8
58 changed files with 0 additions and 10761 deletions

View File

@ -1,67 +0,0 @@
This plugin can store all bans/kicks etc in an sqlite database. It includes a
cgi script to view bans/kicks and comment on them. To view/user the bantracker
web interface a user must use the @btlogin command from the bot. They must also
have the 'bantracker' capability.
You can use the @mark <nick|hostmask> [<channel>] [<comment>]
command to manually add an entry to the bantracker without having to actially
kick/ban someone.
The schema of the SQLite2 database:
CREATE TABLE bans (
id INTEGER PRIMARY KEY,
channel VARCHAR(30) NOT NULL,
mask VARCHAR(100) NOT NULL,
operator VARCHAR(30) NOT NULL,
time VARCHAR(300) NOT NULL,
removal DATETIME,
removal_op VARCHAR(30),
log TEXT
);
CREATE TABLE comments (
ban_id INTEGER,
who VARCHAR(100) NOT NULL,
comment MEDIUMTEXT NOT NULL,
time VARCHAR(300) NOT NULL
);
CREATE TABLE sessions (
session_id VARCHAR(50) PRIMARY KEY,
user MEDIUMTEXT NOT NULL,
time INT NOT NULL
);
CREATE INDEX comments_ban_id ON comments(ban_id);
To configure the plugin, create the SQLite2 database with above structure and
set supybot.plugins.bantracker.database to its filename. Then enable it, either
per-channel or globally, by setting the channel variable:
supybot.plugins.bantracker.enabled
You can create the database by using the "sqlite" command-line tool by passing
the file name and then copy/paste the above table schema. Then type ".quit" to
save and exit.
If you choose to enable this plugin during the initial setup (with the command
supybot-wizard), then the database will be created automatically for you.
If you wish to use the web interface, it also uses commoncgi.py which should be
on your sys.path (or as you can see in cgt/bans.cgi, sys.path is modified to
include the dir of commoncgi.py).
You should place the contents of the cgi/ directory somewhere accessible by
your web server, and modify the CONFIG_FILENAME variable near the top of the
script to point to the location of your bantracker.conf.
Then modify the bantracker.conf file to reflect the proper values for your
setup.
The meanings of the entries in bantracker.conf are:
Key Type Description
anonymous_access Boolean True if annonmous access is allowed, otherwise
False.
database String The full path to the SQLite bantracker database.
results_per_page Number The maximum number of results that should be shown
per page.
plugin_path String The full path to the directory that contains the
commoncgi.py file.
irc_network String
The DNS name of the IRC network anonymous users are
directed to when anonymous access is disabled.
irc_channel String The channel name anonymous users are directed to
when anonmous access is disabled.

View File

@ -1,42 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005-2007 Dennis Kaarsemaker
# Copyright (c) 2008-2010 Terence Simpson
# Copyright (c) 2010 Elián Hanisch
#
# 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 can store all kick/ban/remove/mute actions
"""
import supybot
import supybot.world as world
__version__ = "0.3.1"
__author__ = supybot.Author("Terence Simpson", "tsimpson", "tsimpson@ubuntu.com")
__contributors__ = {
supybot.Author("Elián Hanisch", "m4v", "lambdae2@gmail.com"): ['Author', 'Maintainer'],
supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net"): ['Original Author']
}
__url__ = 'https://launchpad.net/ubuntu-bots/'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

View File

@ -1,13 +0,0 @@
div.main {
text-align: left;
max-width: 500px;
}
#hform > fieldset, #comment_form > fieldset {
max-width: 400px;
}
.highlight {
background-color: yellow;
}

View File

@ -1,118 +0,0 @@
RegExp.escape = function escape(text) {
if (!arguments.callee.sRE) {
var specials = [
'.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^'
];
arguments.callee.sRE = new RegExp(
'(\\' + specials.join('|\\') + ')', 'g'
);
}
return text.replace(arguments.callee.sRE, '\\$1');
}
String.prototype.HalfHTMLEscape = function() {
return this.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;');
}
String.prototype.HTMLEscape = function HTMLEscape() {
return this.HalfHTMLEscape().replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
var banlog = {
doneSetup: false,
hform: null,
log_id: null,
mark: null,
regex: null,
textlog: null,
lines: null,
force: false,
log: function() {},
highlight: function() {
var term = banlog.mark.value;
banlog.log("term: " + term + "\nbanlog.regex.checked: " + banlog.regex.checked)
if(term == "") {
for(var i=0; i<banlog.lines.length; ++i)
banlog.lines[i].className = "";
return true;
}
try {
if(banlog.regex.checked) {
banlog.log("term: " + term.HalfHTMLEscape());
term = new RegExp(term.HalfHTMLEscape(), 'i')
} else {
banlog.log("term: " + RegExp.escape(term.HTMLEscape()));
term = new RegExp(RegExp.escape(term.HTMLEscape()), 'i')
}
} catch(err) {
banlog.log(err);
return true;
}
for(var i=0; i<banlog.lines.length; ++i) {
var line = banlog.lines[i];
if(term.test(line.innerHTML)) {
line.className = "highlight"
} else {
line.className = ""
}
}
return true;
},
keyup: function() {
banlog.highlight();
},
submit: function(e) {
if(banlog.force)
return true;
banlog.highlight();
e.preventDefault();
return false;
},
dolog: function() {
banlog.log("this: " + this + ", arguments:" + arguments[0]);
return false;
},
setup: function() {
if(banlog.doneSetup) return;
banlog.doneSetup = true;
// if(window.console && console.log)
// banlog.log = console.log;
banlog.hform = document.getElementById("hform");
banlog.log_id = document.getElementById("log").value;
banlog.mark = document.getElementById("mark");
banlog.regex = document.getElementById("regex");
banlog.textlog = document.getElementById("textlog");
banlog.lines = banlog.textlog.getElementsByTagName("span");
banlog.hform.addEventListener("submit", banlog.submit , false);
banlog.mark.addEventListener("keyup", banlog.keyup , false);
banlog.regex.addEventListener("change", banlog.keyup , false);
var really_submit = document.createElement("input");
really_submit.type = 'submit';
really_submit.className = 'input';
really_submit.value = 'Refresh';
really_submit.addEventListener('click', function() { banlog.force = true; }, false);
banlog.hform.appendChild(really_submit);
}
};
if(window.addEventListener) {
window.addEventListener('load', banlog.setup, false);
} else if(document.addEventListener) {
document.addEventListener('load', banlog.setup, false);
} else {
window.onload = document.onload = banlog.setup;
}

View File

@ -1,595 +0,0 @@
#!/usr/bin/env python
###
# Copyright (c) 2005-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import sys
import time, datetime, pytz
import ConfigParser
import sqlite
import cPickle as pickle
CONFIG_FILENAME = "bantracker.conf"
config = ConfigParser.RawConfigParser()
config.add_section('webpage')
# set default values
config.set('webpage', 'database', '/home/bot/data/bans.db')
config.set('webpage', 'results_per_page', '100')
config.set('webpage', 'anonymous_access', 'True')
config.set('webpage', 'PLUGIN_PATH', '/var/www/bot')
config.set('webpage', 'irc_network', 'irc.freenode.net')
config.set('webpage', 'irc_channel', '#ubuntu-ops')
try:
config.readfp(open(CONFIG_FILENAME))
except IOError:
try:
config.write(open(CONFIG_FILENAME, 'w'))
except IOError:
pass
# This needs to be set to the location of the commoncgi.py file
PLUGIN_PATH = config.get('webpage', 'PLUGIN_PATH')
if PLUGIN_PATH:
sys.path.append(PLUGIN_PATH)
try:
from commoncgi import *
except:
print "Content-Type: text/html"
print
print "<p>Failed to load the module commoncgi</p>"
print "<p>Check that the configured option PLUGIN_PATH is correct.</p>"
sys.exit(-1)
db = config.get('webpage', 'database')
num_per_page = config.getint('webpage', 'results_per_page')
anonymous_access = config.getboolean('webpage', 'anonymous_access')
irc_network = config.get('webpage', 'irc_network')
irc_channel = config.get('webpage', 'irc_channel')
t1 = time.time()
try:
con = sqlite.connect(db)
cur = con.cursor()
except sqlite.DatabaseError:
print >> sys.stderr, "Unable to connect to to database '%s'" % db
send_page('bans.tmpl')
def db_execute(query, args):
try:
cur.execute(query, args)
return cur
except sqlite.OperationalError:
print >> sys.stderr, "The database is locked, wait a bit and try again."
send_page('bans.tmpl')
# Login check
error = ''
user = None
# Delete old sessions
try:
session_timeout = int(time.time()) - (2592000 * 3)
cur.execute('DELETE FROM sessions WHERE time < %d', (session_timeout,))
except:
pass
# Session handling
if 'sess' in form:
cookie['sess'] = form['sess'].value
if 'sess' in cookie:
sess = cookie['sess'].value
try:
cur.execute('SELECT user FROM sessions WHERE session_id=%s',(sess,))
user = cur.fetchall()[0][0]
except:
con.commit()
pass
if not user and not anonymous_access:
print "Sorry, bantracker is not available for anonymous users<br />"
print 'Join <a href="irc://%s/%s">%s</a> on %s to discuss bans.' % (irc_network, irc_channel[1:], irc_channel, irc_network)
send_page('bans.tmpl')
haveQuery = False
def urlencode(**kwargs):
"""Return the url options as a string, inserting additional ones if given."""
d = dict([ (i.name, i.value) for i in form.list ])
d.update(kwargs)
return utils.web.urlencode(d.items())
def isTrue(value):
"""Returns True if the form value is one of "1", "true", "yes", or "on", case insensitive"""
if not value:
return False
return value.lower() in ('1', 'true', 'yes', 'on')
def isFalse(value):
"""Returns True if the form value is one of "0", "false", "no", or "off", case insensitive"""
if not value:
return False
return value.lower() in ('0', 'false', 'no', 'off')
def isOn(k):
global haveQuery
default = not haveQuery
if not k in form:
return default
if isTrue(form[k].value):
return True
if isFalse(form[k].value):
return False
return default
# Log
if 'log' in form:
log_id = form['log'].value
plain = False
mark = False
mark_value = ''
regex = False
regex_value = ''
if 'plain' in form and isTrue(form['plain'].value):
plain = True
if 'mark' in form:
mark = True
mark_value = form['mark'].value
if 'regex' in form and isTrue(form['regex'].value):
regex = True
regex_value = 'checked="checked"'
log = db_execute("SELECT log FROM bans WHERE id=%s", log_id).fetchall()
if not log or not log[0] or not log[0][0]:
if plain:
print >> sys.stderr, '<div id="error">No such log with ID: %s' % q(log_id)
send_page('empty.tmpl')
else:
print >> sys.stderr, 'No such log with ID: %s' % q(log_id)
send_page('log.tmpl')
log = log[0][0]
if not plain:
print ' <div class="main">'
print ' <form id="hform" action="" method="get">'
print ' <fieldset>'
print ' <input type="hidden" name="log" id="log" value="%s">' % q(log_id)
print ' <label for="mark">Highlight:</label>'
print ' <input type="text" name="mark" id="mark" value="%s"/>' % q(mark_value)
print ' <input type="checkbox" name="regex" id="regex" %s>' % regex_value
print ' <label for="regex">Regex</label>'
print ' </fieldset>'
print ' <input class="input" type="submit" id="hform_submit" value="Update">'
print ' </form>'
print ' </div>'
pad = '<br />'
if plain:
pad = ''
print '<pre id="textlog">'
else:
print '<div id="textlog">'
if mark:
if regex:
try:
mark = re.compile(mark_value, re.I)
except:
print >> sys.stderr, "Malformed regex %r" % mark_value
mark = False
else:
escaped = re.escape(mark_value).replace('%', '.*')
mark = re.compile(escaped, re.I)
lines = log.splitlines()
for line in lines:
if plain:
print q(line)
elif mark:
if mark.search(line):
print ' <span class="highlight">%s</span>%s' % (q(line), pad)
else:
print " <span>%s</span>%s" % (q(line), pad)
else:
print ' <span>%s</span>%s' % (q(line), pad)
if plain:
print '</pre>'
send_page('empty.tmpl')
print '</div><br />'
print '<div>'
print ' <form id="comment_form" action="" method="post">'
print ' <fieldset>'
print ' <legend>Add a comment</legend>'
print ' <textarea cols="50" rows="5" class="input" name="comment"></textarea><br />'
print ' <input type="hidden" name="comment_id" value="%s" />' % log_id
print ' <input class="submit" type="submit" value="Send" />'
print ' </fieldset>'
print ' </form>'
print '</div>'
send_page('log.tmpl')
# Main page
# Process comments
if 'comment' in form and 'comment_id' in form and user:
cur.execute('SELECT ban_id FROM comments WHERE ban_id=%s and comment=%s', (form['comment_id'].value, form['comment'].value))
comm = cur.fetchall()
if not len(comm):
try:
cur.execute('INSERT INTO comments (ban_id, who, comment, time) VALUES (%s, %s, %s, %s)',
(form['comment_id'].value, user,form['comment'].value, pickle.dumps(datetime.datetime.now(pytz.UTC))))
con.commit()
except sqlite.DatabaseError:
con.rollback()
print >> sys.stderr, "Sorry, failed to submit comment to the database. Please try again later."
# Write the page
print '<form action="" method="POST">'
# Personal data
print '<div class="pdata">'
if user:
print 'Logged in as: %s <br /> ' % user
print 'Timezone: '
if 'tz' in form and form['tz'].value in pytz.common_timezones:
tz = form['tz'].value
elif 'tz' in cookie and cookie['tz'].value in pytz.common_timezones:
tz = cookie['tz'].value
else:
tz = 'UTC'
cookie['tz'] = tz
print '<select class="input" name="tz">'
for zone in pytz.common_timezones:
if zone == tz:
print '<option value="%s" selected="selected">%s</option>' % (zone, zone)
else:
print '<option value="%s">%s</option>' % (zone, zone)
print '</select>'
print '<input class="submit" type="submit" value="change" />'
print '</form><br />'
print '</div>'
tz = pytz.timezone(tz)
haveQuery = 'query' in form or 'channel' in form or 'operator' in form
def makeInput(name, label, before=False, type="checkbox", extra=''):
if before:
print '<label for="%s">%s</label>' % (name, label)
value = ''
if type == "checkbox":
if isOn(name):
value = ' checked="checked"'
else:
if name in form:
value = ' value="%s"' % form[name].value,
print '<input class="input" type="%s" name="%s" id="%s"%s /> %s' \
% (type, name, name, value, extra)
if not before:
print '<label for="%s">%s</label>' % (name, label)
print '<br />'
# Search form
print '<div class="search">'
print '<form action="" method="GET">'
makeInput("channel", "Channel:", True, "text")
makeInput("operator", "Operator:", True, "text")
makeInput("query", "Search:", True, "text", extra="(% and _ are wildcards)")
# Search fields
print '<div style="float:left">'
makeInput("kicks", "Kicks")
makeInput("bans", "Bans")
makeInput("oldbans", "Removed bans")
makeInput("marks", "Marks")
print '</div>'
print '<div style="float:left">'
makeInput("mutes", "Mutes")
makeInput("oldmutes", "Removed mutes")
makeInput("floodbots", "Include Floodbots")
print '</div>'
print '<div style="clear:both"><input class="submit" type="submit" value="search" /></div>'
print '</form></div>'
if not haveQuery:
# sqlite2 sucks, getting the last bans takes a lot of time.
# so lets disable that so at least the page loads quickly.
## Maybe we should include a link on the main page for those who do want
## to list the latest bans? --tsimpson
print '<div style="clear: both"></div>'
send_page('bans.tmpl')
# Select and filter bans
def getBans(id=None, mask=None, kicks=True, oldbans=True, bans=True, marks=True, floodbots=True, operator=None,
channel=None, limit=None, offset=0, withCount=False):
sql = "SELECT channel, mask, operator, time, removal, removal_op, id FROM bans"
args = []
where = []
if id:
where.append("id=%s")
args.append(id)
if mask:
where.append("mask LIKE %s")
args.append('%' + mask + '%')
if not floodbots:
where.append("operator NOT LIKE 'floodbot%%'")
if operator:
where.append("operator LIKE %s")
args.append(operator)
if channel:
where.append("channel LIKE %s")
args.append(channel)
if not marks:
where.append("id NOT IN (SELECT ban_id FROM comments WHERE comment LIKE '**MARK**%%')")
if not kicks:
where.append("mask LIKE '%%!%%'")
if not (oldbans or bans):
where.append("mask NOT LIKE '%%!%%'")
else:
if kicks:
s = "(mask NOT LIKE '%%%%!%%%%' OR (mask LIKE '%%%%!%%%%' AND %s))"
else:
s = "%s"
if not oldbans:
where.append(s % "removal IS NULL")
elif not bans:
where.append(s % "removal IS NOT NULL")
if where:
where = " WHERE " + " AND ".join(where)
else:
where = ''
sql += where
sql += " ORDER BY id DESC"
if limit:
sql += " LIMIT %s OFFSET %s" % (limit, offset)
#print sql, "<br/>"
#print args, "<br/>"
# Things seems faster if we do the query BEFORE counting. Due to caches probably.
bans = db_execute(sql, args).fetchall()
count = None
if withCount:
sql_count = "SELECT count(*) FROM bans%s" % where
count = int(db_execute(sql_count, args).fetchone()[0])
return bans, count
return bans
def filterMutes(item):
if item[1][0] == '%':
return False
return True
def getQueryTerm(query, term):
if term[-1] != ':':
term += ':'
if term in query:
idx = query.index(term) + len(term)
ret = query[idx:].split(None, 1)[0]
query = query.replace(term + ret, '', 1).strip()
return (query, ret)
return (query, None)
page = 0
if 'page' in form:
page = int(form['page'].value)
bans = []
ban_count = 0
query = oper = chan = None
if 'query' in form:
query = form['query'].value
if query and query.isdigit():
bans = getBans(id=int(query))
ban_count = len(bans)
if not bans:
if 'channel' in form:
chan = form['channel'].value
if 'operator' in form:
oper = form['operator'].value
filter_mutes = not (isOn('mutes') or isOn('oldmutes'))
filter_bans = not (isOn('bans') or isOn('oldbans'))
filter_bans_or_mutes = bool(filter_bans) ^ bool(filter_mutes)
if filter_bans_or_mutes:
# we are going to filter the mutes from ban list, this sucks, because then we can't
# paginate correctly using SQL with LIMIT and OFFSET, so we *have* to get all bans and
# paginate manually.
limit = offset = None
else:
limit = num_per_page
offset = num_per_page * page
bans, ban_count = getBans(mask=query, kicks=isOn('kicks'),
oldbans=isOn('oldbans') or isOn('oldmutes'),
bans=isOn('bans') or isOn('mutes'),
marks=isOn('marks'),
floodbots=isOn('floodbots'),
operator=oper,
channel=chan,
limit=limit,
offset=offset,
withCount=True)
#print 'filtering', filter_bans_or_mutes, '<br/>'
#print "total count", ban_count, "bans", len(bans), '<br/>'
if filter_bans_or_mutes:
if filter_mutes:
bans = filter(lambda x: filterMutes(x), bans)
elif filter_bans:
bans = filter(lambda x: not filterMutes(x), bans)
ban_count = len(bans)
# pick the bans for current page, since we didn't do it with SQL
if ban_count > num_per_page:
bans = bans[page * num_per_page: (page + 1) * num_per_page]
#print "total count after filtering", ban_count, '<br/>'
# Sort the bans
def _sortf(x1,x2,field):
if x1[field] < x2[field]: return -1
if x1[field] > x2[field]: return 1
return 0
if 'sort' in form:
try:
field = int(form['sort'].value)
except:
pass
else:
if field in (0,1,2,6,10,11,12,16):
bans.sort(lambda x1,x2: _sortf(x1,x2,field%10))
if field >= 10:
bans.reverse()
if haveQuery:
if not ban_count:
print '<div style="clear: both">Nothing found.</div>'
elif ban_count == 1:
print '<div style="clear: both">Found one match.</div>'
else:
print '<div style="clear: both">Found %s matches.</div>' % ban_count
# Pagination
if bans:
pagination = '<div style="clear: both">\n&middot;\n'
num_pages = int(math.ceil(ban_count / float(num_per_page)))
for i in range(num_pages):
if page == i:
pagination += '<a href="?%s">[%d]</a> &middot;\n' % (urlencode(page=i), i + 1)
else:
pagination += '<a href="?%s">%d</a> &middot;\n' % (urlencode(page=i), i + 1)
pagination += '</div>\n'
print pagination
else:
# nothign to show
print '<div style="clear: both"></div>' # if I don't print this the page is messed up.
send_page('bans.tmpl')
# Empty log div, will be filled with AJAX
print '<div id="log" class="log">&nbsp;</div>'
# Main bans table
# Table heading
print '<div>'
print '<table cellspacing="0">'
print '<thead>'
print '<tr>'
for h in [ ('Channel', 0, 45),
('Nick/Mask', 1, 25),
('Operator', 2, 0),
('Time', 6, 15) ]:
# Negative integers for backwards searching
try:
v = int(form['sort'].value)
if v < 10: h[1] += 10
except:
pass
#print '<th style="width: %s%%"><a href="?sort=%s">%s</a></th>' % (h[2], h[1], h[0])
print '<th style="width: %s%%">%s</th>' % (h[2], h[0])
print '<th style="width: 15%">Log</th>'
print '<th>ID</th>'
print '</tr>'
print '</thead>'
print '<tbody>'
# And finally, display them!
i = 0
for b in bans:
if i % 2:
print '<tr class="bg2">'
else:
print "<tr>"
# Channel
print '<td id="channel-%d">%s %s</td>' % (b[6],'',b[0])
# Mask
print '<td id="mask-%d">%s' % (b[6], b[1])
# Ban removal
if b[4]:
print '<br /><span class="removal">(Removed)</span>'
print'</td>'
# Operator
print '<td id="operator-%d">%s' % (b[6], b[2])
if b[4]: # Ban removal
print u'<br /><span class="removal">%s</span>' % b[5]
print '</td>'
# Time
print '<td id="time-%d">%s' % (b[6], 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>
Show log <a class="pseudolink" id="loglink-%s" onclick="showlog('%s')">inline</a>
| <a href="?log=%d">full</a>
</td>""" % (b[6], b[6], b[6])
# ID
print '<td id="id-%d">%d</td>' % (b[6], b[6])
print "</tr>"
# Comments
if i % 2:
print '<tr class="bg2">'
else:
print "<tr>"
db_execute('SELECT who, comment, time FROM comments WHERE ban_id=%d', (b[6],))
comments = cur.fetchall()
if len(comments) == 0:
print '<td colspan="5" class="comment">'
print '<div class="invisible" id="comments">%d</div>' % b[6]
print '<span class="removal">(No comments) </span>'
else:
print '<td colspan="5" class="comment" id="comments-%d">' % b[6]
print '<div class="invisible" id="comments">%d</div>' % b[6]
for c in comments:
print q(c[1]).replace('\n', '<br />')
print u' <span class="removal"><br />%s, %s</span><br />' % \
(c[0],pickle.loads(c[2]).astimezone(tz).strftime("%b %d %Y %H:%M:%S"))
if user:
print """<span class="pseudolink" onclick="toggle('%s','comment')">Add comment</span>""" % b[6]
print """<div class="invisible" id="comment_%s"><br />""" % b[6]
print """ <form action="" method="post">"""
print """ <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" />"""
print """ </form>"""
print """</div>"""
print '</td><td></td></tr>'
i += 1
print '</table>'
if bans:
print pagination
t2 = time.time()
print "<!-- Generated in %.4f seconds -->" % (t2 - t1)
# Aaaaaaaaaaaaaaaaand send!
send_page('bans.tmpl')

View File

@ -1,78 +0,0 @@
window.addEventListener("load", function() {
sc = document.createElement("script");
sc.setAttribute("type", "text/javascript");
sc.setAttribute("src", "banlog.js");
document.getElementsByTagName("head")[0].appendChild(sc);
}, false);
s = null;
r = null;
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) {
if (s == item) {
c = new getObj('log');
if( c.style.display == 'block' || c.style.display == '' ) {
c.style.display = 'none';
document.getElementById("loglink-" + item).textContent = "inline";
} else {
c.style.diaply = 'block';
document.getElementById("loglink-" + item).textContent = "Hide";
}
s = null;
} else {
loadlog(item);
}
}
function loadlog(id) {
r = new XMLHttpRequest();
var qobj = new getObj("query");
/*
var objv = [];
for(var i in qobj)
objv.push(i);
alert(objv);
*/
var reqUri = "bans.cgi?log=" + id;
if(qobj.obj.value && qobj.obj.value != '')
reqUri += "&mark=" + qobj.obj.value.split(' ').pop();
reqUri += "&plain=1";
r.onreadystatechange = printlog;
r.open("GET", reqUri, true);
r.send(null);
s = id;
}
function printlog() {
if (r.readyState == 4) {
var c = new getObj('log');
c.obj.innerHTML = r.responseText;
document.getElementById("loglink-" + s).textContent = "Hide";
c.style.display = 'block';
setupHighlight();
}
}

View File

@ -1,23 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Ubottu bantracker</title>
<link rel="stylesheet" href="bot.css" />
<link rel="shortcut icon" href="favicon.ico" type="image/png" />
<script type="text/javascript" src="bans.js"></script>
</head>
<body>
<div class="main">
<h1>Ubottu Bantracker</h1>
<div class="errors">
%e
</div>
<div>
%s
</div>
<p>&copy;2006 Dennis Kaarsemaker<br>
Edited by Terence Simpson</p>
</div>
</body>
</html>

View File

@ -1,8 +0,0 @@
[webpage]
anonymous_access = True
database = /home/bot/data/bans.db
irc_network = irc.freenode.net
results_per_page = 100
irc_channel = #ubuntu-ops
plugin_path = /var/www/bot

View File

@ -1,2 +0,0 @@
%e
%s

View File

@ -1,17 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Ban log</title>
<link rel="stylesheet" type="text/css" href="bot.css" />
<link rel="stylesheet" type="text/css" href="banlog.css" />
<script type="text/javascript" src="banlog.js"></script>
</head>
<body>
<div class="errors">
%e
</div>
%s
</body>
</html>

View File

@ -1,239 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005-2007 Dennis Kaarsemaker
# Copyright (c) 2008-2011 Terence Simpson
# Copyright (c) 2010 Elián Hanisch
#
# 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
class ValidTypes(registry.OnlySomeStrings):
"""Invalid type, valid types are: 'removal', 'ban' or 'quiet'."""
validStrings = ('removal', 'ban', 'quiet')
class SpaceSeparatedListOfTypes(registry.SpaceSeparatedListOf):
Value = ValidTypes
def configure(advanced):
from supybot.questions import yn, something, output
import sqlite
import re
import os
from supybot.utils.str import format
def anything(prompt, default=None):
"""Because supybot is pure fail"""
from supybot.questions import expect
return expect(prompt, [], default=default)
Bantracker = conf.registerPlugin('Bantracker', True)
def getReviewTime():
output("How many days should the bot wait before requesting a ban/quiet review?")
review = something("Can be an integer or decimal value. Zero disables reviews.", default=str(Bantracker.request.review._default))
try:
review = float(review)
if review < 0:
raise TypeError
except TypeError:
output("%r is an invalid value, it must be an integer or float greater or equal to 0" % review)
return getReviewTime()
else:
return review
output("If you choose not to enabled Bantracker for all channels, it can be enabled per-channel with the '@Config channel' command")
enabled = yn("Enable Bantracker for all channels?")
database = something("Location of the Bantracker database", default=Bantracker.database._default)
bansite = anything("URL of the Bantracker web interface, without the 'bans.cgi'. (leave this blank if you aren't running a web server)")
request = yn("Enable review and comment requests from bot?", default=False)
if request and advanced:
output("Which types would you like the bot to request comments for?")
output(format("The available request types are %L", Bantracker.request.type._default))
types = anything("Separate types by spaces or commas:", default=', '.join(Bantracker.request.type._default))
type = set([])
for name in re.split(r',?\s+', types):
name = name.lower()
if name in ValidTypes.validStrings:
type.add(name)
output("Which nicks should be bot not requets comments from?")
output("This is useful if you have automated channel bots that should not be directly asked for reviews")
output("Is case-insensitive and the wildcards '*' and '?' are accepted.")
ignores = anything("Separate nicks by spaces or commas:", default=', '.join(Bantracker.request.ignore._default))
ignore = set([])
for name in re.split(r',?\s+', ignores):
name = name.lower()
ignore.add(name)
output("You can set the comment and review requests for some nicks to be forwarded to specific nicks/channels")
output("This is useful if you have automated channel bots that should not be directly asked for reviews")
output("Which nicks should these requests be forwarded for?")
output("Is case-insensitive and the wildcards '*' and '?' are accepted.")
forwards = anything("Separate nicks by spaces or commas:", default=', '.join(Bantracker.request.forward._default))
forward = set([])
for name in re.split(r',?\s+', forwards):
name = name.lower()
forward.add(name)
output("Which nicks/channels should those requests be forwarded to?")
output("Is case-insensitive and wildcards '*' and '?' are accepted.")
channels_i = anything("Separate nicks/channels by spaces or commas:", default=', '.join(Bantracker.request.forward._default))
channels = set([])
for name in re.split(r',?\s+', channels_i):
name = name.lower()
channels.add(name)
review = getReviewTime()
else:
type = Bantracker.request.type._default
ignore = Bantracker.request.ignore._default
forward = Bantracker.request.forward._default
channels = Bantracker.request.forward.channels._default
review = Bantracker.request.review._default
Bantracker.enabled.setValue(enabled)
Bantracker.database.setValue(database)
Bantracker.bansite.setValue(bansite)
Bantracker.request.setValue(request)
Bantracker.request.type.setValue(type)
Bantracker.request.ignore.setValue(ignore)
Bantracker.request.forward.setValue(forward)
Bantracker.request.forward.channels.setValue(channels)
Bantracker.request.review.setValue(review)
# Create the initial database
db_file = Bantracker.database()
if not db_file:
db_file = conf.supybot.directories.data.dirize('bans.db')
output("supybot.plugins.Bantracker.database will be set to %r" % db_file)
Bantracker.database.setValue(db_file)
if os.path.exists(db_file):
output("%r already exists" % db_file)
return
output("Creating an initial database in %r" % db_file)
con = sqlite.connect(db_file)
cur = con.cursor()
try:
cur.execute("""CREATE TABLE 'bans' (
id INTEGER PRIMARY KEY,
channel VARCHAR(30) NOT NULL,
mask VARCHAR(100) NOT NULL,
operator VARCHAR(30) NOT NULL,
time VARCHAR(300) NOT NULL,
removal DATETIME,
removal_op VARCHAR(30),
log TEXT
)""")
#"""
cur.execute("""CREATE TABLE comments (
ban_id INTEGER,
who VARCHAR(100) NOT NULL,
comment MEDIUMTEXT NOT NULL,
time VARCHAR(300) NOT NULL
)""")
#"""
cur.execute("""CREATE TABLE sessions (
session_id VARCHAR(50) PRIMARY KEY,
user MEDIUMTEXT NOT NULL,
time INT NOT NULL
)""")
#"""
cur.execute("CREATE INDEX comments_ban_id ON comments(ban_id)")
except:
con.rollback()
raise
else:
con.commit()
finally:
cur.close()
con.close()
## Notes on setting up the web interface.
output("If you wish to use the web interface to Bantracker, please copy the cgi")
output("direcdtory to somewhere accessible from your webserver. Remember to modify")
output("the CONFIG_FILENAME variable in cgi/bans.cgi, and modify the")
output("bantracker.conf configuration file with the appropriate information.")
output("See the README.txt file for more information.")
Bantracker = conf.registerPlugin('Bantracker')
conf.registerChannelValue(Bantracker, 'enabled',
registry.Boolean(False, """Enable the bantracker"""))
conf.registerGlobalValue(Bantracker, 'database',
registry.String(conf.supybot.directories.data.dirize('bans.db'), "Filename of the bans database", private=True))
conf.registerGlobalValue(Bantracker, 'bansite',
registry.String('', "Web site for the bantracker, without the 'bans.cgi' appended", private=True))
conf.registerChannelValue(Bantracker, 'request',
registry.Boolean(False,
"Enable message requests from bot"))
conf.registerChannelValue(Bantracker.request, 'type',
SpaceSeparatedListOfTypes(['removal', 'ban', 'quiet'],
"List of events for which the bot should request a comment."))
conf.registerChannelValue(Bantracker.request, 'ignore',
registry.SpaceSeparatedListOfStrings(['FloodBot?', 'FloodBotK?', 'ChanServ'],
"List of nicks for which the bot won't request a comment."\
" Is case insensible and wildcards * ? are accepted."))
conf.registerChannelValue(Bantracker.request, 'forward',
registry.SpaceSeparatedListOfStrings([],
"List of nicks for which the bot will forward the request to"\
" the channels/nicks defined in forwards.channels option."\
" Is case insensible and wildcards * ? are accepted."))
conf.registerChannelValue(Bantracker.request.forward, 'channels',
registry.SpaceSeparatedListOfStrings([],
"List of channels/nicks to forward the comment request if the op is in the forward list."))
conf.registerChannelValue(Bantracker, 'review',
registry.Boolean(True,
"Enable/disable reviews per channel."))
conf.registerGlobalValue(Bantracker.review, 'when',
registry.Float(7,
"Days after which the bot will request for review a ban. Can be an integer or decimal"
" value. Zero disables reviews globally."))
conf.registerChannelValue(Bantracker.review, 'ignore',
registry.SpaceSeparatedListOfStrings(['FloodBot?', 'FloodBotK?', 'ChanServ'],
"List of nicks for which the bot won't request a review."\
" Is case insensible and wildcards * ? are accepted."))
conf.registerChannelValue(Bantracker.review, 'forward',
registry.SpaceSeparatedListOfStrings([],
"List of nicks for which the bot will forward the reviews to"\
" the channels/nicks defined in forwards.channels option."\
" Is case insensible and wildcards * ? are accepted."))
conf.registerChannelValue(Bantracker.review.forward, 'channels',
registry.SpaceSeparatedListOfStrings([],
"List of channels/nicks to forward the request if the op is in the forward list."))
conf.registerChannelValue(Bantracker, 'autoremove',
registry.Boolean(True,
"""Enable/disable autoremoval of bans."""))
conf.registerChannelValue(Bantracker.autoremove, 'notify',
registry.Boolean(True,
"""Enable/disable notifications of removal of bans."""))
conf.registerChannelValue(Bantracker.autoremove.notify, 'channels',
registry.SpaceSeparatedListOfStrings([],
"""List of channels/nicks to notify about automatic removal of bans."""))

File diff suppressed because it is too large Load Diff

View File

@ -1,672 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
# Copyright (c) 2010 Elián Hanisch
#
# 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.
#
###
from supybot.test import *
import supybot.conf as conf
import supybot.ircmsgs as ircmsgs
import supybot.world as world
import re
import time
pluginConf = conf.supybot.plugins.Bantracker
pluginConf.enabled.setValue(True)
pluginConf.bansite.setValue('http://foo.bar.com')
pluginConf.database.setValue('bantracker-test.db')
def quiet(channel, hostmask, prefix='', msg=None):
"""Returns a MODE to quiet nick on channel."""
return ircmsgs.mode(channel, ('+q', hostmask), prefix, msg)
class BantrackerTestCase(ChannelPluginTestCase):
plugins = ('Bantracker',)
def setUp(self):
self.setDb()
super(BantrackerTestCase, self).setUp()
pluginConf.request.setValue(False) # disable comments
pluginConf.request.ignore.set('')
pluginConf.request.forward.set('')
pluginConf.review.setValue(False) # disable reviews
pluginConf.review.when.setValue(1.0/86400) # one second
pluginConf.review.ignore.set('')
pluginConf.review.forward.set('')
# Bantracker for some reason doesn't use Supybot's own methods for check capabilities,
# so it doesn't have a clue about testing and screws my tests by default.
# This would fix it until I bring myself to take a look
cb = self.getCallback()
f = cb.check_auth
def test_check_auth(*args, **kwargs):
if world.testing:
return True
else:
return f(*args, **kwargs)
cb.check_auth = test_check_auth
cb.opped.clear()
def setDb(self):
import sqlite, os
dbfile = os.path.join(os.curdir, pluginConf.database())
try:
os.remove(dbfile)
except:
pass
db = sqlite.connect(dbfile)
cursor = db.cursor()
cursor.execute('CREATE TABLE bans ('
'id INTEGER PRIMARY KEY,'
'channel VARCHAR(30) NOT NULL,'
'mask VARCHAR(100) NOT NULL,'
'operator VARCHAR(30) NOT NULL,'
'time VARCHAR(300) NOT NULL,'
'removal DATETIME,'
'removal_op VARCHAR(30),'
'log TEXT)')
cursor.execute('CREATE TABLE comments ('
'ban_id INTEGER,'
'who VARCHAR(100) NOT NULL,'
'comment MEDIUMTEXT NOT NULL,'
'time VARCHAR(300) NOT NULL)')
cursor.execute('CREATE TABLE sessions ('
'session_id VARCHAR(50) PRIMARY KEY,'
'user MEDIUMTEXT NOT NULL,'
'time INT NOT NULL)')
cursor.execute('CREATE TABLE users ('
'username VARCHAR(50) PRIMARY KEY,'
'salt VARCHAR(8),'
'password VARCHAR(50))')
db.commit()
cursor.close()
db.close()
def getCallback(self):
for cb in self.irc.callbacks:
if cb.name() == 'Bantracker':
break
return cb
def getDb(self):
return self.getCallback().db
def query(self, query, parms=()):
cursor = self.getDb().cursor()
cursor.execute(query, parms)
return cursor.fetchall()
def feedBan(self, hostmask, prefix='', channel=None, mode='b'):
if not channel:
channel = self.channel
if not prefix:
prefix = 'op!user@host.net'
if mode == 'b':
ban = ircmsgs.ban(channel, hostmask, prefix=prefix)
elif mode == 'q':
ban = quiet(channel, hostmask, prefix=prefix)
elif mode == 'k':
ban = ircmsgs.kick(channel, hostmask, s='kthxbye!', prefix=prefix)
elif mode == 'p':
ban = ircmsgs.part(channel, prefix=hostmask,
s='requested by %s (kthxbye!)' %prefix[:prefix.find('!')])
self.irc.feedMsg(ban)
return ban
def op(self):
msg = ircmsgs.mode(self.channel, ('+o', self.irc.nick),
'Chanserv!service@service')
self.irc.feedMsg(msg)
def deop(self):
msg = ircmsgs.mode(self.channel, ('-o', self.irc.nick),
'Chanserv!service@service')
self.irc.feedMsg(msg)
def testComment(self):
self.assertResponse('comment 1', "I don't know any ban with id 1.")
self.feedBan('asd!*@*')
self.assertResponse('comment 1', 'No comments recorded for ban 1')
self.assertResponse('comment 1 this is a test',
'Comment added.')
self.assertRegexp('comment 1', 'test: this is a test$')
self.assertResponse('comment 1 this is a test, another test',
'Comment added.')
self.feedBan('nick', mode='k')
self.assertResponse('comment 2 this is a kick, 2week',
"Failed to set duration time on 2 (not a ban or quiet)")
msg = self.irc.takeMsg()
self.assertEqual(msg.args[1], 'test: Comment added.')
self.assertResponse('comment 1 not a valid, duration 2',
'Comment added.')
def testMultiComment(self):
self.feedBan('asd!*@*')
self.feedBan('qwe!*@*')
self.assertResponse('comment 1,2,3 this is a test, 2 days',
"I don't know any ban with id 3.")
msg = self.irc.takeMsg()
self.assertEqual(msg.args[1],
"test: Comment added. 1 and 2 will be removed after 2 days.")
self.assertRegexp('comment 1,2', 'test: this is a test, 2 days$')
msg = self.irc.takeMsg()
self.assertTrue(msg.args[1].endswith("test: this is a test, 2 days"))
def testCommentDuration(self):
self.feedBan('asd!*@*')
self.assertResponse('comment 1 this is a test, 1 week 10',
'Comment added. 1 will be removed after 1 week.')
self.assertRegexp('comment 1', 'test: this is a test, 1 week 10$')
self.assertRegexp('duration 1', 'expires in 1 week$')
def testCommentDurationRemove(self):
self.feedBan('asd!*@*')
self.assertResponse('comment 1 this is a test, -10',
"Failed to set duration time on 1 (ban isn't marked for removal)")
msg = self.irc.takeMsg()
self.assertEqual(msg.args[1], 'test: Comment added.')
self.assertResponse('comment 1 this is a test, 10',
'Comment added. 1 will be removed soon.')
self.assertResponse('comment 1 this is a test, -10',
"Comment added. 1 won't expire.")
self.assertRegexp('duration 1', 'never expires$')
def testCommentRequest(self):
pluginConf.request.setValue(True)
# test bans
self.feedBan('asd!*@*')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Please comment on the ban of asd!*@* in #test, use: @comment 1"
" <comment>")
# test quiets
self.feedBan('dude!*@*', mode='q')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Please comment on the quiet of dude!*@* in #test, use: @comment 2"
" <comment>")
# test kick/part
self.feedBan('dude', mode='k')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Please comment on the removal of dude in #test, use: @comment 3"
" <comment>")
self.feedBan('dude!dude@trollpit.com', mode='p')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Please comment on the removal of dude in #test, use: @comment 4"
" <comment>")
def testCommentIgnore(self):
pluginConf.request.setValue(True)
pluginConf.request.ignore.set('FloodBot? FloodBotK?')
self.feedBan('asd!*@*', prefix='floodbotk1!bot@botpit.com')
msg = self.irc.takeMsg()
self.assertEqual(msg, None)
self.feedBan('dude!*@*', mode='q', prefix='FloodBot1!bot@botpit.com')
msg = self.irc.takeMsg()
self.assertEqual(msg, None)
self.feedBan('dude', mode='k', prefix='FloodBot2!bot@botbag.com')
msg = self.irc.takeMsg()
self.assertEqual(msg, None)
self.feedBan('dude!dude@trollpit.com', mode='p', prefix='FloodBotK2!bot@botbag.com')
msg = self.irc.takeMsg()
self.assertEqual(msg, None)
self.feedBan('asd!*@*')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Please comment on the ban of asd!*@* in #test, use: @comment 5"
" <comment>")
def testCommentForward(self):
pluginConf.request.setValue(True)
pluginConf.request.forward.set('bot')
pluginConf.request.forward.channels.set('#channel')
self.feedBan('qwe!*@*')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Please comment on the ban of qwe!*@* in #test, use: @comment 1"
" <comment>")
self.feedBan('zxc!*@*', prefix='bot!user@host.com')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"NOTICE #channel :Please somebody comment on the ban of zxc!*@* in #test done by bot,"
" use: @comment 2 <comment>")
def testReview(self):
pluginConf.review.setValue(True)
cb = self.getCallback()
self.feedBan('asd!*@*')
cb.reviewBans()
self.assertFalse(cb.pendingReviews)
print 'waiting 4 secs..'
time.sleep(2)
cb.reviewBans()
# check is pending
self.assertTrue(cb.pendingReviews)
# send msg if a user with a matching host says something
self.feedMsg('Hi!', frm='op!user@fakehost.net')
self.assertEqual(self.irc.takeMsg(), None)
self.feedMsg('Hi!', frm='op_!user@host.net')
self.assertEqual(str(self.irc.takeMsg()).strip(),
"PRIVMSG op_ :Review: ban 'asd!*@*' set on %s in #test, link: "\
"%s/bans.cgi?log=1" %(cb.bans['#test'][0].ascwhen, pluginConf.bansite()))
# don't ask again
cb.reviewBans()
self.assertFalse(cb.pendingReviews)
# test again with two ops
self.feedBan('asd2!*@*')
self.irc.takeMsg()
self.feedBan('qwe!*@*', prefix='otherop!user@home.net', mode='q')
self.irc.takeMsg()
time.sleep(2)
cb.reviewBans()
self.assertTrue(len(cb.pendingReviews) == 2)
self.feedMsg('Hi!', frm='op!user@fakehost.net')
self.assertEqual(self.irc.takeMsg(), None)
self.assertResponse('banreview', 'Pending ban reviews (2): otherop:1 op:1')
self.feedMsg('Hi!', frm='mynickissocreative!user@home.net')
self.assertEqual(str(self.irc.takeMsg()).strip(),
"PRIVMSG mynickissocreative :Review: quiet 'qwe!*@*' set on %s in #test, link: "\
"%s/bans.cgi?log=3" %(cb.bans['#test'][2].ascwhen, pluginConf.bansite()))
self.feedMsg('ping', to='test', frm='op!user@host.net') # in a query
self.irc.takeMsg() # drop pong reply
self.assertEqual(str(self.irc.takeMsg()).strip(),
"PRIVMSG op :Review: ban 'asd2!*@*' set on %s in #test, link: "\
"%s/bans.cgi?log=2" %(cb.bans['#test'][1].ascwhen, pluginConf.bansite()))
def testReviewForward(self):
pluginConf.review.setValue(True)
pluginConf.review.forward.set('bot')
pluginConf.review.forward.channels.set('#channel')
cb = self.getCallback()
self.feedBan('asd!*@*', prefix='bot!user@host.net')
self.feedBan('asd!*@*', prefix='bot!user@host.net', mode='q')
cb.reviewBans(self.irc)
self.assertFalse(cb.pendingReviews)
print 'waiting 2 secs..'
time.sleep(2)
cb.reviewBans(self.irc)
# since it's a forward, it was sent already
self.assertFalse(cb.pendingReviews)
self.assertTrue(re.search(
r"^NOTICE #channel :Review: ban 'asd!\*@\*' set by bot on .* in #test,"\
r" link: .*/bans\.cgi\?log=1$", str(self.irc.takeMsg()).strip()))
self.assertTrue(re.search(
r"^NOTICE #channel :Review: quiet 'asd!\*@\*' set by bot on .* in #test,"\
r" link: .*/bans\.cgi\?log=2$", str(self.irc.takeMsg()).strip()))
def testReviewIgnore(self):
pluginConf.review.setValue(True)
pluginConf.review.ignore.set('FloodBot? FloodBotK?')
cb = self.getCallback()
self.feedBan('asd!*@*', prefix='floodbotk1!bot@botpit.com')
cb.reviewBans(self.irc)
self.assertFalse(cb.pendingReviews)
print 'waiting 2 secs..'
time.sleep(2)
cb.reviewBans(self.irc)
# since it's was ignored, it should not be queued
self.assertFalse(cb.pendingReviews)
def testReviewNickFallback(self):
"""If for some reason we don't have ops full hostmask, revert to nick match. This may be
needed in the future as hostmasks aren't stored in the db."""
pluginConf.review.setValue(True)
cb = self.getCallback()
self.feedBan('asd!*@*')
cb.bans['#test'][0].who = 'op' # replace hostmask by nick
print 'waiting 2 secs..'
time.sleep(2)
cb.reviewBans()
# check is pending
self.assertTrue(cb.pendingReviews)
self.assertResponse('banreview', 'Pending ban reviews (1): op:1')
# send msg if a user with a matching nick says something
self.feedMsg('Hi!', frm='op_!user@host.net')
msg = self.irc.takeMsg()
self.assertEqual(msg, None)
self.feedMsg('Hi!', frm='op!user@host.net')
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"PRIVMSG op :Review: ban 'asd!*@*' set on %s in #test, link: "\
"%s/bans.cgi?log=1" %(cb.bans['#test'][0].ascwhen, pluginConf.bansite()))
# check not pending anymore
self.assertFalse(cb.pendingReviews)
def testReviewStore(self):
"""Save pending reviews and when bans were last checked. This is needed for plugin
reloads"""
msg1 = ircmsgs.privmsg('nick', 'Hello World')
msg2 = ircmsgs.privmsg('nick', 'Hello World') # duplicate msg, should be ignored
msg2 = ircmsgs.privmsg('nick', 'Hello World2')
msg3 = ircmsgs.notice('#chan', 'Hello World')
msg4 = ircmsgs.privmsg('nick_', 'Hello World')
pr = self.getCallback().pendingReviews
pr['host.net'] = [('op', msg1), ('op', msg2), ('op_', msg3)]
pr['home.net'] = [('dude', msg4)]
self.assertResponse('banreview', 'Pending ban reviews (4): op_:1 dude:1 op:2')
pr.close()
pr.clear()
pr.open()
self.assertResponse('banreview', 'Pending ban reviews (4): op_:1 dude:1 op:2')
items = pr['host.net']
self.assertTrue(items[0][0] == 'op' and items[0][1] == msg1)
self.assertTrue(items[1][0] == 'op' and items[1][1] == msg2)
self.assertTrue(items[2][0] == 'op_' and items[2][1] == msg3)
items = pr['home.net']
self.assertTrue(items[0][0] == 'dude' and items[0][1] == msg4)
def testReviewBanreview(self):
pr = self.getCallback().pendingReviews
m = ircmsgs.privmsg('#test', 'ban review')
pr['host.net'] = [('op', m), ('op_', m), ('op', m)]
pr['home.net'] = [('dude', m)]
pr[None] = [('dude_', m)]
self.assertResponse('banreview', 'Pending ban reviews (5): dude_:1 op_:1 dude:1 op:2')
self.assertResponse('banreview --verbose',
'Pending ban reviews (5): op@host.net:2 dude_:1 op_@host.net:1 dude@home.net:1')
self.assertRegexp('banreview --flush op@host', '^No reviews for op@host')
self.assertResponse('banreview --view dude_', 'ban review')
self.assertResponse('banreview', 'Pending ban reviews (5): dude_:1 op_:1 dude:1 op:2')
self.assertResponse('banreview --flush op@host.net', 'ban review')
# love ya supybot ↓
self.assertEqual(self.irc.takeMsg().args[1], 'test: ban review')
self.assertResponse('banreview', 'Pending ban reviews (3): dude_:1 op_:1 dude:1')
def testBan(self):
self.feedBan('asd!*@*')
fetch = self.query("SELECT id,channel,mask,operator FROM bans")
self.assertEqual((1, '#test', 'asd!*@*', 'op'), fetch[0])
def testBanRealname(self):
self.feedBan('$r:asd')
fetch = self.query("SELECT id,channel,mask,operator FROM bans")
self.assertEqual((1, '#test', '$r:asd', 'op'), fetch[0])
def testQuiet(self):
self.feedBan('asd!*@*', mode='q')
fetch = self.query("SELECT id,channel,mask,operator FROM bans")
self.assertEqual((1, '#test', '%asd!*@*', 'op'), fetch[0])
def testKick(self):
self.feedBan('troll', mode='k')
fetch = self.query("SELECT id,channel,mask,operator FROM bans")
self.assertEqual((1, '#test', 'troll', 'op'), fetch[0])
def testPart(self):
self.feedBan('troll!user@trollpit.net', mode='p')
fetch = self.query("SELECT id,channel,mask,operator FROM bans")
self.assertEqual((1, '#test', 'troll', 'op'), fetch[0])
def testDuration(self):
self.op()
cb = self.getCallback()
self.feedBan('asd!*@*')
cb.autoRemoveBans(self.irc)
self.assertFalse(cb.managedBans)
self.assertResponse('duration 1 1', "1 will be removed soon.")
self.assertTrue(cb.managedBans) # ban in list
print 'waiting 2 secs ...'
time.sleep(2)
cb.autoRemoveBans(self.irc)
self.assertFalse(cb.managedBans) # ban removed
msg = self.irc.takeMsg() # unban msg
self.assertEqual(str(msg).strip(), "MODE #test -b :asd!*@*")
def testDurationRemove(self):
self.feedBan('asd!*@*')
self.assertResponse('duration 1 -1',
"Failed to set duration time on 1 (ban isn't marked for removal)")
self.assertResponse('duration 1 10', '1 will be removed soon.')
self.assertResponse('duration 1 -1', "1 won't expire.")
self.assertRegexp('duration 1', 'never expires')
def testDurationMergeModes(self):
self.op()
cb = self.getCallback()
self.feedBan('asd!*@*')
self.feedBan('qwe!*@*')
self.feedBan('zxc!*@*')
self.feedBan('asd!*@*', mode='q')
self.feedBan('qwe!*@*', mode='q')
self.feedBan('zxc!*@*', mode='q')
self.assertNotError('duration 1,2,3,4,5,6 1')
print 'waiting 2 secs ...'
time.sleep(2)
cb.autoRemoveBans(self.irc)
msg = self.irc.takeMsg() # unban msg
self.assertEqual(str(msg).strip(),
"MODE #test -qqqb zxc!*@* qwe!*@* asd!*@* :zxc!*@*")
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(), "MODE #test -bb qwe!*@* :asd!*@*")
def testDurationMultiSet(self):
self.feedBan('asd!*@*')
self.assertResponse('duration 1,2 10d',
"Failed to set duration time on 2 (unknow id)")
msg = self.irc.takeMsg()
self.assertEqual(msg.args[1],
"test: 1 will be removed after 1 week and 3 days.")
def testDurationQuiet(self):
self.op()
cb = self.getCallback()
self.feedBan('asd!*@*', mode='q')
self.assertNotError('duration 1 1')
print 'waiting 2 sec ...'
time.sleep(2)
cb.autoRemoveBans(self.irc)
msg = self.irc.takeMsg() # unban msg
self.assertEqual(str(msg).strip(), "MODE #test -q :asd!*@*")
def testDurationRealname(self):
self.op()
cb = self.getCallback()
self.feedBan('$r:asd?asd', mode='b')
self.assertNotError('duration 1 1')
print 'waiting 2 sec ...'
time.sleep(2)
cb.autoRemoveBans(self.irc)
msg = self.irc.takeMsg() # unban msg
self.assertEqual(str(msg).strip(), "MODE #test -b :$r:asd?asd")
def testDurationBadType(self):
self.feedBan('nick', mode='k')
self.assertResponse('duration 1 1',
"Failed to set duration time on 1 (not a ban or quiet)")
self.feedBan('$a:nick')
self.assertResponse('duration 2 1', '2 will be removed soon.')
def testDurationBadId(self):
self.assertResponse('duration 1 1',
"Failed to set duration time on 1 (unknow id)")
def testDurationInactiveBan(self):
self.feedBan('asd!*@*')
self.irc.feedMsg(ircmsgs.unban(self.channel, 'asd!*@*',
'op!user@host.net'))
self.assertResponse('duration 1 1',
"Failed to set duration time on 1 (ban was removed)")
def testDurationTimeFormat(self):
cb = self.getCallback()
self.feedBan('asd!*@*')
self.assertNotError('duration 1 10m')
self.assertEqual(cb.managedBans.shelf[0].expires, 600)
self.assertNotError('duration 1 2 weeks')
self.assertEqual(cb.managedBans.shelf[0].expires, 1209600)
self.assertNotError('duration 1 1m 2 days')
self.assertEqual(cb.managedBans.shelf[0].expires, 172860)
self.assertNotError('duration 1 24h 1day')
self.assertEqual(cb.managedBans.shelf[0].expires, 172800)
self.assertNotError('duration 1 1s1m1h1d1w1M1y')
self.assertEqual(cb.managedBans.shelf[0].expires, 34822861)
self.assertNotError('duration 1 999')
self.assertEqual(cb.managedBans.shelf[0].expires, 999)
self.assertNotError('duration 1 1 second')
self.assertEqual(cb.managedBans.shelf[0].expires, 1)
def testDurationTimeFormatBad(self):
self.assertError('duration 1 10 apples')
def testDurationNotice(self):
cb = self.getCallback()
self.feedBan('asd!*@*')
self.assertNotError('duration 1 300')
pluginConf.autoremove.notify.channels.set('#test')
try:
cb.autoRemoveBans(self.irc)
msg = self.irc.takeMsg()
self.assertEqual(str(msg).strip(),
"NOTICE #test :ban \x0309[\x03\x021\x02\x0309]\x03 \x0310asd!*@*\x03"\
" in \x0310#test\x03 will expire in a few minutes.")
# don't send the notice again.
cb.autoRemoveBans(self.irc)
self.assertFalse(self.irc.takeMsg())
finally:
pluginConf.autoremove.notify.channels.set('')
def testAutoremoveStore(self):
self.feedBan('asd!*@*')
self.feedBan('qwe!*@*')
self.feedBan('zxc!*@*', mode='q')
self.assertNotError('duration 1 10m')
self.assertNotError('duration 2 1d')
self.assertNotError('duration 3 1w')
cb = self.getCallback()
cb.managedBans.shelf[1].notified = True
cb.managedBans.close()
cb.managedBans.shelf = []
cb.managedBans.open()
L = cb.managedBans.shelf
for i, n in enumerate((600, 86400, 604800)):
self.assertEqual(L[i].expires, n)
for i, n in enumerate((False, True, False)):
self.assertEqual(L[i].notified, n)
for i, n in enumerate((1, 2, 3)):
self.assertEqual(L[i].ban.id, n)
for i, n in enumerate(('asd!*@*', 'qwe!*@*', '%zxc!*@*')):
self.assertEqual(L[i].ban.mask, n)
self.assertEqual(L[0].ban.channel, '#test')
def testBaninfo(self):
cb = self.getCallback()
self.feedBan('asd!*@*')
self.assertResponse('duration 1',
"[1] ban - asd!*@* - #test - never expires")
self.assertNotError('duration 1 10')
self.assertResponse('duration 1',
"[1] ban - asd!*@* - #test - expires soon")
self.assertNotError('duration 1 34502')
self.assertResponse('duration 1',
"[1] ban - asd!*@* - #test - expires in 9 hours and 35 minutes")
self.irc.feedMsg(ircmsgs.unban(self.channel, 'asd!*@*',
'op!user@host.net'))
self.assertResponse('duration 1',
"[1] ban - asd!*@* - #test - not active")
def testBaninfoGeneral(self):
cb = self.getCallback()
self.feedBan('asd!*@*')
self.feedBan('qwe!*@*')
self.assertNotError('duration 1 1d')
self.assertResponse('duration', "1 ban set to expire: 1")
self.assertNotError('duration 2 1d')
self.assertResponse('duration', "2 bans set to expire: 1 and 2")
def testOpTrack(self):
cb = self.getCallback()
self.assertEqual(cb.opped['#test'], False)
self.op()
self.assertEqual(cb.opped['#test'], True)
self.deop()
self.assertEqual(cb.opped['#test'], False)
self.op()
self.irc.feedMsg(ircmsgs.part('#test', prefix=self.prefix))
self.irc.feedMsg(ircmsgs.join('#test', prefix=self.prefix))
self.irc.takeMsg() # MODE msg
self.irc.takeMsg() # WHO msg
self.assertEqual(cb.opped['#test'], False)
def testOpDuration(self):
cb = self.getCallback()
self.feedBan('asd!*@*')
self.assertNotError('duration 1 1')
print 'waiting 2 secs ...'
time.sleep(2)
cb.autoRemoveBans(self.irc)
msg = self.irc.takeMsg() # op msg
self.assertEqual(str(msg).strip(), "PRIVMSG Chanserv :op #test test")
self.op()
msg = self.irc.takeMsg() # unban msg
self.assertEqual(str(msg).strip(), "MODE #test -bo asd!*@* :test")
def testOpFail(self):
import supybot.drivers as drivers
import supybot.schedule as schedule
pluginConf.autoremove.notify.channels.set('#test')
try:
cb = self.getCallback()
self.feedBan('asd!*@*')
self.assertNotError('duration 1 1')
print 'waiting 4 secs ...'
time.sleep(2)
cb.autoRemoveBans(self.irc)
msg = self.irc.takeMsg() # op msg
self.assertEqual(str(msg).strip(), "PRIVMSG Chanserv :op #test test")
schedule.rescheduleEvent('Bantracker_getop_#test', 1)
time.sleep(2)
drivers.run()
msg = self.irc.takeMsg() # fail msg
self.assertEqual(str(msg).strip(),
"NOTICE #test :Failed to get op in #test")
self.op()
msg = self.irc.takeMsg() # unban msg
self.assertEqual(str(msg).strip(), "MODE #test -b :asd!*@*")
finally:
pluginConf.autoremove.notify.channels.set('')
def testQuietList(self):
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 005 test CHANMODES=eIbq,k,flj,CFLMPQcgimnprstz :are supported'))
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 728 test #channel q troll!*@* op!user@home.com 123456789'))
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 729 test #channel q :End of Channel Quiet List'))
L = self.getCallback().bans.get('#channel')
self.assertTrue(L != None)
self.assertEqual('%troll!*@*', L[0].mask)
def testQuietListNotSupported(self):
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 005 test CHANMODES=eIb,k,flj,CFLMPQcgimnprstz :are supported'))
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 728 test #channel q troll!*@* op!user@home.com 123456789'))
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 729 test #channel q :End of Channel Quiet List'))
L = self.getCallback().bans.get('#channel')
self.assertTrue(L == None)
def testBanList(self):
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 367 test #channel troll!*@* op!user@home.com 123456789'))
self.irc.feedMsg(ircmsgs.IrcMsg(
':server.net 368 test #channel :End of Channel Ban List'))
obj = self.getCallback().bans['#channel'][0]
self.assertEqual('troll!*@*', obj.mask)

View File

@ -1,9 +0,0 @@
Allows any nickserv-identified user to login without password.
Grabs IRC nicks from the members in the ubuntu-irc Launchpad team, these are
stored in a binary file.
It is recommended to run @updateusers manually when the plugin is first loaded
to grab the users list from launchpad.
This plugin is designed to work with the bantracker plugin, so adds the
'bantracker' capability to all the users it finds in the team.

View File

@ -1,38 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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.
#
###
"""
Let nickserv-identified users log in without password via Launchpad
"""
import supybot
import supybot.world as world
__version__ = "0.4"
__author__ = supybot.Author("Terence Simpson","tsimpson","tsimpson@ubuntu.com")
__contributors__ = {
}
__url__ = 'https://launchpad.net/ubuntu-bots/'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

View File

@ -1,46 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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, something, yn
def anything(prompt, default=None):
"""Because supybot is pure fail"""
from supybot.questions import expect
return expect(prompt, [], default=default)
IRCLogin = conf.registerPlugin('IRCLogin', True)
if advanced:
## NOTE: This is currently disabled until rewritten to use launchpadlib
#UserList = anything("What file should be used to contains the list of users?", default=conf.supybot.directories.data.dirize("users.db"))
#teamname = something("What is the Launchpad team name to get the list of users from?", default=IRCLogin.teamname._default)
UserList = IRCLogin.UserList._default
teamname = IRCLogin.teamname._default
else:
UserList = IRCLogin.UserList._default
teamname = IRCLogin.teamname._default
IRCLogin.UserList.setValue(UserList)
IRCLogin.teamname.setValue(teamname)
IRCLogin = conf.registerPlugin('IRCLogin')
conf.registerGlobalValue(IRCLogin, 'UserList',
registry.String('', """Filename of file with list of users""",private=True))
conf.registerGlobalValue(IRCLogin, "teamname",
registry.String('ubuntu-irc', "Name of the Launchpad team to get users from", private=True))

View File

@ -1,229 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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.ircmsgs as ircmsgs
import supybot.callbacks as callbacks
import supybot.ircdb as ircdb
import supybot.conf as conf
import random, os, sys
def checkCapab(msg, irc):
try:
user = ircdb.users.getUser(msg.prefix[:msg.prefix.find('!')])
except:
irc.error(conf.supybot.replies.incorrectAuthentication())
return False
try:
if not user.capabilities.check('Admin'):
irc.error(conf.supybot.replies.noCapability() % 'Admin')
return False
except KeyError:
irc.error(conf.supybot.replies.noCapability() % 'Admin')
return False
return True
class IRCLogin(callbacks.Plugin):
"""Use @login to login, @reloadusers to reload the user list and @updateusers to update the user database from
launchpad"""
threaded = True
def __init__(self, irc):
super(IRCLogin, self).__init__(irc)
self._irc = irc
if hasattr(irc, 'getRealIrc'):
self._irc = irc.getRealIrc()
def die(self):
"""Disable identify-msg, if possible"""
if getattr(self._irc, '_Freenode_capabed', False):
# Only the CAP command can disable identify-msg not CAPAB
self._irc.queueMsg(ircmsgs.IrcMsg('CAP REQ -IDENTIFY-MSG')) # Disable identify-msg
self._irc._Freenode_capabed = self._irc._Freenode_capabed_notices = False
def login(self, irc, msg, args):
"""takes no arguments
Allows users who are identified to NickServ to login without a password.
"""
if not msg.tagged('identified'):
irc.error('You are not identified')
return
nick = msg.nick.lower()
user = None
try:
user = ircdb.users.getUser(msg.prefix)
except:
try:
user = ircdb.users.getUser(msg.prefix)
except:
for (id, obj) in ircdb.users.users.iteritems():
if obj.name.lower() == nick:
user = obj
if not user:
irc.error(conf.supybot.replies.incorrectAuthentication())
return
try:
user.addAuth(msg.prefix)
except:
pass
try:
ircdb.users.setUser(user, flush=False)
except:
pass
irc.replySuccess()
login = wrap(login)
@wrap
def identifymsg(self, irc, msg, args):
"""
takes no arguments.
Sends a requet for the identify-msg capability.
"""
self.do376(irc, msg, force=True)
irc.replySuccess()
@wrap
def haveidentifymsg(self, irc, msg, args):
""""
takes no arguments.
Displays if identify-msg is enabled or disabled.
"""
haveCap = getattr(self._irc, "_Freenode_capabed", False)
irc.reply("identify-msg is %sabled" % (haveCap and "En" or "Dis"))
def doPrivmsg(self, irc, msg):
if not conf.supybot.defaultIgnore(): # Only do this when defaultIgnore is set
return
if chr(1) in msg.args[1]:
return
try:
user = ircdb.users.getUser(msg.prefix)
if user.checkHostmask(msg.prefix):
return
except:
pass
text = callbacks.addressed(irc.nick, msg)
cmd = ''
if not text or text != "login":
if msg.args[1]:
if ircutils.isChannel(msg.args[0]):
if msg.args[1][0] == '@':
cmd = msg.args[1][1:]
else:
if msg.args[1][0] == '@':
cmd = msg.args[1][1:]
else:
cmd = msg.args[1]
if cmd != "login":
return
else:
return
self.log.info("IRCLogin: Calling login for %s" % msg.prefix)
self._callCommand(["login"], irc, msg, [])
def do290(self, irc, msg):
"""hyperiron CAPAB reply"""
self._irc._Freenode_capabed_notices = False
if msg.args[1].lower() == "identify-msg":
self._irc._Freenode_capabed = True
else:
self._irc._Freenode_capabed = False
def doCap(self, irc, msg):
"""ircd-seven CAP reply"""
cmd = msg.args[1].lower()
args = msg.args[2].lower()
if cmd == "ls": # Got capability listing
if "identify-msg" in args: # identify-msg is a capability on this server
irc.queueMsg(ircmsgs.IrcMsg('CAP REQ IDENTIFY-MSG')) # Request identify-msg
if cmd == "ack": # Acknowledge reply
if "identify-msg" in args: # identify-msg is set
self._irc._Freenode_capabed = True
self._irc._Freenode_capabed_notices = True
if cmd == 'nak': # Failure reply
if "identify-msg" in args: # identify-msg is not set
self._irc._Freenode_capabed = False
self._irc._Freenode_capabed_notices = False
def do421(self, irc, msg):
"""Invalid command"""
if msg.args[1].lower() == "cap":
irc.queueMsg(ircmsgs.IrcMsg("CAPAB IDENTIFY MSG"))
def do376(self, irc, msg, force=False): # End of /MOTD command.
"""
The new freenode ircd-seven requires using the 'CAP' command
to set capabilities, rather than hyperirons 'CAPAB' command.
You request "CAP REQ IDENTIFY-MSG" and the server will respond
with either "CAP <nick> ACK :identify-msg" to acknowledge, or
"CAP <nick> NAK :identify-msg" to indicate failure.
Other than that, it's the same.
"""
if not hasattr(self._irc, "_Freenode_capabed") or force: # Do this only once
self._irc._Freenode_capabed = False
self._irc._Freenode_capabed_notices = False
# Try the CAP command first
irc.queueMsg(ircmsgs.IrcMsg("CAP LS"))
do422 = do376
def inFilter(self, irc, msg):
"""
Strip the leading '+' or '-' from each message
"""
if msg.command not in ("PRIVMSG", "NOTICE"):
return msg
if not getattr(irc, '_Freenode_capabed', False):
return msg
if msg.command == "NOTICE" and not getattr(irc, '_Freenode_capabed_notices', False):
return msg
if msg.tagged('identified') == None:
first = msg.args[1][0]
rest = msg.args[1][1:]
msg.tag('identified', first == '+')
if first in ('+', '-'):
if msg.command == "NOTICE":
msg = ircmsgs.notice(msg.args[0], rest, msg=msg)
else:
msg = ircmsgs.privmsg(msg.args[0], rest, msg=msg)
if not ircutils.isChannel(msg.args[0]): # /msg
##TODO: check here that the user isn't already logged in
cmd = msg.args[1]
if cmd and cmd[0] in str(conf.supybot.reply.whenAddressedBy.chars()):
cmd = cmd[1:]
if cmd.lower() == 'login':
self.doPrivmsg(callbacks.ReplyIrcProxy(irc, msg), msg) # If the login command is given in /msg, force it through
return # Don't return the msg otherwise it'll be processed twice
else:
self.do376(irc, msg, True)
assert msg.receivedAt and msg.receivedOn and msg.receivedBy
if len(msg.args) >= 2 and msg.args[1] and msg.args[1][0] in ('+', '-'):
self.do376(irc, msg, True)
return msg
Class = IRCLogin

View File

@ -1,19 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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.
#
###
from supybot.test import *
class IRCLoginTestCase(PluginTestCase):
plugins = ('IRCLogin',)

View File

@ -1,58 +0,0 @@
000057
000001:1150873085.902302,1,'strangles $who with a doohicky mouse cord'
000002:1150873161.7318439,1,"""pours hot grits down the front of $who's pants"""
000003:1150873175.1154349,1,'beats $who senseless with a 50lb Unix manual'
000004:1150873544.0124331,1,'whacks $who with the cluebat'
000005:1150873549.5681801,1,"""resizes $who's terminal to 40x24"""
000006:1150873567.3416319,1,'drops a truckload of VAXen on $who'
000007:1150873584.5915289,1,"""pulls out his louisville slugger and uses $who's head to break the homerun record"""
000008:1150873593.953506,1,'stabs $who'
000009:1150873602.0038359,1,"""steals $who's mojo"""
000010:1150873625.408884,1,'holds $who to the floor and spanks him with a cat-o-nine-tails'
000011:1150873635.824439,1,"""installs WindowsME on $who's computer"""
000012:1150873676.669996,1,'makes Jack Bauer chase $who'
000013:1150873684.938952,1,'pushes the wall down onto $who whilst whistling innocently'
000014:1150873708.7875321,1,'--purges $who'
000015:1150873726.932162,1,'decapitates $who conan the destroyer style'
000016:1150873745.169296,1,"""cats /dev/urandom into $who's ear"""
000017:1150873762.182699,1,"""does a little 'renice 20 -u $who'"""
000018:1150880977.1693139,1,"""tackles $who, sits on $who and starts scratching at $who's chest"""
000019:1151419669.2793391,1,'slaps $who with a soggy sock'
000020:1151962663.4319079,1,'drops $who from a helicopter 5 miles in the sky. Without parachute'
000021:1152091319.803565,1,'throws $who into /dev/null'
000022:1152805300.7266941,1,'chases $who with a big pointy stick'
000023:1152920154.2286861,1,'smacks $who with a big clue-by-four'
000024:1153087484.0331881,1,'bites $who'
000025:1153135286.679605,1,'sends FesterAnvil hurtling through the sky to land on $who'
000026:1153600051.46187,1,'shoots $who in the face with a rocket'
000027:1153769010.2483661,1,'@$chan:~$ deluser $who'
000028:1154013487.0735919,1,'thwacks $who with a BIG POINTY HOBBSЕЕ OF DOOM'
000029:1154449886.891526,1,"""tickles $who's feet with a feather"""
000030:1154456213.370611,1,'splats $who with a large hammer'
000031:1155921496.616538,1,'divides $who by zero'
000032:1166737658.117898,1,"""sets $who's keyboard layout to gaelic"""
000033:1169714572.779995,7,"""breaks $who's machine by running automatix on it. Twice."""
000034:1169826693.3914869,7,'gets the neuraliser out and points it at $who'
000035:1170410240.286854,1,'smacks $who with a vista DVD. COOTIES!'
000036:1170410622.2356141,1,'spanks $who with a pink tutu'
000037:1170410687.610502,1,'shows $who a photo of mneptok: http://tinyurl.com/yv5q8h'
000037:1173736572.6347821,1,'forces $who to use perl for 3 weeks'
000038:1173736775.8736949,1,'forces $who to use perl for 3 weeks'
000039:1173736803.2703841,1,'pokes $who in the eye'
000040:1173736823.520009,1,'signs $who up for AOL'
000041:1173736843.5446689,1,'enrolls $who in Visual Basic 101'
000042:1173736857.85535,1,'judo chops $who'
000043:1173736906.7716081,1,'sells $who on E-Bay'
000044:1173736913.094003,1,'forces $who to use emacs for 3 weeks'
000045:1173736933.924052,1,"""puts alias vim=emacs in $who's /etc/profile"""
000046:1173736963.118736,1,'reads $who some vogon poetry'
000047:1173736973.826055,1,'puts $who in the Total Perspective Vortex'
000048:1173736987.6467011,1,'uses $who as a biological warfare study'
000049:1173737029.025836,1,"""pierces $who's nose with a rusty paper hole puncher"""
000050:1173737055.204941,1,'pokes $who with a rusty nail'
000051:1173889239.086617,1,'files $who under the L for lame'
000052:1176764275.6553509,1,'forces $who to talk in reverse polish notation for the rest of the year'
000053:1181421701.938509,1,"""slurps up all of $who's memory by installing vista"""
000054:1185565575.1513309,1,"""replaces Ubuntu with Windows Vista on $who's PC"""
000055:1186343260.1562171,1,"""annihilates $who's hearing by screaming louder than an arnieboy who got pwned by mjg59"""
000056:1200150976.9736941,1,'forces $who to write an operating system in Logo'

View File

@ -1,62 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005, Daniel DiPaolo
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
This plugin keeps a database of larts, and larts with it.
"""
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__ = "%%VERSION%%"
# XXX Replace this with an appropriate author or supybot.Author instance.
__author__ = supybot.authors.strike
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
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:

View File

@ -1,65 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005, Daniel DiPaolo
# 2006, Dennis Kaarsemaker
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import 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, something, yn
def anything(prompt, default=None):
"""Because supybot is pure fail"""
from supybot.questions import expect
return expect(prompt, [], default=default)
Lart = conf.registerPlugin('Lart', True)
enabled = yn("Enable Lart for all channels?", default=Lart.enabled._default)
if advanced:
showIds = yn("Show the ID of a lart when it is shown?", default=Lart.showIds._default)
else:
showIds = Lart.showIds._default
Lart.enabled.setValue(enabled)
Lart.showIds.setValue(showIds)
Lart = conf.registerPlugin('Lart')
# This is where your configuration variables (if any) should go. For example:
conf.registerChannelValue(Lart, 'enabled',
registry.Boolean(False, "Whether or not to enable the LART for the channel"))
conf.registerChannelValue(Lart, 'showIds',
registry.Boolean(False, "Determines whether the bot will show the ids of a lart when the lart is given."))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -1,168 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005, Daniel DiPaolo
# (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import re
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.ircdb as ircdb
import supybot.conf as conf
import random
def checkIgnored(hostmask, recipient='', users=ircdb.users, channels=ircdb.channels):
if ircdb.ignores.checkIgnored(hostmask):
return True
try:
id = ircdb.users.getUserId(hostmask)
user = users.getUser(id)
except KeyError:
# If there's no user...
if ircutils.isChannel(recipient):
channel = channels.getChannel(recipient)
if channel.checkIgnored(hostmask):
return True
else:
return False
else:
return False
if user._checkCapability('owner'):
# Owners shouldn't ever be ignored.
return False
elif user.ignore:
return True
elif recipient:
if ircutils.isChannel(recipient):
channel = ircdb.channels.getChannel(recipient)
if channel.checkIgnored(hostmask):
return True
else:
return False
else:
return False
else:
return False
class Lart(plugins.ChannelIdDatabasePlugin):
_meRe = re.compile(r'\bme\b', re.I)
_myRe = re.compile(r'\bmy\b', re.I)
def _replaceFirstPerson(self, s, nick):
s = self._meRe.sub(nick, s)
s = self._myRe.sub('%s\'s' % nick, s)
return s
def addValidator(self, irc, text):
if '$who' not in text:
irc.error('Larts must contain $who.', Raise=True)
def lart(self, irc, msg, args, channel, id, text):
"""[<channel>] [<id>] <who|what> [for <reason>]
Uses the Luser Attitude Readjustment Tool on <who|what> (for <reason>,
if given). If <id> is given, uses that specific lart. <channel> is
only necessary if the message isn't sent in the channel itself.
"""
if not self.registryValue('enabled', msg.args[0]):
return
if ' for ' in text:
(target, reason) = map(str.strip, text.split(' for ', 1))
else:
(target, reason) = (text, '')
if id is not None:
try:
lart = self.db.get(channel, id)
except KeyError:
irc.error(format('There is no lart with id #%i.', id))
return
else:
lart = self.db.random(channel)
if not lart:
irc.error(format('There are no larts in my database '
'for %s.', channel))
return
text = self._replaceFirstPerson(lart.text, msg.nick)
formatText = ircutils.stripFormatting(target).lower()
if (ircutils.strEqual(target, irc.nick) or 'Evilrockbot' in formatText) and random.uniform(0,100) < 25:
target = msg.nick
reason = ''
elif 'stdin' in formatText or 'tsimpson' in formatText:
target = msg.nick
reason = ''
else:
target = self._replaceFirstPerson(target, msg.nick)
reason = self._replaceFirstPerson(reason, msg.nick)
if target.endswith('.'):
target = target.rstrip('.')
text = text.replace('$who', target)
text = text.replace('$chan', msg.args[0])
if reason:
text += ' for ' + reason
if self.registryValue('showIds', channel):
text += format(' (#%i)', lart.id)
irc.reply(text, action=True)
lart = wrap(lart, ['channeldb', optional('id'), 'text'])
pity = lart
def callPrecedence(self, irc):
before = []
for cb in irc.callbacks:
if cb.name() == 'IRCLogin':
before.append(cb)
return (before, [])
def inFilter(self, irc, msg):
if not msg.command == 'PRIVMSG':
return msg
if not conf.supybot.defaultIgnore():
return msg
s = callbacks.addressed(irc.nick, msg)
if not s:
return msg
if checkIgnored(msg.prefix):
return msg
try:
if ircdb.users.getUser(msg.prefix):
return msg
except:
pass
cmd, args = (s.split(None, 1) + [None])[:2]
if cmd and cmd[0] in str(conf.supybot.reply.whenAddressedBy.chars.get(msg.args[0])):
cmd = cmd[1:]
if cmd in self.listCommands():
tokens = callbacks.tokenize(s, channel=msg.args[0])
self.Proxy(irc, msg, tokens)
return msg
Class = Lart
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -1,42 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005, Daniel DiPaolo
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from supybot.test import *
class LartTestCase(PluginTestCase):
plugins = ('Lart',)
def testAdd(self):
self.assertError('lart add foo') # needs $who
def testPraise(self):
self.assertError('lart foo') # no praises!
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -1,50 +0,0 @@
I demand that I am Vroomfondel
Time is an illusion, lunchtime doubly so
Oh no, not again!
Beware of the Leopard
In, as you say, the mud
The mere though hasn't even begun to speculate about the merest possibility of crossing my mind
Did I do anything wrong today or has the world always been like this and I've been too wrapped up in myself to notice
This must be Thursday, I never could get the hang of Thursdays
Drink up, the world's about to end
That is really amazing. That really is truly amazing. That is so amazingly amazing I think I'd like to steal it
If I asked you where the hell we were, eould I regret it?
Ah, this is obviously some strange usage of the word safe that I wasn't previously aware of
Here is what to do if you want to get a lift from a Vogon: forget it
The best way to get a drink out of a Vogon is to stick your finger down his throat
Don't panic!
This is your captain speaking, so stop whatever you're doing and pay attention
If you're very lucky, I might read you some of my poetry first
What's so unpleasant about being drunk?
Oh dear, says god, I hadn't thought of that. And prompty vanishes in a puff of logic
Either die in the vacuum of space, or tell me how good you thought my poem was
Resistance is useless!
Take the prisoners to number tree airlock and throw them out
Da da da dum!
Space is big. Really big. You won't believe how vastly hugely mind-boggingly big it is
Bright idea of mine, to find a passing spaceship and get rescued by it
Don't knock it, it worked
Two to the power of one hundred thousand to one against and falling
My legs are drifting off into the sunset
The point is that I am now a perfectly safe penguin
We have normality, I repeat, we have normality. Anything you still can't cope with is therefore your own problem
Okay, so ten out of ten for style, but minus several millions for good thinking, yeah?
I think you ought to know, I'm feeling very depressed
Life, don't talk to me about life
Please do not press this button again
To everyone else out there, the secret is to bang the rocks together guys
If there's anything more important than my ego around, I want it caught and shot now
Did you realize that most people's lives are governed by telephone numbers?
Hey doll, is this guy boring you? Why don't you talk to me instead? I'm from a different planet
I'll sue the council for every penny it's got!
Please enjoy your walk through this door
Glad to be of service
In these enlightened days, of course, no one believes a word of it
I can even work out your personality problems to ten decimal places if it will help
Is there any tea on this spaceship?
Hey, this is terrific! Someone down there is trying to kill us
so, let's call it my stomach
But what are you supposed to do with a manically depressed robot?
My white mice have escaped!
I can see this relationship is something we're all going to have to work at
Meanwhile, the poor Babel fish, by effectively removing all barriers to communication between different races and cultures, has caused more and bloodier wars than anything else in the history of creation.

View File

@ -1,2 +0,0 @@
This is a random mess plugin that hardcodes many paths at the top of the plugin
code. You'll generally want to be careful with it or simply not use it.

View File

@ -1,37 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
"""
Random mess plugin
"""
import supybot
import supybot.world as world
__version__ = "0.5"
__author__ = supybot.Author('Terence Simpson', 'tsimpson', 'tsimpson@ubuntu.com')
__contributors__ = {
supybot.Author('Dennis Kaarsemaker','Seveas','dennis@kaarsemaker.net'): ['Original Author']
}
__url__ = 'https://launchpad.net/ubuntu-bots/'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

View File

@ -1,26 +0,0 @@
Yes
No
Most likely
Definitely not
Ask again later
Maybe
Could be
Only when it rains
Let's hope not
I don't think so
Are you kidding me?
Of course
When pigs fly
No way
Definitely
Of course
Hell no!
Hell yeah!
Keep on dreaming
The stars say so
I have no doubts about it
Forty-Two!
Your guess is as good as mine
Maybe
That seems to be right
That seems to be wrong

View File

@ -1,453 +0,0 @@
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
tectonic 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, it's 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 favorite 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.
Someone 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

View File

@ -1,56 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, something, yn, output
def anything(prompt, default=None):
"""Because supybot is pure fail"""
from supybot.questions import expect
return expect(prompt, [], default=default)
Mess = conf.registerPlugin('Mess', True)
def getDelay():
output("What should be the minimum number of seconds between mess output?")
delay = something("Enter an integer greater or equal to 0", default=Mess.delay._default)
try:
delay = int(delay)
if delay < 0:
raise TypeError
except TypeError:
output("%r is not a valid value, it must be an interger greater or equal to 0" % delay)
return getDelay()
else:
return delay
output("WARNING: This plugin is unmaintained, may have bugs and is potentially offensive to users")
Mess.enabled.setValue(yn("Enable this plugin for all channels?", default=Mess.enabled._default))
Mess.offensive.setValue(yn("Enable possibly offensive content?", default=Mess.offensive._default))
Mess.delay.setValue(getDelay())
Mess = conf.registerPlugin('Mess')
conf.registerChannelValue(conf.supybot.plugins.Mess, 'enabled',
registry.Boolean(False,"""Enable the non-offensive mess that the bot can spit out in the
channel"""))
conf.registerChannelValue(conf.supybot.plugins.Mess, 'offensive',
registry.Boolean(False,"""Enable all possibly offensive mess that the bot can spit out in the
channel"""))
conf.registerChannelValue(conf.supybot.plugins.Mess, 'delay',
registry.Integer(10,""" Minimum number of seconds between mess """))

View File

@ -1,50 +0,0 @@
1: Once you have their money, you never give it back.
3: Never spend more for an acquisition than you have to.
6: Never let family stand in the way of opportunity.
7: Always keep your ears open.
9: Opportunity plus instinct equals profit.
10: Greed is eternal.
16: A deal is a deal.
17: A contract is a contract is a contract but only between Ferengi.
18: A Ferengi without profit is no Ferengi at all.
21: Never place friendship before profit.
22: Wise men can hear profit in the wind.
23: Nothing is more important than your health. Except for money.
31: Never make fun of a Ferengi's mother.
33: It never hurts to suck up to the boss.
34: War is good for business.
35: Peace is good for business.
47: Don't trust a man wearing a better suit than your own.
48: The bigger the smile, the sharper the knife.
57: Good customers are as rare as latinum — treasure them.
59: Free advice is seldom cheap.
62: The riskier the road, the greater the profit.
74: Knowledge equals profit.
75: Home is where the heart is but the stars are made of latinum.
76: Every once in a while, declare peace. It confuses the hell out of your enemies.
94: Females and finances don't mix.
95: Expand or die.
98: Every man has his price.
102: Nature decays, but latinum lasts forever.
103: Sleep can interfere with opportunity.
109: Dignity and an empty sack is worth the sack.
111: Treat people in your debt like family - exploit them.
112: Never have sex with the boss' sister.
119: Buy, sell, or get out of the way.*
125: You can't make a deal if you're dead.
139: Wives serve; brothers inherit.
141: Only fools pay retail.
168: Whisper your way to success.
190: Hear all, trust nothing.
194: It's always good business to know about new customers before they walk in your door.
203: New customers are like razor-toothed gree worms: they can be succulent but sometimes they can bite back.
208: Sometimes the only thing more dangerous than a question is an answer.
211: Employees are the rungs on the ladder of success - don't hesitate to step on them.
214: Never begin a business negotiation on an empty stomach.
217: You can't free a fish from water.
229: Latinum lasts longer than lust.
239: Never be afraid to mis-label a product.
242: More is good, all is better.
263: Never allow doubt to tarnish your lust for Latinum.
285: No good deed ever goes unpunished.
: When no rule applies . . . make one up

View File

@ -1,273 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import 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
import supybot.conf as conf
import threading
import os
mess = {
't': ('Mr. T facts', 'http://4q.cc/?pid=fact&person=mrt', r'<div id="factbox">\s*(?P<fact>.*?)\s*</div>', False),
'chuck': ('Chuck Norris facts', 'http://4q.cc/?pid=fact&person=chuck', r'<div id="factbox">\s*(?P<fact>.*?)\s*</div>', False),
'vin': ('Vin Diesel facts', 'http://4q.cc/?pid=fact&person=vin', r'<div id="factbox">\s*(?P<fact>.*?)\s*</div>', False),
'bauer': ('Jack Bauer facts', 'http://www.notrly.com/jackbauer/', r'<p class="fact">(?P<fact>.*?)</p>', False),
'bruce': ('Bruce Schneier facts', 'http://geekz.co.uk/schneierfacts/', r'p class="fact">(?P<fact>.*?)</p', False),
'esr': ('Eric S. Raymond facts', 'http://geekz.co.uk/esrfacts/', r'p class="fact">(?P<fact>.*?)</p', False),
'mcgyver': ('McGyver facts', 'http://www.macgyver.co.za/', r'wishtable">\s*(?P<fact>.*?)<div', False),
'macgyver': ('McGyver facts', 'http://www.macgyver.co.za/', r'wishtable">\s*(?P<fact>.*?)<div', False),
'hamster': ('Hamster quotes', 'http://hamsterrepublic.com/dyn/bobsez', r'<font.*?<b>(?P<fact>.*?)</font>', False),
#'yourmom': ('', 'http://pfa.php1h.com', r'<p>(?P<fact>.*?)</p>', True),
'bush': ('Bush quotes', 'http://www.dubyaspeak.com/random.phtml', r'(?P<fact><font.*</font>)', True),
#southpark': ('', 'http://www.southparkquotes.com/random.php?num=1', r'<p>(?P<fact>.*)</p>', True),
'mjg': ('Matthew Garrett facts', 'http://www.angryfacts.com', r'</p><h1>(?P<fact>.*?)</h1>', False),
'mjg59': ('Matthew Garrett facts', 'http://www.angryfacts.com', r'</p><h1>(?P<fact>.*?)</h1>', False),
'vmjg': ('Virtual Matthew Garrett', 'http://www.rjek.com/vmjg59.cgi', r'<body>(?P<fact>.*?)<p>', True),
'vmjg59': ('Virtual Matthew Garrett', 'http://www.rjek.com/vmjg59.cgi', r'<body>(?P<fact>.*?)<p>', True),
'shakespeare': ('Shakespeare quotes', 'http://www.pangloss.com/seidel/Shaker/', r'<font.*?>(?P<fact>.*?)</font>', False),
'lugradio': ('Lugradio facts', 'http://planet.lugradio.org/facts/', r'<h2>\s*(?P<fact>.*?)</h2>', False),
'bofh': ('BOFH excuses', '%s/bofh.txt' % os.path.split(os.path.abspath(__file__))[0], 'BOFH Excuse #%d: ', False),
'42': ('HHGTTG quotes', '%s/42.txt' % os.path.split(os.path.abspath(__file__))[0], '', False),
'magic8ball': ('The magic 8ball', '%s/ball.txt' % os.path.split(os.path.abspath(__file__))[0], '', False),
'ferengi': ('Ferengi rules of acquisition', '%s/ferengi.txt' % os.path.split(os.path.abspath(__file__))[0], 'Ferengi rule of acquisition ', False),
}
data = {}
for m in mess.keys():
if mess[m][1].startswith('http'):
mess[m] = (mess[m][0], mess[m][1],re.compile(mess[m][2], re.I|re.DOTALL), mess[m][3])
else:
fd = open(mess[m][1])
data[mess[m][1]] = [x.strip() for x in fd.readlines()]
fd.close()
badwords = ['sex','masturbate','fuck','rape','dick','pussy','prostitute','hooker',
'orgasm','sperm','cunt','penis','shit','piss','urin','bitch','semen','cock',
'retard', 'cancer', 'hiv', 'aids']
tagre = re.compile(r'<.*?>')
def filter(txt,off):
_txt = txt.lower()
if not off:
for b in badwords:
if b in _txt:
return None
txt = txt.replace('<br />','').replace('\n','').replace('\r','')
txt = txt.replace('<i>','/').replace('</i>','/').replace('<b>','*').replace('</b>','*')
txt = txt.replace('&quot;','"').replace('&lt;','<').replace('&gt;','>')
txt = tagre.sub('',txt)
return txt
times = {}
def ok(func):
def newfunc(*args, **kwargs):
global time
plugin = args[0]
channel = args[2].args[0]
if not channel.startswith('#'):
delay = 5
else:
if not plugin.registryValue('enabled', channel):
return
delay = plugin.registryValue('delay', channel)
if channel not in times.keys():
times[channel] = time.time()
elif times[channel] < time.time() - delay:
times[channel] = time.time()
else:
return
i=0
func(*args, **kwargs)
newfunc.__doc__ = func.__doc__
return newfunc
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"]
def isCommandMethod(self, name):
if not callbacks.PluginRegexp.isCommandMethod(self, name):
if name in mess:
return True
else:
return False
else:
return True
def listCommands(self):
commands = callbacks.PluginRegexp.listCommands(self)
commands.remove('messcb')
commands.extend(mess.keys())
commands.sort()
return commands
def getCommandMethod(self, command):
try:
return callbacks.PluginRegexp.getCommandMethod(self, command)
except AttributeError:
return self.messcb
def getCommandHelp(self, command):
try:
return callbacks.PluginRegexp.getCommandMethod(self, command)
except AttributeError:
return mess[command[0]][0]
def _callCommand(self, command, irc, msg, *args, **kwargs):
method = self.getCommandMethod(command)
if command[0] in mess:
msg.tag('messcmd', command[0])
try:
method(irc, msg, *args, **kwargs)
except Exception, e:
self.log.exception('Mess: Uncaught exception in %s.', command)
if conf.supybot.reply.error.detailed():
irc.error(utils.exnToString(e))
else:
irc.replyError()
@ok
def messcb(self, irc, msg, args, text):
"""General mess"""
t = threading.Thread(target=self.messthread, args=(irc, msg, args, text))
t.start()
def messthread(self, irc, msg, args, text):
global data
cmd = msg.tagged('messcmd')
try:
(doc, loc, tx, off) = mess[cmd]
except:
cmd = cmd[1:]
(doc, loc, tx, off) = mess[cmd]
if off and not self.registryValue('offensive', msg.args[0]):
return
if loc.startswith('http'):
i = 0
while i < 5:
inp = utils.web.getUrl(loc)
fact = tx.search(inp).group('fact')
fact = filter(fact,off)
if fact:
irc.reply(fact)
return
i += 1
else:
i = random.randint(0,len(data[loc])-1)
if '%d' in tx:
tx = tx % i
irc.reply(tx + data[loc][i])
messcb = wrap(messcb, [additional('text')])
# WARNING: depends on an alteration in supybot/callbacks.py - don't do
# str(s) if s is unicode!
@ok
def dice(self, irc, msg, args, count):
"""[<count>]
Roll the dice, if count is given then roll that many times.
"""
if not count: count = 1
elif count > 5: count = 5
elif count < 1: count = 1
t = u' '.join([x.__call__([u"\u2680",u"\u2681",u"\u2682",u"\u2683",u"\u2684",u"\u2685"]) for x in [random.choice]*count])
irc.reply(t)
dice = wrap(dice, [additional('int')])
@ok
def hugme(self, irc, msg, match):
r""".*hug.*ubotu"""
irc.queueMsg(ircmsgs.action(msg.args[0], self.hugs[random.randint(0,len(self.hugs)-1)] % msg.nick))
@ok
def fortune(self, irc, msg, args):
""" Display a fortune cookie """
f = commands.getoutput('/usr/games/fortune -s')
f.replace('\t',' ')
f = f.split('\n')
for l in f:
if l:
irc.reply(l)
fortune = wrap(fortune)
@ok
def ofortune(self, irc, msg, args):
""" Display a possibly offensive fortune cookie """
if not self.registryValue('offensive', msg.args[0]):
return
f = commands.getoutput('/usr/games/fortune -so')
f.replace('\t',' ')
f = f.split('\n')
for l in f:
if l:
irc.reply(l)
ofortune = wrap(ofortune)
@ok
def futurama(self, irc, msg, args):
""" Display a futurama quote """
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].replace(' ',' "',1) + '"')
futurama = wrap(futurama)
@ok
def pony(self, irc, msg, args, text):
"""[<user>]
Can you or <user> have a pony?
"""
if not text:
text = 'you'
irc.reply("No %s can't have a pony, %s!" % (text, msg.nick))
pony = wrap(pony, [additional('text')])
def callPrecedence(self, irc):
before = []
for cb in irc.callbacks:
if cb.name() == 'IRCLogin':
before.append(cb)
return (before, [])
def inFilter(self, irc, msg):
if not msg.command == 'PRIVMSG':
return msg
if not conf.supybot.defaultIgnore():
return msg
s = callbacks.addressed(irc.nick, msg)
if not s:
return msg
if checkIgnored(msg.prefix):
return msg
try:
if ircdb.users.getUser(msg.prefix):
return msg
except:
pass
cmd, args = (s.split(None, 1) + [None])[:2]
if cmd and cmd[0] in str(conf.supybot.reply.whenAddressedBy.chars.get(msg.args[0])):
cmd = cmd[1:]
if cmd in self.listCommands():
tokens = callbacks.tokenize(s, channel=msg.args[0])
self.Proxy(irc, msg, tokens)
return msg
# self._callCommand([cmd], irc, msg, [])
Class = Mess

View File

@ -1,19 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
from supybot.test import *
class MessTestCase(PluginTestCase):
plugins = ('Mess',)

View File

@ -1,5 +0,0 @@
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

View File

@ -1,40 +0,0 @@
# -*+ Encoding: utf-8 -*-
###
# Copyright (c) 2005-2007 Dennis Kaarsemaker
# Copyright (c) 2008-2010 Terence Simpson
#
# 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.3"
__author__ = supybot.Author('Terence Simpson', 'tsimpson', 'tsimpson@ubuntu.com')
__contributors__ = {
supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net"): ['Original Author']
}
__url__ = 'https://launchpad.net/ubuntu-bots/'
import config
reload(config)
import plugin
reload(plugin)
import ical
reload(ical)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

View File

@ -1,182 +0,0 @@
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-WR-CALNAME:The Fridge | October 17\, 2008 - December 16\, 2008
PRODID:-//strange bird labs//Drupal iCal API//EN
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081017T190000Z
DTEND;VALUE=DATE-TIME:20081017T210000Z
UID:http://fridge.ubuntu.com/node/1656
URL;VALUE=URI:http://fridge.ubuntu.com/node/1656
SUMMARY:Tunisian LoCo Team IRC Meeting
DESCRIPTION:<p>Location\: #ubuntu-tn<br />
Agenda\: Team participation to SFD Tunisia 2008.</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081018T130000Z
DTEND;VALUE=DATE-TIME:20081018T150000Z
UID:http://fridge.ubuntu.com/node/1571
URL;VALUE=URI:http://fridge.ubuntu.com/node/1571
SUMMARY:Xubuntu Community Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting<br />
Agenda\: https\://wiki.ubuntu.com/Xubuntu/Meetings</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081021T110000Z
DTEND;VALUE=DATE-TIME:20081021T130000Z
UID:http://fridge.ubuntu.com/node/1558
URL;VALUE=URI:http://fridge.ubuntu.com/node/1558
SUMMARY:Community Council Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting<br />
Agenda\: <a href=\\"https\://wiki.ubuntu.com/CommunityCouncilAgenda\\">https\://wiki.ubuntu.com/CommunityCouncilAgenda</a></p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081021T110000Z
DTEND;VALUE=DATE-TIME:20081021T120000Z
UID:http://fridge.ubuntu.com/node/1678
URL;VALUE=URI:http://fridge.ubuntu.com/node/1678
SUMMARY:Asia Oceania Membership Board Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081021T140000Z
DTEND;VALUE=DATE-TIME:20081021T160000Z
UID:http://fridge.ubuntu.com/node/1662
URL;VALUE=URI:http://fridge.ubuntu.com/node/1662
SUMMARY:Technical Board Meeting
DESCRIPTION:<ul>
<li><strong>Agenda\:</strong> <a href=\\"https\://wiki.ubuntu.com/TechnicalBoardAgenda\\">https\://wiki.ubuntu.com/TechnicalBoardAgenda</a></li>
<li><strong>Location\:</strong> #ubuntu-meeting</li>
</ul>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081021T150000Z
DTEND;VALUE=DATE-TIME:20081021T160000Z
UID:http://fridge.ubuntu.com/node/1681
URL;VALUE=URI:http://fridge.ubuntu.com/node/1681
SUMMARY:Server Team Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting on IRC<br />
Agenda\: https\://wiki.ubuntu.com/ServerTeam/Meeting</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081021T170000Z
DTEND;VALUE=DATE-TIME:20081021T180000Z
UID:http://fridge.ubuntu.com/node/1683
URL;VALUE=URI:http://fridge.ubuntu.com/node/1683
SUMMARY:Kernel Team Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting in IRC<br />
Agenda\: Not listed as of publication</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081022T230000Z
DTEND;VALUE=DATE-TIME:20081023T000000Z
UID:http://fridge.ubuntu.com/node/1667
URL;VALUE=URI:http://fridge.ubuntu.com/node/1667
SUMMARY:Forum Council Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081028T160000Z
DTEND;VALUE=DATE-TIME:20081028T170000Z
UID:http://fridge.ubuntu.com/node/1682
URL;VALUE=URI:http://fridge.ubuntu.com/node/1682
SUMMARY:Server Team Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting on IRC<br />
Agenda\: https\://wiki.ubuntu.com/ServerTeam/Meeting</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081028T170000Z
DTEND;VALUE=DATE-TIME:20081028T180000Z
UID:http://fridge.ubuntu.com/node/1684
URL;VALUE=URI:http://fridge.ubuntu.com/node/1684
SUMMARY:Kernel Team Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting in IRC<br />
Agenda\: Not listed as of publication</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081104T140000Z
DTEND;VALUE=DATE-TIME:20081104T140000Z
UID:http://fridge.ubuntu.com/node/1663
URL;VALUE=URI:http://fridge.ubuntu.com/node/1663
SUMMARY:Technical Board Meeting
DESCRIPTION:<ul>
<li><strong>Agenda\:</strong> <a href=\\"https\://wiki.ubuntu.com/TechnicalBoardAgenda\\">https\://wiki.ubuntu.com/TechnicalBoardAgenda</a></li>
<li><strong>Location\:</strong> #ubuntu-meeting</li>
</ul>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081104T210000Z
DTEND;VALUE=DATE-TIME:20081104T230000Z
UID:http://fridge.ubuntu.com/node/1553
URL;VALUE=URI:http://fridge.ubuntu.com/node/1553
SUMMARY:Community Council Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting<br />
Agenda\: <a href=\\"https\://wiki.ubuntu.com/CommunityCouncilAgenda\\">https\://wiki.ubuntu.com/CommunityCouncilAgenda</a></p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081106T000000Z
DTEND;VALUE=DATE-TIME:20081106T010000Z
UID:http://fridge.ubuntu.com/node/1547
URL;VALUE=URI:http://fridge.ubuntu.com/node/1547
SUMMARY:Maryland LoCo IRC Meeting
DESCRIPTION:<p>Location\: #ubuntu-us-md</p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081118T110000Z
DTEND;VALUE=DATE-TIME:20081118T130000Z
UID:http://fridge.ubuntu.com/node/1559
URL;VALUE=URI:http://fridge.ubuntu.com/node/1559
SUMMARY:Community Council Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting<br />
Agenda\: <a href=\\"https\://wiki.ubuntu.com/CommunityCouncilAgenda\\">https\://wiki.ubuntu.com/CommunityCouncilAgenda</a></p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081202T210000Z
DTEND;VALUE=DATE-TIME:20081202T230000Z
UID:http://fridge.ubuntu.com/node/1554
URL;VALUE=URI:http://fridge.ubuntu.com/node/1554
SUMMARY:Community Council Meeting
DESCRIPTION:<p>Location\: #ubuntu-meeting<br />
Agenda\: <a href=\\"https\://wiki.ubuntu.com/CommunityCouncilAgenda\\">https\://wiki.ubuntu.com/CommunityCouncilAgenda</a></p>
END:VEVENT
BEGIN:VEVENT
DTSTAMP;VALUE=DATE:20081017T202549Z
DTSTART;VALUE=DATE-TIME:20081204T000000Z
DTEND;VALUE=DATE-TIME:20081204T010000Z
UID:http://fridge.ubuntu.com/node/1548
URL;VALUE=URI:http://fridge.ubuntu.com/node/1548
SUMMARY:Maryland LoCo IRC Meeting
DESCRIPTION:<p>Location\: #ubuntu-us-md</p>
END:VEVENT
END:VCALENDAR

View File

@ -1,62 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, something, yn, output
def anything(prompt, default=None):
"""Because supybot is pure fail"""
from supybot.questions import expect
return expect(prompt, [], default=default)
Webcal = conf.registerPlugin('Webcal', True)
output("Every option, except for the default channel and URL to a list of time zones, is channel-specific.")
output("The values you enter here will be the defaults unless overridden by a channel-specific value")
doTopic = yn("Manage the topic for all channels?", default=Webcal.doTopic._default)
url = anything("What is the default URL to the iCal feed, for all channels?", default=Webcal.url._default)
defaultChannel = anything("What channel should be default when none is given?", default=Webcal.defaultChannel._default)
tzUrl = anything("What is the URL to the list of available time zonez?", default=Webcal.tzUrl._default)
if advanced:
filter = anything("What should the filter be for the iCal feed, for all channels?", default=Webcal.filter._default)
topic = anything("What template should be used for the topic, for all channels", default=Webcal.topic._default)
else:
filter = Webcal.filter._default
topic = Webcal.topic._default
Webcal.doTopic.setValue(doTopic)
Webcal.url.setValue(url)
Webcal.defaultChannel.setValue(defaultChannel)
Webcal.tzUrl.setValue(tzUrl)
Webcal.filter.setValue(filter)
Webcal.topic.setValue(topic)
Webcal = conf.registerPlugin('Webcal')
conf.registerChannelValue(conf.supybot.plugins.Webcal, 'url',
registry.String('',"""Webcal URL for the channel"""))
conf.registerChannelValue(conf.supybot.plugins.Webcal, 'filter',
registry.String('',"""What to filter on in the ical feed"""))
conf.registerChannelValue(conf.supybot.plugins.Webcal, 'topic',
registry.String('',"""Topic template"""))
conf.registerChannelValue(conf.supybot.plugins.Webcal, 'doTopic',
registry.Boolean(False,"""Whether to manage the topic"""))
conf.registerGlobalValue(conf.supybot.plugins.Webcal, 'defaultChannel',
registry.String('',"""Default channel to determine schedule for /msg replies"""))
conf.registerGlobalValue(conf.supybot.plugins.Webcal, 'tzUrl',
registry.String('http://ubottu.com/timezones.html', """URL to the list of timezones supported by the Webcal plugin"""))

View File

@ -1,296 +0,0 @@
#!/usr/bin/env python
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import sys, os
sys.path.append(os.path.dirname(__file__))
from icalendar import Calendar, cal, prop
from dateutil import tz as tzmod
from cStringIO import StringIO
import pytz
import urllib2
import datetime
import rruler
DEB_OBJ = None
SECONDS_PER_DAY=24*60*60
def seconds(timediff):
return SECONDS_PER_DAY * timediff.days + timediff.seconds
def toTz(date, tz):
assert isinstance(tz, datetime.tzinfo), "tz must be a tzinfo type"
if isinstance(date, datetime.datetime):
try:
return date.astimezone(tz)
except:
return datetime.datetime.combine(date.date(), datetime.time(date.time().hour, date.time().minute, date.time().second, tzinfo=tz))
elif isinstance(datetime.date):
return datetime.datetime.combine(date, datetime.time(0, 0, 0, tzinfo=tz))
class ICalReader:
def __init__(self, data):
self.events = []
self.timezones = {}
self.raw_data = data
self.readEvents()
def readEvents(self):
self.events = []
self.timezones = {}
parser = Calendar.from_string(self.raw_data)
tzs = parser.walk("vtimezone")
self.parseTzs(tzs)
events = parser.walk("vevent")
for event in events:
res = self.parseEvent(event)
if res:
self.events.append(res)
def parseTzs(self, tzs):
if not tzs:
return
for tz in tzs:
if 'X-LIC-LOCATION' in tz:
del tz['X-LIC-LOCATION']
data = ''.join([str(i) for i in tzs])
data = '\r\n'.join([i for i in data.splitlines() if i.strip()])
fd = StringIO(data)
times = tzmod.tzical(fd)
for tz in times.keys():
self.timezones[tz] = times.get(tz)
def parseEvent(self, e):
for k in ["dtstart", "dtend", "summary"]:
if not k in e:
return
if not isinstance(e['dtstart'].dt, datetime.datetime):
return
return ICalEvent.from_event(e, self)
startDate = endDate = rule = summary = None
startDate = self.parseDate(e.get("dtstart"))
endDate = self.parseDate(e.get("dtend"))
rule = e.get("RRULE")
summary = e.get("summary")
if e.get("exdate"):
event.addExceptionDate(e['EXDATE'].ical()[7:])
if not startDate or not endDate or not summary: # Bad event
return
event = ICalEvent()
event.raw_data = str(e)
event.summary = summary
event.startDate = startDate
event.endDate = endDate
if rule:
event.addRecurrenceRule(rule)
return event
def parseDate(self, date):
if not date:
return
tz = pytz.UTC
if 'tzid' in date.params:
tz = self.timezones[date.params['tzid']]
for attr in ['hour', 'minute', 'second']:
if not hasattr(date.dt, attr):
return
return toTz(date.dt, tz)
# return datetime.datetime(date.dt.year, date.dt.month, date.dt.day, date.dt.hour, date.dt.minute, date.dt.second, tzinfo=tz)
def selectEvents(self, selectFunction):
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):
self.events.sort()
ret = []
for event in self.events:
if event.startsOn(date):
ret.append(event)
return re
#class ICalEvent:
# def __init__(self):
# self.exceptionDates = []
# self.dateSet = None
#
# def __str__(self):
# return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
class ICalEvent(cal.Event):
def __init__(self, *args, **kwargs):
self.exceptionDates = []
self.dateSet = None
self.__parent = super(ICalEvent, self)
self.__parent.__init__(self, *args, **kwargs)
@classmethod
def from_event(cls, event, parent):
global DEB_OBJ
x = cls(**dict(event))
x.__dict__ = event.__dict__
x.exceptionDates = []
x.dateSet = None
x.summary = x['summary']
x.timezone = x['dtstart'].dt.tzinfo
x.startDate = parent.parseDate(x['dtstart'])
x.endDate = parent.parseDate(x['dtend'])
if not x.timezone:
x.timezone = pytz.UTC
x.startDate = parent.parseDate(x['dtstart'])
x.endDate = parent.parseDate(x['dtend'])
x.raw_data = str(x)
if 'rrule' in event:
x.addRecurrenceRule(event['rrule'])
if x.summary == "Server Team Meeting":
DEB_OBJ = x
return x
def __str__(self):
return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
def __eq__(self, otherEvent):
return self.startTime() == otherEvent.startTime()
def __lt__(self, otherEvent):
return self.startTime() < otherEvent.startTime()
def __gt__(self, otherEvent):
return self.startTime() > otherEvent.startTime()
def __ge__(self, otherEvent):
return self.startTime() >= otherEvent.startTime()
def __le__(self, otherEvent):
return self.startTime() <= otherEvent.startTime()
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.today() + datetime.timedelta(1)
# 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):
now = datetime.datetime.now(pytz.UTC)
if self.dateSet and self.startDate < now:
dates = self.dateSet.getRecurrence()
for date in dates:
if date.date() >= now.date():
if date.date() > now.date() or (date.date() == now.date and date.astimezone(pytz.UTC).time() >= now.time()):
return toTz(datetime.datetime.combine(date,self.startDate.time()), self.startDate.tzinfo)
return self.startDate
def endTime(self):
now = datetime.datetime.now(pytz.UTC).date()
if self.dateSet and self.endDate.date() < now:
return toTz(datetime.datetime.combine(self.startTime().date(), self.endDate.time()), self.startDate.tzinfo)
return self.endDate
def schedule(self, timezone=None):
if not timezone:
return "%s UTC: %s" % (self.startTime().astimezone(pytz.UTC).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
if isinstance(timezone, basestring):
return "%s: %s" % (self.startTime().astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
return "%s: %s" % (self.startTime().astimezone(timezone).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
def is_on(self):
now = datetime.datetime.now(pytz.UTC)
return self.startTime() >= now and self.endTime() < now
def has_passed(self):
if self.dateSet:
return toTz(datetime.datetime.combine(self.startTime().date(), self.endDate.time()), self.startDate.tzinfo) < datetime.datetime.now(pytz.UTC)
return self.endDate < datetime.datetime.now(pytz.UTC)
def seconds_to_go(self):
return seconds(self.startTime() - datetime.datetime.now(pytz.UTC))
def seconds_ago(self):
return seconds(datetime.datetime.now(pytz.UTC) - self.endTime())
def time_to_go(self):
if self.endTime() < datetime.datetime.now(pytz.UTC):
return False
delta = self.startTime() - datetime.datetime.now(pytz.UTC)
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 = ''
minutes = (delta.seconds % 3600) / 60
if minutes != 1:
s = 's'
return '%s%d minute%s' % (h,minutes,s)
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.dates = None
self.parseRecurrenceRule(rule)
def parseRecurrenceRule(self, rule):
freq = rruler.rrule_map[rule.pop('freq')[0]]
now = datetime.datetime.now(self.startDate.tzinfo)
rule['dtstart'] = now
rule['until'] = now + datetime.timedelta(60)
self.recurrence = rruler.rrule_wrapper(freq, **rule)
def getRecurrence(self):
if not self.dates:
self.dates = []
for x in list(self.recurrence):
self.dates.append(toTz(x, self.startDate.tzinfo))
self.dates.append(self.startDate)
return self.dates
def includes(self, date):
if isinstance(date, datetime.datetime):
date = date.date()
return date in [x.date() for x in self.getRecurrence()]

View File

@ -1,299 +0,0 @@
#!/usr/bin/python
import sys, os
sys.path.append(os.path.dirname(__file__))
import icalendar
reload(icalendar)
from icalendar import Calendar, cal, prop
from dateutil import tz as tzmod
from cStringIO import StringIO
import pytz
import urllib2
import datetime, time
import rruler
reload(rruler)
SECONDS_PER_DAY=24*60*60
def seconds(timediff):
return SECONDS_PER_DAY * timediff.days + timediff.seconds
class ICalReader:
def __init__(self, data):
self.events = []
self.timezones = {}
self.raw_data = data
self.readEvents()
def readEvents(self):
self.events = []
self.timezones = {}
parser = Calendar.from_string(self.raw_data)
tzs = parser.walk("vtimezone")
self.parseTzs(tzs)
events = parser.walk("vevent")
for event in events:
res = self.parseEvent(event)
if res:
self.events.append(res)
def parseTzs(self, tzs):
if not tzs:
return
for tz in tzs:
if 'X-LIC-LOCATION' in tz:
del tz['X-LIC-LOCATION']
data = ''.join([str(i) for i in tzs])
data = '\r\n'.join([i for i in data.splitlines() if i.strip()])
fd = StringIO(data)
times = tzmod.tzical(fd)
for tz in times.keys():
self.timezones[tz] = times.get(tz)
def parseEvent(self, e):
for k in ["dtstart", "dtend", "summary"]:
if not k in e:
return
if not isinstance(e['dtstart'].dt, datetime.datetime):
return
return ICalEvent.from_event(e, self)
startDate = endDate = rule = summary = None
startDate = self.parseDate(e.get("dtstart"))
endDate = self.parseDate(e.get("dtend"))
rule = e.get("RRULE")
summary = e.get("summary")
if e.get("exdate"):
event.addExceptionDate(e['EXDATE'].ical()[7:])
if not startDate or not endDate or not summary: # Bad event
return
event = ICalEvent()
event.raw_data = str(e)
event.summary = summary
event.startDate = startDate
event.endDate = endDate
if rule:
event.addRecurrenceRule(rule)
return event
@staticmethod
def toTz(date, tz):
return datetime.datetime(date.year, date.month, date.day, date.hour, date.minute, date.second, tzinfo=tz)
def parseDate(self, date):
if not date:
return
tz = pytz.UTC
if 'tzid' in date.params:
tz = self.timezones[date.params['tzid']]
for attr in ['hour', 'minute', 'second']:
if not hasattr(date.dt, attr):
return
return self.toTz(date.dt, tz)
# return datetime.datetime(date.dt.year, date.dt.month, date.dt.day, date.dt.hour, date.dt.minute, date.dt.second, tzinfo=tz)
def selectEvents(self, selectFunction):
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):
self.events.sort()
ret = []
for event in self.events:
if event.startsOn(date):
ret.append(event)
return re
#class ICalEvent:
# def __init__(self):
# self.exceptionDates = []
# self.dateSet = None
#
# def __str__(self):
# return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
class ICalEvent(cal.Event):
def __init__(self, *args, **kwargs):
self.exceptionDates = []
self.dateSet = None
self.__parent = super(ICalEvent, self)
self.__parent.__init__(self, *args, **kwargs)
@classmethod
def from_event(cls, event, parent):
x = cls(**dict(event))
x.__dict__ = event.__dict__
x.summary = x['summary']
x.timezone = x['dtstart'].dt.tzinfo
x.startDate = parent.parseDate(x['dtstart'])
x.endDate = parent.parseDate(x['dtend'])
if not x.timezone:
x.timezone = pytz.UTC
x.startDate = parent.parseDate(x['dtstart'])
x.endDate = parent.parseDate(x['dtend'])
x.raw_data = str(x)
if 'rrule' in event:
x.addRecurrenceRule(event['rrule'])
return x
def __str__(self):
return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
def __eq__(self, otherEvent):
return self.startDate == otherEvent.startDate
def __lt__(self, otherEvent):
return self.startDate < otherEvent.startDate
def __gt__(self, otherEvent):
return self.startDate > otherEvent.startDate
def __ge__(self, otherEvent):
return self.startDate >= otherEvent.startDate
def __le__(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
def schedule(self, timezone=None):
if not timezone:
return "%s UTC: %s" % (self.startDate.strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
return "%s: %s" % (self.startDate.astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M %Z"), self.summary.replace('Meeting','').strip())
def is_on(self):
return self.startDate < datetime.datetime.now(pytz.UTC) and self.endDate > datetime.datetime.now(pytz.UTC)
def has_passed(self):
return self.endDate < datetime.datetime.now(pytz.UTC)
def seconds_to_go(self):
return seconds(self.startDate - datetime.datetime.now(pytz.UTC))
def seconds_ago(self):
return seconds(datetime.datetime.now(pytz.UTC) - self.endDate)
def time_to_go(self):
if self.endDate < datetime.datetime.now(pytz.UTC):
return False
delta = self.startDate - datetime.datetime.now(pytz.UTC)
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 = ''
minutes = (delta.seconds % 3600) / 60
if minutes != 1:
s = 's'
return '%s%d minute%s' % (h,minutes,s)
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):
freq = rruler.rrule_map[rule.pop('freq')[0]]
self.recurrence = rruler.rrule_wrapper(freq, **rule)
# if 'freq' in rule:
# self.frequency = rule['freq']
# if 'count' in rule:
# self.count = rule['count']
# if 'until' in rule:
## self.untilDate = rule['until'][0].strftime("%Y%m%dT%H%M%SZ")
# self.untilDate = rule['until'][0]
# if 'interval' in rule:
# self.interval = rule['interval']
# if 'bymonth' in rule:
# self.myMonth = rule['bymonth']
# if 'byday' in rule:
# self.byDay = rule['byday']
def includes(self, date):
if isinstance(date, datetime.datetime):
date = date.date()
return date in [x.date() for x in list(self.recurrence)] or date == self.startDate.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':
# if self.startDate.month == date.month:
# if self.startDate.weekday() == date.weekday():
# return True
#
# elif self.frequency == 'YEARLY':
# if (self.startDate.month == date.month) and (self.startDate.day == date.day):
# return True
#
# return False

View File

@ -1,286 +0,0 @@
#!/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
parent = None
def log(x):
if not parent:
return
parent.log.info(x)
SECONDS_PER_DAY=24*60*60
def seconds(timediff):
return SECONDS_PER_DAY * timediff.days + timediff.seconds
class ICalReader:
def __init__(self, data):
self.events = []
self.raw_data = data.replace('\r','')
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):
inEvent = False
event = self.parseEvent(eventLines)
if event:
self.events.append(event)
self.events.sort()
return self.events
def parseEvent(self, lines):
event = ICalEvent()
event.raw_data = "\n".join(lines)
startDate = None
rule = None
endDate = None
reSummary = re.compile("^SUMMARY:(.*)")
reDstart = re.compile("^DTSTART(.*):([0-9]+T[0-9]+)")
reDend = re.compile("^DTEND(.*):([0-9]+T[0-9]+)")
reExdata = re.compile("^EXDATE:([0-9]+T[0-9]+)")
reRrule = re.compile("^RRULE:(.*)")
for line in lines:
match = False
if reSummary.match(line):
event.summary = reSummary.match(line).group(1)
elif reDstart.match(line):
startDate = self.parseDate(*reDstart.match(line).groups())
elif reDend.match(line):
endDate = self.parseDate(*reDend.match(line).groups())
elif reExdata.match(line):
event.addExceptionDate(reExdate.match(line).group(1))
elif reRrule.match(line):
rule = reRrule.match(line).group(1)
event.startDate = startDate
event.endDate = endDate
if rule:
event.addRecurrenceRule(rule)
if not startDate or not endDate:
return None
return event
def parseDate(self, tz, 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
if tz:
return datetime.datetime(year, month, day, hour, minute, tzinfo=pytz.timezone(tz[6:]))
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 "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
def __eq__(self, otherEvent):
return self.startDate == otherEvent.startDate
def __lt__(self, otherEvent):
return self.startDate < otherEvent.startDate
def __gt__(self, otherEvent):
return self.startDate > otherEvent.startDate
def __ge__(self, otherEvent):
return self.startDate >= otherEvent.startDate
def __le__(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
def schedule(self, timezone=None):
if not timezone:
return "%s UTC: %s" % (self.startDate.strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
return "%s: %s" % (self.startDate.astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
def is_on(self):
return self.startDate < datetime.datetime.now(pytz.UTC) and self.endDate > datetime.datetime.now(pytz.UTC)
def has_passed(self):
return self.endDate < datetime.datetime.now(pytz.UTC)
def seconds_to_go(self):
return seconds(self.startDate - datetime.datetime.now(pytz.UTC))
def seconds_ago(self):
return seconds(datetime.datetime.now(pytz.UTC) - self.endDate)
def time_to_go(self):
if self.endDate < datetime.datetime.now(pytz.UTC):
return False
delta = self.startDate - datetime.datetime.now(pytz.UTC)
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 = ''
minutes = (delta.seconds % 3600) / 60
if minutes != 1:
s = 's'
return '%s%d minute%s' % (h,minutes,s)
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))
self.untilDate = 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':
if self.startDate.month == date.month:
if self.startDate.weekday() == date.weekday():
return True
elif self.frequency == 'YEARLY':
if (self.startDate.month == date.month) and (self.startDate.day == date.day):
return True
return False

View File

@ -1,17 +0,0 @@
# -*- Encoding: utf-8 -*-
# Components
from icalendar.cal import Calendar, Event, Todo, Journal
from icalendar.cal import FreeBusy, Timezone, Alarm, ComponentFactory
# Property Data Value Types
from icalendar.prop import vBinary, vBoolean, vCalAddress, vDatetime, vDate, \
vDDDTypes, vDuration, vFloat, vInt, vPeriod, \
vWeekday, vFrequency, vRecur, vText, vTime, vUri, \
vGeo, vUTCOffset, TypesFactory
# useful tzinfo subclasses
from icalendar.prop import FixedOffset, UTC, LocalTimezone
# Parameters and helper methods for splitting and joining string with escaped
# chars.
from icalendar.parser import Parameters, q_split, q_join

View File

@ -1,534 +0,0 @@
# -*- coding: latin-1 -*-
"""
Calendar is a dictionary like Python object that can render itself as VCAL
files according to rfc2445.
These are the defined components.
"""
# from python
from types import ListType, TupleType
SequenceTypes = (ListType, TupleType)
import re
# from this package
from icalendar.caselessdict import CaselessDict
from icalendar.parser import Contentlines, Contentline, Parameters
from icalendar.parser import q_split, q_join
from icalendar.prop import TypesFactory, vText
######################################
# The component factory
class ComponentFactory(CaselessDict):
"""
All components defined in rfc 2445 are registered in this factory class. To
get a component you can use it like this.
>>> factory = ComponentFactory()
>>> component = factory['VEVENT']
>>> event = component(dtstart='19700101')
>>> event.as_string()
'BEGIN:VEVENT\\r\\nDTSTART:19700101\\r\\nEND:VEVENT\\r\\n'
>>> factory.get('VCALENDAR', Component)
<class 'icalendar.cal.Calendar'>
"""
def __init__(self, *args, **kwargs):
"Set keys to upper for initial dict"
CaselessDict.__init__(self, *args, **kwargs)
self['VEVENT'] = Event
self['VTODO'] = Todo
self['VJOURNAL'] = Journal
self['VFREEBUSY'] = FreeBusy
self['VTIMEZONE'] = Timezone
self['VALARM'] = Alarm
self['VCALENDAR'] = Calendar
# These Properties have multiple property values inlined in one propertyline
# seperated by comma. Use CaselessDict as simple caseless set.
INLINE = CaselessDict(
[(cat, 1) for cat in ('CATEGORIES', 'RESOURCES', 'FREEBUSY')]
)
_marker = []
class Component(CaselessDict):
"""
Component is the base object for calendar, Event and the other components
defined in RFC 2445. normally you will not use this class directy, but
rather one of the subclasses.
A component is like a dictionary with extra methods and attributes.
>>> c = Component()
>>> c.name = 'VCALENDAR'
Every key defines a property. A property can consist of either a single
item. This can be set with a single value
>>> c['prodid'] = '-//max m//icalendar.mxm.dk/'
>>> c
VCALENDAR({'PRODID': '-//max m//icalendar.mxm.dk/'})
or with a list
>>> c['ATTENDEE'] = ['Max M', 'Rasmussen']
if you use the add method you don't have to considder if a value is a list
or not.
>>> c = Component()
>>> c.name = 'VEVENT'
>>> c.add('attendee', 'maxm@mxm.dk')
>>> c.add('attendee', 'test@example.dk')
>>> c
VEVENT({'ATTENDEE': [vCalAddress('maxm@mxm.dk'), vCalAddress('test@example.dk')]})
You can get the values back directly
>>> c.add('prodid', '-//my product//')
>>> c['prodid']
vText(u'-//my product//')
or decoded to a python type
>>> c.decoded('prodid')
u'-//my product//'
With default values for non existing properties
>>> c.decoded('version', 'No Version')
'No Version'
The component can render itself in the RFC 2445 format.
>>> c = Component()
>>> c.name = 'VCALENDAR'
>>> c.add('attendee', 'Max M')
>>> c.as_string()
'BEGIN:VCALENDAR\\r\\nATTENDEE:Max M\\r\\nEND:VCALENDAR\\r\\n'
>>> from icalendar.prop import vDatetime
Components can be nested, so You can add a subcompont. Eg a calendar holds events.
>>> e = Component(summary='A brief history of time')
>>> e.name = 'VEVENT'
>>> e.add('dtend', '20000102T000000', encode=0)
>>> e.add('dtstart', '20000101T000000', encode=0)
>>> e.as_string()
'BEGIN:VEVENT\\r\\nDTEND:20000102T000000\\r\\nDTSTART:20000101T000000\\r\\nSUMMARY:A brief history of time\\r\\nEND:VEVENT\\r\\n'
>>> c.add_component(e)
>>> c.subcomponents
[VEVENT({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', 'SUMMARY': 'A brief history of time'})]
We can walk over nested componentes with the walk method.
>>> [i.name for i in c.walk()]
['VCALENDAR', 'VEVENT']
We can also just walk over specific component types, by filtering them on
their name.
>>> [i.name for i in c.walk('VEVENT')]
['VEVENT']
>>> [i['dtstart'] for i in c.walk('VEVENT')]
['20000101T000000']
INLINE properties have their values on one property line. Note the double
quoting of the value with a colon in it.
>>> c = Calendar()
>>> c['resources'] = 'Chair, Table, "Room: 42"'
>>> c
VCALENDAR({'RESOURCES': 'Chair, Table, "Room: 42"'})
>>> c.as_string()
'BEGIN:VCALENDAR\\r\\nRESOURCES:Chair, Table, "Room: 42"\\r\\nEND:VCALENDAR\\r\\n'
The inline values must be handled by the get_inline() and set_inline()
methods.
>>> c.get_inline('resources', decode=0)
['Chair', 'Table', 'Room: 42']
These can also be decoded
>>> c.get_inline('resources', decode=1)
[u'Chair', u'Table', u'Room: 42']
You can set them directly
>>> c.set_inline('resources', ['A', 'List', 'of', 'some, recources'], encode=1)
>>> c['resources']
'A,List,of,"some, recources"'
and back again
>>> c.get_inline('resources', decode=0)
['A', 'List', 'of', 'some, recources']
>>> c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,19970308T230000Z/19970309T000000Z'
>>> c.get_inline('freebusy', decode=0)
['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', '19970308T230000Z/19970309T000000Z']
>>> freebusy = c.get_inline('freebusy', decode=1)
>>> type(freebusy[0][0]), type(freebusy[0][1])
(<type 'datetime.datetime'>, <type 'datetime.timedelta'>)
"""
name = '' # must be defined in each component
required = () # These properties are required
singletons = () # These properties must only appear once
multiple = () # may occur more than once
exclusive = () # These properties are mutually exclusive
inclusive = () # if any occurs the other(s) MUST occur ('duration', 'repeat')
def __init__(self, *args, **kwargs):
"Set keys to upper for initial dict"
CaselessDict.__init__(self, *args, **kwargs)
# set parameters here for properties that use non-default values
self.subcomponents = [] # Components can be nested.
# def non_complience(self, warnings=0):
# """
# not implemented yet!
# Returns a dict describing non compliant properties, if any.
# If warnings is true it also returns warnings.
#
# If the parser is too strict it might prevent parsing erroneous but
# otherwise compliant properties. So the parser is pretty lax, but it is
# possible to test for non-complience by calling this method.
# """
# nc = {}
# if not getattr(self, 'name', ''):
# nc['name'] = {'type':'ERROR', 'description':'Name is not defined'}
# return nc
#############################
# handling of property values
def _encode(self, name, value, cond=1):
# internal, for conditional convertion of values.
if cond:
klass = types_factory.for_property(name)
return klass(value)
return value
def set(self, name, value, encode=1):
if type(value) == ListType:
self[name] = [self._encode(name, v, encode) for v in value]
else:
self[name] = self._encode(name, value, encode)
def add(self, name, value, encode=1):
"If property exists append, else create and set it"
if name in self:
oldval = self[name]
value = self._encode(name, value, encode)
if type(oldval) == ListType:
oldval.append(value)
else:
self.set(name, [oldval, value], encode=0)
else:
self.set(name, value, encode)
def _decode(self, name, value):
# internal for decoding property values
decoded = types_factory.from_ical(name, value)
return decoded
def decoded(self, name, default=_marker):
"Returns decoded value of property"
if name in self:
value = self[name]
if type(value) == ListType:
return [self._decode(name, v) for v in value]
return self._decode(name, value)
else:
if default is _marker:
raise KeyError, name
else:
return default
########################################################################
# Inline values. A few properties have multiple values inlined in in one
# property line. These methods are used for splitting and joining these.
def get_inline(self, name, decode=1):
"""
Returns a list of values (split on comma).
"""
vals = [v.strip('" ').encode(vText.encoding)
for v in q_split(self[name])]
if decode:
return [self._decode(name, val) for val in vals]
return vals
def set_inline(self, name, values, encode=1):
"""
Converts a list of values into comma seperated string and sets value to
that.
"""
if encode:
values = [self._encode(name, value, 1) for value in values]
joined = q_join(values).encode(vText.encoding)
self[name] = types_factory['inline'](joined)
#########################
# Handling of components
def add_component(self, component):
"add a subcomponent to this component"
self.subcomponents.append(component)
def _walk(self, name):
# private!
result = []
if name is None or self.name == name:
result.append(self)
for subcomponent in self.subcomponents:
result += subcomponent._walk(name)
return result
def walk(self, name=None):
"""
Recursively traverses component and subcomponents. Returns sequence of
same. If name is passed, only components with name will be returned.
"""
if not name is None:
name = name.upper()
return self._walk(name)
#####################
# Generation
def property_items(self):
"""
Returns properties in this component and subcomponents as:
[(name, value), ...]
"""
vText = types_factory['text']
properties = [('BEGIN', vText(self.name).ical())]
property_names = self.keys()
property_names.sort()
for name in property_names:
values = self[name]
if type(values) == ListType:
# normally one property is one line
for value in values:
properties.append((name, value))
else:
properties.append((name, values))
# recursion is fun!
for subcomponent in self.subcomponents:
properties += subcomponent.property_items()
properties.append(('END', vText(self.name).ical()))
return properties
def from_string(st, multiple=False):
"""
Populates the component recursively from a string
"""
stack = [] # a stack of components
comps = []
for line in Contentlines.from_string(st): # raw parsing
if not line:
continue
name, params, vals = line.parts()
uname = name.upper()
# check for start of component
if uname == 'BEGIN':
# try and create one of the components defined in the spec,
# otherwise get a general Components for robustness.
component_name = vals.upper()
component_class = component_factory.get(component_name, Component)
component = component_class()
if not getattr(component, 'name', ''): # for undefined components
component.name = component_name
stack.append(component)
# check for end of event
elif uname == 'END':
# we are done adding properties to this component
# so pop it from the stack and add it to the new top.
component = stack.pop()
if not stack: # we are at the end
comps.append(component)
else:
stack[-1].add_component(component)
# we are adding properties to the current top of the stack
else:
factory = types_factory.for_property(name)
vals = factory(factory.from_ical(vals))
vals.params = params
stack[-1].add(name, vals, encode=0)
if multiple:
return comps
if not len(comps) == 1:
raise ValueError('Found multiple components where '
'only one is allowed')
return comps[0]
from_string = staticmethod(from_string)
def __repr__(self):
return '%s(' % self.name + dict.__repr__(self) + ')'
# def content_line(self, name):
# "Returns property as content line"
# value = self[name]
# params = getattr(value, 'params', Parameters())
# return Contentline.from_parts((name, params, value))
def content_lines(self):
"Converts the Component and subcomponents into content lines"
contentlines = Contentlines()
for name, values in self.property_items():
params = getattr(values, 'params', Parameters())
contentlines.append(Contentline.from_parts((name, params, values)))
contentlines.append('') # remember the empty string in the end
return contentlines
def as_string(self):
return str(self.content_lines())
def __str__(self):
"Returns rendered iCalendar"
return self.as_string()
#######################################
# components defined in RFC 2445
class Event(Component):
name = 'VEVENT'
required = ('UID',)
singletons = (
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'GEO',
'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'DTSTAMP', 'SEQUENCE',
'STATUS', 'SUMMARY', 'TRANSP', 'URL', 'RECURID', 'DTEND', 'DURATION',
'DTSTART',
)
exclusive = ('DTEND', 'DURATION', )
multiple = (
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT','CONTACT', 'EXDATE',
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
)
class Todo(Component):
name = 'VTODO'
required = ('UID',)
singletons = (
'CLASS', 'COMPLETED', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART',
'GEO', 'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY',
'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', 'DUE', 'DURATION',
)
exclusive = ('DUE', 'DURATION',)
multiple = (
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
)
class Journal(Component):
name = 'VJOURNAL'
required = ('UID',)
singletons = (
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'DTSTAMP', 'LAST-MOD',
'ORGANIZER', 'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL',
)
multiple = (
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
'EXRULE', 'RELATED', 'RDATE', 'RRULE', 'RSTATUS',
)
class FreeBusy(Component):
name = 'VFREEBUSY'
required = ('UID',)
singletons = (
'CONTACT', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP', 'ORGANIZER',
'UID', 'URL',
)
multiple = ('ATTENDEE', 'COMMENT', 'FREEBUSY', 'RSTATUS',)
class Timezone(Component):
name = 'VTIMEZONE'
required = (
'TZID', 'STANDARDC', 'DAYLIGHTC', 'DTSTART', 'TZOFFSETTO',
'TZOFFSETFROM'
)
singletons = ('LAST-MOD', 'TZURL', 'TZID',)
multiple = ('COMMENT', 'RDATE', 'RRULE', 'TZNAME',)
class Alarm(Component):
name = 'VALARM'
# not quite sure about these ...
required = ('ACTION', 'TRIGGER',)
singletons = ('ATTACH', 'ACTION', 'TRIGGER', 'DURATION', 'REPEAT',)
inclusive = (('DURATION', 'REPEAT',),)
multiple = ('STANDARDC', 'DAYLIGHTC')
class Calendar(Component):
"""
This is the base object for an iCalendar file.
Setting up a minimal calendar component looks like this
>>> cal = Calendar()
Som properties are required to be compliant
>>> cal['prodid'] = '-//My calendar product//mxm.dk//'
>>> cal['version'] = '2.0'
We also need at least one subcomponent for a calendar to be compliant
>>> from datetime import datetime
>>> event = Event()
>>> event['summary'] = 'Python meeting about calendaring'
>>> event['uid'] = '42'
>>> event.set('dtstart', datetime(2005,4,4,8,0,0))
>>> cal.add_component(event)
>>> cal.subcomponents[0].as_string()
'BEGIN:VEVENT\\r\\nDTSTART:20050404T080000\\r\\nSUMMARY:Python meeting about calendaring\\r\\nUID:42\\r\\nEND:VEVENT\\r\\n'
Write to disc
>>> import tempfile, os
>>> directory = tempfile.mkdtemp()
>>> open(os.path.join(directory, 'test.ics'), 'wb').write(cal.as_string())
"""
name = 'VCALENDAR'
required = ('prodid', 'version', )
singletons = ('prodid', 'version', )
multiple = ('calscale', 'method', )
# These are read only singleton, so one instance is enough for the module
types_factory = TypesFactory()
component_factory = ComponentFactory()

View File

@ -1,93 +0,0 @@
# -*- coding: latin-1 -*-
class CaselessDict(dict):
"""
A dictionary that isn't case sensitive, and only use string as keys.
>>> ncd = CaselessDict(key1='val1', key2='val2')
>>> ncd
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
>>> ncd['key1']
'val1'
>>> ncd['KEY1']
'val1'
>>> ncd['KEY3'] = 'val3'
>>> ncd['key3']
'val3'
>>> ncd.setdefault('key3', 'FOUND')
'val3'
>>> ncd.setdefault('key4', 'NOT FOUND')
'NOT FOUND'
>>> ncd['key4']
'NOT FOUND'
>>> ncd.get('key1')
'val1'
>>> ncd.get('key3', 'NOT FOUND')
'val3'
>>> ncd.get('key4', 'NOT FOUND')
'NOT FOUND'
>>> 'key4' in ncd
True
>>> del ncd['key4']
>>> ncd.has_key('key4')
False
>>> ncd.update({'key5':'val5', 'KEY6':'val6', 'KEY5':'val7'})
>>> ncd['key6']
'val6'
>>> keys = ncd.keys()
>>> keys.sort()
>>> keys
['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6']
"""
def __init__(self, *args, **kwargs):
"Set keys to upper for initial dict"
dict.__init__(self, *args, **kwargs)
for k,v in self.items():
k_upper = k.upper()
if k != k_upper:
dict.__delitem__(self, k)
self[k_upper] = v
def __getitem__(self, key):
return dict.__getitem__(self, key.upper())
def __setitem__(self, key, value):
dict.__setitem__(self, key.upper(), value)
def __delitem__(self, key):
dict.__delitem__(self, key.upper())
def __contains__(self, item):
return dict.__contains__(self, item.upper())
def get(self, key, default=None):
return dict.get(self, key.upper(), default)
def setdefault(self, key, value=None):
return dict.setdefault(self, key.upper(), value)
def pop(self, key, default=None):
return dict.pop(self, key.upper(), default)
def popitem(self):
return dict.popitem(self)
def has_key(self, key):
return dict.has_key(self, key.upper())
def update(self, indict):
"""
Multiple keys where key1.upper() == key2.upper() will be lost.
"""
for entry in indict:
self[entry] = indict[entry]
def copy(self):
return CaselessDict(dict.copy(self))
def clear(self):
dict.clear(self)
def __repr__(self):
return 'CaselessDict(' + dict.__repr__(self) + ')'

View File

@ -1,263 +0,0 @@
# -*- Encoding: utf-8 -*-
try:
from zope.interface import Interface, Attribute
except ImportError:
class Interface:
"""A dummy interface base class"""
class Attribute:
"""A dummy attribute implementation"""
def __init__(self, doc):
self.doc = doc
_marker = object()
class IComponent(Interface):
"""
Component is the base object for calendar, Event and the other
components defined in RFC 2445.
A component is like a dictionary with extra methods and attributes.
"""
# MANIPULATORS
def __setitem__(name, value):
"""Set a property.
name - case insensitive name
value - value of the property to set. This can be either a single
item or a list.
Some iCalendar properties are set INLINE; these properties
have multiple values on one property line in the iCalendar
representation. The list can be supplied as a comma separated
string to __setitem__. If special iCalendar characters exist in
an entry, such as the colon (:) and (,), that comma-separated
entry needs to be quoted with double quotes. For example:
'foo, bar, "baz:hoi"'
See also set_inline() for an easier way to deal with this case.
"""
def set_inline(name, values, encode=1):
"""Set list of INLINE values for property.
Converts a list of values into valid iCalendar comma seperated
string and sets value to that.
name - case insensitive name of property
values - list of values to set
encode - if True, encode Python values as iCalendar types first.
"""
def add(name, value):
"""Add a property. Can be called multiple times to set a list.
name - case insensitive name
value - value of property to set or add to list for this property.
"""
def add_component(component):
"""Add a nested subcomponent to this component.
"""
# static method, can be called on class directly
def from_string(st, multiple=False):
"""Populates the component recursively from a iCalendar string.
Reads the iCalendar string and constructs components and
subcomponents out of it.
"""
# ACCESSORS
def __getitem__(name):
"""Get a property
name - case insensitive name
Returns an iCalendar property object such as vText.
"""
def decoded(name, default=_marker):
"""Get a property as a python object.
name - case insensitive name
default - optional argument. If supplied, will use this if
name cannot be found. If not supplied, decoded will raise a
KeyError if name cannot be found.
Returns python object (such as unicode string, datetime, etc).
"""
def get_inline(name, decode=1):
"""Get list of INLINE values from property.
name - case insensitive name
decode - decode to Python objects.
Returns list of python objects.
"""
def as_string():
"""Render the component in the RFC 2445 (iCalendar) format.
Returns a string in RFC 2445 format.
"""
subcomponents = Attribute("""
A list of all subcomponents of this component,
added using add_component()""")
name = Attribute("""
Name of this component (VEVENT, etc)
""")
def walk(name=None):
"""Recursively traverses component and subcomponents.
name - optional, if given, only return components with that name
Returns sequence of components.
"""
def property_items():
"""Return properties as (name, value) tuples.
Returns all properties in this comopnent and subcomponents as
name, value tuples.
"""
class IEvent(IComponent):
"""A component which conforms to an iCalendar VEVENT.
"""
class ITodo(IComponent):
"""A component which conforms to an iCalendar VTODO.
"""
class IJournal(IComponent):
"""A component which conforms to an iCalendar VJOURNAL.
"""
class IFreeBusy(IComponent):
"""A component which conforms to an iCalendar VFREEBUSY.
"""
class ITimezone(IComponent):
"""A component which conforms to an iCalendar VTIMEZONE.
"""
class IAlarm(IComponent):
"""A component which conforms to an iCalendar VALARM.
"""
class ICalendar(IComponent):
"""A component which conforms to an iCalendar VCALENDAR.
"""
class IPropertyValue(Interface):
"""An iCalendar property value.
iCalendar properties have strongly typed values.
This invariance should always be true:
assert x == vDataType.from_ical(vDataType(x).ical())
"""
def ical():
"""Render property as string, as defined in iCalendar RFC 2445.
"""
# this is a static method
def from_ical(ical):
"""Parse property from iCalendar RFC 2445 text.
Inverse of ical().
"""
class IBinary(IPropertyValue):
"""Binary property values are base 64 encoded
"""
class IBoolean(IPropertyValue):
"""Boolean property.
Also behaves like a python int.
"""
class ICalAddress(IPropertyValue):
"""Email address.
Also behaves like a python str.
"""
class IDateTime(IPropertyValue):
"""Render and generates iCalendar datetime format.
Important: if tzinfo is defined it renders itself as 'date with utc time'
Meaning that it has a 'Z' appended, and is in absolute time.
"""
class IDate(IPropertyValue):
"""Render and generates iCalendar date format.
"""
class IDuration(IPropertyValue):
"""Render and generates timedelta in iCalendar DURATION format.
"""
class IFloat(IPropertyValue):
"""Render and generate floats in iCalendar format.
Also behaves like a python float.
"""
class IInt(IPropertyValue):
"""Render and generate ints in iCalendar format.
Also behaves like a python int.
"""
class IPeriod(IPropertyValue):
"""A precise period of time (datetime, datetime).
"""
class IWeekDay(IPropertyValue):
"""Render and generate weekday abbreviation.
"""
class IFrequency(IPropertyValue):
"""Frequency.
"""
class IRecur(IPropertyValue):
"""Render and generate data based on recurrent event representation.
This acts like a caseless dictionary.
"""
class IText(IPropertyValue):
"""Unicode text.
"""
class ITime(IPropertyValue):
"""Time.
"""
class IUri(IPropertyValue):
"""URI
"""
class IGeo(IPropertyValue):
"""Geographical location.
"""
class IUTCOffset(IPropertyValue):
"""Offset from UTC.
"""
class IInline(IPropertyValue):
"""Inline list.
"""

View File

@ -1,522 +0,0 @@
# -*- coding: latin-1 -*-
"""
This module parses and generates contentlines as defined in RFC 2445
(iCalendar), but will probably work for other MIME types with similar syntax.
Eg. RFC 2426 (vCard)
It is stupid in the sense that it treats the content purely as strings. No type
conversion is attempted.
Copyright, 2005: Max M <maxm@mxm.dk>
License: GPL (Just contact med if and why you would like it changed)
"""
# from python
from types import TupleType, ListType
SequenceTypes = [TupleType, ListType]
import re
# from this package
from icalendar.caselessdict import CaselessDict
#################################################################
# Property parameter stuff
def paramVal(val):
"Returns a parameter value"
if type(val) in SequenceTypes:
return q_join(val)
return dQuote(val)
# Could be improved
NAME = re.compile('[\w-]+')
UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]')
QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]')
FOLD = re.compile('([\r]?\n)+[ \t]{1}')
def validate_token(name):
match = NAME.findall(name)
if len(match) == 1 and name == match[0]:
return
raise ValueError, name
def validate_param_value(value, quoted=True):
validator = UNSAFE_CHAR
if quoted:
validator = QUNSAFE_CHAR
if validator.findall(value):
raise ValueError, value
QUOTABLE = re.compile('[,;:].')
def dQuote(val):
"""
Parameter values containing [,;:] must be double quoted
>>> dQuote('Max')
'Max'
>>> dQuote('Rasmussen, Max')
'"Rasmussen, Max"'
>>> dQuote('name:value')
'"name:value"'
"""
if QUOTABLE.search(val):
return '"%s"' % val
return val
# parsing helper
def q_split(st, sep=','):
"""
Splits a string on char, taking double (q)uotes into considderation
>>> q_split('Max,Moller,"Rasmussen, Max"')
['Max', 'Moller', '"Rasmussen, Max"']
"""
result = []
cursor = 0
length = len(st)
inquote = 0
for i in range(length):
ch = st[i]
if ch == '"':
inquote = not inquote
if not inquote and ch == sep:
result.append(st[cursor:i])
cursor = i + 1
if i + 1 == length:
result.append(st[cursor:])
return result
def q_join(lst, sep=','):
"""
Joins a list on sep, quoting strings with QUOTABLE chars
>>> s = ['Max', 'Moller', 'Rasmussen, Max']
>>> q_join(s)
'Max,Moller,"Rasmussen, Max"'
"""
return sep.join([dQuote(itm) for itm in lst])
class Parameters(CaselessDict):
"""
Parser and generator of Property parameter strings. It knows nothing of
datatypes. It's main concern is textual structure.
Simple parameter:value pair
>>> p = Parameters(parameter1='Value1')
>>> str(p)
'PARAMETER1=Value1'
keys are converted to upper
>>> p.keys()
['PARAMETER1']
Parameters are case insensitive
>>> p['parameter1']
'Value1'
>>> p['PARAMETER1']
'Value1'
Parameter with list of values must be seperated by comma
>>> p = Parameters({'parameter1':['Value1', 'Value2']})
>>> str(p)
'PARAMETER1=Value1,Value2'
Multiple parameters must be seperated by a semicolon
>>> p = Parameters({'RSVP':'TRUE', 'ROLE':'REQ-PARTICIPANT'})
>>> str(p)
'ROLE=REQ-PARTICIPANT;RSVP=TRUE'
Parameter values containing ',;:' must be double quoted
>>> p = Parameters({'ALTREP':'http://www.wiz.org'})
>>> str(p)
'ALTREP="http://www.wiz.org"'
list items must be quoted seperately
>>> p = Parameters({'MEMBER':['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com', ]})
>>> str(p)
'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
Now the whole sheebang
>>> p = Parameters({'parameter1':'Value1', 'parameter2':['Value2', 'Value3'],\
'ALTREP':['http://www.wiz.org', 'value4']})
>>> str(p)
'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'
We can also parse parameter strings
>>> Parameters.from_string('PARAMETER1=Value 1;param2=Value 2')
Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'})
Including empty strings
>>> Parameters.from_string('param=')
Parameters({'PARAM': ''})
We can also parse parameter strings
>>> Parameters.from_string('MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"')
Parameters({'MEMBER': ['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com']})
We can also parse parameter strings
>>> Parameters.from_string('ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3')
Parameters({'PARAMETER1': 'Value1', 'ALTREP': ['http://www.wiz.org', 'value4'], 'PARAMETER2': ['Value2', 'Value3']})
"""
def params(self):
"""
in rfc2445 keys are called parameters, so this is to be consitent with
the naming conventions
"""
return self.keys()
### Later, when I get more time... need to finish this off now. The last majot thing missing.
### def _encode(self, name, value, cond=1):
### # internal, for conditional convertion of values.
### if cond:
### klass = types_factory.for_property(name)
### return klass(value)
### return value
###
### def add(self, name, value, encode=0):
### "Add a parameter value and optionally encode it."
### if encode:
### value = self._encode(name, value, encode)
### self[name] = value
###
### def decoded(self, name):
### "returns a decoded value, or list of same"
def __repr__(self):
return 'Parameters(' + dict.__repr__(self) + ')'
def __str__(self):
result = []
items = self.items()
items.sort() # To make doctests work
for key, value in items:
value = paramVal(value)
result.append('%s=%s' % (key.upper(), value))
return ';'.join(result)
def from_string(st, strict=False):
"Parses the parameter format from ical text format"
try:
# parse into strings
result = Parameters()
for param in q_split(st, ';'):
key, val = q_split(param, '=')
validate_token(key)
param_values = [v for v in q_split(val, ',')]
# Property parameter values that are not in quoted
# strings are case insensitive.
vals = []
for v in param_values:
if v.startswith('"') and v.endswith('"'):
v = v.strip('"')
validate_param_value(v, quoted=True)
vals.append(v)
else:
validate_param_value(v, quoted=False)
if strict:
vals.append(v.upper())
else:
vals.append(v)
if not vals:
result[key] = val
else:
if len(vals) == 1:
result[key] = vals[0]
else:
result[key] = vals
return result
except:
raise ValueError, 'Not a valid parameter string'
from_string = staticmethod(from_string)
#########################################
# parsing and generation of content lines
class Contentline(str):
"""
A content line is basically a string that can be folded and parsed into
parts.
>>> c = Contentline('Si meliora dies, ut vina, poemata reddit')
>>> str(c)
'Si meliora dies, ut vina, poemata reddit'
A long line gets folded
>>> c = Contentline(''.join(['123456789 ']*10))
>>> str(c)
'123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 '
A folded line gets unfolded
>>> c = Contentline.from_string(str(c))
>>> c
'123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 '
We do not fold within a UTF-8 character:
>>> c = Contentline('This line has a UTF-8 character where it should be folded. Make sure it g\xc3\xabts folded before that character.')
>>> '\xc3\xab' in str(c)
True
Don't fail if we fold a line that is exactly X times 74 characters long:
>>> c = str(Contentline(''.join(['x']*148)))
It can parse itself into parts. Which is a tuple of (name, params, vals)
>>> c = Contentline('dtstart:20050101T120000')
>>> c.parts()
('dtstart', Parameters({}), '20050101T120000')
>>> c = Contentline('dtstart;value=datetime:20050101T120000')
>>> c.parts()
('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
>>> c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com')
>>> c.parts()
('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com')
>>> str(c)
'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com'
and back again
>>> parts = ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com')
>>> Contentline.from_parts(parts)
'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com'
and again
>>> parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
>>> Contentline.from_parts(parts)
'ATTENDEE:MAILTO:maxm@example.com'
A value can also be any of the types defined in PropertyValues
>>> from icalendar.prop import vText
>>> parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com'))
>>> Contentline.from_parts(parts)
'ATTENDEE:MAILTO:test@example.com'
A value can also be unicode
>>> from icalendar.prop import vText
>>> parts = ('SUMMARY', Parameters(), vText(u'INternational char æ ø å'))
>>> Contentline.from_parts(parts)
'SUMMARY:INternational char \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5'
Traversing could look like this.
>>> name, params, vals = c.parts()
>>> name
'ATTENDEE'
>>> vals
'MAILTO:maxm@example.com'
>>> for key, val in params.items():
... (key, val)
('ROLE', 'REQ-PARTICIPANT')
('CN', 'Max Rasmussen')
And the traditional failure
>>> c = Contentline('ATTENDEE;maxm@example.com')
>>> c.parts()
Traceback (most recent call last):
...
ValueError: Content line could not be parsed into parts
Another failure:
>>> c = Contentline(':maxm@example.com')
>>> c.parts()
Traceback (most recent call last):
...
ValueError: Content line could not be parsed into parts
>>> c = Contentline('key;param=:value')
>>> c.parts()
('key', Parameters({'PARAM': ''}), 'value')
>>> c = Contentline('key;param="pvalue":value')
>>> c.parts()
('key', Parameters({'PARAM': 'pvalue'}), 'value')
Should bomb on missing param:
>>> c = Contentline.from_string("k;:no param")
>>> c.parts()
Traceback (most recent call last):
...
ValueError: Content line could not be parsed into parts
>>> c = Contentline('key;param=pvalue:value', strict=False)
>>> c.parts()
('key', Parameters({'PARAM': 'pvalue'}), 'value')
If strict is set to True, uppercase param values that are not
double-quoted, this is because the spec says non-quoted params are
case-insensitive.
>>> c = Contentline('key;param=pvalue:value', strict=True)
>>> c.parts()
('key', Parameters({'PARAM': 'PVALUE'}), 'value')
>>> c = Contentline('key;param="pValue":value', strict=True)
>>> c.parts()
('key', Parameters({'PARAM': 'pValue'}), 'value')
"""
def __new__(cls, st, strict=False):
self = str.__new__(cls, st)
setattr(self, 'strict', strict)
return self
def from_parts(parts):
"Turns a tuple of parts into a content line"
(name, params, values) = [str(p) for p in parts]
try:
if params:
return Contentline('%s;%s:%s' % (name, params, values))
return Contentline('%s:%s' % (name, values))
except:
raise ValueError(
'Property: %s Wrong values "%s" or "%s"' % (repr(name),
repr(params),
repr(values)))
from_parts = staticmethod(from_parts)
def parts(self):
""" Splits the content line up into (name, parameters, values) parts
"""
try:
name_split = None
value_split = None
inquotes = 0
for i in range(len(self)):
ch = self[i]
if not inquotes:
if ch in ':;' and not name_split:
name_split = i
if ch == ':' and not value_split:
value_split = i
if ch == '"':
inquotes = not inquotes
name = self[:name_split]
if not name:
raise ValueError, 'Key name is required'
validate_token(name)
if name_split+1 == value_split:
raise ValueError, 'Invalid content line'
params = Parameters.from_string(self[name_split+1:value_split],
strict=self.strict)
values = self[value_split+1:]
return (name, params, values)
except:
raise ValueError, 'Content line could not be parsed into parts'
def from_string(st, strict=False):
"Unfolds the content lines in an iCalendar into long content lines"
try:
# a fold is carriage return followed by either a space or a tab
return Contentline(FOLD.sub('', st), strict=strict)
except:
raise ValueError, 'Expected StringType with content line'
from_string = staticmethod(from_string)
def __str__(self):
"Long content lines are folded so they are less than 75 characters wide"
l_line = len(self)
new_lines = []
start = 0
end = 74
while True:
if end >= l_line:
end = l_line
else:
# Check that we don't fold in the middle of a UTF-8 character:
# http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html
while True:
char_value = ord(self[end])
if char_value < 128 or char_value >= 192:
# This is not in the middle of a UTF-8 character, so we
# can fold here:
break
else:
end -= 1
new_lines.append(self[start:end])
if end == l_line:
# Done
break
start = end
end = start + 74
return '\r\n '.join(new_lines)
class Contentlines(list):
"""
I assume that iCalendar files generally are a few kilobytes in size. Then
this should be efficient. for Huge files, an iterator should probably be
used instead.
>>> c = Contentlines([Contentline('BEGIN:VEVENT\\r\\n')])
>>> str(c)
'BEGIN:VEVENT\\r\\n'
Lets try appending it with a 100 charater wide string
>>> c.append(Contentline(''.join(['123456789 ']*10)+'\\r\\n'))
>>> str(c)
'BEGIN:VEVENT\\r\\n\\r\\n123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 \\r\\n'
Notice that there is an extra empty string in the end of the content lines.
That is so they can be easily joined with: '\r\n'.join(contentlines)).
>>> Contentlines.from_string('A short line\\r\\n')
['A short line', '']
>>> Contentlines.from_string('A faked\\r\\n long line\\r\\n')
['A faked long line', '']
>>> Contentlines.from_string('A faked\\r\\n long line\\r\\nAnd another lin\\r\\n\\te that is folded\\r\\n')
['A faked long line', 'And another line that is folded', '']
"""
def __str__(self):
"Simply join self."
return '\r\n'.join(map(str, self))
def from_string(st):
"Parses a string into content lines"
try:
# a fold is carriage return followed by either a space or a tab
unfolded = FOLD.sub('', st)
lines = [Contentline(line) for line in unfolded.splitlines() if line]
lines.append('') # we need a '\r\n' in the end of every content line
return Contentlines(lines)
except:
raise ValueError, 'Expected StringType with content lines'
from_string = staticmethod(from_string)
# ran this:
# sample = open('./samples/test.ics', 'rb').read() # binary file in windows!
# lines = Contentlines.from_string(sample)
# for line in lines[:-1]:
# print line.parts()
# got this:
#('BEGIN', Parameters({}), 'VCALENDAR')
#('METHOD', Parameters({}), 'Request')
#('PRODID', Parameters({}), '-//My product//mxm.dk/')
#('VERSION', Parameters({}), '2.0')
#('BEGIN', Parameters({}), 'VEVENT')
#('DESCRIPTION', Parameters({}), 'This is a very long description that ...')
#('PARTICIPANT', Parameters({'CN': 'Max M'}), 'MAILTO:maxm@mxm.dk')
#('DTEND', Parameters({}), '20050107T160000')
#('DTSTART', Parameters({}), '20050107T120000')
#('SUMMARY', Parameters({}), 'A second event')
#('END', Parameters({}), 'VEVENT')
#('BEGIN', Parameters({}), 'VEVENT')
#('DTEND', Parameters({}), '20050108T235900')
#('DTSTART', Parameters({}), '20050108T230000')
#('SUMMARY', Parameters({}), 'A single event')
#('UID', Parameters({}), '42')
#('END', Parameters({}), 'VEVENT')
#('END', Parameters({}), 'VCALENDAR')

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
# this is a package

View File

@ -1,17 +0,0 @@
# -*- Encoding: utf-8 -*-
import unittest, doctest, os
from icalendar import cal, caselessdict, parser, prop
def test_suite():
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(caselessdict))
suite.addTest(doctest.DocTestSuite(parser))
suite.addTest(doctest.DocTestSuite(prop))
suite.addTest(doctest.DocTestSuite(cal))
doc_dir = '../../../doc'
for docfile in ['example.txt', 'groupscheduled.txt',
'small.txt', 'multiple.txt', 'recurrence.txt']:
suite.addTest(doctest.DocFileSuite(os.path.join(doc_dir, docfile),
optionflags=doctest.ELLIPSIS),)
return suite

View File

@ -1,54 +0,0 @@
# -*- Encoding: utf-8 -*-
from string import ascii_letters, digits
import random
"""
This module contains non-essential tools for iCalendar. Pretty thin so far eh?
"""
class UIDGenerator:
"""
If you are too lazy to create real uid's. Notice, this doctest is disabled!
Automatic semi-random uid
>> g = UIDGenerator()
>> uid = g.uid()
>> uid.ical()
'20050109T153222-7ekDDHKcw46QlwZK@example.com'
You Should at least insert your own hostname to be more complient
>> g = UIDGenerator()
>> uid = g.uid('Example.ORG')
>> uid.ical()
'20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG'
You can also insert a path or similar
>> g = UIDGenerator()
>> uid = g.uid('Example.ORG', '/path/to/content')
>> uid.ical()
'20050109T153415-/path/to/content@Example.ORG'
"""
chars = list(ascii_letters + digits)
def rnd_string(self, length=16):
"Generates a string with random characters of length"
return ''.join([random.choice(self.chars) for i in range(length)])
def uid(self, host_name='example.com', unique=''):
"""
Generates a unique id consisting of:
datetime-uniquevalue@host. Like:
20050105T225746Z-HKtJMqUgdO0jDUwm@example.com
"""
from PropertyValues import vText, vDatetime
unique = unique or self.rnd_string()
return vText('%s-%s@%s' % (vDatetime.today().ical(), unique, host_name))
if __name__ == "__main__":
import os.path, doctest, tools
# import and test this file
doctest.testmod(tools)

View File

@ -1,51 +0,0 @@
# -*- Encoding: utf-8 -*-
from string import ascii_letters, digits
import random
"""
This module contains non-essential tools for iCalendar. Pretty thin so far eh?
"""
class UIDGenerator:
"""
If you are too lazy to create real uids.
NOTE: this doctest is disabled
(only two > instead of three)
Automatic semi-random uid
>> g = UIDGenerator()
>> uid = g.uid()
>> uid.ical()
'20050109T153222-7ekDDHKcw46QlwZK@example.com'
You should at least insert your own hostname to be more compliant
>> g = UIDGenerator()
>> uid = g.uid('Example.ORG')
>> uid.ical()
'20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG'
You can also insert a path or similar
>> g = UIDGenerator()
>> uid = g.uid('Example.ORG', '/path/to/content')
>> uid.ical()
'20050109T153415-/path/to/content@Example.ORG'
"""
chars = list(ascii_letters + digits)
def rnd_string(self, length=16):
"Generates a string with random characters of length"
return ''.join([random.choice(self.chars) for i in range(length)])
def uid(self, host_name='example.com', unique=''):
"""
Generates a unique id consisting of:
datetime-uniquevalue@host. Like:
20050105T225746Z-HKtJMqUgdO0jDUwm@example.com
"""
from PropertyValues import vText, vDatetime
unique = unique or self.rnd_string()
return vText('%s-%s@%s' % (vDatetime.today().ical(), unique, host_name))

View File

@ -1,324 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2005-2007 Dennis Kaarsemaker
# Copyright (c) 2008-2010 Terence Simpson
#
# 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 supybot.ircdb as ircdb
import supybot.conf as conf
def checkIgnored(hostmask, recipient='', users=ircdb.users, channels=ircdb.channels):
if ircdb.ignores.checkIgnored(hostmask):
return True
try:
id = ircdb.users.getUserId(hostmask)
user = users.getUser(id)
except KeyError:
# If there's no user...
if ircutils.isChannel(recipient):
channel = channels.getChannel(recipient)
if channel.checkIgnored(hostmask):
return True
else:
return False
else:
return False
if user._checkCapability('owner'):
# Owners shouldn't ever be ignored.
return False
elif user.ignore:
return True
elif recipient:
if ircutils.isChannel(recipient):
channel = ircdb.channels.getChannel(recipient)
if channel.checkIgnored(hostmask):
return True
else:
return False
else:
return False
else:
return False
import pytz
import ical
reload(ical)
import datetime, shelve, re
import cPickle as pickle
class Webcal(callbacks.Plugin):
"""@schedule <timezone>
display the schedule in your timezone
"""
threaded = True
noIgnore = True
def __init__(self, irc):
parent = super(Webcal, self)
parent.__init__(irc)
self.irc = irc
self.cache = {}
self.firstevent = {}
try:
schedule.removeEvent(self.name())
schedule.removeEvent(self.name() + 'b')
except AssertionError:
pass
except KeyError:
pass
try:
schedule.addPeriodicEvent(self.refresh_cache, 60 * 20, name=self.name())
schedule.addPeriodicEvent(self.autotopics, 60, name=self.name() + 'b')
except AssertionError:
pass
def die(self):
try:
schedule.removeEvent(self.name())
schedule.removeEvent(self.name() + 'b')
except AssertionError:
pass
self.cache.clear()
def reset(self):
self.cache.clear()
def update(self, url):
data = utils.web.getUrl(url)
parser = ical.ICalReader(data)
self.cache[url] = parser.events
def refresh_cache(self):
for c in self.irc.state.channels:
url = self.registryValue('url', c)
if url:
self.update(url)
def autotopics(self):
for c in self.irc.state.channels:
url = self.registryValue('url', c)
if url and self.registryValue('doTopic', c):
if url not in self.cache:
self.update(url)
events = self.filter(self.cache[url], c)
#if events[0].is_on() and self.firstevent[c].summary == events[0].summary:
# continue
newtopic = self.maketopic(c, template=self.registryValue('topic',c))
if newtopic.strip() != self.irc.state.getTopic(c).strip():
self.irc.queueMsg(ircmsgs.topic(c, newtopic))
def filter(self, events, channel):
now = datetime.datetime.now(pytz.UTC)
fword = self.registryValue('filter', channel)
ret = [x for x in events if fword.lower() in x.raw_data.lower() and x.seconds_ago() < 1800]
ret = [x for x in ret if self.filterChannel(x)]
ret.sort()
ret.sort()
return ret
def filterChannel(self, event):
desc = event['description']
where = u'#ubuntu-meeting'
if "Location\\:" in desc:
where = desc.split('<')[1].split()[-1]
if where[0] != u'#':
where = u'#ubuntu-meeting'
return where == u'#ubuntu-meeting'
def maketopic(self, c, tz=None, template='%s', num_events=6):
url = self.registryValue('url',c)
if not tz:
tz = 'UTC'
if url not in self.cache.keys():
self.update(url)
now = datetime.datetime.now(pytz.UTC)
events = self.filter(self.cache[url],c)[:num_events]
# events.sort()
preamble = ''
if not len(events):
return template % "No meetings scheduled"
# The standard slack of 30 minutes after the meeting will be an
# error if there are 2 conscutive meetings, so remove the first
# one in that case
if len(events) > 1 and events[1].startTime() < now:
events = events[1:]
ev0 = events[0]
if ev0.seconds_to_go() < 600:
preamble = 'Current meeting: %s ' % ev0.summary.replace('Meeting','').strip()
if num_events == 1:
return preamble + (template % '')
events = events[1:]
if num_events == 1:
ev = events[0]
return template % ('Next meeting: %s in %s' % (ev.summary.replace(' Meeting','').strip(), ev.time_to_go()))
events = [x.schedule(tz) for x in events]
return preamble + (template % ' | '.join(events))
# Now the commands
def topic(self, irc, msg, args):
"""No args
Updates the topics in the channel
"""
c = msg.args[0]
url = self.registryValue('url', c)
if not url or not self.registryValue('doTopic',channel=c):
return
self.update(url)
events = self.filter(self.cache[url], c)
if events and events[0].is_on():
irc.error("Won't update topic while a meeting is in progress")
return
newtopic = self.maketopic(c, template=self.registryValue('topic',c))
if not (newtopic.strip() == irc.state.getTopic(c).strip()):
irc.queueMsg(ircmsgs.topic(c, newtopic))
topic = wrap(topic, [('checkCapability', 'admin')])
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)
# Repeat, with spaces replaced by underscores
ud = ud.replace(' ','_')
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):
"""[<timezone>]
Retrieve the date/time of scheduled meetings in a specific timezone, defaults to UTC
"""
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:
c = self.registryValue('defaultChannel')
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:
irc.error('Unknown timezone: %s - Full list: %s' % (tz, self.registryValue('tzUrl') or 'Value not set'))
return
newtopic = self.maketopic(c,tz=tzs[0])
events = self.filter(self.cache[url], msg.args[0])
if events and events[0].is_on(): # FIXME channel filter
irc.error('Please don\'t use @schedule during a meeting')
irc.reply('Schedule for %s: %s' % (tzs[0], newtopic), private=True)
else:
irc.reply('Schedule for %s: %s' % (tzs[0], newtopic))
schedule = wrap(schedule, [additional('text')])
def now(self, irc, msg, args, tz):
"""[<timezone>]
Display the current time, <timezone> defaults to UTC
"""
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:
c = self.registryValue('defaultChannel')
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:
irc.error('Unknown timezone: %s - Full list: %s' % (tz, self.registryValue('tzUrl') or 'Value not set'))
return
now = datetime.datetime.now(pytz.UTC)
newtopic = self.maketopic(c,tz=tzs[0],num_events=1)
events = self.filter(self.cache[url], msg.args[0])
newtopic = 'Current time in %s: %s - %s' % \
(tzs[0], datetime.datetime.now(pytz.UTC).astimezone(pytz.timezone(tzs[0])).strftime("%B %d %Y, %H:%M:%S"), newtopic)
if events and events[0].is_on(): # Fixme -- channel filter
irc.error('Please don\'t use @schedule during a meeting')
irc.reply(newtopic, private=True)
else:
irc.reply(newtopic)
now = wrap(now, [additional('text')])
time = now
# Warn people that you manage the topic
def doTopic(self, irc, msg):
c = msg.args[0]
if not self.registryValue('doTopic', c):
return
url = self.registryValue('url', c)
irc.reply("The topic of %s is managed by me and filled with the contents of %s - please don't change manually" %
(msg.args[0],url), private=True)
def callPrecedence(self, irc):
before = []
for cb in irc.callbacks:
if cb.name() == 'IRCLogin':
before.append(cb)
return (before, [])
def inFilter(self, irc, msg):
if not msg.command == 'PRIVMSG':
return msg
if not conf.supybot.defaultIgnore():
return msg
s = callbacks.addressed(irc.nick, msg)
if not s:
return msg
if checkIgnored(msg.prefix):
return msg
try:
if ircdb.users.getUser(msg.prefix):
return msg
except:
pass
cmd, args = (s.split(None, 1) + [None])[:2]
if cmd and cmd[0] in str(conf.supybot.reply.whenAddressedBy.chars.get(msg.args[0])):
cmd = cmd[1:]
if cmd in self.listCommands():
tokens = callbacks.tokenize(s, channel=msg.args[0])
self.Proxy(irc, msg, tokens)
return msg
Class = Webcal

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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.
#
###
from dateutil import rrule
import re, datetime
#wk_days = ('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU')
wk_days = re.compile("([0-9]?)([M|T|W|F|S][O|U|E|H|R|A])")
rrule_map = {
'SECONDLY': rrule.SECONDLY,
'MINUTELY': rrule.MINUTELY,
'HOURLY': rrule.HOURLY,
'DAILY': rrule.DAILY,
'WEEKLY': rrule.WEEKLY,
'MONTHLY': rrule.MONTHLY,
'YEARLY': rrule.YEARLY,
'MO': rrule.MO,
'TU': rrule.TU,
'WE': rrule.WE,
'TH': rrule.TH,
'FR': rrule.FR,
'SA': rrule.SA,
'SU': rrule.SU }
def rrule_wrapper(*args, **kwargs):
for k, v in kwargs.iteritems():
if k == 'byday' or k == 'BYDAY':
del kwargs[k]
groups = wk_days.match(v[0]).groups()
if groups[0]:
kwargs['byweekday'] = rrule_map[groups[1]](int(groups[0]))
else:
kwargs['byweekday'] = rrule_map[groups[1]]
else:
del kwargs[k]
k = k.lower()
if isinstance(v, list):
if len(v) > 1:
res = []
for x in v:
if isinstance(x, basestring) and wk_days.match(x):
res.append(rrule_map[wk_days.match(x).group(1)])
elif v in rrule_map:
res.append(rrule_map[x])
elif isinstance(x, datetime.datetime):
res.append(datetime.datetime.fromordinal(x.toordinal()))
else:
res.append(v)
kwargs[k] = tuple(res)
else:
if isinstance(v[0], basestring) and wk_days.match(v[0]):
kwargs[k] = rrule_map[wk_days.match(v[0]).group(0)]
elif v[0] in rrule_map:
kwargs[k] = rrule_map[v[0]]
elif isinstance(v[0], datetime.datetime):
kwargs[k] = datetime.datetime.fromordinal(v[0].toordinal())
else:
kwargs[k] = v[0]
else:
kwargs[k] = v
return rrule.rrule(*args, **kwargs)

View File

@ -1,18 +0,0 @@
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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.
#
###
from supybot.test import *
class WebcalTestCase(PluginTestCase):
plugins = ('Webcal',)

View File

@ -1,29 +0,0 @@
#!/usr/bin/env python
# -*- Encoding: utf-8 -*-
###
# Copyright (c) 2008-2010 Terence Simpson
#
# 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 datetime, pytz, urllib2, ical
def filter(events):
ret = [x for x in events if x.seconds_ago() < 1800]
ret.sort()
ret.sort() # Needs this twice for some reason
return ret
data = urllib2.urlopen("http://tinyurl.com/6mzmbr").read()
parser = ical.ICalReader(data)
events = filter(parser.events)
print "\n".join([x.schedule() for x in events])

View File

@ -1,592 +0,0 @@
<html>
<head>
<title>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 Uboto. 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>

View File

@ -1 +0,0 @@
Bantracker/cgi/bans.cgi

View File

@ -1 +0,0 @@
Bantracker/cgi/bans.tmpl

View File

@ -1 +0,0 @@
Bantracker/cgi/empty.tmpl

View File

@ -1 +0,0 @@
Webcal/timezones.html