C#使用 MailKit获取邮件中的附件(QQ邮箱/163网易邮箱)

时间:2022-09-24 09:16:09

背景介绍:VS2017 | .net core | C# |  .netframwork 4.0

-----------------------------------------------------------------------

这东西本来没打算记的,主要是连接邮件服务器的时候百度搜出来的大多博客不能用,浪费1小时,所以来记一下,主要看的是这个参考博客,我在此基础上做了改动


实现功能如下:

1. 通过授权码连接到邮箱服务器,并获取到某文件夹下的邮件,以上出错添加了能看到是哪块的问题(对使用的邮箱需要你开启STMP服务,设置规则以确保符合的邮件移动到此文件夹下)

2. 不打开邮件,只根据主题进行筛选,同主题邮件取日期最新的那个(通过linq筛选实现)

3. 直接下载邮件中所有的普通附件(大于50M的是超大附件,只有链接,且无法通过代码下载(反正我不行,你行你试试))

4. 下载完成删除同类旧压缩包


因为头担心QQ强制改掉授权码,要求换一个,我选了网易,这两个都支持最大50M的,还有新浪什么的,但没注册就没用

关于授权码:

网易:授权码是你自己设定的,方便记忆 最多16位

QQ:授权码给你分配的,12位,但明确说了可以有很多个不用记,但只要你改QQ密码或什么中心密码,之前的全部失效

但连获取邮箱文件夹内容这里,QQ网易不一样啊,网易真的坑,你愣愣的上来会获不到“故意设置障碍,其实是网易需要我们表明我们登录的客户端身份,具体的原理和命令参照我上一篇文章查看IMAP ID COMMAND命令,下面只具体说说mailkit如何使用到这条命令,其实mailkit是提供这个命令的,足见mailkit的强大”   ---参考这个博客





一,nuget包,搜MailKit   找到适合你的版本,下载

-----------------------------------------------------------------------

二.  代码内容(QQ的)


            string account = "198416****@qq.com";
            string passWord = "jsknsh**********";//获得的授权码
            m_logger.LogInformation("开始到邮箱查询更新包");

            //准备工作结束
            ImapClient client = new ImapClient();

            #region 连接到邮件服务器
            try
            {
                //一、创建获取邮件客户端并连接到邮件服务器。
                //带端口号和协议的连接方式
                client.Connect("imap.qq.com", 993, true);
            }
            catch (ImapCommandException ex)
            {
                 m_logger.LogInformation($"---------尝试连接时出错:{0}------------" + ex.Message);
            }
            catch (ImapProtocolException ex)
            {
                 m_logger.LogInformation($"---------尝试连接时的协议错误:{0}------------" + ex.Message);
            }
            catch (Exception ex)
            {
                 m_logger.LogInformation($"---------服务器连接错误:{0}------------" + ex.Message);
            }

            try
            {
                // 二、验证登录信息,输入账号和密码登录。
                client.Authenticate(account, passWord);
            }
            catch (AuthenticationException ex)
            {
                 m_logger.LogInformation($"---------无效的用户名或密码:{0}------------" + ex.Message);
            }
            catch (ImapCommandException ex)
            {
                 m_logger.LogInformation($"---------尝试验证错误:{0}------------" + ex.Message);
            }
            catch (ImapProtocolException ex)
            {
                 m_logger.LogInformation($"---------尝试验证时的协议错误:{0}------------" + ex.Message);
            }
            catch (Exception ex)
            {
                 m_logger.LogInformation($"---------账户认证错误:{0}------------" + ex.Message);
            }
            #endregion

            //  三、获取邮箱文件夹。这次用到的的收件箱文件夹叫YiBaoRDZ。

            //获取所有的文件夹
            //List<IMailFolder> mailFolderList = client.GetFolders(client.PersonalNamespaces[0]).ToList();
            //只获取收件箱文件夹
            var folder = client.GetFolder("其他文件夹/YiBaoRDZ");

            //四、从文件夹获取文件
            //打开文件夹并设置为读的方式
            folder.Open(MailKit.FolderAccess.ReadOnly);

            //获取大于2018-3-26时间的所有邮件的唯一Id
            var uidss = folder.Search(SearchQuery.DeliveredAfter(DateTime.Parse("2018-3-26")));

            //获取邮件头
            var MailHeaders = folder.Fetch(uidss, MessageSummaryItems.UniqueId | MessageSummaryItems.Full);
            //同主题邮件取日期最新
            var NeedMailIds = from a in MailHeaders
                       group a by a.NormalizedSubject into grp
                       let maxTime = grp.Max(a => a.Date.DateTime)
                       from row in grp
                       where row.Date.DateTime == maxTime
                       select row.UniqueId;

            if (NeedMailIds!=null)
            {
                //获取邮件信息
                foreach (var item in NeedMailIds)
                {
                    MimeMessage message = folder.GetMessage(item);
                    //提取该邮件所有普通附件
                    foreach (MimePart attachment in message.Attachments)
                    {
                        //下载附件
                        using (var cancel = new System.Threading.CancellationTokenSource())
                        {
                            string filePath = Path.Combine(outDirPath, attachment.FileName);
                            using (var stream = File.Create(filePath))
                            {
                                attachment.ContentObject.DecodeTo(stream, cancel.Token);
                            }
                        }
/////////////////////////////////从这里开始是我自己写的内容,和本次博客无关//////////////////////////////////
                        // 散列值是否正确
                        string strHash;
                        string strLocalTmpFullPath = Path.Combine(direcotryDownload.FullName, attachment.FileName);
                        using (FileStream fs = new FileStream(strLocalTmpFullPath, FileMode.Open, FileAccess.Read))
                        {
                            strHash = Crypto.GetSHA1String(fs);
                        }
                        if (strHash != singleInfo.HashString)
                        {
                            m_logger.LogError("文件散列值不匹配\n远程散列值:" + singleInfo.HashString + "\n本地计算散列值:" + strHash);
                            continue;
                        }

                        //删除同类旧版更新包
                        DirectoryInfo dyInfo = new DirectoryInfo(outDirPath);
                        //获取文件夹下所有的文件
                        foreach (FileInfo feInfo in dyInfo.GetFiles())
                        {
                            //判断同名文件日期是否小于今天,是则删除
                            if (feInfo.CreationTime < DateTime.Today&& feInfo.Name.Contains(singleInfo.PackageName))
                                feInfo.Delete();
                        }
                    }


                }
            }
            
            //关闭文件夹
            folder.Close();

