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 · modepreview

using System.Text.Json;

using Microsoft.Teams.Api.Cards;
using Microsoft.Teams.Apps.Activities;
using Microsoft.Teams.Apps.Activities.Invokes;
using Microsoft.Teams.Apps.Extensions;
using Microsoft.Teams.Cards;
using Microsoft.Teams.Common;
using Microsoft.Teams.Plugins.AspNetCore.Extensions;

var builder = WebApplication.CreateBuilder(args);
builder.AddTeams();

var app = builder.Build();

app.UseHttpsRedirection();

// Log raw requests
app.Use(async (context, next) =>
{
    if (context.Request.Method == "POST")
    {
        context.Request.EnableBuffering();
        var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
        context.Request.Body.Position = 0;
        Console.WriteLine($"[RAW_REQUEST] {context.Request.Method} {context.Request.Path}: {body}");
    }
    await next();
});

var teams = app.UseTeams();

// Serve settings page
app.MapGet("/settings", () => Results.Content(GetSettingsHtml(), "text/html"));

teams.OnMessage(async (context, cancellationToken) =>
{
    var activity = context.Activity;
    context.Log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}");
    context.Log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}");
    await context.Send($"Echo: {activity.Text}\n\nThis is a message extension bot. Use the message extension commands in Teams to test functionality.", cancellationToken);
});

teams.OnQuery((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_QUERY] Search query received");
    var activity = context.Activity;
    var commandId = activity.Value?.CommandId;
    var query = activity.Value?.Parameters?.FirstOrDefault(p => p.Name == "searchQuery")?.Value?.ToString() ?? "";
    context.Log.Info($"[MESSAGE_EXT_QUERY] Command: {commandId}, Query: {query}");

    if (commandId == "searchQuery")
    {
        return Task.FromResult(CreateSearchResults(query, context.Log));
    }

    return Task.FromResult(new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>()
        }
    });
});

teams.OnSubmitAction((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_SUBMIT] Action submit received");
    var activity = context.Activity;
    var commandId = activity.Value?.CommandId;
    var data = activity.Value?.Data as JsonElement?;

    context.Log.Info($"[MESSAGE_EXT_SUBMIT] Command: {commandId}");
    context.Log.Info($"[MESSAGE_EXT_SUBMIT] Data: {JsonSerializer.Serialize(data)}");

    var response = commandId switch
    {
        "createCard" => HandleCreateCard(data, context.Log),
        "getMessageDetails" => HandleGetMessageDetails(activity, context.Log),
        _ => CreateErrorActionResponse("Unknown command")
    };
    return Task.FromResult(response);
});

teams.OnQueryLink((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_QUERY_LINK] Link unfurling received");
    var activity = context.Activity;
    var url = activity.Value?.Url;
    context.Log.Info($"[MESSAGE_EXT_QUERY_LINK] URL: {url}");

    if (string.IsNullOrEmpty(url))
    {
        return Task.FromResult(CreateErrorResponse("No URL provided"));
    }

    return Task.FromResult(CreateLinkUnfurlResponse(url, context.Log));
});

teams.OnSelectItem((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_SELECT_ITEM] Item selection received");
    var activity = context.Activity;
    var selectedItem = activity.Value;
    context.Log.Info($"[MESSAGE_EXT_SELECT_ITEM] Selected: {JsonSerializer.Serialize(selectedItem)}");
    return Task.FromResult(CreateItemSelectionResponse(selectedItem, context.Log));
});

teams.OnQuerySettingsUrl((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_QUERY_SETTINGS_URL] Settings URL requested");
    return Task.FromResult(new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Config,
            Text = "Settings configuration would be handled here"
        }
    });
});

teams.OnFetchTask((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_FETCH_TASK] Fetch task received");
    var activity = context.Activity;
    var commandId = activity.Value?.CommandId;
    context.Log.Info($"[MESSAGE_EXT_FETCH_TASK] Command: {commandId}");
    return Task.FromResult(CreateFetchTaskResponse(commandId, context.Log));
});

