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

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);
...

Related

Log additional information with TranscriptLoggerMiddleware

I'm using TranscriptLoggerMiddleware to log transcript to Azure blobs.
Now, I want to add additional information to the activity, for example, account ID.
Ideally I want the account ID to be the top level folder when creating the blobs so one can easily locate all conversations for a given account.
The logger is only passed the activity without any context. So I'm looking at the Entities activity property which I can potentially use for storing my account ID.
Is this a valid approach?
Any other ideas on how to implement this?
Answering my own question.
This worked for me:
public class SetEntityMiddleware : IMiddleware
{
private readonly BotState _userState;
private readonly IStatePropertyAccessor<UserProfileState> _userProfileState;
public SetEntityMiddleware(UserState userState)
{
_userState = userState;
_userProfileState = userState.CreateProperty<UserProfileState>(nameof(UserProfileState));
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
var userProfile = await _userProfileState.GetAsync(turnContext, () => new UserProfileState(), cancellationToken);
this.SetEntity(turnContext.Activity, userProfile);
turnContext.OnSendActivities(async (ctx, activities, nextSend) =>
{
var userProfile = await _userProfileState.GetAsync(ctx, () => new UserProfileState(), cancellationToken);
foreach (var activity in activities)
{
this.SetEntity(activity, userProfile);
}
return await nextSend().ConfigureAwait(false);
});
await next(cancellationToken).ConfigureAwait(false);
}
private void SetEntity(Activity activity, UserProfileState userProfile)
{
if (activity.Type == ActivityTypes.Message &&
!string.IsNullOrEmpty(userProfile.AccountNumber))
{
var entity = new Entity();
entity.Type = "userProfile";
entity.Properties["accountNumber"] = userProfile.AccountNumber;
if (activity.Entities == null)
{
activity.Entities = new List<Entity>();
}
activity.Entities.Add(entity);
}
}
}

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

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

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

Microsoft Bot Framework unit tests not continuing dialogs

I have a MSFT Bot Framework application for which I am trying to write unit tests. I have single call/response tests working fine, but anything that requires continuing a dialog does not work. The ActiveDialog property in the DialogContext that get's created on every turn is always null and the Stack property is always empty. I was sort of following this blog post example. What am I missing that allows the bot to maintain it's state between turns?
One-Time Setup
protected virtual void TestFixtureSetup()
{
var environmentName = "development";
var builder = new ConfigurationBuilder();
var configuration = builder.Build();
_connectedServices = new BotServices(_botConfig, configuration, environmentName);
_testAdapter = new TestAdapter();
_testAdapter.Use(new AutoSaveStateMiddleware());
}
Per-Test Setup
protected virtual void TestSetup()
{
var memStore = new MemoryStorage();
var userState = new UserState(memStore);
var conversationState = new ConversationState(memStore);
var dialogState = conversationState.CreateProperty<DialogState>("dialogState");
_dialogSet = new DialogSet(dialogState);
_dialogSet.Add(new MainDialog(_connectedServices, conversationState, userState, new TestTelemetryClient()));
_testFlow = new TestFlow(_testAdapter, async (turnContext, cancellationToken) =>
{
var dc = await _dialogSet.CreateContextAsync(turnContext, cancellationToken);
await dc.ContinueDialogAsync();
if (!turnContext.Responded)
{
await dc.BeginDialogAsync(nameof(MainDialog));
}
});
}
Test
public async Task MenuTestMethod(string subMenuOption, string verificationString)
{
var firstMenu = Responses.BuildFirstMenu(null, null);
var secondMenu = Responses.BuildSecondMenu(null, null);
await _testFlow
.Send("go to first menu")
.AssertReply((activity) =>
{
Assert.AreEqual(firstMenu.Attachments[0].Content, activity.AsMessageActivity().Attachments[0].Content);
})
.Send("go to second menu")
.AssertReply((activity) =>
{
Assert.AreEqual(secondMenu.Attachments[0].Content, activity.AsMessageActivity().Attachments[0].Content);
})
.Send(subMenuOption)
.AssertReply((activity) =>
{
Assert.IsTrue(activity.AsMessageActivity().Text.Contains(verificationString));
})
.StartTestAsync();
}

Azure Notification Hub and WP8 Intermitant notifications

