microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
763c2cf3c2405fce5db7765b35c0a09d3d398b28

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.MessageExtensions/Program.cs

510lines · modecode

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