MS Teams: How to initiate a private chat/conversation to a user from a c# backend - botframework

I currently try to send a private notification to a user.
What I currently have is a connected bot and connector in my MS Teams environment.
In my c# backend I have the bot which inherit from the ActivityHandler, the tenant id and the user id of ms teams.
The case is now:
Someone is creating an object (for example a Task) in my backend by making an API Post call and I want to notify the user in ms teams.
My idea was now to instantiate the Bot in my API controller (including the ITurnContext). Then with the bot find the right ms teams environment with the tenant id and user id, create a new chat/conversation and send the message. But I guess this is not the right way or I do something wrong. Because I think there is no way to initialise the ITurnContext from my code or?
This is my code and my idea was to use the CreatePrivateConversation method in my API controller.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Teams;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Schema.Teams;
namespace IntegrationsService.Bots
{
public class ProactiveBot : ActivityHandler
{
double _secondsToReply = 3;
ICredentialProvider _credentialProvider;
public ProactiveBot(ICredentialProvider credentialProvider)
{
_credentialProvider = credentialProvider;
}
public async Task<ConversationResourceResponse> CreatePrivateConversation(string message, ITurnContext turnContext)
{
ConnectorClient _client = new ConnectorClient(
new Uri(turnContext.Activity.ServiceUrl),
await GetMicrosoftAppCredentialsAsync(turnContext),
new HttpClient());
var channelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
var conversationParameter = new ConversationParameters
{
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("userid") },
// IsGroup = true,
ChannelData = channelData,
TenantId = channelData.Tenant.Id,
Activity = MessageFactory.Text(message)
};
var response = await _client.Conversations.CreateConversationAsync(conversationParameter);
return response;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
try
{
await turnContext.SendActivityAsync(MessageFactory.Text($"I'll reply to you in {_secondsToReply} seconds."));
QueueReplyAndSendItProactively(turnContext).Wait();
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw e;
}
}
public async Task QueueReplyAndSendItProactively(ITurnContext turnContext)
{
string conversationMessage = "I created my own conversation.";
string replyMessage = "I proactively replied to this conversation.";
var task = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(_secondsToReply));
// Let the Bot Proactively create a a conversation.
var response = await CreateConversation(conversationMessage, turnContext);
// Reply to the conversation which the bot created.
await ProactivelyReplyToConversation(response.Id, replyMessage, turnContext);
return Task.CompletedTask;
});
await task;
}
public async Task<ConversationResourceResponse> CreateConversation(string message, ITurnContext turnContext)
{
ConnectorClient _client = new ConnectorClient(new Uri(turnContext.Activity.ServiceUrl), await GetMicrosoftAppCredentialsAsync(turnContext), new HttpClient());
var channelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
var conversationParameter = new ConversationParameters
{
Bot = turnContext.Activity.Recipient,
IsGroup = true,
ChannelData = channelData,
TenantId = channelData.Tenant.Id,
Activity = MessageFactory.Text(message)
};
var response = await _client.Conversations.CreateConversationAsync(conversationParameter);
return response;
}
public async Task ProactivelyReplyToConversation(string conversationId, string message, ITurnContext turnContext)
{
ConnectorClient _client = new ConnectorClient(new Uri(turnContext.Activity.ServiceUrl), await GetMicrosoftAppCredentialsAsync(turnContext), new HttpClient());
var reply = MessageFactory.Text(message);
reply.Conversation = new ConversationAccount(isGroup: true, id: conversationId);
await _client.Conversations.SendToConversationAsync(reply);
}
private async Task<MicrosoftAppCredentials> GetMicrosoftAppCredentialsAsync(ITurnContext turnContext)
{
ClaimsIdentity claimsIdentity = turnContext.TurnState.Get<ClaimsIdentity>("BotIdentity");
Claim botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)
??
claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AppIdClaim);
string appPassword = await _credentialProvider.GetAppPasswordAsync(botAppIdClaim.Value).ConfigureAwait(false);
return new MicrosoftAppCredentials(botAppIdClaim.Value, appPassword);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Hello and Welcome!"), cancellationToken);
}
}
}
}
}
Thanks for the help.

There is no way to initialize the ITurnContext from code.
So If you want to send a private proactive message to user, You need to follow below steps
"OnConversationUpdateActivityAsync" method will be called when user install bot. You can get required parameters like ConversationId, ServiceUrl from turnContext.Activity.From object. you can store these details in database when each user installs the bot.
OnConversationUpdateActivityAsync(
ITurnContext turnContext,
CancellationToken cancellationToken)
When user makes an POST API call, you need to pass user id or AadID based on which you can query database and get user details which are needed to send proactive message. Now you can call "CreatePrivateConversation" method and send the proactive message

Related

How to call api vs parameters cancellationToken xamarin forms

