microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/update-release-process

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Apps.BotBuilder/TeamsApiClient.cs

775lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.Bot.Builder;
5using Microsoft.Bot.Connector;
6using Microsoft.Bot.Schema;
7using Microsoft.Bot.Schema.Teams;
8using Microsoft.Teams.Core;
9using Microsoft.Teams.Core.Http;
10using Microsoft.Teams.Core.Schema;
11using Newtonsoft.Json;
12using static Microsoft.Teams.Apps.BotBuilder.TeamsApiClientModels;
13using BotFrameworkTeams = Microsoft.Bot.Schema.Teams;
14using CustomHeaders = System.Collections.Generic.Dictionary<string, string>;
15
16namespace Microsoft.Teams.Apps.BotBuilder;
17
18/// <summary>
19/// Provides utility methods for the events and interactions that occur within Microsoft Teams.
20/// This class adapts the Teams Bot Core SDK to the Bot Framework v4 SDK TeamsInfo API.
21/// </summary>
22public static class TeamsApiClient
23{
24 internal static CustomHeaders DefaultCustomHeaders { get; } = [];
25
26 #region Helper Methods
27
28
29 private static ConversationClient GetConversationClient(ITurnContext turnContext)
30 {
31 IConnectorClient connectorClient = turnContext.TurnState.Get<IConnectorClient>()
32 ?? throw new InvalidOperationException("This method requires a connector client.");
33
34 if (connectorClient is CompatConnectorClient compatClient)
35 {
36 return ((CompatConversations)compatClient.Conversations)._client;
37 }
38
39 throw new InvalidOperationException("Connector client is not compatible.");
40 }
41
42 private static string GetServiceUrl(ITurnContext turnContext)
43 {
44 return turnContext.Activity.ServiceUrl
45 ?? throw new InvalidOperationException("ServiceUrl is required.");
46 }
47
48 private static AgenticIdentity GetIdentity(ITurnContext turnContext)
49 {
50 CoreActivity coreActivity = turnContext.Activity.FromBotFrameworkActivity();
51 return AgenticIdentity.FromAccount(coreActivity.From) ?? new AgenticIdentity();
52 }
53
54 #endregion
55
56 #region Member & Participant Methods
57
58 /// <summary>
59 /// Gets the account of a single conversation member.
60 /// This works in one-on-one, group, and teams scoped conversations.
61 /// </summary>
62 /// <param name="turnContext">Turn context.</param>
63 /// <param name="userId">ID of the user in question.</param>
64 /// <param name="cancellationToken">Cancellation token.</param>
65 /// <returns>The member's channel account information.</returns>
66 public static async Task<BotFrameworkTeams.TeamsChannelAccount> GetMemberAsync(
67 ITurnContext turnContext,
68 string userId,
69 CancellationToken cancellationToken = default)
70 {
71 ArgumentNullException.ThrowIfNull(turnContext);
72 TeamInfo? teamInfo = turnContext.Activity.TeamsGetTeamInfo();
73
74 if (teamInfo?.Id != null)
75 {
76 return await GetTeamMemberAsync(turnContext, userId, teamInfo.Id, cancellationToken).ConfigureAwait(false);
77 }
78 else
79 {
80 string conversationId = turnContext.Activity?.Conversation?.Id
81 ?? throw new InvalidOperationException("The GetMember operation needs a valid conversation Id.");
82
83 if (userId == null)
84 {
85 throw new InvalidOperationException("The GetMember operation needs a valid user Id.");
86 }
87
88 ConversationClient client = GetConversationClient(turnContext);
89 Uri serviceUrl = new(GetServiceUrl(turnContext));
90 AgenticIdentity identity = GetIdentity(turnContext);
91
92 Core.Schema.ConversationAccount result = await client.GetConversationMemberAsync<Core.Schema.ConversationAccount>(
93 conversationId, userId, serviceUrl, identity, null, cancellationToken).ConfigureAwait(false);
94
95 return result.ToCompatTeamsChannelAccount();
96 }
97 }
98
99 /// <summary>
100 /// Gets the conversation members of a one-on-one or group chat.
101 /// </summary>
102 /// <param name="turnContext">Turn context.</param>
103 /// <param name="cancellationToken">Cancellation token.</param>
104 /// <returns>List of channel accounts.</returns>
105 [Obsolete("Microsoft Teams is deprecating the non-paged version of the getMembers API which this method uses. Please use GetPagedMembersAsync instead of this API.")]
106 public static async Task<IEnumerable<BotFrameworkTeams.TeamsChannelAccount>> GetMembersAsync(
107 ITurnContext turnContext,
108 CancellationToken cancellationToken = default)
109 {
110 ArgumentNullException.ThrowIfNull(turnContext);
111 TeamInfo? teamInfo = turnContext.Activity.TeamsGetTeamInfo();
112
113 if (teamInfo?.Id != null)
114 {
115 return await GetTeamMembersAsync(turnContext, teamInfo.Id, cancellationToken).ConfigureAwait(false);
116 }
117 else
118 {
119 string conversationId = turnContext.Activity?.Conversation?.Id
120 ?? throw new InvalidOperationException("The GetMembers operation needs a valid conversation Id.");
121
122 ConversationClient client = GetConversationClient(turnContext);
123 Uri serviceUrl = new(GetServiceUrl(turnContext));
124 AgenticIdentity identity = GetIdentity(turnContext);
125
126 IList<Core.Schema.ConversationAccount> members = await client.GetConversationMembersAsync(
127 conversationId, serviceUrl, identity, null, cancellationToken).ConfigureAwait(false);
128
129 return members.Select(m => m.ToCompatTeamsChannelAccount());
130 }
131 }
132
133 /// <summary>
134 /// Gets a paginated list of members of one-on-one, group, or team conversation.
135 /// </summary>
136 /// <param name="turnContext">Turn context.</param>
137 /// <param name="pageSize">Suggested number of entries on a page.</param>
138 /// <param name="continuationToken">Continuation token.</param>
139 /// <param name="cancellationToken">Cancellation token.</param>
140 /// <returns>Paged members result.</returns>
141 public static async Task<BotFrameworkTeams.TeamsPagedMembersResult> GetPagedMembersAsync(
142 ITurnContext turnContext,
143 int? pageSize = default,
144 string? continuationToken = default,
145 CancellationToken cancellationToken = default)
146 {
147 ArgumentNullException.ThrowIfNull(turnContext);
148 TeamInfo? teamInfo = turnContext.Activity.TeamsGetTeamInfo();
149
150 if (teamInfo?.Id != null)
151 {
152 return await GetPagedTeamMembersAsync(turnContext, teamInfo.Id, continuationToken, pageSize, cancellationToken).ConfigureAwait(false);
153 }
154 else
155 {
156 string conversationId = turnContext.Activity?.Conversation?.Id
157 ?? throw new InvalidOperationException("The GetMembers operation needs a valid conversation Id.");
158
159 ConversationClient client = GetConversationClient(turnContext);
160 Uri serviceUrl = new(GetServiceUrl(turnContext));
161 AgenticIdentity identity = GetIdentity(turnContext);
162
163 Core.PagedMembersResult pagedMembers = await client.GetConversationPagedMembersAsync(
164 conversationId, serviceUrl, pageSize, continuationToken, identity, null, cancellationToken).ConfigureAwait(false);
165
166 return pagedMembers.ToCompatTeamsPagedMembersResult();
167 }
168 }
169
170 /// <summary>
171 /// Gets the member of a teams scoped conversation.
172 /// </summary>
173 /// <param name="turnContext">Turn context.</param>
174 /// <param name="userId">User id.</param>
175 /// <param name="teamId">ID of the Teams team.</param>
176 /// <param name="cancellationToken">Cancellation token.</param>
177 /// <returns>Team member's channel account.</returns>
178 public static async Task<BotFrameworkTeams.TeamsChannelAccount> GetTeamMemberAsync(
179 ITurnContext turnContext,
180 string userId,
181 string? teamId = null,
182 CancellationToken cancellationToken = default)
183 {
184 ArgumentNullException.ThrowIfNull(turnContext);
185 string t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id
186 ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
187
188 if (userId == null)
189 {
190 throw new InvalidOperationException("The GetMember operation needs a valid user Id.");
191 }
192
193 ConversationClient client = GetConversationClient(turnContext);
194 Uri serviceUrl = new(GetServiceUrl(turnContext));
195 AgenticIdentity identity = GetIdentity(turnContext);
196
197 Core.Schema.ConversationAccount result = await client.GetConversationMemberAsync<Core.Schema.ConversationAccount>(
198 t, userId, serviceUrl, identity, null, cancellationToken).ConfigureAwait(false);
199
200 return result.ToCompatTeamsChannelAccount();
201 }
202
203 /// <summary>
204 /// Gets the list of BotFrameworkTeams.TeamsChannelAccounts within a team.
205 /// This only works in teams scoped conversations.
206 /// </summary>
207 /// <param name="turnContext">Turn context.</param>
208 /// <param name="teamId">ID of the Teams team.</param>
209 /// <param name="cancellationToken">Cancellation token.</param>
210 /// <returns>List of team members.</returns>
211 [Obsolete("Microsoft Teams is deprecating the non-paged version of the getMembers API which this method uses. Please use GetPagedTeamMembersAsync instead of this API.")]
212 public static async Task<IEnumerable<BotFrameworkTeams.TeamsChannelAccount>> GetTeamMembersAsync(
213 ITurnContext turnContext,
214 string? teamId = null,
215 CancellationToken cancellationToken = default)
216 {
217 ArgumentNullException.ThrowIfNull(turnContext);
218 string t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id
219 ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
220
221 ConversationClient client = GetConversationClient(turnContext);
222 Uri serviceUrl = new(GetServiceUrl(turnContext));
223 AgenticIdentity identity = GetIdentity(turnContext);
224
225 IList<Core.Schema.ConversationAccount> members = await client.GetConversationMembersAsync(
226 t, serviceUrl, identity, null, cancellationToken).ConfigureAwait(false);
227
228 return members.Select(m => m.ToCompatTeamsChannelAccount());
229 }
230
231 /// <summary>
232 /// Gets a paginated list of members of a team.
233 /// This only works in teams scoped conversations.
234 /// </summary>
235 /// <param name="turnContext">Turn context.</param>
236 /// <param name="teamId">ID of the Teams team.</param>
237 /// <param name="continuationToken">Continuation token.</param>
238 /// <param name="pageSize">Number of entries on the page.</param>
239 /// <param name="cancellationToken">Cancellation token.</param>
240 /// <returns>Paged team members result.</returns>
241 public static async Task<BotFrameworkTeams.TeamsPagedMembersResult> GetPagedTeamMembersAsync(
242 ITurnContext turnContext,
243 string? teamId = null,
244 string? continuationToken = default,
245 int? pageSize = default,
246 CancellationToken cancellationToken = default)
247 {
248 ArgumentNullException.ThrowIfNull(turnContext);
249 string t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id
250 ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
251
252 ConversationClient client = GetConversationClient(turnContext);
253 Uri serviceUrl = new(GetServiceUrl(turnContext));
254 AgenticIdentity identity = GetIdentity(turnContext);
255
256 Core.PagedMembersResult pagedMembers = await client.GetConversationPagedMembersAsync(
257 t, serviceUrl, pageSize, continuationToken, identity, null, cancellationToken).ConfigureAwait(false);
258
259 return pagedMembers.ToCompatTeamsPagedMembersResult();
260 }
261
262 #endregion
263
264 #region Meeting Methods
265
266 /// <summary>
267 /// Gets the information for the given meeting id.
268 /// </summary>
269 /// <param name="turnContext">Turn context.</param>
270 /// <param name="meetingId">The BASE64-encoded id of the Teams meeting.</param>
271 /// <param name="cancellationToken">Cancellation token.</param>
272 /// <returns>Meeting information.</returns>
273 public static async Task<BotFrameworkTeams.MeetingInfo> GetMeetingInfoAsync(
274 ITurnContext turnContext,
275 string? meetingId = null,
276 CancellationToken cancellationToken = default)
277 {
278 ArgumentNullException.ThrowIfNull(turnContext);
279 meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id
280 ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting.");
281
282 Uri serviceUrl = new(GetServiceUrl(turnContext));
283 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
284
285 ConversationClient client = GetConversationClient(turnContext);
286 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}";
287
288 return (await client.BotHttpClient.SendAsync<MeetingInfo>(
289 HttpMethod.Get,
290 url,
291 body: null,
292 CreateRequestOptions(agenticIdentity, "fetching meeting info", DefaultCustomHeaders),
293 cancellationToken).ConfigureAwait(false))!;
294 }
295
296 /// <summary>
297 /// Gets the details for the given meeting participant. This only works in teams meeting scoped conversations.
298 /// </summary>
299 /// <param name="turnContext">Turn context.</param>
300 /// <param name="meetingId">The id of the Teams meeting. BotFrameworkTeams.TeamsChannelData.Meeting.Id will be used if none provided.</param>
301 /// <param name="participantId">The id of the Teams meeting participant. From.AadObjectId will be used if none provided.</param>
302 /// <param name="tenantId">The id of the Teams meeting Tenant. BotFrameworkTeams.TeamsChannelData.Tenant.Id will be used if none provided.</param>
303 /// <param name="cancellationToken">Cancellation token.</param>
304 /// <returns>Team participant channel account.</returns>
305 public static async Task<BotFrameworkTeams.TeamsMeetingParticipant> GetMeetingParticipantAsync(
306 ITurnContext turnContext,
307 string? meetingId = null,
308 string? participantId = null,
309 string? tenantId = null,
310 CancellationToken cancellationToken = default)
311 {
312 ArgumentNullException.ThrowIfNull(turnContext);
313 meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id
314 ?? throw new InvalidOperationException("This method is only valid within the scope of a MS Teams Meeting.");
315 participantId ??= turnContext.Activity.From.AadObjectId
316 ?? throw new InvalidOperationException($"{nameof(participantId)} is required.");
317 tenantId ??= turnContext.Activity.GetChannelData<BotFrameworkTeams.TeamsChannelData>()?.Tenant?.Id
318 ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
319
320 ConversationClient client = GetConversationClient(turnContext);
321 Uri serviceUrl = new(GetServiceUrl(turnContext));
322 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
323
324 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}/participants/{Uri.EscapeDataString(participantId)}?tenantId={Uri.EscapeDataString(tenantId)}";
325
326
327 return (await client.BotHttpClient.SendAsync<TeamsMeetingParticipant>(
328 HttpMethod.Get,
329 url,
330 body: null,
331 CreateRequestOptions(agenticIdentity, "fetching meeting participant", DefaultCustomHeaders),
332 cancellationToken).ConfigureAwait(false))!;
333 }
334
335 /// <summary>
336 /// Sends a notification to meeting participants. This functionality is available only in teams meeting scoped conversations.
337 /// </summary>
338 /// <param name="turnContext">Turn context.</param>
339 /// <param name="notification">The notification to send to Teams.</param>
340 /// <param name="meetingId">The id of the Teams meeting. BotFrameworkTeams.TeamsChannelData.Meeting.Id will be used if none provided.</param>
341 /// <param name="cancellationToken">Cancellation token.</param>
342 /// <returns>Meeting notification response.</returns>
343 public static async Task<BotFrameworkTeams.MeetingNotificationResponse> SendMeetingNotificationAsync(
344 ITurnContext turnContext,
345 BotFrameworkTeams.MeetingNotificationBase? notification,
346 string? meetingId = null,
347 CancellationToken cancellationToken = default)
348 {
349 ArgumentNullException.ThrowIfNull(turnContext);
350 meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id
351 ?? throw new InvalidOperationException("This method is only valid within the scope of a MS Teams Meeting.");
352 notification = notification ?? throw new InvalidOperationException($"{nameof(notification)} is required.");
353
354 ConversationClient client = GetConversationClient(turnContext);
355 Uri serviceUrl = new(GetServiceUrl(turnContext));
356 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
357
358 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}/notification";
359 string body = JsonConvert.SerializeObject(notification);
360
361 return (await client.BotHttpClient.SendAsync<MeetingNotificationResponse>(
362 HttpMethod.Post,
363 url,
364 body,
365 CreateRequestOptions(agenticIdentity, "sending meeting notification", DefaultCustomHeaders),
366 cancellationToken).ConfigureAwait(false))!;
367 }
368
369 #endregion
370
371 #region Team & Channel Methods
372
373 /// <summary>
374 /// Gets the details for the given team id. This only works in teams scoped conversations.
375 /// </summary>
376 /// <param name="turnContext">Turn context.</param>
377 /// <param name="teamId">The id of the Teams team.</param>
378 /// <param name="cancellationToken">Cancellation token.</param>
379 /// <returns>Team details.</returns>
380 public static async Task<BotFrameworkTeams.TeamDetails> GetTeamDetailsAsync(
381 ITurnContext turnContext,
382 string? teamId = null,
383 CancellationToken cancellationToken = default)
384 {
385 ArgumentNullException.ThrowIfNull(turnContext);
386 string t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id
387 ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
388
389 Uri serviceUrl = new(GetServiceUrl(turnContext));
390 AgenticIdentity identity = GetIdentity(turnContext);
391
392 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/teams/{Uri.EscapeDataString(t)}";
393
394 ConversationClient cc = GetConversationClient(turnContext);
395
396 return (await cc.BotHttpClient.SendAsync<TeamDetails>(
397 HttpMethod.Get,
398 url,
399 body: null,
400 new BotRequestOptions { AgenticIdentity = identity },
401 cancellationToken).ConfigureAwait(false))!;
402 }
403
404 /// <summary>
405 /// Returns a list of channels in a Team.
406 /// This only works in teams scoped conversations.
407 /// </summary>
408 /// <param name="turnContext">Turn context.</param>
409 /// <param name="teamId">ID of the Teams team.</param>
410 /// <param name="cancellationToken">Cancellation token.</param>
411 /// <returns>List of channel information.</returns>
412 public static async Task<ConversationList> GetTeamChannelsAsync(
413 ITurnContext turnContext,
414 string? teamId = null,
415 CancellationToken cancellationToken = default)
416 {
417 ArgumentNullException.ThrowIfNull(turnContext);
418 string t = teamId ?? turnContext.Activity.TeamsGetTeamInfo()?.Id
419 ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team.");
420
421 Uri serviceUrl = new(GetServiceUrl(turnContext));
422 AgenticIdentity identity = GetIdentity(turnContext);
423
424 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/teams/{Uri.EscapeDataString(t)}/conversations";
425
426 ConversationClient client = GetConversationClient(turnContext);
427
428 return (await client.BotHttpClient.SendAsync<ConversationList>(
429 HttpMethod.Get,
430 url,
431 body: null,
432 new BotRequestOptions { AgenticIdentity = identity },
433 cancellationToken).ConfigureAwait(false))!;
434 }
435
436 #endregion
437
438
439 #region Batch Messaging Methods
440
441 /// <summary>
442 /// Sends a message to the provided list of Teams members.
443 /// </summary>
444 /// <param name="turnContext">Turn context.</param>
445 /// <param name="activity">The activity to send.</param>
446 /// <param name="teamsMembers">The list of members.</param>
447 /// <param name="tenantId">The tenant ID.</param>
448 /// <param name="cancellationToken">Cancellation token.</param>
449 /// <returns>The operation Id.</returns>
450 public static async Task<string> SendMessageToListOfUsersAsync(
451 ITurnContext turnContext,
452 IActivity activity,
453 IList<BotFrameworkTeams.TeamMember> teamsMembers,
454 string tenantId,
455 CancellationToken cancellationToken = default)
456 {
457 ArgumentNullException.ThrowIfNull(turnContext);
458 activity = activity ?? throw new InvalidOperationException($"{nameof(activity)} is required.");
459 teamsMembers = teamsMembers ?? throw new InvalidOperationException($"{nameof(teamsMembers)} is required.");
460 tenantId = tenantId ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
461
462 ConversationClient client = GetConversationClient(turnContext);
463 Uri serviceUrl = new(GetServiceUrl(turnContext));
464 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
465
466 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/users/";
467 SendMessageToUsersRequest request = new()
468 {
469 Members = teamsMembers,
470 Activity = activity,
471 TenantId = tenantId
472 };
473 string body = JsonConvert.SerializeObject(request);
474
475 return (await client.BotHttpClient.SendAsync<string>(
476 HttpMethod.Post,
477 url,
478 body,
479 CreateRequestOptions(agenticIdentity, "sending message to list of users", DefaultCustomHeaders),
480 cancellationToken).ConfigureAwait(false))!;
481 }
482
483 /// <summary>
484 /// Sends a message to the provided list of Teams channels.
485 /// </summary>
486 /// <param name="turnContext">Turn context.</param>
487 /// <param name="activity">The activity to send.</param>
488 /// <param name="channelsMembers">The list of channels.</param>
489 /// <param name="tenantId">The tenant ID.</param>
490 /// <param name="cancellationToken">Cancellation token.</param>
491 /// <returns>The operation Id.</returns>
492 public static async Task<string> SendMessageToListOfChannelsAsync(
493 ITurnContext turnContext,
494 IActivity activity,
495 IList<BotFrameworkTeams.TeamMember> channelsMembers,
496 string tenantId,
497 CancellationToken cancellationToken = default)
498 {
499 ArgumentNullException.ThrowIfNull(turnContext);
500 activity = activity ?? throw new InvalidOperationException($"{nameof(activity)} is required.");
501 channelsMembers = channelsMembers ?? throw new InvalidOperationException($"{nameof(channelsMembers)} is required.");
502 tenantId = tenantId ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
503
504 ConversationClient client = GetConversationClient(turnContext);
505 Uri serviceUrl = new(GetServiceUrl(turnContext));
506 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
507 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/channels/";
508 SendMessageToUsersRequest request = new()
509 {
510 Members = channelsMembers,
511 Activity = activity,
512 TenantId = tenantId
513 };
514 string body = JsonConvert.SerializeObject(request);
515
516
517 return (await client.BotHttpClient.SendAsync<string>(
518 HttpMethod.Post,
519 url,
520 body,
521 CreateRequestOptions(agenticIdentity, "sending message to list of channels", DefaultCustomHeaders),
522 cancellationToken).ConfigureAwait(false))!;
523 }
524
525 /// <summary>
526 /// Sends a message to all the users in a team.
527 /// </summary>
528 /// <param name="turnContext">The turn context.</param>
529 /// <param name="activity">The activity to send to the users in the team.</param>
530 /// <param name="teamId">The team ID.</param>
531 /// <param name="tenantId">The tenant ID.</param>
532 /// <param name="cancellationToken">Cancellation token.</param>
533 /// <returns>The operation Id.</returns>
534 public static async Task<string> SendMessageToAllUsersInTeamAsync(
535 ITurnContext turnContext,
536 IActivity activity,
537 string teamId,
538 string tenantId,
539 CancellationToken cancellationToken = default)
540 {
541 ArgumentNullException.ThrowIfNull(turnContext);
542 activity = activity ?? throw new InvalidOperationException($"{nameof(activity)} is required.");
543 teamId = teamId ?? throw new InvalidOperationException($"{nameof(teamId)} is required.");
544 tenantId = tenantId ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
545
546 ConversationClient client = GetConversationClient(turnContext);
547 Uri serviceUrl = new(GetServiceUrl(turnContext));
548 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
549 if (activity is not Activity teamActivity)
550 throw new ArgumentException("Expected a Bot Framework Activity instance.", nameof(activity));
551 CoreActivity coreActivity = teamActivity.FromBotFrameworkActivity();
552
553 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/team/";
554 SendMessageToTeamRequest request = new()
555 {
556 Activity = activity,
557 TeamId = teamId,
558 TenantId = tenantId
559 };
560 string body = JsonConvert.SerializeObject(request);
561
562
563 return (await client.BotHttpClient.SendAsync<string>(
564 HttpMethod.Post,
565 url,
566 body,
567 CreateRequestOptions(agenticIdentity, "sending message to all users in team", DefaultCustomHeaders),
568 cancellationToken).ConfigureAwait(false))!;
569 }
570
571 /// <summary>
572 /// Sends a message to all the users in a tenant.
573 /// </summary>
574 /// <param name="turnContext">The turn context.</param>
575 /// <param name="activity">The activity to send to the tenant.</param>
576 /// <param name="tenantId">The tenant ID.</param>
577 /// <param name="cancellationToken">Cancellation token.</param>
578 /// <returns>The operation Id.</returns>
579 public static async Task<string> SendMessageToAllUsersInTenantAsync(
580 ITurnContext turnContext,
581 IActivity activity,
582 string tenantId,
583 CancellationToken cancellationToken = default)
584 {
585 ArgumentNullException.ThrowIfNull(turnContext);
586 activity = activity ?? throw new InvalidOperationException($"{nameof(activity)} is required.");
587 tenantId = tenantId ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
588
589 ConversationClient client = GetConversationClient(turnContext);
590 Uri serviceUrl = new(GetServiceUrl(turnContext));
591 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
592 if (activity is not Activity tenantActivity)
593 throw new ArgumentException("Expected a Bot Framework Activity instance.", nameof(activity));
594 CoreActivity coreActivity = tenantActivity.FromBotFrameworkActivity();
595
596 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/tenant/";
597 SendMessageToTenantRequest request = new()
598 {
599 Activity = activity,
600 TenantId = tenantId
601 };
602 string body = JsonConvert.SerializeObject(request);
603
604
605 return (await client.BotHttpClient.SendAsync<string>(
606 HttpMethod.Post,
607 url,
608 body,
609 CreateRequestOptions(agenticIdentity, "sending message to all users in tenant", DefaultCustomHeaders),
610 cancellationToken).ConfigureAwait(false))!;
611 }
612
613 /// <summary>
614 /// Creates a new thread in a team chat and sends an activity to that new thread.
615 /// Use this method if you are using CloudAdapter where credentials are handled by the adapter.
616 /// </summary>
617 /// <param name="turnContext">Turn context.</param>
618 /// <param name="activity">The activity to send on starting the new thread.</param>
619 /// <param name="teamsChannelId">The Team's Channel ID, note this is distinct from the Bot Framework activity property with same name.</param>
620 /// <param name="botAppId">The bot's appId.</param>
621 /// <param name="cancellationToken">Cancellation token.</param>
622 /// <returns>Tuple with conversation reference and activity id.</returns>
623 public static async Task<Tuple<ConversationReference, string>> SendMessageToTeamsChannelAsync(
624 ITurnContext turnContext,
625 IActivity activity,
626 string teamsChannelId,
627 string botAppId,
628 CancellationToken cancellationToken = default)
629 {
630 ArgumentNullException.ThrowIfNull(turnContext);
631
632 if (turnContext.Activity == null)
633 {
634 throw new InvalidOperationException(nameof(turnContext.Activity));
635 }
636
637 ArgumentException.ThrowIfNullOrWhiteSpace(teamsChannelId);
638
639 ConversationReference? conversationReference = null;
640 string newActivityId = string.Empty;
641 string serviceUrl = turnContext.Activity.ServiceUrl;
642 Microsoft.Bot.Schema.ConversationParameters conversationParameters = new()
643 {
644 IsGroup = true,
645 ChannelData = new BotFrameworkTeams.TeamsChannelData { Channel = new BotFrameworkTeams.ChannelInfo { Id = teamsChannelId } },
646 Activity = activity as Activity ?? throw new ArgumentException("Expected a Bot Framework Activity instance.", nameof(activity)),
647 };
648
649 await turnContext.Adapter.CreateConversationAsync(
650 botAppId,
651 Channels.Msteams,
652 serviceUrl,
653 null,
654 conversationParameters,
655 (t, ct) =>
656 {
657 conversationReference = t.Activity.GetConversationReference();
658 newActivityId = t.Activity.Id;
659 return Task.CompletedTask;
660 },
661 cancellationToken).ConfigureAwait(false);
662
663 return new Tuple<ConversationReference, string>(conversationReference!, newActivityId);
664 }
665
666 #endregion
667
668 #region Batch Operation Management
669
670 /// <summary>
671 /// Gets the state of an operation.
672 /// </summary>
673 /// <param name="turnContext">Turn context.</param>
674 /// <param name="operationId">The operationId to get the state of.</param>
675 /// <param name="cancellationToken">Cancellation token.</param>
676 /// <returns>The state and responses of the operation.</returns>
677 public static async Task<BotFrameworkTeams.BatchOperationState> GetOperationStateAsync(
678 ITurnContext turnContext,
679 string operationId,
680 CancellationToken cancellationToken = default)
681 {
682 ArgumentNullException.ThrowIfNull(turnContext);
683 operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");
684
685 ConversationClient client = GetConversationClient(turnContext);
686 Uri serviceUrl = new(GetServiceUrl(turnContext));
687 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
688 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}";
689
690
691 return (await client.BotHttpClient.SendAsync<BatchOperationState>(
692 HttpMethod.Get,
693 url,
694 body: null,
695 CreateRequestOptions(agenticIdentity, "getting operation state", DefaultCustomHeaders),
696 cancellationToken).ConfigureAwait(false))!;
697 }
698
699 /// <summary>
700 /// Gets the failed entries of a batch operation.
701 /// </summary>
702 /// <param name="turnContext">The turn context.</param>
703 /// <param name="operationId">The operationId to get the failed entries of.</param>
704 /// <param name="continuationToken">The continuation token.</param>
705 /// <param name="cancellationToken">Cancellation token.</param>
706 /// <returns>The list of failed entries of the operation.</returns>
707 public static async Task<BotFrameworkTeams.BatchFailedEntriesResponse> GetPagedFailedEntriesAsync(
708 ITurnContext turnContext,
709 string operationId,
710 string? continuationToken = null,
711 CancellationToken cancellationToken = default)
712 {
713 ArgumentNullException.ThrowIfNull(turnContext);
714 operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");
715
716 ConversationClient client = GetConversationClient(turnContext);
717 Uri serviceUrl = new(GetServiceUrl(turnContext));
718 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
719
720 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/failedentries/{Uri.EscapeDataString(operationId)}";
721
722 if (!string.IsNullOrWhiteSpace(continuationToken))
723 {
724 url += $"?continuationToken={Uri.EscapeDataString(continuationToken)}";
725 }
726
727 return (await client.BotHttpClient.SendAsync<BatchFailedEntriesResponse>(
728 HttpMethod.Get,
729 url,
730 body: null,
731 CreateRequestOptions(agenticIdentity, "getting paged failed entries", DefaultCustomHeaders),
732 cancellationToken).ConfigureAwait(false))!;
733 }
734
735 /// <summary>
736 /// Cancels a batch operation by its id.
737 /// </summary>
738 /// <param name="turnContext">The turn context.</param>
739 /// <param name="operationId">The id of the operation to cancel.</param>
740 /// <param name="cancellationToken">Cancellation token.</param>
741 /// <returns>A task representing the asynchronous operation.</returns>
742 public static async Task CancelOperationAsync(
743 ITurnContext turnContext,
744 string operationId,
745 CancellationToken cancellationToken = default)
746 {
747 ArgumentNullException.ThrowIfNull(turnContext);
748 operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");
749
750 ConversationClient client = GetConversationClient(turnContext);
751 Uri serviceUrl = new(GetServiceUrl(turnContext));
752 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
753
754 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}";
755
756 await client.BotHttpClient.SendAsync(
757 HttpMethod.Delete,
758 url,
759 body: null,
760 CreateRequestOptions(agenticIdentity, "cancelling operation", DefaultCustomHeaders),
761 cancellationToken).ConfigureAwait(false);
762 }
763
764 #endregion
765
766
767 private static BotRequestOptions CreateRequestOptions(AgenticIdentity? agenticIdentity, string operationDescription, CustomHeaders? customHeaders) =>
768 new()
769 {
770 AgenticIdentity = agenticIdentity,
771 OperationDescription = operationDescription,
772 DefaultHeaders = DefaultCustomHeaders,
773 CustomHeaders = customHeaders
774 };
775}
776