From 2c8bc43bf8a89feee10fa5fd965ccae1c101ea1b Mon Sep 17 00:00:00 2001 From: Krytarik Raido Date: Sat, 4 Dec 2021 04:04:04 +0100 Subject: [PATCH] Remove old, unported, and unused plugins. * Bantracker * IRCLogin * Lart * Mess * Webcal --- Bantracker/README.txt | 67 - Bantracker/__init__.py | 42 - Bantracker/cgi/banlog.css | 13 - Bantracker/cgi/banlog.js | 118 -- Bantracker/cgi/bans.cgi | 595 ------- Bantracker/cgi/bans.js | 78 - Bantracker/cgi/bans.tmpl | 23 - Bantracker/cgi/bantracker.conf | 8 - Bantracker/cgi/empty.tmpl | 2 - Bantracker/cgi/log.tmpl | 17 - Bantracker/config.py | 239 --- Bantracker/plugin.py | 1907 ---------------------- Bantracker/test.py | 672 -------- IRCLogin/README.txt | 9 - IRCLogin/__init__.py | 38 - IRCLogin/config.py | 46 - IRCLogin/plugin.py | 229 --- IRCLogin/test.py | 19 - Lart/Lart.flat.db | 58 - Lart/__init__.py | 62 - Lart/config.py | 65 - Lart/plugin.py | 168 -- Lart/test.py | 42 - Mess/42.txt | 50 - Mess/README.txt | 2 - Mess/__init__.py | 37 - Mess/ball.txt | 26 - Mess/bofh.txt | 453 ----- Mess/config.py | 56 - Mess/ferengi.txt | 50 - Mess/plugin.py | 273 ---- Mess/test.py | 19 - Webcal/README.txt | 5 - Webcal/__init__.py | 40 - Webcal/cal.ical | 182 --- Webcal/config.py | 62 - Webcal/ical.py | 296 ---- Webcal/ical.py.bak | 299 ---- Webcal/ical.py.bak.bac2 | 286 ---- Webcal/icalendar/__init__.py | 17 - Webcal/icalendar/cal.py | 534 ------ Webcal/icalendar/caselessdict.py | 93 -- Webcal/icalendar/interfaces.py | 263 --- Webcal/icalendar/parser.py | 522 ------ Webcal/icalendar/prop.py | 1513 ----------------- Webcal/icalendar/tests/__init__.py | 1 - Webcal/icalendar/tests/test_icalendar.py | 17 - Webcal/icalendar/tools.py | 54 - Webcal/icalendar/util.py | 51 - Webcal/plugin.py | 324 ---- Webcal/rruler.py | 76 - Webcal/test.py | 18 - Webcal/testical.py | 29 - Webcal/timezones.html | 592 ------- bans.cgi | 1 - bans.tmpl | 1 - empty.tmpl | 1 - timezones.html | 1 - 58 files changed, 10761 deletions(-) delete mode 100644 Bantracker/README.txt delete mode 100644 Bantracker/__init__.py delete mode 100644 Bantracker/cgi/banlog.css delete mode 100644 Bantracker/cgi/banlog.js delete mode 100755 Bantracker/cgi/bans.cgi delete mode 100644 Bantracker/cgi/bans.js delete mode 100644 Bantracker/cgi/bans.tmpl delete mode 100644 Bantracker/cgi/bantracker.conf delete mode 100644 Bantracker/cgi/empty.tmpl delete mode 100644 Bantracker/cgi/log.tmpl delete mode 100644 Bantracker/config.py delete mode 100644 Bantracker/plugin.py delete mode 100644 Bantracker/test.py delete mode 100644 IRCLogin/README.txt delete mode 100644 IRCLogin/__init__.py delete mode 100644 IRCLogin/config.py delete mode 100644 IRCLogin/plugin.py delete mode 100644 IRCLogin/test.py delete mode 100644 Lart/Lart.flat.db delete mode 100644 Lart/__init__.py delete mode 100644 Lart/config.py delete mode 100644 Lart/plugin.py delete mode 100644 Lart/test.py delete mode 100644 Mess/42.txt delete mode 100644 Mess/README.txt delete mode 100644 Mess/__init__.py delete mode 100644 Mess/ball.txt delete mode 100644 Mess/bofh.txt delete mode 100644 Mess/config.py delete mode 100644 Mess/ferengi.txt delete mode 100644 Mess/plugin.py delete mode 100644 Mess/test.py delete mode 100644 Webcal/README.txt delete mode 100644 Webcal/__init__.py delete mode 100644 Webcal/cal.ical delete mode 100644 Webcal/config.py delete mode 100644 Webcal/ical.py delete mode 100644 Webcal/ical.py.bak delete mode 100644 Webcal/ical.py.bak.bac2 delete mode 100644 Webcal/icalendar/__init__.py delete mode 100644 Webcal/icalendar/cal.py delete mode 100644 Webcal/icalendar/caselessdict.py delete mode 100644 Webcal/icalendar/interfaces.py delete mode 100644 Webcal/icalendar/parser.py delete mode 100644 Webcal/icalendar/prop.py delete mode 100644 Webcal/icalendar/tests/__init__.py delete mode 100644 Webcal/icalendar/tests/test_icalendar.py delete mode 100644 Webcal/icalendar/tools.py delete mode 100644 Webcal/icalendar/util.py delete mode 100644 Webcal/plugin.py delete mode 100644 Webcal/rruler.py delete mode 100644 Webcal/test.py delete mode 100644 Webcal/testical.py delete mode 100644 Webcal/timezones.html delete mode 120000 bans.cgi delete mode 120000 bans.tmpl delete mode 120000 empty.tmpl delete mode 120000 timezones.html diff --git a/Bantracker/README.txt b/Bantracker/README.txt deleted file mode 100644 index c515712..0000000 --- a/Bantracker/README.txt +++ /dev/null @@ -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 [] [] -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. - diff --git a/Bantracker/__init__.py b/Bantracker/__init__.py deleted file mode 100644 index 558d3b1..0000000 --- a/Bantracker/__init__.py +++ /dev/null @@ -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 - diff --git a/Bantracker/cgi/banlog.css b/Bantracker/cgi/banlog.css deleted file mode 100644 index 190979a..0000000 --- a/Bantracker/cgi/banlog.css +++ /dev/null @@ -1,13 +0,0 @@ -div.main { - text-align: left; - max-width: 500px; -} - -#hform > fieldset, #comment_form > fieldset { - max-width: 400px; -} - -.highlight { - background-color: yellow; -} - diff --git a/Bantracker/cgi/banlog.js b/Bantracker/cgi/banlog.js deleted file mode 100644 index fae19ce..0000000 --- a/Bantracker/cgi/banlog.js +++ /dev/null @@ -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, '&').replace(/>/g, '>').replace(/Failed to load the module commoncgi

" - print "

Check that the configured option PLUGIN_PATH is correct.

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

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

' % \ - (c[0],pickle.loads(c[2]).astimezone(tz).strftime("%b %d %Y %H:%M:%S")) - if user: - print """Add comment""" % b[6] - print """""" - print '
' -if bans: - print pagination - -t2 = time.time() - -print "" % (t2 - t1) - -# Aaaaaaaaaaaaaaaaand send! -send_page('bans.tmpl') diff --git a/Bantracker/cgi/bans.js b/Bantracker/cgi/bans.js deleted file mode 100644 index c6ee2c3..0000000 --- a/Bantracker/cgi/bans.js +++ /dev/null @@ -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(); - } -} diff --git a/Bantracker/cgi/bans.tmpl b/Bantracker/cgi/bans.tmpl deleted file mode 100644 index 71dbf49..0000000 --- a/Bantracker/cgi/bans.tmpl +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Ubottu bantracker - - - - - -
-

Ubottu Bantracker

-
- %e -
-
- %s -
-

©2006 Dennis Kaarsemaker
- Edited by Terence Simpson

