C# 使用imap協(xié)議獲取郵箱任意文件夾中的郵件

大家使用C#同步郵件的時(shí)候屎蜓,會(huì)不會(huì)遇到一件頭疼的事情辅鲸,C#只支持POP協(xié)議(只能獲取收件箱的郵件,其他文件夾獲取不到)沼溜,Imap需要用第三方的來潮梯,而常見的幾個(gè)第三方的類庫骗灶,在同步大附件的時(shí)候,總是會(huì)超時(shí)秉馏。迫于無奈耙旦,我只好自己寫一個(gè)Imap協(xié)議獲取郵件。通過不斷調(diào)試萝究,獲取大附件再也不會(huì)超時(shí)了母廷,下面就跟大家分享一下。

查看原文

image

準(zhǔn)備知識(shí)

程序原理就是利用TCP請(qǐng)求郵件服務(wù)器糊肤,然后發(fā)送IMAP命令執(zhí)行一系列操作琴昆。

1.登錄

a01 xxx@126.com password
結(jié)果:a01 OK LOGIN completed

2.獲取文件夾列表

a02 list "" *
結(jié)果:

LIST () “/” “INBOX”
LIST (\Drafts) “/” “&g0l6P3ux-”
LIST (\Sent) “/” “&XfJT0ZAB-”
LIST (\Trash) “/” “&XfJSIJZk-”
LIST (\Junk) “/” “&V4NXPpCuTvY-”
LIST () “/” “&dcVr0pCuTvY-”
LIST () “/” “&Xn9USpCuTvY-”
LIST () “/” “&i6KWBZCuTvY-”
LIST () “/” “&YhF2hGWHaGM-”
LIST () “/” “java”
LIST () “/” “&bUuL1WWHTvZZOQ-”
LIST () “/” “&Y6hef5CuTvY-”
LIST () “/” “test”
LIST () “/” “folder”
a02 OK LIST Completed

3.選擇某個(gè)文件夾

a03 select inbox
結(jié)果:

2918 EXISTS
458 RECENT
OK [UIDVALIDITY 1] UIDs valid
FLAGS (\Answered \Seen \Deleted \Draft \Flagged)
OK [PERMANENTFLAGS (\Answered \Seen \Deleted \Draft \Flagged)] Limited
a03 OK [READ-WRITE] SELECT completed

4.獲取日期開始郵件

a04 SEARCH SINCE 日-月-年
其中月為英文縮寫,如Jan馆揉,F(xiàn)eb

5.獲取郵件內(nèi)容

b01 fetch body[header]  //郵件頭
b01 fetch body[1]       //郵件主體

具體命令大家可自行百度學(xué)習(xí)业舍,這邊就不過多講解。

代碼詳解

連接