teams.OnSetting((context, cancellationToken) =>
{
    context.Log.Info("[MESSAGE_EXT_SETTING] Settings received");
    var activity = context.Activity;
    var state = activity.Value?.State;
    context.Log.Info($"[MESSAGE_EXT_SETTING] State: {state}");

    if (state == "cancel")
    {
        context.Log.Info("[MESSAGE_EXT_SETTING] Settings cancelled by user");
    }
    else
    {
        context.Log.Info("[MESSAGE_EXT_SETTING] Settings processing completed");
    }

    return Task.CompletedTask;
});

app.Run();

static string SanitizeForLog(string? input)
{
    if (input == null) return "";
    return input.Replace("\r", "").Replace("\n", "");
}

static Microsoft.Teams.Api.MessageExtensions.Response CreateSearchResults(string query, Microsoft.Teams.Common.Logging.ILogger log)
{
    var attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>();

    for (int i = 1; i <= 5; i++)
    {
        var card = new Microsoft.Teams.Cards.AdaptiveCard
        {
            Body = new List<CardElement>
            {
                new TextBlock($"Search Result {i}") { Weight = TextWeight.Bolder, Size = TextSize.Large },
                new TextBlock($"Query: '{query}' - Result description for item {i}") { Wrap = true, IsSubtle = true }
            }
        };

        var previewCard = new ThumbnailCard()
        {
            Title = $"Result {i}",
            Text = $"This is a preview of result {i} for query '{query}'."
        };

        var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
        {
            ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard,
            Content = card,
            Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment
            {
                ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard,
                Content = previewCard
            }
        };

        attachments.Add(attachment);
    }

    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
            Attachments = attachments
        }
    };
}

static Microsoft.Teams.Api.MessageExtensions.Response HandleCreateCard(JsonElement? data, Microsoft.Teams.Common.Logging.ILogger log)
{
    var title = GetJsonValue(data, "title") ?? "Default Title";
    var description = GetJsonValue(data, "description") ?? "Default Description";

    log.Info($"[CREATE_CARD] Title: {title}, Description: {description}");

    var card = new Microsoft.Teams.Cards.AdaptiveCard
    {
        Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
        Body = new List<CardElement>
        {
            new TextBlock("Custom Card Created") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Good },
            new TextBlock(title) { Weight = TextWeight.Bolder, Size = TextSize.Medium },
            new TextBlock(description) { Wrap = true, IsSubtle = true }
        }
    };

    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
    {
        ContentType = Microsoft.Teams.Api.ContentType.AdaptiveCard,
        Content = card
    };

    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
        }
    };
}

static Microsoft.Teams.Api.MessageExtensions.Response HandleGetMessageDetails(Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SubmitActionActivity activity, Microsoft.Teams.Common.Logging.ILogger log)
{
    var messageText = activity.Value?.MessagePayload?.Body?.Content ?? "No message content";
    var messageId = activity.Value?.MessagePayload?.Id ?? "Unknown";

    log.Info($"[GET_MESSAGE_DETAILS] Message ID: {messageId}");

    var card = new Microsoft.Teams.Cards.AdaptiveCard
    {
        Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
        Body = new List<CardElement>
        {
            new TextBlock("Message Details") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Accent },
            new TextBlock($"Message ID: {messageId}") { Wrap = true },
            new TextBlock($"Content: {messageText}") { Wrap = true }
        }
    };

    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
    {
        ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
        Content = card
    };

    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
        }
    };
}

static Microsoft.Teams.Api.MessageExtensions.Response CreateLinkUnfurlResponse(string url, Microsoft.Teams.Common.Logging.ILogger log)
{
    var card = new Microsoft.Teams.Cards.AdaptiveCard
    {
        Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
        Body = new List<CardElement>
        {
            new TextBlock("Link Preview") { Weight = TextWeight.Bolder, Size = TextSize.Medium },
            new TextBlock($"URL: {url}") { IsSubtle = true, Wrap = true },
            new TextBlock("This is a preview of the linked content generated by the message extension.") { Wrap = true, Size = TextSize.Small }
        }
    };

    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
    {
        ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
        Content = card,
        Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment
        {
            ContentType = Microsoft.Teams.Api.ContentType.ThumbnailCard,
            Content = new ThumbnailCard { Title = "Link Preview", Text = url }
        }
    };

    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
        }
    };
}

