I am working on creating a MSTeams BOT. I am using Microsoft.Bot.Builder(4.15.2), Microsoft.Bot.Connector (4.15.2) to send a message to the 1000 bot users asynchronously using the below code.
public async Task SendMessage(string tenantId, ConnectorClient connectorClient, UserDetails user, Activity personalMessageActivity)
{
ChannelAccount member = GetChannelAccount(user.id, user.name);
ConversationParameters conversationParameters = GetConversationParameters(tenantId, member);
string conversationId = user.conversationId;
var resp = await connectorClient.Conversations.SendToConversationAsync(user.conversationId, personalMessageActivity);
}
public async Task SendMessageToUsers(ProactiveMessage proactiveMessage, string tenantId, ConnectorClient connectorClient, UserDetails member, Activity personalMessageActivity)
{
var result = await SendMessage(tenantId, connectorClient, member, personalMessageActivity);
}
Below is the for loop to send message to 1000 users
List<Task> TaskList = new List<Task>();
foreach (UserDetails member in allMembersArr)
{
TaskList.Add(SendMessageToUsers (proactiveMessage, tenantId, connectorClient, member, personalMessageActivity));
}
await Task.WhenAll(TaskList);
It sent a message to few users (approx 600 users) successfully but for the remaining users, I am getting an error “A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.”.
I tried to use multithreading and asynchronous programming but it still fails after sending a messages to approx 600 users.
Below is the error log :
enter image description here
Update
We tried sending the message from Node Js but we are still not able to send 1000 messages from our bot. We get timeout error from the library for random user on every run. Here is the main code that we are using to send the messages:
const sendMessage = (membersArry, activity) => {
membersArry.forEach((member) => {
connectorClient.conversations.sendToConversation(
member.conversationId,
activity
);
});
}
Library Used:
"botbuilder": "^4.15.0",
"botframework-connector": "^4.15.0",
It sent a message to a few users successfully but for the remaining users, I am getting an error "Error: connect ETIMEDOUT 52.xx.xxx.x:443"
Note:- We are using the Azure Bot with pricing tier - Standard S1
Related
I have created a one way notification only bot in Teams (only personal scope), I am able to send proactive messages but however, when someone reacts to a message, Teams is showing a notification for the message which was reacted to. How do I prevent this behavior and just silently ignore the message reaction. I was hoping since it's a one way notification bot, there would be an option to disable it, but apparently there isn't.
I have a PHP REST API endpoint which is configured to be the bot endpoint address. This API is pretty basic and handles only certain types of requests like installationUpdate. For all other types, it just sends a HTTP 200 response with an empty body.
When the user first installs the App in teams, I am storing the conversationId, tenantId and the serviceUrl and later use these values to send notifications (proactive messages) when certain events happen in a web application. These are sent via a C# Console Application.
When a user reacts to a message, I get a request with the type messageReaction, this is where I am unable to figure out how to handle this so that the message reaction is ignored and does not cause a notification in Teams.
This is what my PHP REST API (bot endpoint) looks like
function onBotRequest() {
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(404);
return;
}
$requestJson = json_decode(file_get_contents('php://input'), true);
if ($requestJson['channelId'] != 'msteams') {
http_response_code(404);
return;
} elseif ($requestJson['type'] == 'installationUpdate') {
$serviceUrl = $requestJson["serviceUrl"];
$conversationId = $requestJson["conversation"]["id"];
$tenantId = $requestJson["conversation"]["tenantId"];
if ($requestJson['action'] == 'add') {
// App installed
// Store conversationId, tenantId, serviceUrl in db
} elseif($requestJson['action'] == 'remove') {
// App uninstalled
// Remove conversationId, tenantId, serviceUrl from db
}
} elseif ($requestJson['type'] == 'messageReaction') {
// What should be sent as the response here to ignore the message reaction?
}
header('Content-Type: application/json');
http_response_code(200);
}
The code used for sending proactive messages
var credentials = new MicrosoftAppCredentials(appId, appPassword);
var connectorClient = new ConnectorClient(new Uri(serviceUrl), credentials);
var response = await connectorClient.Conversations.SendToConversationAsync(conversationId, activity);
I tried sending different HTTP status codes like 400 but irrespective of the response status code, the notification still occurs. I guess I am missing some required params in the response body, but I couldn't find any documentation.
Removing the call to TeamsNotifyUser will prevent Teams sending notifications when message reactions are added/removed.
I have developed a bot which sends Proactive messages to user
I also cretaed bots which trigger skills.
I was trying to write something where a skills bot/Dialog would be able to send proactive message received via webhooks to the user and continue with the existing Skill dialog.
I am not able to quite understand that. If anyone could help me there.
I used the sample from here to create a simple Skill bot which saves the ConversationReference of the current Activity and calls a service in OnMessageActivityReceived()
// Save ConversationReference
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
// Calling external service
HttpResponseMessage responsemMsg = await client.PostAsync(RequestURI, stringContent);
if (responsemMsg.IsSuccessStatusCode)
{
var apiResponse = await responsemMsg.Content.ReadAsStringAsync();
//Post the API response to bot again
await turnContext.SendActivityAsync(MessageFactory.Text($"Message Sent {turnContext.Activity.Text}"), cancellationToken);
}
The called service eventually emits an event which calls an action in the NotifyController in my Skills Bot. It tries to grab the ConverstaionReference and send the activity using the TurnContext.
//I am using the Skill Bot Id for _appId parameter
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
{
await context.SendActivityAsync(proMsg.eventName);
await context.SendActivityAsync(proMsg.context.ToString());
}, new System.Threading.CancellationToken());
This SendActivity fails and OnTurnError from the Skill Bot handles the exception.
I am not sure where excatly I am going wrong. I am new to the Bot framework learning.
My issue got resolved by using the ContinueConversation() overload with ClaimsIdentity and setting the right claims for audience, appid etc. It was basically authentication issue.
public virtual Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, ConversationReference reference, string audience, BotCallbackHandler callback, CancellationToken cancellationToken);
This is how I created the ClaimsIdentity:
System.Collections.Generic.List<Claim> lclaim = new System.Collections.Generic.List<Claim>
{
new Claim(AuthenticationConstants.VersionClaim, "2.0"),
new Claim(AuthenticationConstants.AudienceClaim, <SkillBotId>),
new Claim(AuthenticationConstants.AppIdClaim, <SkillBotId>),
new Claim(AuthenticationConstants.AuthorizedParty, <SkillBotId>),
};
ClaimsIdentity ci = new ClaimsIdentity(lclaim);
async Task BotCallBack(ITurnContext turnContext, CancellationToken token)
{
<code to send activity back to parent bot>
}
await ((BotAdapter)this.botFrameworkHttpAdapter).ContinueConversationAsync(
ci,
conversationData.ConversationReference,
<ParentBotId>,
callback: BotCallBack,
default).ConfigureAwait(false);
I have a MS Teams bot. I have installed the bot for couple of users in tenant. Now when I'm starting conversation, for few users it is responding and few it is not.
I investigated further and found out that for users who are getting reply from bot, the serviceurl is "https://smba.trafficmanager.net/in/".
For users who are not getting reply from bot, the serviceurl is "https://smba.trafficmanager.net/apac/".
Exception message: Operation returned an invalid status code 'NotFound'
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var reply = activity.CreateReply();
reply.Text = "Hi there";
await context.PostAsync(reply);
}
This sounds like it's possibly a TrustServiceUrl Issue (despite the 500 vs 401 error message).
You can fix it by adding all of your ServiceUrls to the list of trusted URLs:
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var serviceUrl = activity.ServiceUrl;
MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);
var reply = activity.CreateReply();
reply.Text = "Hi there";
await context.PostAsync(reply);
}
This should ensure that your bot "trusts" the ServiceUrl of any message that it receives.
Let me know how that goes. I'm 90% sure this is the issue, but it might not be.
Here's a link to the library, if that helps. Otherwise, browsing these issues should help.
Note to others:
This "Trust Service URL Issue" doesn't apply to just Teams. This happens for lots of other URLs when trying to use Proactive messaging. Just replace serviceUrl with whatever is appropriate for your use case. And yes, if you're using multiple channels, you can add multiple URLs when using MicrosoftAppCredentials.TrustServiceUrl() by calling it multiple times.
Here's the method definition. Note: you can add expiration for this, as well.
I've submitted a PR for this, which so far has resulted in some updated docs
How can I send a message to the user without the user sending me a message? Like for example CNN bot is sending messages every day in the morning by itself. How can I do that in the bot framework?
See this.
In fact, you do not strictly need to receive a message from the user first, but addressing manually can be error-prone (you have to know the user's and bot's channel account, the service URL, etc.)
And in turn (per #thegaram's message), that only works for some channels. For example, Skype requires that the user contact the bot before the bot can message the user.
Once contacted, you can store the user's channelAccount data once they contact you and use that to send them proactive messages. For example if the user has subscribed to hear sports scores for a particular team over time.
Any sort of unsolicited spam messages of course are prohibited by the policies of the Bot Framework (and most of the channels).
Yes you can do that. We called it Greeting from Bot. I have done it and sharing a sample code with you.
Write this code in your messageController or first dialog used in bot.
if (activity.Text == null)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isActivityTyping = activity.CreateReply();
isActivityTyping.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isActivityTyping);
await Conversation.SendAsync(activity, () => new Dialogs.GreetDialog());
}
after this code you need to create a dialog GreetDialog. Below is the cs file code for your reference.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace GPP.Bot.Dialogs
{
[Serializable]
internal class GreetDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(Greeting);
}
private async Task Greeting(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
if (string.IsNullOrEmpty(message.Text))
{
// Hero Card
var cardMsg = context.MakeMessage();
var attachment = BotWelcomeCard("Hello, I am a bot. Right now I am on training and in a prototype state", "");
cardMsg.Attachments.Add(attachment);
await context.PostAsync(cardMsg);
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
else
{
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
}
private static Attachment BotWelcomeCard(string responseFromQNAMaker, string userQuery)
{
var heroCard = new HeroCard
{
Title = userQuery,
Subtitle = "",
Text = responseFromQNAMaker,
Images = new List<CardImage> { new CardImage("https://i2.wp.com/lawyerist.com/lawyerist/wp-content/uploads/2016/08/docubot.gif?fit=322%2C294&ssl=1") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.ImBack, "Show Menu", value: "Show Bot Menu") }
};
return heroCard.ToAttachment();
}
private async Task AfterGreetingDialogCompleted(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(new object());
}
}
}
this is a working code. Do let me know in case you face ant issue.
~cheers :)
Updated
I am developing a Skype bot with 1:1 conversation with Bot Framework.
In that I have a WebHook method which will call from an external service and sends message to my bot, then my bot will send that message to a skype user.
The following code is for v1 in message controller along with api/messages post method
public async Task<Message> Post([FromBody]Message message){}
[Route("~/api/messages/hook")]
[HttpPost]
public async Task<IHttpActionResult> WebHook([FromBody]WebHookMessage message)
{
if (message.Type == "EmotionUpdate")
{
const string fromBotAddress = "<Skype Bot ID here>";
const string toBotAddress = "<Destination Skype name here>";
var text = resolveEmoji(message.Data);
using (var client = new ConnectorClient())
{
var outMessage = new Message
{
To = new ChannelAccount("skype", address: toBotAddress , isBot: false),
From = new ChannelAccount("skype", address: $"8:{fromBotAddress}", isBot: true),
Text = text,
Language = "en",
};
await client.Messages.SendMessageAsync(outMessage);
}
}
return Ok();
}
I will call above WebHook from another service, so that my bot will send messages to the respective skype user.
Can anyone please help me how can I achieve the same in V3 bot framework?
I tried the following but not working
const string fromBotAddress = "Microsoft App ID of my bot";
const string toBotAddress = "skype username";
WebHookMessage processedData = JsonConvert.DeserializeObject<WebHookMessage>(message);
var text = resolveEmoji(processedData.Data);
using (var client = new ConnectorClient(new Uri("https://botname.azurewebsites.net/")
, "Bot Microsoft App Id", "Bot Microsoft App secret",null))
{
var outMessage = new Activity
{
ReplyToId = toBotAddress,
From = new ChannelAccount("skype", $"8:{fromBotAddress}"),
Text = text
};
await client.Conversations.SendToConversationAsync(outMessage);
}
But it is not working, finally what I want to achieve is I want my bot send a message to a user any time how we will send message to a person in skype.
The following code works, but there are some things that are not that obvious that I figured out (tested on Skype channel)
When a user interacts with the bot the user is allocated an id that can only be used from a specific bot..for example: I have multiple bots each using a skype channel. When I send a message from my skype user to bot A the id is different than for bot B. In the previous version of the bot framework I could just send a message to my real skype user id, but not anymore. In a way it simplifies the whole process because you only need the recipient's id and the framework takes care of the rest, so you don't have to specify a sender or bot Id (I guessed all that is linked behind the scenes)
[Route("OutboundMessages/Skype")]
[HttpPost]
public async Task<HttpResponseMessage> SendSkypeMessage(SkypePayload payload)
{
using (var client = new ConnectorClient(new Uri("https://skype.botframework.com")))
{
var conversation = await client.Conversations.CreateDirectConversationAsync(new ChannelAccount(), new ChannelAccount(payload.ToSkypeId));
IMessageActivity message = Activity.CreateMessageActivity();
message.From = new ChannelAccount();
message.Recipient = new ChannelAccount(payload.ToSkypeId);
message.Conversation = new ConversationAccount { Id= conversation.Id };
message.Text = payload.MessageBody;
await client.Conversations.SendToConversationAsync((Activity)message);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
I'm not sure I understand what you're trying to do. If you'd like to answer a message (activity), try something like this:
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var reply = activity.createReply(text, "en");
await connector.Conversations.ReplyToActivityAsync(reply);
Activity.createReply switches the From and Recipient fields from the incoming activity. You can also try setting these field manually.
UPDATE
You need to create a ConnectorClient to the Skype Connector Service, not to your bot! So try with the Uri http://skype.botframework.com it might work.
However, I don't think you can message a user on Skype without receiving a message from it in the first place (i.e. your bot needs to be added to the user's contacts). Once you have an incoming message from the user, you can use it the create replies, just as described above.
WebHookMessage processedData = JsonConvert.DeserializeObject<WebHookMessage>(message);
var text = resolveEmoji(processedData.Data);
var client = new ConnectorClient(new Uri(activity.serviceUrl));
var outMessage = activity.createReply(text);
await client.Conversations.SendToConversationAsync(outMessage);
activity is a message received from the given user earlier. In this case, activity.serviceUrl should be http://skype.botframework.com, but generally you should not rely on this.
You can try to create the activity (outMessage) manually; for that, I'd recommend inspecting the From and Recipient fields of a message coming from a Skype user and setting these fields accordingly. However, as mentioned before, your bot needs to be added to the user's contacts, so at this point it will have received a message from the user.