-
- - diff --git a/Bantracker/cgi/bantracker.conf b/Bantracker/cgi/bantracker.conf deleted file mode 100644 index fd79118..0000000 --- a/Bantracker/cgi/bantracker.conf +++ /dev/null @@ -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 - diff --git a/Bantracker/cgi/empty.tmpl b/Bantracker/cgi/empty.tmpl deleted file mode 100644 index 22d0be1..0000000 --- a/Bantracker/cgi/empty.tmpl +++ /dev/null @@ -1,2 +0,0 @@ -%e -%s diff --git a/Bantracker/cgi/log.tmpl b/Bantracker/cgi/log.tmpl deleted file mode 100644 index 139a8b2..0000000 --- a/Bantracker/cgi/log.tmpl +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Ban log - - - - - -
-%e -
-%s - - - diff --git a/Bantracker/config.py b/Bantracker/config.py deleted file mode 100644 index 700f27c..0000000 --- a/Bantracker/config.py +++ /dev/null @@ -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.""")) - - - diff --git a/Bantracker/plugin.py b/Bantracker/plugin.py deleted file mode 100644 index f75bf43..0000000 --- a/Bantracker/plugin.py +++ /dev/null @@ -1,1907 +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. -# -### -### -# Based on the standard supybot logging plugin, which has the following -# copyright: -# -# Copyright (c) 2002-2004, Jeremiah Fincher -# 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.commands import * -import supybot.ircutils as ircutils -import supybot.callbacks as callbacks -import supybot.ircmsgs as ircmsgs -import supybot.conf as conf -import supybot.ircdb as ircdb -import supybot.schedule as schedule -import supybot.utils as utils -from supybot.utils.str import format as Format -from fnmatch import fnmatch -from collections import defaultdict -import sqlite -import pytz -import cPickle -import datetime -import csv -import time -import random -import hashlib -import threading - -isUserHostmask = ircutils.isUserHostmask - -tz = 'UTC' - -def now(): - return cPickle.dumps(datetime.datetime.now(pytz.timezone(tz))) - -def nowSeconds(): - # apparently time.time() isn't the same thing. - # return time.time() - return int(time.mktime(time.gmtime())) - -def fromTime(x): - return cPickle.dumps(datetime.datetime(*time.gmtime(x)[:6], **{'tzinfo': pytz.timezone("UTC")})) - - -class FuzzyDict(dict): - def __getitem__(self, k): - try: - return dict.__getitem__(self, k) - except KeyError: - # ok, lets find the closest match - n = len(k) - keys = [ s for s in self if s[:n] == k ] - if len(keys) != 1: - # ambiguous - raise - return dict.__getitem__(self, keys[0]) - -timeUnits = FuzzyDict({ - 'seconds': 1, - 'minutes': 60, 'm': 60, - 'hours' : 3600, 'M': 2592000, - 'days' : 86400, - 'weeks' : 604800, - 'months' : 2592000, - 'years' : 31536000, - }) - -def readTimeDelta(s): - """convert a string like "2 days" or "1h2d3w" into seconds""" - # split number and words - if not s: - raise ValueError(s) - - digit = string = number = None - seconds = 0 - for c in s: - if c == ' ': - continue - - if c in '+-0123456789': - if string is None: - # start - digit, string = True, '' - elif digit is False: - digit = True - # completed an unit, add to seconds - string = string.strip() - if string: - try: - unit = timeUnits[string] - except KeyError: - raise ValueError(string) - seconds += number * unit - string = '' - string += c - else: - if digit is None: - # need a number first - raise ValueError(s) - if digit is True: - digit = False - # completed a number - number, string = int(string), '' - string += c - - # check last string - if string is None: - raise ValueError(s) - - try: - seconds += int(string) - except ValueError: - string = string.strip() - if string: - try: - unit = timeUnits[string] - except KeyError: - raise ValueError(string) - seconds += number * unit - - return seconds - -# utils.gen.timeElapsed is too noisy, what do I care of the seconds and minutes -# if the period is like a month long, or the zero values? -def timeElapsed(elapsed, short=False, resolution=2): - """Given seconds, returns a string with an English description of - the amount of time passed. - """ - - ret = [] - before = False - def Format(s, i): - if i: - if short: - ret.append('%s%s' % (i, s[0])) - else: - ret.append(utils.str.format('%n', (i, s))) - elapsed = int(elapsed) - - # Handle negative times - if elapsed < 0: - before = True - elapsed = -elapsed - - for s, i in (('year', 31536000), ('month', 2592000), ('week', 604800), - ('day', 86400), ('hour', 3600), ('minute', 60)): - count, elapsed = elapsed // i, elapsed % i - Format(s, count) - if len(ret) == resolution: - break - #Format('second', elapsed) # seconds are pointless for now - if not ret: - raise ValueError, 'Time difference not great enough to be noted.' - result = '' - #ret = ret[:resolution] - if short: - result = ' '.join(ret) - else: - result = utils.str.format('%L', ret) - if before: - result += ' ago' - return result - -def splitID(s): - """get a list of integers from a comma separated list of numbers""" - for id in s.split(','): - if id.isdigit(): - id = int(id) - if id > 0: - yield id - -def capab(user, capability): - capability = capability.lower() - capabilities = list(user.capabilities) - # Capability hierarchy # - if capability == "bantracker": - if capab(user, "admin"): - return True - if capability == "admin": - if capab(user, "owner"): - return True - # End # - if capability in capabilities: - return True - else: - return False - -def hostmaskPatternEqual(pattern, hostmask): - if pattern.count('!') != 1 or pattern.count('@') != 1: - return False - if pattern.count('$') == 1: - pattern = pattern.split('$',1)[0] - if pattern.startswith('%'): - pattern = pattern[1:] - return ircutils.hostmaskPatternEqual(pattern, hostmask) - -def nickMatch(nick, pattern): - """Checks if a given nick matches a pattern or in a list of patterns.""" - if isinstance(pattern, str): - pattern = [pattern] - nick = nick.lower() - for s in pattern: - if fnmatch(nick, s.lower()): - return True - return False - -def dequeue(parent, irc): - global queue - queue.dequeue(parent, irc) - -def supported(irc, mode): - chanmodes = irc.state.supported.get('chanmodes', '') - return mode in chanmodes.split(',')[0] - -class MsgQueue(object): - def __init__(self): - self.msgcache = [] - def queue(self, msg): - if msg not in self.msgcache: - self.msgcache.append(msg) - def clear(self): - self.msgcache = [] - def dequeue(self, parent, irc): - parent.thread_timer.cancel() - parent.thread_timer = threading.Timer(10.0, dequeue, args=(parent, irc)) - if len(self.msgcache): - msg = self.msgcache.pop(0) - irc.queueMsg(msg) - parent.thread_timer.start() - -queue = MsgQueue() - - -class Ban(object): - """Hold my bans""" - def __init__(self, args=None, **kwargs): - self.id = None - if args: - # in most ircd: args = (nick, channel, mask, who, when) - self.channel = args[1] - self.mask = args[2] - self.who = args[3] - self.when = float(args[4]) - else: - self.channel = kwargs['channel'] - self.mask = kwargs['mask'] - self.who = kwargs['who'] - self.when = float(kwargs['when']) - if 'id' in kwargs: - self.id = kwargs['id'] - self.ascwhen = time.asctime(time.gmtime(self.when)) - - def __tuple__(self): - return (self.mask, self.who, self.ascwhen) - - def __iter__(self): - return self.__tuple__().__iter__() - - def __str__(self): - return "%s by %s on %s" % tuple(self) - - def __repr__(self): - return '<%s object "%s" at 0x%x>' % (self.__class__.__name__, self, id(self)) - - def __eq__(self, ban): - return self.mask == ban.mask - - def __ne__(self, ban): - return not self.__eq__(ban) - - def op(self): - return self.mask.split('!')[0] - - def time(self): - return datetime.datetime.fromtimestamp(self.when) - - @property - def type(self): - return guessBanType(self.mask) - - def serialize(self): - id = self.id - if id is None: - id = '' - return (id, self.channel, self.mask, self.who, self.when) - - def deserialize(self, L): - id = L[0] - if id == '': - id = None - else: - id = int(id) - self.id = id - self.channel, self.mask, self.who = L[1:4] - self.when = float(L[4]) - self.ascwhen = time.asctime(time.gmtime(self.when)) - - -def guessBanType(mask): - if mask[0] == '%': - return 'quiet' - elif ircutils.isUserHostmask(mask) \ - or mask[0] == '$' \ - or mask.endswith('(realname)'): - if not ('*' in mask or \ - '?' in mask or \ - '$' in mask or \ - ' ' in mask): - # XXX hack over hack, we are supposing these are marks as normal - # bans aren't usually set to exact match, while marks are. - return 'mark' - return 'ban' - return 'removal' - - -class ReviewStore(dict): - def __init__(self, filename): - self.filename = conf.supybot.directories.data.dirize(filename) - self.lastReview = 0 - - def __getitem__(self, k): - try: - return dict.__getitem__(self, k) - except KeyError: - self[k] = L = [] - return L - - def open(self): - import csv - try: - reader = csv.reader(open(self.filename, 'rb')) - except IOError: - return - self.lastReview = int(reader.next()[1]) - for row in reader: - host, value = self.deserialize(*row) - try: - L = self[host] - if value not in L: - L.append(value) - except KeyError: - self[host] = [value] - - def close(self): - import csv - try: - writer = csv.writer(open(self.filename, 'wb')) - except IOError: - return - writer.writerow(('time', str(int(self.lastReview)))) - for host, values in self.iteritems(): - for v in values: - writer.writerow(self.serialize(host, v)) - - def deserialize(self, host, nick, command, channel, text): - if command == 'PRIVMSG': - msg = ircmsgs.privmsg(channel, text) - elif command == 'NOTICE': - msg = ircmsgs.notice(channel, text) - else: - return - return (host, (nick, msg)) - - def serialize(self, host, value): - nick, msg = value - command, channel, text = msg.command, msg.args[0], msg.args[1] - return (host, nick, command, channel, text) - - -class BanRemoval(object): - """This object saves information about a ban that should be removed when expires""" - def __init__(self, ban, expires): - """ - ban: ban object - expires: time in seconds for it to expire - """ - self.ban = ban - self.expires = expires - self.notified = False - - def __getattr__(self, attr): - return getattr(self.ban, attr) - - def timeLeft(self): - return (self.when + self.expires) - nowSeconds() - - def expired(self, offset=0): - """Check if the ban did expire.""" - if (nowSeconds() + offset) > (self.when + self.expires): - return True - return False - - def serialize(self): - notified = self.notified and 1 or 0 - L = [ self.expires, notified ] - L.extend(self.ban.serialize()) - return tuple(L) - - def deserialize(self, L): - self.expires = int(L[0]) - self.notified = bool(int(L[1])) - self.ban = Ban(args=(None, None, None, None, 0)) - self.ban.deserialize(L[2:]) - -def enumerateReversed(L): - """enumerate in reverse order""" - for i in reversed(xrange(len(L))): - yield i, L[i] - -class BanStore(object): - def __init__(self, filename): - self.filename = conf.supybot.directories.data.dirize(filename) - self.shelf = [] - - def __iter__(self): - return iter(self.shelf) - - def __len__(self): - return len(self.shelf) - - def open(self): - try: - reader = csv.reader(open(self.filename, 'rb')) - except IOError: - return - - for row in reader: - ban = BanRemoval(None, None) - ban.deserialize(row) - self.add(ban) - - def close(self): - try: - writer = csv.writer(open(self.filename, 'wb')) - except IOError: - return - - for ban in self: - writer.writerow(ban.serialize()) - - def add(self, obj): - self.shelf.append(obj) - - def sort(self): - """Sort bans by expire date""" - def key(x): - return x.when + x.expires - - self.shelf.sort(key=key, reverse=True) - - def popExpired(self, time=0): - """Pops a list of expired bans""" - L = [] - for i, ban in enumerateReversed(self.shelf): - if ban.expired(offset=time): - L.append(ban) - del self.shelf[i] - return L - - def getExpired(self, time=0): - def generator(): - for ban in self.shelf: - if ban.expired(offset=time): - yield ban - return generator() - -# opStatus stores in which channels are we currently opped. We define it here -# in a try-except block so it survives if the plugin is reloaded. -try: - opStatus -except: - opStatus = defaultdict(lambda: False) - -class Bantracker(callbacks.Plugin): - """Plugin to manage bans. - See '@list Bantracker' and '@help ' for commands""" - noIgnore = True - threaded = True - - def __init__(self, irc): - self.__parent = super(Bantracker, self) - self.__parent.__init__(irc) - self.default_irc = irc - self.lastMsgs = {} - self.lastStates = {} - self.replies = {} - self.logs = ircutils.IrcDict() - self.nicks = {} - self.hosts = {} - self.bans = ircutils.IrcDict() - self.opped = opStatus - self.pendingBanremoval = {} - - self.thread_timer = threading.Timer(10.0, dequeue, args=(self,irc)) - self.thread_timer.start() - - db = self.registryValue('database') - if db: - self.db = sqlite.connect(db) - else: - self.db = None - self.get_bans(irc) - self.get_bans(irc, mode='q') - self.get_nicks(irc) - - # init review stuff - self.pendingReviews = ReviewStore('bt.reviews.db') - self.pendingReviews.open() - self._banreviewfix() - - # init autoremove stuff - self.managedBans = BanStore('bt.autoremove.db') - self.managedBans.open() - - # add our scheduled events for check bans for reviews or removal - schedule.addPeriodicEvent(lambda: self.reviewBans(irc), 60*60, - 'Bantracker_review') - schedule.addPeriodicEvent(lambda: self.autoRemoveBans(irc), 600, - 'Bantracker_autoremove') - - def get_nicks(self, irc): - self.hosts.clear() - for (channel, c) in irc.state.channels.iteritems(): - if not self.registryValue('enabled', channel): - continue - for nick in list(c.users): - nick = nick.lower() - if not nick in self.nicks: - host = self.nick_to_host(irc, nick, False).lower() - self.nicks[nick] = host - host = host.split('@', 1)[1] - if '*' not in host: - if host not in self.hosts: - self.hosts[host] = [] - self.hosts[host].append(nick) - - def get_bans(self, irc, channel=None, mode='b'): - global queue - - if not supported(irc, mode): - return - - def fetch(channel): - if not self.registryValue('enabled', channel): - return - - if channel not in self.bans: - self.bans[channel] = [] - queue.queue(ircmsgs.mode(channel, mode)) - - if not channel: - for channel in irc.state.channels.keys(): - fetch(channel) - else: - fetch(channel) - - def sendWhois(self, irc, nick, do_reply=False, *args): - nick = nick.lower() - irc.queueMsg(ircmsgs.whois(nick, nick)) - if do_reply: - self.replies[nick] = [args[0], args[1:]] - - def do311(self, irc, msg): - """/whois""" - nick = msg.args[1].lower() - mask = "%s!%s@%s" % (nick, msg.args[2].lower(), msg.args[3].lower()) - self.nicks[nick] = mask - if nick in self.replies: - f = getattr(self, "%s_real" % self.replies[nick][0]) - args = self.replies[nick][1] - del self.replies[nick] - kwargs={'from_reply': True, 'reply': "%s!%s@%s" % (msg.args[1], msg.args[2], msg.args[3])} - f(*args, **kwargs) - - def do314(self, irc, msg): - """/whowas""" - nick = msg.args[1].lower() - mask = "%s!%s@%s" % (nick, msg.args[2].lower(), msg.args[3].lower()) - if not nick in self.nicks: - self.nicks[nick] = mask - if nick in self.replies: - f = getattr(self, "%s_real" % self.replies[nick][0]) - args = self.replies[nick][1] - del self.replies[nick] - kwargs={'from_reply': True, 'reply': "%s!%s@%s" % (msg.args[1], msg.args[2], msg.args[3])} - f(*args, **kwargs) - - def do401(self, irc, msg): - """/whois faild""" - irc.queueMsg(ircmsgs.IrcMsg(prefix="", command='WHOWAS', args=(msg.args[1],), msg=msg)) - - def do406(self, irc, msg): - """/whowas faild""" - nick = msg.args[1].lower() - if nick in self.replies: - f = getattr(self, "%s_real" % self.replies[nick][0]) - args = self.replies[nick][1] - del self.replies[nick] - kwargs = {'from_reply': True, 'reply': None} - f(*args, **kwargs) - - def do367(self, irc, msg, quiet=False): - """Got ban""" - channel = msg.args[1] - try: - bans = self.bans[channel] - except KeyError: - bans = self.bans[channel] = [] - if quiet: - # args = (nick, channel, mode, mask, who, when) - args = list(msg.args) - del args[2] # drop the 'q' bit - args[2] = '%' + args[2] - ban = Ban(args) - else: - ban = Ban(msg.args) - if ban not in bans: - bans.append(ban) - - def do368(self, irc, msg): - """End of channel ban list.""" - channel = msg.args[1] - try: - bans = self.bans[channel] - bans.sort(key=lambda x: x.when) # needed for self.reviewBans - except KeyError: - pass - - def do728(self, irc, msg): - """Got quiet""" - if supported(irc, 'q'): - self.do367(irc, msg, quiet=True) - - # End of channel quiet list. - def do729(self, irc, msg): - if supported(irc, 'q'): - self.do368(irc, msg) - - def nick_to_host(self, irc=None, target='', with_nick=True, reply_now=True): - target = target.lower() - if ircutils.isUserHostmask(target): - return target - elif target in self.nicks: - return self.nicks[target] - elif irc: - try: - return irc.state.nickToHostmask(target) - except: - if reply_now: - if with_nick: - return "%s!*@*" % target - return "*@*" - return - - if target in self.nicks: - return self.nicks[target] - else: - return "%s!*@*" % target - - def die(self): - global queue - if self.db: - try: - self.db.close() - except: - pass - try: - self.thread_timer.cancel() - except: - pass - queue.clear() - schedule.removeEvent(self.name() + '_review') - schedule.removeEvent(self.name() + '_autoremove') - self.pendingReviews.close() - self.managedBans.close() - - def reset(self): - global queue - if self.db: - try: - self.db.close() - except: - pass - queue.clear() -# self.logs.clear() - self.lastMsgs.clear() - self.lastStates.clear() -# self.nicks.clear() - - def __call__(self, irc, msg): - try: - super(self.__class__, self).__call__(irc, msg) - if irc in self.lastMsgs: - if irc not in self.lastStates: - self.lastStates[irc] = irc.state.copy() - self.lastStates[irc].addMsg(irc, self.lastMsgs[irc]) - finally: - self.lastMsgs[irc] = msg - - def db_run(self, query, parms, expect_result = False, expect_id = False, retry = True): - if not self.db or self.db.closed: - db = self.registryValue('database') - if db: - try: - self.db = sqlite.connect(db) - except: - self.log.error("Bantracker: failed to connect to database") - return - else: - self.log.error("Bantracker: no database") - return - - count = 0 - maxCount = 5 #TODO: Make this configurable? - err = None - - while count < maxCount: - try: - cur = self.db.cursor() - cur.execute(query, parms) - break - except Exception, err: - count += 1 - - if count == maxCount: - self.log.error("Bantracker: Error while trying to access the Bantracker database (%s(%s)).", type(err).__name__, str(err)) - try: - self.db.close() - except: - pass - self.db = None # force reconnection to database - - if not retry: # We probably failed twice, so bigger issues than database locking - return None - return self.db_run(query, parms, expect_result, expect_id, False) # Try again - - data = None - if expect_result and cur: data = cur.fetchall() - if expect_id: data = self.db.insert_id() - self.db.commit() - return data - - def requestComment(self, irc, channel, ban): - if not ban or not self.registryValue('request', channel): - return - # check the type of the action taken - mask = ban.mask - type = ban.type - if type == 'quiet': - mask = mask[1:] - # check if type is enabled - if type not in self.registryValue('request.type', channel): - return - prefix = conf.supybot.reply.whenAddressedBy.chars()[0] # prefix char for commands - # check to who send the request - try: - nick = ircutils.nickFromHostmask(ban.who) - except: - nick = ban.who - if nickMatch(nick, self.registryValue('request.ignore', channel)): - return - if nickMatch(nick, self.registryValue('request.forward', channel)): - # somebody else should comment this (like with bans set by bots) - s = "Please somebody comment on the %s of %s in %s done by %s, use:"\ - " %scomment %s " %(type, mask, channel, nick, prefix, ban.id) - self._sendForward(irc, s, 'request', channel) - else: - # send to operator - s = "Please comment on the %s of %s in %s, use: %scomment %s " \ - %(type, mask, channel, prefix, ban.id) - irc.reply(s, to=nick, private=True) - - def reviewBans(self, irc=None): - reviewTime = int(self.registryValue('review.when') * 86400) - if not reviewTime: - # time is zero, do nothing - return - - now = nowSeconds() - lastReview = self.pendingReviews.lastReview - self.pendingReviews.lastReview = now # update last time reviewed - if not lastReview: - # initialize last time reviewed timestamp - lastReview = now - reviewTime - - for channel, bans in self.bans.iteritems(): - if not self.registryValue('enabled', channel) \ - or not self.registryValue('review', channel): - continue - - for ban in bans: - # XXX this shouldn't be hardcoded, but I'm starting to hate this plugin, - # the less I touch it the better. - if ban.mask.endswith('$#ubuntu-read-topic'): - continue - - type = ban.type - if type in ('removal', 'mark'): - # skip kicks and marks - continue - - # skip bans have a duration set - skipBan = False - for mban in self.managedBans: - if ban.id == mban.id: - if mban.expires: - skipBan = True - break - - banAge = now - ban.when - reviewWindow = lastReview - ban.when - #self.log.debug('review ban: %s ban %s by %s (%s/%s/%s %s)', - # channel, ban.mask, ban.who, reviewWindow, reviewTime, - # banAge, reviewTime - reviewWindow) - skipBan = skipBan or banAge < reviewTime - if skipBan: - # since we made sure bans are sorted by time, the bans left are more recent - break - elif reviewWindow <= reviewTime < banAge: - # ban is old enough, and inside the "review window" - try: - # ban.who should be a user hostmask - nick = ircutils.nickFromHostmask(ban.who) - host = ircutils.hostFromHostmask(ban.who) - except: - if ircutils.isNick(ban.who, strictRfc=True): - # ok, op's nick, use it - nick = ban.who - host = None - else: - # probably a ban restored by IRC server in a netsplit - # XXX see if something can be done about this - continue - if nickMatch(nick, self.registryValue('review.ignore', channel)): - # in the ignore list - continue - if not ban.id: - ban.id = self.get_banId(ban.mask, channel) - mask = ban.mask - if type == 'quiet': - mask = mask[1:] - if nickMatch(nick, self.registryValue('review.forward', channel)): - s = "Review: %s '%s' set by %s on %s in %s, link: %s/bans.cgi?log=%s" \ - % (type, - mask, - nick, - ban.ascwhen, - channel, - self.registryValue('bansite'), - ban.id) - self._sendForward(irc, s, 'review', channel) - else: - s = "Review: %s '%s' set on %s in %s, link: %s/bans.cgi?log=%s" \ - % (type, - mask, - ban.ascwhen, - channel, - self.registryValue('bansite'), - ban.id) - msg = ircmsgs.privmsg(nick, s) - if (nick, msg) not in self.pendingReviews[host]: - self.pendingReviews[host].append((nick, msg)) - - def _sendForward(self, irc, s, setting, channel=None): - if not irc: - return - for chan in self.registryValue('%s.forward.channels' % setting, channel=channel): - msg = ircmsgs.notice(chan, s) - irc.queueMsg(msg) - - def _banreviewfix(self): - # FIXME workaround until proper fix is done. - bag = set() - nodups = set() - for host, reviews in self.pendingReviews.iteritems(): - for nick, msg in reviews: - if nick == 'Automated-Addition': - continue - chan, m = msg.args[0], msg.args[1] - s = m.rpartition(' ')[0] #remove the url - if (nick, chan, s) not in bag: - bag.add((nick, chan, s)) - nodups.add((host, nick, msg)) - - self.pendingReviews.clear() - - for host, nick, msg in nodups: - self.pendingReviews[host].append((nick, msg)) - - def _sendReviews(self, irc, msg): - host = ircutils.hostFromHostmask(msg.prefix) - if host in self.pendingReviews: - self._banreviewfix() - for nick, m in self.pendingReviews[host]: - if msg.nick != nick and not irc.isChannel(nick): # I'm a bit extra careful here - # correct nick in msg - m = ircmsgs.privmsg(msg.nick, m.args[1]) - irc.queueMsg(m) - del self.pendingReviews[host] - # check if we have any reviews by nick to send - if None in self.pendingReviews: - L = self.pendingReviews[None] - for i, v in enumerate(L): - nick, m = v - if ircutils.strEqual(msg.nick, nick): - irc.queueMsg(m) - del L[i] - if not L: - del self.pendingReviews[None] - - def getOp(self, irc, channel): - msg = ircmsgs.privmsg('Chanserv', "op %s %s" % (channel, irc.nick)) - irc.queueMsg(msg) - schedule.addEvent(lambda: self._getOpFail(irc, channel), time.time() + 60, - 'Bantracker_getop_%s' % channel) - - def _getOpFail(self, irc, channel): - for c in self.registryValue('autoremove.notify.channels', channel): - notice = ircmsgs.notice(c, "Failed to get op in %s" % channel) - irc.queueMsg(notice) - - def _getOpOK(self, channel): - try: - schedule.removeEvent('Bantracker_getop_%s' % channel) - return True - except KeyError: - return False - - def removeBans(self, irc, channel, modes, deop=False): - # send unban messages, with 4 modes max each. - maxModes = 4 - if deop: - modes.append(('-o', irc.nick)) - for i in range(len(modes) / maxModes + 1): - L = modes[i * maxModes : (i + 1) * maxModes] - if L: - msg = ircmsgs.mode(channel, ircutils.joinModes(L)) - irc.queueMsg(msg) - - def autoRemoveBans(self, irc): - modedict = { 'quiet': '-q', 'ban': '-b' } - unbandict = defaultdict(list) - for ban in self.managedBans.popExpired(): - channel, mask, type = ban.channel, ban.mask, ban.type - if not self.registryValue('autoremove', channel): - continue - - if type == 'quiet': - mask = mask[1:] - self.log.info("%s [%s] %s in %s expired", type, - ban.id, - mask, - channel) - unbandict[channel].append((modedict[type], mask)) - for channel, modes in unbandict.iteritems(): - if not self.opped[channel]: - self.pendingBanremoval[channel] = modes - self.getOp(irc, channel) - else: - self.removeBans(irc, channel, modes) - - # notify about bans soon to expire - for ban in self.managedBans.getExpired(600): - if ban.notified: - continue - - channel = ban.channel - if not self.registryValue('autoremove', channel) \ - or not self.registryValue('autoremove.notify', channel): - continue - - type, mask = ban.type, ban.mask - if type == 'quiet': - mask = mask[1:] - for c in self.registryValue('autoremove.notify.channels', channel): - notice = ircmsgs.notice(c, "%s %s%s%s %s in %s will expire in a few minutes." \ - % (type, - ircutils.mircColor('[', 'light green'), - ircutils.bold(ban.id), - ircutils.mircColor(']', 'light green'), - ircutils.mircColor(mask, 'teal'), - ircutils.mircColor(channel, 'teal'))) - irc.queueMsg(notice) - ban.notified = True - - def doLog(self, irc, channel, s): - if not self.registryValue('enabled', channel): - return - channel = ircutils.toLower(channel) - if channel not in self.logs.keys(): - self.logs[channel] = [] - format = conf.supybot.log.timestampFormat() - if format: - s = time.strftime(format, time.gmtime()) + " " + ircutils.stripFormatting(s) - self.logs[channel] = self.logs[channel][-199:] + [s.strip()] - - def doKickban(self, irc, channel, *args, **kwargs): - ban = self._doKickban(irc, channel, *args, **kwargs) - self.requestComment(irc, channel, ban) - return ban - - def _doKickban(self, irc, channel, operator, target, kickmsg = None, use_time = None, - extra_comment = None, add_to_cache = True): - if not self.registryValue('enabled', channel): - return - n = now() - if use_time: - n = fromTime(use_time) - try: - nick = ircutils.nickFromHostmask(operator) - except: - nick = operator - id = self.db_run("INSERT INTO bans (channel, mask, operator, time, log) values(%s, %s, %s, %s, %s)", - (channel, target, nick, n, '\n'.join(self.logs[channel])), expect_id=True) - if kickmsg and id and not (kickmsg == nick): - self.db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, kickmsg, n)) - if extra_comment: - self.db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, extra_comment, n)) - ban = Ban(mask=target, who=operator, when=time.mktime(time.gmtime()), id=id, channel=channel) - if add_to_cache: - if channel not in self.bans: - self.bans[channel] = [] - self.bans[channel].append(ban) - return ban - - def doUnban(self, irc, channel, nick, mask, id = None): - if id is None and not self.registryValue('enabled', channel): - return - if id is None: - data = self.db_run("SELECT MAX(id) FROM bans where channel=%s and mask=%s", (channel, mask), expect_result=True) - else: - data = [[id]] - if data and len(data) and not (data[0][0] == None): - self.db_run("UPDATE bans SET removal=%s , removal_op=%s WHERE id=%s", (now(), nick, int(data[0][0]))) - if not channel in self.bans: - self.bans[channel] = [] - for idx, ban in enumerateReversed(self.bans[channel]): - if ban.mask == mask: - del self.bans[channel][idx] - # we don't break here because bans might be duplicated. - for idx, br in enumerateReversed(self.managedBans.shelf): - if (channel == br.ban.channel) and (mask == br.ban.mask): - del self.managedBans.shelf[idx] - - def doPrivmsg(self, irc, msg): - (recipients, text) = msg.args - for channel in recipients.split(','): - if irc.isChannel(channel): - nick = msg.nick or irc.nick - if ircmsgs.isAction(msg): - self.doLog(irc, channel, - '* %s %s\n' % (nick, ircmsgs.unAction(msg))) - else: - self.doLog(irc, channel, '<%s> %s\n' % (nick, text)) - self._sendReviews(irc, msg) - - def doNotice(self, irc, msg): - (recipients, text) = msg.args - for channel in recipients.split(','): - if irc.isChannel(channel): - self.doLog(irc, channel, '-%s- %s\n' % (msg.nick, text)) - - def doNick(self, irc, msg): - oldNick = msg.nick - newNick = msg.args[0] - for (channel, c) in irc.state.channels.iteritems(): - if newNick in c.users: - self.doLog(irc, channel, - '*** %s is now known as %s\n' % (oldNick, newNick)) - if oldNick.lower() in self.nicks: - del self.nicks[oldNick.lower()] - nick = newNick.lower() - hostmask = nick + "!".join(msg.prefix.lower().split('!')[1:]) - self.nicks[nick] = hostmask - - def doJoin(self, irc, msg): - global queue - for channel in msg.args[0].split(','): - if msg.nick: - self.doLog(irc, channel, - '*** %s (%s) has joined %s\n' % (msg.nick, msg.prefix.split('!', 1)[1], channel)) - else: - self.doLog(irc, channel, - '*** %s has joined %s\n' % (msg.prefix, channel)) - if msg.nick == irc.nick: - if channel in self.opped: - del self.opped[channel] - if channel in self.bans: - del self.bans[channel] - self.get_bans(irc, channel) - self.get_bans(irc, channel, 'q') - nick = msg.nick.lower() or msg.prefix.lower().split('!', 1)[0] - self.nicks[nick] = msg.prefix.lower() - - def doKick(self, irc, msg): - if len(msg.args) == 3: - (channel, target, kickmsg) = msg.args - else: - (channel, target) = msg.args - kickmsg = '' - host = self.nick_to_host(irc, target, True) - if host == "%s!*@*" % host: - host = None - if kickmsg: - self.doLog(irc, channel, - '*** %s was kicked by %s (%s)\n' % (target, msg.nick, kickmsg)) - else: - self.doLog(irc, channel, - '*** %s was kicked by %s\n' % (target, msg.nick)) - self.doKickban(irc, channel, msg.prefix, target, kickmsg, extra_comment=host) - - def doPart(self, irc, msg): - for channel in msg.args[0].split(','): - self.doLog(irc, channel, '*** %s (%s) has left %s (%s)\n' % (msg.nick, msg.prefix, channel, len(msg.args) > 1 and msg.args[1] or '')) - if len(msg.args) > 1 and msg.args[1].startswith('requested by'): - args = msg.args[1].split() - self.doKickban(irc, channel, args[2], msg.nick, ' '.join(args[3:]).strip(), extra_comment=msg.prefix) - - def doMode(self, irc, msg): - channel = msg.args[0] - if irc.isChannel(channel) and msg.args[1:]: - self.doLog(irc, channel, - '*** %s sets mode: %s %s\n' % - (msg.nick or msg.prefix, msg.args[1], - ' '.join(msg.args[2:]))) - modes = ircutils.separateModes(msg.args[1:]) - for param in modes: - mode = param[0] - # op stuff - if mode[1] == "o": - if ircutils.nickEqual(irc.nick, param[1]): - opped = self.opped[channel] = mode[0] == '+' - if opped == True: - opped_ok = self._getOpOK(channel) - # check if we have bans to remove - if channel in self.pendingBanremoval: - modes = self.pendingBanremoval.pop(channel) - self.removeBans(irc, channel, modes, deop=opped_ok) - continue - - # channel mask stuff - mask = '' - comment = None - if mode[1] not in "bq": - continue - - mask = param[1] - if mode[1] == 'q': - mask = '%' + mask - - if mode[0] == '+': - comment = self.getHostFromBan(irc, msg, mask) - ban = self.doKickban(irc, channel, msg.prefix, mask, - extra_comment=comment) - elif mode[0] == '-': - self.doUnban(irc,channel, msg.nick, mask) - - def getHostFromBan(self, irc, msg, mask): - if irc not in self.lastStates: - self.lastStates[irc] = irc.state.copy() - if mask[0] == '%': - mask = mask[1:] - try: - (nick, ident, host) = ircutils.splitHostmask(mask) - except AssertionError: - # not a hostmask - return None - channel = None - chan = None - if mask[0] not in ('*', '?'): # Nick ban - if nick in self.nicks: - return self.nicks[nick] - else: # Host/ident ban - for (inick, ihost) in self.nicks.iteritems(): - if ircutils.hostmaskPatternEqual(mask, ihost): - return ihost - return None - - def doTopic(self, irc, msg): - if len(msg.args) == 1: - return # It's an empty TOPIC just to get the current topic. - channel = msg.args[0] - self.doLog(irc, channel, - '*** %s changes topic to "%s"\n' % (msg.nick, msg.args[1])) - - def doQuit(self, irc, msg): - if irc not in self.lastStates: - self.lastStates[irc] = irc.state.copy() - for (channel, chan) in self.lastStates[irc].channels.iteritems(): - if msg.nick in chan.users: - self.doLog(irc, channel, '*** %s (%s) has quit IRC (%s)\n' % (msg.nick, msg.prefix, msg.args[0])) -# if msg.nick in self.user: -# del self.user[msg.nick] - - def outFilter(self, irc, msg): - # Gotta catch my own messages *somehow* :) - # Let's try this little trick... - if msg.command in ('PRIVMSG', 'NOTICE'): - # Other messages should be sent back to us. - m = ircmsgs.IrcMsg(msg=msg, prefix=irc.prefix) - self(irc, m) - return msg - -# def callPrecedence(self, irc): -# before = [] -# for cb in irc.callbacks: -# if cb.name() == 'IRCLogin': -# return (['IRCLogin'], []) -# return ([], []) - - def check_auth(self, irc, msg, args, cap='bantracker'): - hasIRCLogin = False - for cb in self.callPrecedence(irc)[0]: - if cb.name() == "IRCLogin": - hasIRCLogin = True - if hasIRCLogin and not msg.tagged('identified'): - irc.error(conf.supybot.replies.incorrectAuthentication()) - return False - try: - user = ircdb.users.getUser(msg.prefix) - except: - irc.error(conf.supybot.replies.incorrectAuthentication()) - return False - - if not capab(user, cap): - irc.error(conf.supybot.replies.noCapability() % cap) - return False - return user - - def btlogin(self, irc, msg, args): - """Takes no arguments - - Sends you a message with a link to login to the bantracker. - """ - user = self.check_auth(irc, msg, args) - if not user: - return - user.addAuth(msg.prefix) - try: - ircdb.users.setUser(user, flush=False) - except: - pass - - if not capab(user, 'bantracker'): - irc.error(conf.supybot.replies.noCapability() % 'bantracker') - return - if not self.registryValue('bansite'): - irc.error("No bansite set, please set supybot.plugins.Bantracker.bansite") - return - sessid = hashlib.md5('%s%s%d' % (msg.prefix, time.time(), random.randint(1,100000))).hexdigest() - self.db_run("INSERT INTO sessions (session_id, user, time) VALUES (%s, %s, %d);", - ( sessid, msg.nick, int(time.mktime(time.gmtime())) ) ) - irc.reply('Log in at %s/bans.cgi?sess=%s' % (self.registryValue('bansite'), sessid), private=True) - - btlogin = wrap(btlogin) - - def mark(self, irc, msg, args, channel, target, kickmsg): - """[] [] - - Creates an entry in the Bantracker as if was kicked from with the comment , - if is given it will be uses as the comment on the Bantracker, is only needed when send in /msg - """ - user = self.check_auth(irc, msg, args) - if not user: - return - - if target == '*' or target[0] == '*': - irc.error("Can not create a mark for '%s'" % target) - return - - if not channel: - irc.error(' must be given if not in a channel') - return - channel = channel.lower() - channels = [] - for chan in irc.state.channels.keys(): - channels.append(chan.lower()) - - if not channel in channels: - irc.error('Not in that channel') - return - - if not kickmsg: - kickmsg = '**MARK**' - else: - kickmsg = "**MARK** - %s" % kickmsg - hostmask = self.nick_to_host(irc, target) - - self.doLog(irc, channel.lower(), '*** %s requested a mark for %s\n' % (msg.nick, target)) - self._doKickban(irc, channel.lower(), msg.prefix, hostmask, kickmsg) - irc.replySuccess() - - mark = wrap(mark, [optional('channel'), 'something', additional('text')]) - - def sort_bans(self, channel=None): - data = self.db_run("SELECT mask, removal, channel, id FROM bans", (), expect_result=True) - if channel: - data = [i for i in data if i[2] == channel] - bans = [(i[0], i[3]) for i in data if i[1] == None and '%' not in i[0]] - mutes = [(i[0], i[3]) for i in data if i[1] == None and '%' in i[0]] - return mutes + bans - - def get_banId(self, mask, channel): - data = self.db_run("SELECT MAX(id) FROM bans WHERE mask=%s AND channel=%s", (mask, channel), True) - if data: - data = data[0] - if not data or not data[0]: - return - return int(data[0]) - - def getBans(self, hostmask, channel): - match = [] - if channel: - if channel in self.bans and self.bans[channel]: - for b in self.bans[channel]: - if hostmaskPatternEqual(b.mask, hostmask): - match.append((b.mask, self.get_banId(b.mask,channel))) - data = self.sort_bans(channel) - for e in data: - if hostmaskPatternEqual(e[0], hostmask): - if (e[0], e[1]) not in match: - match.append((e[0], e[1])) - else: - for c in self.bans: - for b in self.bans[c]: - if hostmaskPatternEqual(b.mask, hostmask): - match.append((b.mask, self.get_banId(b.mask,c))) - data = self.sort_bans() - for e in data: - if hostmaskPatternEqual(e[0], hostmask): - if (e[0], e[1]) not in match: - match.append((e[0], e[1])) - return match - - def bansearch_real(self, irc, msg, args, target, channel, from_reply=False, reply=None): - """ [] - - Search bans database for a ban on , - if is not given search all channel bans. - """ - def format_entry(entry): - ret = list(entry[:-1]) - t = cPickle.loads(entry[-1]).astimezone(pytz.timezone('UTC')).strftime("%b %d %Y %H:%M:%S") - ret.append(t) - return tuple(ret) - - user = self.check_auth(irc, msg, args) - if not user: - return - - if from_reply: - if not reply: - if capab(user, 'admin'): - if len(queue.msgcache) > 0: - irc.reply("Warning: still syncing (%i)" % len(queue.msgcache)) - irc.reply("No matches found for %s in %s" % (hostmask, True and channel or "any channel")) - hostmask = reply - else: - hostmask = self.nick_to_host(irc, target, reply_now=False) - if not hostmask: - self.sendWhois(irc, target, True, 'bansearch', irc, msg, args, target, channel) - return - - if capab(user, 'owner'): - if len(queue.msgcache) > 0: - irc.reply("Warning: still syncing (%i)" % len(queue.msgcache)) - - if channel: - if not ircutils.isChannel(channel): - channel = None - - if '*' in target or '?' in target: - irc.error("Can only search for a complete hostmask") - return - hostmask = target - if '!' not in target or '@' not in target: - hostmask = self.nick_to_host(irc, target) - if '!' not in hostmask: - if "n=" in hostmask: - hostmask = hostmask.replace("n=", "!n=", 1) - elif "i=" in hostmask: - hostmask = hostmask.replace("i=", "!i=", 1) - match = self.getBans(hostmask, channel) - - if not match: - irc.reply("No matches found for %s in %s" % (hostmask, True and channel or "any channel")) - return - ret = [] - replies = [] - for m in match: - if m[1]: - ret.append((format_entry(self.db_run("SELECT mask, operator, channel, time FROM bans WHERE id=%d", m[1], expect_result=True)[0]), m[1])) - if not ret: - done = [] - for c in self.bans: - for b in self.bans[c]: - for m in match: - if m[0] == b.mask: - if not c in done: - irc.reply("Match %s in %s" % (b, c)) - done.append(c) - return - for i in ret: - if '*' in i[0][0] or '?' in i[0][0]: - banstr = "Match: %s by %s in %s on %s (ID: %s)" % (i[0] + (i[1],)) - else: - banstr = "Mark: by %s in %s on %s (ID: %s)" % (i[0][1:] + (i[1],)) - if (banstr, False) not in replies: - replies.append((banstr, False)) - - if replies: - for r in replies: - irc.reply(r[0], private=r[1]) - return - irc.error("Something not so good happened, please tell stdin about it") - - bansearch = wrap(bansearch_real, ['something', optional('something', default=None)]) - - def banlog(self, irc, msg, args, target, channel): - """ [] - - Prints the last 5 messages from the nick/host logged before a ban/mute, - the nick/host has to have an active ban/mute against it. - If channel is not given search all channel bans. - """ - user = self.check_auth(irc, msg, args) - if not user: - return - - if capab(user, 'owner') and len(queue.msgcache) > 0: - irc.reply("Warning: still syncing (%i)" % len(queue.msgcache)) - - hostmask = self.nick_to_host(irc, target) - target = target.split('!', 1)[0] - match = self.getBans(hostmask, channel) - - if not match: - irc.reply("No matches found for %s (%s) in %s" % (target, hostmask, True and channel or "any channel")) - return - - ret = [] - for m in match: - if m[1]: - ret.append((self.db_run("SELECT log, channel FROM bans WHERE id=%d", m[1], expect_result=True), m[1])) - - sent = [] - if not ret: - irc.reply("No matches in tracker") - for logs in ret: - log = logs[0] - id = logs[1] - lines = ["%s: %s" % (log[0][1], i) for i in log[0][0].split('\n') if "<%s>" % target.lower() in i.lower() and i[21:21+len(target)].lower() == target.lower()] - show_sep = False - if not lines: - show_sep = False - irc.error("No log for ID %s available" % id) - else: - for l in lines[:5]: - if l not in sent: - show_sep = True - irc.reply(l) - sent.append(l) - if show_sep: - irc.reply('--') - - banlog = wrap(banlog, ['something', optional('anything', default=None)]) - - def updatebt(self, irc, msg, args, channel): - """[] - - Update bans in the tracker from the channel ban list, - if channel is not given then run in all channels - """ - - def getBans(chan): - data = self.db_run("SELECT mask, removal FROM bans WHERE channel=%s", chan, expect_result=True) - L = [] - for mask, removal in data: - if removal is not None: - continue - elif not isUserHostmask(mask) and mask[0] != '$': - continue - L.append(mask) - return L - - def remBans(chan): - bans = getBans(chan) - old_bans = bans[:] - new_bans = [i.mask for i in self.bans[chan]] - remove_bans = [] - for ban in old_bans: - if ban not in new_bans: - remove_bans.append(ban) - bans.remove(ban) - - for ban in remove_bans: - self.log.info("Bantracker: Removing ban %s from %s" % (ban.replace('%', '%%'), chan)) - self.doUnban(irc, channel, "Automated-Removal", ban) - - return len(remove_bans) - - def addBans(chan): - bans = self.bans[chan] - old_bans = getBans(chan) - add_bans = [] - for ban in bans: - if ban.mask not in old_bans and ban not in add_bans: - add_bans.append(ban) - - for ban in add_bans: - nick = ban.who - if nick.endswith('.freenode.net'): - nick = "Automated-Addition" - self.log.info("Bantracker: Adding ban %s to %s (%s)" % (str(ban).replace('%', '%%'), chan, nick)) - self.doLog(irc, channel.lower(), '*** Ban sync from channel: %s\n' % str(ban).replace('%', '%%')) - self._doKickban(irc, chan, nick, ban.mask, use_time = ban.when, add_to_cache = False) - return len(add_bans) - - if not self.check_auth(irc, msg, args, 'owner'): - return - - add_res = 0 - rem_res = 0 - - if len(queue.msgcache) > 0: - irc.reply("Error: still syncing (%i)" % len(queue.msgcache)) - return - - try: - if channel: - rem_res += remBans(channel) - add_res += addBans(channel) - else: - for channel in irc.state.channels.keys(): - if channel not in self.bans: - self.bans[channel] = [] - rem_res += remBans(channel) - add_res += addBans(channel) - except KeyError, e: - irc.error("%s, Please wait longer" % e) - return - - irc.reply("Cleared %i obsolete bans, Added %i new bans" % (rem_res, add_res)) - - updatebt = wrap(updatebt, [optional('anything', default=None)]) - - def clearban(self, irc, msg, args, ids, comment): - """[, ...] [] - - Marks the ban with as removed with , if no comment is - given it defaults to "Cleared by $nick". - """ - - def addComment(id, nick, message): - self.db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, message, now())) - - if not self.check_auth(irc, msg, args): - return - - if comment is None: - comment = "Cleared by " + msg.nick - - removed = [] - unknown = [] - existing = [] - for id in splitID(ids): - try: - mask, channel, removal = self._getBan(id) - except ValueError: - unknown.append(id) - continue - - if removal: - existing.append(id) - continue - - addComment(id, msg.nick, comment) - self.doUnban(irc, channel, msg.nick, mask, id) - removed.append(id) - - if removed: - irc.reply("Removed %s" % utils.str.commaAndify(map(str, removed))) - else: - irc.reply("No bans removed") - - if unknown: - irc.reply("The following ban ID(s) are unknown: %s" % utils.str.commaAndify(map(str, unknown))) - if existing: - irc.reply("The following ban ID(s) are already marked as removed: %s" % utils.str.commaAndify(map(str, existing))) - - - clearban = wrap(clearban, ['something', optional('text')]) - - def _getBan(self, id): - """gets mask, channel and removal date of ban""" - L = self.db_run("SELECT mask, channel, removal FROM bans WHERE id = %s", - id, expect_result=True) - if not L: - raise ValueError - return L[0] - - def _setBanDuration(self, id, duration): - """Set ban for remove after time, if is negative - or zero, never remove the ban. - """ - # check if ban has already a duration time - for idx, br in enumerate(self.managedBans): - if id == br.id: - ban = br.ban - del self.managedBans.shelf[idx] - break - else: - if duration < 1: - # nothing to do. - raise Exception("ban isn't marked for removal") - - # ban obj ins't in self.managedBans - try: - mask, channel, removal = self._getBan(id) - except ValueError: - raise Exception("unknow id") - - type = guessBanType(mask) - if type not in ('ban', 'quiet'): - raise Exception("not a ban or quiet") - - if removal: - raise Exception("ban was removed") - - for ban in self.bans[channel]: - if mask == ban.mask: - if ban.id is None: - ban.id = id - break - else: - # ban not in sync it seems, shouldn't happen normally. - raise Exception("bans not in sync") - - # add ban duration if is positive and non-zero - if duration > 0: - self.managedBans.add(BanRemoval(ban, duration)) - - def comment(self, irc, msg, args, ids, kickmsg): - """[, ...] [][, ] - - Reads or adds the for the ban with , use @bansearch to - find the id of a ban. Using will set the duration of the ban. - """ - - def addComment(id, nick, msg): - n = now() - self.db_run("INSERT INTO comments (ban_id, who, comment, time) values(%s,%s,%s,%s)", (id, nick, msg, n)) - - def readComment(id): - return self.db_run("SELECT who, comment, time FROM comments WHERE ban_id=%i", (id,), True) - - nick = msg.nick - duration, banset = None, [] - if kickmsg and ',' in kickmsg: - s = kickmsg[kickmsg.rfind(',') + 1:] - try: - duration = readTimeDelta(s) - except ValueError: - pass - - for id in splitID(ids): - try: - self._getBan(id) - except ValueError: - irc.reply("I don't know any ban with id %s." % id) - continue - - if kickmsg: - addComment(id, nick, kickmsg) - if duration is not None: - # set duration time - try: - self._setBanDuration(id, duration) - banset.append(str(id)) - except Exception as exc: - irc.reply("Failed to set duration time on %s (%s)" % (id, exc)) - else: - data = readComment(id) - if data: - for c in data: - date = cPickle.loads(c[2]).astimezone(pytz.timezone('UTC')).strftime("%b %d %Y %H:%M") - irc.reply("%s %s: %s" % (date, c[0], c[1].strip())) - else: - irc.reply("No comments recorded for ban %s" % id) - - # success reply. If duration time used, say which ones were set. - if kickmsg: - if banset: - if duration < 1: - irc.reply(Format("Comment added. %L won't expire.", banset)) - return - - try: - time = 'after ' + timeElapsed(duration) - except ValueError: - time = 'soon' - irc.reply(Format("Comment added. %L will be removed %s.", - banset, time)) - else: - # only a comment - irc.reply("Comment added.") - - comment = wrap(comment, ['something', optional('text')]) - - def duration(self, irc, msg, args, ids, duration): - """[[, ...]] [] - - Sets the duration of a ban. If isn't given show when a ban expires. If no is - given shows the ids of bans set to expire. - """ - if ids is None: - count = len(self.managedBans) - L = [ str(item.id) for item in self.managedBans ] - irc.reply(Format("%n set to expire: %L", (count, 'ban'), L)) - return - - if duration is not None: - try: - duration = readTimeDelta(duration) - except ValueError: - irc.error("bad time format.") - return - - banset = [] - for id in splitID(ids): - if duration is not None: - # set ban duration - try: - self._setBanDuration(id, duration) - banset.append(str(id)) - except Exception as exc: - irc.reply("Failed to set duration time on %s (%s)" \ - % (id, exc)) - else: - # get ban information - try: - mask, channel, removal = self._getBan(id) - except ValueError: - irc.reply("I don't know any ban with id %s." % id) - continue - - type = guessBanType(mask) - if type == 'quiet': - mask = mask[1:] - for br in self.managedBans: - if br.id == id: - break - else: - br = None - - expires = None - if br: - expires = br.timeLeft() - if expires > 0: - try: - expires = "expires in %s" % timeElapsed(expires) - except ValueError: - expires = "expires soon" - else: - expires = "expired and will be removed soon" - else: - if type in ('quiet', 'ban'): - if not removal: - expires = "never expires" - else: - expires = "not active" - - if expires: - irc.reply("[%s] %s - %s - %s - %s" % (id, type, mask, channel, expires)) - else: - irc.reply("[%s] %s - %s - %s" % (id, type, mask, channel)) - - # reply with the bans ids that were correctly set. - if banset: - if duration < 1: - irc.reply(Format("%L won't expire.", banset)) - return - - try: - time = 'after ' + timeElapsed(duration) - except ValueError: - time = 'soon' - irc.reply(Format("%L will be removed %s.", banset, time)) - - duration = wrap(duration, [optional('something'), optional('text')]) - - def banlink(self, irc, msg, args, id, highlight): - """ [] - - Returns a link to the log of the ban/kick with id . - If is given, lines containing that term will be highlighted - """ - if not self.check_auth(irc, msg, args): - return - if not highlight: - irc.reply("%s/bans.cgi?log=%s" % (self.registryValue('bansite'), id), private=True) - else: - irc.reply("%s/bans.cgi?log=%s&mark=%s" % (self.registryValue('bansite'), id, highlight), private=True) - banlink = wrap(banlink, ['id', optional('somethingWithoutSpaces')]) - - def banreview(self, irc, msg, args, optlist): - """[--verbose | --flush | --view ] - Lists pending ban reviews.""" - if not self.check_auth(irc, msg, args): - return - verbose = False - flush = view = None - for k, v in optlist: - if k == 'verbose': - verbose = True - elif k == 'flush': - flush = v - elif k == 'view': - view = v - - key = view or flush - if key: - if '@' in key: - nick, host = key.split('@', 1) - else: - nick, host = key, None - if host in self.pendingReviews: - reviews = self.pendingReviews[host] - else: - irc.reply('No reviews for %s, use --verbose for check the correct nick@host key.' % key) - return - - L = [] - for _nick, msg in reviews: - if nick == _nick: - irc.reply(msg.args[1]) - elif flush: - L.append((_nick, msg)) - if flush: - if L: - self.pendingReviews[host] = L - else: - del self.pendingReviews[host] - return - - count = {} - for host, reviews in self.pendingReviews.iteritems(): - for nick, msg in reviews: - if verbose and host: # host can be None for those "nick only" reviews. - key = '%s@%s' % (nick, host) - else: - key = nick - try: - count[key] += 1 - except KeyError: - count[key] = 1 - total = sum(count.itervalues()) - s = ' '.join([ '%s:%s' %pair for pair in count.iteritems() ]) - s = 'Pending ban reviews (%s): %s' %(total, s) - irc.reply(s) - - banreview = wrap(banreview, [getopts({'verbose':'', - 'flush': 'something', - 'view': 'something'})]) - -Class = Bantracker diff --git a/Bantracker/test.py b/Bantracker/test.py deleted file mode 100644 index 4e5a046..0000000 --- a/Bantracker/test.py +++ /dev/null @@ -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" - " ") - # 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" - " ") - # 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" - " ") - 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" - " ") - - 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" - " ") - - 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" - " ") - 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 ") - - 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) - - diff --git a/IRCLogin/README.txt b/IRCLogin/README.txt deleted file mode 100644 index e905274..0000000 --- a/IRCLogin/README.txt +++ /dev/null @@ -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. diff --git a/IRCLogin/__init__.py b/IRCLogin/__init__.py deleted file mode 100644 index 3c5cebd..0000000 --- a/IRCLogin/__init__.py +++ /dev/null @@ -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 diff --git a/IRCLogin/config.py b/IRCLogin/config.py deleted file mode 100644 index c7dc8a8..0000000 --- a/IRCLogin/config.py +++ /dev/null @@ -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)) diff --git a/IRCLogin/plugin.py b/IRCLogin/plugin.py deleted file mode 100644 index 36a4224..0000000 --- a/IRCLogin/plugin.py +++ /dev/null @@ -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 ACK :identify-msg" to acknowledge, or - "CAP 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 diff --git a/IRCLogin/test.py b/IRCLogin/test.py deleted file mode 100644 index 5ebeba1..0000000 --- a/IRCLogin/test.py +++ /dev/null @@ -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',) diff --git a/Lart/Lart.flat.db b/Lart/Lart.flat.db deleted file mode 100644 index 699f75a..0000000 --- a/Lart/Lart.flat.db +++ /dev/null @@ -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' diff --git a/Lart/__init__.py b/Lart/__init__.py deleted file mode 100644 index e37bfe1..0000000 --- a/Lart/__init__.py +++ /dev/null @@ -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: diff --git a/Lart/config.py b/Lart/config.py deleted file mode 100644 index efe0a6d..0000000 --- a/Lart/config.py +++ /dev/null @@ -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: diff --git a/Lart/plugin.py b/Lart/plugin.py deleted file mode 100644 index 66b0bfe..0000000 --- a/Lart/plugin.py +++ /dev/null @@ -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): - """[] [] [for ] - - Uses the Luser Attitude Readjustment Tool on (for , - if given). If is given, uses that specific lart. 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: diff --git a/Lart/test.py b/Lart/test.py deleted file mode 100644 index fbb4d48..0000000 --- a/Lart/test.py +++ /dev/null @@ -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: diff --git a/Mess/42.txt b/Mess/42.txt deleted file mode 100644 index 2e77fc0..0000000 --- a/Mess/42.txt +++ /dev/null @@ -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. diff --git a/Mess/README.txt b/Mess/README.txt deleted file mode 100644 index cadf43b..0000000 --- a/Mess/README.txt +++ /dev/null @@ -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. diff --git a/Mess/__init__.py b/Mess/__init__.py deleted file mode 100644 index 74310a5..0000000 --- a/Mess/__init__.py +++ /dev/null @@ -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 diff --git a/Mess/ball.txt b/Mess/ball.txt deleted file mode 100644 index 8c85084..0000000 --- a/Mess/ball.txt +++ /dev/null @@ -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 diff --git a/Mess/bofh.txt b/Mess/bofh.txt deleted file mode 100644 index eb5e7ec..0000000 --- a/Mess/bofh.txt +++ /dev/null @@ -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 diff --git a/Mess/config.py b/Mess/config.py deleted file mode 100644 index 0063cbc..0000000 --- a/Mess/config.py +++ /dev/null @@ -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 """)) diff --git a/Mess/ferengi.txt b/Mess/ferengi.txt deleted file mode 100644 index 732d9c3..0000000 --- a/Mess/ferengi.txt +++ /dev/null @@ -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 diff --git a/Mess/plugin.py b/Mess/plugin.py deleted file mode 100644 index 3fa5491..0000000 --- a/Mess/plugin.py +++ /dev/null @@ -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'
\s*(?P.*?)\s*
', False), - 'chuck': ('Chuck Norris facts', 'http://4q.cc/?pid=fact&person=chuck', r'
\s*(?P.*?)\s*
', False), - 'vin': ('Vin Diesel facts', 'http://4q.cc/?pid=fact&person=vin', r'
\s*(?P.*?)\s*
', False), - 'bauer': ('Jack Bauer facts', 'http://www.notrly.com/jackbauer/', r'

