2011年2月20日日曜日

Gmailの内容をIMAPで取得する LumiSoft.Net編

前回のGmailの内容をIMAPで取得する ImapX編に引き続き、今回は下記ライブラリを使用してGmailの内容を取得する方法を解説する。

LumiSoft.Net
LumiSoft Forum
Source code
Examples

ImapXに比べてこなれていないObject Modelなのでとっつきにくいけれど、慣れればImapX以上の柔軟さがあるし、何よりOSSなので自分で問題解決できるのが良い。

コードはこんな感じ。
// すべてのメールを取得する
try
{
    using (var imap = new IMAP_Client())
    {
        imap.Logger = new Logger();
        imap.Logger.WriteLog += (s, e) => Console.WriteLine(e.LogEntry.Text);
        imap.Connect("imap.gmail.com", 993, true);
        imap.Login("username", "password");
        imap.SelectFolder("INBOX");

        var fetchHandler = new IMAP_Client_FetchHandler();
        fetchHandler.NextMessage += (s, e) =>
        {
            // 次のメッセージ
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("New message");
        };
        fetchHandler.Envelope += (s, e) =>
        {
            // To, Fromとか
            Console.WriteLine("Envelope");
            var envelope = e.Value;
            var from = "";
            if (envelope.From != null)
                from = envelope.From.ToList().Select(x => x.ToString()).Aggregate((x, y) => string.Format("{0};{1}", x, y));
            else
                from = "none";

            Console.WriteLine(from);
            var subject = envelope.Subject ?? "none";
            Console.WriteLine(subject);
        };
        fetchHandler.Flags += (s, e) =>
        {
            // フラグ(SEEN, UNSEENとか)
            e.Value.ToList().ForEach(f => Console.WriteLine(f));
        };
        fetchHandler.InternalDate += (s, e) =>
        {
            // 日付
            Console.WriteLine(e.Value.ToString());
        };
        fetchHandler.Rfc822Size += (s, e) =>
        {
            // サイズ
            Console.WriteLine(((decimal)(e.Value / (decimal)1000)).ToString("f2") + " kb");
        };
        fetchHandler.UID += (s, e) =>
        {
            // メールID
            Console.WriteLine(e.Value);
        };

        var sequence = new IMAP_SequenceSet();
        sequence.Parse("1:*");  // 取得する範囲を指定する
        // ※メールが15通あるとして
        // 2,4:7,9,12:*を指定すると
        // 2,4,5,6,7,9,12,13,14,15の順番のメールが取得できる
        // 詳細はIMAP_SequenceSetを参照のこと

        // 第一引数は指定のsequenceがUIDかどうかの判定用。UIDを指定する場合もIMAP_SequenceSetの記述は変わらない
        // 第三引数に指定したものがサーバーから取得され、値の解析後、fetchHandlerがコールバックされる
        imap.Fetch(false, sequence, new IMAP_Fetch_DataItem[]
                                                            {
                                                                new IMAP_Fetch_DataItem_Envelope(),
                                                                new IMAP_Fetch_DataItem_Flags(),
                                                                new IMAP_Fetch_DataItem_InternalDate(),
                                                                new IMAP_Fetch_DataItem_Rfc822Size(),
                                                                new IMAP_Fetch_DataItem_Uid()
                                                            }, fetchHandler);
    }
}
catch (Exception x)
{
    Console.WriteLine(string.Format("IMAP server returned : {0}", x.Message));
}

一見して分かる通りかなり癖があるのでとっつきにくい部分もあるが、慣れてしまえば細かいところまで制御できるので非常に有用だ。

