microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
aamirj/StackOverflowTest

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.MessageExtensions/Program.cs

628lines · 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.QuerySettingsUrlActivity 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 async Task<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 };
395
396 return new Microsoft.Teams.Api.MessageExtensions.Response
397 {
398 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
399 {
400 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
401 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
402 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
403 }
404 };
405 }
406
407 private static Microsoft.Teams.Api.MessageExtensions.Response CreateItemSelectionResponse(object? selectedItem, Microsoft.Teams.Common.Logging.ILogger log)
408 {
409 var itemJson = JsonSerializer.Serialize(selectedItem);
410
411 var card = new Microsoft.Teams.Cards.AdaptiveCard
412 {
413 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
414 Body = new List<CardElement>
415 {
416 new TextBlock("Item Selected")
417 {
418 Weight = TextWeight.Bolder,
419 Size = TextSize.Large,
420 Color = TextColor.Good
421 },
422 new TextBlock("You selected the following item:")
423 {
424 Wrap = true
425 },
426 new TextBlock(itemJson)
427 {
428 Wrap = true,
429 FontType = FontType.Monospace,
430 Separator = true
431 }
432 }
433 };
434
435 var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
436 {
437 ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
438 Content = card
439 };
440
441 return new Microsoft.Teams.Api.MessageExtensions.Response
442 {
443 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
444 {
445 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
446 AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
447 Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
448 }
449 };
450 }
451
452 private static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorResponse(string message)
453 {
454 return new Microsoft.Teams.Api.MessageExtensions.Response
455 {
456 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
457 {
458 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message,
459 Text = message
460 }
461 };
462 }
463
464 private static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorActionResponse(string message)
465 {
466 return new Microsoft.Teams.Api.MessageExtensions.Response
467 {
468 ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
469 {
470 Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message,
471 Text = message
472 }
473 };
474 }
475
476 private static string? GetJsonValue(JsonElement? data, string key)
477 {
478 if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value))
479 {
480 return value.GetString();
481 }
482 return null;
483 }
484
485 private static Microsoft.Teams.Api.MessageExtensions.ActionResponse CreateFetchTaskResponse(string? commandId, Microsoft.Teams.Common.Logging.ILogger log)
486 {
487 log.Info($"[CREATE_FETCH_TASK] Creating task for command: {commandId}");
488 // Updated to use actual converation members
489
490 // Create an adaptive card for the task module
491 var card = new Microsoft.Teams.Cards.AdaptiveCard
492 {
493 Body = new List<CardElement>
494 {
495 new TextBlock("Conversation Members is not implemented in C# yet :(")
496 {
497 Weight = TextWeight.Bolder,
498 Color = TextColor.Accent
499 },
500 }
501 };
502
503 return new Microsoft.Teams.Api.MessageExtensions.ActionResponse
504 {
505 Task = new Microsoft.Teams.Api.TaskModules.ContinueTask(new Microsoft.Teams.Api.TaskModules.TaskInfo
506 {
507 Title = "Fetch Task Dialog",
508 Height = new Microsoft.Teams.Common.Union<int, Microsoft.Teams.Api.TaskModules.Size>(Microsoft.Teams.Api.TaskModules.Size.Small),
509 Width = new Microsoft.Teams.Common.Union<int, Microsoft.Teams.Api.TaskModules.Size>(Microsoft.Teams.Api.TaskModules.Size.Small),
510 Card = new Microsoft.Teams.Api.Attachment(card)
511 })
512 };
513 }
514
515 private static string SanitizeForLog(string? input)
516 {
517 if (input == null) return "";
518 return input.Replace("\r", "").Replace("\n", "");
519 }
520 }
521
522 private static string GetSettingsHtml()
523 {
524 return """
525<!DOCTYPE html>
526<html>
527<head>
528 <title>Message Extension Settings</title>
529 <meta charset="utf-8">
530 <meta name="viewport" content="width=device-width, initial-scale=1.0">
531 <script src="https://statics.teams.cdn.office.net/sdk/v1.12.0/js/MicrosoftTeams.min.js"></script>
532 <style>
533 body {
534 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
535 margin: 20px;
536 background-color: #f5f5f5;
537 }
538 .container {
539 background-color: white;
540 padding: 20px;
541 border-radius: 8px;
542 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
543 max-width: 500px;
544 }
545 .form-group {
546 margin-bottom: 15px;
547 }
548 label {
549 display: block;
550 margin-bottom: 5px;
551 font-weight: 600;
552 }
553 select, input {
554 width: 100%;
555 padding: 8px;
556 border: 1px solid #ddd;
557 border-radius: 4px;
558 font-size: 14px;
559 }
560 .buttons {
561 margin-top: 20px;
562 text-align: right;
563 }
564 button {
565 padding: 8px 16px;
566 margin-left: 8px;
567 border: none;
568 border-radius: 4px;
569 cursor: pointer;
570 font-size: 14px;
571 }
572 .btn-primary {
573 background-color: #0078d4;
574 color: white;
575 }
576 .btn-secondary {
577 background-color: #6c757d;
578 color: white;
579 }
580 </style>
581</head>
582<body>
583 <div class="container">
584 <h2>Message Extension Settings</h2>
585 <form id="settingsForm">
586 <div class="form-group">
587 <label for="defaultAction">Default Action:</label>
588 <select id="defaultAction" name="defaultAction">
589 <option value="search">Search</option>
590 <option value="compose">Compose</option>
591 <option value="both">Both</option>
592 </select>
593 </div>
594
595 <div class="form-group">
596 <label for="maxResults">Max Search Results:</label>
597 <input type="number" id="maxResults" name="maxResults" value="10" min="1" max="50">
598 </div>
599
600 <div class="buttons">
601 <button type="button" class="btn-secondary" onclick="cancelSettings()">Cancel</button>
602 <button type="button" class="btn-primary" onclick="saveSettings()">Save</button>
603 </div>
604 </form>
605 </div>
606
607 <script>
608 microsoftTeams.initialize();
609
610 function saveSettings() {
611 const formData = new FormData(document.getElementById('settingsForm'));
612 const settings = {};
613 for (let [key, value] of formData.entries()) {
614 settings[key] = value;
615 }
616
617 microsoftTeams.tasks.submitTask(settings);
618 }
619
620 function cancelSettings() {
621 microsoftTeams.tasks.submitTask();
622 }
623 </script>
624</body>
625</html>
626""";
627 }
628}