# Copyright (C) 2021 Yuchen Pei. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # Generates a graphviz dot file based on an irc log file. # usage: # python gengraph.py /path/to/input-file /path/to/output-file.dot # after this you can generate a graph using graphviz: # dot -Tsvg /path/to/output-file.dot -o out.svg # The log file is either in quassel (copied from quassel buffer) or # znc format (collected with the log module). import sys import textwrap class Message: def __init__(self): # message id self.id = -1 # sender self.sender = "" # the message / line self.message = "" # mssage ids this message is replying to self.reply_to_msgs = [] # previous message id of the same sender self.previous_msg = -1 def to_dict(self): return {'id': self.id, 'sender': self.sender, 'message': self.message, 'reply_to_msgs': self.reply_to_msgs, 'previous_msg': self.previous_msg} def parse_log(log): """Parses a quassel or znc log. A line is in the form of message: [hh:mm:ss] blahblah. me (quassel): [hh:mm:ss] -*- sender-nick does something. me (znc): [hh:mm:ss] * sender-nick does something. other notices [hh:mm:ss] *** Mode #abc +o def by ChanServ args: log: a string of quassel log. return: a list of Messages """ last_messages = dict() index = 0 messages = [] for line in log.splitlines(): message = Message() line = line.lstrip() [_, nick, payload] = line.split(' ', maxsplit=2) # notices if nick == '***': continue # me if nick == '-*-' or nick == '*': [_, _, nick, payload] = line.split(' ', maxsplit=3) # message else: nick = nick[1:-1] # removes <> message.message = line message.id = index # Remove special symbol for op if nick[0] in ['@', '%']: nick = nick[1:] message.sender = nick if nick in last_messages: message.previous_msg = last_messages[nick] last_messages[nick] = index for participant, msg_id in last_messages.items(): if payload.find(participant) != -1: message.reply_to_msgs.append(msg_id) messages.append(message) index += 1 return messages def gen_graph(meeting): dot = """ digraph meeting { graph [ width = 10, ranksep = 0.02, concentrate = true ]; node [ shape = box, margin = 0, pad = 0 ]; """ for msg in meeting: quoted_message = textwrap.fill(msg.message.replace('"', '\\"')) dot += f'\nm{str(msg.id)} [label="{quoted_message}"];' if msg.id > 0: dot += f'\nm{str(msg.id - 1)} -> m{str(msg.id)} [style="invis"];' for msg in meeting: if msg.previous_msg > -1: dot += f'\nm{str(msg.previous_msg)} -> m{str(msg.id)} [style="dashed"];' for id in msg.reply_to_msgs: dot += f'\nm{str(id)} -> m{str(msg.id)};' dot += '\n}' return dot def main(): log = open(sys.argv[1]).read() meeting = parse_log(log) graph = gen_graph(meeting) open(sys.argv[2], 'w').write(graph) if __name__ == '__main__': main()