summaryrefslogblamecommitdiff
path: root/gengraph.py
blob: 16a9c76c312273b7d2c37e035e9aa4ccb9c56dd9 (plain) (tree)




































































































































                                                                                    
# 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 <https://www.gnu.org/licenses/>. 

# 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] <sender-nick> 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()