Skip to content

Instantly share code, notes, and snippets.

@jstedfast
Last active February 26, 2016 18:19
Show Gist options
  • Save jstedfast/92c1a52e93efa4cc0f91 to your computer and use it in GitHub Desktop.
Save jstedfast/92c1a52e93efa4cc0f91 to your computer and use it in GitHub Desktop.
A sample program illustrating how to generate a reply to a message
//
// ReplyVisitor.cs
//
// Author: Jeffrey Stedfast <jeff@xamarin.com>
//
// Copyright (c) 2016 Xamarin Inc. (www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using MimeKit;
using MimeKit.Text;
namespace Examples
{
public class Program
{
public static void Main (string[] args)
{
var visitor = new ReplyVisitor (new MailboxAddress ("Example Name", "user@example.com"), false);
var message = MimeMessage.Load (args[0]);
visitor.Visit (message);
using (var stdout = Console.OpenStandardOutput ())
visitor.Reply.WriteTo (stdout);
}
}
public class ReplyVisitor : MimeVisitor
{
readonly Stack<Multipart> stack = new Stack<Multipart> ();
MimeMessage original, reply;
MailboxAddress from;
bool replyToAll;
/// <summary>
/// Creates a new ReplyVisitor.
/// </summary>
public ReplyVisitor (MailboxAddress from, bool replyToAll)
{
this.replyToAll = replyToAll;
this.from = from;
}
/// <summary>
/// Gets the reply.
/// </summary>
/// <value>The reply.</value>
public MimeMessage Reply {
get { return reply; }
}
void Push (MimeEntity entity)
{
var multipart = entity as Multipart;
if (reply.Body == null) {
reply.Body = entity;
} else {
var parent = stack.Peek ();
parent.Add (entity);
}
if (multipart != null)
stack.Push (multipart);
}
void Pop ()
{
stack.Pop ();
}
static string GetOnDateSenderWrote (MimeMessage message)
{
var sender = message.Sender != null ? message.Sender : message.From.Mailboxes.FirstOrDefault ();
var name = sender != null ? (!string.IsNullOrEmpty (sender.Name) ? sender.Name : sender.Address) : "an unknown sender";
return string.Format ("On {0}, {1} wrote:", message.Date.ToString ("f"), name);
}
/// <summary>
/// Visit the specified message.
/// </summary>
/// <param name="message">The message.</param>
public override void Visit (MimeMessage message)
{
reply = new MimeMessage ();
original = message;
stack.Clear ();
reply.From.Add (from.Clone ());
// reply to the sender of the message
if (message.ReplyTo.Count > 0) {
reply.To.AddRange (message.ReplyTo);
} else if (message.From.Count > 0) {
reply.To.AddRange (message.From);
} else if (message.Sender != null) {
reply.To.Add (message.Sender);
}
if (replyToAll) {
// include all of the other original recipients - TODO: remove ourselves from these lists
reply.To.AddRange (message.To);
reply.Cc.AddRange (message.Cc);
}
// set the reply subject
if (!message.Subject.StartsWith ("Re:", StringComparison.OrdinalIgnoreCase))
reply.Subject = "Re: " + message.Subject;
else
reply.Subject = message.Subject;
// construct the In-Reply-To and References headers
if (!string.IsNullOrEmpty (message.MessageId)) {
reply.InReplyTo = message.MessageId;
foreach (var id in message.References)
reply.References.Add (id);
reply.References.Add (message.MessageId);
}
base.Visit (message);
}
/// <summary>
/// Visit the specified entity.
/// </summary>
/// <param name="entity">The MIME entity.</param>
/// <exception cref="System.NotSupportedException">
/// Only Visit(MimeMessage) is supported.
/// </exception>
public override void Visit (MimeEntity entity)
{
throw new NotSupportedException ();
}
protected override void VisitMultipartAlternative (MultipartAlternative alternative)
{
var multipart = new MultipartAlternative ();
Push (multipart);
for (int i = 0; i < alternative.Count; i++)
alternative[i].Accept (this);
Pop ();
}
protected override void VisitMultipartRelated (MultipartRelated related)
{
var multipart = new MultipartRelated ();
var root = related.Root;
Push (multipart);
root.Accept (this);
for (int i = 0; i < related.Count; i++) {
if (related[i] != root)
related[i].Accept (this);
}
Pop ();
}
protected override void VisitMultipart (Multipart multipart)
{
foreach (var part in multipart) {
if (part is MultipartAlternative)
part.Accept (this);
else if (part is MultipartRelated)
part.Accept (this);
else if (part is TextPart)
part.Accept (this);
}
}
void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
{
if (ctx.TagId == HtmlTagId.Body && !ctx.IsEmptyElementTag) {
if (ctx.IsEndTag) {
// end our opening <blockquote>
htmlWriter.WriteEndTag (HtmlTagId.BlockQuote);
// pass the </body> tag through to the output
ctx.WriteTag (htmlWriter, true);
} else {
// pass the <body> tag through to the output
ctx.WriteTag (htmlWriter, true);
// prepend the HTML reply with "On {DATE}, {SENDER} wrote:"
htmlWriter.WriteStartTag (HtmlTagId.P);
htmlWriter.WriteText (GetOnDateSenderWrote (original));
htmlWriter.WriteEndTag (HtmlTagId.P);
// Wrap the original content in a <blockquote>
htmlWriter.WriteStartTag (HtmlTagId.BlockQuote);
htmlWriter.WriteAttribute (HtmlAttributeId.Style, "border-left: 1px #ccc solid; margin: 0 0 0 .8ex; padding-left: 1ex;");
ctx.InvokeCallbackForEndTag = true;
}
} else {
// pass the tag through to the output
ctx.WriteTag (htmlWriter, true);
}
}
string QuoteText (string text)
{
using (var quoted = new StringWriter ()) {
quoted.WriteLine (GetOnDateSenderWrote (original));
using (var reader = new StringReader (text)) {
string line;
while ((line = reader.ReadLine ()) != null) {
quoted.Write ("> ");
quoted.WriteLine (line);
}
}
return quoted.ToString ();
}
}
protected override void VisitTextPart (TextPart entity)
{
string text;
if (entity.IsHtml) {
var converter = new HtmlToHtml {
HtmlTagCallback = HtmlTagCallback
};
text = converter.Convert (entity.Text);
} else if (entity.IsFlowed) {
var converter = new FlowedToText ();
text = converter.Convert (entity.Text);
text = QuoteText (text);
} else {
// quote the original message text
text = QuoteText (entity.Text);
}
var part = new TextPart (entity.ContentType.MediaSubtype.ToLowerInvariant ()) {
Text = text
};
Push (part);
}
protected override void VisitMessagePart (MessagePart entity)
{
// don't descend into message/rfc822 parts
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment