[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