We are using the ms graph api to post messages to a teams channel from a internal desktop application. The main purpose is to attach images to the message. We upload the image files into the one-drive folder of the channel as shown below.
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "replace" }
}
};
var session = await graphClient.Drives[driveId]
.Items[parentId].ItemWithPath(fileName).CreateUploadSession(uploadProps).Request().PostAsync(token);
int maxSliceSize = 320 * 1024;
var fileUploadTask =
new LargeFileUploadTask<DriveItem>(session, fileStream, maxSliceSize);
// Create a callback that is invoked after each slice is uploaded
IProgress<long> progress = new Progress<long>(reportAsync);
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
return uploadResult.ItemResponse;
}
We then send a message to the channel and attach the images uploaded previously as reference attachments.
var chatMsg = new ChatMessage();
chatMsg.Body = new ItemBody();
chatMsg.Body.ContentType = BodyType.Html;
chatMsg.Body.Content = msg + " " + string.Join(" ", attachments.Select(d => $"<attachment id=\"{parseEtag(d.ETag)}\"></attachment>"));
chatMsg.Attachments = attachments.Select(d => new ChatMessageAttachment()
{
Id = parseEtag(d.ETag),
ContentType = "reference",
ContentUrl = d.WebUrl,
Name = d.Name
});
return await this.graphClient.Teams[teamId].Channels[channelId].Messages
.Request()
.AddAsync(chatMsg, token);
The problem is that the message only shows the names of the attachments with no preview as seen in the message at the bottom. We want to have a preview as seen (top message) when attaching a file within the teams application.
We've tried to set the thumbnailurl property of the attachment to the thumbnail url fetched from the ms-graph api with no success.
We've uploaded a file using the teams application (with preview) and then created an identical message with the same file (same driveitem id) in our application (show's no preview). Then we fetched both messages using the graph api and could not discern any differences between the two besides the message id's ofc.
We've scoured these forums, the ms documentations and even suggestion pages and found nothing.
We have been able to show previews separately in the body of the message referencing the thumbnail urls and in messagecards but ideally we want the preview directly in the attachments.
EDIT
The thumbnail urls seem to expire after 24 hours and are therefor not a great solution.
We managed to solve exactly this problem using the Simple Upload Api, with the added ?$expand=thumbnails query parameter. I haven't tried but the query param ought to work for the endpoint you're using as well.
Pick a size from the ThumbnailSet in the upload response and add it to the body of your message as an image tag. See below:
// channel, file, extractIdFromEtag, message omitted for brevity.
// PUT /groups/{group-id}/drive/items/{parent-id}:/{filename}:/content
const uploadUrl = `https://graph.microsoft.com/beta/groups/${channel.teamId}/drive/items/root:/${channel.displayName}/${file.name}:/content?$expand=thumbnails`;
const res = await this.http.put(uploadUrl, file).toPromise(); // FYI Using Angular http service
const attachment = {
id: extractIdFromEtag(res.eTag),
contentType: 'reference',
contentUrl: res.webUrl,
name: res.name,
thumbnailUrl: res.webUrl
};
const postBody = {
subject: null,
body: {
contentType: 'html',
content: message
},
};
// This is what makes the image show in the message as if posted from teams
postBody.body.content += `<br><br><img src="${res.thumbnails[0].large.url}" alt="${res.name}"/>`;
const messageUrl = `https://graph.microsoft.com/beta/teams/${channel.teamId}/channels/${channel.id}/messages`;
const result = await this.http.post(messageUrl, postBody).toPromise();
// Done
You can also keep adding the attachment as you already do, if you want the original image attached as a file, as well as showing the image preview in the message.
Related
Email Not been recieved with attachments when I try to use Uploadsession using Graph API. can someone help me uderstand why this is happening. I have not recieved any error.
Message draft = await graphServiceClient.Users["UserID"].Messages.Request().AddAsync(email);
//Message draft = graphServiceClient.Users["UserID"].Mailfolders.Drafts.Messages.Request().AddAsync(email);
var stream = System.IO.File.Open(#"C:\attach\DYN28_6579332_33242556.csv", System.IO.FileMode.Open, FileAccess.Read, FileShare.None);
var attachmentItem = new AttachmentItem
{
AttachmentType = AttachmentType.File,
Name = "DYN28_6579332_33242556.csv",
Size = stream.Length
};
var uploadSession = await graphServiceClient.Users["Userid"].Messages[draft.Id]
.Attachments
.CreateUploadSession(attachmentItem)
.Request()
.PostAsync();
var maxSlicesize = 320 * 1024;
var largeFileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, stream, maxSlicesize);
IProgress<long> progress = new Progress<long>(prog => {
Console.WriteLine($"Uploaded {prog} bytes of {stream.Length} bytes");
});
// Upload the file
var uploadResult = await largeFileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
// The ItemResponse object in the result represents the
// created item.
//Console.WriteLine($"Upload complete, item ID: {uploadResult.ItemResponse.Id}");
Console.WriteLine("upload completed");
}
Finally sending email with
await graphServiceClient.Users["userid"].Messages[draft.Id]
.Send()
.Request()
.PostAsync();
There is a limit of 4MB on a single request in the Graph API. To send larger attachments, you need to first create an upload session against the email message/calendar event, and upload your attachment in a number of requests as part of this session. AFAIK each of the smaller POST requests would also need to stay below the 4MB limit.
You can find more detailed documentation and a sample walkthrough here.
POST https://graph.microsoft.com/v1.0/me/messages/AAMkADI5MAAIT3drCAAA=/attachments/createUploadSession
Content-type: application/json
{
"AttachmentItem": {
"attachmentType": "file",
"name": "flower",
"size": 3483322
}
}
I am trying to use the following code, but am getting "Message: The audience claim value is invalid for current resource. Audience claim is 'https://graph.microsoft.com', request url is 'https://outlook.office.com/api/beta/Users..."
I get it on the provider.GetUploadChunkRequests(); call below:
AttachmentItem attachmentItem= new AttachmentItem
{
Name = [Name],
AttachmentType = AttachmentType.File,
Size = [Size]
};
var session = graphClient.Users[USEREMAIL].Messages[MESSAGEID].Attachments.CreateUploadSession(attachmentItem).Request().PostAsync().Result;
var stream = new MemoryStream(BYTEARRAY);
var maxSizeChunk = DEFAULT_CHUNK_SIZE;
var provider = new ChunkedUploadProvider(session, graphClient, stream, maxSizeChunk);
var chunkRequests = provider.GetUploadChunkRequests();
(I am using the graphClient to send emails successfully, and have also used it to upload large files using the uploadSession method)
From Andrue Eastman on GitHub:
You are most likely getting the error because of using the ChunkedUploadPorvider instead of using the FileUploadTask to upload the attachment which is setting the Auth header to cause the error you are receiving.
To use the file upload task, follow the following steps
First create an upload session and handing it over to the task as illustrated.
// Create task
var maxSliceSize = 320 * 1024; // 320 KB - Change this to your chunk size. 4MB is the default.
LargeFileUploadTask<FileAttachment> largeFileUploadTask = new LargeFileUploadTask<FileAttachment>(uploadSession, stream, maxSliceSize);
Create an upload monitor (optional)
// Setup the progress monitoring
IProgress<long> progress = new Progress<long>(progress =>
{
Console.WriteLine($"Uploaded {progress} bytes of {stream.Length} bytes");
});
The service only returns location URI which can be read off from the result object as follows.
UploadResult<FileAttachment> uploadResult = null;
try
{
uploadResult = await largeFileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
Console.WriteLine(uploadResult.Location);//the location of the object
}
}
catch (ServiceException e)
{
Console.WriteLine(e.Message);
}
When we get email using Microsoft Graph/Outlook REST API it's body contains the references for embedded images like below.
<img src="cid:image001.jpg#1D3E60C.5A00BC30">
I am looking to find out a way so i can display the embedded images properly as the above image tag does not display any image. I have done some search but did not found any help on that.
Below is sample code for getting an email by id using Microsoft Graph API.
// Get the message.
Message message = await graphClient.Me.Messages[id].Request(requestOptions).WithUserAccount(ClaimsPrincipal.Current.ToGraphUserAccount()).GetAsync();
For getting attached resources with email using Microsoft Graph API you need to get email like below.
// Get the message with all attachments(Embedded or separately attached).
Message message = await graphClient.Me.Messages[id].Request(requestOptions).WithUserAccount(ClaimsPrincipal.Current.ToGraphUserAccount()).Expand("attachments").GetAsync();
Once you have all attachments with email detail you need to iterate through attachments list and check if attachment IsInline property is set as true then simply replace the
cid:image001.jpg#1D3E60C.5A00BC30
with Base64String created from bytes array of attachment.
string emailBody = message.Body.Content;
foreach (var attachment in message.Attachments)
{
if (attachment.IsInline.HasValue && attachment.IsInline.Value)
{
if ((attachment is FileAttachment) &&(attachment.ContentType.Contains("image")))
{
FileAttachment fileAttachment = attachment as FileAttachment;
byte[] contentBytes = fileAttachment.ContentBytes;
string imageContentIDToReplace = "cid:" + fileAttachment.ContentId;
emailBody = emailBody.Replace(imageContentIDToReplace,
String.Format("data:image;base64,{0}", Convert.ToBase64String(contentBytes as
byte[])));
}
}
}
Now render the email body using emailBody variable it will display all embedded images.
Use below code to display logo image on image tag using graph Api in C#.
var fileAttachment = new FileAttachment
{
ODataType = "#microsoft.graph.fileAttachment",
Name = Path.GetFileName(attachment),
ContentLocation = attachment,
ContentBytes = contentBytes,
ContentType = contentType,
ContentId= contentId,
IsInline = true
};
Note : Here IsInline = true must need to be added if you want to display image on image tag only not as attachment.
For an End Screens report, how can I determine which target is represented by the end_screen_element_id on each record?
For example: on my channel, let's say I have "Video 1" set up with two video end screen elements "Video 2" and Video 3". I want to know how many clicks at the end of "Video 1" went to "Video 2" and how many went to "Video 3".
The data returned for this report gives me a video_id field indicating which video was watched and an end_screen_element_clicks field indicating how many times a viewer clicked through to the end screen video...
...HOWEVER, the only identifier for which video the viewer clicked through to is an end_screen_element_id field, which looks like a GUID and apparently somehow refers to the full end screen element definition, and therefore presumably what video is represented by that definition.
I'm unable to find any reports or other API calls for getting detail information on that end_screen_element_id field, particularly which video it represents.
How can I use that field or otherwise figure out what end screen video the viewer clicked through to?
More Information
The data returned looks like this:
Data returned in the End Screens report
Here's a screenshot that may help explain what I'm trying to do with the data: YouTube Analytics screen shot
The following C# code demonstrates how the report is requested:
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
{
ClientId = CLIENT_ID,
ClientSecret = CLIENT_SECRET
},
new[] { YouTubeReportingService.Scope.YtAnalyticsReadonly },
"user",
CancellationToken.None,
new FileDataStore("Drive.Auth.Store")).Result;
YouTubeReportingService reportingService = new YouTubeReportingService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = APPLICATION_NAME
});
// Submit a report job to obtain the latest End Screen statistics.
Job channelEndScreenJob = new Job();
channelEndScreenJob.ReportTypeId = "channel_end_screens_a1";
channelEndScreenJob.Name = JOB_NAME;
Job createdJob =
reportingService.Jobs.Create(channelEndScreenJob).Execute();
A separate service retrieves the report like this:
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
{
ClientId = CLIENT_ID,
ClientSecret = CLIENT_SECRET
},
new[] { YouTubeReportingService.Scope.YtAnalyticsReadonly },
"user",
CancellationToken.None,
new FileDataStore("Drive.Auth.Store")).Result;
YouTubeReportingService reportingService = new YouTubeReportingService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = APPLICATION_NAME
});
// Retrieve data from jobs that were previously submitted.
ListJobsResponse jobList = reportingService.Jobs.List().Execute();
if (jobList.Jobs != null)
{
foreach (Job job in jobList.Jobs)
{
ListReportsResponse reportList = reportingService.Jobs.Reports.List(job.Id).Execute();
if (reportList.Reports != null)
{
foreach (Report report in reportList.Reports)
{
MediaResource.DownloadRequest getRequest = reportingService.Media.Download("");
// Download the report data.
using (MemoryStream stream = new MemoryStream())
{
getRequest.MediaDownloader.Download(report.DownloadUrl, stream);
stream.Flush();
stream.Position = 0;
using (StreamReader reader = new StreamReader(stream))
{
// Parse report...
DataTable parsedReport = ReportToDataTable(reader.ReadToEnd());
// ...and get data on videos watched and videos clicked to.
foreach (DataRow row in parsedReport.Rows)
{
string videoWatched = row["video_id"].ToString();
string videoClickedToFromEndScreen = **WHAT???**
}
}
}
}
}
}
}
Issue #1
When i'm uploading a file to google docs i receive status code "201" created, but when i try to open the file it seems that i'm doing something wrong, because i can't open it, and when i'm trying to download and open it on my PC i see the binary data instead of text or image. Current language is APEX, but i think it's pretty understandable.
First of all i'm getting Upload URL and then putting data to this URL;
public void getUploadURL()
{
Httprequest req = new Httprequest();
req.setEndpoint('https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false');
req.setMethod('POST');
req.setHeader('GData-Version', '3.0');
req.setHeader('Authorization', 'OAuth '+accessToken);
req.setHeader('Content-Length', '359');
req.setHeader('X-Upload-Content-Type', fileType);
req.setHeader('X-Upload-Content-Length', fileSize);
Dom.Document requestDoc = new Dom.Document();
String xml =
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
+'<entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">'
+'<title>'+fileName+'</title></entry>';
requestDoc.load(xml);
req.setBodyDocument(requestDoc);
Http h = new Http();
Httpresponse res = h.send(req);
System.debug('response=\n'+res.getHeader('Location'));
uploadFIle(res.getHeader('Location'));
}
public void uploadFIle(String uploadUrl)
{
Httprequest req = new Httprequest();
req.setEndpoint(uploadUrl);
req.setMethod('PUT');
req.setHeader('GData-Version', '3.0');
req.setHeader('Authorization', 'OAuth '+accessToken);
req.setHeader('Host', 'docs.google.com');
req.setHeader('Content-Length', fileSize);
req.setHeader('Content-Type', fileType);
req.setBody(''+binaryData);
Http h = new Http();
Httpresponse res = h.send(req);
System.debug('response=\n'+res.getBody());
}
As for "binaryData" property - i receive it from the page using javascript like this:
<input type="file" id="myuploadfield" onchange="getBinary()"/>
<script>
function getBinary()
{
var file = document.getElementById('myuploadfield').files[0];
fileSizeToController.val(file.size.toString());
fileNameToController.val(file.name.toString());
fileTypeToController.val(file.type.toString());
var r = new FileReader();
r.onload = function(){ binaryToController.val(r.result); };
r.readAsBinaryString(file);
}
</script>
r.onload = function(){ binaryToController.val(r.result); }; - this is the string that sends file binary data to my controller.
Issue #2
I'm trying to move one collection(folder) to another, and using this article (protocol tab instead of .NET). The issue is that i need to move collection instead of copying it and when i add my collection to another using this article, i'm currently adding reference to my collection instead of moving the whole collection from one place to another.
Please tell me what am i doing wrong.
Thank you for consideration.
Your "binary" data is being corrupted, when you are performing '' + binaryData.
In general, I have had more success using slicing of files, here is an example for webkit:
var chunk = this.file.webkitSlice(startByte, startByte + chunkSize, file_type);
// Upload the chunk
uploadChunk(startByte, chunk, callback);