microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
samples/repro-da

Branches

Tags

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

Clone

HTTPS

Download ZIP

Libraries/Microsoft.Teams.Api/Activities/Activity.cs

526lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4using System.Diagnostics.CodeAnalysis;
5using System.Text.Json;
6using System.Text.Json.Serialization;
7
8using Microsoft.Teams.Api.Entities;
9using Microsoft.Teams.Common;
10
11namespace Microsoft.Teams.Api.Activities;
12
13[JsonConverter(typeof(JsonConverter<ActivityType>))]
14public partial class ActivityType(string value) : StringEnum(value)
15{
16 public Type ToType()
17 {
18 if (IsTyping) return typeof(TypingActivity);
19 if (IsCommand) return typeof(CommandActivity);
20 if (IsCommandResult) return typeof(CommandResultActivity);
21 if (IsConversationUpdate) return typeof(ConversationUpdateActivity);
22 if (IsEndOfConversation) return typeof(EndOfConversationActivity);
23 if (IsInstallUpdate) return typeof(InstallUpdateActivity);
24 if (IsMessage) return typeof(MessageActivity);
25 if (IsMessageUpdate) return typeof(MessageUpdateActivity);
26 if (IsMessageDelete) return typeof(MessageDeleteActivity);
27 if (IsMessageReaction) return typeof(MessageReactionActivity);
28 if (IsEvent) return typeof(EventActivity);
29 if (IsInvoke) return typeof(InvokeActivity);
30 return typeof(Activity);
31 }
32
33 public string ToPrettyString()
34 {
35 var value = ToString();
36 return $"{value.First().ToString().ToUpper()}{value.AsSpan(1).ToString()}";
37 }
38}
39
40[JsonConverter(typeof(ActivityJsonConverter))]
41public partial interface IActivity : IConvertible, ICloneable
42{
43 public string Id { get; set; }
44
45 public ActivityType Type { get; set; }
46
47 public string? ReplyToId { get; set; }
48
49 public ChannelId ChannelId { get; set; }
50
51 public Account From { get; set; }
52
53 public Account Recipient { get; set; }
54
55 public Conversation Conversation { get; set; }
56
57 public ConversationReference? RelatesTo { get; set; }
58
59 public string? ServiceUrl { get; set; }
60
61 public string? Locale { get; set; }
62
63 public DateTime? Timestamp { get; set; }
64
65 public DateTime? LocalTimestamp { get; set; }
66
67 public IList<IEntity>? Entities { get; set; }
68
69 public ChannelData? ChannelData { get; set; }
70
71 public IDictionary<string, object?> Properties { get; set; }
72
73 /// <summary>
74 /// is this a streaming activity
75 /// </summary>
76 [JsonIgnore]
77 public bool IsStreaming { get; }
78
79 /// <summary>
80 /// get the activity type/name path
81 /// </summary>
82 public string GetPath();
83
84 /// <summary>
85 /// get the quote reply string form of this activity
86 /// </summary>
87 public string ToQuoteReply();
88}
89
90[JsonConverter(typeof(ActivityJsonConverter))]
91public partial class Activity : IActivity
92{
93 [JsonPropertyName("id")]
94 [JsonPropertyOrder(0)]
95 public string Id { get; set; }
96
97 [JsonPropertyName("type")]
98 [JsonPropertyOrder(10)]
99 public ActivityType Type { get; set; }
100
101 [JsonPropertyName("replyToId")]
102 [JsonPropertyOrder(20)]
103 public string? ReplyToId { get; set; }
104
105 [JsonPropertyName("channelId")]
106 [JsonPropertyOrder(30)]
107 public ChannelId ChannelId { get; set; } = ChannelId.MsTeams;
108
109 [JsonPropertyName("from")]
110 [JsonPropertyOrder(40)]
111 public Account From { get; set; }
112
113 [JsonPropertyName("recipient")]
114 [JsonPropertyOrder(50)]
115 public Account Recipient { get; set; }
116
117 [JsonPropertyName("conversation")]
118 [JsonPropertyOrder(60)]
119 public Conversation Conversation { get; set; }
120
121 [JsonPropertyName("relatesTo")]
122 [JsonPropertyOrder(70)]
123 public ConversationReference? RelatesTo { get; set; }
124
125 [JsonPropertyName("serviceUrl")]
126 [JsonPropertyOrder(80)]
127 public string? ServiceUrl { get; set; }
128
129 [JsonPropertyName("locale")]
130 [JsonPropertyOrder(90)]
131 public string? Locale { get; set; }
132
133 [JsonPropertyName("timestamp")]
134 [JsonPropertyOrder(100)]
135 public DateTime? Timestamp { get; set; }
136
137 [JsonPropertyName("localTimestamp")]
138 [JsonPropertyOrder(110)]
139 public DateTime? LocalTimestamp { get; set; }
140
141 [JsonPropertyName("entities")]
142 [JsonPropertyOrder(120)]
143 public IList<IEntity>? Entities { get; set; }
144
145 [JsonPropertyName("channelData")]
146 [JsonPropertyOrder(130)]
147 public ChannelData? ChannelData { get; set; }
148
149 [JsonExtensionData]
150 public IDictionary<string, object?> Properties { get; set; } = new Dictionary<string, object?>();
151
152 [JsonConstructor]
153 public Activity(string type)
154 {
155 Type = new(type);
156 }
157
158 public Activity(ActivityType type)
159 {
160 Type = type;
161 }
162
163 public Activity(IActivity activity)
164 {
165 Id = activity.Id;
166 Type = activity.Type;
167 ReplyToId = activity.ReplyToId;
168 ChannelId = activity.ChannelId;
169 From = activity.From;
170 Recipient = activity.Recipient;
171 Conversation = activity.Conversation;
172 RelatesTo = activity.RelatesTo;
173 ServiceUrl = activity.ServiceUrl;
174 Locale = activity.Locale;
175 Timestamp = activity.Timestamp;
176 LocalTimestamp = activity.LocalTimestamp;
177 Entities = activity.Entities;
178 ChannelData = activity.ChannelData;
179 Properties = activity.Properties;
180 }
181
182 [JsonIgnore]
183 public bool IsStreaming => Entities?.Any(entity => entity.Type == "streaminfo" && entity is StreamInfoEntity) ?? false;
184
185 public object Clone() => MemberwiseClone();
186 public virtual Activity Copy() => (Activity)Clone();
187 public virtual string GetPath() => string.Join(".", ["Activity", Type.ToPrettyString()]);
188
189 public virtual Activity WithId(string value)
190 {
191 Id = value;
192 return this;
193 }
194
195 public virtual Activity WithReplyToId(string value)
196 {
197 ReplyToId = value;
198 return this;
199 }
200
201 public virtual Activity WithChannelId(ChannelId value)
202 {
203 ChannelId = value;
204 return this;
205 }
206
207 public virtual Activity WithFrom(Account value)
208 {
209 From = value;
210 return this;
211 }
212
213 public virtual Activity WithConversation(Conversation value)
214 {
215 Conversation = value;
216 return this;
217 }
218
219 public virtual Activity WithRelatesTo(ConversationReference value)
220 {
221 RelatesTo = value;
222 return this;
223 }
224
225 public virtual Activity WithRecipient(Account value)
226 {
227 Recipient = value;
228 #pragma warning disable ExperimentalTeamsTargeted
229 Recipient.IsTargeted = null;
230 #pragma warning restore ExperimentalTeamsTargeted
231 return this;
232 }
233
234 [Experimental("ExperimentalTeamsTargeted")]
235 public virtual Activity WithRecipient(Account value, bool isTargeted)
236 {
237 Recipient = value;
238 #pragma warning disable ExperimentalTeamsTargeted
239 Recipient.IsTargeted = isTargeted ? true : null;
240 #pragma warning restore ExperimentalTeamsTargeted
241 return this;
242 }
243
244 public virtual Activity WithServiceUrl(string value)
245 {
246 ServiceUrl = value;
247 return this;
248 }
249
250 public virtual Activity WithTimestamp(DateTime value)
251 {
252 Timestamp = value;
253 return this;
254 }
255
256 public virtual Activity WithLocale(string value)
257 {
258 Locale = value;
259 return this;
260 }
261
262 public virtual Activity WithLocalTimestamp(DateTime value)
263 {
264 LocalTimestamp = value;
265 return this;
266 }
267
268 public virtual Activity WithData(ChannelData value)
269 {
270 ChannelData ??= new();
271 ChannelData.Merge(value);
272 NormalizeFeedback();
273 return this;
274 }
275
276 /// <summary>
277 /// The Teams service rejects <c>feedbackLoop</c> and <c>feedbackLoopEnabled</c>
278 /// set at the same time. When <see cref="ChannelData.FeedbackLoop"/> is set it
279 /// wins; otherwise a legacy <c>FeedbackLoopEnabled = true</c> is upgraded to
280 /// <see cref="FeedbackType.Default"/>.
281 /// </summary>
282 private void NormalizeFeedback()
283 {
284 if (ChannelData is null) return;
285
286 if (ChannelData.FeedbackLoop is not null)
287 {
288 ChannelData.FeedbackLoopEnabled = null;
289 }
290 else if (ChannelData.FeedbackLoopEnabled == true)
291 {
292 ChannelData.FeedbackLoop = new FeedbackLoop(FeedbackType.Default);
293 ChannelData.FeedbackLoopEnabled = null;
294 }
295 }
296
297 public virtual Activity WithData(string key, object? value)
298 {
299 ChannelData ??= new();
300 ChannelData.Properties[key] = value;
301 return this;
302 }
303
304 public virtual Activity WithAppId(string value)
305 {
306 ChannelData ??= new();
307 ChannelData.App ??= new App() { Id = value };
308 return this;
309 }
310
311 /// <summary>
312 /// add an entity
313 /// </summary>
314 public virtual Activity AddEntity(params IEntity[] entities)
315 {
316 Entities ??= [];
317
318 foreach (var entity in entities)
319 {
320 Entities.Add(entity);
321 }
322
323 return this;
324 }
325
326 public virtual Activity UpdateEntity(IEntity oldEntity, IEntity newEntity)
327 {
328 if (Entities != null)
329 {
330 Entities.Remove(oldEntity);
331 }
332 else
333 {
334 Entities = [];
335 }
336
337 Entities.Add(newEntity);
338 return this;
339 }
340
341 /// <summary>
342 /// ensures a single root level message entity exists
343 /// </summary>
344 private IMessageEntity GetRootLevelMessageEntity()
345 {
346 var messageEntity = Entities?.FirstOrDefault(
347 e => e.Type == "https://schema.org/Message" && e.OType == "Message"
348 ) as IMessageEntity;
349
350 if (messageEntity is null)
351 {
352 messageEntity = new MessageEntity()
353 {
354 Type = "https://schema.org/Message",
355 OType = "Message",
356 OContext = "https://schema.org"
357 };
358
359 AddEntity(messageEntity);
360 }
361
362 return messageEntity;
363 }
364
365 /// <summary>
366 /// add the `Generated By AI` label
367 /// </summary>
368 public virtual Activity AddAIGenerated()
369 {
370 var messageEntity = GetRootLevelMessageEntity();
371 messageEntity.AdditionalType ??= [];
372
373 if (!messageEntity.AdditionalType.Contains("AIGeneratedContent"))
374 {
375 messageEntity.AdditionalType.Add("AIGeneratedContent");
376 }
377
378 return this;
379 }
380
381 /// <summary>
382 /// add content sensitivity label
383 /// </summary>
384 /// <param name="name">the content title</param>
385 /// <param name="description">the content description</param>
386 /// <param name="pattern">the pattern</param>
387 public virtual Activity AddSensitivityLabel(string name, string? description = null, DefinedTerm? pattern = null)
388 {
389 return AddEntity(new SensitiveUsageEntity()
390 {
391 Name = name,
392 Description = description,
393 Pattern = pattern
394 });
395 }
396
397 /// <summary>
398 /// Legacy builder method of enabling default message feedback.
399 /// </summary>
400 /// <param name="value">Whether to enable default message feedback.</param>
401 public virtual Activity AddFeedback(bool value = true)
402 {
403 ChannelData ??= new();
404
405 if (value)
406 {
407 ChannelData.FeedbackLoop = new FeedbackLoop(FeedbackType.Default);
408 }
409 else
410 {
411 ChannelData.FeedbackLoop = null;
412 }
413
414 ChannelData.FeedbackLoopEnabled = null;
415 return this;
416 }
417
418 /// <summary>
419 /// Enable message feedback with an explicit mode (default or custom).
420 /// </summary>
421 /// <param name="mode">
422 /// <see cref="FeedbackType.Default"/> shows Teams' built-in thumbs up/down UI.
423 /// <see cref="FeedbackType.Custom"/> triggers a <c>message/fetchTask</c> invoke
424 /// so the bot can return its own task module dialog.
425 /// </param>
426 public virtual Activity AddFeedback(FeedbackType mode)
427 {
428 ChannelData ??= new();
429 ChannelData.FeedbackLoop = new FeedbackLoop(mode);
430 ChannelData.FeedbackLoopEnabled = null;
431 return this;
432 }
433
434 /// <summary>
435 /// add a citation
436 /// </summary>
437 public virtual Activity AddCitation(int position, CitationAppearance appearance)
438 {
439 var messageEntity = GetRootLevelMessageEntity();
440 var citationEntity = new CitationEntity(messageEntity);
441 citationEntity.Citation ??= [];
442 citationEntity.Citation.Add(new CitationEntity.Claim()
443 {
444 Position = position,
445 Appearance = appearance.ToDocument()
446 });
447
448 UpdateEntity(messageEntity, citationEntity);
449 return this;
450 }
451
452 public CommandActivity ToCommand() => (CommandActivity)this;
453 public CommandResultActivity ToCommandResult() => (CommandResultActivity)this;
454 public TypingActivity ToTyping() => (TypingActivity)this;
455 public InstallUpdateActivity ToInstallUpdate() => (InstallUpdateActivity)this;
456 public MessageActivity ToMessage() => (MessageActivity)this;
457 public MessageUpdateActivity ToMessageUpdate() => (MessageUpdateActivity)this;
458 public MessageDeleteActivity ToMessageDelete() => (MessageDeleteActivity)this;
459 public MessageReactionActivity ToMessageReaction() => (MessageReactionActivity)this;
460 public ConversationUpdateActivity ToConversationUpdate() => (ConversationUpdateActivity)this;
461 public EndOfConversationActivity ToEndOfConversation() => (EndOfConversationActivity)this;
462 public EventActivity ToEvent() => (EventActivity)this;
463 public InvokeActivity ToInvoke() => (InvokeActivity)this;
464
465 public Activity Merge(Activity from)
466 {
467 Id ??= from.Id;
468 ReplyToId ??= from.ReplyToId;
469 ChannelId ??= from.ChannelId;
470 From ??= from.From;
471 Recipient ??= from.Recipient;
472 Conversation ??= from.Conversation;
473 RelatesTo ??= from.RelatesTo;
474 ServiceUrl ??= from.ServiceUrl;
475 Locale ??= from.Locale;
476 Timestamp ??= from.Timestamp;
477 LocalTimestamp ??= from.LocalTimestamp;
478 AddEntity(from.Entities?.ToArray() ?? []);
479
480 if (from.ChannelData is not null)
481 {
482 WithData(from.ChannelData);
483 }
484
485 if (from.Properties is not null)
486 {
487 Properties ??= new Dictionary<string, object?>();
488
489 foreach (var kv in from.Properties)
490 {
491 Properties[kv.Key] = kv.Value;
492 }
493 }
494
495 return this;
496 }
497
498 public string ToQuoteReply()
499 {
500 var text = string.Empty;
501
502 if (this is MessageActivity message)
503 {
504 text = $"<p itemprop=\"preview\">{message.Text}</p>";
505 }
506
507 return $"""
508 <blockquote itemscope="" itemtype="http://schema.skype.com/Reply" itemid="{Id}">
509 <strong itemprop="mri" itemid="{From.Id}">
510 {From.Name}
511 </strong>
512 <span itemprop="time" itemid="{Id}"></span>
513 {text}
514 </blockquote>
515 """;
516 }
517
518 public override string ToString()
519 {
520 return JsonSerializer.Serialize(this, new JsonSerializerOptions()
521 {
522 WriteIndented = true,
523 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
524 });
525 }
526}