もうひとつサンプルとして未読メッセージのみ取得する方法を紹介しておく。
try
{
    using (var imap = new IMAP_Client())
    {
        imap.Logger = new Logger();
        imap.Logger.WriteLog += (s, e) => Console.WriteLine(e.LogEntry.Text);
        imap.Connect("imap.gmail.com", 993, true);
        imap.Login("username", "password");
        imap.SelectFolder("INBOX");
        
        // 未読のものだけ取得
        var unseenMessages = imap.Search(false, "", "unseen");
        unseenMessages.ToList().ForEach(uid =>
        {
            var seqSet = new IMAP_SequenceSet();
            seqSet.Parse(uid.ToString());

            // Bodyのみハンドル
            var fetchHandler = new IMAP_Client_FetchHandler();
            fetchHandler.Rfc822 += (s, e) =>
            {
                var storeStream = new MemoryStream();
                e.Stream = storeStream;
                e.StoringCompleted += (s2, e2) =>
                {
                    storeStream.Position = 0;
                    var mime = Mail_Message.ParseFromStream(storeStream);

                    Console.WriteLine("Attachment");
                    foreach (var entity in mime.Attachments)
                    {
                        if (entity.ContentDisposition != null && entity.ContentDisposition.Param_FileName != null)
                        {
                            // 添付ファイルの保存
                            var path = Path.Combine(@"C:\", entity.ContentDisposition.Param_FileName);
                            File.WriteAllBytes(path,((MIME_b_SinglepartBase)entity.Body).Data);
                            Console.WriteLine(string.Format("{0} saved", entity.ContentDisposition.Param_FileName));
                        }
                        else
                        {
                            Console.WriteLine("untitled");
                        }
                    }

                    if (mime.BodyText != null)
                        Console.WriteLine(mime.BodyText);

                     // おまけ
                     // テスト用に今回の処理で既読メールになったのを未読メールに戻す
                     imap.StoreMessageFlags(true, seqSet, IMAP_Flags_SetType.Replace, IMAP_MessageFlags.Recent);
                };                
            };

            imap.Fetch(true, seqSet,
                new IMAP_Fetch_DataItem[]
                    {
                        new IMAP_Fetch_DataItem_Rfc822()
                    },fetchHandler);
        });
    }
}
catch (Exception x)
{
    Console.WriteLine(string.Format("IMAP server returned : {0}", x.Message));
}

Gmailの内容をIMAPで取得する ImapX編

送信されてきたメールをIMAPで取得、解析して内容をDBに突っ込もう、というツールが必要だったので良いライブラリが無いかと探して見つけたのが下記。

ImapX – free for use .NET library
数あるライブラリの中でもObject Modelがよく設計されているので至極簡単に使えて便利。ただOpen Sourceではないので何か問題があったときに何も対処できないし、作者のレスポンスもかなり遅いので対応は期待できないので注意が必要だ。

今回の要件は下記。
  • Gmailを使う
  • 画像の添付がある
  • IMAPを使う(未読メールのみ取得したいため)

GmailのアカウントでIMAPを使えるようにする方法は下記を参照のこと。
Enabling IMAP

で、実際のソースコードはこんな感じ。
var client = new ImapX.ImapClient("imap.gmail.com", 993, true);
var result = false;

result = client.Connection();
if (result)
    Console.WriteLine("@Connected");

result = client.LogIn("username", "password");
if (result)
    Console.WriteLine("@Logged in");

// 未読のものだけ取得
var messages = client.Folders["INBOX"].Search("UNSEEN", true);  // Searchの第二引数をfalseにする場合は、後でMessage.Processを呼び出す必要がある
foreach (var m in messages)
{
    //m.Process();  // Searchの第二引数がfalseの場合はこの処理が必要
    m.SetFlag(ImapX.ImapFlags.SEEN);    // 既読フラグを設定
    foreach (var attachment in m.Attachments)
    {
        var location = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        attachment.SaveFile(Path.Combine(location, m.From[0].Address));     // SaveFileを呼び出すと添付ファイルのファイル名で指定の場所に保存してくれる
    }

    // テキストをデコードする
    var encoding = Encoding.GetEncoding("iso-2022-jp");  // 実際にはContentTypeのCharsetからエンコード名を取得しよう
    var text = encoding.GetString(encoding.GetBytes(m.TextBody.TextData));

    // Do someting...
}

Searchメソッドに使えるコマンドは、ALL、ANSWERE、BCC、BEFORE、BODY、CC、DELETED、DRAFT、FLAGGED、SEEN、UNSEENなどのほかにもかなりあるのでImapX.dllと一緒に配布されるimap search commands.txtを参照して欲しい。

POP3で良いのならOpenPop.NETもかなり使いやすい。サンプルコード集も分かりやすい。