microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
samples/migration-bot

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

442lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5using Microsoft.Extensions.Logging;
6using Microsoft.Teams.Bot.Core.Http;
7using Microsoft.Teams.Bot.Core.Schema;
8
9namespace Microsoft.Teams.Bot.Apps;
10
11using CustomHeaders = Dictionary<string, string>;
12
13/// <summary>
14/// Provides methods for interacting with Teams-specific APIs.
15/// </summary>
16/// <param name="httpClient">The HTTP client instance used to send requests to the Teams service. Must not be null.</param>
17/// <param name="logger">The logger instance used for logging. Optional.</param>
18public class TeamsApiClient(HttpClient httpClient, ILogger<TeamsApiClient> logger = default!)
19{
20 private readonly BotHttpClient _botHttpClient = new(httpClient, logger);
21 internal const string TeamsHttpClientName = "TeamsAPXClient";
22
23 /// <summary>
24 /// Gets the default custom headers that will be included in all requests.
25 /// </summary>
26 public CustomHeaders DefaultCustomHeaders { get; } = [];
27
28 #region Team Operations
29
30 /// <summary>
31 /// Fetches the list of channels for a given team.
32 /// </summary>
33 /// <param name="teamId">The ID of the team. Cannot be null or whitespace.</param>
34 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
35 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
36 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
37 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
38 /// <returns>A task that represents the asynchronous operation. The task result contains the list of channels.</returns>
39 /// <exception cref="HttpRequestException">Thrown if the channel list could not be retrieved successfully.</exception>
40 public async Task<ChannelList> FetchChannelListAsync(string teamId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
41 {
42 ArgumentException.ThrowIfNullOrWhiteSpace(teamId);
43 ArgumentNullException.ThrowIfNull(serviceUrl);
44
45 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/teams/{Uri.EscapeDataString(teamId)}/conversations";
46
47 logger?.LogTrace("Fetching channel list from {Url}", url);
48
49 return (await _botHttpClient.SendAsync<ChannelList>(
50 HttpMethod.Get,
51 url,
52 body: null,
53 CreateRequestOptions(agenticIdentity, "fetching channel list", customHeaders),
54 cancellationToken).ConfigureAwait(false))!;
55 }
56
57 /// <summary>
58 /// Fetches details related to a team.
59 /// </summary>
60 /// <param name="teamId">The ID of the team. Cannot be null or whitespace.</param>
61 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
62 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
63 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
64 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
65 /// <returns>A task that represents the asynchronous operation. The task result contains the team details.</returns>
66 /// <exception cref="HttpRequestException">Thrown if the team details could not be retrieved successfully.</exception>
67 public async Task<TeamDetails> FetchTeamDetailsAsync(string teamId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
68 {
69 ArgumentException.ThrowIfNullOrWhiteSpace(teamId);
70 ArgumentNullException.ThrowIfNull(serviceUrl);
71
72 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/teams/{Uri.EscapeDataString(teamId)}";
73
74 logger?.LogTrace("Fetching team details from {Url}", url);
75
76 return (await _botHttpClient.SendAsync<TeamDetails>(
77 HttpMethod.Get,
78 url,
79 body: null,
80 CreateRequestOptions(agenticIdentity, "fetching team details", customHeaders),
81 cancellationToken).ConfigureAwait(false))!;
82 }
83
84 #endregion
85
86 #region Meeting Operations
87
88 /// <summary>
89 /// Fetches information about a meeting.
90 /// </summary>
91 /// <param name="meetingId">The ID of the meeting, encoded as a BASE64 string. Cannot be null or whitespace.</param>
92 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
93 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
94 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
95 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
96 /// <returns>A task that represents the asynchronous operation. The task result contains the meeting information.</returns>
97 /// <exception cref="HttpRequestException">Thrown if the meeting info could not be retrieved successfully.</exception>
98 public async Task<MeetingInfo> FetchMeetingInfoAsync(string meetingId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
99 {
100 ArgumentException.ThrowIfNullOrWhiteSpace(meetingId);
101 ArgumentNullException.ThrowIfNull(serviceUrl);
102
103 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}";
104
105 logger?.LogTrace("Fetching meeting info from {Url}", url);
106
107 return (await _botHttpClient.SendAsync<MeetingInfo>(
108 HttpMethod.Get,
109 url,
110 body: null,
111 CreateRequestOptions(agenticIdentity, "fetching meeting info", customHeaders),
112 cancellationToken).ConfigureAwait(false))!;
113 }
114
115 /// <summary>
116 /// Fetches details for a meeting participant.
117 /// </summary>
118 /// <param name="meetingId">The ID of the meeting. Cannot be null or whitespace.</param>
119 /// <param name="participantId">The ID of the participant. Cannot be null or whitespace.</param>
120 /// <param name="tenantId">The ID of the tenant. Cannot be null or whitespace.</param>
121 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
122 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
123 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
124 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
125 /// <returns>A task that represents the asynchronous operation. The task result contains the participant details.</returns>
126 /// <exception cref="HttpRequestException">Thrown if the participant details could not be retrieved successfully.</exception>
127 public async Task<MeetingParticipant> FetchParticipantAsync(string meetingId, string participantId, string tenantId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
128 {
129 ArgumentException.ThrowIfNullOrWhiteSpace(meetingId);
130 ArgumentException.ThrowIfNullOrWhiteSpace(participantId);
131 ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
132 ArgumentNullException.ThrowIfNull(serviceUrl);
133
134 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}/participants/{Uri.EscapeDataString(participantId)}?tenantId={Uri.EscapeDataString(tenantId)}";
135
136 logger?.LogTrace("Fetching meeting participant from {Url}", url);
137
138 return (await _botHttpClient.SendAsync<MeetingParticipant>(
139 HttpMethod.Get,
140 url,
141 body: null,
142 CreateRequestOptions(agenticIdentity, "fetching meeting participant", customHeaders),
143 cancellationToken).ConfigureAwait(false))!;
144 }
145
146 /// <summary>
147 /// Sends a notification to meeting participants.
148 /// </summary>
149 /// <param name="meetingId">The ID of the meeting. Cannot be null or whitespace.</param>
150 /// <param name="notification">The notification to send. Cannot be null.</param>
151 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
152 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
153 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
154 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
155 /// <returns>A task that represents the asynchronous operation. The task result contains information about failed recipients.</returns>
156 /// <exception cref="HttpRequestException">Thrown if the notification could not be sent successfully.</exception>
157 public async Task<MeetingNotificationResponse> SendMeetingNotificationAsync(string meetingId, TargetedMeetingNotification notification, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
158 {
159 ArgumentException.ThrowIfNullOrWhiteSpace(meetingId);
160 ArgumentNullException.ThrowIfNull(notification);
161 ArgumentNullException.ThrowIfNull(serviceUrl);
162
163 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}/notification";
164 string body = JsonSerializer.Serialize(notification);
165
166 logger?.LogTrace("Sending meeting notification to {Url}: {Notification}", url, body);
167
168 return (await _botHttpClient.SendAsync<MeetingNotificationResponse>(
169 HttpMethod.Post,
170 url,
171 body,
172 CreateRequestOptions(agenticIdentity, "sending meeting notification", customHeaders),
173 cancellationToken).ConfigureAwait(false))!;
174 }
175
176 #endregion
177
178 #region Batch Message Operations
179
180 /// <summary>
181 /// Sends a message to a list of Teams users.
182 /// </summary>
183 /// <param name="activity">The activity to send. Cannot be null.</param>
184 /// <param name="teamsMembers">The list of team members to send the message to. Cannot be null or empty.</param>
185 /// <param name="tenantId">The ID of the tenant. Cannot be null or whitespace.</param>
186 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
187 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
188 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
189 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
190 /// <returns>A task that represents the asynchronous operation. The task result contains the operation ID.</returns>
191 /// <exception cref="HttpRequestException">Thrown if the message could not be sent successfully.</exception>
192 public async Task<string> SendMessageToListOfUsersAsync(CoreActivity activity, IList<TeamMember> teamsMembers, string tenantId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
193 {
194 ArgumentNullException.ThrowIfNull(activity);
195 ArgumentNullException.ThrowIfNull(teamsMembers);
196 if (teamsMembers.Count == 0)
197 {
198 throw new ArgumentException("teamsMembers cannot be empty", nameof(teamsMembers));
199 }
200 ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
201 ArgumentNullException.ThrowIfNull(serviceUrl);
202
203 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/users/";
204 SendMessageToUsersRequest request = new()
205 {
206 Members = teamsMembers,
207 Activity = activity,
208 TenantId = tenantId
209 };
210 string body = JsonSerializer.Serialize(request);
211
212 logger?.LogTrace("Sending message to list of users at {Url}: {Request}", url, body);
213
214 return (await _botHttpClient.SendAsync<string>(
215 HttpMethod.Post,
216 url,
217 body,
218 CreateRequestOptions(agenticIdentity, "sending message to list of users", customHeaders),
219 cancellationToken).ConfigureAwait(false))!;
220 }
221
222 /// <summary>
223 /// Sends a message to all users in a tenant.
224 /// </summary>
225 /// <param name="activity">The activity to send. Cannot be null.</param>
226 /// <param name="tenantId">The ID of the tenant. Cannot be null or whitespace.</param>
227 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
228 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
229 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
230 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
231 /// <returns>A task that represents the asynchronous operation. The task result contains the operation ID.</returns>
232 /// <exception cref="HttpRequestException">Thrown if the message could not be sent successfully.</exception>
233 public async Task<string> SendMessageToAllUsersInTenantAsync(CoreActivity activity, string tenantId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
234 {
235 ArgumentNullException.ThrowIfNull(activity);
236 ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
237 ArgumentNullException.ThrowIfNull(serviceUrl);
238
239 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/tenant/";
240 SendMessageToTenantRequest request = new()
241 {
242 Activity = activity,
243 TenantId = tenantId
244 };
245 string body = JsonSerializer.Serialize(request);
246
247 logger?.LogTrace("Sending message to all users in tenant at {Url}: {Request}", url, body);
248
249 return (await _botHttpClient.SendAsync<string>(
250 HttpMethod.Post,
251 url,
252 body,
253 CreateRequestOptions(agenticIdentity, "sending message to all users in tenant", customHeaders),
254 cancellationToken).ConfigureAwait(false))!;
255 }
256
257 /// <summary>
258 /// Sends a message to all users in a team.
259 /// </summary>
260 /// <param name="activity">The activity to send. Cannot be null.</param>
261 /// <param name="teamId">The ID of the team. Cannot be null or whitespace.</param>
262 /// <param name="tenantId">The ID of the tenant. Cannot be null or whitespace.</param>
263 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
264 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
265 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
266 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
267 /// <returns>A task that represents the asynchronous operation. The task result contains the operation ID.</returns>
268 /// <exception cref="HttpRequestException">Thrown if the message could not be sent successfully.</exception>
269 public async Task<string> SendMessageToAllUsersInTeamAsync(CoreActivity activity, string teamId, string tenantId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
270 {
271 ArgumentNullException.ThrowIfNull(activity);
272 ArgumentException.ThrowIfNullOrWhiteSpace(teamId);
273 ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
274 ArgumentNullException.ThrowIfNull(serviceUrl);
275
276 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/team/";
277 SendMessageToTeamRequest request = new()
278 {
279 Activity = activity,
280 TeamId = teamId,
281 TenantId = tenantId
282 };
283 string body = JsonSerializer.Serialize(request);
284
285 logger?.LogTrace("Sending message to all users in team at {Url}: {Request}", url, body);
286
287 return (await _botHttpClient.SendAsync<string>(
288 HttpMethod.Post,
289 url,
290 body,
291 CreateRequestOptions(agenticIdentity, "sending message to all users in team", customHeaders),
292 cancellationToken).ConfigureAwait(false))!;
293 }
294
295 /// <summary>
296 /// Sends a message to a list of Teams channels.
297 /// </summary>
298 /// <param name="activity">The activity to send. Cannot be null.</param>
299 /// <param name="channelMembers">The list of channels to send the message to. Cannot be null or empty.</param>
300 /// <param name="tenantId">The ID of the tenant. Cannot be null or whitespace.</param>
301 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
302 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
303 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
304 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
305 /// <returns>A task that represents the asynchronous operation. The task result contains the operation ID.</returns>
306 /// <exception cref="HttpRequestException">Thrown if the message could not be sent successfully.</exception>
307 public async Task<string> SendMessageToListOfChannelsAsync(CoreActivity activity, IList<TeamMember> channelMembers, string tenantId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
308 {
309 ArgumentNullException.ThrowIfNull(activity);
310 ArgumentNullException.ThrowIfNull(channelMembers);
311 if (channelMembers.Count == 0)
312 {
313 throw new ArgumentException("channelMembers cannot be empty", nameof(channelMembers));
314 }
315 ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
316 ArgumentNullException.ThrowIfNull(serviceUrl);
317
318 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/channels/";
319 SendMessageToUsersRequest request = new()
320 {
321 Members = channelMembers,
322 Activity = activity,
323 TenantId = tenantId
324 };
325 string body = JsonSerializer.Serialize(request);
326
327 logger?.LogTrace("Sending message to list of channels at {Url}: {Request}", url, body);
328
329 return (await _botHttpClient.SendAsync<string>(
330 HttpMethod.Post,
331 url,
332 body,
333 CreateRequestOptions(agenticIdentity, "sending message to list of channels", customHeaders),
334 cancellationToken).ConfigureAwait(false))!;
335 }
336
337 #endregion
338
339 #region Batch Operation Management
340
341 /// <summary>
342 /// Gets the state of a batch operation.
343 /// </summary>
344 /// <param name="operationId">The ID of the operation. Cannot be null or whitespace.</param>
345 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
346 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
347 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
348 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
349 /// <returns>A task that represents the asynchronous operation. The task result contains the operation state.</returns>
350 /// <exception cref="HttpRequestException">Thrown if the operation state could not be retrieved successfully.</exception>
351 public async Task<BatchOperationState> GetOperationStateAsync(string operationId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
352 {
353 ArgumentException.ThrowIfNullOrWhiteSpace(operationId);
354 ArgumentNullException.ThrowIfNull(serviceUrl);
355
356 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}";
357
358 logger?.LogTrace("Getting operation state from {Url}", url);
359
360 return (await _botHttpClient.SendAsync<BatchOperationState>(
361 HttpMethod.Get,
362 url,
363 body: null,
364 CreateRequestOptions(agenticIdentity, "getting operation state", customHeaders),
365 cancellationToken).ConfigureAwait(false))!;
366 }
367
368 /// <summary>
369 /// Gets the failed entries of a batch operation with error code and message.
370 /// </summary>
371 /// <param name="operationId">The ID of the operation. Cannot be null or whitespace.</param>
372 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
373 /// <param name="continuationToken">Optional continuation token for pagination.</param>
374 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
375 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
376 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
377 /// <returns>A task that represents the asynchronous operation. The task result contains the failed entries.</returns>
378 /// <exception cref="HttpRequestException">Thrown if the failed entries could not be retrieved successfully.</exception>
379 public async Task<BatchFailedEntriesResponse> GetPagedFailedEntriesAsync(string operationId, Uri serviceUrl, string? continuationToken = null, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
380 {
381 ArgumentException.ThrowIfNullOrWhiteSpace(operationId);
382 ArgumentNullException.ThrowIfNull(serviceUrl);
383
384 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/failedentries/{Uri.EscapeDataString(operationId)}";
385
386 if (!string.IsNullOrWhiteSpace(continuationToken))
387 {
388 url += $"?continuationToken={Uri.EscapeDataString(continuationToken)}";
389 }
390
391 logger?.LogTrace("Getting paged failed entries from {Url}", url);
392
393 return (await _botHttpClient.SendAsync<BatchFailedEntriesResponse>(
394 HttpMethod.Get,
395 url,
396 body: null,
397 CreateRequestOptions(agenticIdentity, "getting paged failed entries", customHeaders),
398 cancellationToken).ConfigureAwait(false))!;
399 }
400
401 /// <summary>
402 /// Cancels a batch operation by its ID.
403 /// </summary>
404 /// <param name="operationId">The ID of the operation to cancel. Cannot be null or whitespace.</param>
405 /// <param name="serviceUrl">The service URL for the Teams service. Cannot be null.</param>
406 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
407 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
408 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
409 /// <returns>A task that represents the asynchronous operation.</returns>
410 /// <exception cref="HttpRequestException">Thrown if the operation could not be cancelled successfully.</exception>
411 public async Task CancelOperationAsync(string operationId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
412 {
413 ArgumentException.ThrowIfNullOrWhiteSpace(operationId);
414 ArgumentNullException.ThrowIfNull(serviceUrl);
415
416 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}";
417
418 logger?.LogTrace("Cancelling operation at {Url}", url);
419
420 await _botHttpClient.SendAsync(
421 HttpMethod.Delete,
422 url,
423 body: null,
424 CreateRequestOptions(agenticIdentity, "cancelling operation", customHeaders),
425 cancellationToken).ConfigureAwait(false);
426 }
427
428 #endregion
429
430 #region Private Methods
431
432 private BotRequestOptions CreateRequestOptions(AgenticIdentity? agenticIdentity, string operationDescription, CustomHeaders? customHeaders) =>
433 new()
434 {
435 AgenticIdentity = agenticIdentity,
436 OperationDescription = operationDescription,
437 DefaultHeaders = DefaultCustomHeaders,
438 CustomHeaders = customHeaders
439 };
440
441 #endregion
442}
443