Graph API - BadRequest on sending channel messages with #mention - asp.net-web-api

We had some code that has been working for the past 10 months (since it was developed) and just stopped working this afternoon. It's a WebAPI code to send a channel message mentioning the bot and a user, which now is returning "Bad Request. Invalid request body was sent."
If the "Mentions" property is not provided, the call works, and the message is sent without the #mentions. So, I wonder if there was a breaking change in this API that's now expecting a different format for the "Mentions" property.
It's quite simple to reproduce by following the example code found in the Microsoft Graph documentation.
I'm posting here in the hope some fellow dev spots something obvious or is aware of an alternative way of using the API that it might stop complaining, as Microsoft takes forever to reply.
Here's the code we have that can lead me to discover the issue:
private async Task SendMentionToTheBotAsync(GraphServiceClient onBehalfOfClient, string userName, string teamId, string channelId)
{
var supportAgentUser = await onBehalfOfClient.Me.Request().GetAsync();
var chatMessage = new ChatMessage
{
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = $"<at id=\"0\">{Configuration["BotName"]}</at>: This is the start of the conversation between {userName} and <at id=\"1\">{supportAgentUser.DisplayName}</at>."
},
Mentions = new List<ChatMessageMention>
{
new ChatMessageMention
{
Id = 0,
MentionText = Configuration["BotName"],
Mentioned = new IdentitySet
{
Application = new Identity
{
DisplayName = Configuration["BotName"],
Id = Configuration["BotAppId"],
AdditionalData = new Dictionary<string,object>
{
{
"applicationIdentityType", "bot"
}
}
}
}
},
new ChatMessageMention
{
Id = 1,
MentionText = supportAgentUser.DisplayName,
Mentioned = new IdentitySet
{
User = new Identity
{
DisplayName = supportAgentUser.DisplayName,
Id = supportAgentUser.Id,
AdditionalData = new Dictionary<string,object>
{
{
"userIdentityType", "aadUser"
}
}
}
}
}
}
};
await onBehalfOfClient.Teams[teamId].Channels[channelId].Messages
.Request()
.AddAsync(chatMessage);
}

Microsoft Support responded with :
"Thank you for contacting Microsoft Support.
I understand the issue is related to the post messages to Teams. Based on the screenshot, it seems you are using mention to a channel. It's possible that you are using key "conversationIdentityType#odata.type" in your request.
Could you please try to remove "conversationIdentityType#odata.type" key from the request body and try again. It should work. It is because deployment is on the way in the Asia region. Once it's 100% rolled out, this key WILL NOT be entertained in the request."
Removed the key and it worked for me.

Paulo,
Unfortunately i am not a programmer. I am using Graph calls in a Microsoft 365 Power Automate workflow. I have an app that i use to get the Authorisation Bearer token and then post to Teams messages using a graph HTTP action.
Here is the syntax of the HTTP ( purple items are variables if u r not familiar with Flow )
click to view image of Power Automate workflow HTTP action

Related

Teams bot, transfer a call to another application / voicemail

In our Teams calling bot, we would like to transfer certain calls to specific Teams users, PSTN, but also to an other Teams calling bot and/or voicemail.
For specific Teams users and PSTN we got it working. If we want to transfer a call to another application, we can do so by using its pstn number. But ideally we would also like to transfer using its objectId.
I tried using a transferrequest like this:
var requestBody = new CallTransferRequestBody()
{
TransferTarget = new InvitationParticipantInfo()
{
Identity = new IdentitySet()
{
AdditionalData = new Dictionary<string, object>()
}
}
};
requestBody.TransferTarget.Identity.Application = new Identity { Id = transferTargetId };
//this line does not make any difference
requestBody.TransferTarget.Identity.Application.SetTenantId(tenantId);
But this results in a "Request authorization tenant mismatch." error. Is it possible to directly transfer to another application?
I haven't tried voicemail boxes yet, but if any info on how to transfer to those, is appreciated.
Basically we can transfer an active peer-to-peer call. This is only supported if both the transferee and transfer target are Microsoft Teams users that belong to the same tenant.
However for redirecting call to call queue or auto attendants, you can use the "applicationInstance" identity. The bot is expected to redirect the call before the call times out. The current timeout value is 15 seconds.
const requestBody = {
"targets": [{
"#odata.type": "#microsoft.graph.invitationParticipantInfo",
"identity": {
"#odata.type": "#microsoft.graph.identitySet",
"applicationInstance": {
"#odata.type": "#microsoft.graph.identity",
"displayName": "Call Queue",
"id": queueId
}
}
}],}
Please refer to the documentation here: https://learn.microsoft.com/en-us/graph/api/call-redirect?view=graph-rest-beta&tabs=csharp#request
The redirect API is still having that limitation from my understanding.
But that should work with the new Transfer API:
https://learn.microsoft.com/en-us/graph/api/call-transfer?view=graph-rest-beta&tabs=http