(?P.*?)

', False), - 'bruce': ('Bruce Schneier facts', 'http://geekz.co.uk/schneierfacts/', r'p class="fact">(?P.*?)(?P.*?)\s*(?P.*?)\s*(?P.*?)(?P.*?)', False), - #'yourmom': ('', 'http://pfa.php1h.com', r'

(?P.*?)

', True), - 'bush': ('Bush quotes', 'http://www.dubyaspeak.com/random.phtml', r'(?P)', True), - #southpark': ('', 'http://www.southparkquotes.com/random.php?num=1', r'

(?P.*)

', True), - 'mjg': ('Matthew Garrett facts', 'http://www.angryfacts.com', r'

(?P.*?)

', False), - 'mjg59': ('Matthew Garrett facts', 'http://www.angryfacts.com', r'

(?P.*?)

', False), - 'vmjg': ('Virtual Matthew Garrett', 'http://www.rjek.com/vmjg59.cgi', r'(?P.*?)

', True), - 'vmjg59': ('Virtual Matthew Garrett', 'http://www.rjek.com/vmjg59.cgi', r'(?P.*?)

', True), - 'shakespeare': ('Shakespeare quotes', 'http://www.pangloss.com/seidel/Shaker/', r'(?P.*?)', False), - 'lugradio': ('Lugradio facts', 'http://planet.lugradio.org/facts/', r'

