2003-10-16 16:53:42 +02:00
|
|
|
#!/usr/bin/env python
|
2003-10-07 08:50:21 +02:00
|
|
|
|
|
|
|
###
|
2003-10-10 07:47:19 +02:00
|
|
|
# Copyright (c) 2003, Daniel DiPaolo
|
2003-10-07 08:50:21 +02:00
|
|
|
# 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.
|
|
|
|
###
|
|
|
|
|
|
|
|
"""
|
2003-10-10 07:47:19 +02:00
|
|
|
The Todo module allows registered users to keep their own personal list of
|
|
|
|
tasks to do, with an optional priority for each.
|
2003-10-07 08:50:21 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
import plugins
|
|
|
|
|
2003-10-10 07:47:19 +02:00
|
|
|
import glob
|
2003-10-07 08:50:21 +02:00
|
|
|
import time
|
|
|
|
import getopt
|
2003-10-10 07:47:19 +02:00
|
|
|
import string
|
2003-10-07 08:50:21 +02:00
|
|
|
import os.path
|
|
|
|
|
|
|
|
import sqlite
|
|
|
|
|
|
|
|
import conf
|
2003-10-10 07:47:19 +02:00
|
|
|
import debug
|
2003-10-07 08:50:21 +02:00
|
|
|
import ircdb
|
|
|
|
import utils
|
|
|
|
import privmsgs
|
|
|
|
import callbacks
|
|
|
|
|
|
|
|
dbfilename = os.path.join(conf.dataDir, 'Todo.db')
|
|
|
|
|
|
|
|
def configure(onStart, afterConnect, advanced):
|
|
|
|
# This will be called by setup.py to configure this module. onStart and
|
|
|
|
# afterConnect are both lists. Append to onStart the commands you would
|
|
|
|
# like to be run when the bot is started; append to afterConnect the
|
|
|
|
# commands you would like to be run when the bot has finished connecting.
|
|
|
|
from questions import expect, anything, something, yn
|
|
|
|
onStart.append('load Todo')
|
|
|
|
|
|
|
|
example = utils.wrapLines("""
|
|
|
|
Add an example IRC session using this module here.
|
|
|
|
""")
|
|
|
|
|
|
|
|
class Todo(callbacks.Privmsg):
|
|
|
|
def __init__(self):
|
|
|
|
callbacks.Privmsg.__init__(self)
|
|
|
|
self.makeDB(dbfilename)
|
|
|
|
|
|
|
|
def makeDB(self, filename):
|
|
|
|
"""create Todo database and tables"""
|
|
|
|
if os.path.exists(filename):
|
|
|
|
self.db = sqlite.connect(filename)
|
|
|
|
return
|
|
|
|
self.db = sqlite.connect(filename, converters={'bool': bool})
|
|
|
|
cursor = self.db.cursor()
|
|
|
|
cursor.execute("""CREATE TABLE todo (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
priority INTEGER,
|
|
|
|
added_at TIMESTAMP,
|
|
|
|
userid INTEGER,
|
2003-10-28 18:40:41 +01:00
|
|
|
task TEXT,
|
|
|
|
active BOOLEAN
|
2003-10-07 08:50:21 +02:00
|
|
|
)""")
|
|
|
|
self.db.commit()
|
|
|
|
|
|
|
|
def die(self):
|
|
|
|
self.db.commit()
|
|
|
|
self.db.close()
|
|
|
|
del self.db
|
|
|
|
|
|
|
|
def todo(self, irc, msg, args):
|
2003-10-28 18:40:41 +01:00
|
|
|
"""[<username>|<task id>]
|
2003-10-07 08:50:21 +02:00
|
|
|
|
|
|
|
Retrieves a task for the given task id. If no task id is given, it
|
|
|
|
will return a list of task ids that that user has added to their todo
|
|
|
|
list.
|
|
|
|
"""
|
2003-10-28 18:40:41 +01:00
|
|
|
arg = privmsgs.getArgs(args, needed=0, optional=1)
|
2003-10-07 08:50:21 +02:00
|
|
|
|
2003-10-28 18:40:41 +01:00
|
|
|
userid = None
|
|
|
|
taskid = None
|
|
|
|
# Figure out what userid and taskid need to be set to (if anything)
|
|
|
|
if not arg:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
taskid = int(arg)
|
|
|
|
except ValueError:
|
|
|
|
try:
|
|
|
|
userid = ircdb.users.getUserId(arg)
|
|
|
|
except KeyError:
|
|
|
|
irc.error('%r is not a valid task id or username' % arg)
|
|
|
|
return
|
|
|
|
# Everything needs a cursor, might as well plop it outside
|
|
|
|
if not userid and not taskid:
|
2003-10-07 08:50:21 +02:00
|
|
|
try:
|
2003-10-28 18:40:41 +01:00
|
|
|
id = ircdb.users.getUserId(msg.prefix)
|
|
|
|
except KeyError:
|
|
|
|
irc.error(msg, conf.replyNotRegistered)
|
2003-10-07 08:50:21 +02:00
|
|
|
return
|
|
|
|
cursor = self.db.cursor()
|
2003-10-28 18:40:41 +01:00
|
|
|
cursor.execute("""SELECT id, task FROM todo
|
|
|
|
WHERE userid = %s AND active = 1""", id)
|
2003-10-07 08:50:21 +02:00
|
|
|
if cursor.rowcount == 0:
|
2003-10-28 18:40:41 +01:00
|
|
|
irc.reply(msg, 'You have no tasks in your todo list.')
|
|
|
|
return
|
|
|
|
s = ['#%s: %s' % (item[0], utils.ellipsisify(item[1], 50)) \
|
|
|
|
for item in cursor.fetchall()]
|
|
|
|
irc.reply(msg, utils.commaAndify(s))
|
|
|
|
else:
|
|
|
|
cursor = self.db.cursor()
|
|
|
|
if userid:
|
|
|
|
cursor.execute("""SELECT id, task FROM todo
|
|
|
|
WHERE userid = %s AND active = 1""", userid)
|
|
|
|
if cursor.rowcount == 0:
|
|
|
|
irc.reply(msg, 'That user has no todos.')
|
|
|
|
return
|
|
|
|
s = ['#%s: %s' % (item[0], utils.ellipsisify(item[1], 50)) \
|
|
|
|
for item in cursor.fetchall()]
|
|
|
|
reply = "%s for %s: %s" % \
|
|
|
|
(utils.pluralize(len(s), 'Todo'), arg,
|
|
|
|
utils.commaAndify(s))
|
|
|
|
irc.reply(msg, reply)
|
|
|
|
return
|
2003-10-07 08:50:21 +02:00
|
|
|
else:
|
2003-10-28 18:40:41 +01:00
|
|
|
cursor.execute("""SELECT userid, priority, added_at, task
|
|
|
|
FROM todo WHERE id = %s
|
|
|
|
AND active = 1""", taskid)
|
|
|
|
if cursor.rowcount == 0:
|
|
|
|
irc.error(msg, '%r is not a valid task id' % taskid)
|
|
|
|
return
|
|
|
|
userid, pri, added_at, task = cursor.fetchone()
|
|
|
|
# Construct and return the reply
|
|
|
|
username = ircdb.users.getUser(userid).name
|
2003-10-07 08:50:21 +02:00
|
|
|
if pri == 0:
|
|
|
|
priority = ""
|
|
|
|
else:
|
|
|
|
priority = ", priority: %s" % pri
|
|
|
|
added_time = time.strftime(conf.humanTimestampFormat,
|
|
|
|
time.localtime(int(added_at)))
|
2003-10-28 18:40:41 +01:00
|
|
|
s = "Todo for %s: %s%s (Added at %s)" % \
|
|
|
|
(username, task, priority, added_time)
|
2003-10-07 08:50:21 +02:00
|
|
|
irc.reply(msg, s)
|
|
|
|
|
2003-10-21 06:50:03 +02:00
|
|
|
def add(self, irc, msg, args):
|
2003-10-07 08:50:21 +02:00
|
|
|
"""[--priority=<num>] <text>
|
|
|
|
|
|
|
|
Adds <text> as a task in your own personal todo list. The optional
|
|
|
|
priority argument allows you to set a task as a high or low priority.
|
|
|
|
Any integer is valid.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
id = ircdb.users.getUserId(msg.prefix)
|
|
|
|
except KeyError:
|
|
|
|
irc.error(msg, conf.replyNotRegistered)
|
|
|
|
return
|
|
|
|
(optlist, rest) = getopt.getopt(args, '', ['priority='])
|
|
|
|
priority = 0
|
|
|
|
for (option, arg) in optlist:
|
|
|
|
if option == '--priority':
|
|
|
|
try:
|
|
|
|
priority = int(arg)
|
|
|
|
except ValueError, e:
|
2003-10-22 20:29:02 +02:00
|
|
|
irc.error(msg, '%r is an invalid priority' % arg)
|
2003-10-07 08:50:21 +02:00
|
|
|
return
|
2003-10-10 07:47:19 +02:00
|
|
|
text = privmsgs.getArgs(rest, needed=1)
|
2003-10-07 08:50:21 +02:00
|
|
|
cursor = self.db.cursor()
|
2003-10-28 18:40:41 +01:00
|
|
|
cursor.execute("""INSERT INTO todo
|
|
|
|
VALUES (NULL, %s, %s, %s, %s, 1)""",
|
|
|
|
priority, int(time.time()), id, text)
|
2003-10-07 08:50:21 +02:00
|
|
|
self.db.commit()
|
|
|
|
irc.reply(msg, conf.replySuccess)
|
|
|
|
|
2003-10-21 06:50:03 +02:00
|
|
|
def remove(self, irc, msg, args):
|
2003-10-07 08:50:21 +02:00
|
|
|
"""<task id>
|
|
|
|
|
|
|
|
Removes <task id> from your personal todo list.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
id = ircdb.users.getUserId(msg.prefix)
|
|
|
|
except KeyError:
|
|
|
|
irc.error(msg, conf.replyNotRegistered)
|
|
|
|
return
|
|
|
|
|
|
|
|
taskid = privmsgs.getArgs(args, needed=1)
|
|
|
|
cursor = self.db.cursor()
|
|
|
|
cursor.execute("""SELECT * FROM todo
|
2003-10-28 18:40:41 +01:00
|
|
|
WHERE id = %s AND userid = %s
|
|
|
|
AND active = 1""", taskid, id)
|
2003-10-07 08:50:21 +02:00
|
|
|
if cursor.rowcount == 0:
|
|
|
|
irc.error(msg, 'None of your tasks match that id.')
|
|
|
|
return
|
2003-10-28 18:40:41 +01:00
|
|
|
cursor.execute("""UPDATE todo SET active = 0 WHERE id = %s""", taskid)
|
2003-10-07 08:50:21 +02:00
|
|
|
self.db.commit()
|
|
|
|
irc.reply(msg, conf.replySuccess)
|
|
|
|
|
2003-10-10 07:47:19 +02:00
|
|
|
_sqlTrans = string.maketrans('*?', '%_')
|
2003-10-21 06:50:03 +02:00
|
|
|
def search(self, irc, msg, args):
|
2003-10-10 07:47:19 +02:00
|
|
|
"""[--{regexp,exact}=<value>] [<glob>]
|
|
|
|
|
|
|
|
Searches the keyspace for tasks matching <glob>. If --regexp is given,
|
|
|
|
it associated value is taken as a regexp and matched against the tasks;
|
|
|
|
if --exact is given, its associated value is taken as an exact string
|
|
|
|
to match against the task.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
id = ircdb.users.getUserId(msg.prefix)
|
|
|
|
except KeyError:
|
|
|
|
irc.error(msg, conf.replyNotRegistered)
|
|
|
|
return
|
|
|
|
|
|
|
|
(optlist, rest) = getopt.getopt(args, '', ['regexp=', 'exact='])
|
|
|
|
if not optlist and not rest:
|
|
|
|
raise callbacks.ArgumentError
|
2003-10-28 18:40:41 +01:00
|
|
|
criteria = ['userid = %s' % id, 'active = 1']
|
2003-10-10 07:47:19 +02:00
|
|
|
formats = []
|
|
|
|
predicateName = 'p'
|
|
|
|
for (option, arg) in optlist:
|
|
|
|
if option == '--exact':
|
|
|
|
criteria.append('task LIKE %s')
|
|
|
|
formats.append('%' + arg + '%')
|
|
|
|
elif option == '--regexp':
|
|
|
|
criteria.append('%s(task)' % predicateName)
|
|
|
|
try:
|
|
|
|
r = utils.perlReToPythonRe(arg)
|
|
|
|
except ValueError, e:
|
2003-10-22 20:29:02 +02:00
|
|
|
irc.error(msg, '%r is not a valid regular expression' %
|
|
|
|
arg)
|
2003-10-10 07:47:19 +02:00
|
|
|
return
|
|
|
|
def p(s, r=r):
|
|
|
|
return int(bool(r.search(s)))
|
|
|
|
self.db.create_function(predicateName, 1, p)
|
|
|
|
predicateName += 'p'
|
|
|
|
for glob in rest:
|
|
|
|
criteria.append('task LIKE %s')
|
|
|
|
formats.append(glob.translate(self._sqlTrans))
|
|
|
|
cursor = self.db.cursor()
|
|
|
|
sql = """SELECT id, task FROM todo WHERE %s""" % ' AND '.join(criteria)
|
|
|
|
cursor.execute(sql, formats)
|
|
|
|
if cursor.rowcount == 0:
|
|
|
|
irc.reply(msg, 'No tasks matched that query.')
|
|
|
|
else:
|
|
|
|
tasks = ['#%s: %s' % (item[0], utils.ellipsisify(item[1], 50)) \
|
|
|
|
for item in cursor.fetchall()]
|
|
|
|
irc.reply(msg, utils.commaAndify(tasks))
|
|
|
|
|
2003-10-24 09:12:51 +02:00
|
|
|
def setpriority(self, irc, msg, args):
|
|
|
|
"""<id> <priority>
|
|
|
|
|
|
|
|
Sets the priority of the todo with the given id to the specified value.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
user_id = ircdb.users.getUserId(msg.prefix)
|
|
|
|
except KeyError:
|
|
|
|
irc.error(msg, conf.replyNotRegistered)
|
|
|
|
return
|
|
|
|
(id, priority) = privmsgs.getArgs(args, needed=2)
|
|
|
|
cursor = self.db.cursor()
|
|
|
|
cursor.execute("""SELECT userid, priority FROM todo
|
2003-10-28 18:40:41 +01:00
|
|
|
WHERE id = %s AND active = 1""", id)
|
2003-10-24 09:12:51 +02:00
|
|
|
if cursor.rowcount == 0:
|
|
|
|
irc.error(msg, 'No note with id %d' % id)
|
|
|
|
return
|
|
|
|
(userid, oldpriority) = cursor.fetchone()
|
|
|
|
if userid != user_id:
|
|
|
|
irc.error(msg, 'Todo #%d does not belong to you.' % id)
|
|
|
|
return
|
|
|
|
# If we make it here, we're okay
|
|
|
|
cursor.execute("""UPDATE todo SET priority = %s
|
|
|
|
WHERE id = %s""", priority, id)
|
|
|
|
self.db.commit()
|
|
|
|
irc.reply(msg, conf.replySuccess)
|
|
|
|
|
2003-10-28 19:43:43 +01:00
|
|
|
def change(self, irc, msg, args):
|
|
|
|
"""<task id> <regexp>
|
|
|
|
|
|
|
|
Modify the task with the given id using the supplied regexp.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
userid = ircdb.users.getUserId(msg.prefix)
|
|
|
|
except KeyError:
|
|
|
|
irc.error(msg, conf.replyNotRegistered)
|
|
|
|
return
|
|
|
|
taskid, regexp = privmsgs.getArgs(args, needed=2)
|
|
|
|
# Check the regexp first, it's easier and doesn't require a db query
|
|
|
|
try:
|
|
|
|
replacer = utils.perlReToReplacer(regexp)
|
|
|
|
except ValueError:
|
|
|
|
irc.error(msg, '%r is not a valid regexp' % regexp)
|
|
|
|
return
|
|
|
|
cursor = self.db.cursor()
|
|
|
|
cursor.execute("""SELECT task FROM todo
|
|
|
|
WHERE userid = %s AND id = %s
|
|
|
|
AND active = 1""", userid, taskid)
|
|
|
|
if cursor.rowcount == 0:
|
|
|
|
irc.error(msg, '%r is not a valid task id' % taskid)
|
|
|
|
return
|
|
|
|
newtext = replacer(cursor.fetchone()[0])
|
|
|
|
cursor.execute("""UPDATE todo SET task = %s
|
|
|
|
WHERE id = %s""", newtext, taskid)
|
|
|
|
self.db.commit()
|
|
|
|
irc.reply(msg, conf.replySuccess)
|
|
|
|
|
2003-10-07 08:50:21 +02:00
|
|
|
Class = Todo
|
|
|
|
|
|
|
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|