public void Connection(string serveraddress, int port)
{
    try
    {
        tcpclient.Connect(serveraddress, port);
        sslstream = new SslStream(tcpclient.GetStream());
        sslstream.AuthenticateAsClient(serveraddress);
        bool flag = sslstream.IsAuthenticated;
        if (!flag)
        {
            throw new Exception("sslstream IsAuthenticated return false");
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

登錄

構(gòu)造imap命令升酣,使用StreamWriter寫入命令舷暮,StreamReader逐行獲取,判斷失敗返回的關(guān)鍵字

public void login(string username, string password)
{
    sw = new StreamWriter(sslstream);
    // Assigned reader to stream
    reader = new StreamReader(sslstream);
    sw.WriteLine("a01 LOGIN " + username + " " + password);
    sw.Flush();
    try
    {
        string strTemp = string.Empty;
        while ((strTemp = reader.ReadLine()) != null)
        {
            if (strTemp.IndexOf("OK LOGIN completed") != -1)
            {
                break;
            }
            else if (strTemp.IndexOf("NO LOGIN failed") != -1)
            {
                throw new Exception("NO LOGIN failed");
            }
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

查看文件夾

同理提交查看文件夾的命令噩茄,循環(huán)讀取返回內(nèi)容下面,直到"OK LIST completed"結(jié)束

public string FoldersList()
{
    sw.WriteLine("a02 list \"\" *");
    sw.Flush();
    string str = string.Empty;
    try
    {
        string strTemp = string.Empty;
        while ((strTemp = reader.ReadLine()) != null)
        {
            if (strTemp.IndexOf("OK LIST completed") != -1)
            {
                break;
            }
            str += strTemp + "\r";
        }
    }
    catch (Exception ex)
    {
        return "error:" + ex.Message;
    }
    return str;
}

選擇文件夾

存在"SELECT completed"返回True,否則False

public bool SelectFolder(string folder)
{
    sw.WriteLine("a03 select " + folder);
    sw.Flush();
    try
    {
        string strTemp = string.Empty;
        while ((strTemp = reader.ReadLine()) != null)
        {
            if (strTemp.IndexOf("NO SELECT failed") != -1)
            {
                return false;
            }
            if (strTemp.IndexOf("SELECT completed") != -1)
            {
                return true;
            }
        }
    }
    catch (Exception ex)
    {
        return false;
    }
    return false;
}

獲取郵件id

使用"SEARCH SINCE 日期"命令獲取某天開始的郵件绩聘,ToSearchDt()方法會(huì)將日期轉(zhuǎn)成IMAP命令需要的格式沥割,返回的是uid數(shù)組

public string[] Search(DateTime dateTime, bool isReverse = true)
{
    string strDateTime = ToSearchDt(dateTime);
    sw.WriteLine("a04 SEARCH SINCE " + strDateTime);
    sw.Flush();
    string str = string.Empty;
    try
    {
        string strTemp = string.Empty;
        while ((strTemp = reader.ReadLine()) != null)
        {
            if (strTemp.IndexOf("SEARCH") != -1)
            {
                str = strTemp.Replace("SEARCH", "").Replace("*", "").Trim();
            }
            break;
        }
    }
    catch (Exception ex)
    {
        ;
    }
    if (string.IsNullOrEmpty(str)) return null;
    string[] arr = System.Text.RegularExpressions.Regex.Split(str, @"\s+");
    if (isReverse)
    {
        arr = arr.Reverse().ToArray();
    }
    return arr;
}

獲取郵件

這邊主要是做了一個(gè)判斷是否附件的處理耗啦,如果是附件設(shè)置一次讀取的大小,再循環(huán)讀取机杜。如果用行讀取的話帜讲,附件太大的話會(huì)卡很久到超時(shí)。這邊設(shè)置的是一次100K椒拗,這個(gè)值可以適當(dāng)調(diào)整

private string fetch(int id, string data, bool line = false)
{
    sw.WriteLine("b01 fetch " + id + " " + data);
    sw.Flush();
    string str = string.Empty;
    try
    {
        System.Text.RegularExpressions.Regex reg1 = new System.Text.RegularExpressions.Regex(@"body\[[1-9][0-9]{0,1}\]");
        bool ismatch = reg1.IsMatch(data);
        if (ismatch && data != "body[1]")
        {
            //100k
            var clen = 1024 * 100;
            var read = new Char[clen];
            var count = reader.Read(read, 0, clen);
            while (count > 0)
            {
                var strTemp = new string(read, 0, count);
                str += strTemp;
                if (strTemp.IndexOf("OK FETCH completed") != -1)
                    break;
                count = reader.Read(read, 0, clen);
            }
            var arr = str.Split('\r');
            string[] b = new string[arr.Length - 3];
            Array.Copy(arr, 1, b, 0, arr.Length - 3);
            str = string.Join("\r", b).Replace("\r\n", "").Replace(")", "");
        }
        else
        {
            string strTemp = string.Empty;
            while ((strTemp = reader.ReadLine()) != null)
            {
                if (strTemp.IndexOf("OK SEARCH completed") != -1 || strTemp.ToLower().IndexOf(data) != -1)
                {
                    continue;
                }
                if (strTemp.IndexOf("OK FETCH completed") != -1 || strTemp.IndexOf("BAD invalid command or parameters") != -1)
                {
                    break;
                }
                if (line)
                {
                    str += strTemp;
                }
                else
                {
                    str += strTemp + "\r";
                }
            }
        }
    }
    catch (Exception ex)
    {
        ;
    }
    return str;
}

這邊主要講解核心的方法似将,其余的是一些解析郵件內(nèi)容的方法,大家可下載源碼自行研究蚀苛。

為何需要解析在验?因?yàn)楂@取下來的是一大串的文本,我們需要根據(jù)一些標(biāo)識(shí)堵未,獲取到我們想要的東西译红,如發(fā)件時(shí)間、發(fā)送的郵箱兴溜、標(biāo)題、主題和附件等耻陕。我解析的方法可能略顯粗糙拙徽,希望大家有興趣的話可以來優(yōu)化一下。

調(diào)用實(shí)例

這邊舉例獲取收件箱的郵件诗宣,如果全部文件夾都需要?jiǎng)t可以遍歷list膘怕,

using (ImapClient client = new ImapClient())
{
    try
    {
        client.Connection("imap.mxhichina.com", 993);
        client.login("郵箱", "密碼");
        //獲取郵箱文件夾
        var list = client.FoldersList();
        //獲取指定文件夾
        var hasFolder = client.SelectFolder("INBOX");
        if (hasFolder)
        {
            //要查詢的時(shí)間
            var ids = client.Search(DateTime.Now.AddDays(-1));
            if (ids != null)
            {
                foreach (var id in ids)
                {
                    try
                    {
                        var ID = Convert.ToInt32(id);
                        //獲取郵件頭
                        var header = client.GetHeader(ID);
                        //獲取郵件主體
                        var body = client.GetBody(ID);
                        //獲取附件
                        var attachemnts = client.GetAttachments(ID);
                        foreach (var attachment in attachemnts)
                        {
                            byte[] bytes = Convert.FromBase64String(attachment.bs64);
                            string uploadDir = "img";
                            if (!Directory.Exists(uploadDir))
                            {
                                Directory.CreateDirectory(uploadDir);
                            }
                            using (Image img = Image.FromStream(new MemoryStream(bytes)))
                            {
                                var fileName = uploadDir += "/" + System.Guid.NewGuid().ToString() + ".jpg";
                                img.Save(uploadDir, ImageFormat.Jpeg);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("獲取郵件異常,id:{0},異常{1}", id, ex.Message);
                    }
                }
            }
        }
    }
    catch (Exception ex)
    {
        ;
    }
}

源代碼:https://github.com/codernice/ImapHelper.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末召庞,一起剝皮案震驚了整個(gè)濱河市岛心,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篮灼,老刑警劉巖忘古,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異诅诱,居然都是意外死亡髓堪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門娘荡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來干旁,“玉大人,你說我怎么就攤上這事炮沐≌海” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵大年,是天一觀的道長(zhǎng)换薄。 經(jīng)常有香客問我玉雾,道長(zhǎng),這世上最難降的妖魔是什么专控? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任抹凳,我火速辦了婚禮,結(jié)果婚禮上伦腐,老公的妹妹穿的比我還像新娘赢底。我一直安慰自己,他們只是感情好柏蘑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布幸冻。 她就那樣靜靜地躺著,像睡著了一般咳焚。 火紅的嫁衣襯著肌膚如雪洽损。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天革半,我揣著相機(jī)與錄音碑定,去河邊找鬼。 笑死又官,一個(gè)胖子當(dāng)著我的面吹牛延刘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播六敬,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼碘赖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了外构?” 一聲冷哼從身側(cè)響起普泡,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎审编,沒想到半個(gè)月后撼班,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垒酬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年权烧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伤溉。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡般码,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乱顾,到底是詐尸還是另有隱情板祝,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布走净,位于F島的核電站券时,受9級(jí)特大地震影響孤里,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜橘洞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一捌袜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炸枣,春花似錦虏等、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侯养,卻和暖如春敦跌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逛揩。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工柠傍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辩稽。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓惧笛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親搂誉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容