\s*(?P.*?)

', 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('
','').replace('\n','').replace('\r','') - txt = txt.replace('','/').replace('','/').replace('','*').replace('','*') - txt = txt.replace('"','"').replace('<','<').replace('>','>') - 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): - """[] - 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): - """[] - Can you or 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 diff --git a/Mess/test.py b/Mess/test.py deleted file mode 100644 index 4513bc3..0000000 --- a/Mess/test.py +++ /dev/null @@ -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',) diff --git a/Webcal/README.txt b/Webcal/README.txt deleted file mode 100644 index 754296c..0000000 --- a/Webcal/README.txt +++ /dev/null @@ -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 diff --git a/Webcal/__init__.py b/Webcal/__init__.py deleted file mode 100644 index 71b44f2..0000000 --- a/Webcal/__init__.py +++ /dev/null @@ -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 diff --git a/Webcal/cal.ical b/Webcal/cal.ical deleted file mode 100644 index a5f52a0..0000000 --- a/Webcal/cal.ical +++ /dev/null @@ -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:

Location\: #ubuntu-tn
- Agenda\: Team participation to SFD Tunisia 2008.

- -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:

Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/Xubuntu/Meetings

- -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:

Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda

- -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:

Location\: #ubuntu-meeting

- -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: - -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:

Location\: #ubuntu-meeting on IRC
- Agenda\: https\://wiki.ubuntu.com/ServerTeam/Meeting

- -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:

Location\: #ubuntu-meeting in IRC
- Agenda\: Not listed as of publication

- -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:

Location\: #ubuntu-meeting

- -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:

Location\: #ubuntu-meeting on IRC
- Agenda\: https\://wiki.ubuntu.com/ServerTeam/Meeting

- -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:

Location\: #ubuntu-meeting in IRC
- Agenda\: Not listed as of publication

- -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: - -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:

Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda

- -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:

Location\: #ubuntu-us-md

- -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:

Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda

- -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:

Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda

- -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:

Location\: #ubuntu-us-md

- -END:VEVENT -END:VCALENDAR diff --git a/Webcal/config.py b/Webcal/config.py deleted file mode 100644 index 556097a..0000000 --- a/Webcal/config.py +++ /dev/null @@ -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""")) diff --git a/Webcal/ical.py b/Webcal/ical.py deleted file mode 100644 index 5f47e1d..0000000 --- a/Webcal/ical.py +++ /dev/null @@ -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()] diff --git a/Webcal/ical.py.bak b/Webcal/ical.py.bak deleted file mode 100644 index 1d932ce..0000000 --- a/Webcal/ical.py.bak +++ /dev/null @@ -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 - diff --git a/Webcal/ical.py.bak.bac2 b/Webcal/ical.py.bak.bac2 deleted file mode 100644 index 0152082..0000000 --- a/Webcal/ical.py.bak.bac2 +++ /dev/null @@ -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 - diff --git a/Webcal/icalendar/__init__.py b/Webcal/icalendar/__init__.py deleted file mode 100644 index e2ce67f..0000000 --- a/Webcal/icalendar/__init__.py +++ /dev/null @@ -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 diff --git a/Webcal/icalendar/cal.py b/Webcal/icalendar/cal.py deleted file mode 100644 index c1603bd..0000000 --- a/Webcal/icalendar/cal.py +++ /dev/null @@ -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) - - """ - - 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]) - (, ) - """ - - 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() diff --git a/Webcal/icalendar/caselessdict.py b/Webcal/icalendar/caselessdict.py deleted file mode 100644 index 8d943f7..0000000 --- a/Webcal/icalendar/caselessdict.py +++ /dev/null @@ -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) + ')' diff --git a/Webcal/icalendar/interfaces.py b/Webcal/icalendar/interfaces.py deleted file mode 100644 index e30e940..0000000 --- a/Webcal/icalendar/interfaces.py +++ /dev/null @@ -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. - """ diff --git a/Webcal/icalendar/parser.py b/Webcal/icalendar/parser.py deleted file mode 100644 index de695cb..0000000 --- a/Webcal/icalendar/parser.py +++ /dev/null @@ -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 -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') diff --git a/Webcal/icalendar/prop.py b/Webcal/icalendar/prop.py deleted file mode 100644 index 61aa3fd..0000000 --- a/Webcal/icalendar/prop.py +++ /dev/null @@ -1,1513 +0,0 @@ -# -*- coding: latin-1 -*- - -""" - -This module contains the parser/generators (or coders/encoders if you prefer) -for the classes/datatypes that are used in Icalendar: - -########################################################################### -# This module defines these property value data types and property parameters - -4.2 Defined property parameters are: - - ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE, - FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP, - SENT-BY, TZID, VALUE - -4.3 Defined value data types are: - - BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER, - PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET - -########################################################################### - - -iCalendar properties has values. The values are strongly typed. This module -defines these types, calling val.ical() on them, Will render them as defined in -rfc2445. - -If you pass any of these classes a Python primitive, you will have an object -that can render itself as iCalendar formatted date. - -Property Value Data Types starts with a 'v'. they all have an ical() and -from_ical() method. The ical() method generates a text string in the iCalendar -format. The from_ical() method can parse this format and return a primitive -Python datatype. So it should allways be true that: - - x == vDataType.from_ical(VDataType(x).ical()) - -These types are mainly used for parsing and file generation. But you can set -them directly. - -""" - -# from python >= 2.3 -from datetime import datetime, timedelta, time, date, tzinfo -from types import IntType, StringType, UnicodeType, TupleType, ListType -SequenceTypes = [TupleType, ListType] -import re -import time as _time - -# from this package -from icalendar.caselessdict import CaselessDict -from icalendar.parser import Parameters - -DATE_PART = r'(\d+)D' -TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?' -DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART) -WEEKS_PART = r'(\d+)W' -DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$' - % (WEEKS_PART, DATETIME_PART)) -WEEKDAY_RULE = re.compile('(?P[+-]?)(?P[\d]?)' - '(?P[\w]{2})$') - -class vBinary: - """ - Binary property values are base 64 encoded - >>> b = vBinary('This is gibberish') - >>> b.ical() - 'VGhpcyBpcyBnaWJiZXJpc2g=' - >>> b = vBinary.from_ical('VGhpcyBpcyBnaWJiZXJpc2g=') - >>> b - 'This is gibberish' - - The roundtrip test - >>> x = 'Binary data æ ø å \x13 \x56' - >>> vBinary(x).ical() - 'QmluYXJ5IGRhdGEg5iD4IOUgEyBW' - >>> vBinary.from_ical('QmluYXJ5IGRhdGEg5iD4IOUgEyBW') - 'Binary data \\xe6 \\xf8 \\xe5 \\x13 V' - - >>> b = vBinary('txt') - >>> b.params - Parameters({'VALUE': 'BINARY', 'ENCODING': 'BASE64'}) - """ - - def __init__(self, obj): - self.obj = obj - self.params = Parameters(encoding='BASE64', value="BINARY") - - def __repr__(self): - return "vBinary(%s)" % str.__repr__(self.obj) - - def ical(self): - return self.obj.encode('base-64')[:-1] - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return ical.decode('base-64') - except: - raise ValueError, 'Not valid base 64 encoding.' - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vBoolean(int): - """ - Returns specific string according to state - >>> bin = vBoolean(True) - >>> bin.ical() - 'TRUE' - >>> bin = vBoolean(0) - >>> bin.ical() - 'FALSE' - - The roundtrip test - >>> x = True - >>> x == vBoolean.from_ical(vBoolean(x).ical()) - True - >>> vBoolean.from_ical('true') - True - """ - - def __init__(self, *args, **kwargs): - int.__init__(self, *args, **kwargs) - self.params = Parameters() - - def ical(self): - if self: - return 'TRUE' - return 'FALSE' - - bool_map = CaselessDict(true=True, false=False) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return vBoolean.bool_map[ical] - except: - raise ValueError, "Expected 'TRUE' or 'FALSE'. Got %s" % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vCalAddress(str): - """ - This just returns an unquoted string - >>> a = vCalAddress('MAILTO:maxm@mxm.dk') - >>> a.params['cn'] = 'Max M' - >>> a.ical() - 'MAILTO:maxm@mxm.dk' - >>> str(a) - 'MAILTO:maxm@mxm.dk' - >>> a.params - Parameters({'CN': 'Max M'}) - >>> vCalAddress.from_ical('MAILTO:maxm@mxm.dk') - 'MAILTO:maxm@mxm.dk' - """ - - def __init__(self, *args, **kwargs): - str.__init__(self, *args, **kwargs) - self.params = Parameters() - - def __repr__(self): - return u"vCalAddress(%s)" % str.__repr__(self) - - def ical(self): - return str(self) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return str(ical) - except: - raise ValueError, 'Expected vCalAddress, got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return str.__str__(self) - -#################################################### -# handy tzinfo classes you can use. - -ZERO = timedelta(0) -HOUR = timedelta(hours=1) -STDOFFSET = timedelta(seconds = -_time.timezone) -if _time.daylight: - DSTOFFSET = timedelta(seconds = -_time.altzone) -else: - DSTOFFSET = STDOFFSET -DSTDIFF = DSTOFFSET - STDOFFSET - - -class FixedOffset(tzinfo): - """Fixed offset in minutes east from UTC.""" - - def __init__(self, offset, name): - self.__offset = timedelta(minutes = offset) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - -class Utc(tzinfo): - """UTC tzinfo subclass""" - - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO -UTC = Utc() - -class LocalTimezone(tzinfo): - """ - Timezone of the machine where the code is running - """ - - def utcoffset(self, dt): - if self._isdst(dt): - return DSTOFFSET - else: - return STDOFFSET - - def dst(self, dt): - if self._isdst(dt): - return DSTDIFF - else: - return ZERO - - def tzname(self, dt): - return _time.tzname[self._isdst(dt)] - - def _isdst(self, dt): - tt = (dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second, - dt.weekday(), 0, -1) - stamp = _time.mktime(tt) - tt = _time.localtime(stamp) - return tt.tm_isdst > 0 - -#################################################### - - - -class vDatetime: - """ - 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. - - >>> d = datetime(2001, 1,1, 12, 30, 0) - - >>> dt = vDatetime(d) - >>> dt.ical() - '20010101T123000' - - >>> vDatetime.from_ical('20000101T120000') - datetime.datetime(2000, 1, 1, 12, 0) - - >>> dutc = datetime(2001, 1,1, 12, 30, 0, tzinfo=UTC) - >>> vDatetime(dutc).ical() - '20010101T123000Z' - - >>> vDatetime.from_ical('20010101T000000') - datetime.datetime(2001, 1, 1, 0, 0) - - >>> vDatetime.from_ical('20010101T000000A') - Traceback (most recent call last): - ... - ValueError: Wrong datetime format: 20010101T000000A - - >>> utc = vDatetime.from_ical('20010101T000000Z') - >>> vDatetime(utc).ical() - '20010101T000000Z' - """ - - def __init__(self, dt): - self.dt = dt - self.params = Parameters() - - def ical(self): - if self.dt.tzinfo: - offset = self.dt.tzinfo.utcoffset(datetime.now()) - utc_time = self.dt - self.dt.tzinfo.utcoffset(datetime.now()) - return utc_time.strftime("%Y%m%dT%H%M%SZ") - return self.dt.strftime("%Y%m%dT%H%M%S") - - def from_ical(ical): - "Parses the data format from ical text format" - try: - timetuple = map(int, (( - ical[:4], # year - ical[4:6], # month - ical[6:8], # day - ical[9:11], # hour - ical[11:13], # minute - ical[13:15], # second - ))) - if not ical[15:]: - return datetime(*timetuple) - elif ical[15:16] == 'Z': - timetuple += [0, UTC] - return datetime(*timetuple) - else: - raise ValueError, ical - except: - raise ValueError, 'Wrong datetime format: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vDate: - """ - Render and generates iCalendar date format. - >>> d = date(2001, 1,1) - >>> vDate(d).ical() - '20010101' - - >>> vDate.from_ical('20010102') - datetime.date(2001, 1, 2) - - >>> vDate('d').ical() - Traceback (most recent call last): - ... - ValueError: Value MUST be a date instance - """ - - def __init__(self, dt): - if not isinstance(dt, date): - raise ValueError('Value MUST be a date instance') - self.dt = dt - self.params = Parameters() - - def ical(self): - return self.dt.strftime("%Y%m%d") - - def from_ical(ical): - "Parses the data format from ical text format" - try: - timetuple = map(int, (( - ical[:4], # year - ical[4:6], # month - ical[6:8], # day - ))) - return date(*timetuple) - except: - raise ValueError, 'Wrong date format %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vDuration: - """ - Subclass of timedelta that renders itself in the iCalendar DURATION format. - - >>> vDuration(timedelta(11)).ical() - 'P11D' - >>> vDuration(timedelta(-14)).ical() - '-P14D' - >>> vDuration(timedelta(1, 7384)).ical() - 'P1DT2H3M4S' - >>> vDuration(timedelta(1, 7380)).ical() - 'P1DT2H3M' - >>> vDuration(timedelta(1, 7200)).ical() - 'P1DT2H' - >>> vDuration(timedelta(0, 7200)).ical() - 'PT2H' - >>> vDuration(timedelta(0, 7384)).ical() - 'PT2H3M4S' - >>> vDuration(timedelta(0, 184)).ical() - 'PT3M4S' - >>> vDuration(timedelta(0, 22)).ical() - 'PT22S' - >>> vDuration(timedelta(0, 3622)).ical() - 'PT1H0M22S' - - >>> vDuration(timedelta(days=1, hours=5)).ical() - 'P1DT5H' - >>> vDuration(timedelta(hours=-5)).ical() - '-PT5H' - >>> vDuration(timedelta(days=-1, hours=-5)).ical() - '-P1DT5H' - - How does the parsing work? - >>> vDuration.from_ical('PT1H0M22S') - datetime.timedelta(0, 3622) - - >>> vDuration.from_ical('kox') - Traceback (most recent call last): - ... - ValueError: Invalid iCalendar duration: kox - - >>> vDuration.from_ical('-P14D') - datetime.timedelta(-14) - - >>> vDuration(11) - Traceback (most recent call last): - ... - ValueError: Value MUST be a timedelta instance - """ - - def __init__(self, td): - if not isinstance(td, timedelta): - raise ValueError('Value MUST be a timedelta instance') - self.td = td - self.params = Parameters() - - def ical(self): - sign = "" - if self.td.days < 0: - sign = "-" - self.td = -self.td - timepart = "" - if self.td.seconds: - timepart = "T" - hours = self.td.seconds // 3600 - minutes = self.td.seconds % 3600 // 60 - seconds = self.td.seconds % 60 - if hours: - timepart += "%dH" % hours - if minutes or (hours and seconds): - timepart += "%dM" % minutes - if seconds: - timepart += "%dS" % seconds - if self.td.days == 0 and timepart: - return "%sP%s" % (sign, timepart) - else: - return "%sP%dD%s" % (sign, abs(self.td.days), timepart) - - def from_ical(ical): - """ - Parses the data format from ical text format. - """ - try: - match = DURATION_REGEX.match(ical) - sign, weeks, days, hours, minutes, seconds = match.groups() - if weeks: - value = timedelta(weeks=int(weeks)) - else: - value = timedelta(days=int(days or 0), - hours=int(hours or 0), - minutes=int(minutes or 0), - seconds=int(seconds or 0)) - if sign == '-': - value = -value - return value - except: - raise ValueError('Invalid iCalendar duration: %s' % ical) - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vFloat(float): - """ - Just a float. - >>> f = vFloat(1.0) - >>> f.ical() - '1.0' - >>> vFloat.from_ical('42') - 42.0 - >>> vFloat(42).ical() - '42.0' - """ - - def __init__(self, *args, **kwargs): - float.__init__(self, *args, **kwargs) - self.params = Parameters() - - def ical(self): - return str(self) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return float(ical) - except: - raise ValueError, 'Expected float value, got: %s' % ical - from_ical = staticmethod(from_ical) - - - -class vInt(int): - """ - Just an int. - >>> f = vInt(42) - >>> f.ical() - '42' - >>> vInt.from_ical('13') - 13 - >>> vInt.from_ical('1s3') - Traceback (most recent call last): - ... - ValueError: Expected int, got: 1s3 - """ - - def __init__(self, *args, **kwargs): - int.__init__(self, *args, **kwargs) - self.params = Parameters() - - def ical(self): - return str(self) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return int(ical) - except: - raise ValueError, 'Expected int, got: %s' % ical - from_ical = staticmethod(from_ical) - - - -class vDDDTypes: - """ - A combined Datetime, Date or Duration parser/generator. Their format cannot - be confused, and often values can be of either types. So this is practical. - - >>> d = vDDDTypes.from_ical('20010101T123000') - >>> type(d) - - - >>> repr(vDDDTypes.from_ical('20010101T123000Z'))[:65] - 'datetime.datetime(2001, 1, 1, 12, 30, tzinfo=>> d = vDDDTypes.from_ical('20010101') - >>> type(d) - - - >>> vDDDTypes.from_ical('P31D') - datetime.timedelta(31) - - >>> vDDDTypes.from_ical('-P31D') - datetime.timedelta(-31) - - Bad input - >>> vDDDTypes(42) - Traceback (most recent call last): - ... - ValueError: You must use datetime, date or timedelta - """ - - def __init__(self, dt): - "Returns vDate from" - wrong_type_used = 1 - for typ in (datetime, date, timedelta): - if isinstance(dt, typ): - wrong_type_used = 0 - if wrong_type_used: - raise ValueError ('You must use datetime, date or timedelta') - self.dt = dt - - def ical(self): - dt = self.dt - if isinstance(dt, datetime): - return vDatetime(dt).ical() - elif isinstance(dt, date): - return vDate(dt).ical() - elif isinstance(dt, timedelta): - return vDuration(dt).ical() - else: - raise ValueEror ('Unknown date type') - - def from_ical(ical): - "Parses the data format from ical text format" - u = ical.upper() - if u.startswith('-P') or u.startswith('P'): - return vDuration.from_ical(ical) - try: - return vDatetime.from_ical(ical) - except: - return vDate.from_ical(ical) - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - -class vDDDLists: - """ - A list of vDDDTypes values. - - >>> dt_list = vDDDLists.from_ical('19960402T010000Z') - >>> type(dt_list) - - - >>> len(dt_list) - 1 - - >>> type(dt_list[0]) - - - >>> str(dt_list[0]) - '1996-04-02 01:00:00+00:00' - - >>> dt_list = vDDDLists.from_ical('19960402T010000Z,19960403T010000Z,19960404T010000Z') - >>> len(dt_list) - 3 - - >>> str(dt_list[0]) - '1996-04-02 01:00:00+00:00' - >>> str(dt_list[2]) - '1996-04-04 01:00:00+00:00' - - >>> dt_list = vDDDLists('19960402T010000Z') - Traceback (most recent call last): - ... - ValueError: Value MUST be a list (of date instances) - - >>> dt_list = vDDDLists([]) - >>> str(dt_list) - '' - - >>> dt_list = vDDDLists([datetime(2000,1,1)]) - >>> str(dt_list) - '20000101T000000' - - >>> dt_list = vDDDLists([datetime(2000,1,1), datetime(2000,11,11)]) - >>> str(dt_list) - '20000101T000000,20001111T000000' - """ - - def __init__(self, dt_list): - if not isinstance(dt_list, list): - raise ValueError('Value MUST be a list (of date instances)') - vDDD = [] - for dt in dt_list: - vDDD.append(vDDDTypes(dt)) - self.dts = vDDD - - def ical(self): - ''' - Generates the text string in the iCalendar format. - ''' - dts_ical = [dt.ical() for dt in self.dts] - return ",".join(dts_ical) - - def from_ical(ical): - ''' - Parses the list of data formats from ical text format. - @param ical: ical text format - ''' - out = [] - ical_dates = ical.split(",") - for ical_dt in ical_dates: - out.append(vDDDTypes.from_ical(ical_dt)) - return out - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - -class vPeriod: - """ - A precise period of time. - One day in exact datetimes - >>> per = (datetime(2000,1,1), datetime(2000,1,2)) - >>> p = vPeriod(per) - >>> p.ical() - '20000101T000000/20000102T000000' - - >>> per = (datetime(2000,1,1), timedelta(days=31)) - >>> p = vPeriod(per) - >>> p.ical() - '20000101T000000/P31D' - - Roundtrip - >>> p = vPeriod.from_ical('20000101T000000/20000102T000000') - >>> p - (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0)) - >>> vPeriod(p).ical() - '20000101T000000/20000102T000000' - - >>> vPeriod.from_ical('20000101T000000/P31D') - (datetime.datetime(2000, 1, 1, 0, 0), datetime.timedelta(31)) - - Roundtrip with absolute time - >>> p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z') - >>> vPeriod(p).ical() - '20000101T000000Z/20000102T000000Z' - - And an error - >>> vPeriod.from_ical('20000101T000000/Psd31D') - Traceback (most recent call last): - ... - ValueError: Expected period format, got: 20000101T000000/Psd31D - - Utc datetime - >>> da_tz = FixedOffset(+1.0, 'da_DK') - >>> start = datetime(2000,1,1, tzinfo=da_tz) - >>> end = datetime(2000,1,2, tzinfo=da_tz) - >>> per = (start, end) - >>> vPeriod(per).ical() - '19991231T235900Z/20000101T235900Z' - - >>> p = vPeriod((datetime(2000,1,1, tzinfo=da_tz), timedelta(days=31))) - >>> p.ical() - '19991231T235900Z/P31D' - """ - - def __init__(self, per): - start, end_or_duration = per - if not (isinstance(start, datetime) or isinstance(start, date)): - raise ValueError('Start value MUST be a datetime or date instance') - if not (isinstance(end_or_duration, datetime) or - isinstance(end_or_duration, date) or - isinstance(end_or_duration, timedelta)): - raise ValueError('end_or_duration MUST be a datetime, date or timedelta instance') - self.start = start - self.end_or_duration = end_or_duration - self.by_duration = 0 - if isinstance(end_or_duration, timedelta): - self.by_duration = 1 - self.duration = end_or_duration - self.end = self.start + self.duration - else: - self.end = end_or_duration - self.duration = self.end - self.start - if self.start > self.end: - raise ValueError("Start time is greater than end time") - self.params = Parameters() - - def __cmp__(self, other): - if not isinstance(other, vPeriod): - raise NotImplementedError( - 'Cannot compare vPeriod with %s' % repr(other)) - return cmp((self.start, self.end), (other.start, other.end)) - - def overlaps(self, other): - if self.start > other.start: - return other.overlaps(self) - if self.start <= other.start < self.end: - return True - return False - - def ical(self): - if self.by_duration: - return '%s/%s' % (vDatetime(self.start).ical(), vDuration(self.duration).ical()) - return '%s/%s' % (vDatetime(self.start).ical(), vDatetime(self.end).ical()) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - start, end_or_duration = ical.split('/') - start = vDDDTypes.from_ical(start) - end_or_duration = vDDDTypes.from_ical(end_or_duration) - return (start, end_or_duration) - except: - raise ValueError, 'Expected period format, got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - def __repr__(self): - if self.by_duration: - p = (self.start, self.duration) - else: - p = (self.start, self.end) - return 'vPeriod(%s)' % repr(p) - -class vWeekday(str): - """ - This returns an unquoted weekday abbrevation - >>> a = vWeekday('mo') - >>> a.ical() - 'MO' - - >>> a = vWeekday('erwer') - Traceback (most recent call last): - ... - ValueError: Expected weekday abbrevation, got: ERWER - - >>> vWeekday.from_ical('mo') - 'MO' - - >>> vWeekday.from_ical('+3mo') - '+3MO' - - >>> vWeekday.from_ical('Saturday') - Traceback (most recent call last): - ... - ValueError: Expected weekday abbrevation, got: Saturday - - >>> a = vWeekday('+mo') - >>> a.ical() - '+MO' - - >>> a = vWeekday('+3mo') - >>> a.ical() - '+3MO' - - >>> a = vWeekday('-tu') - >>> a.ical() - '-TU' - """ - - week_days = CaselessDict({"SU":0, "MO":1, "TU":2, "WE":3, - "TH":4, "FR":5, "SA":6}) - - def __init__(self, *args, **kwargs): - str.__init__(self, *args, **kwargs) - match = WEEKDAY_RULE.match(self) - if match is None: - raise ValueError, 'Expected weekday abbrevation, got: %s' % self - match = match.groupdict() - sign = match['signal'] - weekday = match['weekday'] - relative = match['relative'] - if not weekday in vWeekday.week_days or sign not in '+-': - raise ValueError, 'Expected weekday abbrevation, got: %s' % self - self.relative = relative and int(relative) or None - self.params = Parameters() - - def ical(self): - return self.upper() - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return vWeekday(ical.upper()) - except: - raise ValueError, 'Expected weekday abbrevation, got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vFrequency(str): - """ - A simple class that catches illegal values. - >>> f = vFrequency('bad test') - Traceback (most recent call last): - ... - ValueError: Expected frequency, got: BAD TEST - >>> vFrequency('daily').ical() - 'DAILY' - >>> vFrequency('daily').from_ical('MONTHLY') - 'MONTHLY' - """ - - frequencies = CaselessDict({ - "SECONDLY":"SECONDLY", - "MINUTELY":"MINUTELY", - "HOURLY":"HOURLY", - "DAILY":"DAILY", - "WEEKLY":"WEEKLY", - "MONTHLY":"MONTHLY", - "YEARLY":"YEARLY", - }) - - def __init__(self, *args, **kwargs): - str.__init__(self, *args, **kwargs) - if not self in vFrequency.frequencies: - raise ValueError, 'Expected frequency, got: %s' % self - self.params = Parameters() - - def ical(self): - return self.upper() - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return vFrequency(ical.upper()) - except: - raise ValueError, 'Expected weekday abbrevation, got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vRecur(CaselessDict): - """ - Let's see how close we can get to one from the rfc: - FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 - - >>> r = dict(freq='yearly', interval=2) - >>> r['bymonth'] = 1 - >>> r['byday'] = 'su' - >>> r['byhour'] = [8,9] - >>> r['byminute'] = 30 - >>> r = vRecur(r) - >>> r.ical() - 'BYHOUR=8,9;BYDAY=SU;BYMINUTE=30;BYMONTH=1;FREQ=YEARLY;INTERVAL=2' - - >>> r = vRecur(FREQ='yearly', INTERVAL=2) - >>> r['BYMONTH'] = 1 - >>> r['BYDAY'] = 'su' - >>> r['BYHOUR'] = [8,9] - >>> r['BYMINUTE'] = 30 - >>> r.ical() - 'BYDAY=SU;BYMINUTE=30;BYMONTH=1;INTERVAL=2;FREQ=YEARLY;BYHOUR=8,9' - - >>> r = vRecur(freq='DAILY', count=10) - >>> r['bysecond'] = [0, 15, 30, 45] - >>> r.ical() - 'COUNT=10;FREQ=DAILY;BYSECOND=0,15,30,45' - - >>> r = vRecur(freq='DAILY', until=datetime(2005,1,1,12,0,0)) - >>> r.ical() - 'FREQ=DAILY;UNTIL=20050101T120000' - - How do we fare with regards to parsing? - >>> r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10') - >>> r - {'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]} - >>> vRecur(r).ical() - 'COUNT=10;FREQ=DAILY;INTERVAL=2' - - >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;BYHOUR=8,9;BYMINUTE=30') - >>> r - {'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30], 'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]} - >>> vRecur(r).ical() - 'BYDAY=-SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9' - - Some examples from the spec - - >>> r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1') - >>> vRecur(r).ical() - 'BYSETPOS=-1;FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR' - - >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30') - >>> vRecur(r).ical() - 'BYDAY=SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9' - - and some errors - >>> r = vRecur.from_ical('BYDAY=12') - Traceback (most recent call last): - ... - ValueError: Error in recurrence rule: BYDAY=12 - - """ - - frequencies = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", - "MONTHLY", "YEARLY"] - - types = CaselessDict({ - 'COUNT':vInt, - 'INTERVAL':vInt, - 'BYSECOND':vInt, - 'BYMINUTE':vInt, - 'BYHOUR':vInt, - 'BYMONTHDAY':vInt, - 'BYYEARDAY':vInt, - 'BYMONTH':vInt, - 'UNTIL':vDDDTypes, - 'BYSETPOS':vInt, - 'WKST':vWeekday, - 'BYDAY':vWeekday, - 'FREQ':vFrequency - }) - - def __init__(self, *args, **kwargs): - CaselessDict.__init__(self, *args, **kwargs) - self.params = Parameters() - - def ical(self): - # SequenceTypes - result = [] - for key, vals in self.items(): - typ = self.types[key] - if not type(vals) in SequenceTypes: - vals = [vals] - vals = ','.join([typ(val).ical() for val in vals]) - result.append('%s=%s' % (key, vals)) - return ';'.join(result) - - def parse_type(key, values): - # integers - parser = vRecur.types.get(key, vText) - return [parser.from_ical(v) for v in values.split(',')] - parse_type = staticmethod(parse_type) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - recur = vRecur() - for pairs in ical.split(';'): - key, vals = pairs.split('=') - recur[key] = vRecur.parse_type(key, vals) - return dict(recur) - except: - raise ValueError, 'Error in recurrence rule: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vText(unicode): - """ - Simple text - >>> t = vText(u'Simple text') - >>> t.ical() - 'Simple text' - - Escaped text - >>> t = vText('Text ; with escaped, chars') - >>> t.ical() - 'Text \\\\; with escaped\\\\, chars' - - Escaped newlines - >>> vText('Text with escaped\N chars').ical() - 'Text with escaped\\\\n chars' - - If you pass a unicode object, it will be utf-8 encoded. As this is the - (only) standard that RFC 2445 support. - - >>> t = vText(u'international chars æøå ÆØÅ ü') - >>> t.ical() - 'international chars \\xc3\\xa6\\xc3\\xb8\\xc3\\xa5 \\xc3\\x86\\xc3\\x98\\xc3\\x85 \\xc3\\xbc' - - Unicode is converted to utf-8 - >>> t = vText(u'international æ ø å') - >>> str(t) - 'international \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5' - - and parsing? - >>> vText.from_ical('Text \\; with escaped\\, chars') - u'Text ; with escaped, chars' - - >>> print vText.from_ical('A string with\\; some\\\\ characters in\\Nit') - A string with; some\\ characters in - it - """ - - encoding = 'utf-8' - - def __init__(self, *args, **kwargs): - unicode.__init__(self, *args, **kwargs) - self.params = Parameters() - - def escape(self): - """ - Format value according to iCalendar TEXT escaping rules. - """ - return (self.replace('\N', '\n') - .replace('\\', '\\\\') - .replace(';', r'\;') - .replace(',', r'\,') - .replace('\r\n', r'\n') - .replace('\n', r'\n') - ) - - def __repr__(self): - return u"vText(%s)" % unicode.__repr__(self) - - def ical(self): - return self.escape().encode(self.encoding) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - ical = (ical.replace(r'\N', r'\n') - .replace(r'\r\n', '\n') - .replace(r'\n', '\n') - .replace(r'\,', ',') - .replace(r'\;', ';') - .replace('\\\\', '\\')) - return ical.decode(vText.encoding) - except: - raise ValueError, 'Expected ical text, got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vTime(time): - """ - A subclass of datetime, that renders itself in the iCalendar time - format. - >>> dt = vTime(12, 30, 0) - >>> dt.ical() - '123000' - - >>> vTime.from_ical('123000') - datetime.time(12, 30) - - We should also fail, right? - >>> vTime.from_ical('263000') - Traceback (most recent call last): - ... - ValueError: Expected time, got: 263000 - """ - - def __init__(self, *args, **kwargs): - time.__init__(self, *args, **kwargs) - self.params = Parameters() - - def ical(self): - return self.strftime("%H%M%S") - - def from_ical(ical): - "Parses the data format from ical text format" - try: - timetuple = map(int, (ical[:2],ical[2:4],ical[4:6])) - return time(*timetuple) - except: - raise ValueError, 'Expected time, got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vUri(str): - """ - Uniform resource identifier is basically just an unquoted string. - >>> u = vUri('http://www.example.com/') - >>> u.ical() - 'http://www.example.com/' - >>> vUri.from_ical('http://www.example.com/') # doh! - 'http://www.example.com/' - """ - - def __init__(self, *args, **kwargs): - str.__init__(self, *args, **kwargs) - self.params = Parameters() - - def ical(self): - return str(self) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - return str(ical) - except: - raise ValueError, 'Expected , got: %s' % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return str.__str__(self) - - - -class vGeo: - """ - A special type that is only indirectly defined in the rfc. - - >>> g = vGeo((1.2, 3.0)) - >>> g.ical() - '1.2;3.0' - - >>> g = vGeo.from_ical('37.386013;-122.082932') - >>> g - (37.386012999999998, -122.082932) - - >>> vGeo(g).ical() - '37.386013;-122.082932' - - >>> vGeo('g').ical() - Traceback (most recent call last): - ... - ValueError: Input must be (float, float) for latitude and longitude - """ - - def __init__(self, geo): - try: - latitude, longitude = geo - latitude = float(latitude) - longitude = float(longitude) - except: - raise ValueError('Input must be (float, float) for latitude and longitude') - self.latitude = latitude - self.longitude = longitude - self.params = Parameters() - - def ical(self): - return '%s;%s' % (self.latitude, self.longitude) - - def from_ical(ical): - "Parses the data format from ical text format" - try: - latitude, longitude = ical.split(';') - return (float(latitude), float(longitude)) - except: - raise ValueError, "Expected 'float;float' , got: %s" % ical - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vUTCOffset: - """ - Renders itself as a utc offset - - >>> u = vUTCOffset(timedelta(hours=2)) - >>> u.ical() - '+0200' - - >>> u = vUTCOffset(timedelta(hours=-5)) - >>> u.ical() - '-0500' - - >>> u = vUTCOffset(timedelta()) - >>> u.ical() - '0000' - - >>> u = vUTCOffset(timedelta(minutes=-30)) - >>> u.ical() - '-0030' - - >>> u = vUTCOffset(timedelta(hours=2, minutes=-30)) - >>> u.ical() - '+0130' - - >>> u = vUTCOffset(timedelta(hours=1, minutes=30)) - >>> u.ical() - '+0130' - - Parsing - - >>> vUTCOffset.from_ical('0000') - datetime.timedelta(0) - - >>> vUTCOffset.from_ical('-0030') - datetime.timedelta(-1, 84600) - - >>> vUTCOffset.from_ical('+0200') - datetime.timedelta(0, 7200) - - >>> o = vUTCOffset.from_ical('+0230') - >>> vUTCOffset(o).ical() - '+0230' - - And a few failures - >>> vUTCOffset.from_ical('+323k') - Traceback (most recent call last): - ... - ValueError: Expected utc offset, got: +323k - - >>> vUTCOffset.from_ical('+2400') - Traceback (most recent call last): - ... - ValueError: Offset must be less than 24 hours, was +2400 - """ - - def __init__(self, td): - if not isinstance(td, timedelta): - raise ValueError('Offset value MUST be a timedelta instance') - self.td = td - self.params = Parameters() - - def ical(self): - td = self.td - day_in_minutes = (td.days * 24 * 60) - seconds_in_minutes = td.seconds // 60 - total_minutes = day_in_minutes + seconds_in_minutes - if total_minutes == 0: - sign = '%s' - elif total_minutes < 0: - sign = '-%s' - else: - sign = '+%s' - hours = abs(total_minutes) // 60 - minutes = total_minutes % 60 - duration = '%02i%02i' % (hours, minutes) - return sign % duration - - def from_ical(ical): - "Parses the data format from ical text format" - try: - sign, hours, minutes = (ical[-5:-4], int(ical[-4:-2]), int(ical[-2:])) - offset = timedelta(hours=hours, minutes=minutes) - except: - raise ValueError, 'Expected utc offset, got: %s' % ical - if offset >= timedelta(hours=24): - raise ValueError, 'Offset must be less than 24 hours, was %s' % ical - if sign == '-': - return -offset - return offset - from_ical = staticmethod(from_ical) - - def __str__(self): - return self.ical() - - - -class vInline(str): - """ - This is an especially dumb class that just holds raw unparsed text and has - parameters. Conversion of inline values are handled by the Component class, - so no further processing is needed. - - >>> vInline('Some text') - 'Some text' - - >>> vInline.from_ical('Some text') - 'Some text' - - >>> t2 = vInline('other text') - >>> t2.params['cn'] = 'Test Osterone' - >>> t2.params - Parameters({'CN': 'Test Osterone'}) - - """ - - def __init__(self,obj): - self.obj = obj - self.params = Parameters() - - def ical(self): - return str(self) - - def from_ical(ical): - return str(ical) - from_ical = staticmethod(from_ical) - - def __str__(self): - return str(self.obj) - - -class TypesFactory(CaselessDict): - """ - All Value types defined in rfc 2445 are registered in this factory class. To - get a type you can use it like this. - >>> factory = TypesFactory() - >>> datetime_parser = factory['date-time'] - >>> dt = datetime_parser(datetime(2001, 1, 1)) - >>> dt.ical() - '20010101T000000' - - A typical use is when the parser tries to find a content type and use text - as the default - >>> value = '20050101T123000' - >>> value_type = 'date-time' - >>> typ = factory.get(value_type, 'text') - >>> typ.from_ical(value) - datetime.datetime(2005, 1, 1, 12, 30) - - It can also be used to directly encode property and parameter values - >>> comment = factory.ical('comment', u'by Rasmussen, Max Møller') - >>> str(comment) - 'by Rasmussen\\\\, Max M\\xc3\\xb8ller' - >>> factory.ical('priority', 1) - '1' - >>> factory.ical('cn', u'Rasmussen, Max Møller') - 'Rasmussen\\\\, Max M\\xc3\\xb8ller' - - >>> factory.from_ical('cn', 'Rasmussen\\\\, Max M\\xc3\\xb8ller') - u'Rasmussen, Max M\\xf8ller' - - The value and parameter names don't overlap. So one factory is enough for - both kinds. - """ - - def __init__(self, *args, **kwargs): - "Set keys to upper for initial dict" - CaselessDict.__init__(self, *args, **kwargs) - self['binary'] = vBinary - self['boolean'] = vBoolean - self['cal-address'] = vCalAddress - self['date'] = vDDDTypes - self['date-time'] = vDDDTypes - self['duration'] = vDDDTypes - self['float'] = vFloat - self['integer'] = vInt - self['period'] = vPeriod - self['recur'] = vRecur - self['text'] = vText - self['time'] = vTime - self['uri'] = vUri - self['utc-offset'] = vUTCOffset - self['geo'] = vGeo - self['inline'] = vInline - self['date-time-list'] = vDDDLists - - - ################################################# - # Property types - - # These are the default types - types_map = CaselessDict({ - #################################### - # Property valye types - # Calendar Properties - 'calscale' : 'text', - 'method' : 'text', - 'prodid' : 'text', - 'version' : 'text', - # Descriptive Component Properties - 'attach' : 'uri', - 'categories' : 'text', - 'class' : 'text', - 'comment' : 'text', - 'description' : 'text', - 'geo' : 'geo', - 'location' : 'text', - 'percent-complete' : 'integer', - 'priority' : 'integer', - 'resources' : 'text', - 'status' : 'text', - 'summary' : 'text', - # Date and Time Component Properties - 'completed' : 'date-time', - 'dtend' : 'date-time', - 'due' : 'date-time', - 'dtstart' : 'date-time', - 'duration' : 'duration', - 'freebusy' : 'period', - 'transp' : 'text', - # Time Zone Component Properties - 'tzid' : 'text', - 'tzname' : 'text', - 'tzoffsetfrom' : 'utc-offset', - 'tzoffsetto' : 'utc-offset', - 'tzurl' : 'uri', - # Relationship Component Properties - 'attendee' : 'cal-address', - 'contact' : 'text', - 'organizer' : 'cal-address', - 'recurrence-id' : 'date-time', - 'related-to' : 'text', - 'url' : 'uri', - 'uid' : 'text', - # Recurrence Component Properties - 'exdate' : 'date-time-list', - 'exrule' : 'recur', - 'rdate' : 'date-time-list', - 'rrule' : 'recur', - # Alarm Component Properties - 'action' : 'text', - 'repeat' : 'integer', - 'trigger' : 'duration', - # Change Management Component Properties - 'created' : 'date-time', - 'dtstamp' : 'date-time', - 'last-modified' : 'date-time', - 'sequence' : 'integer', - # Miscellaneous Component Properties - 'request-status' : 'text', - #################################### - # parameter types (luckilly there is no name overlap) - 'altrep' : 'uri', - 'cn' : 'text', - 'cutype' : 'text', - 'delegated-from' : 'cal-address', - 'delegated-to' : 'cal-address', - 'dir' : 'uri', - 'encoding' : 'text', - 'fmttype' : 'text', - 'fbtype' : 'text', - 'language' : 'text', - 'member' : 'cal-address', - 'partstat' : 'text', - 'range' : 'text', - 'related' : 'text', - 'reltype' : 'text', - 'role' : 'text', - 'rsvp' : 'boolean', - 'sent-by' : 'cal-address', - 'tzid' : 'text', - 'value' : 'text', - }) - - - def for_property(self, name): - "Returns a the default type for a property or parameter" - return self[self.types_map.get(name, 'text')] - - def ical(self, name, value): - """ - Encodes a named value from a primitive python type to an - icalendar encoded string. - """ - type_class = self.for_property(name) - return type_class(value).ical() - - def from_ical(self, name, value): - """ - Decodes a named property or parameter value from an icalendar encoded - string to a primitive python type. - """ - type_class = self.for_property(name) - decoded = type_class.from_ical(str(value)) - return decoded diff --git a/Webcal/icalendar/tests/__init__.py b/Webcal/icalendar/tests/__init__.py deleted file mode 100644 index a003759..0000000 --- a/Webcal/icalendar/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# this is a package diff --git a/Webcal/icalendar/tests/test_icalendar.py b/Webcal/icalendar/tests/test_icalendar.py deleted file mode 100644 index a96073a..0000000 --- a/Webcal/icalendar/tests/test_icalendar.py +++ /dev/null @@ -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 diff --git a/Webcal/icalendar/tools.py b/Webcal/icalendar/tools.py deleted file mode 100644 index b6cb746..0000000 --- a/Webcal/icalendar/tools.py +++ /dev/null @@ -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) diff --git a/Webcal/icalendar/util.py b/Webcal/icalendar/util.py deleted file mode 100644 index eadc3ef..0000000 --- a/Webcal/icalendar/util.py +++ /dev/null @@ -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)) diff --git a/Webcal/plugin.py b/Webcal/plugin.py deleted file mode 100644 index f19513e..0000000 --- a/Webcal/plugin.py +++ /dev/null @@ -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 - 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): - """[] - 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): - """[] - Display the current time, 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 diff --git a/Webcal/rruler.py b/Webcal/rruler.py deleted file mode 100644 index 75bde7f..0000000 --- a/Webcal/rruler.py +++ /dev/null @@ -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) diff --git a/Webcal/test.py b/Webcal/test.py deleted file mode 100644 index d0a0cfa..0000000 --- a/Webcal/test.py +++ /dev/null @@ -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',) diff --git a/Webcal/testical.py b/Webcal/testical.py deleted file mode 100644 index d6caa5a..0000000 --- a/Webcal/testical.py +++ /dev/null @@ -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]) diff --git a/Webcal/timezones.html b/Webcal/timezones.html deleted file mode 100644 index 2263aa0..0000000 --- a/Webcal/timezones.html +++ /dev/null @@ -1,592 +0,0 @@ - - - Ubotu - - - -
-

