gpt4 book ai didi

c# - 如何使用MimeKit获取电子邮件的所见即所得主体

转载 作者:行者123 更新时间:2023-11-30 13:25:57 33 4
gpt4 key购买 nike

我使用的是名为EAgetmail的库来检索指定电子邮件的正文,并且运行良好,但是现在使用的是Mailkit。 EAgetmail的问题是message.body等同于用户在电子邮件客户端中看到的正文,但是在mailkit中它返回了许多不同的数据。

这是相关代码:

using (var client = new ImapClient())
{
client.Connect(emailServer, 993, true);
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(username, password);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
SearchQuery query;
if (checkBox.IsChecked == false)
{
query = SearchQuery.DeliveredBefore((DateTime)dateEnd).And(
SearchQuery.DeliveredAfter((DateTime)dateStart)).And(
SearchQuery.SubjectContains("Subject to find"));
}
else
{
query = SearchQuery.SubjectContains("Subject to find");
}
foreach (var uid in inbox.Search(query))
{
var message = inbox.GetMessage(uid);
formEmails.Add(message.TextBody);
messageDate.Add(message.Date.LocalDateTime);
}
client.Disconnect(true);
}


我还尝试了message.Body.ToString()并在消息部分中搜索纯文本,但均无济于事。
我的问题是如何使用Mailkit复制EAgetmail的.body属性的效果(仅返回纯文本内容,如用户所见)?

最佳答案

关于电子邮件的一个常见误解是,有一个定义明确的邮件正文,然后是附件列表。事实并非如此。现实情况是,MIME是内容的树形结构,非常类似于文件系统。

幸运的是,MIME确实为邮件客户端应如何解释MIME部分的树结构定义了一组通用规则。 Content-Disposition标头用于向接收客户端提供提示,以提示哪些部分应显示为消息正文的一部分,哪些部分应解释为附件。

Content-Disposition标头通常具有以下两个值之一:inlineattachment

这些值的含义应该很明显。如果该值为attachment,则所述MIME部分的内容将被表示为与核心消息分开的文件附件。但是,如果值为inline,则该MIME部分的内容应在邮件客户端的核心消息正文呈现中内联显示。如果Content-Disposition标头不存在,则应将其视为inline值。

从技术上讲,每个缺少Content-Disposition标头或标记为inline的部分都是核心消息主体的一部分。

但是,还有更多的东西。

现代MIME消息通常包含一个multipart/alternative MIME容器,该容器通常包含发件人编写的文本的text/plaintext/html版本。与text/html版本相比,text/plain版本的格式通常与发件人在其所见即所得编辑器中看到的格式更接近。

以两种格式发送消息文本的原因是,并非所有邮件客户端都能够显示HTML。

接收方客户端应仅显示multipart/alternative容器中包含的备用视图之一。由于按照发送人在其所见即所得编辑器中所看到的内容从最不忠实到最忠实的顺序列出了替代视图,因此接收方客户端应从末尾开始遍历替代视图列表,然后向后工作,直到找到一部分能够显示。

例:

multipart/alternative
text/plain
text/html


如上例所示, text/html部分列在最后,因为它最忠实于发件人在编写消息时在其所见即所得编辑器中看到的内容。

为了使事情变得更加复杂,有时现代邮件客户端将使用 multipart/related MIME容器而不是简单的 text/html部分,以便将图像和其他多媒体内容嵌入HTML。

例:

multipart/alternative
text/plain
multipart/related
text/html
image/jpeg
video/mp4
image/png


在上面的示例中,替代视图之一是 multipart/related容器,其中包含引用同级视频和图像的消息正文的HTML版本。

既然您对消息的结构以及如何解释各种MIME实体有了一个大概的了解,我们就可以开始弄清楚如何按照预期的方式实际呈现消息了。

使用MimeVisitor(呈现消息的最准确方法)

MimeKit包含一个 MimeVisitor类,用于访问MIME树结构中的每个节点。例如,以下 MimeVisitor子类可用于生成将由浏览器控件(例如 WebBrowser)呈现的HTML:

