microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Bot.Compat/CompatTeamsInfo.cs

771lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5using Microsoft.Bot.Builder;
6using Microsoft.Bot.Connector;
7using Microsoft.Bot.Schema;
8using Microsoft.Bot.Schema.Teams;
9using Microsoft.Teams.Bot.Core;
10using Microsoft.Teams.Bot.Core.Http;
11using Microsoft.Teams.Bot.Core.Schema;
12using static Microsoft.Teams.Bot.Compat.CompatTeamsInfoModels;
13using BotFrameworkTeams = Microsoft.Bot.Schema.Teams;
14using CustomHeaders = System.Collections.Generic.Dictionary<string, string>;
15
16namespace Microsoft.Teams.Bot.Compat;
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 CompatTeamsInfo
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.FromCompatActivity();
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 = JsonSerializer.Serialize(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 = JsonSerializer.Serialize(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 = JsonSerializer.Serialize(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 CoreActivity coreActivity = ((Activity)activity).FromCompatActivity();
550
551 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/team/";
552 SendMessageToTeamRequest request = new()
553 {
554 Activity = activity,
555 TeamId = teamId,
556 TenantId = tenantId
557 };
558 string body = JsonSerializer.Serialize(request);
559
560
561 return (await client.BotHttpClient.SendAsync<string>(
562 HttpMethod.Post,
563 url,
564 body,
565 CreateRequestOptions(agenticIdentity, "sending message to all users in team", DefaultCustomHeaders),
566 cancellationToken).ConfigureAwait(false))!;
567 }
568
569 /// <summary>
570 /// Sends a message to all the users in a tenant.
571 /// </summary>
572 /// <param name="turnContext">The turn context.</param>
573 /// <param name="activity">The activity to send to the tenant.</param>
574 /// <param name="tenantId">The tenant ID.</param>
575 /// <param name="cancellationToken">Cancellation token.</param>
576 /// <returns>The operation Id.</returns>
577 public static async Task<string> SendMessageToAllUsersInTenantAsync(
578 ITurnContext turnContext,
579 IActivity activity,
580 string tenantId,
581 CancellationToken cancellationToken = default)
582 {
583 ArgumentNullException.ThrowIfNull(turnContext);
584 activity = activity ?? throw new InvalidOperationException($"{nameof(activity)} is required.");
585 tenantId = tenantId ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");
586
587 ConversationClient client = GetConversationClient(turnContext);
588 Uri serviceUrl = new(GetServiceUrl(turnContext));
589 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
590 CoreActivity coreActivity = ((Activity)activity).FromCompatActivity();
591
592 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/tenant/";
593 SendMessageToTenantRequest request = new()
594 {
595 Activity = activity,
596 TenantId = tenantId
597 };
598 string body = JsonSerializer.Serialize(request);
599
600
601 return (await client.BotHttpClient.SendAsync<string>(
602 HttpMethod.Post,
603 url,
604 body,
605 CreateRequestOptions(agenticIdentity, "sending message to all users in tenant", DefaultCustomHeaders),
606 cancellationToken).ConfigureAwait(false))!;
607 }
608
609 /// <summary>
610 /// Creates a new thread in a team chat and sends an activity to that new thread.
611 /// Use this method if you are using CloudAdapter where credentials are handled by the adapter.
612 /// </summary>
613 /// <param name="turnContext">Turn context.</param>
614 /// <param name="activity">The activity to send on starting the new thread.</param>
615 /// <param name="teamsChannelId">The Team's Channel ID, note this is distinct from the Bot Framework activity property with same name.</param>
616 /// <param name="botAppId">The bot's appId.</param>
617 /// <param name="cancellationToken">Cancellation token.</param>
618 /// <returns>Tuple with conversation reference and activity id.</returns>
619 public static async Task<Tuple<ConversationReference, string>> SendMessageToTeamsChannelAsync(
620 ITurnContext turnContext,
621 IActivity activity,
622 string teamsChannelId,
623 string botAppId,
624 CancellationToken cancellationToken = default)
625 {
626 ArgumentNullException.ThrowIfNull(turnContext);
627
628 if (turnContext.Activity == null)
629 {
630 throw new InvalidOperationException(nameof(turnContext.Activity));
631 }
632
633 ArgumentException.ThrowIfNullOrWhiteSpace(teamsChannelId);
634
635 ConversationReference? conversationReference = null;
636 string newActivityId = string.Empty;
637 string serviceUrl = turnContext.Activity.ServiceUrl;
638 Microsoft.Bot.Schema.ConversationParameters conversationParameters = new()
639 {
640 IsGroup = true,
641 ChannelData = new BotFrameworkTeams.TeamsChannelData { Channel = new BotFrameworkTeams.ChannelInfo { Id = teamsChannelId } },
642 Activity = (Activity)activity,
643 };
644
645 await turnContext.Adapter.CreateConversationAsync(
646 botAppId,
647 Channels.Msteams,
648 serviceUrl,
649 null,
650 conversationParameters,
651 (t, ct) =>
652 {
653 conversationReference = t.Activity.GetConversationReference();
654 newActivityId = t.Activity.Id;
655 return Task.CompletedTask;
656 },
657 cancellationToken).ConfigureAwait(false);
658
659 return new Tuple<ConversationReference, string>(conversationReference!, newActivityId);
660 }
661
662 #endregion
663
664 #region Batch Operation Management
665
666 /// <summary>
667 /// Gets the state of an operation.
668 /// </summary>
669 /// <param name="turnContext">Turn context.</param>
670 /// <param name="operationId">The operationId to get the state of.</param>
671 /// <param name="cancellationToken">Cancellation token.</param>
672 /// <returns>The state and responses of the operation.</returns>
673 public static async Task<BotFrameworkTeams.BatchOperationState> GetOperationStateAsync(
674 ITurnContext turnContext,
675 string operationId,
676 CancellationToken cancellationToken = default)
677 {
678 ArgumentNullException.ThrowIfNull(turnContext);
679 operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");
680
681 ConversationClient client = GetConversationClient(turnContext);
682 Uri serviceUrl = new(GetServiceUrl(turnContext));
683 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
684 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}";
685
686
687 return (await client.BotHttpClient.SendAsync<BatchOperationState>(
688 HttpMethod.Get,
689 url,
690 body: null,
691 CreateRequestOptions(agenticIdentity, "getting operation state", DefaultCustomHeaders),
692 cancellationToken).ConfigureAwait(false))!;
693 }
694
695 /// <summary>
696 /// Gets the failed entries of a batch operation.
697 /// </summary>
698 /// <param name="turnContext">The turn context.</param>
699 /// <param name="operationId">The operationId to get the failed entries of.</param>
700 /// <param name="continuationToken">The continuation token.</param>
701 /// <param name="cancellationToken">Cancellation token.</param>
702 /// <returns>The list of failed entries of the operation.</returns>
703 public static async Task<BotFrameworkTeams.BatchFailedEntriesResponse> GetPagedFailedEntriesAsync(
704 ITurnContext turnContext,
705 string operationId,
706 string? continuationToken = null,
707 CancellationToken cancellationToken = default)
708 {
709 ArgumentNullException.ThrowIfNull(turnContext);
710 operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");
711
712 ConversationClient client = GetConversationClient(turnContext);
713 Uri serviceUrl = new(GetServiceUrl(turnContext));
714 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
715
716 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/failedentries/{Uri.EscapeDataString(operationId)}";
717
718 if (!string.IsNullOrWhiteSpace(continuationToken))
719 {
720 url += $"?continuationToken={Uri.EscapeDataString(continuationToken)}";
721 }
722
723 return (await client.BotHttpClient.SendAsync<BatchFailedEntriesResponse>(
724 HttpMethod.Get,
725 url,
726 body: null,
727 CreateRequestOptions(agenticIdentity, "getting paged failed entries", DefaultCustomHeaders),
728 cancellationToken).ConfigureAwait(false))!;
729 }
730
731 /// <summary>
732 /// Cancels a batch operation by its id.
733 /// </summary>
734 /// <param name="turnContext">The turn context.</param>
735 /// <param name="operationId">The id of the operation to cancel.</param>
736 /// <param name="cancellationToken">Cancellation token.</param>
737 /// <returns>A task representing the asynchronous operation.</returns>
738 public static async Task CancelOperationAsync(
739 ITurnContext turnContext,
740 string operationId,
741 CancellationToken cancellationToken = default)
742 {
743 ArgumentNullException.ThrowIfNull(turnContext);
744 operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");
745
746 ConversationClient client = GetConversationClient(turnContext);
747 Uri serviceUrl = new(GetServiceUrl(turnContext));
748 AgenticIdentity agenticIdentity = GetIdentity(turnContext);
749
750 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}";
751
752 await client.BotHttpClient.SendAsync(
753 HttpMethod.Delete,
754 url,
755 body: null,
756 CreateRequestOptions(agenticIdentity, "cancelling operation", DefaultCustomHeaders),
757 cancellationToken).ConfigureAwait(false);
758 }
759
760 #endregion
761
762
763 private static BotRequestOptions CreateRequestOptions(AgenticIdentity? agenticIdentity, string operationDescription, CustomHeaders? customHeaders) =>
764 new()
765 {
766 AgenticIdentity = agenticIdentity,
767 OperationDescription = operationDescription,
768 DefaultHeaders = DefaultCustomHeaders,
769 CustomHeaders = customHeaders
770 };
771}
772