• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / botbuilder-dotnet / 333683

15 Dec 2022 06:25PM UTC coverage: 79.054% (-0.008%) from 79.062%
333683

Pull #6570

CI-PR build

GitHub
Merge 605095e6d into a908ca355
Pull Request #6570: Support bot sdk dot net for targeted meeting notification

25800 of 32636 relevant lines covered (79.05%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.76
/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
// Licensed under the MIT License.
3

4
using System;
5
using System.Collections.Generic;
6
using System.Linq;
7
using System.Threading;
8
using System.Threading.Tasks;
9
using Microsoft.Bot.Connector;
10
using Microsoft.Bot.Connector.Authentication;
11
using Microsoft.Bot.Connector.Teams;
12
using Microsoft.Bot.Schema;
13
using Microsoft.Bot.Schema.Teams;
14
using Newtonsoft.Json.Linq;
15

16
namespace Microsoft.Bot.Builder.Teams
17
{
18
    /// <summary>
19
    /// The TeamsInfo
20
    /// provides utility methods for the events and interactions that occur within Microsoft Teams.
21
    /// </summary>
22
    public static class TeamsInfo
23
    {
24
        /// <summary>
25
        /// Gets the details for the given meeting participant. This only works in teams meeting scoped conversations. 
26
        /// </summary>
27
        /// <param name="turnContext">Turn context.</param>
28
        /// <param name="meetingId">The id of the Teams meeting. TeamsChannelData.Meeting.Id will be used if none provided.</param>
29
        /// <param name="participantId">The id of the Teams meeting participant. From.AadObjectId will be used if none provided.</param>
30
        /// <param name="tenantId">The id of the Teams meeting Tenant. TeamsChannelData.Tenant.Id will be used if none provided.</param>
31
        /// <param name="cancellationToken">Cancellation token.</param>
32
        /// <remarks>InvalidOperationException will be thrown if meetingId, participantId or tenantId have not been
33
        /// provided, and also cannot be retrieved from turnContext.Activity.</remarks>
34
        /// <returns>Team participant channel account.</returns>
35
        public static async Task<TeamsMeetingParticipant> GetMeetingParticipantAsync(ITurnContext turnContext, string meetingId = null, string participantId = null, string tenantId = null, CancellationToken cancellationToken = default)
36
        {
37
            meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of a MS Teams Meeting.");
×
38
            participantId ??= turnContext.Activity.From.AadObjectId ?? throw new InvalidOperationException($"{nameof(participantId)} is required.");
×
39
            tenantId ??= turnContext.Activity.GetChannelData<TeamsChannelData>()?.Tenant?.Id ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
×
40

41
            using (var teamsClient = GetTeamsConnectorClient(turnContext))
1✔
42
            {
43
                return await teamsClient.Teams.FetchParticipantAsync(meetingId, participantId, tenantId, cancellationToken).ConfigureAwait(false);
1✔
44
            }
45
        }
1✔
46

47
        /// <summary>
48
        /// Gets the information for the given meeting id.
49
        /// </summary>
50
        /// <param name="turnContext"> Turn context.</param>
51
        /// <param name="meetingId"> The BASE64-encoded id of the Teams meeting.</param>
52
        /// <param name="cancellationToken"> Cancellation token.</param>
53
        /// <returns>Team Details.</returns>
54
        public static async Task<MeetingInfo> GetMeetingInfoAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default)
55
        {
56
            meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting.");
×
57
            using (var teamsClient = GetTeamsConnectorClient(turnContext))
1✔
58
            {
59
                return await teamsClient.Teams.FetchMeetingInfoAsync(meetingId, cancellationToken: cancellationToken).ConfigureAwait(false);
1✔
60
            }
61
        }
1✔
62

63
        /// <summary>
64
        /// Gets the details for the given team id. This only works in teams scoped conversations. 
65
        /// </summary>
66
        /// <param name="turnContext"> Turn context. </param>
67
        /// <param name="teamId"> The id of the Teams team. </param>
68
        /// <param name="cancellationToken"> Cancellation token. </param>
69
        /// <returns>Team Details.</returns>
70
        public static async Task<TeamDetails> GetTeamDetailsAsync(ITurnContext turnContext, string teamId = null, CancellationToken cancellationToken = default)
71
        {
72
            var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
×
73
            using (var teamsClient = GetTeamsConnectorClient(turnContext))
1✔
74
            {
75
                return await teamsClient.Teams.FetchTeamDetailsAsync(t, cancellationToken).ConfigureAwait(false);
1✔
76
            }
77
        }
1✔
78

79
        /// <summary>
80
        /// Returns a list of channels in a Team. 
81
        /// This only works in teams scoped conversations.
82
        /// </summary>
83
        /// <param name="turnContext"> Turn context. </param>
84
        /// <param name="teamId"> ID of the Teams team. </param>
85
        /// <param name="cancellationToken"> cancellation token. </param>
86
        /// <returns>Team Details.</returns>
87
        public static async Task<IList<ChannelInfo>> GetTeamChannelsAsync(ITurnContext turnContext, string teamId = null, CancellationToken cancellationToken = default)
88
        {
89
            var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
×
90
            using (var teamsClient = GetTeamsConnectorClient(turnContext))
1✔
91
            {
92
                var channelList = await teamsClient.Teams.FetchChannelListAsync(t, cancellationToken).ConfigureAwait(false);
1✔
93
                return channelList.Conversations;
1✔
94
            }
95
        }
1✔
96

97
        /// <summary>
98
        /// Gets the list of TeamsChannelAccounts within a team. 
99
        /// This only works in teams scoped conversations.
100
        /// </summary>
101
        /// <param name="turnContext"> Turn context. </param>
102
        /// <param name="teamId"> ID of the Teams team. </param>
103
        /// <param name="cancellationToken"> cancellation token. </param>
104
        /// <returns>TeamsChannelAccount.</returns>
105
        [Obsolete("Microsoft Teams is deprecating the non-paged version of the getMembers API which this method uses. Please use GetPagedTeamMembersAsync instead of this API.")]
106
        public static Task<IEnumerable<TeamsChannelAccount>> GetTeamMembersAsync(ITurnContext turnContext, string teamId = null, CancellationToken cancellationToken = default)
107
        {
108
            var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
×
109
            return GetMembersAsync(GetConnectorClient(turnContext), t, cancellationToken);
1✔
110
        }
111

112
        /// <summary>
113
        /// Gets the conversation members of a one-on-one or group chat.
114
        /// </summary>
115
        /// <param name="turnContext"> Turn context. </param>
116
        /// <param name="cancellationToken"> Cancellation token. </param>
117
        /// <returns>TeamsChannelAccount.</returns>
118
        [Obsolete("Microsoft Teams is deprecating the non-paged version of the getMembers API which this method uses. Please use GetPagedTeamMembersAsync instead of this API.")]
119
        public static Task<IEnumerable<TeamsChannelAccount>> GetMembersAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
120
        {
121
            var teamInfo = turnContext.Activity.TeamsGetTeamInfo();
1✔
122

123
            if (teamInfo?.Id != null)
1✔
124
            {
125
                return GetTeamMembersAsync(turnContext, teamInfo.Id, cancellationToken);
1✔
126
            }
127
            else
128
            {
129
                var conversationId = turnContext.Activity?.Conversation?.Id;
×
130
                return GetMembersAsync(GetConnectorClient(turnContext), conversationId, cancellationToken);
1✔
131
            }
132
        }
133

134
        /// <summary>
135
        /// Gets a paginated list of members of a team. 
136
        /// This only works in teams scoped conversations.
137
        /// </summary>
138
        /// <param name="turnContext"> Turn context. </param>
139
        /// <param name="teamId"> ID of the Teams team. </param>
140
        /// <param name="continuationToken"> continuationToken token. </param>
141
        /// <param name="pageSize"> number of entries on the page. </param>
142
        /// /// <param name="cancellationToken"> cancellation token. </param>
143
        /// <returns>TeamsPagedMembersResult.</returns>
144
        public static Task<TeamsPagedMembersResult> GetPagedTeamMembersAsync(ITurnContext turnContext, string teamId = null, string continuationToken = default(string), int? pageSize = default(int?), CancellationToken cancellationToken = default)
145
        {
146
            var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
×
147
            return GetPagedMembersAsync(GetConnectorClient(turnContext), t, continuationToken, cancellationToken, pageSize);
×
148
        }
149

150
        /// <summary>
151
        /// Gets a paginated list of members of one-on-one, group, or team conversation.
152
        /// </summary>
153
        /// <param name="turnContext"> Turn context. </param>
154
        /// <param name="pageSize"> Suggested number of entries on a page. </param>
155
        /// <param name="continuationToken"> ContinuationToken token. </param>
156
        /// /// <param name="cancellationToken"> Cancellation token. </param>
157
        /// <returns>TeamsPagedMembersResult.</returns>
158
        public static Task<TeamsPagedMembersResult> GetPagedMembersAsync(ITurnContext turnContext, int? pageSize = default(int?), string continuationToken = default(string), CancellationToken cancellationToken = default)
159
        {
160
            var teamInfo = turnContext.Activity.TeamsGetTeamInfo();
×
161

162
            if (teamInfo?.Id != null)
×
163
            {
164
                return GetPagedTeamMembersAsync(turnContext, teamInfo.Id, continuationToken, pageSize, cancellationToken);
×
165
            }
166
            else
167
            {
168
                var conversationId = turnContext.Activity?.Conversation?.Id;
×
169
                return GetPagedMembersAsync(GetConnectorClient(turnContext), conversationId, continuationToken, cancellationToken, pageSize);
×
170
            }
171
        }
172

173
        /// <summary>
174
        /// Gets the member of a teams scoped conversation.
175
        /// </summary>
176
        /// <param name="turnContext"> Turn context. </param>
177
        /// <param name="userId"> user id. </param>
178
        /// <param name="teamId"> ID of the Teams team. </param>
179
        /// <param name="cancellationToken"> cancellation token. </param>
180
        /// <returns>Team Details.</returns>
181
        public static Task<TeamsChannelAccount> GetTeamMemberAsync(ITurnContext turnContext, string userId, string teamId = null, CancellationToken cancellationToken = default)
182
        {
183
            var t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
×
184
            return GetMemberAsync(GetConnectorClient(turnContext), userId, t, cancellationToken);
1✔
185
        }
186

187
        /// <summary>
188
        /// Gets the account of a single conversation member. 
189
        /// This works in one-on-one, group, and teams scoped conversations.
190
        /// </summary>
191
        /// <param name="turnContext"> Turn context. </param>
192
        /// <param name="userId"> ID of the user in question. </param>
193
        /// <param name="cancellationToken"> cancellation token. </param>
194
        /// <returns>Team Details.</returns>
195
        public static Task<TeamsChannelAccount> GetMemberAsync(ITurnContext turnContext, string userId, CancellationToken cancellationToken = default)
196
        {
197
            var teamInfo = turnContext.Activity.TeamsGetTeamInfo();
1✔
198

199
            if (teamInfo?.Id != null)
1✔
200
            {
201
                return GetTeamMemberAsync(turnContext, userId, teamInfo.Id, cancellationToken);
1✔
202
            }
203
            else
204
            {
205
                var conversationId = turnContext.Activity?.Conversation?.Id;
×
206
                return GetMemberAsync(GetConnectorClient(turnContext), userId, conversationId, cancellationToken);
1✔
207
            }
208
        }
209

210
        /// <summary>
211
        /// Creates a new thread in a team chat and sends an activity to that new thread. Use this method if you are using BotFrameworkAdapter and are handling credentials in your code.
212
        /// </summary>
213
        /// <param name="turnContext"> Turn context. </param>
214
        /// <param name="activity"> The activity to send on starting the new thread. </param>
215
        /// <param name="teamsChannelId"> The Team's Channel ID, note this is distinct from the Bot Framework activity property with same name. </param>
216
        /// <param name="credentials"> Microsoft app credentials. </param>
217
        /// <param name="cancellationToken"> The cancellation token. </param>
218
        /// <returns>Team Details.</returns>
219
        public static async Task<Tuple<ConversationReference, string>> SendMessageToTeamsChannelAsync(ITurnContext turnContext, IActivity activity, string teamsChannelId, MicrosoftAppCredentials credentials, CancellationToken cancellationToken = default)
220
        {
221
            if (turnContext == null)
1✔
222
            {
223
                throw new ArgumentNullException(nameof(turnContext));
×
224
            }
225

226
            if (turnContext.Activity == null)
1✔
227
            {
228
                throw new InvalidOperationException(nameof(turnContext.Activity));
×
229
            }
230

231
            if (string.IsNullOrEmpty(teamsChannelId))
1✔
232
            {
233
                throw new ArgumentNullException(nameof(teamsChannelId));
×
234
            }
235

236
            if (credentials == null)
1✔
237
            {
238
                throw new ArgumentNullException(nameof(credentials));
×
239
            }
240

241
            ConversationReference conversationReference = null;
1✔
242
            var newActivityId = string.Empty;
1✔
243
            var serviceUrl = turnContext.Activity.ServiceUrl;
1✔
244
            var conversationParameters = new ConversationParameters
1✔
245
            {
1✔
246
                IsGroup = true,
1✔
247
                ChannelData = new TeamsChannelData { Channel = new ChannelInfo() { Id = teamsChannelId } },
1✔
248
                Activity = (Activity)activity,
1✔
249
            };
1✔
250

251
            await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
1✔
252
                teamsChannelId,
1✔
253
                serviceUrl,
1✔
254
                credentials,
1✔
255
                conversationParameters,
1✔
256
                (t, ct) =>
1✔
257
                {
1✔
258
                    conversationReference = t.Activity.GetConversationReference();
1✔
259
                    newActivityId = t.Activity.Id;
1✔
260
                    return Task.CompletedTask;
1✔
261
                },
1✔
262
                cancellationToken).ConfigureAwait(false);
1✔
263

264
            return new Tuple<ConversationReference, string>(conversationReference, newActivityId);
1✔
265
        }
1✔
266

267
        /// <summary>
268
        /// Creates a new thread in a team chat and sends an activity to that new thread. Use this method if you are using CloudAdapter where credentials are handled by the adapter.
269
        /// </summary>
270
        /// <param name="turnContext"> Turn context. </param>
271
        /// <param name="activity"> The activity to send on starting the new thread. </param>
272
        /// <param name="teamsChannelId"> The Team's Channel ID, note this is distinct from the Bot Framework activity property with same name. </param>
273
        /// <param name="botAppId"> The bot's appId. </param>
274
        /// <param name="cancellationToken"> The cancellation token. </param>
275
        /// <returns>Team Details.</returns>
276
        public static async Task<Tuple<ConversationReference, string>> SendMessageToTeamsChannelAsync(ITurnContext turnContext, IActivity activity, string teamsChannelId, string botAppId, CancellationToken cancellationToken = default)
277
        {
278
            if (turnContext == null)
1✔
279
            {
280
                throw new ArgumentNullException(nameof(turnContext));
×
281
            }
282

283
            if (turnContext.Activity == null)
1✔
284
            {
285
                throw new InvalidOperationException(nameof(turnContext.Activity));
×
286
            }
287

288
            if (string.IsNullOrEmpty(teamsChannelId))
1✔
289
            {
290
                throw new ArgumentNullException(nameof(teamsChannelId));
×
291
            }
292

293
            ConversationReference conversationReference = null;
1✔
294
            var newActivityId = string.Empty;
1✔
295
            var serviceUrl = turnContext.Activity.ServiceUrl;
1✔
296
            var conversationParameters = new ConversationParameters
1✔
297
            {
1✔
298
                IsGroup = true,
1✔
299
                ChannelData = new TeamsChannelData { Channel = new ChannelInfo() { Id = teamsChannelId } },
1✔
300
                Activity = (Activity)activity,
1✔
301
            };
1✔
302

303
            await turnContext.Adapter.CreateConversationAsync(
1✔
304
                botAppId,
1✔
305
                Channels.Msteams,
1✔
306
                serviceUrl,
1✔
307
                null,
1✔
308
                conversationParameters,
1✔
309
                (t, ct) =>
1✔
310
                {
1✔
311
                    conversationReference = t.Activity.GetConversationReference();
1✔
312
                    newActivityId = t.Activity.Id;
1✔
313
                    return Task.CompletedTask;
1✔
314
                },
1✔
315
                cancellationToken).ConfigureAwait(false);
1✔
316

317
            return new Tuple<ConversationReference, string>(conversationReference, newActivityId);
1✔
318
        }
1✔
319

320
        /// <summary>
321
        /// Sends a notification to meeting participants. This functionality is available only in teams meeting scoped conversations. 
322
        /// </summary>
323
        /// <param name="turnContext">Turn context.</param>
324
        /// <param name="notification">The notification to send to Teams.</param>
325
        /// <param name="meetingId">The id of the Teams meeting. TeamsChannelData.Meeting.Id will be used if none provided.</param>
326
        /// <param name="cancellationToken">Cancellation token.</param>
327
        /// <remarks>InvalidOperationException will be thrown if meetingId or notification have not been
328
        /// provided, and also cannot be retrieved from turnContext.Activity.</remarks>
329
        /// <returns>List of <see cref="TeamsMeetingNotificationRecipientFailureInfo"/> for whom the notification failed.</returns>
330
        public static async Task<TeamsMeetingNotificationRecipientFailureInfos> SendMeetingNotificationAsync(ITurnContext turnContext, TeamsMeetingNotification notification, string meetingId = null, CancellationToken cancellationToken = default)
331
        {
332
            meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of a MS Teams Meeting.");
×
333
            notification = notification ?? throw new InvalidOperationException($"{nameof(notification)} is required.");
×
334

335
            using (var teamsClient = GetTeamsConnectorClient(turnContext))
1✔
336
            {
337
                return await teamsClient.Teams.SendMeetingNotificationAsync(meetingId, notification, cancellationToken).ConfigureAwait(false);
1✔
338
            }
339
        }
1✔
340

341
        private static async Task<IEnumerable<TeamsChannelAccount>> GetMembersAsync(IConnectorClient connectorClient, string conversationId, CancellationToken cancellationToken)
342
        {
343
            if (conversationId == null)
1✔
344
            {
345
                throw new InvalidOperationException("The GetMembers operation needs a valid conversation Id.");
×
346
            }
347

348
            var teamMembers = await connectorClient.Conversations.GetConversationMembersAsync(conversationId, cancellationToken).ConfigureAwait(false);
1✔
349
            var teamsChannelAccounts = teamMembers.Select(channelAccount => JObject.FromObject(channelAccount).ToObject<TeamsChannelAccount>());
1✔
350
            return teamsChannelAccounts;
1✔
351
        }
1✔
352

353
        private static IConnectorClient GetConnectorClient(ITurnContext turnContext)
354
        {
355
            return turnContext.TurnState.Get<IConnectorClient>() ?? throw new InvalidOperationException("This method requires a connector client.");
×
356
        }
357

358
        private static async Task<TeamsChannelAccount> GetMemberAsync(IConnectorClient connectorClient, string userId, string conversationId, CancellationToken cancellationToken)
359
        {
360
            if (conversationId == null)
1✔
361
            {
362
                throw new InvalidOperationException("The GetMembers operation needs a valid conversation Id.");
×
363
            }
364

365
            if (userId == null)
1✔
366
            {
367
                throw new InvalidOperationException("The GetMembers operation needs a valid user Id.");
×
368
            }
369

370
            var teamMember = await ((Conversations)connectorClient.Conversations).GetConversationMemberAsync(userId, conversationId, cancellationToken).ConfigureAwait(false);
1✔
371
            var teamsChannelAccount = JObject.FromObject(teamMember).ToObject<TeamsChannelAccount>();
1✔
372
            return teamsChannelAccount;
1✔
373
        }
1✔
374

375
        private static async Task<TeamsPagedMembersResult> GetPagedMembersAsync(IConnectorClient connectorClient, string conversationId, string continuationToken, CancellationToken cancellationToken, int? pageSize = default(int?))
376
        {
377
            if (conversationId == null)
×
378
            {
379
                throw new InvalidOperationException("The GetMembers operation needs a valid conversation Id.");
×
380
            }
381

382
            var pagedMemberResults = await connectorClient.Conversations.GetConversationPagedMembersAsync(conversationId, pageSize, continuationToken, cancellationToken).ConfigureAwait(false);
×
383
            var teamsPagedMemberResults = new TeamsPagedMembersResult(pagedMemberResults.ContinuationToken, pagedMemberResults.Members);
×
384
            return teamsPagedMemberResults;
×
385
        }
×
386

387
        private static ITeamsConnectorClient GetTeamsConnectorClient(ITurnContext turnContext)
388
        {
389
            var connectorClient = GetConnectorClient(turnContext);
1✔
390
            if (connectorClient is ConnectorClient connectorClientImpl)
1✔
391
            {
392
                return new TeamsConnectorClient(connectorClientImpl.BaseUri, connectorClientImpl.Credentials, connectorClientImpl.HttpClient, connectorClientImpl.HttpClient == null);
1✔
393
            }
394
            else
395
            {
396
                return new TeamsConnectorClient(connectorClient.BaseUri, connectorClient.Credentials);
×
397
            }
398
        }
399
    }
400
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc