Made into a flatfile, w00t!

This commit is contained in:
Jeremy Fincher 2004-02-03 05:29:40 +00:00
parent 07689261c6
commit e9aac2e191

View File

@ -47,9 +47,12 @@ import getopt
import string import string
from itertools import imap, ifilter from itertools import imap, ifilter
import log
import conf import conf
import utils import utils
import world
import ircdb import ircdb
import irclib
import ircmsgs import ircmsgs
import plugins import plugins
import ircutils import ircutils
@ -90,62 +93,164 @@ conf.registerChannelValue(conf.supybot.plugins.ChannelStats, 'frowns',
(i.e., pieces of text with no spaces in them ) are considered 'frowns' for (i.e., pieces of text with no spaces in them ) are considered 'frowns' for
the purposes of stats-keeping.""")) the purposes of stats-keeping."""))
class ChannelStat(irclib.IrcCommandDispatcher):
def __init__(self, actions=0, chars=0, frowns=0, joins=0, kicks=0, modes=0,
msgs=0, parts=0, quits=0, smileys=0, topics=0, words=0):
self.actions = actions
self.chars = chars
self.frowns = frowns
self.joins = joins
self.kicks = kicks
self.modes = modes
self.msgs = msgs
self.parts = parts
self.quits = quits
self.smileys = smileys
self.topics = topics
self.words = words
self._values = ['actions', 'chars', 'frowns', 'joins', 'kicks','modes',
'msgs', 'parts', 'quits', 'smileys', 'topics', 'words']
def values(self):
return map(curry(getattr, self), self._values)
def addMsg(self, msg):
self.msgs += 1
method = self.dispatchCommand(msg.command)
if method is not None:
method(msg)
def doPayload(self, channel, payload):
self.chars += len(payload)
self.words += len(payload.split())
fRe = conf.supybot.plugins.ChannelStats.get('frowns').get(channel)()
sRe =conf.supybot.plugins.ChannelStats.get('smileys').get(channel)()
self.frowns += len(fRe.findall(payload))
self.smileys += len(sRe.findall(payload))
def doPrivmsg(self, msg):
self.doPayload(*msg.args)
if ircmsgs.isAction(msg):
self.actions += 1
def doTopic(self, msg):
self.doPayload(*msg.args)
self.topics += 1
def doKick(self, msg):
self.kicks += 1
def doPart(self, msg):
self.doPayload(*msg.args)
self.parts += 1
def doMode(self, msg):
self.modes += 1
class UserStat(ChannelStat):
def __init__(self, id, kicked=0, *args):
ChannelStat.__init__(self, *args)
self.id = id
self.kicked = kicked
self._values.insert(0, 'kicked')
def doKick(self, msg):
self.doPayload(msg.args[0], msg.args[2])
self.kicks += 1
class StatsDB(object):
def __init__(self, filename):
self.filename = filename
self.channels = ircutils.IrcDict()
self.channelStats = 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')
try:
(channelOrId, rest) = line.split(':', 1)
if ircutils.isChannel(channelOrId):
channel = channelOrId
values = map(int, rest.split(','))
self.channelStats[channel] = ChannelStat(*values)
else:
id = int(channelOrId)
(channel, rest) = rest.split(':', 1)
d = self.channels.setdefault(channel, {})
values = map(int, rest.split(','))
d[id] = UserStat(id, *values)
except ValueError:
log.warning('Invalid line (#%s): %r', lineno, line)
continue
fd.close()
def flush(self):
fd = file(self.filename, 'w')
L = self.channelStats.items()
L.sort()
for (channel, stat) in L:
fd.write('%s:%s' % (channel, ','.join(map(str, stat.values()))))
fd.write(os.linesep)
if channel not in self.channels:
continue
d = self.channels[channel]
LL = d.items()
LL.sort()
for (id, stat) in LL:
fd.write('%s:%s:%s' % (id, channel,
','.join(map(str, stat.values()))))
fd.write(os.linesep)
fd.close()
def close(self):
self.flush()
self.channels.clear()
self.channelStats.clear()
def addMsg(self, msg, id=None):
channel = msg.args[0]
if ircutils.isChannel(channel):
if channel not in self.channelStats:
self.channelStats[channel] = ChannelStat()
self.channelStats[channel].addMsg(msg)
try:
if id is None:
id = ircdb.users.getUserId(msg.prefix)
except KeyError:
return
if channel not in self.channels:
self.channels[channel] = {}
if id not in self.channels[channel]:
self.channels[channel][id] = UserStat(id)
self.channels[channel][id].addMsg(msg)
def get(self, channel, id=None):
if id is None:
return self.channelStats[channel]
else:
return self.channels[channel][id]
class ChannelStats(plugins.ChannelDBHandler, callbacks.Privmsg): class ChannelStats(callbacks.Privmsg):
noIgnore = True noIgnore = True
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.ChannelDBHandler.__init__(self)
self.lastmsg = None self.lastmsg = None
self.laststate = None self.laststate = None
self.outFiltering = False self.outFiltering = False
self.db = StatsDB(os.path.join(conf.supybot.directories.data(),
'ChannelStats.db'))
world.flushers.append(self.db.flush)
def die(self): def die(self):
world.flushers.remove(self.db.flush)
self.db.close()
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.ChannelDBHandler.die(self)
def makeDb(self, filename):
if os.path.exists(filename):
db = sqlite.connect(filename)
else:
db = sqlite.connect(filename)
cursor = db.cursor()
cursor.execute("""CREATE TABLE user_stats (
id INTEGER PRIMARY KEY,
user_id INTEGER UNIQUE ON CONFLICT IGNORE,
smileys INTEGER,
frowns INTEGER,
chars INTEGER,
words INTEGER,
msgs INTEGER,
actions INTEGER,
joins INTEGER,
parts INTEGER,
kicks INTEGER,
kicked INTEGER,
modes INTEGER,
topics INTEGER,
quits INTEGER
)""")
cursor.execute("""CREATE TABLE channel_stats (
smileys INTEGER,
frowns INTEGER,
chars INTEGER,
words INTEGER,
msgs INTEGER,
actions INTEGER,
joins INTEGER,
parts INTEGER,
kicks INTEGER,
modes INTEGER,
topics INTEGER,
quits INTEGER
)""")
cursor.execute("""INSERT INTO channel_stats
VALUES (0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0)""")
db.commit()
return db
def __call__(self, irc, msg): def __call__(self, irc, msg):
try: try:
@ -155,152 +260,49 @@ class ChannelStats(plugins.ChannelDBHandler, callbacks.Privmsg):
self.laststate = irc.state.copy() self.laststate = irc.state.copy()
finally: finally:
self.lastmsg = msg self.lastmsg = msg
self.db.addMsg(msg)
super(ChannelStats, self).__call__(irc, msg) super(ChannelStats, self).__call__(irc, msg)
def doPrivmsg(self, irc, msg):
if not ircutils.isChannel(msg.args[0]):
return
else:
self._updatePrivmsgStats(msg)
def _updatePrivmsgStats(self, msg):
(channel, s) = msg.args
db = self.getDb(channel)
cursor = db.cursor()
chars = len(s)
words = len(s.split())
isAction = ircmsgs.isAction(msg)
frowns = len(self.registryValue('frowns', channel).findall(s))
smileys = len(self.registryValue('smileys', channel).findall(s))
s = ircmsgs.prettyPrint(msg)
cursor.execute("""UPDATE channel_stats
SET smileys=smileys+%s,
frowns=frowns+%s,
chars=chars+%s,
words=words+%s,
msgs=msgs+1,
actions=actions+%s""",
smileys, frowns, chars, words, int(isAction))
try:
if self.outFiltering:
id = 0
else:
id = ircdb.users.getUserId(msg.prefix)
except KeyError:
return
cursor.execute("""INSERT INTO user_stats VALUES (
NULL, %s, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0)""", id)
cursor.execute("""UPDATE user_stats SET
chars=chars+%s,
words=words+%s, msgs=msgs+1,
actions=actions+%s, smileys=smileys+%s,
frowns=frowns+%s
WHERE user_id=%s""",
chars, words, int(isAction), smileys, frowns, id)
db.commit()
def outFilter(self, irc, msg): def outFilter(self, irc, msg):
if msg.command == 'PRIVMSG': if msg.command == 'PRIVMSG':
if ircutils.isChannel(msg.args[0]): if ircutils.isChannel(msg.args[0]):
if self.registryValue('selfStats', msg.args[0]): if self.registryValue('selfStats', msg.args[0]):
try: try:
self.outFiltering = True self.outFiltering = True
self._updatePrivmsgStats(msg) self.db.addMsg(msg, 0)
finally: finally:
self.outFiltering = False self.outFiltering = False
return msg return msg
def doPart(self, irc, msg):
channel = msg.args[0]
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""UPDATE channel_stats SET parts=parts+1""")
try:
id = ircdb.users.getUserId(msg.prefix)
cursor.execute("""UPDATE user_stats SET parts=parts+1
WHERE user_id=%s""", id)
except KeyError:
pass
db.commit()
def doTopic(self, irc, msg):
channel = msg.args[0]
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""UPDATE channel_stats SET topics=topics+1""")
try:
id = ircdb.users.getUserId(msg.prefix)
cursor.execute("""UPDATE user_stats
SET topics=topics+1
WHERE user_id=%s""", id)
except KeyError:
pass
db.commit()
def doMode(self, irc, msg):
channel = msg.args[0]
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""UPDATE channel_stats SET modes=modes+1""")
try:
id = ircdb.users.getUserId(msg.prefix)
cursor.execute("""UPDATE user_stats
SET modes=modes+1
WHERE user_id=%s""", id)
except KeyError:
pass
db.commit()
def doKick(self, irc, msg):
channel = msg.args[0]
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""UPDATE channel_stats SET kicks=kicks+1""")
try:
id = ircdb.users.getUserId(msg.prefix)
cursor.execute("""UPDATE user_stats
SET kicks=kicks+1
WHERE user_id=%s""", id)
except KeyError:
pass
try:
kicked = msg.args[1]
id = ircdb.users.getUserId(irc.state.nickToHostmask(kicked))
cursor.execute("""UPDATE user_stats
SET kicked=kicked+1
WHERE user_id=%s""", id)
except KeyError:
pass
db.commit()
def doJoin(self, irc, msg):
channel = msg.args[0]
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""UPDATE channel_stats SET joins=joins+1""")
try:
id = ircdb.users.getUserId(msg.prefix)
cursor.execute("""UPDATE user_stats
SET joins=joins+1
WHERE user_id=%s""", id)
except KeyError:
pass
db.commit()
def doQuit(self, irc, msg): def doQuit(self, irc, msg):
try:
id = ircdb.users.getUserId(msg.prefix)
except KeyError:
id = None
for (channel, c) in self.laststate.channels.iteritems(): for (channel, c) in self.laststate.channels.iteritems():
if msg.nick in c.users: if msg.nick in c.users:
db = self.getDb(channel) if channel not in self.db.channelStats:
cursor = db.cursor() self.db.channelStats[channel] = ChannelStat()
cursor.execute("""UPDATE channel_stats SET quits=quits+1""") self.db.channelStats[channel].quits += 1
try: if id is not None:
id = ircdb.users.getUserId(msg.prefix) if channel not in self.db.channels:
cursor.execute("""UPDATE user_stats SET quits=quits+1 self.db.channels[channel] = {}
WHERE user_id=%s""", id) if id not in self.db.channels[channel]:
except KeyError: self.db.channels[channel][id] = UserStat(id)
pass self.db.channels[channel][id].quits += 1
db.commit()
def doKick(self, irc, msg):
(channel, nick, _) = msg.args
hostmask = irc.state.nickToHostmask(nick)
try:
id = ircdb.users.getUserId(hostmask)
except KeyError:
return
if channel not in self.db.channels:
self.db.channels[channel] = {}
if id not in self.db.channels[channel]:
self.db.channels[channel][id] = UserStat(id)
self.db.channels[channel][id].kicked += 1
def stats(self, irc, msg, args): def stats(self, irc, msg, args):
"""[<channel>] [<name>] """[<channel>] [<name>]
@ -329,34 +331,31 @@ class ChannelStats(plugins.ChannelDBHandler, callbacks.Privmsg):
return return
else: else:
id = ircdb.users.getUserId(name) id = ircdb.users.getUserId(name)
db = self.getDb(channel) try:
cursor = db.cursor() stats = self.db.get(channel, id)
cursor.execute("""SELECT * FROM user_stats WHERE user_id=%s""", id) s = '%s has sent %s; a total of %s, %s, ' \
if cursor.rowcount == 0: '%s, and %s; %s of those messages %s' \
irc.error('I have no stats for that user.') '%s has joined %s, parted %s, quit %s, kicked someone %s, ' \
return 'been kicked %s, changed the topic %s, ' \
values = cursor.fetchone() 'and changed the mode %s.' % \
s = '%s has sent %s; a total of %s, %s, ' \ (name, utils.nItems('message', stats.msgs),
'%s, and %s; %s of those messages %s' \ utils.nItems('character', stats.chars),
'%s has joined %s, parted %s, quit %s, kicked someone %s, ' \ utils.nItems('word', stats.words),
'been kicked %s, changed the topic %s, ' \ utils.nItems('smiley', stats.smileys),
'and changed the mode %s.' % \ utils.nItems('frown', stats.frowns),
(name, utils.nItems('message', values.msgs), stats.actions, stats.actions == 1 and 'was an ACTION. '
utils.nItems('character', values.chars), or 'were ACTIONs. ',
utils.nItems('word', values.words), name,
utils.nItems('smiley', values.smileys), utils.nItems('time', stats.joins),
utils.nItems('frown', values.frowns), utils.nItems('time', stats.parts),
values.actions, values.actions == 1 and 'was an ACTION. ' utils.nItems('time', stats.quits),
or 'were ACTIONs. ', utils.nItems('time', stats.kicks),
name, utils.nItems('time', stats.kicked),
utils.nItems('time', values.joins), utils.nItems('time', stats.topics),
utils.nItems('time', values.parts), utils.nItems('time', stats.modes))
utils.nItems('time', values.quits), irc.reply(s)
utils.nItems('time', values.kicks), except KeyError:
utils.nItems('time', values.kicked), irc.error('I have no stats for that %s in %s' % (name, channel))
utils.nItems('time', values.topics),
utils.nItems('time', values.modes))
irc.reply(s)
def channelstats(self, irc, msg, args): def channelstats(self, irc, msg, args):
"""[<channel>] """[<channel>]
@ -365,19 +364,27 @@ class ChannelStats(plugins.ChannelDBHandler, callbacks.Privmsg):
the message isn't sent on the channel itself. the message isn't sent on the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel) try:
cursor = db.cursor() stats = self.db.get(channel)
cursor.execute("""SELECT * FROM channel_stats""") s = 'On %s there have been %s messages, containing %s ' \
values = cursor.fetchone() 'characters, %s, %s, and %s; ' \
s = 'On %s there have been %s messages, containing %s characters, ' \ '%s of those messages %s. There have been ' \
'%s words, %s smileys, and %s frowns; %s of those messages were ' \ '%s, %s, %s, %s, %s, and %s.' % \
'ACTIONs. There have been %s joins, %s parts, %s quits, ' \ (channel, stats.msgs, stats.chars,
'%s kicks, %s mode changes, and %s topic changes.' % \ utils.nItems('word', stats.words),
(channel, values.msgs, values.chars, utils.nItems('smiley', stats.smileys),
values.words, values.smileys, values.frowns, values.actions, utils.nItems('frown', stats.frowns),
values.joins, values.parts, values.quits, stats.actions, stats.actions == 1 and 'was an ACTION'
values.kicks, values.modes, values.topics) or 'were ACTIONs',
irc.reply(s) utils.nItems('join', stats.joins),
utils.nItems('part', stats.parts),
utils.nItems('quit', stats.quits),
utils.nItems('kick', stats.kicks),
utils.nItems('change', stats.modes, between='mode'),
utils.nItems('change', stats.topics, between='topic'))
irc.reply(s)
except KeyError:
irc.error('I\'ve never been on %s.' % channel)
Class = ChannelStats Class = ChannelStats