// 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());
}
}