Proactive Bot Messaging - CreateDirectConversation - unauthorized exception

I am creating a bot to proactively start a conversation with an account I have never had a previous conversation with. I have created another controller that I am posting to and doing the following steps:
public class OutboundController : ApiController {
public HttpResponseMessage Post([FromUri] int id, [FromBody] OutboundData outboundData) {
MicrosoftAppCredentials.TrustServiceUrl(outboundData.ServiceUrl);
//create conversation
var connector = new ConnectorClient(new Uri(outboundData.ServiceUrl));
var botAccount = new ChannelAccount { Id = outboundData.FromAccountId, Name = outboundData.FromAccountName };
var toAccount = new ChannelAccount { Id = outboundData.ToAccountId, Name = outboundData.ToAccountName };
if(!MicrosoftAppCredentials.IsTrustedServiceUrl(outboundData.ServiceUrl)) {
throw new Exception("service URL is not trusted!");
}
var conversationResponse = connector.Conversations.CreateDirectConversation(botAccount, toAccount);
var client = new BuslogicClient();
var confirmData = client.GetOutboundData(id);
var greetingMessage = CreateGreetingMessage(confirmData);
var convoMessage = Activity.CreateMessageActivity();
convoMessage.Text = greetingMessage;
convoMessage.From = botAccount;
convoMessage.Recipient = toAccount;
convoMessage.Conversation = new ConversationAccount(id: conversationResponse.Id);
convoMessage.Locale = "en-Us";
connector.Conversations.SendToConversationAsync((Activity)convoMessage);
string message = string.Format("I received correlationid:{0} and started conversationId:{1}", id, conversationResponse.Id);
var response = Request.CreateResponse(HttpStatusCode.OK, message);
return response;
}
When I call connector.Conversations.CreateDirectConversation I am getting the following exception: Additional information: Authorization for Microsoft App ID [ID] failed with status code Unauthorized and reason phrase 'Unauthorized'. If I do this with appId and password blank everything works fine in the channel emulator. I've tried providing the MicrosoftAppCredentials to the constructor of the ConnectorClient, but that has no affect. I've read on other threads that the service URL must be trusted so I used MicrosoftAppCredentials.TrustServiceUrl.
versions I am using:
BotBuilder 3.5.3
Channel Emulator 3.0.0.59
The use-case for my bot is to post to the outbound controller with some user info to create a proactive message to be sent out (specifically SMS). If the user responds to my message it will be intercepted by the messages controller and passed to my dialogs for further processing and conversation responses on that same channel.
I've also taken a look at: https://github.com/Microsoft/BotBuilder/issues/2155 but don't quite understand solution described in the comments or if it even pertains to the issue I'm trying to solve.
Any suggestions or help would be appreciated!
You need to pass credentials explicitly to connector:
var credentials = new MicrosoftAppCredentials("YoursMicrosoftAppId", "YoursMicrosoftAppPassword");
var connector = new ConnectorClient(serviceUrl, credentials);

Is it possible to detect when a user opens the chat window on Facebook?

I'm trying to create a chatbot where in order to avoid the user opening the chat window and not knowing the available options, I want to give some basic instructions when the user opens the chat window.
Is there any trigger available when the user opens a chat window? Maybe then I can check, and if there's not an ongoing conversation I could provide basic instructions.
I did some googling and found nothing about this. Is it possible to do something like this, and if not, is there a way to mitigate this problem, and provide the user with information regarding the chatbot capabilities and supported instructions?
Facebook does not allow bots to initiate a conversation, unlike Skype or other platforms.
There are still some tricks you can do :
Go on the Settings of your Facebook page, then Messaging and check "Show a Messenger Greeting" as below, and write your greeting sentence.
The result will look like this :
You can also set a "Get Started" button to trigger an event.
Here's the doc :
"https://developers.facebook.com/docs/messenger-platform/thread-settings/get-started-button"
You can monitor for two event types: ConversationUpdate and ContactRelationUpdate.
The first one (ConversationUpdate) is called when a user is added or removed from the conversation. So, there's a place where you can introduce available options. It will be each type the new conversation has started. So, it may become annoying, you may add a check - do not show it if the user has been using the bot for some time.
The second (ContactRelationUpdate) is called when a user adds or removes the bot to/from the contacts. In general, it is only called once per user action.
Here's the extract from the Bot-Frameworks examples:
For Node.Js
bot.on('conversationUpdate', function (message) {
// Check for group conversations
if (message.address.conversation.isGroup) {
// Send a hello message when bot is added
if (message.membersAdded) {
message.membersAdded.forEach(function (identity) {
if (identity.id === message.address.bot.id) {
var reply = new builder.Message()
.address(message.address)
.text("Hello everyone!");
bot.send(reply);
}
});
}
// Send a goodbye message when bot is removed
if (message.membersRemoved) {
message.membersRemoved.forEach(function (identity) {
if (identity.id === message.address.bot.id) {
var reply = new builder.Message()
.address(message.address)
.text("Goodbye");
bot.send(reply);
}
});
}
}
});
bot.on('contactRelationUpdate', function (message) {
if (message.action === 'add') {
var name = message.user ? message.user.name : null;
var reply = new builder.Message()
.address(message.address)
.text("Hello %s... Thanks for adding me. Say 'hello' to see some great demos.", name || 'there');
bot.send(reply);
} else {
// delete their data
}
});
For C#
private void HandleMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate)
{
if (activity.MembersAdded.Any(m => m.Id == activity.Recipient.Id))
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var response = activity.CreateReply();
response.Text = "Hi! I am Bot. Here's what you can do...";
await connector.Conversations.ReplyToActivityAsync(response);
}
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
if (Activity.AsContactRelationUpdateActivity().Action == ContactRelationUpdateActionTypes.Add)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var response = activity.CreateReply();
response.Text = "Hi! I am Bot. Thanks for adding me. Here's what you can do...";
}
}
return null;
}
I think the acid answer is not.
But you can intercept the IConversationUpdateActivity type message to know if the user has added the bot to a conversation. In the C# project template you can find a code block that ask for this message type but do nothing.

