blog · git · desktop · images · contact & privacy · gopher


Alte Thunderbird-Mails zu Mutt ins Maildir holen

Wollte schon lange mal meine ganzen alten Mails wieder zum Leben erwecken. Früher habe ich allerdings Thunderbird 1 und 2 genutzt, was seine Mails als mbox gespeichert hat (glaube, das ist heute immernoch so). Irgendwie musste das also Maildir-kompatibel gemacht werden. Python weiß Rat.

Erstmal musste alles von den alten Platten runter. Erstaunlicherweise habe ich nur 3 Thunderbird-Profile gefunden und die Mails reichen nur bis 2002 zurück -- wie auch immer, insgesamt sind's trotzdem 620MB. Also alles in ein Verzeichnis schieben:

MailsAlt $ l
drwxr-xr-x 5 void users 4.1k Feb 21 11:00 ./
drwx------ 8 void users 4.1k Feb 20 22:15 ../
drwx------ 7 void users 4.1k Feb 20 22:16 5o92qf76.default/
drwx------ 6 void users 4.1k Feb 20 22:15 rhpec2v2.default/
drwx------ 8 void users 4.1k Feb 20 22:18 zif8818n.default/

Jetzt habe ich tollerweise im Laufe der Zeit sehr viele (Thunderbird-)Ordner angelegt und oft die Mailaccounts gewechselt. Meine Inbox hieß auch nicht immer "Inbox"... Also alle Boxen aufspüren:

MailsAlt $ for i in */Mail; do find "$i" -type f -and -not -size 0 -and -not -iname "*.???" -and -not -iname "*.????"; done

Am besten diese Liste gleich in eine Datei schreiben, denn die wird die Grundlage für die Einsortierung sein. Alles möchte ich nämlich auch nicht wiederhaben, insbesondere die diversen Trash- und Junk-Boxen nicht (bitte nicht fragen, wieso ich die noch habe -- ich weiß es nicht).

Es gibt nun zwei nicht zu vernachlässigende Probleme:

Der zweite Punkt brachte mich im ersten Moment ein bisschen zum Rotieren, als ich das merkte. Wie sich herausstellen wird, ist das aber halb so schlimm.

Wer Thunderbird nutzt, kennt sicher diese "Compact folders"-Option. Was hat es damit auf sich? Nun, wenn man im Thunderbird eine Mail aus der Inbox löscht, sieht es erstmal so aus, als wäre die dann im Trash. Das ist aber nicht ganz so. Tatsächlich wird die Mail dupliziert und eine Kopie landet im Trash. Die ursprüngliche Mail wird dann als "gone" gekennzeichnet. Erst, wenn man "Compact folders" aufruft, fliegen die Mails wirklich aus der Inbox raus und sind nur noch im Trash. Ähnliches passiert mit "Draft" und "Sent", da scheinbar jede Mail erstmal ein Draft ist, bevor sie tatsächlich gesendet wird und im "Sent" landet. Das alles passiert mit dem Argument, dass das Löschen einer Mail in einer mbox-File vergleichsweise teuer ist.

Glücklicherweise wird diese Kennzeichnung immerhin in der Mail selbst vorgenommen und nicht etwa im Thunderbird-Index, der zu der Box gehört (oder sonstwo). Das läuft über das Header-Feld "X-Mozilla-Status", was hier erklärt ist: X-Mozilla-Status explained.

Okay, auf zum Python-Teil. Über das mailbox-Modul können bequem mbox'en, Maildirs und anderes gelesen und geschrieben werden. Eine konkrete mbox kriegt man so ins Maildir-Format:

#!/usr/bin/env python
# -*- coding: utf8 -*-

import mailbox
import sys

if len(sys.argv) <= 2:
    print 'Usage: %s <mbox> <target maildir>' % sys.argv[0]
    sys.exit(1)

def isDeleted(msg):
    flags = msg['X-Mozilla-Status']
    try:
        flags = int(flags, 16)
        if (flags & 8) != 0:
            return True
        else:
            return False
    except:
        return False

box = mailbox.mbox(sys.argv[1])
target = mailbox.Maildir(sys.argv[2])

for k in box.keys():
    msg = box.get_message(k)
    delstat = isDeleted(msg)
    print delstat, msg['date'], msg['subject']
    if not delstat:
        target.add(msg)

Dabei wird auch gleich überprüft, ob die Mail als gelöscht gekennzeichnet war, dann wird sie ausgelassen. Das Ziel-Maildir sollte besser existieren.

Die Liste, die find ganz am Anfang ausgespuckt hat, könnte man noch ein bisschen vorverarbeiten. Insbesondere würde ich gerne angeben, welche mbox in welches Maildir soll. Dazu setze ich den Namen des Maildirs an den Anfang der Zeile, gefolgt von einem Tab. Sieht dann also irgendwie so aus:

...
.uninformativ   5o92qf76.default/Mail/mail.whatever.de/Inbox
.Sent   rhpec2v2.default/Mail/mail.uni-tralala.de/Sent
...

Wenn ich kein Maildir angebe, möchte ich, dass diese mbox ausgelassen wird. Die Liste kann man dann zum Beispiel so abarbeiten, wobei noch der Basispfad für alle Maildirs angegeben werden muss:

#!/usr/bin/env python
# -*- coding: utf8 -*-

import sys
import os

if len(sys.argv) <= 2:
    print 'Usage: %s <path-to-boxes-list> <target-basepath>' % sys.argv[0]
    sys.exit(1)

basepath = sys.argv[2]

fp = open(sys.argv[1])
for line in fp.readlines():
    tokens = line.split('\t')
    if len(tokens) <= 1:
        continue
    tokens = map(str.strip, tokens)
    os.system("./box.py '" + tokens[1] + "' '" + os.path.join(basepath,
        tokens[0]) + "'")
fp.close()

Okay, machen wir das, wobei ich das Ziel-Maildir mal lieber erst in ein Testverzeichnis lege. Dort kann man dann die manuelle Filterung mit Mutt machen:

mutt -f /tmp/boxes/.Sent

Wenn alles nach Belieben sortiert ist, kann das nach ~/Mail oder wohin auch immer. Fertig.

Mir ist jedoch aufgefallen, dass vereinzelt Anhänge nicht mehr gehen. Ob das schon immer so war oder erst jetzt so geworden ist, kann ich nicht genau sagen. Unter Umständen ist das Vorgehen also verbesserungsfähig -- aber auf jeden Fall besser und kürzer als alles komplett selbst zu parsen, was ich am Anfang mal tun wollte. ;)