[Logger] Add Logger, an SQLite3 channel logger
parent
ad0ea5ff2e
commit
9c709372e6
|
@ -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
|
||||||
|
|
|
@ -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."""))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,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"]))
|
||||||
|
|
Loading…
Reference in New Issue