Push-Nachrichten von MacTechNews.de
Würden Sie gerne aktuelle Nachrichten aus der Apple-Welt direkt über Push-Nachrichten erhalten?
Journals>Journals von MrChad>PDF-Anhänge automatisiert aus eingehender Mail extrahieren - Teil 2

PDF-Anhänge automatisiert aus eingehender Mail extrahieren - Teil 2

Das folgende kleine Demo-Programm in C# ist eine vereinfachte Version eines Programms, das wir hier im Einsatz haben. Um die Demonstration so einfach wie möglich zu halten, habe ich Fehlerbehandlung, Logging und die weitere Verarbeitung fast komplett herausgestrichen.

Das Programm ist der zweite Teil eines kleinen Projektes zum automatisierten Abarbeiten eingehender Mail. Es analysiert die .eml-Dateien in einem bestimmten Ordner und extrahiert daraus die PDF-Anhänge. Woher die .eml-Dateien kommen, ist in Teil 1 beschrieben:

Das Überraschendste zuerst:
  • Man kann heutzutage mit Microsoft-Programmier-Tools gut laufende Programme für den Mac schreiben
  • Dabei kann man Windows-Systemaufrufe und tausende von .NET-Libraries problemlos mitverwenden
  • Diese .NET-Libraries hatten noch nie 'nen Mac von weitem gesehen und funktionieren trotzdem
  • Im Ergebnis ist hier ein überraschend simples, ja beinahe primitives Progrämmchen entstanden.

Disclaimer:
  • Ich möchte keinen Streit über die "beste Programmiersprache aller Zeiten" vom Zaun brechen.
  • Ohne Zweifel gibt es andere, gute Programmier-Umgebungen für den Mac.
  • Für mich war es aber einfach am bequemsten, in einer längst vertrauten Umgebung wie gewohnt weiterzuarbeiten.


Geschrieben wurde das Programm in Visual Studio für Mac unter Verwendung des .NET-Framework 7.0 . Als "kleinere" Variante müsste auch VS-Code möglich sein, das habe ich aber nicht getestet.
  • Zur Zerlegung von MIME-Mail und zur Extraktion von PDF-Anhängen bedient sich das Programm einer externen .NET-Library, die man sich einmalig in Visual Studio als NuGet-Verweis dazuladen muss:
  • Nach dem Kompilieren ("Erstellen") und "veröffentlichen" entsteht in den Tiefen des bin-Zweiges ein Ordner publish mit 6 Dateien. Zu diesem Ordner legt man sich am besten einen Symlink, z.B. ~/GetPdfFromEml .
  • In dem publish Ordner braucht man jetzt noch einen weiteren Symlink namens Daten, der auf den Datei-Ordner aus Teil 1 (z.B. ~/Dokumente/mailDrop) zeigt.

Das eigentlich Interessante an dem Demo-Programm ist diese kleine und ganz verblüffend einfache Funktion:
private static void HandleMimeEntity(MimeEntity entity, DirectoryInfo dir)

Die Funktion wandert rekursiv durch die gesamte "multipart"-MIME-Struktur und ruft sich dabei mehrfach selbst wieder auf, bis sie endlich auf einen einzelnen Anhang ("attachment") trifft. Den (HTML-)Text und alle evtl. eingebetteten Bilder usw. übergeht sie einfach.
Wenn die Aufruf-Kaskade dann endlich an der richtigen Stelle mit den "attachments" angekommen ist, wird die dort eingebettete Datei einfach angelegt und blind beschrieben. Die ganze Arbeit des Dekodieren und Umwandeln macht die eingebundene Library ganz allein.

Das war's dann eigentlich schon. Der Rest des Programms ist "Sozialkram", weitgehend selbsterklärend und kaum der Rede wert.

Der vollständige Sourcecode des hier vorgestellten Programms folgt im Kommentar #1 und kann direkt in Visual Studio eingesetzt werden.
Um den Code möglichst einfach zu halten, habe ich wieder viel von dem Logging und der Fehlerbehandlung herausgestrichen.

Kommentare

MrChad04.03.23 08:53
using MimeKit;

namespace GetPdfFromEml;
class Program
{
    static void Main(string[] args)
    {
        Directory.SetCurrentDirectory(
            System.Reflection.Assembly.GetExecutingAssembly().Location + "/../");
        DirectoryInfo? theDir;
        try
        {
            theDir = new DirectoryInfo("Daten").ResolveLinkTarget(true) as DirectoryInfo;
        }
        catch
        {
            Console.WriteLine("Daten-Ordner fehlt --nix getan--");
            return;
        }

        foreach (var fi in theDir.GetFiles())
        {
            if (!fi.Attributes.HasFlag(FileAttributes.Directory) &&
                fi.Extension.ToLowerInvariant() == ".eml")
            {
                var stm = fi.OpenRead();
                var msg = MimeMessage.Load(stm);

                HandleMimeEntity(msg.Body, theDir);
                File.Move(fi.FullName, fi.FullName + ".done");
            }
        }
    }

    private static void HandleMimeEntity(MimeEntity entity, DirectoryInfo dir)
    {
        var multipart = entity as Multipart;

        if (multipart != null)
        {
            for (int i = 0; i < multipart.Count; i++)
                HandleMimeEntity(multipart[i], dir);
            return;
        }

        var rfc822 = entity as MessagePart;
        if (rfc822 != null)
        {
            var message = rfc822.Message;
            HandleMimeEntity(message.Body, dir);
            return;
        }

        var part = (MimePart)entity;
        if (part.ContentDisposition == null || part.ContentType == null)
        {
            // nix
        }
        else if (part.ContentDisposition.Disposition == "attachment" &&
            (part.ContentType.MediaType == "application" || part.ContentType.MediaType == "text"))
        {
            var fileName = dir + "/" + part.FileName;
            if (!File.Exists(fileName))
            {
                using (var stm = File.Create(fileName))
                {
                    part.Content.DecodeTo(stm);
                }
            }
        }
    }
}

Kommentieren

Sie müssen sich einloggen, um diese Funktion nutzen zu können.