microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/msal

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.MessageExtensions/Program.cs

637lines · modecode

1using System.Text.Json;
2
3using Microsoft.Teams.Api.Cards;
4using Microsoft.Teams.Apps;
5using Microsoft.Teams.Apps.Activities;
6using Microsoft.Teams.Apps.Activities.Invokes;
7using Microsoft.Teams.Apps.Annotations;
8using Microsoft.Teams.Apps.Extensions;
9using Microsoft.Teams.Cards;
10using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions;
11using Microsoft.Teams.Plugins.AspNetCore.Extensions;
12
13namespace Samples.MessageExtensions;
14
15public static partial class Program
16{
17 public static void Main(string[] args)
18 {
19 var builder = WebApplication.CreateBuilder(args);
20 builder.Services.AddTransient<Controller>();
21 builder.AddTeams().AddTeamsDevTools();
22
23 var app = builder.Build();
24
25 app.UseHttpsRedirection();
26
27 // Log raw requests
28 app.Use(async (context, next) =>
29 {
30 if (context.Request.Method == "POST")
31 {
32 context.Request.EnableBuffering();
33 var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
34 context.Request.Body.Position = 0;
35
36 // var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
37 Console.WriteLine($"[RAW_REQUEST] {context.Request.Method} {context.Request.Path}: {body}");
38 }
39
40 await next();
41 });
42
43 app.UseTeams();
44
45 // Serve settings page
46 app.MapGet("/settings", () => Results.Content(GetSettingsHtml(), "text/html"));
47
48 app.Run();
49 }
50
51 [TeamsController]
52 public class Controller
53 {
54 private readonly IConfiguration _configuration;
55
56 public Controller(IConfiguration configuration)
57 {
58 _configuration = configuration;
59 }
60
61 [Message]
62 public async System.Threading.Tasks.Task OnMessage([Context] Microsoft.Teams.Api.Activities.MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log)
63 {
64 log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}");
65 log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}");
66
67 await client.Send($"Echo: {activity.Text}\n\nThis is a message extension bot. Use the message extension commands in Teams to test functionality.");
68 }
69
70 [MessageExtension.Query]
71 public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQuery(
72 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QueryActivity activity,
73 [Context] IContext.Client client,
74 [Context] Microsoft.Teams.Common.Logging.ILogger log)
75 {
76 log.Info("[MESSAGE_EXT_QUERY] Search query received");
77
78 var commandId = activity.Value?.CommandId;
79 var query = activity.Value?.Parameters?.FirstOrDefault(p => p.Name == "searchQuery")?.Value?.ToString() ?? "";
80
81 log.Info($"[MESSAGE_EXT_QUERY] Command: {commandId}, Query: {query}");
82
83 if (commandId == "searchQuery")
84 {
85 return CreateSearchResults(query, log);
86 }
87
88 return new Microsoft.Teams.Api.MessageExtensions.Response
89 {
90 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
91 {
92 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
93 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
94 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>()
95 }
96 };
97 }
98
99 [MessageExtension.SubmitAction]
100 public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSubmit(
101 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity,
102 [Context] IContext.Client client,
103 [Context] Microsoft.Teams.Common.Logging.ILogger log)
104 {
105 log.Info("[MESSAGE_EXT_SUBMIT] Action submit received");
106
107 var commandId = activity.Value?.CommandId;
108 var data = activity.Value?.Data as JsonElement?;
109
110 log.Info($"[MESSAGE_EXT_SUBMIT] Command: {commandId}");
111 log.Info($"[MESSAGE_EXT_SUBMIT] Data: {JsonSerializer.Serialize(data)}");
112
113 switch (commandId)
114 {
115 case "createCard":
116 return HandleCreateCard(data, log);
117
118 case "getMessageDetails":
119 return HandleGetMessageDetails(activity, log);
120
121 default:
122 log.Error($"[MESSAGE_EXT_SUBMIT] Unknown command: {commandId}");
123 return CreateErrorActionResponse("Unknown command");
124 }
125 }
126
127 [MessageExtension.QueryLink]
128 public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQueryLink(
129 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QueryLinkActivity activity,
130 [Context] IContext.Client client,
131 [Context] Microsoft.Teams.Common.Logging.ILogger log)
132 {
133 log.Info("[MESSAGE_EXT_QUERY_LINK] Link unfurling received");
134
135 var url = activity.Value?.Url;
136 log.Info($"[MESSAGE_EXT_QUERY_LINK] URL: {url}");
137
138 if (string.IsNullOrEmpty(url))
139 {
140 return CreateErrorResponse("No URL provided");
141 }
142
143 return CreateLinkUnfurlResponse(url, log);
144 }
145
146 [MessageExtension.SelectItem]
147 public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSelectItem(
148 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SelectItemActivity activity,
149 [Context] IContext.Client client,
150 [Context] Microsoft.Teams.Common.Logging.ILogger log)
151 {
152 log.Info("[MESSAGE_EXT_SELECT_ITEM] Item selection received");
153
154 var selectedItem = activity.Value;
155 log.Info($"[MESSAGE_EXT_SELECT_ITEM] Selected: {JsonSerializer.Serialize(selectedItem)}");
156
157 return CreateItemSelectionResponse(selectedItem, log);
158 }
159
160 [MessageExtension.QuerySettingsUrl]
161 public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQuerySettingsUrl(
162 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QuerySettingUrlActivity activity,
163 [Context] IContext.Client client,
164 [Context] Microsoft.Teams.Common.Logging.ILogger log)
165 {
166 log.Info("[MESSAGE_EXT_QUERY_SETTINGS_URL] Settings URL requested");
167
168 return new Microsoft.Teams.Api.MessageExtensions.Response
169 {
170 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
171 {
172 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Config,
173 Text = "Settings configuration would be handled here"
174 }
175 };
176 }
177
178 [MessageExtension.FetchTask]
179 public Microsoft.Teams.Api.MessageExtensions.ActionResponse OnMessageExtensionFetchTask(
180 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.FetchTaskActivity activity,
181 [Context] Microsoft.Teams.Common.Logging.ILogger log)
182 {
183 log.Info("[MESSAGE_EXT_FETCH_TASK] Fetch task received");
184
185 var commandId = activity.Value?.CommandId;
186 log.Info($"[MESSAGE_EXT_FETCH_TASK] Command: {commandId}");
187
188 return CreateFetchTaskResponse(commandId, log);
189 }
190
191 [MessageExtension.Setting]
192 public Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSetting(
193 [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SettingActivity activity,
194 [Context] IContext.Client client,
195 [Context] Microsoft.Teams.Common.Logging.ILogger log)
196 {
197 log.Info("[MESSAGE_EXT_SETTING] Settings received");
198
199 var state = activity.Value?.State;
200 log.Info($"[MESSAGE_EXT_SETTING] State: {state}");
201
202 if (state == "cancel")
203 {
204 log.Info("[MESSAGE_EXT_SETTING] Settings cancelled by user");
205 return new Microsoft.Teams.Api.MessageExtensions.Response();
206 }
207
208 // Process settings data
209 // Note: Settings property may not be available in current API
210 log.Info("[MESSAGE_EXT_SETTING] Settings processing completed");
211
212 return new Microsoft.Teams.Api.MessageExtensions.Response();
213 }
214
215 // Helper methods for creating responses
216 private static Microsoft.Teams.Api.MessageExtensions.Response CreateSearchResults(string query, Microsoft.Teams.Common.Logging.ILogger log)
217 {
218 var attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>();
219
220 // Create simple search results
221 for (int i = 1; i <= 5; i++)
222 {
223 var card = new Microsoft.Teams.Cards.AdaptiveCard
224 {
225 Body = new List<CardElement>
226 {
227 new TextBlock($"Search Result {i}")
228 {
229 Weight = TextWeight.Bolder,
230 Size = TextSize.Large
231 },
232 new TextBlock($"Query: '{query}' - Result description for item {i}")
233 {
234 Wrap = true,
235 IsSubtle = true
236 }
237 }
238 };
239
240 var previewCard = new ThumbnailCard()
241 {
242 Title = $"Result {i}",
243 Text = $"This is a preview of result {i} for query '{query}'."
244 };
245
246 var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
247 {
248 ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard,
249 Content = card,
250 Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment
251 {
252 ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard,
253 Content = previewCard
254 }
255 };
256
257 attachments.Add(attachment);
258 }
259
260 return new Microsoft.Teams.Api.MessageExtensions.Response
261 {
262 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
263 {
264 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
265 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
266 Attachments = attachments
267 }
268 };
269 }
270
271 private static Microsoft.Teams.Api.MessageExtensions.Response HandleCreateCard(JsonElement? data, Microsoft.Teams.Common.Logging.ILogger log)
272 {
273 var title = GetJsonValue(data, "title") ?? "Default Title";
274 var description = GetJsonValue(data, "description") ?? "Default Description";
275
276 log.Info($"[CREATE_CARD] Title: {title}, Description: {description}");
277
278 var card = new Microsoft.Teams.Cards.AdaptiveCard
279 {
280 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
281 Body = new List<CardElement>
282 {
283 new TextBlock("Custom Card Created")
284 {
285 Weight = TextWeight.Bolder,
286 Size = TextSize.Large,
287 Color = TextColor.Good
288 },
289 new TextBlock(title)
290 {
291 Weight = TextWeight.Bolder,
292 Size = TextSize.Medium
293 },
294 new TextBlock(description)
295 {
296 Wrap = true,
297 IsSubtle = true
298 }
299 }
300 };
301
302 var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
303 {
304 ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard,
305 Content = card
306 };
307
308 return new Microsoft.Teams.Api.MessageExtensions.Response
309 {
310 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
311 {
312 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
313 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
314 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
315 }
316 };
317 }
318
319 private static Microsoft.Teams.Api.MessageExtensions.Response HandleGetMessageDetails(Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, Microsoft.Teams.Common.Logging.ILogger log)
320 {
321 var messageText = activity.Value?.MessagePayload?.Body?.Content ?? "No message content";
322 var messageId = activity.Value?.MessagePayload?.Id ?? "Unknown";
323
324 log.Info($"[GET_MESSAGE_DETAILS] Message ID: {messageId}");
325
326 var card = new Microsoft.Teams.Cards.AdaptiveCard
327 {
328 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
329 Body = new List<CardElement>
330 {
331 new TextBlock("Message Details")
332 {
333 Weight = TextWeight.Bolder,
334 Size = TextSize.Large,
335 Color = TextColor.Accent
336 },
337 new TextBlock($"Message ID: {messageId}")
338 {
339 Wrap = true
340 },
341 new TextBlock($"Content: {messageText}")
342 {
343 Wrap = true
344 }
345 }
346 };
347
348 var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
349 {
350 ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
351 Content = card
352 };
353
354 return new Microsoft.Teams.Api.MessageExtensions.Response
355 {
356 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
357 {
358 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
359 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
360 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
361 }
362 };
363 }
364
365 private static Microsoft.Teams.Api.MessageExtensions.Response CreateLinkUnfurlResponse(string url, Microsoft.Teams.Common.Logging.ILogger log)
366 {
367 var card = new Microsoft.Teams.Cards.AdaptiveCard
368 {
369 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
370 Body = new List<CardElement>
371 {
372 new TextBlock("Link Preview")
373 {
374 Weight = TextWeight.Bolder,
375 Size = TextSize.Medium
376 },
377 new TextBlock($"URL: {url}")
378 {
379 IsSubtle = true,
380 Wrap = true
381 },
382 new TextBlock("This is a preview of the linked content generated by the message extension.")
383 {
384 Wrap = true,
385 Size = TextSize.Small
386 }
387 }
388 };
389
390 var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
391 {
392 ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
393 Content = card,
394 Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment
395 {
396 ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard,
397 Content = new ThumbnailCard
398 {
399 Title = "Link Preview",
400 Text = url
401 }
402 }
403 };
404
405 return new Microsoft.Teams.Api.MessageExtensions.Response
406 {
407 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
408 {
409 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
410 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
411 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
412 }
413 };
414 }
415
416 private static Microsoft.Teams.Api.MessageExtensions.Response CreateItemSelectionResponse(object? selectedItem, Microsoft.Teams.Common.Logging.ILogger log)
417 {
418 var itemJson = JsonSerializer.Serialize(selectedItem);
419
420 var card = new Microsoft.Teams.Cards.AdaptiveCard
421 {
422 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
423 Body = new List<CardElement>
424 {
425 new TextBlock("Item Selected")
426 {
427 Weight = TextWeight.Bolder,
428 Size = TextSize.Large,
429 Color = TextColor.Good
430 },
431 new TextBlock("You selected the following item:")
432 {
433 Wrap = true
434 },
435 new TextBlock(itemJson)
436 {
437 Wrap = true,
438 FontType = FontType.Monospace,
439 Separator = true
440 }
441 }
442 };
443
444 var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
445 {
446 ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
447 Content = card
448 };
449
450 return new Microsoft.Teams.Api.MessageExtensions.Response
451 {
452 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
453 {
454 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
455 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
456 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
457 }
458 };
459 }
460
461 private static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorResponse(string message)
462 {
463 return new Microsoft.Teams.Api.MessageExtensions.Response
464 {
465 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
466 {
467 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message,
468 Text = message
469 }
470 };
471 }
472
473 private static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorActionResponse(string message)
474 {
475 return new Microsoft.Teams.Api.MessageExtensions.Response
476 {
477 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
478 {
479 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message,
480 Text = message
481 }
482 };
483 }
484
485 private static string? GetJsonValue(JsonElement? data, string key)
486 {
487 if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value))
488 {
489 return value.GetString();
490 }
491 return null;
492 }
493
494 private static Microsoft.Teams.Api.MessageExtensions.ActionResponse CreateFetchTaskResponse(string? commandId, Microsoft.Teams.Common.Logging.ILogger log)
495 {
496 log.Info($"[CREATE_FETCH_TASK] Creating task for command: {commandId}");
497 // Updated to use actual converation members
498
499 // Create an adaptive card for the task module
500 var card = new Microsoft.Teams.Cards.AdaptiveCard
501 {
502 Body = new List<CardElement>
503 {
504 new TextBlock("Conversation Members is not implemented in C# yet :(")
505 {
506 Weight = TextWeight.Bolder,
507 Color = TextColor.Accent
508 },
509 }
510 };
511
512 return new Microsoft.Teams.Api.MessageExtensions.ActionResponse
513 {
514 Task = new Microsoft.Teams.Api.TaskModules.ContinueTask(new Microsoft.Teams.Api.TaskModules.TaskInfo
515 {
516 Title = "Fetch Task Dialog",
517 Height = new Microsoft.Teams.Common.Union<int, Microsoft.Teams.Api.TaskModules.Size>(Microsoft.Teams.Api.TaskModules.Size.Small),
518 Width = new Microsoft.Teams.Common.Union<int, Microsoft.Teams.Api.TaskModules.Size>(Microsoft.Teams.Api.TaskModules.Size.Small),
519 Card = new Microsoft.Teams.Api.Attachment(card)
520 })
521 };
522 }
523
524 private static string SanitizeForLog(string? input)
525 {
526 if (input == null) return "";
527 return input.Replace("\r", "").Replace("\n", "");
528 }
529 }
530
531 private static string GetSettingsHtml()
532 {
533 return """
534<!DOCTYPE html>
535<html>
536<head>
537 <title>Message Extension Settings</title>
538 <meta charset="utf-8">
539 <meta name="viewport" content="width=device-width, initial-scale=1.0">
540 <script src="https://statics.teams.cdn.office.net/sdk/v1.12.0/js/MicrosoftTeams.min.js"></script>
541 <style>
542 body {
543 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
544 margin: 20px;
545 background-color: #f5f5f5;
546 }
547 .container {
548 background-color: white;
549 padding: 20px;
550 border-radius: 8px;
551 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
552 max-width: 500px;
553 }
554 .form-group {
555 margin-bottom: 15px;
556 }
557 label {
558 display: block;
559 margin-bottom: 5px;
560 font-weight: 600;
561 }
562 select, input {
563 width: 100%;
564 padding: 8px;
565 border: 1px solid #ddd;
566 border-radius: 4px;
567 font-size: 14px;
568 }
569 .buttons {
570 margin-top: 20px;
571 text-align: right;
572 }
573 button {
574 padding: 8px 16px;
575 margin-left: 8px;
576 border: none;
577 border-radius: 4px;
578 cursor: pointer;
579 font-size: 14px;
580 }
581 .btn-primary {
582 background-color: #0078d4;
583 color: white;
584 }
585 .btn-secondary {
586 background-color: #6c757d;
587 color: white;
588 }
589 </style>
590</head>
591<body>
592 <div class="container">
593 <h2>Message Extension Settings</h2>
594 <form id="settingsForm">
595 <div class="form-group">
596 <label for="defaultAction">Default Action:</label>
597 <select id="defaultAction" name="defaultAction">
598 <option value="search">Search</option>
599 <option value="compose">Compose</option>
600 <option value="both">Both</option>
601 </select>
602 </div>
603
604 <div class="form-group">
605 <label for="maxResults">Max Search Results:</label>
606 <input type="number" id="maxResults" name="maxResults" value="10" min="1" max="50">
607 </div>
608
609 <div class="buttons">
610 <button type="button" class="btn-secondary" onclick="cancelSettings()">Cancel</button>
611 <button type="button" class="btn-primary" onclick="saveSettings()">Save</button>
612 </div>
613 </form>
614 </div>
615
616 <script>
617 microsoftTeams.initialize();
618
619 function saveSettings() {
620 const formData = new FormData(document.getElementById('settingsForm'));
621 const settings = {};
622 for (let [key, value] of formData.entries()) {
623 settings[key] = value;
624 }
625
626 microsoftTeams.tasks.submitTask(settings);
627 }
628
629 function cancelSettings() {
630 microsoftTeams.tasks.submitTask();
631 }
632 </script>
633</body>
634</html>
635""";
636 }
637}