I am building an operation that calls api with the cancellationToken parameter. cancel () from event A, when the party B is calling, the api will be caught, but I want this exception to be arrested on the api side. ..... help, it took me 4 days but it didn't work out
api server
public async Task<Actionresult> getText(CancellationToken token){ string s= "";try{for(int i=0;i<10;i++)
{
a += " number "+i;
await Task.Delay(1000,token);
}
}catch(Excaption ex){
return badrequest("canceled")
}
}
client xamaarin app
CancellationTokenSource cts = new CancellationTokenSource();
private async commandEventA()
{
cts.Cancel();
}
private async commandEventB()
{
var token =cts.Token();
string txtkq ="";
var client = new RestClient("urlwebb");
RestRequest request = new RestRequest("");
try{
var a = await
client.ExcuteTaskAsync(request,token);
}catch(TaskCancellationException ex){}
}

how to delete the posted adaptive messge in MSteams using c# code

the below code is getting error while deleting the message based on the channel id
public async Task DeleteSentNotification(
string conversationId,
string recipientId,
string serviceUrl,
string tenantId,
string name)
{
// Set the service URL in the trusted list to ensure the SDK includes the token in the request.
MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);
var conversationReference = new ConversationReference
{
ServiceUrl = serviceUrl,
Conversation = new ConversationAccount
{
TenantId = tenantId,
Id = conversationId,
name = name,(AdaptiveCard Json)
},
};
await this.botAdapter.ContinueConversationAsync(
botAppId: this.microsoftAppId,
reference: conversationReference,
callback: async (turnContext, cancellationToken) =>
{
try
{
// Delete message.
await turnContext.DeleteActivityAsync(conversationReference);
}
catch (ErrorResponseException e)
{
var errorMessage = $"{e.GetType()}: {e.Message}";
}
},
cancellationToken: CancellationToken.None);
// return response;
}
DeleteActivityAsync has two overloads, but they're both basically aimed towards deleting an "Activity" (i.e. a specific message or thread). If you want to use the overload that takes in a conversationReference, then that means your conversationReference needs to have the ActivityId set (see here for more on the property). Alternatively, you can use the overload that takes in an activityId (it looks like this: public System.Threading.Tasks.Task DeleteActivityAsync (string activityId, System.Threading.CancellationToken cancellationToken = default);), and then you need to pass in the activityId.
Essentially, as stated at the beginning of my answer, you're telling it to delete an "Activity", but you're not telling it which Activity. See here for the docs: https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.turncontext.deleteactivityasync?view=botbuilder-dotnet-stable

Microsoft teams - Get list of all the members in that team using bot (node js)