This is a fairly long piece of code but I am getting nowhere with this and cannot see any issues, although I am new to using notification hubs. I am trying to register for targeted notifications (the logged on user) using the notification hub in Azure. After the registration, a test notification is sent.
The issue I am having is that sometimes the notification is sent to the device, and sometimes it is not. It mostly isn't but occasionally when I step through the code on the server, i will get the notification on the emulator come through. Once when I deployed the app to my phone the notification came though on the emulator! I cannot discover a pattern.
My Controller class looks like this;
private NotificationHelper hub;
public RegisterController()
{
hub = NotificationHelper.Instance;
}
public async Task<RegistrationDescription> Post([FromBody]JObject registrationCall)
{
var obj = await hub.Post(registrationCall);
return obj;
}
And the helper class (which is used elsewhere so is not directly in the controller) looks like this;
public static NotificationHelper Instance = new NotificationHelper();
public NotificationHubClient Hub { get; set; }
// Create the client in the constructor.
public NotificationHelper()
{
var cn = "<my-cn>";
Hub = NotificationHubClient.CreateClientFromConnectionString(cn, "<my-hub>");
}
public async Task<RegistrationDescription> Post([FromBody] JObject registrationCall)
{
// Get the registration info that we need from the request.
var platform = registrationCall["platform"].ToString();
var installationId = registrationCall["instId"].ToString();
var channelUri = registrationCall["channelUri"] != null
? registrationCall["channelUri"].ToString()
: null;
var deviceToken = registrationCall["deviceToken"] != null
? registrationCall["deviceToken"].ToString()
: null;
var userName = HttpContext.Current.User.Identity.Name;
// Get registrations for the current installation ID.
var regsForInstId = await Hub.GetRegistrationsByTagAsync(installationId, 100);
var updated = false;
var firstRegistration = true;
RegistrationDescription registration = null;
// Check for existing registrations.
foreach (var registrationDescription in regsForInstId)
{
if (firstRegistration)
{
// Update the tags.
registrationDescription.Tags = new HashSet<string>() {installationId, userName};
// We need to handle each platform separately.
switch (platform)
{
case "windows":
var winReg = registrationDescription as MpnsRegistrationDescription;
winReg.ChannelUri = new Uri(channelUri);
registration = await Hub.UpdateRegistrationAsync(winReg);
break;
case "ios":
var iosReg = registrationDescription as AppleRegistrationDescription;
iosReg.DeviceToken = deviceToken;
registration = await Hub.UpdateRegistrationAsync(iosReg);
break;
}
updated = true;
firstRegistration = false;
}
else
{
// We shouldn't have any extra registrations; delete if we do.
await Hub.DeleteRegistrationAsync(registrationDescription);
}
}
// Create a new registration.
if (!updated)
{
switch (platform)
{
case "windows":
registration = await Hub.CreateMpnsNativeRegistrationAsync(channelUri,
new string[] {installationId, userName});
break;
case "ios":
registration = await Hub.CreateAppleNativeRegistrationAsync(deviceToken,
new string[] {installationId, userName});
break;
}
}
// Send out a test notification.
await SendNotification(string.Format("Test notification for {0}", userName), userName);
return registration;
And finally, my SendNotification method is here;
internal async Task SendNotification(string notificationText, string tag)
{
try
{
var toast = PrepareToastPayload("<my-hub>", notificationText);
// Send a notification to the logged-in user on both platforms.
await NotificationHelper.Instance.Hub.SendMpnsNativeNotificationAsync(toast, tag);
//await hubClient.SendAppleNativeNotificationAsync(alert, tag);
}
catch (ArgumentException ex)
{
// This is expected when an APNS registration doesn't exist.
Console.WriteLine(ex.Message);
}
}
I suspect the issue is in my phone client code, which is here and SubscribeToService is called immediately after WebAPI login;
public void SubscribeToService()
{
_channel = HttpNotificationChannel.Find("mychannel");
if (_channel == null)
{
_channel = new HttpNotificationChannel("mychannel");
_channel.Open();
_channel.BindToShellToast();
}
_channel.ChannelUriUpdated += async (o, args) =>
{
var hub = new NotificationHub("<my-hub>", "<my-cn>");
await hub.RegisterNativeAsync(args.ChannelUri.ToString());
await RegisterForMessageNotificationsAsync();
};
}
public async Task RegisterForMessageNotificationsAsync()
{
using (var client = GetNewHttpClient(true))
{
// Get the info that we need to request registration.
var installationId = LocalStorageManager.GetInstallationId(); // a new Guid
var registration = new Dictionary<string, string>()
{
{"platform", "windows"},
{"instId", installationId},
{"channelUri", _channel.ChannelUri.ToString()}
};
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(ApiUrl + "api/Register/RegisterForNotifications"));
request.Content = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json");
string message;
try
{
HttpResponseMessage response = await client.SendAsync(request);
message = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
message = ex.Message;
}
_registrationId = message;
}
}
Any help would be greatly appriciated as I have been stuck on this now for days! I know this is a lot of code to paste up here but it is all relevant.
Thanks,
EDIT: The SubscribeToService() method is called when the user logs in and authenticates with the WebAPI. The method is here;
public async Task<User> SendSubmitLogonAsync(LogonObject lo)
{
_logonObject = lo;
using (var client = GetNewHttpClient(false))
{
var logonString = String.Format("grant_type=password&username={0}&password={1}", lo.username, lo.password);
var sc = new StringContent(logonString, Encoding.UTF8);
var response = await client.PostAsync("Token", sc);
if (response.IsSuccessStatusCode)
{
_logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
var userInfo = await GetUserInfoAsync();
if (_channel == null)
SubscribeToService();
else
await RegisterForMessageNotificationsAsync();
return userInfo;
}
// ...
}
}
I have solved the issue. There are tons of fairly poorly organised howto's for azure notification hubs and only one of them has this note toward the bottom;
NOTE:
You will not receive the notification when you are still in the app.
To receive a toast notification while the app is active, you must
handle the ShellToastNotificationReceived event.
This is why I was experiencing intermittent results, as i assumed you would still get a notification if you were in the app. And this little note is pretty well hidden.
Have you used proper tag / tag expressions while register/send the message. Also, Where are you storing the id back from the notification hub. It should be used when you update the channel uri (it will expire).
I would suggest to start from scratch.
Ref: http://msdn.microsoft.com/en-us/library/dn530749.aspx

Resources