diff --git a/plugins/Seen/plugin.py b/plugins/Seen/plugin.py index 8ce098067..b487334dc 100644 --- a/plugins/Seen/plugin.py +++ b/plugins/Seen/plugin.py @@ -36,6 +36,7 @@ import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * +import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils @@ -61,7 +62,7 @@ class SeenDB(plugins.ChannelUserDB): seen = time.time() self[channel, nickOrId] = (seen, saying) self[channel, ''] = (seen, saying) - + def seenWildcard(self, channel, nick): nicks = [] nickRe = re.compile('.*'.join(nick.split('*')), re.I) @@ -88,6 +89,7 @@ class SeenDB(plugins.ChannelUserDB): return self[channel, nickOrId] filename = conf.supybot.directories.data.dirize('Seen.db') +anyfilename = conf.supybot.directories.data.dirize('Seen.any.db') class Seen(callbacks.Plugin): noIgnore = True @@ -95,6 +97,9 @@ class Seen(callbacks.Plugin): self.__parent = super(Seen, self) self.__parent.__init__(irc) self.db = SeenDB(filename) + self.anydb = SeenDB(anyfilename) + self.lastmsg = {} + self.ircstates = {} world.flushers.append(self.db.flush) def die(self): @@ -103,36 +108,93 @@ class Seen(callbacks.Plugin): else: self.log.debug('Odd, no flush in flushers: %r', world.flushers) self.db.close() + if self.anydb.flush in world.flushers: + world.flushers.remove(self.anydb.flush) + else: + self.log.debug('Odd, no flush in flushers: %r', world.flushers) + self.anydb.close() self.__parent.die() + def __call__(self, irc, msg): + try: + if irc not in self.ircstates: + self._addIrc(irc) + self.ircstates[irc].addMsg(irc, self.lastmsg[irc]) + finally: + self.lastmsg[irc] = msg + self.__parent.__call__(irc, msg) + + def _addIrc(self, irc): + # Let's just be extra-special-careful here. + if irc not in self.ircstates: + self.ircstates[irc] = irclib.IrcState() + if irc not in self.lastmsg: + self.lastmsg[irc] = ircmsgs.ping('this is just a fake message') + for channel in irc.state.channels: + irc.queueMsg(ircmsgs.who(channel)) + irc.queueMsg(ircmsgs.names(channel)) + def doPrivmsg(self, irc, msg): if irc.isChannel(msg.args[0]): + channel = msg.args[0] said = ircmsgs.prettyPrint(msg) - channel = plugins.getChannel(msg.args[0]) self.db.update(channel, msg.nick, said) + self.anydb.update(channel, msg.nick, said) try: id = ircdb.users.getUserId(msg.prefix) self.db.update(channel, id, said) + self.anydb.update(channel, id, said) except KeyError: pass # Not in the database. - def seen(self, irc, msg, args, channel, name): - """[] + def doPart(self, irc, msg): + channel = msg.args[0] + said = ircmsgs.prettyPrint(msg) + self.anydb.update(channel, msg.nick, said) + try: + id = ircdb.users.getUserId(msg.prefix) + self.anydb.update(channel, id, said) + except KeyError: + pass # Not in the database. + doJoin = doPart + doKick = doPart - Returns the last time was seen and what was last seen - saying. is only necessary if the message isn't sent on the - channel itself. - """ + def doQuit(self, irc, msg): + said = ircmsgs.prettyPrint(msg) + if irc not in self.ircstates: + return + try: + id = ircdb.users.getUserId(msg.prefix) + except KeyError: + id = None # Not in the database. + for channel in self.ircstates[irc].channels: + if msg.nick in self.ircstates[irc].channels[channel].users: + self.anydb.update(channel, msg.nick, said) + if id is not None: + self.anydb.update(channel, id, said) + doNick = doQuit + + def doMode(self, irc, msg): + # Filter out messages from network Services + if msg.nick: + self.doQuit(irc, msg) + doTopic = doMode + + def _seen(self, irc, channel, name, any=False): + if any: + db = self.anydb + else: + db = self.db try: results = [] if '*' in name: - results = self.db.seenWildcard(channel, name) + results = db.seenWildcard(channel, name) else: - results = [[name, self.db.seen(channel, name)]] + results = [[name, db.seen(channel, name)]] if len(results) == 1: (nick, info) = results[0] (when, said) = info - irc.reply(format('%s was last seen in %s %s ago saying: %s', + irc.reply(format('%s was last seen in %s %s ago: %s', nick, channel, utils.timeElapsed(time.time()-when), said)) elif len(results) > 1: @@ -146,7 +208,53 @@ class Seen(callbacks.Plugin): irc.reply(format('I haven\'t seen anyone matching %s.', name)) except KeyError: irc.reply(format('I have not seen %s.', name)) - seen = wrap(seen, ['channeldb', 'nick']) + + def seen(self, irc, msg, args, channel, name): + """[] + + Returns the last time was seen and what was last seen + saying. is only necessary if the message isn't sent on the + channel itself. + """ + self._seen(irc, channel, name) + seen = wrap(seen, ['channel', 'nick']) + + def any(self, irc, msg, args, channel, optlist, name): + """[] [--user ] [] + + Returns the last time was seen and what was last seen + doing. This includes any form of activity, instead of just PRIVMSGs. + If isn't specified, returns the last activity seen in + . If --user is specified, looks up name in the user database + and returns the last time user was active in . is + only necessary if the message isn't sent on the channel itself. + """ + if name and optlist: + raise callbacks.ArgumentError + elif name: + self._seen(irc, channel, name, any=True) + elif optlist: + for (option, arg) in optlist: + if option == 'user': + user = arg + self._user(irc, channel, user, any=True) + else: + self._last(irc, channel, any=True) + any = wrap(any, ['channel', getopts({'user': 'otherUser'}), + additional('nick')]) + + def _last(self, irc, channel, any=False): + if any: + db = self.anydb + else: + db = self.db + try: + (when, said) = db.seen(channel, '') + irc.reply(format('Someone was last seen in %s %s ago: %s', + channel, utils.timeElapsed(time.time()-when), + said)) + except KeyError: + irc.reply('I have never seen anyone.') def last(self, irc, msg, args, channel): """[] @@ -154,15 +262,21 @@ class Seen(callbacks.Plugin): Returns the last thing said in . is only necessary if the message isn't sent in the channel itself. """ - try: - (when, said) = self.db.seen(channel, '') - irc.reply(format('Someone was last seen in %s %s ago saying: %s', - channel, utils.timeElapsed(time.time()-when), - said)) - except KeyError: - irc.reply('I have never seen anyone.') - last = wrap(last, ['channeldb']) + self._last(irc, channel) + last = wrap(last, ['channel']) + def _user(self, irc, channel, user, any=False): + if any: + db = self.anydb + else: + db = self.db + try: + (when, said) = db.seen(channel, user.id) + irc.reply(format('%s was last seen in %s %s ago: %s', + user.name, channel, + utils.timeElapsed(time.time()-when), said)) + except KeyError: + irc.reply(format('I have not seen %s.', user.name)) def user(self, irc, msg, args, channel, user): """[] @@ -173,15 +287,8 @@ class Seen(callbacks.Plugin): is only necessary if the message isn't sent in the channel itself. """ - try: - (when, said) = self.db.seen(channel, user.id) - irc.reply(format('%s was last seen in %s %s ago saying: %s', - user.name, channel, - utils.timeElapsed(time.time()-when), said)) - except KeyError: - irc.reply(formen('I have not seen %s.',user.name)) - user = wrap(user, ['channeldb', 'otherUser']) - + self._user(irc, channel, user) + user = wrap(user, ['channel', 'otherUser']) Class = Seen diff --git a/plugins/Seen/test.py b/plugins/Seen/test.py index c6811d63a..4795c638d 100644 --- a/plugins/Seen/test.py +++ b/plugins/Seen/test.py @@ -49,6 +49,17 @@ class ChannelDBTestCase(ChannelPluginTestCase): self.assertRegexp('seen asldfkjasdlfkj', '^I have not seen') self.assertNotRegexp('seen asldfkjasdlfkj', 'KeyError') + def testAny(self): + self.irc.feedMsg(ircmsgs.join(self.channel, 'baz!foo@bar')) + self.assertRegexp('seen any', 'baz has joined') + self.irc.feedMsg(ircmsgs.part(self.channel, 'baz!foo@bar')) + self.assertRegexp('seen any', 'baz has parted') + self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+o', 'baz'), + prefix='baz!foo@bar')) + self.assertRegexp('seen any baz', 'baz sets mode') + self.assertRegexp('seen any %s' % self.nick, + '^%s was last seen' % self.nick) + def testSeen(self): self.assertNotError('seen last') self.assertNotError('list')