From aeae334295242009b3eb62c21579b5ff1fb3eee7 Mon Sep 17 00:00:00 2001 From: kengwang Date: Tue, 29 Aug 2023 00:08:29 +0800 Subject: [PATCH 01/11] =?UTF-8?q?[refact]=20=E9=87=8D=E6=9E=84=20MailServi?= =?UTF-8?q?ce=20=E5=9F=BA=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MicaApps.Mail.UWP.csproj | 3 ++ .../MailService/MailServiceBase.cs | 23 +++++++++ .../Abstraction/Models/EmailAccount.cs | 7 +++ .../Abstraction/Models/MailFolder.cs | 7 +++ .../Abstraction/Models/SingleMailMessage.cs | 10 ++++ src/MicaApps.Mail/Class1.cs | 5 -- src/MicaApps.Mail/Extensions/Results.cs | 48 +++++++++++++++++++ src/MicaApps.Mail/MicaApps.Mail.csproj | 3 ++ 8 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/MailFolder.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs delete mode 100644 src/MicaApps.Mail/Class1.cs create mode 100644 src/MicaApps.Mail/Extensions/Results.cs diff --git a/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj b/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj index c64e32c..e692959 100644 --- a/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj +++ b/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj @@ -312,6 +312,9 @@ 0.0.13 + + 4.1.0 + 0.1.10 diff --git a/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs b/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs new file mode 100644 index 0000000..213d1a8 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs @@ -0,0 +1,23 @@ +using MicaApps.Mail.Abstraction.Models; + +namespace MicaApps.Mail.Abstraction.MailService; + +public abstract class MailServiceBase +{ + public abstract string Id { get; } + public abstract string Name { get; } + + public abstract Task ConnectAsync(CancellationToken cancellationToken = default); + public abstract Task DisconnectAsync(CancellationToken cancellationToken = default); + + public abstract Task> GetMailFoldersAsync(CancellationToken cancellationToken = default); + + public abstract Task> GetMailsInFolderAsync( + MailFolder mailFolder, CancellationToken cancellationToken = default); + + public abstract Task + GetMailDetailAsync(string id, CancellationToken cancellationToken = default); + + + public abstract Task SendMailAsync(SingleMailMessage mailMessage, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs b/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs new file mode 100644 index 0000000..6bed7e0 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs @@ -0,0 +1,7 @@ +namespace MicaApps.Mail.Abstraction.Models; + +public class EmailAccount +{ + public string? Name { get; set; } + public string Email { get; set; } = null!; +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/MailFolder.cs b/src/MicaApps.Mail/Abstraction/Models/MailFolder.cs new file mode 100644 index 0000000..a4f5580 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/MailFolder.cs @@ -0,0 +1,7 @@ +namespace MicaApps.Mail.Abstraction.Models; + +public class MailFolder +{ + public string Id { get; set; } = null!; + public string? Name { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs b/src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs new file mode 100644 index 0000000..9a76a7c --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs @@ -0,0 +1,10 @@ +namespace MicaApps.Mail.Abstraction.Models; + +public class SingleMailMessage +{ + public string MailId { get; set; } = null!; + public string? Subject { get; set; } + public string Body { get; set; } = null!; + public EmailAccount Sender { get; set; } = null!; + public List Recipients { get; set; } = new(); +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Class1.cs b/src/MicaApps.Mail/Class1.cs deleted file mode 100644 index 16d774d..0000000 --- a/src/MicaApps.Mail/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace MicaApps.Mail; -public class Class1 -{ - -} diff --git a/src/MicaApps.Mail/Extensions/Results.cs b/src/MicaApps.Mail/Extensions/Results.cs new file mode 100644 index 0000000..234dad0 --- /dev/null +++ b/src/MicaApps.Mail/Extensions/Results.cs @@ -0,0 +1,48 @@ +namespace MicaApps.Mail.Extensions; + +public struct Results +{ + private readonly TValue? _value; + private readonly TError? _error; + + public bool IsError => !IsSuccess; + public bool IsSuccess { get; set; } + + public TValue? Value => _value; + public TError? Error => _error; + + private Results(TValue value) + { + IsSuccess = true; + _value = value; + } + + private Results(TError error) + { + IsSuccess = false; + _error = error; + } + + public static implicit operator Results(TValue value) => new(value); + public static implicit operator Results(TError error) => new(error); + + public static Results CreateError(TError error) => new(error); + public static Results CreateSuccess(TValue value) => new(value); + + /* + // 若要使用此方法, 请先去除 _value, _error 的 readonly 访问符 + public Results WithValue(TValue value) + { + _value = value; + return this; + } + + public Results WithError(TError error) + { + _error = error; + return this; + } + */ + public TResult Match(Func success, Func error) + => IsSuccess ? success(_value!) : error(_error!); +} \ No newline at end of file diff --git a/src/MicaApps.Mail/MicaApps.Mail.csproj b/src/MicaApps.Mail/MicaApps.Mail.csproj index 330f350..d07f7ae 100644 --- a/src/MicaApps.Mail/MicaApps.Mail.csproj +++ b/src/MicaApps.Mail/MicaApps.Mail.csproj @@ -7,4 +7,7 @@ enable + + + From 539d8689a1d65bfe824789ed7b1d9d05c65449cd Mon Sep 17 00:00:00 2001 From: kengwang Date: Tue, 29 Aug 2023 11:44:22 +0800 Subject: [PATCH 02/11] =?UTF-8?q?[feat]=20=E5=AE=8C=E5=96=84=20ProtocolMai?= =?UTF-8?q?lService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MicaApps.Mail.UWP.csproj | 3 - .../MailService/MailServiceBase.cs | 12 +- .../Abstraction/Models/EmailAccount.cs | 4 +- .../Models/Messages/ImapMailMessage.cs | 10 + .../Models/Messages/Interfaces/IHasBcc.cs | 6 + .../Models/Messages/Interfaces/IHasCc.cs | 6 + .../Messages/Interfaces/IHasHtmlBody.cs | 6 + .../MailMessage.cs} | 5 +- ...Runtime.CompilerServices.IsExternalInit.cs | 19 ++ .../MailServices/ProtocolMailService.cs | 178 ++++++++++++++++++ src/MicaApps.Mail/MicaApps.Mail.csproj | 8 + 11 files changed, 243 insertions(+), 14 deletions(-) create mode 100644 src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasBcc.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasCc.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasHtmlBody.cs rename src/MicaApps.Mail/Abstraction/Models/{SingleMailMessage.cs => Messages/MailMessage.cs} (66%) create mode 100644 src/MicaApps.Mail/Extensions/Roslyn/System.Runtime.CompilerServices.IsExternalInit.cs create mode 100644 src/MicaApps.Mail/MailServices/ProtocolMailService.cs diff --git a/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj b/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj index e692959..c64e32c 100644 --- a/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj +++ b/src/MicaApps.Mail.UWP/MicaApps.Mail.UWP.csproj @@ -312,9 +312,6 @@ 0.0.13 - - 4.1.0 - 0.1.10 diff --git a/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs b/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs index 213d1a8..c560f00 100644 --- a/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs +++ b/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs @@ -1,4 +1,5 @@ using MicaApps.Mail.Abstraction.Models; +using MicaApps.Mail.Abstraction.Models.Messages; namespace MicaApps.Mail.Abstraction.MailService; @@ -7,17 +8,16 @@ public abstract class MailServiceBase public abstract string Id { get; } public abstract string Name { get; } - public abstract Task ConnectAsync(CancellationToken cancellationToken = default); - public abstract Task DisconnectAsync(CancellationToken cancellationToken = default); + public abstract Task ConnectAsync(CancellationToken cancellationToken = default); + public abstract Task DisconnectAsync(CancellationToken cancellationToken = default); public abstract Task> GetMailFoldersAsync(CancellationToken cancellationToken = default); - public abstract Task> GetMailsInFolderAsync( + public abstract Task> GetMailsInFolderAsync( MailFolder mailFolder, CancellationToken cancellationToken = default); - public abstract Task - GetMailDetailAsync(string id, CancellationToken cancellationToken = default); + public abstract Task GetMailDetailAsync(string id, CancellationToken cancellationToken = default); - public abstract Task SendMailAsync(SingleMailMessage mailMessage, CancellationToken cancellationToken = default); + public abstract Task SendMailAsync(MailMessage sendingMailMessage, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs b/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs index 6bed7e0..43edcdb 100644 --- a/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs +++ b/src/MicaApps.Mail/Abstraction/Models/EmailAccount.cs @@ -1,7 +1,5 @@ namespace MicaApps.Mail.Abstraction.Models; -public class EmailAccount +public record EmailAccount(string Email, string? Name) { - public string? Name { get; set; } - public string Email { get; set; } = null!; } \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs new file mode 100644 index 0000000..b796806 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs @@ -0,0 +1,10 @@ +using MicaApps.Mail.Abstraction.Models.Messages.Interfaces; + +namespace MicaApps.Mail.Abstraction.Models.Messages; + +public class ImapMailMessage : MailMessage, IHasBcc, IHasCc, IHasHtmlBody +{ + public List Bcc { get; set; } = new(); + public List Cc { get; set; } = new(); + public string? HtmlBody { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasBcc.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasBcc.cs new file mode 100644 index 0000000..bd9217b --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasBcc.cs @@ -0,0 +1,6 @@ +namespace MicaApps.Mail.Abstraction.Models.Messages.Interfaces; + +public interface IHasBcc +{ + public List Bcc { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasCc.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasCc.cs new file mode 100644 index 0000000..b45b07c --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasCc.cs @@ -0,0 +1,6 @@ +namespace MicaApps.Mail.Abstraction.Models.Messages.Interfaces; + +public interface IHasCc +{ + public List Cc { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasHtmlBody.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasHtmlBody.cs new file mode 100644 index 0000000..c26dfc0 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasHtmlBody.cs @@ -0,0 +1,6 @@ +namespace MicaApps.Mail.Abstraction.Models.Messages.Interfaces; + +public interface IHasHtmlBody +{ + public string? HtmlBody { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/MailMessage.cs similarity index 66% rename from src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs rename to src/MicaApps.Mail/Abstraction/Models/Messages/MailMessage.cs index 9a76a7c..ffe0185 100644 --- a/src/MicaApps.Mail/Abstraction/Models/SingleMailMessage.cs +++ b/src/MicaApps.Mail/Abstraction/Models/Messages/MailMessage.cs @@ -1,10 +1,11 @@ -namespace MicaApps.Mail.Abstraction.Models; +namespace MicaApps.Mail.Abstraction.Models.Messages; -public class SingleMailMessage +public class MailMessage { public string MailId { get; set; } = null!; public string? Subject { get; set; } public string Body { get; set; } = null!; + public DateTimeOffset SendDate { get; set; } public EmailAccount Sender { get; set; } = null!; public List Recipients { get; set; } = new(); } \ No newline at end of file diff --git a/src/MicaApps.Mail/Extensions/Roslyn/System.Runtime.CompilerServices.IsExternalInit.cs b/src/MicaApps.Mail/Extensions/Roslyn/System.Runtime.CompilerServices.IsExternalInit.cs new file mode 100644 index 0000000..8a7c8f6 --- /dev/null +++ b/src/MicaApps.Mail/Extensions/Roslyn/System.Runtime.CompilerServices.IsExternalInit.cs @@ -0,0 +1,19 @@ +// +#pragma warning disable +#nullable enable annotations + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal static class IsExternalInit + { + } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs new file mode 100644 index 0000000..4ed2ab3 --- /dev/null +++ b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs @@ -0,0 +1,178 @@ +using System.Net; +using MailKit; +using MailKit.Net.Imap; +using MailKit.Net.Smtp; +using MicaApps.Mail.Abstraction.MailService; +using MicaApps.Mail.Abstraction.Models; +using MicaApps.Mail.Abstraction.Models.Messages; +using MicaApps.Mail.Abstraction.Models.Messages.Interfaces; +using MimeKit; +using MailFolder = MicaApps.Mail.Abstraction.Models.MailFolder; + +namespace MicaApps.Mail.MailServices; + +public class ProtocolMailService : MailServiceBase, IDisposable +{ + public override string Id => "protocol"; + public override string Name => "SMTP/IMAP 邮件服务"; + + private readonly SmtpClient _smtpClient = new SmtpClient(); + private readonly ImapClient _imapClient = new ImapClient(); + + public NetworkCredential? Credential { get; set; } + public string Host { get; } = null!; + public int Port { get; } = 0; + public bool UseSsl { get; } = false; + + public override async Task ConnectAsync(CancellationToken cancellationToken = default) + { + await _smtpClient.ConnectAsync(Host, Port, UseSsl, cancellationToken: cancellationToken); + await _smtpClient.AuthenticateAsync(Credential, cancellationToken); + + await _imapClient.ConnectAsync(Host, Port, UseSsl, cancellationToken: cancellationToken); + await _imapClient.AuthenticateAsync(Credential, cancellationToken); + } + + public override async Task DisconnectAsync(CancellationToken cancellationToken = default) + { + await _smtpClient.DisconnectAsync(true, cancellationToken); + await _smtpClient.DisconnectAsync(true, cancellationToken); + } + + public override Task> GetMailFoldersAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(new List() + { + new() + { + Id = _imapClient.Inbox.Id, + Name = _imapClient.Inbox.Name + } + }); + } + + public override async Task> GetMailsInFolderAsync( + MailFolder mailFolder, CancellationToken cancellationToken = default) + { + if (!_imapClient.Inbox.IsOpen) + await _imapClient.Inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken); + var results = await _imapClient.Inbox.FetchAsync( + 0, 50, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, + cancellationToken: cancellationToken); + var ret = new List(); + foreach (var messageSummary in results) + { + var message = new ImapMailMessage + { + MailId = messageSummary.UniqueId.Id.ToString(), + Subject = messageSummary.Envelope.Subject, + Body = messageSummary.PreviewText, + SendDate = messageSummary.Date + }; + if (messageSummary.Envelope.Sender.FirstOrDefault() is MailboxAddress senderAddr) + { + message.Sender = new EmailAccount(senderAddr.Address, senderAddr.Name); + } + + foreach (var recipient in (messageSummary.Envelope.To.OfType())) + { + message.Recipients.Add(new EmailAccount(recipient.Address, recipient.Name)); + } + + foreach (var cc in (messageSummary.Envelope.Cc.OfType())) + { + message.Recipients.Add(new EmailAccount(cc.Address, cc.Name)); + } + + foreach (var bcc in (messageSummary.Envelope.Bcc.OfType())) + { + message.Recipients.Add(new EmailAccount(bcc.Address, bcc.Name)); + } + + ret.Add(message); + } + + return ret; + } + + public override async Task GetMailDetailAsync( + string id, CancellationToken cancellationToken = default) + { + if (!uint.TryParse(id, out var uniqueId)) + return null; + if (!_imapClient.Inbox.IsOpen) + await _imapClient.Inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken); + var messageSummary = await _imapClient.Inbox.GetMessageAsync(new UniqueId(uniqueId), cancellationToken); + + var message = new ImapMailMessage + { + MailId = id, + Subject = messageSummary.Subject, + Body = messageSummary.TextBody, + HtmlBody = messageSummary.HtmlBody, + SendDate = messageSummary.Date + }; + if (messageSummary.Sender is { } senderAddr) + { + message.Sender = new EmailAccount(senderAddr.Address, senderAddr.Name); + } + + foreach (var recipient in (messageSummary.To.OfType())) + { + message.Recipients.Add(new EmailAccount(recipient.Address, recipient.Name)); + } + + foreach (var cc in (messageSummary.Cc.OfType())) + { + message.Recipients.Add(new EmailAccount(cc.Address, cc.Name)); + } + + foreach (var bcc in (messageSummary.Bcc.OfType())) + { + message.Recipients.Add(new EmailAccount(bcc.Address, bcc.Name)); + } + + return message; + } + + public override async Task SendMailAsync(MailMessage sendingMailMessage, + CancellationToken cancellationToken = default) + { + var message = new MimeMessage(); + message.From.Add(new MailboxAddress(sendingMailMessage.Sender.Name, message.Sender.Address)); + foreach (var (email, name) in sendingMailMessage.Recipients) + { + message.To.Add(new MailboxAddress(name, email)); + } + + message.Subject = sendingMailMessage.Subject; + message.Body = new TextPart("plain") + { + Text = sendingMailMessage.Body + }; + + if (sendingMailMessage is IHasBcc hasBcc && hasBcc.Bcc is { Count: > 0 }) + { + foreach (var (email, name) in hasBcc.Bcc) + { + message.Bcc.Add(new MailboxAddress(name, email)); + } + } + + if (sendingMailMessage is IHasCc hasCc && hasCc.Cc is { Count: > 0 }) + { + foreach (var (email, name) in hasCc.Cc) + { + message.Cc.Add(new MailboxAddress(name, email)); + } + } + + await _smtpClient.SendAsync(message, cancellationToken); + } + + public void Dispose() + { + _smtpClient.Dispose(); + _imapClient.Dispose(); + } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/MicaApps.Mail.csproj b/src/MicaApps.Mail/MicaApps.Mail.csproj index d07f7ae..e356c90 100644 --- a/src/MicaApps.Mail/MicaApps.Mail.csproj +++ b/src/MicaApps.Mail/MicaApps.Mail.csproj @@ -5,8 +5,16 @@ latest enable enable + + + + 4.1.0 + + + + From 05635560dcf89347cbc06085c9a1c228b45271bc Mon Sep 17 00:00:00 2001 From: kengwang Date: Tue, 29 Aug 2023 11:59:52 +0800 Subject: [PATCH 03/11] =?UTF-8?q?[feat]=20=E6=B7=BB=E5=8A=A0=20Unit=20Test?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mail.sln | 25 +++++++++++++++++++ .../ProtocolMailServiceTests.cs | 24 ++++++++++++++++++ MicaApps.Mail.Tests/Secrets.cs | 8 ++++++ .../MailService/MailServiceBase.cs | 4 +-- .../MailServices/ProtocolMailService.cs | 11 ++++---- 5 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 MicaApps.Mail.Tests/ProtocolMailServiceTests.cs create mode 100644 MicaApps.Mail.Tests/Secrets.cs diff --git a/Mail.sln b/Mail.sln index a2befa2..3882f36 100644 --- a/Mail.sln +++ b/Mail.sln @@ -24,6 +24,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicaApps.Mail.UWP", "src\Mi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicaApps.Mail", "src\MicaApps.Mail\MicaApps.Mail.csproj", "{A910A7C5-9F5C-457C-88B6-4CED1725398A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FDF4ACB0-3370-41FC-B005-221E43AC34D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicaApps.Mail.Tests", "MicaApps.Mail.Tests\MicaApps.Mail.Tests.csproj", "{C1BE9EEF-C620-455E-A331-E448EB4A4615}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -88,12 +92,33 @@ Global {A910A7C5-9F5C-457C-88B6-4CED1725398A}.Release|x64.Build.0 = Release|Any CPU {A910A7C5-9F5C-457C-88B6-4CED1725398A}.Release|x86.ActiveCfg = Release|Any CPU {A910A7C5-9F5C-457C-88B6-4CED1725398A}.Release|x86.Build.0 = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|ARM.Build.0 = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|ARM64.Build.0 = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|x64.ActiveCfg = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|x64.Build.0 = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Debug|x86.Build.0 = Debug|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|Any CPU.Build.0 = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|ARM.ActiveCfg = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|ARM.Build.0 = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|ARM64.ActiveCfg = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|ARM64.Build.0 = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|x64.ActiveCfg = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|x64.Build.0 = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|x86.ActiveCfg = Release|Any CPU + {C1BE9EEF-C620-455E-A331-E448EB4A4615}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {D232482A-0A17-4F0E-A242-F97C4D503937} = {9AF8B660-344F-4CDC-8EF1-CAEBE2AF4081} + {C1BE9EEF-C620-455E-A331-E448EB4A4615} = {FDF4ACB0-3370-41FC-B005-221E43AC34D0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93DE7270-2FAA-4FBE-9E2E-418D69D44180} diff --git a/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs b/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs new file mode 100644 index 0000000..6a07f84 --- /dev/null +++ b/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs @@ -0,0 +1,24 @@ +using MicaApps.Mail.MailServices; +using NSubstitute; + +namespace MicaApps.Mail.Tests; + + +public class ProtocolMailServiceTests +{ + + private readonly ProtocolMailService _mailService; + + public ProtocolMailServiceTests(ProtocolMailService mailService) + { + _mailService = new ProtocolMailService(); + _mailService.Name = "测试服务"; + _mailService.Host = ""; + } + + [Fact] + public async void FetchMessages() + { + + } +} \ No newline at end of file diff --git a/MicaApps.Mail.Tests/Secrets.cs b/MicaApps.Mail.Tests/Secrets.cs new file mode 100644 index 0000000..9995807 --- /dev/null +++ b/MicaApps.Mail.Tests/Secrets.cs @@ -0,0 +1,8 @@ +namespace MicaApps.Mail.Tests; + +// THIS FILE SHOULD BE FILLED BY GITHUB ACTION +// **DO NOT** COMMIT YOUR SECRETS TO ORIGIN +public class Secrets +{ + +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs b/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs index c560f00..976ff92 100644 --- a/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs +++ b/src/MicaApps.Mail/Abstraction/MailService/MailServiceBase.cs @@ -5,8 +5,8 @@ namespace MicaApps.Mail.Abstraction.MailService; public abstract class MailServiceBase { - public abstract string Id { get; } - public abstract string Name { get; } + public abstract string Id { get; set; } + public abstract string Name { get; set; } public abstract Task ConnectAsync(CancellationToken cancellationToken = default); public abstract Task DisconnectAsync(CancellationToken cancellationToken = default); diff --git a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs index 4ed2ab3..9b638a9 100644 --- a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs +++ b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs @@ -13,16 +13,17 @@ namespace MicaApps.Mail.MailServices; public class ProtocolMailService : MailServiceBase, IDisposable { - public override string Id => "protocol"; - public override string Name => "SMTP/IMAP 邮件服务"; + public override string Id { get; set; } = "protocol"; + public override string Name { get; set; } = "SMTP/IMAP 邮件服务"; private readonly SmtpClient _smtpClient = new SmtpClient(); private readonly ImapClient _imapClient = new ImapClient(); public NetworkCredential? Credential { get; set; } - public string Host { get; } = null!; - public int Port { get; } = 0; - public bool UseSsl { get; } = false; + public string Host { get; set; } = null!; + public int Port { get; set; } = 0; + public bool UseSsl { get; set; } = false; + public override async Task ConnectAsync(CancellationToken cancellationToken = default) { From 29d68cac3a372ae05109e6e36a5695a4fc903329 Mon Sep 17 00:00:00 2001 From: kengwang Date: Tue, 29 Aug 2023 12:30:34 +0800 Subject: [PATCH 04/11] [feat] Add Secrets --- MicaApps.Mail.Tests/Secrets.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/MicaApps.Mail.Tests/Secrets.cs b/MicaApps.Mail.Tests/Secrets.cs index 9995807..096382b 100644 --- a/MicaApps.Mail.Tests/Secrets.cs +++ b/MicaApps.Mail.Tests/Secrets.cs @@ -2,7 +2,24 @@ // THIS FILE SHOULD BE FILLED BY GITHUB ACTION // **DO NOT** COMMIT YOUR SECRETS TO ORIGIN -public class Secrets +public static class Secrets { - + public static ProtocolMailSettings ProtocolMailSettings = + new() + { + Host = null, + Port = 0, + UseSsl = false, + Username = false, + Password = false + }; +} + +public class ProtocolMailSettings +{ + public string Host; + public int Port; + public bool UseSsl; + public bool Username; + public bool Password; } \ No newline at end of file From 5e0ce6a5c8a604046b806ba8c000bd6557acbb25 Mon Sep 17 00:00:00 2001 From: kengwang Date: Tue, 29 Aug 2023 12:39:21 +0800 Subject: [PATCH 05/11] [feat] --- .../ProtocolMailServiceTests.cs | 33 +++++++++++++++++-- MicaApps.Mail.Tests/Secrets.cs | 8 ++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs b/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs index 6a07f84..9f53c06 100644 --- a/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs +++ b/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs @@ -1,24 +1,51 @@ -using MicaApps.Mail.MailServices; +using System.Net; +using FluentAssertions; +using MicaApps.Mail.MailServices; using NSubstitute; namespace MicaApps.Mail.Tests; -public class ProtocolMailServiceTests +public class ProtocolMailServiceTests : IAsyncLifetime { private readonly ProtocolMailService _mailService; + + public ProtocolMailServiceTests(ProtocolMailService mailService) { _mailService = new ProtocolMailService(); _mailService.Name = "测试服务"; - _mailService.Host = ""; + _mailService.Host = Secrets.ProtocolMailSettings.Host; + _mailService.UseSsl = Secrets.ProtocolMailSettings.UseSsl; + _mailService.Port = Secrets.ProtocolMailSettings.Port; + _mailService.Credential = + new NetworkCredential(Secrets.ProtocolMailSettings.Username, Secrets.ProtocolMailSettings.Password); + } [Fact] public async void FetchMessages() { + // Arrange + var folders = await _mailService.GetMailFoldersAsync(); + // Action + var mails = await _mailService.GetMailsInFolderAsync(folders[0]); + + // Assert + mails.Should().NotBeEmpty(); + } + + public async Task InitializeAsync() + { + await _mailService.ConnectAsync(); + } + + public async Task DisposeAsync() + { + await _mailService.DisconnectAsync(); + _mailService.Dispose(); } } \ No newline at end of file diff --git a/MicaApps.Mail.Tests/Secrets.cs b/MicaApps.Mail.Tests/Secrets.cs index 096382b..380353f 100644 --- a/MicaApps.Mail.Tests/Secrets.cs +++ b/MicaApps.Mail.Tests/Secrets.cs @@ -10,8 +10,8 @@ public static class Secrets Host = null, Port = 0, UseSsl = false, - Username = false, - Password = false + Username = "", + Password = "" }; } @@ -20,6 +20,6 @@ public class ProtocolMailSettings public string Host; public int Port; public bool UseSsl; - public bool Username; - public bool Password; + public string Username; + public string Password; } \ No newline at end of file From ae7daa8816b07646f909cd3093d3b676aadc2972 Mon Sep 17 00:00:00 2001 From: kengwang Date: Tue, 29 Aug 2023 13:34:52 +0800 Subject: [PATCH 06/11] =?UTF-8?q?[fix]=20=E4=BF=AE=E5=A4=8D=20ProtocolMail?= =?UTF-8?q?Service=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProtocolMailServiceTests.cs | 10 ++----- MicaApps.Mail.Tests/Secrets.cs | 29 ++++++++++--------- .../Models/ProtocolMailSettings.cs | 10 +++++++ .../MailServices/ProtocolMailService.cs | 16 +++++----- 4 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs diff --git a/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs b/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs index 9f53c06..4a00efb 100644 --- a/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs +++ b/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs @@ -13,16 +13,12 @@ public class ProtocolMailServiceTests : IAsyncLifetime - public ProtocolMailServiceTests(ProtocolMailService mailService) + public ProtocolMailServiceTests() { _mailService = new ProtocolMailService(); _mailService.Name = "测试服务"; - _mailService.Host = Secrets.ProtocolMailSettings.Host; - _mailService.UseSsl = Secrets.ProtocolMailSettings.UseSsl; - _mailService.Port = Secrets.ProtocolMailSettings.Port; - _mailService.Credential = - new NetworkCredential(Secrets.ProtocolMailSettings.Username, Secrets.ProtocolMailSettings.Password); - + _mailService.SmtpSettings = Secrets.SmtpSettings; + _mailService.ImapSettings = Secrets.ImapSettings; } [Fact] diff --git a/MicaApps.Mail.Tests/Secrets.cs b/MicaApps.Mail.Tests/Secrets.cs index 380353f..78a7aa5 100644 --- a/MicaApps.Mail.Tests/Secrets.cs +++ b/MicaApps.Mail.Tests/Secrets.cs @@ -1,25 +1,28 @@ -namespace MicaApps.Mail.Tests; +using MicaApps.Mail.Abstraction.Models; + +namespace MicaApps.Mail.Tests; // THIS FILE SHOULD BE FILLED BY GITHUB ACTION // **DO NOT** COMMIT YOUR SECRETS TO ORIGIN public static class Secrets { - public static ProtocolMailSettings ProtocolMailSettings = + // TODO: Please fill this secrets file + public static ProtocolMailSettings SmtpSettings = new() { - Host = null, + Host = "", Port = 0, - UseSsl = false, + UseSsl = true, + Username = "", + Password = "" + }; + public static ProtocolMailSettings ImapSettings = + new() + { + Host = "", + Port = 0, + UseSsl = true, Username = "", Password = "" }; } - -public class ProtocolMailSettings -{ - public string Host; - public int Port; - public bool UseSsl; - public string Username; - public string Password; -} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs b/src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs new file mode 100644 index 0000000..3c93d4d --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs @@ -0,0 +1,10 @@ +namespace MicaApps.Mail.Abstraction.Models; + +public class ProtocolMailSettings +{ + public string Host { get; set; } + public int Port { get; set; } + public bool UseSsl { get; set; } + public string Username { get; set; } + public string Password { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs index 9b638a9..c3ebd5d 100644 --- a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs +++ b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs @@ -19,19 +19,19 @@ public class ProtocolMailService : MailServiceBase, IDisposable private readonly SmtpClient _smtpClient = new SmtpClient(); private readonly ImapClient _imapClient = new ImapClient(); - public NetworkCredential? Credential { get; set; } - public string Host { get; set; } = null!; - public int Port { get; set; } = 0; - public bool UseSsl { get; set; } = false; + public ProtocolMailSettings SmtpSettings = new ProtocolMailSettings(); + public ProtocolMailSettings ImapSettings = new ProtocolMailSettings(); public override async Task ConnectAsync(CancellationToken cancellationToken = default) { - await _smtpClient.ConnectAsync(Host, Port, UseSsl, cancellationToken: cancellationToken); - await _smtpClient.AuthenticateAsync(Credential, cancellationToken); + await _smtpClient.ConnectAsync(SmtpSettings.Host, SmtpSettings.Port, SmtpSettings.UseSsl, cancellationToken: cancellationToken); + await _smtpClient.AuthenticateAsync(SmtpSettings.Username, SmtpSettings.Password, + cancellationToken: cancellationToken); - await _imapClient.ConnectAsync(Host, Port, UseSsl, cancellationToken: cancellationToken); - await _imapClient.AuthenticateAsync(Credential, cancellationToken); + await _imapClient.ConnectAsync(ImapSettings.Host, ImapSettings.Port, ImapSettings.UseSsl, cancellationToken: cancellationToken); + await _imapClient.AuthenticateAsync(ImapSettings.Username, ImapSettings.Password, + cancellationToken: cancellationToken); } public override async Task DisconnectAsync(CancellationToken cancellationToken = default) From fe7380af334914ead08b21cc03a2ee4c6b60eef7 Mon Sep 17 00:00:00 2001 From: Kengwang Date: Tue, 29 Aug 2023 13:41:48 +0800 Subject: [PATCH 07/11] =?UTF-8?q?[chore]=20=E4=B8=8A=E4=BC=A0=20Unit=20Tes?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +++- .../MicaApps.Mail.Tests.csproj | 30 +++++++++++++++++++ MicaApps.Mail.Tests/Usings.cs | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj create mode 100644 MicaApps.Mail.Tests/Usings.cs diff --git a/.gitignore b/.gitignore index 376092e..08865d7 100644 --- a/.gitignore +++ b/.gitignore @@ -361,4 +361,7 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +# Secrets +MicaApps.Mail.Tests/Secrets.cs diff --git a/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj b/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj new file mode 100644 index 0000000..f224814 --- /dev/null +++ b/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj @@ -0,0 +1,30 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/MicaApps.Mail.Tests/Usings.cs b/MicaApps.Mail.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/MicaApps.Mail.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file From 2c9016a1779e4d4310e5d484431b629d884ac4f2 Mon Sep 17 00:00:00 2001 From: Kengwang Date: Tue, 29 Aug 2023 13:44:45 +0800 Subject: [PATCH 08/11] =?UTF-8?q?[refact]=20=E6=9B=B4=E6=94=B9=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mail.sln | 2 +- .../MicaApps.Mail.Tests}/MicaApps.Mail.Tests.csproj | 1 + .../MicaApps.Mail.Tests}/ProtocolMailServiceTests.cs | 0 {MicaApps.Mail.Tests => tests/MicaApps.Mail.Tests}/Secrets.cs | 2 +- {MicaApps.Mail.Tests => tests/MicaApps.Mail.Tests}/Usings.cs | 0 5 files changed, 3 insertions(+), 2 deletions(-) rename {MicaApps.Mail.Tests => tests/MicaApps.Mail.Tests}/MicaApps.Mail.Tests.csproj (93%) rename {MicaApps.Mail.Tests => tests/MicaApps.Mail.Tests}/ProtocolMailServiceTests.cs (100%) rename {MicaApps.Mail.Tests => tests/MicaApps.Mail.Tests}/Secrets.cs (99%) rename {MicaApps.Mail.Tests => tests/MicaApps.Mail.Tests}/Usings.cs (100%) diff --git a/Mail.sln b/Mail.sln index 3882f36..07605a5 100644 --- a/Mail.sln +++ b/Mail.sln @@ -26,7 +26,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicaApps.Mail", "src\MicaAp EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FDF4ACB0-3370-41FC-B005-221E43AC34D0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicaApps.Mail.Tests", "MicaApps.Mail.Tests\MicaApps.Mail.Tests.csproj", "{C1BE9EEF-C620-455E-A331-E448EB4A4615}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicaApps.Mail.Tests", "tests\MicaApps.Mail.Tests\MicaApps.Mail.Tests.csproj", "{C1BE9EEF-C620-455E-A331-E448EB4A4615}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj b/tests/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj similarity index 93% rename from MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj rename to tests/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj index f224814..5083153 100644 --- a/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj +++ b/tests/MicaApps.Mail.Tests/MicaApps.Mail.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs b/tests/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs similarity index 100% rename from MicaApps.Mail.Tests/ProtocolMailServiceTests.cs rename to tests/MicaApps.Mail.Tests/ProtocolMailServiceTests.cs diff --git a/MicaApps.Mail.Tests/Secrets.cs b/tests/MicaApps.Mail.Tests/Secrets.cs similarity index 99% rename from MicaApps.Mail.Tests/Secrets.cs rename to tests/MicaApps.Mail.Tests/Secrets.cs index 78a7aa5..8e11d80 100644 --- a/MicaApps.Mail.Tests/Secrets.cs +++ b/tests/MicaApps.Mail.Tests/Secrets.cs @@ -25,4 +25,4 @@ public static class Secrets Username = "", Password = "" }; -} +} \ No newline at end of file diff --git a/MicaApps.Mail.Tests/Usings.cs b/tests/MicaApps.Mail.Tests/Usings.cs similarity index 100% rename from MicaApps.Mail.Tests/Usings.cs rename to tests/MicaApps.Mail.Tests/Usings.cs From 896260e1b9d557e233ee71d62f7d18c223ca5b36 Mon Sep 17 00:00:00 2001 From: Kengwang Date: Tue, 29 Aug 2023 13:51:53 +0800 Subject: [PATCH 09/11] [feat] Add Progressive Fetchable MailService --- .../MailService/IProgressiveEmailFetchable.cs | 10 ++++++++++ src/MicaApps.Mail/MailServices/ProtocolMailService.cs | 11 ++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/MicaApps.Mail/Abstraction/MailService/IProgressiveEmailFetchable.cs diff --git a/src/MicaApps.Mail/Abstraction/MailService/IProgressiveEmailFetchable.cs b/src/MicaApps.Mail/Abstraction/MailService/IProgressiveEmailFetchable.cs new file mode 100644 index 0000000..b84c850 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/MailService/IProgressiveEmailFetchable.cs @@ -0,0 +1,10 @@ +using MicaApps.Mail.Abstraction.Models; +using MicaApps.Mail.Abstraction.Models.Messages; + +namespace MicaApps.Mail.Abstraction.MailService; + +public interface IProgressiveEmailFetchable +{ + public Task> GetMailsInFolderAsync(MailFolder mailFolder, int start, int count, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs index c3ebd5d..b9b0f84 100644 --- a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs +++ b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs @@ -11,7 +11,7 @@ namespace MicaApps.Mail.MailServices; -public class ProtocolMailService : MailServiceBase, IDisposable +public class ProtocolMailService : MailServiceBase, IProgressiveEmailFetchable , IDisposable { public override string Id { get; set; } = "protocol"; public override string Name { get; set; } = "SMTP/IMAP 邮件服务"; @@ -54,11 +54,16 @@ public override Task> GetMailFoldersAsync(CancellationToken can public override async Task> GetMailsInFolderAsync( MailFolder mailFolder, CancellationToken cancellationToken = default) + { + return await GetMailsInFolderAsync(mailFolder, 0, 50, cancellationToken); + } + + public async Task> GetMailsInFolderAsync(MailFolder mailFolder, int start, int count, CancellationToken cancellationToken = default) { if (!_imapClient.Inbox.IsOpen) await _imapClient.Inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken); var results = await _imapClient.Inbox.FetchAsync( - 0, 50, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, + start, start + count - 1, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, cancellationToken: cancellationToken); var ret = new List(); foreach (var messageSummary in results) @@ -95,7 +100,7 @@ public override async Task> GetMailsInFolderAsync( return ret; } - + public override async Task GetMailDetailAsync( string id, CancellationToken cancellationToken = default) { From cafe3ff01e790442ac59831fd8010c5335f63270 Mon Sep 17 00:00:00 2001 From: Kengwang Date: Tue, 29 Aug 2023 20:07:09 +0800 Subject: [PATCH 10/11] =?UTF-8?q?[feat]=20=E6=96=B0=E5=A2=9E=E9=99=84?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailService/IAttachmentGettable.cs | 9 ++ .../Abstraction/Models/MailAttachment.cs | 17 ++++ .../Models/MailAttachmentContent.cs | 22 +++++ .../Models/Messages/ImapMailMessage.cs | 10 --- .../Messages/Interfaces/IHasAttachments.cs | 6 ++ .../Models/ProtocolMailSettings.cs | 10 --- .../MailServices/ProtocolMailService.cs | 87 +++++++++++++++++-- 7 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 src/MicaApps.Mail/Abstraction/MailService/IAttachmentGettable.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/MailAttachment.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/MailAttachmentContent.cs delete mode 100644 src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs create mode 100644 src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasAttachments.cs delete mode 100644 src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs diff --git a/src/MicaApps.Mail/Abstraction/MailService/IAttachmentGettable.cs b/src/MicaApps.Mail/Abstraction/MailService/IAttachmentGettable.cs new file mode 100644 index 0000000..8076b94 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/MailService/IAttachmentGettable.cs @@ -0,0 +1,9 @@ +using MicaApps.Mail.Abstraction.Models; +using MicaApps.Mail.Abstraction.Models.Messages; + +namespace MicaApps.Mail.Abstraction.MailService; + +public interface IAttachmentGettable +{ + public Task GetAttachmentContentAsync(MailAttachment attachment); +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/MailAttachment.cs b/src/MicaApps.Mail/Abstraction/Models/MailAttachment.cs new file mode 100644 index 0000000..8dbd932 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/MailAttachment.cs @@ -0,0 +1,17 @@ +namespace MicaApps.Mail.Abstraction.Models; + +public class MailAttachment +{ + public string Id { get; } + public string? Name { get; } + public string? ContentType { get; } + public long Length { get; } + + public MailAttachment(string id,string? name,string? contentType,long length) + { + Id = id; + Name = name; + ContentType = contentType; + Length = length; + } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/MailAttachmentContent.cs b/src/MicaApps.Mail/Abstraction/Models/MailAttachmentContent.cs new file mode 100644 index 0000000..0f3d6d6 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/MailAttachmentContent.cs @@ -0,0 +1,22 @@ +namespace MicaApps.Mail.Abstraction.Models; + +public class MailAttachmentContent +{ + public MailAttachmentContent(string id, string? name, string? contentType, long length) + { + Id = id; + Name = name; + ContentType = contentType; + Length = length; + } + + public virtual Task WriteToStreamAsync(Stream stream, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public string Id { get; } + public string? Name { get; } + public string? ContentType { get; } + public long Length { get; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs deleted file mode 100644 index b796806..0000000 --- a/src/MicaApps.Mail/Abstraction/Models/Messages/ImapMailMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MicaApps.Mail.Abstraction.Models.Messages.Interfaces; - -namespace MicaApps.Mail.Abstraction.Models.Messages; - -public class ImapMailMessage : MailMessage, IHasBcc, IHasCc, IHasHtmlBody -{ - public List Bcc { get; set; } = new(); - public List Cc { get; set; } = new(); - public string? HtmlBody { get; set; } -} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasAttachments.cs b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasAttachments.cs new file mode 100644 index 0000000..c28e7e4 --- /dev/null +++ b/src/MicaApps.Mail/Abstraction/Models/Messages/Interfaces/IHasAttachments.cs @@ -0,0 +1,6 @@ +namespace MicaApps.Mail.Abstraction.Models.Messages.Interfaces; + +public interface IHasAttachments +{ + public List Attachments { get; set; } +} \ No newline at end of file diff --git a/src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs b/src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs deleted file mode 100644 index 3c93d4d..0000000 --- a/src/MicaApps.Mail/Abstraction/Models/ProtocolMailSettings.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MicaApps.Mail.Abstraction.Models; - -public class ProtocolMailSettings -{ - public string Host { get; set; } - public int Port { get; set; } - public bool UseSsl { get; set; } - public string Username { get; set; } - public string Password { get; set; } -} \ No newline at end of file diff --git a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs index b9b0f84..251ff80 100644 --- a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs +++ b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs @@ -2,6 +2,7 @@ using MailKit; using MailKit.Net.Imap; using MailKit.Net.Smtp; +using MailKit.Security; using MicaApps.Mail.Abstraction.MailService; using MicaApps.Mail.Abstraction.Models; using MicaApps.Mail.Abstraction.Models.Messages; @@ -11,7 +12,7 @@ namespace MicaApps.Mail.MailServices; -public class ProtocolMailService : MailServiceBase, IProgressiveEmailFetchable , IDisposable +public class ProtocolMailService : MailServiceBase, IProgressiveEmailFetchable, IAttachmentGettable, IDisposable { public override string Id { get; set; } = "protocol"; public override string Name { get; set; } = "SMTP/IMAP 邮件服务"; @@ -21,15 +22,17 @@ public class ProtocolMailService : MailServiceBase, IProgressiveEmailFetchable , public ProtocolMailSettings SmtpSettings = new ProtocolMailSettings(); public ProtocolMailSettings ImapSettings = new ProtocolMailSettings(); - + public override async Task ConnectAsync(CancellationToken cancellationToken = default) { - await _smtpClient.ConnectAsync(SmtpSettings.Host, SmtpSettings.Port, SmtpSettings.UseSsl, cancellationToken: cancellationToken); + await _smtpClient.ConnectAsync(SmtpSettings.Host, SmtpSettings.Port, SecureSocketOptions.StartTls, + cancellationToken: cancellationToken); await _smtpClient.AuthenticateAsync(SmtpSettings.Username, SmtpSettings.Password, cancellationToken: cancellationToken); - await _imapClient.ConnectAsync(ImapSettings.Host, ImapSettings.Port, ImapSettings.UseSsl, cancellationToken: cancellationToken); + await _imapClient.ConnectAsync(ImapSettings.Host, ImapSettings.Port, ImapSettings.UseSsl, + cancellationToken: cancellationToken); await _imapClient.AuthenticateAsync(ImapSettings.Username, ImapSettings.Password, cancellationToken: cancellationToken); } @@ -58,12 +61,13 @@ public override async Task> GetMailsInFolderAsync( return await GetMailsInFolderAsync(mailFolder, 0, 50, cancellationToken); } - public async Task> GetMailsInFolderAsync(MailFolder mailFolder, int start, int count, CancellationToken cancellationToken = default) + public async Task> GetMailsInFolderAsync(MailFolder mailFolder, int start, int count, + CancellationToken cancellationToken = default) { if (!_imapClient.Inbox.IsOpen) await _imapClient.Inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken); var results = await _imapClient.Inbox.FetchAsync( - start, start + count - 1, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, + start, -1, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, cancellationToken: cancellationToken); var ret = new List(); foreach (var messageSummary in results) @@ -95,12 +99,23 @@ public async Task> GetMailsInFolderAsync(MailFolder mailFolder message.Recipients.Add(new EmailAccount(bcc.Address, bcc.Name)); } + if (messageSummary.Attachments.Any()) + { + foreach (var messageSummaryAttachment in messageSummary.Attachments) + { + message.Attachments.Add(new ImapMailAttachment(messageSummaryAttachment.ContentId, + messageSummaryAttachment.FileName, + messageSummaryAttachment.ContentType.MimeType, + messageSummaryAttachment.Octets, messageSummaryAttachment, messageSummary.UniqueId)); + } + } + ret.Add(message); } return ret; } - + public override async Task GetMailDetailAsync( string id, CancellationToken cancellationToken = default) { @@ -137,7 +152,8 @@ public async Task> GetMailsInFolderAsync(MailFolder mailFolder { message.Recipients.Add(new EmailAccount(bcc.Address, bcc.Name)); } - + + return message; } @@ -181,4 +197,59 @@ public void Dispose() _smtpClient.Dispose(); _imapClient.Dispose(); } + + public async Task GetAttachmentContentAsync(MailAttachment attachment) + { + if (attachment is not ImapMailAttachment mailAttachment) return null; + var mime = (MimePart)await _imapClient.Inbox.GetBodyPartAsync(mailAttachment.MailId, mailAttachment.Body); + return new ImapMailAttachmentContent(mime.ContentId, mime.FileName, mime.ContentType.MimeType, + attachment.Length, mime); + + } +} + + +public class ProtocolMailSettings +{ + public string Host { get; set; } + public int Port { get; set; } + public bool UseSsl { get; set; } + public string Username { get; set; } + public string Password { get; set; } +} + +public class ImapMailMessage : MailMessage, IHasBcc, IHasCc, IHasHtmlBody, IHasAttachments +{ + public List Bcc { get; set; } = new(); + public List Cc { get; set; } = new(); + public string? HtmlBody { get; set; } + public List Attachments { get; set; } = new(); +} + +public class ImapMailAttachment : MailAttachment +{ + public BodyPartBasic Body { get; } + public UniqueId MailId { get; } + + public ImapMailAttachment(string id, string? name, string? contentType, long length, BodyPartBasic body, UniqueId mailId) : base(id, name, contentType, length) + { + Body = body; + MailId = mailId; + } +} + +public class ImapMailAttachmentContent : MailAttachmentContent +{ + + public MimePart MimePart { get; set; } + + public ImapMailAttachmentContent(string id, string? name, string? contentType, long length, MimePart mimePart) : base(id, name, contentType, length) + { + MimePart = mimePart; + } + + public override async Task WriteToStreamAsync(Stream stream, CancellationToken cancellationToken = default) + { + await MimePart.WriteToAsync(stream, cancellationToken); + } } \ No newline at end of file From 71ac009043edd4e2ec47f3a3a9ce683a6c8c48ef Mon Sep 17 00:00:00 2001 From: Kengwang Date: Tue, 29 Aug 2023 20:49:18 +0800 Subject: [PATCH 11/11] =?UTF-8?q?[fix]=20=E4=BF=AE=E5=A4=8D=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E5=80=92=E5=BA=8F,=20=E6=96=B0=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=86=E7=B1=BB=E5=9E=8B=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailServices/ProtocolMailService.cs | 63 ++++++++++++++----- tests/MicaApps.Mail.Tests/Secrets.cs | 6 +- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs index 251ff80..ed9146d 100644 --- a/src/MicaApps.Mail/MailServices/ProtocolMailService.cs +++ b/src/MicaApps.Mail/MailServices/ProtocolMailService.cs @@ -26,12 +26,12 @@ public class ProtocolMailService : MailServiceBase, IProgressiveEmailFetchable, public override async Task ConnectAsync(CancellationToken cancellationToken = default) { - await _smtpClient.ConnectAsync(SmtpSettings.Host, SmtpSettings.Port, SecureSocketOptions.StartTls, + await _smtpClient.ConnectAsync(SmtpSettings.Host, SmtpSettings.Port, (SecureSocketOptions)SmtpSettings.SecureType, cancellationToken: cancellationToken); await _smtpClient.AuthenticateAsync(SmtpSettings.Username, SmtpSettings.Password, cancellationToken: cancellationToken); - await _imapClient.ConnectAsync(ImapSettings.Host, ImapSettings.Port, ImapSettings.UseSsl, + await _imapClient.ConnectAsync(ImapSettings.Host, ImapSettings.Port, (SecureSocketOptions)ImapSettings.SecureType, cancellationToken: cancellationToken); await _imapClient.AuthenticateAsync(ImapSettings.Username, ImapSettings.Password, cancellationToken: cancellationToken); @@ -66,9 +66,9 @@ public async Task> GetMailsInFolderAsync(MailFolder mailFolder { if (!_imapClient.Inbox.IsOpen) await _imapClient.Inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken); - var results = await _imapClient.Inbox.FetchAsync( - start, -1, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, - cancellationToken: cancellationToken); + var results = (await _imapClient.Inbox.FetchAsync( + _imapClient.Inbox.Count - start - count, _imapClient.Inbox.Count - start, MessageSummaryItems.Envelope | MessageSummaryItems.PreviewText, + cancellationToken: cancellationToken)).Reverse(); var ret = new List(); foreach (var messageSummary in results) { @@ -106,7 +106,8 @@ public async Task> GetMailsInFolderAsync(MailFolder mailFolder message.Attachments.Add(new ImapMailAttachment(messageSummaryAttachment.ContentId, messageSummaryAttachment.FileName, messageSummaryAttachment.ContentType.MimeType, - messageSummaryAttachment.Octets, messageSummaryAttachment, messageSummary.UniqueId)); + messageSummaryAttachment.Octets, + messageSummaryAttachment, messageSummary.UniqueId)); } } @@ -152,8 +153,8 @@ public async Task> GetMailsInFolderAsync(MailFolder mailFolder { message.Recipients.Add(new EmailAccount(bcc.Address, bcc.Name)); } - - + + return message; } @@ -204,20 +205,51 @@ public void Dispose() var mime = (MimePart)await _imapClient.Inbox.GetBodyPartAsync(mailAttachment.MailId, mailAttachment.Body); return new ImapMailAttachmentContent(mime.ContentId, mime.FileName, mime.ContentType.MimeType, attachment.Length, mime); - } } - public class ProtocolMailSettings { public string Host { get; set; } public int Port { get; set; } - public bool UseSsl { get; set; } + public SecureType SecureType { get; set; } public string Username { get; set; } public string Password { get; set; } } +public enum SecureType +{ + /// No SSL or TLS encryption should be used. + None, + + /// + /// Allow the to decide which SSL or TLS + /// options to use (default). If the server does not support SSL or TLS, + /// then the connection will continue without any encryption. + /// + Auto, + + /// + /// The connection should use SSL or TLS encryption immediately. + /// + SslOnConnect, + + /// + /// Elevates the connection to use TLS encryption immediately after + /// reading the greeting and capabilities of the server. If the server + /// does not support the STARTTLS extension, then the connection will + /// fail and a will be thrown. + /// + StartTls, + + /// + /// Elevates the connection to use TLS encryption immediately after + /// reading the greeting and capabilities of the server, but only if + /// the server supports the STARTTLS extension. + /// + StartTlsWhenAvailable, +} + public class ImapMailMessage : MailMessage, IHasBcc, IHasCc, IHasHtmlBody, IHasAttachments { public List Bcc { get; set; } = new(); @@ -231,7 +263,8 @@ public class ImapMailAttachment : MailAttachment public BodyPartBasic Body { get; } public UniqueId MailId { get; } - public ImapMailAttachment(string id, string? name, string? contentType, long length, BodyPartBasic body, UniqueId mailId) : base(id, name, contentType, length) + public ImapMailAttachment(string id, string? name, string? contentType, long length, BodyPartBasic body, + UniqueId mailId) : base(id, name, contentType, length) { Body = body; MailId = mailId; @@ -240,10 +273,10 @@ public ImapMailAttachment(string id, string? name, string? contentType, long len public class ImapMailAttachmentContent : MailAttachmentContent { - public MimePart MimePart { get; set; } - - public ImapMailAttachmentContent(string id, string? name, string? contentType, long length, MimePart mimePart) : base(id, name, contentType, length) + + public ImapMailAttachmentContent(string id, string? name, string? contentType, long length, MimePart mimePart) : + base(id, name, contentType, length) { MimePart = mimePart; } diff --git a/tests/MicaApps.Mail.Tests/Secrets.cs b/tests/MicaApps.Mail.Tests/Secrets.cs index 8e11d80..d8fd46a 100644 --- a/tests/MicaApps.Mail.Tests/Secrets.cs +++ b/tests/MicaApps.Mail.Tests/Secrets.cs @@ -1,4 +1,4 @@ -using MicaApps.Mail.Abstraction.Models; +using MicaApps.Mail.MailServices; namespace MicaApps.Mail.Tests; @@ -12,7 +12,7 @@ public static class Secrets { Host = "", Port = 0, - UseSsl = true, + SecureType = SecureType.Auto, Username = "", Password = "" }; @@ -21,7 +21,7 @@ public static class Secrets { Host = "", Port = 0, - UseSsl = true, + SecureType = SecureType.Auto, Username = "", Password = "" };