Timezones Hi!

-

- 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 -

-

-

- Examples:
-
- For Europe/Amsterdam:
- @schedule amsterdam
- For America/North_Dakota/Center:
- @schedule North_Dakota/Center
-

-

-

All timezones

-

- Africa/Abidjan
- Africa/Accra
- Africa/Addis_Ababa
- Africa/Algiers
- Africa/Asmera
- Africa/Bamako
- Africa/Bangui
- Africa/Banjul
- Africa/Bissau
- Africa/Blantyre
- Africa/Brazzaville
- Africa/Bujumbura
- Africa/Cairo
- Africa/Casablanca
- Africa/Ceuta
- Africa/Conakry
- Africa/Dakar
- Africa/Dar_es_Salaam
- Africa/Djibouti
- Africa/Douala
- Africa/El_Aaiun
- Africa/Freetown
- Africa/Gaborone
- Africa/Harare
- Africa/Johannesburg
- Africa/Kampala
- Africa/Khartoum
- Africa/Kigali
- Africa/Kinshasa
- Africa/Lagos
- Africa/Libreville
- Africa/Lome
- Africa/Luanda
- Africa/Lubumbashi
- Africa/Lusaka
- Africa/Malabo
- Africa/Maputo
- Africa/Maseru
- Africa/Mbabane
- Africa/Mogadishu
- Africa/Monrovia
- Africa/Nairobi
- Africa/Ndjamena
- Africa/Niamey
- Africa/Nouakchott
- Africa/Ouagadougou
- Africa/Porto-Novo
- Africa/Sao_Tome
- Africa/Timbuktu
- Africa/Tripoli
- Africa/Tunis
- Africa/Windhoek -