I am developing a bot in microsoft teams using node js sdk 4. I have installed the app in a team and i want to know the list of members in that team using my bot. I have tried this code (below) but only getting data of only one member(myself) .
async getAllMembers(context) {
var continuationToken;
var members = [];
do {
var pagedMembers = await TeamsInfo.getPagedMembers(context,10, continuationToken); //return my data only
continuationToken = pagedMembers.continuationToken;
members.push(...pagedMembers.members);
} while (continuationToken !== undefined);
for (var i = 0; i < members.length; i++) {
console.log(members[i]);
}
return members;
};
Could you please try getting roster for the Team. Here is the code sample:
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (turnContext, next) => {
const teamDetails = await TeamsInfo.getTeamDetails(turnContext);
if (teamDetails) {
await turnContext.sendActivity(`The group ID is: ${teamDetails.aadGroupId}`);
} else {
await turnContext.sendActivity('This message did not come from a channel in a team.');
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
The same was happening to me. The reason was that I was sending a direct message to the bot.
The code is supposed to be used inside a Teams channel context. That way, the bot has access to all the team members that belong to that channel.
private async Task MessageAllMembersAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var teamsChannelId = turnContext.Activity.TeamsGetChannelId();
var serviceUrl = turnContext.Activity.ServiceUrl;
var credentials = new MicrosoftAppCredentials(_appId, _appPassword);
ConversationReference conversationReference = null;
var members = await GetPagedMembers(turnContext, cancellationToken);
...

Dialog Continuation Issue on Bot Framework V4

I want to start a user dialog right after a welcome message has been displayed in my bot - without any initial user interaction.
Code snippet to accomplish that:
public RootDialogBot(BotServices services, BotAccessors accessors, ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new System.ArgumentNullException(nameof(loggerFactory));
}
_logger = loggerFactory.CreateLogger<RootDialogBot>();
_accessors = accessors ?? throw new System.ArgumentNullException(nameof(accessors));
_botServices = services ?? throw new System.ArgumentNullException(nameof(services));
_studentProfileAccessor = _accessors.UserState.CreateProperty<StudentProfile>("studentProfile");
if (!_botServices.QnAServices.ContainsKey("QnAMaker"))
{
throw new System.ArgumentException($"Invalid configuration. Please check your '.bot' file for a QnA service named QnAMaker'.");
}
if (!_botServices.LuisServices.ContainsKey("LUIS"))
{
throw new System.ArgumentException($"Invalid configuration. Please check your '.bot' file for a Luis service named LUIS'.");
}
.Add(new Activity2MainDialog(_accessors.UserState, Activity2MainDialog))
.Add(new Activity2LedFailToWorkDialog(_accessors.UserState, Activity2LedFailToWorkDialog));
}
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
...
if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
{
if (turnContext.Activity.MembersAdded != null)
{
// Save the new turn count into the conversation state.
await _accessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
await _accessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
var message = "Welcome!";
await SendWelcomeMessageAsync(turnContext, dc, message,cancellationToken); //Welcome message
}
}
...
}
private static async Task SendWelcomeMessageAsync(ITurnContext turnContext, DialogContext dc,string message, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(message, cancellationToken: cancellationToken);
await dc.BeginDialogAsync(Activity2MainDialog, "activity2MainDialog", cancellationToken);
}
}
}
The dialog (Activity2MainDialog) works fine until it reaches a return await stepContext.ContinueDialogAsync(cancellationToken); call.
Then it halts.
I believe it has something to do with the conversation state but I still couldn't find a solution for that.
Code snippet of the return await stepContext.ContinueDialogAsync(cancellationToken); call
public class Activity2MainDialog : ComponentDialog
{
private static BellaMain BellaMain = new BellaMain();
private static FTDMain FTDMain = new FTDMain();
private readonly IStatePropertyAccessor<StudentProfile> _studentProfileAccessor;
...
public Activity2MainDialog(UserState userState, string dialogMainId)
: base(dialogMainId)
{
InitialDialogId = Id;
_studentProfileAccessor = userState.CreateProperty<StudentProfile>("studentProfile");
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
MsgWelcomeStepAsync
...
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
AddDialog(new WaterfallDialog(dialogMainId, waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
}
private async Task<DialogTurnResult> MsgWelcomeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync("**Oi**", "Oi", cancellationToken: cancellationToken);
return await stepContext.ContinueDialogAsync(cancellationToken);
}
private async Task<DialogTurnResult> QuestGoAheadStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
message = "Vamos nessa?";
await stepContext.Context.SendActivityAsync(message , message , cancellationToken: cancellationToken);
retryPromptMessage = message;
return await stepContext.PromptAsync(nameof(ChoicePrompt),
new PromptOptions
{
Prompt = null,
RetryPrompt = MessageFactory.Text(retryPromptMessage, retryPromptSpeakMessage), InputHints.ExpectingInput),
Choices = new[]
{
new Choice {Value = "Sim", Synonyms = new List<string> {"yes","yeah","esta","ta","esta","ok","vamos","curti","curtir","claro","tá","sei","top"}},
new Choice {Value = "Não", Synonyms = new List<string> {"no"}}
}.ToList(),
Style = ListStyle.Auto
});
}
Thoughts on how to fix it? Thx
I'm fairly certain the issue is the ContinueDialog call. You need to end that step with:
return await stepContext.NextAsync(null, cancellationToken);
See CoreBot for more example code.
If this doesn't fix your issue, let me know and I'll adjust my answer.

How to pass the prompt values from maindialog to dispatcher?

`namespace Microsoft.BotBuilderSamples
{
public class DispatchBot : ActivityHandler
{
private ILogger _logger;
private IBotServices _botServices;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger)
{
_logger = logger;
_botServices = botServices;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
// Next, we call the dispatcher with the top intent.
await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
const string WelcomeText = "I am here to make your bot experience much more easier";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Hi {member.Name}, I am your IT assistant at your service . {WelcomeText}"), cancellationToken);
}
}
}
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "l_us_bot":
await ProcessHomeAutomationAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "t_abs-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
_logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
break;
}
}
private Activity CreateResponse(IActivity activity, Attachment attachment)
{
var response = ((Activity)activity).CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private async Task ProcessHomeAutomationAsync(ITurnContext<IMessageActivity> turnContext, LuisResult luisResult, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessHomeAutomationAsync");
// Retrieve LUIS result for Process Automation.
var result = luisResult.ConnectedServiceResult;
var topIntent = result.TopScoringIntent.Intent;
var entity = result.Entities;
if (topIntent == "welcome")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hi,This is your IT assistant"), cancellationToken);
}
if (topIntent == "None")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry I didnt get you!"), cancellationToken);
}
if (topIntent == "DateTenure")
{
// Here i want to call my dialog class
}
}
}
private async Task ProcessSampleQnAAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
`I have a scenario as described:
When user says hi, the bot shows the adaptive cards (with Action.Submit on each card). ----Suppose the cards are as: 1. Book tickets 2. Need Help
3. Cancel
whenever user click any of the option ,i get the same user clicked value. Like
If the user selects "Need help". I get the same value..
then i am getting input from user like any question(query).
Then this query will go to the specific intent from dispatcher but its not going to intent from dispatcher and showing error (object reference not set to an instance of an object)
so what should i do?
for reference i already gone through this link "How to retrieve user entered input in adaptive card to the c# code and how to call next intent on submit button click"
it should take value from the dispatcher bot after user input.

Resources