Microsoft Teams bot sending proactive messages to each member of a team - c#

I am building a bot using the Microsoft C# bot framework v4. When the bot is first added to a team, I want it to proactively 1:1 message each member of the team, introducing itself. I know it should be possible to start a chat with a user, even if the user has not previously interacted with the bot. This process is described in the documentation:
When using proactive messaging to send a welcome message to a user you must keep in mind that for most people receiving the message they will have no context for why they are receiving it. This is also the first time they will have interacted with your app; it is your opportunity to create a good first impression. The best welcome messages will include:
Why are they receiving this message. It should be very clear to the user why they are receiving the message. If your bot was installed in a channel and you sent a welcome message to all users, let them know what channel it was installed in and potentially who installed it.
To me this indicates that I should be able to initiate a chat message with each member of the channel, but I cannot get the bot to message anyone in the channel other than me. In the TeamsConversationBot sample provided by Microsoft, there is a MessageAllMembers() method that seems to be what I am looking for, however when I call it in my code, and add the bot to a team in Teams, the bot only messages me.
Here is the MessageAllMembers() code I am using:
private async Task MessageAllMembersAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var Id = turnContext.Activity.TeamsGetChannelId();
var serviceUrl = turnContext.Activity.ServiceUrl;
var credentials = new MicrosoftAppCredentials(_configuration["MicrosoftAppId"], _configuration["MicrosoftAppPassword"]);
ConversationReference conversationReference = null;
var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);
foreach (var teamMember in members)
{
if (teamMember.Id != turnContext.Activity.Recipient.Id) {
var proactiveMessage = MessageFactory.Text($"Hello {teamMember.GivenName} {teamMember.Surname}. I'm a Teams conversation bot.");
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new ChannelAccount[] { teamMember },
TenantId = turnContext.Activity.Conversation.TenantId,
};
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
Id,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
_configuration["MicrosoftAppId"],
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
}
}
await turnContext.SendActivityAsync(MessageFactory.Text("All messages have been sent."), cancellationToken);
}
It is not throwing any exceptions, it's just not doing what I expect.

Related

Microsoft bot framework - Bot channel Registration. Unable to save the recorded video from skype to Azure storage account

For the Microsoft Bot framework chatbot application that I am working on, I have configured the "Bot Channel Registration" and have hosted it on Azure.
One of the scenarios expects the user to record a video on skype and send it as an answer. I have an Azure function that saves the recorded video from skype to the Azure Storage account.
The issue I am encountering is, When I record a video on skype ()via Video Messaging option.
To gain access to the uploaded video from skype, I am providing appropriate bearer token along with the above mentioned URL but failing to get access to it.
Though the file that is uploaded from skype to the Queue (Azure function Queue triggers), the accessibility to this file is denied.
Assuming the latest patches would help, I updated all the references to .NET core 3.0.1 as of today. Looking forward to the desired approach to resolve this.
Note: This issue is only happening in "Skype for Desktop" version.
Below is the code block for your reference.
private static async Task<HttpResponseMessage> RequestFile(string contentUrl, ILogger logger, string serviceUrl)
{
var credentials = DIContainer.Instance.GetService<MicrosoftAppCredentials>();
var token = await credentials.GetTokenAsync();
using (var connectorClient = new ConnectorClient(new Uri(serviceUrl), credentials.MicrosoftAppId, credentials.MicrosoftAppPassword))
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
var test = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseContentRead);
return test;
}
}
}
Adding more code snippets:
private async Task<(string, string)> TrySaveAndGetContentUrl(IMessageActivity activity, string user)
{
var attachments = activity.Attachments;
if (attachments?.Any() ?? false)
{
var video = attachments.First();
return (await _attachmentsService.Save(video, user), video.ContentUrl);
}
return (null, null);
}
///_attachmentsService.Save method implementation
public async Task<string> Save(Attachment attachment, string user)
{
_logger.LogInformation("Enqueue save command. {#Attachment}", attachment);
var blobName = $"{user}/{Guid.NewGuid().ToString()}-{attachment.Name}";
var blob = _cloudBlobContainer.GetBlockBlobReference(blobName);
await EnqueueSaveCommand(attachment.ContentUrl, blobName, user);
return blob.Uri.ToString();
}
Please refer the below code block to save the attachments to Azure blob.
private async Task EnqueueSaveCommand(string contentUrl, string blobName, string user)
{
var queue = _queueClient.GetQueueReference(RouteNames.MediaAttachmentQueue); //RouteNames.MediaAttachmentQueue is "media-attachment-queue"
await queue.CreateIfNotExistsAsync();
var serializedMessage = JsonConvert.SerializeObject(new SaveMediaAttachmentCommand
{
FromUrl = contentUrl,
AttachmentName = blobName,
UserName = "userid#gmail.com",
});
var queueMessage = new CloudQueueMessage(serializedMessage);
await queue.AddMessageAsync(queueMessage);
}
Please suggest.
The Skype channel configuration contains the following message:
As of October 31, 2019 the Skype channel no longer accepts new Bot publishing requests. This means that you can continue to develop bots using the Skype channel, but your bot will be limited to 100 users. You will not be able to publish your bot to a larger audience. Current Skype bots will continue to run uninterrupted. Learn more
Many Skype features have been deprecated. If it was ever possible to send a video to a bot over Skype, it may not be possible any longer. It's recommended that you switch to other channels like Direct Line and Microsoft Teams.