Get "API key is missing" error when querying account details to Mailchimp API 3.0 using RestSharp

When using RestSharp to query account details in your MailChimp account I get a "401: unauthorized" with "API key is missing", even though it clearly isn't!
We're using the same method to create our RestClient with several different methods, and in all requests it is working flawlessly. However, when we're trying to request the account details, meaning the RestRequest URI is empty, we get this weird error and message.
Examples:
private static RestClient CreateApi3Client(string apikey)
{
var client = new RestClient("https://us2.api.mailchimp.com/3.0");
client.Authenticator = new HttpBasicAuthenticator(null, apiKey);
return client;
}
public void TestCases() {
var client = CreateApi3Client(_account.MailChimpApiKey);
var req1 = new RestRequest($"lists/{_account.MailChimpList}/webhooks", Method.GET);
var res1 = client.Execute(req1); // works perfectly
var req2 = new RestRequest($"automations/{account.MailChimpTriggerEmail}/emails", Method.GET);
var res2 = client.Execute(req2); // no problem
var req3 = new RestRequest(Method.GET);
var res3 = client.Execute(req3); // will give 401, api key missing
var req4 = new RestRequest(string.Empty, Method.GET);
var res4 = client.Execute(req4); // same here, 401
}
When trying the api call in Postman all is well. https://us2.api.mailchimp.com/3.0, GET with basic auth gives me all the account information and when debugging in c# all looks identical.
I'm trying to decide whether to point blame to a bug in either RestSharp or MailChimp API. Has anyone had a similar problem?
After several hours we finally found what was causing this..
When RestSharp is making the request to https://us2.api.mailchimp.com/3.0/ it's opting to omit the trailing '/'
(even if you specifically add this in the RestRequest, like: new RestRequest("/", Method.GET))
so the request was made to https://us2.api.mailchimp.com/3.0
This caused a serverside redirect to 'https://us2.api.mailchimp.com/3.0/' (with the trailing '/') and for some reason this redirect scrubbed away the authentication header.
So we tried making a
new RestRequest("/", Method.GET)
with some parameters (req.AddParameter("fields", "email")) to make it not scrub the trailing '/', but this to was failing.
The only way we were able to "fool" RestSharp was to write it a bit less sexy like:
new RestRequest("/?fields=email", Method.GET)

