[Logger] Add Logger, an SQLite3 channel logger

master
ShadowNinja 2014-02-22 20:36:41 -06:00
parent ad0ea5ff2e
commit 9c709372e6
6 changed files with 384 additions and 0 deletions

33
Logger/__init__.py Normal file
View File

@ -0,0 +1,33 @@
"""
An SQLite3 channel logger
"""
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__ = ""
__author__ = supybot.Author("Owen", "ShadowNinja", "shadowninja@minetest.net")
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__contributors__ = {}
from . import config
from . import storage
from . import plugin
from imp import reload
# In case we're being reloaded.
reload(config)
reload(storage)
reload(plugin)
if world.testing:
from . import test
Class = plugin.Class
configure = config.configure

13
Logger/config.py Normal file
View File

@ -0,0 +1,13 @@
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Logger', True)
Logger = conf.registerPlugin('Logger')
conf.registerChannelValue(Logger, "enable",
registry.Boolean(True, """Determines whether logging is enabled."""))

128
Logger/plugin.py Normal file
View File

@ -0,0 +1,128 @@
import supybot.callbacks as callbacks
import supybot.conf as conf
import supybot.utils as utils
import supybot.ircmsgs as ircmsgs
import supybot.world as world
import supybot.irclib as irclib
from supybot.commands import *
import time
from .storage import LogDB, MessageType
filename = conf.supybot.directories.data.dirize("Log.sqlite")
class Logger(callbacks.Plugin):
"""
An SQLite3 channel logger
"""
noIgnore = True
threaded = True
lastMsgs = {}
lastStates = {}
def __init__(self, irc):
self.__parent = super(Logger, self)
self.__parent.__init__(irc)
self.db = LogDB(filename)
world.flushers.append(self.flush)
def __call__(self, irc, msg):
try:
self.__parent.__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 reset(self):
self.lastMsgs.clear()
self.lastStates.clear()
def die(self):
self.__parent.die()
world.flushers.remove(self.flush)
del self.db
def flush(self):
self.db.commit()
def shouldLog(self, irc, msg, msgtype):
if not self.registryValue("enable"):
return False
if msgtype == MessageType.privmsg or msgtype == MessageType.notice:
if not irc.isChannel(msg.args[0]):
return False
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
return False
if msgtype == MessageType.mode and msg.args[0] == irc.nick:
return False
return True
def _add(self, irc, msg, msgtype):
if not self.shouldLog(irc, msg, msgtype):
return
channel = msg.args[0]
if not self.registryValue("enable", channel):
return
text = None
if len(msg.args) > 1:
text = msg.args[1]
if msgtype == MessageType.mode:
text = " ".join(msg.args[1:])
elif ircmsgs.isAction(msg):
msgtype = MessageType.action
text = msg.args[1][8:-1]
self.db.add(msgtype, irc.network, channel, msg, text)
self.db.commit()
def doPrivmsg(self, irc, msg): self._add(irc, msg, MessageType.privmsg)
def doNotice (self, irc, msg): self._add(irc, msg, MessageType.notice)
def doJoin (self, irc, msg): self._add(irc, msg, MessageType.join)
def doPart (self, irc, msg): self._add(irc, msg, MessageType.part)
def doKick (self, irc, msg): self._add(irc, msg, MessageType.kick)
def doMode (self, irc, msg): self._add(irc, msg, MessageType.mode)
def doTopic (self, irc, msg): self._add(irc, msg, MessageType.topic)
def doNick(self, irc, msg):
if not self.shouldLog(irc, msg, MessageType.nick):
return
oldNick = msg.nick
newNick = msg.args[0]
for (channel, chan) in irc.state.channels.items():
if newNick in chan.users:
self.db.add(MessageType.nick,
irc.network,
channel,
msg,
newNick)
self.db.commit()
def doQuit(self, irc, msg):
if not self.shouldLog(irc, msg, MessageType.quit):
return
reason = None
if len(msg.args) == 1:
reason = msg.args[0]
if not isinstance(irc, irclib.Irc):
irc = irc.getRealIrc()
for (channel, chan) in self.lastStates[irc].channels.items():
if msg.nick in chan.users:
self.db.add(MessageType.quit,
irc.network,
channel,
msg,
reason)
self.db.commit()
Class = Logger

172
Logger/storage.py Normal file
View File

