Thursday, September 22, 2011

Mutt'n'Gmail: Show me the full thread

The 'Only One Copy of a Mail' scheme in Gmail makes really sense. I like it a lot. There was just one piece missing when I accessed my account with Mutt: Display the full thread of a particular message. Luckily, the full thread is available in the 'All Mail' folder. So, just extract the Message-ID of the current mail, and focus the thread with that id in the 'All Mail' folder.

BTW: I still cannot believe that there is no way to pass a mail through a filter in Mutt. I mean, to pipe a mail to a script and capture the output in a variable. If you know how to do that, please let me know.

This is the Python script that takes a mail on standard input, and writes the message-id of that mail into a temporary file:

#!/usr/bin/env python

import os, sys, re
from email import message_from_string, message_from_file
from email.header import Header, decode_header

RECORDFILE = "/tmp/mutt-store-for-message-id"
CHARS_TO_ESCAPE='^$+*{}[]|'
MUTTCMD = """
set my_message_id='%s'
"""

class MessageIdException(Exception):
    """
    The base exception of this module.
    """

class HeaderNotAvailable(MessageIdException):
    """
    catch this if the you are looking for is optional.
    """

def get_header(mail, header):
    """
    Return the requested header as one decoded string or throw
    HeaderNotAvailable.
    """
    header_raw = mail.get(header)
    if not header_raw:
        #print(mail.as_string())
        raise HeaderNotAvailable("looking for header %s in %s"
                % (header, mail.get('Subject')))
    header_parts = []
    for part, charset in decode_header(header_raw):
        header_parts.append(unicode(part, charset or 'ascii', 'replace'))
    return ''.join(header_parts).strip()

def escape_message_id(idstring):
    """
    Return an escaped version of the message-id string.

    Some characters need to be escaped to not be evaluated in mutt.
    """
    for c in CHARS_TO_ESCAPE:
        idstring = idstring.replace(c, '\\' + c)
    return idstring

def main():
    """
    Read a mail piped on standard input, and write the escaped message-id
    string to a file.
    """
    if os.isatty(file.fileno(sys.stdin)):
        raise MessageIdException("stdin is not a pipe")
    mail = message_from_file(sys.stdin)
    msg_id_header = 'Message-ID'
    raw_message_id = get_header(mail, msg_id_header)
    if not raw_message_id:
        raise MessageIdException("empty message-id")
    esc_message_id = escape_message_id(raw_message_id)
    print(esc_message_id)
    with open(RECORDFILE, "w") as fw:
        fw.write(MUTTCMD % esc_message_id)

if __name__ == '__main__':
    main()

Now, the Mutt macro. You need to fill in the path to where you stored the above script -- called 'muttcmd-print-message-id' in this example:

macro index,pager ,T "\
<enter-command>set my_auto_tag=\$auto_tag auto_tag=no<Enter>\
<enter-command>set my_pipe_decode=\$pipe_decode pipe_decode=no<Enter>\
<enter-command>set my_wait_key=\$wait_key wait_key=no<Enter>\
<pipe-message>/home/YOU/bin/muttcmd-print-message-id<enter>\
<enter-command>set wait_key=\$my_wait_key<Enter>\
<enter-command>set pipe_decode=\$my_pipe_decode<Enter>\
<enter-command>set auto_tag=\$my_auto_tag<Enter>\
<enter-command>source /tmp/mutt-store-for-message-id<enter>\
<change-folder>=[Google<quote-char><space>Mail]/All<quote-char><space>Mail<enter>\
<clear-flag>*\
<tag-pattern>~i \$my_message_id<enter>\
<limit>~(~i \$my_message_id)<enter>\
"

1 comment:

  1. Well, I guess, I rushed a bit too much. Fixed the macro and compared the result. It should work now.

    ReplyDelete