How to send a 'conversationUpdate' to Microsoft Teams from bot manually?

I have a bot written with the help of bot framework v4. The bot is integrated with Microsoft Teams. I want to send a welcome message to the user when the user installed the bot and joins the 1:1 conversation. In Teams the conversationUpdate is fired exactly once (this is when the suer joins the 1:1 conversation) and then never again for that user. My idea was to write a function that is triggered by a chat message to send the updateConversation activity manually to debug the welcome message.
I failed so far and got a
BadArgument: Unknown activity type exception.
I have tried using the Microsoft.Bot.Builder.Teams nuget using the ConnectorClient to send the conversationUpdate activity to the conversation.
Also I set up a console application and tried using the v3/directline/conversations/{conversationId}/activities and got a Forbidden error.
private async Task SendConversationUpdateToTeamsAsync(ITurnContext turnContext, CancellationToken cToken = default)
{
var connectorClient = turnContext.TurnState.Get<IConnectorClient>();
var conversationUpdateMessage = new Activity
{
Type = ActivityTypes.ConversationUpdate,
Id = turnContext.Activity.Id,
ServiceUrl = turnContext.Activity.ServiceUrl,
From = turnContext.Activity.From,
Recipient = turnContext.Activity.Recipient,
Conversation = turnContext.Activity.Conversation,
ChannelData = turnContext.Activity.ChannelData,
ChannelId = turnContext.Activity.ChannelId,
Timestamp = turnContext.Activity.Timestamp,
MembersAdded = new List<ChannelAccount>
{
turnContext.Activity.From,
turnContext.Activity.Recipient
},
};
var result = await connectorClient.Conversations.SendToConversationAsync(conversationUpdateMessage, cToken);
}
I expect that sending a conversationUpdate manually to debug the behavior in Teams works. Creating new users in the office portal and installing the bot for them to debug the conversationUpdate behavior is no option for me, because it is to time consuming. If there is another workaround to trigger the conversationUpdate in Teams please let me know.
I'm not sure of a way to force a ConversationUpdate to be sent in the way you're attempting to. Instead, I'd just throw something like this in OnMessageAsync():
if (turnContext.Activity.Text == "fakeConversationUpdate")
{
var fakeTurnContext = new TurnContext(turnContext.Adapter, MessageFactory.Text(string.Empty));
fakeTurnContext.Activity.AsConversationUpdateActivity();
fakeTurnContext.Activity.Type = ActivityTypes.ConversationUpdate;
fakeTurnContext.Activity.MembersAdded = new List<ChannelAccount>()
{
new ChannelAccount()
{
Id = "fakeUserId",
Name = "fakeUserName"
}
};
await OnConversationUpdateActivityAsync(new DelegatingTurnContext<IConversationUpdateActivity>(fakeTurnContext), cancellationToken);
}
Then to debug, you just write "fakeConversationUpdate" (which you can change/customize) to the bot in chat and it will send your fakeTurnContext (which you can change/customize) through OnConversationUpdateActivityAsync()

