microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v2.0.8

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Apps/Schema/TeamsActivityBuilder.cs

433lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Diagnostics.CodeAnalysis;
5using Microsoft.Teams.Apps.Schema.Entities;
6using Microsoft.Teams.Core.Schema;
7
8namespace Microsoft.Teams.Apps.Schema;
9
10/// <summary>
11/// Provides a fluent API for building TeamsActivity instances.
12/// </summary>
13public class TeamsActivityBuilder : CoreActivityBuilder<TeamsActivity, TeamsActivityBuilder>
14{
15 /// <summary>
16 /// Initializes a new instance of the TeamsActivityBuilder class.
17 /// </summary>
18 internal TeamsActivityBuilder() : base(new TeamsActivity())
19 {
20 }
21
22 /// <summary>
23 /// Initializes a new instance of the TeamsActivityBuilder class with an existing activity.
24 /// </summary>
25 /// <param name="activity">The activity to build upon.</param>
26 internal TeamsActivityBuilder(TeamsActivity activity) : base(activity)
27 {
28 }
29
30 /// <summary>
31 /// Apply Conversation Reference from the specified activity.
32 /// </summary>
33 /// <param name="activity">The source activity to copy conversation reference from.</param>
34 /// <returns>The builder instance for chaining.</returns>
35 public TeamsActivityBuilder WithConversationReference(TeamsActivity activity)
36 {
37 ArgumentNullException.ThrowIfNull(activity);
38 ArgumentNullException.ThrowIfNull(activity.ChannelId);
39 ArgumentNullException.ThrowIfNull(activity.ServiceUrl);
40 ArgumentNullException.ThrowIfNull(activity.Conversation);
41 ArgumentNullException.ThrowIfNull(activity.From);
42 ArgumentNullException.ThrowIfNull(activity.Recipient);
43
44 WithServiceUrl(activity.ServiceUrl);
45 WithChannelId(activity.ChannelId);
46 WithConversation(activity.Conversation);
47 WithFrom(activity.Recipient);
48
49 return this;
50 }
51
52 /// <summary>
53 /// Sets the sender account information.
54 /// </summary>
55 /// <param name="from">The sender account.</param>
56 /// <returns>The builder instance for chaining.</returns>
57 public new TeamsActivityBuilder WithFrom(ConversationAccount? from)
58 {
59 _activity.From = from is TeamsConversationAccount teamsAccount
60 ? teamsAccount
61 : TeamsConversationAccount.FromConversationAccount(from)!;
62 return this;
63 }
64
65 /// <summary>
66 /// Sets the recipient account information.
67 /// </summary>
68 /// <param name="recipient">The recipient account.</param>
69 /// <returns>The builder instance for chaining.</returns>
70 public new TeamsActivityBuilder WithRecipient(ConversationAccount? recipient)
71 {
72 _activity.Recipient = recipient is TeamsConversationAccount teamsAccount
73 ? teamsAccount
74 : TeamsConversationAccount.FromConversationAccount(recipient)!;
75 return this;
76 }
77
78 /// <summary>
79 /// Sets the recipient account information and optionally marks this as a targeted message.
80 /// </summary>
81 /// <param name="recipient">The recipient account.</param>
82 /// <param name="isTargeted">If true, marks this as a targeted message visible only to the specified recipient.</param>
83 /// <returns>The builder instance for chaining.</returns>
84 [Experimental("ExperimentalTeamsTargeted")]
85 public TeamsActivityBuilder WithRecipient(ConversationAccount? recipient, bool isTargeted)
86 {
87 if (recipient is not null)
88 {
89 recipient.IsTargeted = isTargeted ? true : null;
90 _activity.Recipient = recipient is TeamsConversationAccount teamsAccount
91 ? teamsAccount
92 : TeamsConversationAccount.FromConversationAccount(recipient)!;
93 }
94 return this;
95 }
96
97 /// <summary>
98 /// Sets the conversation information.
99 /// </summary>
100 /// <param name="conversation">The conversation information.</param>
101 /// <returns>The builder instance for chaining.</returns>
102 public new TeamsActivityBuilder WithConversation(Conversation? conversation)
103 {
104 ArgumentNullException.ThrowIfNull(conversation);
105
106 _activity.Conversation = conversation is TeamsConversation teamsConv
107 ? teamsConv
108 : TeamsConversation.FromConversation(conversation);
109
110 return this;
111 }
112
113 /// <summary>
114 /// Sets the Teams-specific channel data.
115 /// </summary>
116 /// <param name="channelData">The channel data.</param>
117 /// <returns>The builder instance for chaining.</returns>
118 public TeamsActivityBuilder WithChannelData(TeamsChannelData? channelData)
119 {
120 _activity.ChannelData = channelData;
121 return this;
122 }
123
124 /// <summary>
125 /// Sets the entities collection.
126 /// </summary>
127 /// <param name="entities">The entities collection.</param>
128 /// <returns>The builder instance for chaining.</returns>
129 public TeamsActivityBuilder WithEntities(EntityList entities)
130 {
131 _activity.Entities = entities;
132 return this;
133 }
134
135 /// <summary>
136 /// Sets the attachments collection.
137 /// </summary>
138 /// <param name="attachments">The attachments collection.</param>
139 /// <returns>The builder instance for chaining.</returns>
140 public TeamsActivityBuilder WithAttachments(IList<TeamsAttachment> attachments)
141 {
142 if (_activity is MessageActivity msg)
143 msg.Attachments = attachments;
144 else
145 _activity.Properties["attachments"] = attachments;
146 return this;
147 }
148
149 // TODO: Builders should only have "With" methods, not "Add" methods.
150 /// <summary>
151 /// Replaces the attachments collection with a single attachment.
152 /// </summary>
153 /// <param name="attachment">The attachment to set. Passing null clears the attachments.</param>
154 /// <returns>The builder instance for chaining.</returns>
155 public TeamsActivityBuilder WithAttachment(TeamsAttachment? attachment)
156 {
157 IList<TeamsAttachment>? attachments = attachment is null ? null : [attachment];
158 if (_activity is MessageActivity msg)
159 msg.Attachments = attachments;
160 else
161 _activity.Properties["attachments"] = attachments;
162 return this;
163 }
164
165 /// <summary>
166 /// Adds an entity to the activity's Entities collection.
167 /// </summary>
168 /// <param name="entity">The entity to add.</param>
169 /// <returns>The builder instance for chaining.</returns>
170 public TeamsActivityBuilder AddEntity(Entity entity)
171 {
172 _activity.Entities ??= [];
173 _activity.Entities.Add(entity);
174 return this;
175 }
176
177 /// <summary>
178 /// Adds an attachment to the activity's Attachments collection.
179 /// </summary>
180 /// <param name="attachment">The attachment to add.</param>
181 /// <returns>The builder instance for chaining.</returns>
182 public TeamsActivityBuilder AddAttachment(TeamsAttachment attachment)
183 {
184 if (_activity is MessageActivity msg)
185 {
186 msg.Attachments ??= [];
187 msg.Attachments.Add(attachment);
188 }
189 else
190 {
191 if (!_activity.Properties.TryGetValue("attachments", out object? existing) || existing is not List<TeamsAttachment> list)
192 {
193 list = [];
194 _activity.Properties["attachments"] = list;
195 }
196 list.Add(attachment);
197 }
198 return this;
199 }
200
201 /// <summary>
202 /// Adds an Adaptive Card attachment to the activity.
203 /// </summary>
204 /// <param name="adaptiveCard">The Adaptive Card payload.</param>
205 /// <param name="configure">Optional callback to further configure the attachment before it is added.</param>
206 /// <returns>The builder instance for chaining.</returns>
207 public TeamsActivityBuilder AddAdaptiveCardAttachment(object adaptiveCard, Action<TeamsAttachmentBuilder>? configure = null)
208 {
209 TeamsAttachment attachment = BuildAdaptiveCardAttachment(adaptiveCard, configure);
210 return AddAttachment(attachment);
211 }
212
213 /// <summary>
214 /// Sets the activity attachments collection to a single Adaptive Card attachment.
215 /// </summary>
216 /// <param name="adaptiveCard">The Adaptive Card payload.</param>
217 /// <param name="configure">Optional callback to further configure the attachment.</param>
218 /// <returns>The builder instance for chaining.</returns>
219 public TeamsActivityBuilder WithAdaptiveCardAttachment(object adaptiveCard, Action<TeamsAttachmentBuilder>? configure = null)
220 {
221 TeamsAttachment attachment = BuildAdaptiveCardAttachment(adaptiveCard, configure);
222 return WithAttachment(attachment);
223 }
224
225 /// <summary>
226 /// Adds or sets the text content of the activity.
227 /// </summary>
228 /// <param name="text"></param>
229 /// <param name="textFormat"></param>
230 /// <returns></returns>
231 public TeamsActivityBuilder WithText(string text, string textFormat = "plain")
232 {
233 if (_activity is MessageActivity msg)
234 {
235 msg.Text = text;
236 msg.TextFormat = textFormat;
237 }
238 else
239 {
240 _activity.Properties["text"] = text;
241 _activity.Properties["textFormat"] = textFormat;
242 }
243 return this;
244 }
245
246 /// <summary>
247 /// With Suggested Actions
248 /// </summary>
249 /// <param name="suggestedActions"></param>
250 /// <returns></returns>
251 public TeamsActivityBuilder WithSuggestedActions(SuggestedActions suggestedActions)
252 {
253 ArgumentNullException.ThrowIfNull(_activity);
254 _activity.SuggestedActions = suggestedActions;
255 return this;
256 }
257
258 /// <summary>
259 /// Adds a quoted reply entity and appends a placeholder to the activity text.
260 /// The activity type must be set to Message (via <see cref="CoreActivityBuilder{TActivity,TBuilder}.WithType"/>) before calling this method.
261 /// </summary>
262 /// <param name="messageId">The ID of the message to quote.</param>
263 /// <param name="text">Optional text, appended to the quoted message placeholder.</param>
264 /// <returns>The builder instance for chaining.</returns>
265 /// <exception cref="InvalidOperationException">Thrown when the activity type is not Message.</exception>
266 [Experimental("ExperimentalTeamsQuotedReplies")]
267 public TeamsActivityBuilder AddQuote(string messageId, string? text = null)
268 {
269 ArgumentException.ThrowIfNullOrWhiteSpace(messageId);
270
271 if (_activity.Type != TeamsActivityType.Message)
272 {
273 throw new InvalidOperationException("AddQuote can only be used on message activities. Call WithType(TeamsActivityType.Message) first.");
274 }
275
276 QuotedReplyEntityExtensions.AddToActivity(_activity, messageId, text);
277
278 return this;
279 }
280
281 /// <summary>
282 /// Adds a targetedMessageInfo entity for Prompt Preview, referencing the inbound targeted-message id.
283 /// Any existing quotedReply entities and matching &lt;quoted messageId="..."/&gt; placeholders are stripped
284 /// to prevent collision with prompt preview. If a targetedMessageInfo entity is already present, no new entity is added.
285 /// The activity type must be set to Message (via <see cref="CoreActivityBuilder{TActivity,TBuilder}.WithType"/>) before calling this method.
286 /// </summary>
287 /// <remarks>
288 /// After the placeholder strip, the activity text is trimmed of leading and trailing whitespace.
289 /// </remarks>
290 /// <param name="messageId">The id of the inbound targeted message being responded to.</param>
291 /// <returns>The builder instance for chaining.</returns>
292 /// <exception cref="InvalidOperationException">Thrown when the activity type is not Message.</exception>
293 [Experimental("ExperimentalTeamsTargeted")]
294 public TeamsActivityBuilder WithTargetedMessageInfo(string messageId)
295 {
296 ArgumentException.ThrowIfNullOrWhiteSpace(messageId);
297
298 if (_activity.Type != TeamsActivityType.Message)
299 {
300 throw new InvalidOperationException("WithTargetedMessageInfo can only be used on message activities. Call WithType(TeamsActivityType.Message) first.");
301 }
302
303 TargetedMessageInfoEntityExtensions.AddToActivity(_activity, messageId);
304
305 return this;
306 }
307
308 /// <summary>
309 /// Adds a mention to the activity.
310 /// </summary>
311 /// <param name="account">The account to mention.</param>
312 /// <param name="text">Optional custom text for the mention. If null, uses the account name.</param>
313 /// <param name="addText">Whether to prepend the mention text to the activity's text content.</param>
314 /// <returns>The builder instance for chaining.</returns>
315 public TeamsActivityBuilder AddMention(ConversationAccount account, string? text = null, bool addText = true)
316 {
317 ArgumentNullException.ThrowIfNull(account);
318 MentionEntityExtensions.AddToActivity(_activity, account, text, addText);
319 return this;
320 }
321
322 /// <summary>
323 /// Adds a clientInfo entity to the activity.
324 /// </summary>
325 /// <param name="platform">The client platform (for example, Web or Desktop).</param>
326 /// <param name="country">The client's country/region code.</param>
327 /// <param name="timezone">The client's IANA timezone.</param>
328 /// <param name="locale">The client's locale (for example, en-US).</param>
329 /// <returns>The builder instance for chaining.</returns>
330 public TeamsActivityBuilder AddClientInfo(string? platform, string? country, string? timezone, string? locale)
331 {
332 ClientInfoEntityExtensions.AddToActivity(_activity, platform, country, timezone, locale);
333 return this;
334 }
335
336 /// <summary>
337 /// Adds a productInfo entity to the activity.
338 /// </summary>
339 /// <param name="id">The product identifier.</param>
340 /// <returns>The builder instance for chaining.</returns>
341 public TeamsActivityBuilder AddProductInfo(string? id)
342 {
343 ProductInfoEntityExtensions.AddToActivity(_activity, id);
344 return this;
345 }
346
347 /// <summary>
348 /// Adds the AI-generated content label to the root message entity.
349 /// </summary>
350 public TeamsActivityBuilder AddAIGenerated()
351 {
352 ArgumentNullException.ThrowIfNull(_activity);
353
354 OMessageEntityExtensions.AddAIGeneratedContent(_activity);
355 return this;
356 }
357
358 /// <summary>
359 /// Enables/disables feedback loop on the activity.
360 /// </summary>
361 public TeamsActivityBuilder AddFeedback(bool value = true)
362 {
363 ArgumentNullException.ThrowIfNull(_activity);
364
365 _activity.ChannelData ??= new TeamsChannelData();
366 _activity.ChannelData.FeedbackLoopEnabled = value;
367 return this;
368 }
369
370 /// <summary>
371 /// Configures feedback loop mode on the activity.
372 /// </summary>
373 /// <param name="mode">The feedback loop type. See <see cref="FeedbackType"/> for known values.</param>
374 /// <returns>The builder instance for chaining.</returns>
375 public TeamsActivityBuilder AddFeedback(string mode)
376 {
377 ArgumentNullException.ThrowIfNull(_activity);
378
379 _activity.ChannelData ??= new TeamsChannelData();
380 _activity.ChannelData.FeedbackLoop = new FeedbackLoop(mode);
381 _activity.ChannelData.FeedbackLoopEnabled = null;
382 return this;
383 }
384
385 /// <summary>
386 /// Adds a citation claim to the activity.
387 /// </summary>
388 public TeamsActivityBuilder AddCitation(int position, CitationAppearance appearance)
389 {
390 ArgumentNullException.ThrowIfNull(_activity);
391 ArgumentNullException.ThrowIfNull(appearance);
392
393 _activity.Entities ??= [];
394 CitationEntityExtensions.AddToActivity(_activity, position, appearance);
395 return this;
396 }
397
398 /// <summary>
399 /// Adds a content sensitivity label to the activity.
400 /// </summary>
401 /// <param name="name">The name of the sensitivity label.</param>
402 /// <param name="description">Optional description of the sensitivity label.</param>
403 /// <param name="pattern">Optional pattern associated with the sensitivity label.</param>
404 /// <returns>The builder instance for chaining.</returns>
405 public TeamsActivityBuilder AddSensitivityLabel(string name, string? description = null, DefinedTerm? pattern = null)
406 {
407 ArgumentNullException.ThrowIfNull(_activity);
408 SensitiveUsageEntityExtensions.AddToActivity(_activity, name, description, pattern);
409 return this;
410 }
411
412 /// <summary>
413 /// Builds and returns the configured TeamsActivity instance.
414 /// </summary>
415 /// <returns>The configured TeamsActivity.</returns>
416 public override TeamsActivity Build()
417 {
418 return _activity;
419 }
420
421 private static TeamsAttachment BuildAdaptiveCardAttachment(object adaptiveCard, Action<TeamsAttachmentBuilder>? configure)
422 {
423 ArgumentNullException.ThrowIfNull(adaptiveCard);
424
425 TeamsAttachmentBuilder attachmentBuilder = TeamsAttachment
426 .CreateBuilder()
427 .WithAdaptiveCard(adaptiveCard);
428
429 configure?.Invoke(attachmentBuilder);
430
431 return attachmentBuilder.Build();
432 }
433}
434