static Microsoft.Teams.Api.MessageExtensions.Response CreateItemSelectionResponse(object? selectedItem, Microsoft.Teams.Common.Logging.ILogger log)
{
    var itemJson = JsonSerializer.Serialize(selectedItem);

    var card = new Microsoft.Teams.Cards.AdaptiveCard
    {
        Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
        Body = new List<CardElement>
        {
            new TextBlock("Item Selected") { Weight = TextWeight.Bolder, Size = TextSize.Large, Color = TextColor.Good },
            new TextBlock("You selected the following item:") { Wrap = true },
            new TextBlock(itemJson) { Wrap = true, FontType = FontType.Monospace, Separator = true }
        }
    };

    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment
    {
        ContentType = new Microsoft.Teams.Api.ContentType("application/vnd.microsoft.card.adaptive"),
        Content = card
    };

    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,
            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,
            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }
        }
    };
}

static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorResponse(string message)
{
    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message,
            Text = message
        }
    };
}

static Microsoft.Teams.Api.MessageExtensions.Response CreateErrorActionResponse(string message)
{
    return new Microsoft.Teams.Api.MessageExtensions.Response
    {
        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result
        {
            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Message,
            Text = message
        }
    };
}

static string? GetJsonValue(JsonElement? data, string key)
{
    if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value))
    {
        return value.GetString();
    }
    return null;
}

static Microsoft.Teams.Api.MessageExtensions.ActionResponse CreateFetchTaskResponse(string? commandId, Microsoft.Teams.Common.Logging.ILogger log)
{
    log.Info($"[CREATE_FETCH_TASK] Creating task for command: {commandId}");

    var card = new Microsoft.Teams.Cards.AdaptiveCard
    {
        Body = new List<CardElement>
        {
            new TextBlock("Conversation Members is not implemented in C# yet :(")
            {
                Weight = TextWeight.Bolder,
                Color = TextColor.Accent
            },
        }
    };

    return new Microsoft.Teams.Api.MessageExtensions.ActionResponse
    {
        Task = new Microsoft.Teams.Api.TaskModules.ContinueTask(new Microsoft.Teams.Api.TaskModules.TaskInfo
        {
            Title = "Fetch Task Dialog",
            Height = new Union<int, Microsoft.Teams.Api.TaskModules.Size>(Microsoft.Teams.Api.TaskModules.Size.Small),
            Width = new Union<int, Microsoft.Teams.Api.TaskModules.Size>(Microsoft.Teams.Api.TaskModules.Size.Small),
            Card = new Microsoft.Teams.Api.Attachment(card)
        })
    };
}

static string GetSettingsHtml()
{
    return """
<!DOCTYPE html>
<html>
<head>
    <title>Message Extension Settings</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://statics.teams.cdn.office.net/sdk/v1.12.0/js/MicrosoftTeams.min.js"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            max-width: 500px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: 600;
        }
        select, input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }
        .buttons {
            margin-top: 20px;
            text-align: right;
        }
        button {
            padding: 8px 16px;
            margin-left: 8px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        .btn-primary {
            background-color: #0078d4;
            color: white;
        }
        .btn-secondary {
            background-color: #6c757d;
            color: white;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>Message Extension Settings</h2>
        <form id="settingsForm">
            <div class="form-group">
                <label for="defaultAction">Default Action:</label>
                <select id="defaultAction" name="defaultAction">
                    <option value="search">Search</option>
                    <option value="compose">Compose</option>
                    <option value="both">Both</option>
                </select>
            </div>
            
            <div class="form-group">
                <label for="maxResults">Max Search Results:</label>
                <input type="number" id="maxResults" name="maxResults" value="10" min="1" max="50">
            </div>
            
            <div class="buttons">
                <button type="button" class="btn-secondary" onclick="cancelSettings()">Cancel</button>
                <button type="button" class="btn-primary" onclick="saveSettings()">Save</button>
            </div>
        </form>
    </div>

    <script>
        microsoftTeams.initialize();
        
        function saveSettings() {
            const formData = new FormData(document.getElementById('settingsForm'));
            const settings = {};
            for (let [key, value] of formData.entries()) {
                settings[key] = value;
            }
            
            microsoftTeams.tasks.submitTask(settings);
        }
        
        function cancelSettings() {
            microsoftTeams.tasks.submitTask();
        }
    </script>
</body>
</html>
""";
}