# 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()