diff --git a/plugins/Seen.py b/plugins/Seen.py new file mode 100644 index 000000000..f0ece765b --- /dev/null +++ b/plugins/Seen.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +### +# Copyright (c) 2002, 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. +### + +""" +Keeps track of the last time a user was seen on a channel. +""" + +__revision__ = "$Id$" + +import plugins + +import os +import re +import sets +import time +import getopt +import string +from itertools import imap, ifilter + +import log +import conf +import utils +import world +import ircdb +import ircmsgs +import plugins +import ircutils +import privmsgs +import registry +import callbacks + +class SeenDB(object): + def __init__(self, filename): + self.filename = filename + self.channels = ircutils.IrcDict() + try: + fd = file(filename) + except EnvironmentError, e: + log.info('Couldn\'t open %s: %s', filename, e) + return + lineno = 0 + for line in fd: + lineno += 1 + line = line.rstrip('\r\n') + (channel, nickOrId, when, said) = line.split(':', 3) + if channel not in self.channels: + self.channels[channel] = ircutils.IrcDict() + try: + self.channels[channel][nickOrId] = (float(when), said) + except ValueError: # Invalid float. + log.warning('Invalid line #%s in %s: %s',lineno,filename,line) + continue + fd.close() + + def flush(self): + fd = file(self.filename, 'w') + for (channel, d) in self.channels.iteritems(): + for (nickOrId, (when, said)) in d.iteritems(): + fd.write('%s:%s:%s:%s\n' % (channel, nickOrId, when, said)) + fd.close() + + def close(self): + self.flush() + self.channels.clear() + + def update(self, channel, nickOrId, said): + if channel not in self.channels: + self.channels[channel] = ircutils.IrcDict() + when = time.time() + self.channels[channel][str(nickOrId)] = (when, said) + + def seen(self, channel, nickOrId): + return self.channels[channel][str(nickOrId)] + + +filename = os.path.join(conf.supybot.directories.data(), 'Seen.db') + +class Seen(callbacks.Privmsg): + noIgnore = True + def __init__(self): + self.db = SeenDB(filename) + world.flushers.append(self.db.flush) + callbacks.Privmsg.__init__(self) + + def die(self): + world.flushers.remove(self.db.flush) + self.db.close() + callbacks.Privmsg.die(self) + + def doPrivmsg(self, irc, msg): + if ircutils.isChannel(msg.args[0]): + said = ircmsgs.prettyPrint(msg) + channel = msg.args[0] + self.db.update(channel, msg.nick, said) + try: + id = ircdb.users.getUserId(msg.prefix) + self.db.update(channel, id, said) + except KeyError: + pass # Not in the database. + + def seen(self, irc, msg, args): + """[] [--user] + + Returns the last time was seen and what was last seen + saying. --user will look for user instead of using as + a nick (registered users, remember, can be recognized under any number + of nicks) is only necessary if the message isn't sent on the + channel itself. + """ + channel = privmsgs.getChannel(msg, args) + (optlist, rest) = getopt.getopt(args, '', ['user']) + name = privmsgs.getArgs(rest) + nickOrId = name + if ('--user', '') in optlist: + try: + nickOrId = ircdb.users.getUserId(name) + except KeyError: + try: + hostmask = irc.state.nickToHostmask(name) + nickOrId = ircdb.users.getUserId(hostmask) + except KeyError: + irc.errorNoUser() + return + try: + (when, said) = self.db.seen(channel, nickOrId) + irc.reply('%s was last seen here %s ago saying: %s' % + (name, utils.timeElapsed(time.time()-when), said)) + except KeyError: + irc.error('I have not seen %s.' % name) + + +Class = Seen + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_ChannelDB.py b/test/test_ChannelStats.py similarity index 66% rename from test/test_ChannelDB.py rename to test/test_ChannelStats.py index ad014d464..27ccabb10 100644 --- a/test/test_ChannelDB.py +++ b/test/test_ChannelStats.py @@ -39,8 +39,8 @@ except ImportError: sqlite = None if sqlite is not None: - class ChannelDBTestCase(ChannelPluginTestCase): - plugins = ('ChannelDB', 'User') + class ChannelStatsTestCase(ChannelPluginTestCase): + plugins = ('ChannelStats', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' @@ -57,43 +57,28 @@ if sqlite is not None: self.assertNotError('channelstats') def testStats(self): - self.assertError('channeldb stats %s' % self.nick) - self.assertNotError('channeldb stats %s' % self.nick) - self.assertNotError('channeldb stats %s' % self.nick.upper()) - self.assertNotError('channeldb stats') - self.assertRegexp('channeldb stats', self.nick) + self.assertError('channelstats stats %s' % self.nick) + self.assertNotError('channelstats stats %s' % self.nick) + self.assertNotError('channelstats stats %s' % self.nick.upper()) + self.assertNotError('channelstats stats') + self.assertRegexp('channelstats stats', self.nick) def testSelfStats(self): - self.assertError('channeldb stats %s' % self.irc.nick) - self.assertNotError('channeldb stats %s' % self.irc.nick) - self.assertNotError('channeldb stats %s' % self.irc.nick) + self.assertError('channelstats stats %s' % self.irc.nick) + self.assertNotError('channelstats stats %s' % self.irc.nick) + self.assertNotError('channelstats stats %s' % self.irc.nick) id = ircdb.users.getUserId(self.prefix) u = ircdb.users.getUser(id) u.addCapability(ircdb.makeChannelCapability(self.channel, 'op')) ircdb.users.setUser(id, u) - self.assertNotError('channeldb config self-stats off') - m1 = self.getMsg('channeldb stats %s' % self.irc.nick) - m2 = self.getMsg('channeldb stats %s' % self.irc.nick) + self.assertNotError('channelstats config self-stats off') + m1 = self.getMsg('channelstats stats %s' % self.irc.nick) + m2 = self.getMsg('channelstats stats %s' % self.irc.nick) self.assertEqual(m1.args[1], m2.args[1]) - def testNoKeyErrorEscapeFromSeen(self): - self.assertRegexp('seen asldfkjasdlfkj', '^I have not seen') - self.assertNotRegexp('seen asldfkjasdlfkj', 'KeyError') - def testNoKeyErrorStats(self): self.assertNotRegexp('stats sweede', 'KeyError') - def testSeen(self): - self.assertNotError('list') - self.assertNotError('seen %s' % self.nick) - m = self.assertNotError('seen %s' % self.nick.upper()) - self.failUnless(self.nick.upper() in m.args[1]) - self.assertRegexp('seen --user %s' % self.nick, - '^%s was last seen' % self.nick) - - def testSeenNoUser(self): - self.assertNotRegexp('seen --user alsdkfjalsdfkj', 'KeyError') - # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_Seen.py b/test/test_Seen.py new file mode 100644 index 000000000..1bf0f8e7c --- /dev/null +++ b/test/test_Seen.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +### +# Copyright (c) 2002, 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 testsupport import * + +import ircdb + +class ChannelDBTestCase(ChannelPluginTestCase): + plugins = ('Seen',) + def setUp(self): + ChannelPluginTestCase.setUp(self) + self.prefix = 'foo!bar@baz' + self.nick = 'foo' + self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, + 'register foo bar', + prefix=self.prefix)) + _ = self.irc.takeMsg() + ircdb.users.getUser(self.nick).addCapability(self.channel + '.op') + + def testNoKeyErrorEscapeFromSeen(self): + self.assertRegexp('seen asldfkjasdlfkj', '^I have not seen') + self.assertNotRegexp('seen asldfkjasdlfkj', 'KeyError') + + def testSeen(self): + self.assertNotError('list') + self.assertNotError('seen %s' % self.nick) + m = self.assertNotError('seen %s' % self.nick.upper()) + self.failUnless(self.nick.upper() in m.args[1]) + self.assertRegexp('seen --user %s' % self.nick, + '^%s was last seen' % self.nick) + + def testSeenNoUser(self): + self.assertNotRegexp('seen --user alsdkfjalsdfkj', 'KeyError') + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +