// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Text.Json.Serialization; using Microsoft.Teams.Apps.Schema; namespace Microsoft.Teams.Apps.Handlers.MessageExtension; /// /// Messaging extension response types. /// public static class MessageExtensionResponseTypes { /// /// Result type - displays a list of search results. /// public const string Result = "result"; /// /// Message type - displays a plain text message. /// public const string Message = "message"; /// /// Bot message preview type - shows a preview that can be edited before sending. /// public const string BotMessagePreview = "botMessagePreview"; /// /// Config type - prompts the user to set up the message extension. /// public const string Config = "config"; } /// /// Messaging extension response wrapper. /// public class MessageExtensionResponse { /// /// The compose extension result (for message extension results, auth, config, etc.). /// [JsonPropertyName("composeExtension")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ComposeExtension? ComposeExtension { get; set; } /// /// Creates a new builder for MessageExtensionResponse. /// public static MessageExtensionResponseBuilder CreateBuilder() { return new MessageExtensionResponseBuilder(); } } /// /// Messaging extension result. /// public class ComposeExtension { /// /// Type of result. /// See for common values. /// [JsonPropertyName("type")] public string? Type { get; set; } /// /// Layout for attachments. /// See for common values. /// [JsonPropertyName("attachmentLayout")] public string? AttachmentLayout { get; set; } /// /// Array of attachments (cards) to display. /// // TODO : there is an extra preview field but when is it used ? [JsonPropertyName("attachments")] public IList? Attachments { get; set; } /// /// Text to display. /// [JsonPropertyName("text")] public string? Text { get; set; } /// /// Activity preview for bot message preview. /// //TODO : this needs to be activity type or something else - format is type, attachments[] [JsonPropertyName("activityPreview")] public TeamsActivity? ActivityPreview { get; set; } /// /// Suggested actions for config type. /// [JsonPropertyName("suggestedActions")] public MessageExtensionSuggestedAction? SuggestedActions { get; set; } } /// /// Suggested actions for messaging extension configuration. /// public class MessageExtensionSuggestedAction { /// /// Array of actions. /// [JsonPropertyName("actions")] public IList? Actions { get; set; } } /// /// Builder for MessageExtensionResponse. /// public class MessageExtensionResponseBuilder { private string? _type; private string? _attachmentLayout; private TeamsAttachment[]? _attachments; private TeamsActivity? _activityPreview; private SuggestedAction[]? _suggestedActions; private string? _text; /// /// Sets the type of the response. Common values: "result", "auth", "config", "message", "botMessagePreview". /// public MessageExtensionResponseBuilder WithType(string type) { _type = type; return this; } /// /// Sets the attachment layout. Common values: "list", "grid". /// public MessageExtensionResponseBuilder WithAttachmentLayout(string layout) { _attachmentLayout = layout; return this; } /// /// Sets the attachments for the response. /// public MessageExtensionResponseBuilder WithAttachments(params TeamsAttachment[] attachments) { _attachments = attachments; return this; } /// /// Sets the activity preview for bot message preview type. /// public MessageExtensionResponseBuilder WithActivityPreview(TeamsActivity activityPreview) { _activityPreview = activityPreview; return this; } /// /// Sets suggested actions for config type. /// public MessageExtensionResponseBuilder WithSuggestedActions(params SuggestedAction[] actions) { _suggestedActions = actions; return this; } /// /// Sets the text message for message type. /// public MessageExtensionResponseBuilder WithText(string text) { _text = text; return this; } /// /// Validates and builds the MessageExtensionResponse. /// internal MessageExtensionResponse Validate() { if (string.IsNullOrEmpty(_type)) { throw new InvalidOperationException("Type must be set. Use WithType() to specify MessageExtensionResponseTypes.Result, Message, BotMessagePreview, or Config."); } return _type switch { MessageExtensionResponseTypes.Result => ValidateResultType(), MessageExtensionResponseTypes.Message => ValidateMessageType(), MessageExtensionResponseTypes.BotMessagePreview => ValidateBotMessagePreviewType(), MessageExtensionResponseTypes.Config => ValidateConfigType(), _ => throw new InvalidOperationException($"Unknown message extension response type: {_type}") }; } private MessageExtensionResponse ValidateResultType() { if (_attachments == null || _attachments.Length == 0) { throw new InvalidOperationException("Attachments must be set for Result type. Use WithAttachments()."); } if (!string.IsNullOrEmpty(_text)) { throw new InvalidOperationException("Text cannot be set for Result type. Text is only used with Message type."); } if (_activityPreview != null) { throw new InvalidOperationException("ActivityPreview cannot be set for Result type. ActivityPreview is only used with BotMessagePreview type."); } if (_suggestedActions != null) { throw new InvalidOperationException("SuggestedActions cannot be set for Result type. SuggestedActions is only used with Config type."); } return new MessageExtensionResponse { ComposeExtension = new ComposeExtension { Type = _type, AttachmentLayout = _attachmentLayout, Attachments = _attachments } }; } private MessageExtensionResponse ValidateMessageType() { if (string.IsNullOrEmpty(_text)) { throw new InvalidOperationException("Text must be set for Message type. Use WithText()."); } if (_attachments != null) { throw new InvalidOperationException("Attachments cannot be set for Message type. Attachments is only used with Result or BotMessagePreview type."); } if (!string.IsNullOrEmpty(_attachmentLayout)) { throw new InvalidOperationException("AttachmentLayout cannot be set for Message type. AttachmentLayout is only used with Result type."); } if (_activityPreview != null) { throw new InvalidOperationException("ActivityPreview cannot be set for Message type. ActivityPreview is only used with BotMessagePreview type."); } if (_suggestedActions != null) { throw new InvalidOperationException("SuggestedActions cannot be set for Message type. SuggestedActions is only used with Config type."); } return new MessageExtensionResponse { ComposeExtension = new ComposeExtension { Type = _type, Text = _text } }; } private MessageExtensionResponse ValidateBotMessagePreviewType() { if (_activityPreview == null) { throw new InvalidOperationException("ActivityPreview must be set for BotMessagePreview type. Use WithActivityPreview()."); } if (!string.IsNullOrEmpty(_text)) { throw new InvalidOperationException("Text cannot be set for BotMessagePreview type. Text is only used with Message type."); } if (!string.IsNullOrEmpty(_attachmentLayout)) { throw new InvalidOperationException("AttachmentLayout cannot be set for BotMessagePreview type. AttachmentLayout is only used with Result type."); } if (_suggestedActions != null) { throw new InvalidOperationException("SuggestedActions cannot be set for BotMessagePreview type. SuggestedActions is only used with Config type."); } return new MessageExtensionResponse { ComposeExtension = new ComposeExtension { Type = _type, ActivityPreview = _activityPreview, Attachments = _attachments } }; } private MessageExtensionResponse ValidateConfigType() { if (_suggestedActions == null || _suggestedActions.Length == 0) { throw new InvalidOperationException("SuggestedActions must be set for Config type. Use WithSuggestedActions()."); } if (_attachments != null) { throw new InvalidOperationException("Attachments cannot be set for Config type. Attachments is only used with Result or BotMessagePreview type."); } if (!string.IsNullOrEmpty(_attachmentLayout)) { throw new InvalidOperationException("AttachmentLayout cannot be set for Config type. AttachmentLayout is only used with Result type."); } if (!string.IsNullOrEmpty(_text)) { throw new InvalidOperationException("Text cannot be set for Config type. Text is only used with Message type."); } if (_activityPreview != null) { throw new InvalidOperationException("ActivityPreview cannot be set for Config type. ActivityPreview is only used with BotMessagePreview type."); } return new MessageExtensionResponse { ComposeExtension = new ComposeExtension { Type = _type, SuggestedActions = new MessageExtensionSuggestedAction { Actions = _suggestedActions } } }; } /// /// Builds the MessageExtensionResponse and wraps it in an . /// /// The HTTP status code (default: 200). public InvokeResponse Build(int statusCode = 200) { return new InvokeResponse(statusCode, Validate()); } }