-

-

- America/Adak
- America/Anchorage
- America/Anguilla
- America/Antigua
- America/Araguaina
- America/Argentina/Buenos_Aires
- America/Argentina/Catamarca
- America/Argentina/ComodRivadavia
- America/Argentina/Cordoba
- America/Argentina/Jujuy
- America/Argentina/La_Rioja
- America/Argentina/Mendoza
- America/Argentina/Rio_Gallegos
- America/Argentina/San_Juan
- America/Argentina/Tucuman
- America/Argentina/Ushuaia
- America/Aruba
- America/Asuncion
- America/Atka
- America/Bahia
- America/Barbados
- America/Belem
- America/Belize
- America/Boa_Vista
- America/Bogota
- America/Boise
- America/Buenos_Aires
- America/Cambridge_Bay
- America/Campo_Grande
- America/Cancun
- America/Caracas
- America/Catamarca
- America/Cayenne
- America/Cayman
- America/Chicago
- America/Chihuahua
- America/Coral_Harbour
- America/Cordoba
- America/Costa_Rica
- America/Cuiaba
- America/Curacao
- America/Danmarkshavn
- America/Dawson
- America/Dawson_Creek
- America/Denver
- America/Detroit
- America/Dominica
- America/Edmonton
- America/Eirunepe
- America/El_Salvador
- America/Ensenada
- America/Fort_Wayne
- America/Fortaleza
- America/Glace_Bay
- America/Godthab
- America/Goose_Bay
- America/Grand_Turk
- America/Grenada
- America/Guadeloupe
- America/Guatemala
- America/Guayaquil
- America/Guyana
- America/Halifax
- America/Havana
- America/Hermosillo
- America/Indiana/Indianapolis
- America/Indiana/Knox
- America/Indiana/Marengo
- America/Indiana/Vevay
- America/Indianapolis
- America/Inuvik
- America/Iqaluit
- America/Jamaica
- America/Jujuy
- America/Juneau
- America/Kentucky/Louisville
- America/Kentucky/Monticello
- America/Knox_IN
- America/La_Paz
- America/Lima
- America/Los_Angeles
- America/Louisville
- America/Maceio
- America/Managua
- America/Manaus
- America/Martinique
- America/Mazatlan
- America/Mendoza
- America/Menominee
- America/Merida
- America/Mexico_City
- America/Miquelon
- America/Monterrey
- America/Montevideo
- America/Montreal
- America/Montserrat
- America/Nassau
- America/New_York
- America/Nipigon
- America/Nome
- America/Noronha
- America/North_Dakota/Center
- America/Panama
- America/Pangnirtung
- America/Paramaribo
- America/Phoenix
- America/Port-au-Prince
- America/Port_of_Spain
- America/Porto_Acre
- America/Porto_Velho
- America/Puerto_Rico
- America/Rainy_River
- America/Rankin_Inlet
- America/Recife
- America/Regina
- America/Rio_Branco
- America/Rosario
- America/Santiago
- America/Santo_Domingo
- America/Sao_Paulo
- America/Scoresbysund
- America/Shiprock
- America/St_Johns
- America/St_Kitts
- America/St_Lucia
- America/St_Thomas
- America/St_Vincent
- America/Swift_Current
- America/Tegucigalpa
- America/Thule
- America/Thunder_Bay
- America/Tijuana
- America/Toronto
- America/Tortola
- America/Vancouver
- America/Virgin
- America/Whitehorse
- America/Winnipeg
- America/Yakutat
- America/Yellowknife -

