microsoft/teams.net

Public

mirrored fromhttps://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

Samples/Samples.MessageExtensions/Program.cs

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