Unable to update previous messages after reconnection

I created a bot with a command, that allows the user to configure some sort of 'feed' to their channel.
This feed is supposed to send a message, save guild, channel and message id. And in a stand-alone update cycle, try to update the message with new information.
This all works fairly well, as long as it is within the same session.
Say the bot losses it's connection due to a discord outage, and re-connects x amount of time later, the bot no longer seems to be able to find, and thus update the message anymore.
In particular, it seems to be unable to retrieve the message by id
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
It's worth to note that I make use of _settings which is persisted in json format, and is loaded again upon bot reboot.
I also confirmed that the message still exists in the server at the channel, with the same message id. And that the bot has permissions to view the message history of the channel.
Thus my question, how come the GetMessageAsync is unable to retrieve a previously posted message after reconnecting?
Initialy invoked command
public async Task BindPlaytimeFeedAsync(ICommandContext context)
{
var builder = await _scumService.GetTop25PlaytimeByDate(new DateTime(), DateTime.Now);
var message = await context.Channel.SendMessageAsync(null, false, builder.Build());
_settings.PlaytimeFeed = new MessageInfo()
{
GuildId = context.Guild.Id,
ChannelId = context.Channel.Id,
MessageId = message.Id,
};
var ptFeedMessage = await context.Channel.SendMessageAsync("Playtime feed is now bound to this channel (this message self-destructs in 5 seconds)");
await Task.Delay(5000);
await ptFeedMessage.DeleteAsync();
}
The refresh interval of the feed is defined alongside the bot itself using a timer as seen below.
...
_client = new DiscordSocketClient(
new DiscordSocketConfig
{
LogLevel = LogSeverity.Verbose,
AlwaysDownloadUsers = true, // Start the cache off with updated information.
MessageCacheSize = 1000
}
);
_service = ConfigureServices();
_feedInterval = new Timer(async (e) =>
{
Console.WriteLine("doing feed stuff");
await HandleFeedsAsync();
}, null, 15000, 300000);
CmdHandler = new CommandHandler(_service, state);
...
private async Task HandleFeedsAsync()
{
var botSettings = _service.GetService<ISettings>() as BotSettings;
await HandleKdFeedAsync(botSettings.KdFeed);
await HandlePlaytimeFeedAsync(botSettings.PlaytimeFeed);
await HandleWeeklyPlaytimeFeed(botSettings.WeeklyPlaytimeFeed);
await HandleAdminFeed(botSettings);
}
And ultimately the message is overwritten using the below snippet.
private async Task HandlePlaytimeFeedAsync(MessageInfo playtimeFeed)
{
if (playtimeFeed == null)
return;
var scumService = _service.GetService<ScumService>();
var guild = _client.GetGuild(playtimeFeed.GuildId);
var channel = guild.GetTextChannel(playtimeFeed.ChannelId);
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
if (message == null)
return;
var builder = await scumService.GetTop25PlaytimeByDate(new DateTime(), DateTime.Now);
await message.ModifyAsync(prop =>
{
prop.Embed = builder.Build();
});
}
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
The GetMessageAsync method attempts to retrieve a message from cache as a SocketUserMessage, if however the message is not found in cache, a rest request is performed which would return a RestUserMessge. By performing a soft cast on the result of GetMessageAsync, you can get null if/when a RestUserMessage is returned.
When the possibility exists that the message you are dealing with can be either a Socket entity or Rest entity, simply use the interface to interact with it -- IUserMessage.

Bot Framework - Bot initiates the conversation on Skype