QQ官网帮助文档:QQ邮箱的POP3与SMTP服务器是什么  我用的SMTP,而SMTP需要身份验证,密码不再是邮箱的登陆密                                                                                                码,而是授权码    如何拿到授权码

                              帮助中心

-----------------------------------------------------------------------

三.  代码内容(网易的)

//mailkit还提供查询是否支持命令的语句,如果你不知道邮箱是否支持这个命令可以使用下面的函数来判断:
        private static bool HasImapCapabilitiesId(ImapCapabilities sourceFlag, ImapCapabilities targetFlag)
        {
            return ((sourceFlag | targetFlag) == sourceFlag);
        }
            string account = "182379*****@163.com";
            string passWord = "zltdkj******";//获得的授权码
            m_logger.LogInformation("开始到邮箱查询更新包");

            //Result result;
            List<PackageInfo> listPackages = m_managerPackages.GetAllExeInfos();//获取可更新程序信息(更新程序,主程序,校验引擎,四个数据库)
            List<DownloadedInfo> listDownloadeds = m_managerDownload.GetDownloadedPackageInfos();//获得本地所有压缩包文件信息

            DirectoryInfo direcotryDownload = m_managerDownload.GetDownloadDirectory();//获得本地更新压缩包文件存放路径
            if (null == direcotryDownload)
            {
                m_logger.LogError("获取本地包存放目录失败");
                return;
            }

            #endregion
            //准备工作结束
            ImapClient client = new ImapClient();

            #region 连接到邮件服务器
            try
            {
                //一、创建获取邮件客户端并连接到邮件服务器。
                //带端口号和协议的连接方式
                client.Connect("imap.163.com", 993, true);
            }
            catch (ImapCommandException ex)
            {
                 m_logger.LogInformation($"---------尝试连接时出错:{0}------------" + ex.Message);
            }
            catch (ImapProtocolException ex)
            {
                 m_logger.LogInformation($"---------尝试连接时的协议错误:{0}------------" + ex.Message);
            }
            catch (Exception ex)
            {
                 m_logger.LogInformation($"---------服务器连接错误:{0}------------" + ex.Message);
            }

            try
            {
                // 二、验证登录信息,输入账号和密码登录。
                client.Authenticate(account, passWord);
            }
            catch (AuthenticationException ex)
            {
                 m_logger.LogInformation($"---------无效的用户名或密码:{0}------------" + ex.Message);
            }
            catch (ImapCommandException ex)
            {
                 m_logger.LogInformation($"---------尝试验证错误:{0}------------" + ex.Message);
            }
            catch (ImapProtocolException ex)
            {
                 m_logger.LogInformation($"---------尝试验证时的协议错误:{0}------------" + ex.Message);
            }
            catch (Exception ex)
            {
                 m_logger.LogInformation($"---------账户认证错误:{0}------------" + ex.Message);
            }
            #endregion

            //这是网易故意设置障碍,其实是网易需要我们表明我们登录的客户端身份,具体的原理和命令参照我上一篇文章查看IMAP ID COMMAND命令,下面只具体说说mailkit如何使用到这条命令,其实mailkit是提供这个命令的,足见mailkit的强大
            //判断是否 添加ID COMMOND命令
            if (HasImapCapabilitiesId(client.Capabilities, ImapCapabilities.Id))
            {
                var clientImplementation = new ImapImplementation
                {
                    Name = "MeSince",
                    Version = "2.0"
                };
                var serverImplementation = client.Identify(clientImplementation);
            }

            //  三、获取邮箱文件夹。这次用到的的收件箱文件夹叫YBrdz。

            //获取所有的文件夹
            //List<IMailFolder> mailFolderList = client.GetFolders(client.PersonalNamespaces[0]).ToList();
            List<IMailFolder> mailFolderList = client.GetFolders(client.PersonalNamespaces[0]).ToList();
            IMailFolder InBoxFolder;
            if (mailFolderList != null)
            {
                InBoxFolder = mailFolderList.Find(o => o.FullName.ToUpper() == "YBRDZ");
                //InBoxFolder.Open(MailKit.FolderAccess.ReadOnly);
            }
            //只获取收件箱文件夹
            var folder = client.GetFolder("YBrdz");

            //四、从文件夹获取文件
            //打开文件夹并设置为读的方式

            //folder.Open(MailKit.FolderAccess.ReadOnly);
            //已读写的方式打开文件夹
            folder.Open(FolderAccess.ReadWrite);

            //获取大于2018-3-26时间的所有邮件的唯一Id
            var uidss = folder.Search(SearchQuery.DeliveredAfter(DateTime.Parse("2018-3-26")));

            //获取邮件头
            var MailHeaders = folder.Fetch(uidss, MessageSummaryItems.UniqueId | MessageSummaryItems.Full);
            //同主题邮件取日期最新
            var NeedMailIds = from a in MailHeaders
                       group a by a.NormalizedSubject into grp
                       let maxTime = grp.Max(a => a.Date.DateTime)
                       from row in grp
                       where row.Date.DateTime == maxTime
                       select row.UniqueId;

            if (NeedMailIds!=null)
            {
                //获取邮件信息
                foreach (var item in NeedMailIds)
                {
                    MimeMessage message = folder.GetMessage(item);
                    //提取该邮件所有普通附件
                    foreach (MimePart attachment in message.Attachments)
                    {
                        //由附件名称获得信息,进行比较
                        FileNameInfo singleInfo = FileNameInfo.Create(attachment.FileName);
                        // 是否已经下载过相同或更高版本
                        if (listDownloadeds.
                            Where(s => (s.Name == singleInfo.PackageName) && (s.Version >= singleInfo.Version)).
                            Count() > 0)
                        {
                            continue;
                        }
                        //下载附件
                        using (var cancel = new System.Threading.CancellationTokenSource())
                        {
                            string filePath = Path.Combine(outDirPath, attachment.FileName);
                            using (var stream = File.Create(filePath))
                            {
                                attachment.ContentObject.DecodeTo(stream, cancel.Token);
                                m_logger.LogInformation("更新压缩包 " + attachment.FileName + " 已下载");
                            }
                        } 
/////////////////////////////////从这里开始是我自己写的内容,和本次博客无关//////////////////////////////////

这次参考的博客汇总:

MailKit---获取邮件

使用MailKit / MimeKit从电子邮件中剥离附件

mailkit----163邮箱登录拉取邮件的坑