I'm using the new gmail api in c# application, and I want to know how I can read the body of a message after getting the message with the get method? Can i get a MailMessage object from the "Raw" property of the message (to create a Raw from a mailMessage i use this, is there a way to convert it back?), or i need to use the "Payload" property?
This is my code: (the ListMessages and the GetMessage methods are from the
API Reference on google's site)
List<Message> msgList = ListMessages(gs, "me", "is:unread");
string id = msgList[0].Id;
Message msg = GetMessage(gs, "me", id);
Now what?
Please help.
Thanks.
From the API, your Message (1) has a Payload property of type MessagePart (2). MessagePart has a Body property of type MessagePartBody (3) which (finally) has a string Data property.
Data is the content of the message, so (using your example code) to get the message you would do something like:
msg.Payload.Body.Data
From there, how you use it is up to you, although you have to be aware that there may or may not be HTML in that value. From the API reference, we also see this for the Parts property of the Payload:
For non- container MIME message part types, such as text/plain, this
field is empty
So you could make the assumption that if msg.Payload.Parts contains no elements then it is a plain-text message.
The Gmail API is not super easy to use. They really leave a lot to the user to just figure out.
You're going to need to use recursion to get the correct structure and do some decoding of the message. The structure of the JSON is going to be very different depending on the format of the message, if there are attachments and the sending client.
This guide goes over exactly how to handle extracting the HTML and Plain text versions of the body.
Here part of the code from the guide that shows how to extract the body parts:
public static void ExtractMessagePart(MessagePart part, ref EmailMessageModel message)
{
if (part == null)
return;
var contentDisposition = part.Headers?.FirstOrDefault(h => h.Name == "Content-Disposition");
if (contentDisposition != null && (contentDisposition.Value.StartsWith("attachment") || contentDisposition.Value == "inline"))
{
message.Attachments.Add(new DragnetTech.EventProcessors.Email.EmailMessageModel.Attachment
{
AttachmentId = part.Body.AttachmentId,
Filename = part.Filename,
ContentID = contentDisposition.Value.StartsWith("inline") || part.Headers?.FirstOrDefault(h => h.Name == "Content-ID") != null ? Utils.UnescapeUnicodeCharacters(part.Headers.FirstOrDefault(h => h.Name == "Content-ID")?.Value) : null,
Size = part.Body.Size ?? 0,
ExchangeID = part.Body.AttachmentId,
Data = part.Body.Data,
ContentType = part.Headers?.FirstOrDefault(h => h.Name == "Content-Type")?.Value
});
}
else
{
if (part.MimeType == "text/plain")
{
message.Body = DecodeSection(part.Headers?.FirstOrDefault(h => h.Name == "Content-Transfer-Encoding")?.Value, part.Body?.Data);
message.IsHtml = false;
}
else if (part.MimeType == "text/html")
{
message.Body = DecodeSection(part.Headers?.FirstOrDefault(h => h.Name == "Content-Transfer-Encoding")?.Value, part.Body?.Data);
message.IsHtml = true;
}
}
if (part.Parts != null)
{
foreach (var np in part.Parts)
{
ExtractMessagePart(np, ref message);
}
}
}
Related
I'm trying using gRPC dynamically typed values but with the little information about their usefulness, It's almost impossible to do this... So I will show the image/code that I have problems and the questions that are eating my brain
gRPC Method I'm doing:
public override Task<HelloReply2> TestObject(Status request, ServerCallContext context) {
//The part I may have problems
var status = new Status();
//here I want a User that corresponds to my request.Data
//example -> request.Data = User1 (gives me null if User1 don`t exist in db)
// request.Data = 14 (gives me null if 14 don`t exist in db)
// request.Data = true (gives me null if true don`t exist in db)
var a1 = _context.Users_5.FirstOrDefault(x => x.Username.Equals(request.Data));
var b1 = _context.Users_5.FirstOrDefault(x => x.Email.Equals(request.Data));
var c1 = _context.Users_5.FirstOrDefault(x => x.Age.Equals(request.Data));
var d1 = _context.Users_5.FirstOrDefault(x => x.Test.Equals(request.Data));
//is a bool
//here i want too Create dynamic values
status.Data = Value.ForStruct(new Struct {
Fields =
{
["Integer"] = Value.ForNumber(c1!.Age),
["StringName"] = Value.ForString(a1!.Username),
["StringEmail"] = Value.ForString(b1!.Email),
["Boolean"] = Value.ForBool(d1!.Test)
}
});
//Below is just a simple string who gives different string (depending on the
//data Status (also how to read the message from the status.Data ?)
HelloReply2 hello = new();
if(a1 != null)
{
hello.Message = "There is a User with the Username " + request.Data + ". His Email is " + a1.Email;
} else if (b1 != null) {
hello.Message = "There is a User with the Email " + request.Data + ". His Username is " + b1.Username;
}
else if (c1 != null)
{
hello.Message = "There is at least one User with that Age of " + request.Data + ". His Username is " + c1.Username;
}
else if (d1 != null)
{
if(d1.Test == true)
{
hello.Message = "There is at least one User who dislikes chocolate: " + request.Data + ". His Username is " + d1.Username;
} else
{
hello.Message = hello.Message = "There is at least one User who likes chocolate: " + request.Data + ". His Username is " + d1.Username;
}
}
else
{
hello.Message = "We didn't find something with the value that the User put in. Value:" + request.Data;
}
return Task.FromResult(hello);
}
Questions: How to Get the one Value from my gRPC? How to convert a "Object" in c# (one string, one integer or one List) into a ONE value of google.protobuf.Value (so it not give me errors like this Controller from a Web Api below)? Is something wrong with my gRPC Service Method (is something wrong reading the dynamic values? Can I do that calls for getting a User for a DB? How to read dynamic values?)
// I try using Google.Protobuf.WellKnownTypes.Value obj but
//not workings because gives me a lot of values to put
[HttpGet("TypeObject/{obj}")]
public async Task<ActionResult<HelloReply2>> TypeObject([FromRoute] Object obj){
Status objRequest = new Status { Data = (Google.Protobuf.WellKnownTypes.Value)
obj };
//cannot do this (gives me error of casting but the ideia is doing something
//similar to this)
var hello = await _greetClient.TestObjectAsync(objRequest);
return Ok(hello);
}
Any help on how to resolve this error of using Value gRPC or if is something wrong with the code is always welcome.
Edit:
One day after this question I don't have any solutions / progress. I was think of doing Any or OneOf for testing but it also gives me errors (who don't make sense at all). This code from Microsoft (C# Format part is not recognize) doesn't work in my project with the protos reload (the problem is not in the protos)
Link: https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/protobuf-any-oneof
How I can use Any / OneOf without give me error in the Formating? What is the difference between Value and this two? Can the three (Value, Any, OneOf) be dynamic/Object values (if yes how to convert the types)?
Edit 2:
Still have problems, I'm trying using gRPC Any , and maybe have some progress (not all).
So with Any I have my method in the server gRPC and it is like this
public override Task<HelloReply2> TestObject3(AnyMessage request, ServerCallContext context){
HelloReply2 anyMessageResponse;
var y = request.TypeUrl;
switch (request.TypeUrl)
{
case "type.googleapis.com/any.HelloRequest":
var string_1 = request.Unpack<HelloRequest>();
anyMessageResponse = new HelloReply2{
Message = "You type String: " + $"{string_1.Name}"
};
break;
case "type.googleapis.com/any.TestInteger1":
var integer_1 = request.Unpack<TestInteger1>();
anyMessageResponse = new HelloReply2{
Message = "You type Integer: " + $"{integer_1.Message}"
};
break;
case "type.googleapis.com/any.TestBool1":
var bool_1 = request.Unpack<TestInteger1>();
anyMessageResponse = new HelloReply2{
Message = "You type Bool: " + $"{bool_1.Message}"
};
break;
default:
throw new InvalidOperationException("Unexpected type URL.");}
return Task.FromResult(anyMessageResponse);
}
This ideia comes from here (https://github.com/grpc/grpc-dotnet/issues/917), but the client part their don't have any much info or I don't understand that part
This is what I did in the WebApi (who is my client and the code is similar to the above one)
using AnyMessage = Google.Protobuf.WellKnownTypes.Any;
[HttpGet("TypeObject3/{obj3}")]
public async Task<ActionResult<HelloReply2>> TypeObject3([FromRoute] string obj3)
{
AnyMessage objRequest = new() { TypeUrl = obj3 };
var hello = await _greetClient.TestObject3Async(objRequest);
var l = hello.Message;
return Ok(hello);
}
First I had the variable Any declared in the method instead of string but as you can only put string and stringBytes so I preferred to put it like this (with the string as an obj3 variable) but my goal is to see if the variable is of type TestBool1 or TestInteger1 as I have declared in the protos and not be a string that I will be able to see, and the biggest problem was if I had more variables inside the messages how to proceed? So my secondary question is how to use Any on the client side via the Web-Api? I forgot to say but I'm using .Net 6 Core and for testing I'm using Swagger, where at this moment my error hits the Exception dictated by the Server method.
Questions: Why TypeUrl is a string and not object? How to fix my problem? How to test the object type (or string) for more values if the messages was with 1 more types?
Also I will show my test proto too show how I'm doing this
import "google/protobuf/struct.proto";
import "google/protobuf/any.proto";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayNormalHello (Empty_2) returns (HelloReply);
rpc SayHello (HelloRequest) returns (HelloReply2);
rpc TestInt (TestInteger1) returns (HelloReply2);
rpc TestBoolean (TestBool1) returns (HelloReply2);
rpc TestObject (Status) returns (HelloReply2); //Not working
rpc TestObject2 (Status2) returns (HelloReply2); //Not working
rpc TestObject3 (google.protobuf.Any) returns (HelloReply2); //Also
//Not working
}
message Empty_2{
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
// The response message containing the greetings.
message HelloReply2 {
string message = 1;
}
message TestInteger1 {
int32 message = 1;
}
message TestBool1 {
bool message = 1;
}
message Status {
google.protobuf.Value data = 1;
}
message Status2 {
google.protobuf.Any data = 1;
}
Any help is welcome.
In the project I developed, I needed an infrastructure where I could perform dynamic operations such as REST service using gRPC. As I understand it, what you want is something similar to this.
I have developed a solution for this. It might work for you too.
You can gain some flexibility by making a definition with a single String field for Requests and Responses that you want to be Dynamic, and then using this field to hold JSON data as strings.
For example, instead of defining different responses for different types, you can solve it by making one definition like this.
message HelloReply {
// Stringified JSON Data
string data = 1;
}
I've encountered an error SQLite is not available while decrypting a mime message after executing pkcs7.Decrypt(). Please see my block of code below:
public MimeEntity decryptMessage(MimeMessage message)
{
// Putting the value of message.body to a string for checking only
using (var memory = new MemoryStream())
{
responseString.Body.WriteTo(memory);
string bodystring = Encoding.UTF8.GetString(memory.ToArray());
}
var pkcs7 = message.Body as ApplicationPkcs7Mime;
if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.EnvelopedData)
{
// the top-level MIME part of the message is encrypted using S/MIME
return pkcs7.Decrypt();
}
else
{
// the top-level MIME part is not encrypted
return message.Body;
}
}
I've tried to put the value of message.Body into a variable and check its value but it seems that it doesn't have any problem:
Content-Type: application/pkcs7-mime; smime-type=enveloped-data;
name=smime.p7m
Content-Disposition: attachment; filename=smime.p7m
Content-Transfer-Encoding: base64
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
1t7wTLW53B/TF88W1Ufa9oUAAAAAAAAAAAAA
So now I can't understand why the error says SQLite is not available despite that I'm not using any database connection on this one. I'm new to using MimeKit. If anyone knows how can I do this it will be a great help.
I'm using MailKit for received messages via Imap.
Now I need a search-logic for the software and I got a some troubles.
For ex, I using:
var query = SearchQuery.BodyContains("SomeTextfromEmail");
( or var query = SearchQuery.MessageContains("SomeTextfromEmail"); )
foreach (UniqueId uid in imapfolder.Search(query)){
//some logic
}
So, I can't get messages with text contains on it with Search filter.
I'm downloaded message ( message.WriteTo(string.Format(#"C:{0}.eml", uid)); )
and see the message content. Text of message in a base-64 format.
How do it correctly? I need decode message text from base64?
Problem are here:
var query = SearchQuery.BodyContains("Iphone").And(SearchQuery.All); //this construction find messages in folder
foreach (var uid in imapfolder.Search(query))
{
Console.WriteLine(message.From + " : " + uid.ToString());
}
var query = SearchQuery.BodyContains("Received from Iphone").And(SearchQuery.All); //this construction can't find message in folder
foreach (var uid in imapfolder.Search(query))
{
Console.WriteLine(message.From + " : " + uid.ToString());
}
Your question is a jumbled mess of confusion (your question starts out saying you are trying to figure out how to search and then you end with asking how to base64 decode the raw message that you saved to a file) so I have no idea exactly what you want to know.
If all you want is the decoded text body, you can do this:
var message = imapfolder.GetMessage (uid);
var text = message.TextBody;
This will be the decoded text string (either base64 decoded or quoted-printable decoded if it needs to be).
The MimeKit README and FAQ are both full of useful information on the basics of how to use them. I would highly recommend reading them as they may be helpful.
Update:
Based on the comment added to my answer, it sounds like what you want to do is this?
var matched = new UniqueIdSet ();
foreach (var uid in folder.Search (SearchQuery.BodyContains ("iPhone"))) {
var message = folder.GetMessage (uid);
var body = message.TextBody;
if (body != null && body.Contains ("Received from iPhone"))
matched.Add (uid);
}
I am using the Exchange Web Services Managed API 2.2 to monitor users inboxes and need to determine if an e-mail is a new item, a reply or a forwarded message.
I have seen various articles on SO such as how to notice if a mail is a forwarded mail? and Is there a way to determine if a email is a reply/response using ews c#? which both help in their specific cases but I still cannot work out how to distinguish between a reply and a forwarded item.
In the first article an extra 5 bytes is added each time (forward or reply) so I don't know what the last action was.
The second article suggests using the InReplyTo however when I examine the property for forwarded e-mails it contains the original senders e-mail address (not null).
I have seen other articles such as this or this that suggest using extended properties to examine the values in PR_ICON_INDEX, PR_LAST_VERB_EXECUTED and PR_LAST_VERB_EXECUTION_TIME.
My code looks as follows but never returns a value for lastVerbExecuted
var lastVerbExecutedProperty = new ExtendedPropertyDefinition(4225, MapiPropertyType.Integer);
var response = service.BindToItems(newMails, new PropertySet(BasePropertySet.IdOnly, lastVerbExecutedProperty));
var items = response.Select(itemResponse => itemResponse.Item);
foreach (var item in items)
{
object lastVerb;
if (item.TryGetProperty(lastVerbExecutedProperty, out lastVerb))
{
// do something
}
}
PR_ICON_INDEX, PR_LAST_VERB_EXECUTED and PR_LAST_VERB_EXECUTION_TIME would only work to tell you if the recipient has acted on a message in their Inbox. Eg if the user had replied or forwarded a message in their inbox then these properties get set on the message in their Inbox. On the message that was responded to or forwarded these properties would not be set. I would suggest you use the In-Reply-To Transport header which should be set on any message that is replied to or forwarded, this should contain the internet messageid of the message that was replied to or forwarded eg.
FindItemsResults<Item> fiRs = service.FindItems(WellKnownFolderName.Inbox, new ItemView(10));
PropertySet fiRsPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
ExtendedPropertyDefinition PR_TRANSPORT_MESSAGE_HEADERS = new ExtendedPropertyDefinition(0x007D, MapiPropertyType.String);
fiRsPropSet.Add(PR_TRANSPORT_MESSAGE_HEADERS);
service.LoadPropertiesForItems(fiRs.Items, fiRsPropSet);
foreach (Item itItem in fiRs)
{
Object TransportHeaderValue = null;
if(itItem.TryGetProperty(PR_TRANSPORT_MESSAGE_HEADERS,out TransportHeaderValue)) {
string[] stringSeparators = new string[] { "\r\n" };
String[] taArray = TransportHeaderValue.ToString().Split(stringSeparators, StringSplitOptions.None);
for (Int32 txCount = 0; txCount < taArray.Length; txCount++)
{
if (taArray[txCount].Length > 12)
{
if (taArray[txCount].Substring(0, 12).ToLower() == "in-reply-to:")
{
String OriginalId = taArray[txCount].Substring(13);
Console.WriteLine(OriginalId);
}
}
}
}
}
Apart from the Subject prefix that was discussed in the other link I don't know of any other proprieties that will differentiate between a reply or forward.
Cheers
Glen
The best way to rely is on the ResponeCode of Extended properties
Refer below scripts
private static int IsForwardOrReplyMail(ExchangeService service, EmailMessage messageToCheck)
{
try
{
// Create extended property definitions for PidTagLastVerbExecuted and PidTagLastVerbExecutionTime.
ExtendedPropertyDefinition PidTagLastVerbExecuted = new ExtendedPropertyDefinition(0x1081, MapiPropertyType.Integer);
ExtendedPropertyDefinition PidTagLastVerbExecutionTime = new ExtendedPropertyDefinition(0x1082, MapiPropertyType.SystemTime);
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly, EmailMessageSchema.Subject, PidTagLastVerbExecutionTime, PidTagLastVerbExecuted);
messageToCheck = EmailMessage.Bind(service, messageToCheck.Id, propSet);
// Determine the last verb executed on the message and display output.
object responseType;
messageToCheck.TryGetProperty(PidTagLastVerbExecuted, out responseType);
if (responseType != null && ((Int32)responseType) == 104)
{
//FORWARD
return 104;
}
else if (responseType != null && ((Int32)responseType) == 102)
{
//REPLY
return 102;
}
}
catch (Exception)
{
return 0;
//throw new NotImplementedException();
}
}
To determine if it was a reply to a email, you can use the EmailMessage objects InReplyTo property, e.g:
EmailMessage mail = ((EmailMessage)Item.Bind(service, new ItemId(UniqueId)));
if (mail.InReplyTo == null)
return;
else
..your code
In Outlook, I can set the subject for a new message (when composing a new mail message), but I want to prepend text. So I need to get the subject first, and then set it.
Outlook.Application application = Globals.ThisAddIn.Application;
Outlook.Inspector inspector = application.ActiveInspector();
Outlook.MailItem myMailItem = (Outlook.MailItem)inspector.CurrentItem;
if (myMailItem != null && !string.IsNullOrEmpty(myMailItem.Subject))
{
myMailItem.Subject = "Following up on your order";
}
This code works on replies, but not for new messages, because in that case, myMailItem is null.
This is what I was looking for:
if (thisMailItem != null)
{
thisMailItem.Save();
if (thisMailItem.EntryID != null)
{
thisMailItem.Subject = "prepended text: " + thisMailItem.Subject;
thisMailItem.Send();
}
}
The subject was null until the mail item had been saved, either because it was sent, or as a draft. We can save it programmatically and then get the subject.
One other note: if the subject is blank at the time of saving, it will still show as null.