/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control.
/// </summary>
class HtmlPreviewVisitor : MimeVisitor
{
List<MultipartRelated> stack = new List<MultipartRelated> ();
List<MimeEntity> attachments = new List<MimeEntity> ();
readonly string tempDir;
string body;

/// <summary>
/// Creates a new HtmlPreviewVisitor.
/// </summary>
/// <param name="tempDirectory">A temporary directory used for storing image files.</param>
public HtmlPreviewVisitor (string tempDirectory)
{
tempDir = tempDirectory;
}

/// <summary>
/// The list of attachments that were in the MimeMessage.
/// </summary>
public IList<MimeEntity> Attachments {
get { return attachments; }
}

/// <summary>
/// The HTML string that can be set on the BrowserControl.
/// </summary>
public string HtmlBody {
get { return body ?? string.Empty; }
}

protected override void VisitMultipartAlternative (MultipartAlternative alternative)
{
// walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
for (int i = alternative.Count - 1; i >= 0 && body == null; i--)
alternative[i].Accept (this);
}

protected override void VisitMultipartRelated (MultipartRelated related)
{
var root = related.Root;

// push this multipart/related onto our stack
stack.Add (related);

// visit the root document
root.Accept (this);

// pop this multipart/related off our stack
stack.RemoveAt (stack.Count - 1);
}

// look up the image based on the img src url within our multipart/related stack
bool TryGetImage (string url, out MimePart image)
{
UriKind kind;
int index;
Uri uri;

if (Uri.IsWellFormedUriString (url, UriKind.Absolute))
kind = UriKind.Absolute;
else if (Uri.IsWellFormedUriString (url, UriKind.Relative))
kind = UriKind.Relative;
else
kind = UriKind.RelativeOrAbsolute;

try {
uri = new Uri (url, kind);
} catch {
image = null;
return false;
}

for (int i = stack.Count - 1; i >= 0; i--) {
if ((index = stack[i].IndexOf (uri)) == -1)
continue;

image = stack[i][index] as MimePart;
return image != null;
}

image = null;

return false;
}

// Save the image to our temp directory and return a "file://" url suitable for
// the browser control to load.
// Note: if you'd rather embed the image data into the HTML, you can construct a
// "data:" url instead.
string SaveImage (MimePart image, string url)
{
string fileName = url.Replace (':', '_').Replace ('\\', '_').Replace ('/', '_');

string path = Path.Combine (tempDir, fileName);

if (!File.Exists (path)) {
using (var output = File.Create (path))
image.ContentObject.DecodeTo (output);
}

return "file://" + path.Replace ('\\', '/');
}

// Replaces <img src=...> urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
{
if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
ctx.WriteTag (htmlWriter, false);

// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes) {
if (attribute.Id == HtmlAttributeId.Src) {
MimePart image;
string url;

if (!TryGetImage (attribute.Value, out image)) {
htmlWriter.WriteAttribute (attribute);
continue;
}

url = SaveImage (image, attribute.Value);

htmlWriter.WriteAttributeName (attribute.Name);
htmlWriter.WriteAttributeValue (url);
} else {
htmlWriter.WriteAttribute (attribute);
}
}
} else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag) {
ctx.WriteTag (htmlWriter, false);

// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes) {
if (attribute.Name.ToLowerInvariant () == "oncontextmenu")
continue;

htmlWriter.WriteAttribute (attribute);
}

htmlWriter.WriteAttribute ("oncontextmenu", "return false;");
} else {
// pass the tag through to the output
ctx.WriteTag (htmlWriter, true);
}
}

protected override void VisitTextPart (TextPart entity)
{
TextConverter converter;

if (body != null) {
// since we've already found the body, treat this as an attachment
attachments.Add (entity);
return;
}

if (entity.IsHtml) {
converter = new HtmlToHtml {
HtmlTagCallback = HtmlTagCallback
};
} else if (entity.IsFlowed) {
var flowed = new FlowedToHtml ();
string delsp;

if (entity.ContentType.Parameters.TryGetValue ("delsp", out delsp))
flowed.DeleteSpace = delsp.ToLowerInvariant () == "yes";

converter = flowed;
} else {
converter = new TextToHtml ();
}

body = converter.Convert (entity.Text);
}

protected override void VisitTnefPart (TnefPart entity)
{
// extract any attachments in the MS-TNEF part
attachments.AddRange (entity.ExtractAttachments ());
}

protected override void VisitMessagePart (MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add (entity);
}

protected override void VisitMimePart (MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add (entity);
}
}


您使用此访问者的方式可能如下所示:

void Render (MimeMessage message)
{
var tmpDir = Path.Combine (Path.GetTempPath (), message.MessageId);
var visitor = new HtmlPreviewVisitor (tmpDir);

Directory.CreateDirectory (tmpDir);

message.Accept (visitor);

DisplayHtml (visitor.HtmlBody);
DisplayAttachments (visitor.Attachments);
}


使用 TextBodyHtmlBody属性(最简单的方法)

为了简化获取消息文本的常见任务, MimeMessage包括两个属性,可以帮助您获取消息正文的 text/plaintext/html版本。它们分别是 TextBodyHtmlBody

但是请记住,至少使用 HtmlBody属性,可能是HTML部分是 multipart/related的子级,从而允许它引用图像和其他包含在该<< cc>实体。此属性实际上仅是一个便利属性,并不是您自己遍历MIME结构的真正替代,因此您可以正确解释相关内容。

关于c# - 如何使用MimeKit获取电子邮件的所见即所得主体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34980154/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com