Claws-Mail Plugin: Cat Into Tree

This is a Python plugin script for your .claws-mail/python-scripts/main folder, which you can then assign to a keystroke or toolbar button. Once activated, it opens a window with the senders, subjects and bodies of the current selected messages loaded into an expanding shallow tree widget.

cat-into-tree.py (Source)

# cat-into-tree.py - Copyright 2020 MJ Ray GPL-2+

from __future__ import print_function, unicode_literals

# import clawsmail
import email
import re
import sys
import textwrap
# The modern way is to import gi and then from gi.repository import Gtk
# but I suspect something else imports gtk which prevents that
import gtk


__author__ = "MJ Ray"
__copyright__ = "Copyright 2020 MJ Ray"
__license__ = "GPL-2+"
__version__ = "1.01"


def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)


def make_window():

    def dismiss_window(button, w):
        w.destroy()

    def show_window(texts):
        win = gtk.Window()
        win.set_title("Summary")
        win.connect("destroy", gtk.main_quit)
        table = gtk.Table(2, 1)
        win.add(table)

        textscroller = gtk.ScrolledWindow()
        textfield = gtk.TreeView()
        textfield.set_model(texts)

        rendererText = gtk.CellRendererText()
        rendererText.set_property('font', 'Serif 9')

        column = gtk.TreeViewColumn("Messages")
        column.pack_start(rendererText, True)
        column.add_attribute(rendererText, 'text', 0)

        textfield.append_column(column)
        textfield.set_property('hover-expand', True)
        # textfield.expand_all()

        def click_row(treev, path, column):
            if treev.row_expanded(path):
                treev.collapse_row(path)
            else:
                treev.expand_row(path, True)

        textfield.connect("row-activated", click_row)

        textscroller.add(textfield)

        table.attach(textscroller, 0, 1, 1, 2)

        button = gtk.Button("Dismiss")
        button.connect("clicked", dismiss_window, win)
        button.connect("activate", dismiss_window, win)
        button.set_flags(gtk.CAN_DEFAULT)
        table.attach(button, 0, 1, 0, 1, yoptions=gtk.FILL)

        win.set_default(button)

        def parse_key(w, event):
            if gtk.gdk.keyval_name(event.keyval) in ('q', 'Escape'):
                win.destroy()
                return True
            elif gtk.gdk.keyval_name(event.keyval) == 'a':
                visible = textfield.get_visible_rect()
                textfield.scroll_to_point(-1, visible.y + visible.height - 10)
                return True
            elif gtk.gdk.keyval_name(event.keyval) == 'A':
                visible = textfield.get_visible_rect()
                textfield.scroll_to_point(-1, visible.y - visible.height + 10)
                return True
            return False
        win.add_events(gtk.gdk.KEY_PRESS_MASK)
        win.connect("key-press-event", parse_key)

        win.show_all()
        win.present()
        gtk.main()
        return True

    alltexts = gtk.TreeStore(str)
    # iterate over the whole selection
    # TODO: can this be sorted into a better order?
    messages = clawsmail.get_summaryview_selected_message_list()
    messages.sort(cmp=lambda x, y: cmp(x.FilePath, y.FilePath))
    for msginfo in messages:
        file = open(msginfo.FilePath, "r")
        msgfile = file.read()
        file.close()
        eprint("Got file "+msginfo.FilePath)

        # parse the email file
        # TODO: can we use what claws has already parsed?
        msg = email.message_from_string(msgfile)
        eprint("Decoded a file")

        # extract the decoded plain text body parts
        body = u''
        if msg.is_multipart():
            # body += 'multipart\n'
            for part in msg.walk():
                # body += part.get_content_type() + "\n\n"
                if part.get_content_type() == 'text/plain':
                    if part.get_content_charset():
                        body += part.get_payload(
                            decode=True).decode(part.get_content_charset())
                    else:
                        body += part.get_payload(decode=True)
            eprint("Extracted multipart body")
            # body += msg.get_payload(0,decode=True)
        else:
            # body += 'unipart\n'
            # body += msg.get_content_type() + "\n\n"
            if msg.get_content_charset():
                body += msg.get_payload(
                    decode=True).decode(msg.get_content_charset())
            else:
                body += msg.get_payload(decode=True)
            eprint("Extracted unipart body")

        body = re.sub(
            r'A new comment on the post "[^"]*" .* approval.*\nhttp.*\n\n',
            '',
            body
        )
        # body = re.sub(r'^.*?\n\n','',msg.as_string(),count=1,flags=re.DOTALL)

        # text += '----\n' + body + '----\n\n'
        bodylines = body.splitlines()
        body = ("\n".join(map(lambda(x): textwrap.fill(x, 72), bodylines))
                + "\n(On "
                + msg['Date']
                + ")")

        def is_header_or_quote(x):
            return (len(x) > 1
                    and x[0] not in ('-', '=', '>', ' ')
                    and x[-1] not in ('-', '=', ',', ':', '0'))

        leaderwidth = 88
        lg = textwrap.fill(
            " ".join(filter(is_header_or_quote,
                            bodylines[0:min(len(bodylines), 50)])),
            leaderwidth,
            initial_indent="  ",
            subsequent_indent="  ")

        # Add to what we're going to display
        text = (u""
                + email.utils.parseaddr(msg["From"])[0]
                + " > "
                + msg['Subject']
                + "\n"
                + lg[0:lg.rfind(' ', 0, min(len(lg), leaderwidth*2))])
        message = alltexts.append(None, [text])
        eprint("Added header")

        alltexts.append(message, [body])
        eprint("Added body")
        msginfo.unread = False
        # msginfo.new = False

    eprint("Showing window")
    show_window(alltexts)


make_window()