@ -0,0 +1,172 @@
import time
import sqlite3
from threading import RLock
class MessageType:
privmsg = 0
notice = 1
action = 2
join = 3
part = 4
quit = 5
kick = 6
nick = 7
mode = 8
topic = 9
class LogDB:
def __init__(self, filename):
self.conn = sqlite3.connect(filename, 5, 0, None, False)
self.conn.row_factory = sqlite3.Row
self.cur = self.conn.cursor()
self.cur.executescript("""
CREATE TABLE IF NOT EXISTS sender (
id INTEGER NOT NULL,
nick VARCHAR,
user VARCHAR,
host VARCHAR,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS network (
id INTEGER NOT NULL,
name VARCHAR,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS buffer (
id INTEGER NOT NULL,
networkid INTEGER NOT NULL,
name VARCHAR,
PRIMARY KEY (id),
FOREIGN KEY(networkid) REFERENCES network (id)
);
CREATE TABLE IF NOT EXISTS log (
id INTEGER NOT NULL,
type INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
bufferid INTEGER NOT NULL,
senderid INTEGER NOT NULL,
message VARCHAR,
PRIMARY KEY (id),
FOREIGN KEY(bufferid) REFERENCES buffer (id),
FOREIGN KEY(senderid) REFERENCES sender (id)
);
""")
self.lock = RLock()
def __del__(self):
with self.lock:
self.cur.close()
self.conn.commit()
self.conn.close()
def commit(self):
with self.lock:
self.conn.commit()
def _getNetwork(self, network):
return self.cur.execute("""
SELECT * FROM network WHERE name=?
""", (network,)).fetchone()
def getNetwork(self, network, create=True):
with self.lock:
row = self._getNetwork(network)
if row is None and create:
self.cur.execute("""
INSERT INTO network (name) VALUES (?)
""", (network,))
self.conn.commit()
row = self._getNetwork(network)
return row
def _getBuffer(self, network, name):
return self.cur.execute("""
SELECT * FROM buffer
INNER JOIN network ON network.id=buffer.networkid
WHERE
network.name=? AND
buffer.name=?
""", (network, name)).fetchone()
def getBuffer(self, network, name=None, create=True):
with self.lock:
row = self._getBuffer(network, name)
if row is None and create:
self.cur.execute("""
INSERT INTO buffer (name, networkid) VALUES (?, ?)
""", (name, self.getNetwork(network)["id"]))
self.conn.commit()
row = self._getBuffer(network, name)
return row
def _getSender(self, msg):
return self.cur.execute("""
SELECT * FROM sender
WHERE
nick=? AND
user=? AND
host=?
""", (msg.nick, msg.user, msg.host)).fetchone()
def getSender(self, msg, create=True):
with self.lock:
row = self._getSender(msg)
if row is None and create:
self.cur.execute("""
INSERT INTO sender (nick, user, host) VALUES (?, ?, ?)
""", (msg.nick, msg.user, msg.host))
self.conn.commit()
row = self._getSender(msg)
return row
def add(self, msgtype, network, channel, msg, text):
with self.lock:
self.cur.execute("""
INSERT INTO log (type, timestamp, bufferid, senderid, message)
VALUES (?, ?, ?, ?, ?)
""", (
msgtype,
int(time.time()),
self.getBuffer(network, channel)["id"],
self.getSender(msg)["id"],
text
))
def get(self, bufid, starttime, timelen):
with self.lock:
return self.cur.execute("""
SELECT * FROM log
INNER JOIN buffer ON buffer.id=log.bufferid
INNER JOIN network ON network.id=buffer.networkid
INNER JOIN sender ON sender.id=log.senderid
WHERE
log.bufferid=? AND
log.timestamp BETWEEN ? AND ?
ORDER BY id ASC
""", (bufid, starttime, starttime + timelen)).fetchall()
def getBuffers(self):
with self.lock:
return self.cur.execute("""
SELECT * FROM buffer
INNER JOIN network ON network.id=buffer.networkid
ORDER BY id ASC
""").fetchall()
def getNetworks(self):
with self.lock:
return self.cur.execute("""
SELECT * FROM network
ORDER BY id ASC
""").fetchall()
def getSenders(self):
with self.lock:
return self.cur.execute("""
SELECT * FROM serder
ORDER BY id ASC
""").fetchall()

0
Logger/test.py Normal file
View File

38
Logger/totext.py Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import sys
import time
from storage import LogDB
if len(sys.argv) != 5:
print("""Usage:
%s <LogDB> <Network> <Channel> <Time>
""" % sys.argv[0])
sys.exit(1)
db = LogDB(sys.argv[1])
msgstrs = [
"<{nick}> {message}",
"-{nick}- {message}",
"-*- {nick} {message}",
"--> {nick} has joined",
"<-- {nick} has parted ({message})",
"<-- {nick} has quit ({message})",
"*** {nick} has been kicked {message}",
"<-> {nick} is now known as {message}",
"*** Mode [{message}] by {nick}",
"*** Topic set by {nick} to {message}"
]
def msgToStr(mtype, nick, msg):
return msgstrs[mtype].format(nick=nick, message=msg)
latest = db.cur.execute("SELECT timestamp FROM log ORDER BY id DESC LIMIT 1").fetchone()[0]
buf = db.getBuffer(sys.argv[2], sys.argv[3])
log = db.get(buf["id"], latest - int(sys.argv[4]), int(sys.argv[4]))
for message in log:
tm = time.strftime("[%H:%M:%S] ", time.gmtime(message["timestamp"]))
print(tm + msgToStr(message["type"], message["nick"], message["message"]))