Google+ insert moment using google-api-dotnet-client

I am trying to write an activity in Google+ using the dotnet-client. The issue is that I can't seem to get the configuration of my client app correctly. According to the Google+ Sign-In configuration and this SO question we need to add the requestvisibleactions parameter. I did that but it did not work. I am using the scope https://www.googleapis.com/auth/plus.login and I even added the scope https://www.googleapis.com/auth/plus.moments.write but the insert still did not work.
This is what my request url looks like:
https://accounts.google.com/ServiceLogin?service=lso&passive=1209600&continue=https://accounts.google.com/o/oauth2/auth?scope%3Dhttps://www.googleapis.com/auth/plus.login%2Bhttps://www.googleapis.com/auth/plus.moments.write%26response_type%3Dcode%26redirect_uri%3Dhttp://localhost/%26state%3D%26requestvisibleactions%3Dhttp://schemas.google.com/AddActivity%26client_id%3D000.apps.googleusercontent.com%26request_visible_actions%3Dhttp://schemas.google.com/AddActivity%26hl%3Den%26from_login%3D1%26as%3D-1fbe06f1c6120f4d&ltmpl=popup&shdf=Cm4LEhF0aGlyZFBhcnR5TG9nb1VybBoADAsSFXRoaXJkUGFydHlEaXNwbGF5TmFtZRoHQ2hpa3V0bwwLEgZkb21haW4aB0NoaWt1dG8MCxIVdGhpcmRQYXJ0eURpc3BsYXlUeXBlGgdERUZBVUxUDBIDbHNvIhTeWybcoJ9pXSeN2t-k8A4SUbfhsygBMhQivAmfNSs_LkjXXZ7bPxilXgjMsQ&scc=1
As you can see from there that there is a request_visible_actions and I even added one that has no underscore in case I got the parameter wrong (requestvisibleactions).
Let me say that my app is being authenticated successfully by the API. I can get the user's profile after being authenticated and it is on the "insert moment" part that my app fails. My insert code:
var body = new Moment();
var target = new ItemScope();
target.Id = referenceId;
target.Image = image;
target.Type = "http://schemas.google.com/AddActivity";
target.Description = description;
target.Name = caption;
body.Target = target;
body.Type = "http://schemas.google.com/AddActivity";
var insert =
new MomentsResource.InsertRequest(
// this is a valid service instance as I am using this to query the user's profile
_plusService,
body,
id,
MomentsResource.Collection.Vault);
Moment result = null;
try
{
result = insert.Fetch();
}
catch (ThreadAbortException)
{
// User was not yet authenticated and is being forwarded to the authorization page.
throw;
}
catch (Google.GoogleApiRequestException requestEx)
{
// here I get a 401 Unauthorized error
}
catch (Exception ex)
{
} `
For the OAuth flow, there are two issues with your request:
request_visible_actions is what is passed to the OAuth v2 server (don't pass requestvisibleactions)
plus.moments.write is a deprecated scope, you only need to pass in plus.login
Make sure your project references the latest version of the Google+ .NET client library from here:
https://developers.google.com/resources/api-libraries/download/stable/plus/v1/csharp
I have created a project on GitHub showing a full server-side flow here:
https://github.com/gguuss/gplus_csharp_ssflow
As Brettj said, you should be using the Google+ Sign-in Button as demonstrated in the latest Google+ samples from here:
https://github.com/googleplus/gplus-quickstart-csharp
First, ensure you are requesting all of the activity types you're writing. You will know this is working because the authorization dialog will show "Make your app activity available via Google, visible to you and: [...]" below the text that starts with "This app would like to". I know you checked this but I'm 90% sure this is why you are getting the 401 error code. The following markup shows how to render the Google+ Sign-In button requesting access to Add activities.
<div id="gConnect">
<button class="g-signin"
data-scope="https://www.googleapis.com/auth/plus.login"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-clientId="YOUR_CLIENT_ID"
data-accesstype="offline"
data-callback="onSignInCallback"
data-theme="dark"
data-cookiepolicy="single_host_origin">
</button>
Assuming you have a PlusService object with the correct activity type set in data-requestvisibleactions, the following code, which you should be able to copy/paste to see it work, concisely demonstrates writing moments using the .NET client and has been tested to work:
Moment body = new Moment();
ItemScope target = new ItemScope();
target.Id = "replacewithuniqueforaddtarget";
target.Image = "http://www.google.com/s2/static/images/GoogleyEyes.png";
target.Type = "";
target.Description = "The description for the activity";
target.Name = "An example of add activity";
body.Target = target;
body.Type = "http://schemas.google.com/AddActivity";
MomentsResource.InsertRequest insert =
new MomentsResource.InsertRequest(
_plusService,
body,
"me",
MomentsResource.Collection.Vault);
Moment wrote = insert.Fetch();
Note, I'm including Google.Apis.Plus.v1.Data for convenience.
Ah it's that simple! Maybe not? I am answering my own question and consequently accept it as the answer (after a few days of course) so others having the same issue may be guided. But I will definitely up-vote Gus' answer for it led me to the fix for my code.
So according to #class answer written above and as explained on his blog the key to successfully creating a moment is adding the request_visible_actions parameter. I did that but my request still failed and it is because I was missing an important thing. You need to add one more parameter and that is the access_type and it should be set to offline. The OAuth request, at a minimum, should look like: https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/plus.login&response_type=code&redirect_uri=http://localhost/&request_visible_actions=http://schemas.google.com/AddActivity&access_type=offline.
For the complete and correct client code you can get Gus' example here or download the entire dotnet client library including the source and sample and add what I added below. The most important thing that you should remember is modifying your AuthorizationServerDescription for the Google API. Here's my version of the authenticator:
public static OAuth2Authenticator<WebServerClient> CreateAuthenticator(
string clientId, string clientSecret)
{
if (string.IsNullOrWhiteSpace(clientId))
throw new ArgumentException("clientId cannot be empty");
if (string.IsNullOrWhiteSpace(clientSecret))
throw new ArgumentException("clientSecret cannot be empty");
var description = GoogleAuthenticationServer.Description;
var uri = description.AuthorizationEndpoint.AbsoluteUri;
// This is the one that has been documented on Gus' blog site
// and over at Google's (https://developers.google.com/+/web/signin/)
// This is not in the dotnetclient sample by the way
// and you need to understand how OAuth and DNOA works.
// I had this already, see my original post,
// I thought it will make my day.
if (uri.IndexOf("request_visible_actions") < 1)
{
var param = (uri.IndexOf('?') > 0) ? "&" : "?";
description.AuthorizationEndpoint = new Uri(
uri + param +
"request_visible_actions=http://schemas.google.com/AddActivity");
}
// This is what I have been missing!
// They forgot to tell us about this or did I just miss this somewhere?
uri = description.AuthorizationEndpoint.AbsoluteUri;
if (uri.IndexOf("offline") < 1)
{
var param = (uri.IndexOf('?') > 0) ? "&" : "?";
description.AuthorizationEndpoint =
new Uri(uri + param + "access_type=offline");
}
// Register the authenticator.
var provider = new WebServerClient(description)
{
ClientIdentifier = clientId,
ClientSecret = clientSecret,
};
var authenticator =
new OAuth2Authenticator<WebServerClient>(provider, GetAuthorization)
{ NoCaching = true };
return authenticator;
}
Without the access_type=offline my code never worked and it will never work. Now I wonder why? It would be good to have some explanation.

Resources