I want my bot to display an introductory message when a user begins a new conversation. I've seen this working with bots in Skype where the bot sends a message before the user types anything.
I have got this working using the Bot Framework Channel Emulator with this code in the MessagesController class:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
await this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate)
{
var reply = message.CreateReply("Hello World!");
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
This displays 'Hello World!' at the beginning of a new conversation. No input required. However on Skype this introductory message does not appear. What I am misunderstanding here? I know it is possible.
Skype is throwing different ActivityTypes given the situation:
You will get a contactRelationUpdate after adding the bot in your contacts. Then we you start talking to the bot, there is no special Activity
When you start a conversation group with the bot included, you will get conversationUpdate
So if you want to welcome your user, you should add the contactRelationUpdate activity type in your test, like:
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate || message.Type == ActivityTypes.ContactRelationUpdate)
{
var reply = message.CreateReply("Hello World!");
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
Extract of the content of the message you receive when adding the bot:
Here From is my user and Recipient is bot. You can see that the Action value is add

How to create multiple dialogs in bot using MS bot framework so that bot remembers which dialog is in progress

I'm using MS Bot Framework and C# to build a bot that can handle 3 dialogs. Each dialog is built using the FormDialog and FormBuilder, like this:
internal static IDialog<OrderDialogForm> BuildDialog()
{
return Chain.From(() => FormDialog.FromForm(BuildForm));
}
When you first talk to the bot, it offers you to select one of the three dialogs, e.g. "fill in the order", "enter your user profile", "get support",
Once the users selects, for example, "fill in the order", the bot launches the appropriate dialog.
Obviously, the user should just continue answering the questions inside the dialog until the dialog is over.
But every time the user sends a message, it is passed to this method in the API controller:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
From here, the bot needs to decide which of the three dialogs is currently in progress, and continue that dialog.
How do I do that, remember which dialog is currently in progress and with each new message from the user, continue that dialog instead of returning the user to the main screen?
My idea is to create some kind of global variable or a record that is stored somewhere else, maybe in the database. The record would contain the type of the current dialog that this user is having with the bot right now. Every time the bot receives a message, it would query the database to find out that the last interaction of the user was with the OrderDialog, and so the program code can decide to continue with the OrderDialog. But it seems slow and maybe there is some kind of built-in function in Bot Framework to store data about the user, such as which dialog type it last interacted with.
Use the Bot State Service
https://docs.botframework.com/en-us/csharp/builder/sdkreference/stateapi.html
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
// Detect if this is a Message activity
if (activity.Type == ActivityTypes.Message)
{
// Get any saved values
StateClient sc = activity.GetStateClient();
BotData userData = sc.BotState.GetPrivateConversationData(
activity.ChannelId, activity.Conversation.Id, activity.From.Id);
var boolProfileComplete = userData.GetProperty<bool>("ProfileComplete");
if (!boolProfileComplete)
{
// Call our FormFlow by calling MakeRootDialog
await Conversation.SendAsync(activity, MakeRootDialog);
}
else
{
// Get the saved profile values
var FirstName = userData.GetProperty<string>("FirstName");
var LastName = userData.GetProperty<string>("LastName");
var Gender = userData.GetProperty<string>("Gender");
// Tell the user their profile is complete
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("Your profile is complete.\n\n");
sb.Append(String.Format("FirstName = {0}\n\n", FirstName));
sb.Append(String.Format("LastName = {0}\n\n", LastName));
sb.Append(String.Format("Gender = {0}", Gender));
// Create final reply
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity replyMessage = activity.CreateReply(sb.ToString());
await connector.Conversations.ReplyToActivityAsync(replyMessage);
}
}
else
{
// This was not a Message activity
HandleSystemMessage(activity);
}
// Send response
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Sample From:
Introduction To FormFlow With The Microsoft Bot Framework
http://aihelpwebsite.com/Blog/EntryId/8/Introduction-To-FormFlow-With-The-Microsoft-Bot-Framework

Categories

Resources