-

-

- Antarctica/Casey
- Antarctica/Davis
- Antarctica/DumontDUrville
- Antarctica/Mawson
- Antarctica/McMurdo
- Antarctica/Palmer
- Antarctica/Rothera
- Antarctica/South_Pole
- Antarctica/Syowa
- Antarctica/Vostok -

-

-

- Arctic/Longyearbyen -

-

-

- Asia/Aden
- Asia/Almaty
- Asia/Amman
- Asia/Anadyr
- Asia/Aqtau
- Asia/Aqtobe
- Asia/Ashgabat
- Asia/Ashkhabad
- Asia/Baghdad
- Asia/Bahrain
- Asia/Baku
- Asia/Bangkok
- Asia/Beirut
- Asia/Bishkek
- Asia/Brunei
- Asia/Calcutta
- Asia/Choibalsan
- Asia/Chongqing
- Asia/Chungking
- Asia/Colombo
- Asia/Dacca
- Asia/Damascus
- Asia/Dhaka
- Asia/Dili
- Asia/Dubai
- Asia/Dushanbe
- Asia/Gaza
- Asia/Harbin
- Asia/Hong_Kong
- Asia/Hovd
- Asia/Irkutsk
- Asia/Istanbul
- Asia/Jakarta
- Asia/Jayapura
- Asia/Jerusalem
- Asia/Kabul
- Asia/Kamchatka
- Asia/Karachi
- Asia/Kashgar
- Asia/Katmandu
- Asia/Krasnoyarsk
- Asia/Kuala_Lumpur
- Asia/Kuching
- Asia/Kuwait
- Asia/Macao
- Asia/Macau
- Asia/Magadan
- Asia/Makassar
- Asia/Manila
- Asia/Muscat
- Asia/Nicosia
- Asia/Novosibirsk
- Asia/Omsk
- Asia/Oral
- Asia/Phnom_Penh
- Asia/Pontianak
- Asia/Pyongyang
- Asia/Qatar
- Asia/Qyzylorda
- Asia/Rangoon
- Asia/Riyadh
- Asia/Saigon
- Asia/Sakhalin
- Asia/Samarkand
- Asia/Seoul
- Asia/Shanghai
- Asia/Singapore
- Asia/Taipei
- Asia/Tashkent
- Asia/Tbilisi
- Asia/Tehran
- Asia/Tel_Aviv
- Asia/Thimbu
- Asia/Thimphu
- Asia/Tokyo
- Asia/Ujung_Pandang
- Asia/Ulaanbaatar
- Asia/Ulan_Bator
- Asia/Urumqi
- Asia/Vientiane
- Asia/Vladivostok
- Asia/Yakutsk
- Asia/Yekaterinburg
- Asia/Yerevan -

-

-

- Atlantic/Azores
- Atlantic/Bermuda
- Atlantic/Canary
- Atlantic/Cape_Verde
- Atlantic/Faeroe
- Atlantic/Jan_Mayen
- Atlantic/Madeira
- Atlantic/Reykjavik
- Atlantic/South_Georgia
- Atlantic/St_Helena
- Atlantic/Stanley -

-

-

- Australia/ACT
- Australia/Adelaide
- Australia/Brisbane
- Australia/Broken_Hill
- Australia/Canberra
- Australia/Currie
- Australia/Darwin
- Australia/Hobart
- Australia/LHI
- Australia/Lindeman
- Australia/Lord_Howe
- Australia/Melbourne
- Australia/NSW
- Australia/North
- Australia/Perth
- Australia/Queensland
- Australia/South
- Australia/Sydney
- Australia/Tasmania
- Australia/Victoria
- Australia/West
- Australia/Yancowinna -

-

-

- Brazil/Acre
- Brazil/DeNoronha
- Brazil/East
- Brazil/West -

-

-

- CET
- CST6CDT -

-

-

- Canada/Atlantic
- Canada/Central
- Canada/East-Saskatchewan
- Canada/Eastern
- Canada/Mountain
- Canada/Newfoundland
- Canada/Pacific
- Canada/Saskatchewan
- Canada/Yukon
- Chile/Continental
- Chile/EasterIsland -

-

-

- Cuba
- EET
- EST
- EST5EDT
- Egypt
- Eire -

-

-

- Etc/Greenwich
- Etc/UCT
- Etc/UTC
- Etc/Universal
- Etc/Zulu -

-

- Europe/Amsterdam
- Europe/Andorra
- Europe/Athens
- Europe/Belfast
- Europe/Belgrade
- Europe/Berlin
- Europe/Bratislava
- Europe/Brussels
- Europe/Bucharest
- Europe/Budapest
- Europe/Chisinau
- Europe/Copenhagen
- Europe/Dublin
- Europe/Gibraltar
- Europe/Helsinki
- Europe/Istanbul
- Europe/Kaliningrad
- Europe/Kiev
- Europe/Lisbon
- Europe/Ljubljana
- Europe/London
- Europe/Luxembourg
- Europe/Madrid
- Europe/Malta
- Europe/Mariehamn
- Europe/Minsk
- Europe/Monaco
- Europe/Moscow
- Europe/Nicosia
- Europe/Oslo
- Europe/Paris
- Europe/Prague
- Europe/Riga
- Europe/Rome
- Europe/Samara
- Europe/San_Marino
- Europe/Sarajevo
- Europe/Simferopol
- Europe/Skopje
- Europe/Sofia
- Europe/Stockholm
- Europe/Tallinn
- Europe/Tirane
- Europe/Tiraspol
- Europe/Uzhgorod
- Europe/Vaduz
- Europe/Vatican
- Europe/Vienna
- Europe/Vilnius
- Europe/Warsaw
- Europe/Zagreb
- Europe/Zaporozhye
- Europe/Zurich -

-

-

- GB
- GB-Eire
- Greenwich
- HST
- Hongkong
- Iceland -

-

-

- Indian/Antananarivo
- Indian/Chagos
- Indian/Christmas
- Indian/Cocos
- Indian/Comoro
- Indian/Kerguelen
- Indian/Mahe
- Indian/Maldives
- Indian/Mauritius
- Indian/Mayotte
- Indian/Reunion -

-

-

- Iran
- Israel
- Jamaica
- Japan
- Kwajalein
- Libya
- MET
- MST
- MST7MDT -

-

-

- Mexico/BajaNorte
- Mexico/BajaSur
- Mexico/General -

-

-

- NZ
- NZ-CHAT
- Navajo
- PRC
- PST8PDT -

-

-

- Pacific/Apia
- Pacific/Auckland
- Pacific/Chatham
- Pacific/Easter
- Pacific/Efate
- Pacific/Enderbury
- Pacific/Fakaofo
- Pacific/Fiji
- Pacific/Funafuti
- Pacific/Galapagos
- Pacific/Gambier
- Pacific/Guadalcanal
- Pacific/Guam
- Pacific/Honolulu
- Pacific/Johnston
- Pacific/Kiritimati
- Pacific/Kosrae
- Pacific/Kwajalein
- Pacific/Majuro
- Pacific/Marquesas
- Pacific/Midway
- Pacific/Nauru
- Pacific/Niue
- Pacific/Norfolk
- Pacific/Noumea
- Pacific/Pago_Pago
- Pacific/Palau
- Pacific/Pitcairn
- Pacific/Ponape
- Pacific/Port_Moresby
- Pacific/Rarotonga
- Pacific/Saipan
- Pacific/Samoa
- Pacific/Tahiti
- Pacific/Tarawa
- Pacific/Tongatapu
- Pacific/Truk
- Pacific/Wake
- Pacific/Wallis
- Pacific/Yap -

-

-

- Poland
- Portugal
- ROC
- ROK
- Singapore
- Turkey
- UCT -

-

-

- US/Alaska
- US/Aleutian
- US/Arizona
- US/Central
- US/East-Indiana
- US/Eastern
- US/Hawaii
- US/Indiana-Starke
- US/Michigan
- US/Mountain
- US/Pacific
- US/Pacific-New
- US/Samoa -

-

-

- UTC
- Universal
- W-SU
- WET
- Zulu
- posixrules -

-
- - diff --git a/bans.cgi b/bans.cgi deleted file mode 120000 index ca19de1..0000000 --- a/bans.cgi +++ /dev/null @@ -1 +0,0 @@ -Bantracker/cgi/bans.cgi \ No newline at end of file diff --git a/bans.tmpl b/bans.tmpl deleted file mode 120000 index 203d00e..0000000 --- a/bans.tmpl +++ /dev/null @@ -1 +0,0 @@ -Bantracker/cgi/bans.tmpl \ No newline at end of file diff --git a/empty.tmpl b/empty.tmpl deleted file mode 120000 index c03fee4..0000000 --- a/empty.tmpl +++ /dev/null @@ -1 +0,0 @@ -Bantracker/cgi/empty.tmpl \ No newline at end of file diff --git a/timezones.html b/timezones.html deleted file mode 120000 index b835898..0000000 --- a/timezones.html +++ /dev/null @@ -1 +0,0 @@ -Webcal/timezones.html \ No newline at end of file