#!/usr/bin/python -tt## SILAN# Simple IRC Log Analyzer# http://slackforge.com/silan/## Version 0.0.2# An extremely simple IRC log analyzer --;# Only supports dircproxy logs, because that's what I use## Copyright (c) 2007 Shidayu <shidayu@gmail.com>## Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:## The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN# THE SOFTWARE.##### Import Other Modules#import os, re, stat, sys, glob, datetime, string, time, random### Globals## Regular Expressions used by SILANre_rawlog = re.compile('^\[(\d\d) ([A-Z][a-z][a-z]) (\d\d):(\d\d)\] ([^\n\r\f\v]*)$')re_ownnick = re.compile('--- You changed your nickname to (\S*)')re_nickchange = re.compile('--- (\S*) \([^\t\n\r\f\v)]*\) changed nickname to (\S*)')re_quit = re.compile('<-- (\S*) \([^\t\n\r\f\v)]*\) quit from IRC: ([^\n\r\f\v]*)$')re_join = re.compile('--> (\S*) \([^\t\n\r\f\v)]*\) joined the channel$')re_part = re.compile('<-- (\S*) \([^\t\n\r\f\v)]*\) left the channel$')re_message = re.compile('<([^ \t\n\r\f\v!]*)![^\t\n\r\f\v>]*> ([^\n\r\f\v]*)$')re_describe = re.compile('\* (\S*) ([^\n\r\f\v]*)')re_topic = re.compile('--- (\S*) \([^\t\n\r\f\v)]*\) changed topic: ([^\t\n\r\f\v]*)$')re_kick = re.compile('<-- (\S*) kicked off by (\S*) \([^\t\n\r\f\v)]*\): ([^\n\r\f\v]*)$')re_ownkick = re.compile('<-- Kicked off by (\S*) \([^\t\n\r\f\v)]*\): ([^\n\r\f\v]*)$')re_nonword = re.compile('[^a-zA-Z0-9]*')re_channame = re.compile('^(\S*).log$')# Month conversion Dictionaryfmt_month = { "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6, "Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12 }### Class Definitions#class Silan(object): """ Simple IRC Log Analyzer """ def __init__(self): self.networks = [Network()] channellogs = glob.glob("#*.log") for filename in channellogs: self.networks[0].channels.append(Channel(re_channame.match(filename).group(1), self.networks[0], filename))class Line(object): """ Line of log data with date and time """ def __init__(self, dttm, text): self.dttm = dttm self.text = text def wordcount(self): return len(self.text.split())class UserLine(Line): """ Line of log data that includes user instance """ def __init__(self, dttm, user, text): Line.__init__(self, dttm, text) self.user = userclass Message(UserLine): """ Standard IRC message from a user """ def __init__(self, dttm, user, text): UserLine.__init__(self, dttm, user, text)class Describe(UserLine): """ Describe or action from a user """ def __init__(self, dttm, user, text): UserLine.__init__(self, dttm, user, text)class Topic(UserLine): """ Channel topic """ def __init__(self, dttm, user, text): UserLine.__init__(self, dttm, user, text)class Kick(UserLine): """ Channel kick """ def __init__(self, dttm, target, user, text): UserLine.__init__(self, dttm, user, text) self.target = targetclass Log(object): """ Log recorded by IRC client/proxy """ def __init__(self, filename): self.filename = filename self.lines = [] self._read_log() def _read_log(self): loghandler = open(self.filename, 'r') loglist = loghandler.readlines() loghandler.close() creationdttm = datetime.datetime.fromtimestamp(os.stat(self.filename).st_ctime) firstday = int(re_rawlog.match(loglist[0]).group(1)) firstmonth = fmt_month[re_rawlog.match(loglist[0]).group(2)] firsthour = int(re_rawlog.match(loglist[0]).group(3)) firstminute = int(re_rawlog.match(loglist[0]).group(4)) prevdttm = datetime.datetime(creationdttm.year, firstmonth, firstday, firsthour, firstminute) for logtext in loglist: day = int(re_rawlog.match(logtext).group(1)) month = fmt_month[re_rawlog.match(logtext).group(2)] hour = int(re_rawlog.match(logtext).group(3)) minute = int(re_rawlog.match(logtext).group(4)) text = re_rawlog.match(logtext).group(5) if month < prevdttm.month or (month == prevdttm.month and day < prevdttm.day): # The preceding: Does it look like we've gone back in time? prevdttm = datetime.datetime(prevdttm.year + 1, month, day, hour, minute) else: prevdttm = datetime.datetime(prevdttm.year, month, day, hour, minute) self.lines.append(Line(prevdttm, text))class Network(Log): """ IRC network """ def __init__(self, name="default", filename="Server.log"): Log.__init__(self, filename) self.name = name self.channels = [] self.users = [] self.ownuser = None self._parse() def get_channel(self, channame): for netchannel in self.channels: if channame.lower() == netchannel.name.lower(): return netchannel return None def get_user(self, nickname): netuser = self.find_user(nickname) if not netuser: newuser = User(nickname) self.users.append(newuser) return newuser else: return netuser def find_user(self, nickname): for netuser in self.users: if nickname.lower() == netuser.name.lower() or nickname.lower() in [alt.lower() for alt in netuser.alternates]: return netuser def add_alternate(self, nick1, nick2): netuser1 = self.find_user(nick1) netuser2 = self.find_user(nick2) if not netuser1 and not netuser2: newuser = self.get_user(nick1) newuser.alternates.append(nick2) elif netuser1 and not netuser2: netuser1.alternates.append(nick2) elif netuser2 and not netuser1: netuser2.alternates.append(nick1) elif netuser1 and netuser2 and netuser1 != netuser2: if self.users.index(netuser1) < self.users.index(netuser2): netuser1.alternates.append(netuser2.name) netuser1.alternates.extend(netuser2.alternates) self.users.remove(netuser2) if self.ownuser == netuser2: self.ownuser = netuser1 else: netuser2.alternates.append(netuser1.name) netuser2.alternates.extend(netuser1.alternates) self.users.remove(netuser1) if self.ownuser == netuser1: self.ownuser = netuser2 def _parse(self): for line in self.lines: if re_nickchange.match(line.text): oldnick = re_nickchange.match(line.text).group(1) newnick = re_nickchange.match(line.text).group(2) self.add_alternate(oldnick, newnick) if re_ownnick.match(line.text): newnick = re_ownnick.match(line.text).group(1) if not self.ownuser: self.ownuser = self.get_user(newnick) else: self.add_alternate(self.ownuser.name, newnick)class Channel(Log): """ IRC channel """ def __init__(self, name, network, filename): Log.__init__(self, filename) self.name = name self.network = network self.topics = [] self.messages = [] self.describes = [] self.kicks = [] self._parse() def get_messages_by_hour(self, hour): return [msg for msg in self.messages if msg.dttm.hour == hour] def get_messages_by_weekday(self, weekday): return [msg for msg in self.messages if msg.dttm.weekday == weekday] def get_messages_by_user(self, nickname): return [msg for msg in self.messages if nickname.lower() == msg.user.name.lower() or nickname.lower() in [alt.lower() for alt in msg.user.alternates]] def get_describes_by_user(self, nickname): return [dsc for dsc in self.describes if nickname.lower() == dsc.user.name.lower() or nickname.lower() in [alt.lower() for alt in dsc.user.alternates]] def get_kicks_by_user(self, nickname): return [kck for kck in self.kicks if nickname.lower() == kck.user.name.lower() or nickname.lower() in [alt.lower() for alt in kck.user.alternates]] def get_kicks_by_target(self, nickname): return [kck for kck in self.kicks if nickname.lower() == kck.target.name.lower() or nickname.lower() in [alt.lower() for alt in kck.target.alternates]] def _parse(self): for line in self.lines: if re_message.match(line.text): user = self.network.get_user(re_message.match(line.text).group(1)) text = re_message.match(line.text).group(2) self.messages.append(Message(line.dttm, user, text)) elif re_describe.match(line.text): user = self.network.get_user(re_describe.match(line.text).group(1)) text = re_describe.match(line.text).group(2) self.describes.append(Describe(line.dttm, user, text)) elif re_kick.match(line.text): target = self.network.get_user(re_kick.match(line.text).group(1)) user = self.network.get_user(re_kick.match(line.text).group(2)) text = re_kick.match(line.text).group(3) self.kicks.append(Kick(line.dttm, target, user, text)) elif re_ownkick.match(line.text): user = self.network.get_user(re_ownkick.match(line.text).group(1)) text = re_ownkick.match(line.text).group(2) self.kicks.append(Kick(line.dttm, self.network.ownuser, user, text)) elif re_topic.match(line.text): user = self.network.get_user(re_topic.match(line.text).group(1)) text = re_topic.match(line.text).group(2) self.topics.append(Topic(line.dttm, user, text))class User(object): """ IRC user """ def __init__(self, name): self.name = name self.alternates = []### Functions#def main(): appversion = "0.0.2" print '\n\nSILAN', appversion, '- Simple IRC Log Analyzer - http://slackforge.com/silan/' print 'Copyright (c) 2007 Shidayu <shidayu@gmail.com>' try: appinstance = Silan() for message in appinstance.networks[0].get_channel("#C&T").get_messages_by_user("Shidayu"): print message.dttm, message.user.name, ":", message.text except: print "An unexpected error has occurred:", sys.exc_info()[0] raise### Begin Here!#if __name__=="__main__": main()