// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Teams.Apps.Schema.Entities;
using Microsoft.Teams.Core.Schema;
namespace Microsoft.Teams.Apps.Schema;
///
/// Fluent extension methods for that delegate to internally.
/// These methods provide backward compatibility with the old library's message.WithText(...).WithSuggestedActions(...) pattern.
///
public static class MessageActivityExtensions
{
///
/// Sets the activity id.
///
public static MessageActivity WithId(this MessageActivity message, string value)
{
ArgumentNullException.ThrowIfNull(message);
message.Id = value;
return message;
}
///
/// Sets the channel id.
///
public static MessageActivity WithChannelId(this MessageActivity message, string? value)
{
ArgumentNullException.ThrowIfNull(message);
message.ChannelId = value;
return message;
}
///
/// Sets the sender account.
///
public static MessageActivity WithFrom(this MessageActivity message, ConversationAccount? value)
{
ArgumentNullException.ThrowIfNull(message);
message.From = value is TeamsConversationAccount teamsAccount
? teamsAccount
: TeamsConversationAccount.FromConversationAccount(value);
return message;
}
///
/// Sets the recipient account on the message.
///
/// The message activity.
/// The recipient account.
/// The message activity for chaining.
public static MessageActivity WithRecipient(this MessageActivity message, ConversationAccount account)
{
ArgumentNullException.ThrowIfNull(message);
message.Recipient = account is TeamsConversationAccount teamsAccount
? teamsAccount
: TeamsConversationAccount.FromConversationAccount(account);
return message;
}
///
/// Sets the recipient account and targeted flag on the message.
///
/// The message activity.
/// The recipient account.
/// Whether the recipient is targeted.
/// The message activity for chaining.
[Experimental("ExperimentalTeamsTargeted")]
public static MessageActivity WithRecipient(this MessageActivity message, ConversationAccount account, bool isTargeted = false)
{
ArgumentNullException.ThrowIfNull(message);
if (account is not null)
{
account.IsTargeted = isTargeted ? true : null;
message.Recipient = account is TeamsConversationAccount teamsAccount
? teamsAccount
: TeamsConversationAccount.FromConversationAccount(account);
}
return message;
}
///
/// Sets the conversation information.
///
public static MessageActivity WithConversation(this MessageActivity message, Conversation? value)
{
ArgumentNullException.ThrowIfNull(message);
message.Conversation = value is TeamsConversation teamsConversation
? teamsConversation
: TeamsConversation.FromConversation(value);
return message;
}
///
/// Sets the service url.
///
public static MessageActivity WithServiceUrl(this MessageActivity message, Uri? value)
{
ArgumentNullException.ThrowIfNull(message);
message.ServiceUrl = value;
return message;
}
///
/// Sets the locale.
///
public static MessageActivity WithLocale(this MessageActivity message, string? value)
{
ArgumentNullException.ThrowIfNull(message);
message.Locale = value;
return message;
}
///
/// Sets the UTC timestamp value.
///
public static MessageActivity WithTimestamp(this MessageActivity message, string? value)
{
ArgumentNullException.ThrowIfNull(message);
message.Timestamp = value;
return message;
}
///
/// Sets the local timestamp value.
///
public static MessageActivity WithLocalTimestamp(this MessageActivity message, string? value)
{
ArgumentNullException.ThrowIfNull(message);
message.LocalTimestamp = value;
return message;
}
///
/// Merges channel data properties into the activity.
///
public static MessageActivity WithData(this MessageActivity message, ChannelData value)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(value);
message.ChannelData ??= new TeamsChannelData();
foreach (KeyValuePair kv in value.Properties)
{
message.ChannelData.Properties[kv.Key] = kv.Value;
}
return message;
}
///
/// Sets a channel data key/value property.
///
public static MessageActivity WithData(this MessageActivity message, string key, object? value)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentException.ThrowIfNullOrWhiteSpace(key);
message.ChannelData ??= new TeamsChannelData();
message.ChannelData.Properties[key] = value;
return message;
}
///
/// Sets the app id inside channel data.
///
public static MessageActivity WithAppId(this MessageActivity message, string value)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentException.ThrowIfNullOrWhiteSpace(value);
message.ChannelData ??= new TeamsChannelData();
message.ChannelData.Properties["app"] = new Dictionary { ["id"] = value };
return message;
}
///
/// Sets the text content of the message.
///
/// The message activity.
/// The text to set.
/// The text format. Default is "plain".
/// The message activity for chaining.
public static MessageActivity WithText(this MessageActivity message, string text, string textFormat = TextFormats.Plain)
{
ArgumentNullException.ThrowIfNull(message);
message.Text = text;
message.TextFormat = textFormat;
return message;
}
///
/// Appends text to the current message text.
///
/// The message activity.
/// The text to append.
/// The message activity for chaining.
public static MessageActivity AddText(this MessageActivity message, string text)
{
ArgumentNullException.ThrowIfNull(message);
message.Text = $"{message.Text}{text}";
return message;
}
///
/// Sets the text format for the message.
///
/// The message activity.
/// The text format. See for common values.
/// The message activity for chaining.
public static MessageActivity WithTextFormat(this MessageActivity message, string textFormat)
{
ArgumentNullException.ThrowIfNull(message);
message.TextFormat = textFormat;
return message;
}
///
/// Adds one or more attachments to the message.
///
/// The message activity.
/// The attachments to add.
/// The message activity for chaining.
public static MessageActivity AddAttachment(this MessageActivity message, params TeamsAttachment[] attachments)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(attachments);
message.Attachments ??= [];
foreach (TeamsAttachment attachment in attachments)
{
message.Attachments.Add(attachment);
}
return message;
}
///
/// Sets the attachment layout for the message.
///
/// The message activity.
/// The attachment layout (e.g., "list", "carousel").
/// The message activity for chaining.
public static MessageActivity WithAttachmentLayout(this MessageActivity message, string attachmentLayout)
{
ArgumentNullException.ThrowIfNull(message);
message.AttachmentLayout = attachmentLayout;
return message;
}
///
/// Sets the suggested actions for the message.
///
/// The message activity.
/// The suggested actions to set.
/// The message activity for chaining.
public static MessageActivity WithSuggestedActions(this MessageActivity message, SuggestedActions suggestedActions)
{
ArgumentNullException.ThrowIfNull(message);
message.SuggestedActions = suggestedActions;
return message;
}
///
/// Adds one or more entities to the message.
///
/// The target message.
/// Entities to add.
/// The message for chaining.
public static MessageActivity AddEntity(this MessageActivity message, params Entity[] entities)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(entities);
message.Entities ??= [];
foreach (Entity entity in entities)
{
message.Entities.Add(entity);
}
return message;
}
///
/// Replaces an existing entity with a new entity.
///
/// The target message.
/// The entity to replace.
/// The replacement entity.
/// The message for chaining.
public static MessageActivity UpdateEntity(this MessageActivity message, Entity oldEntity, Entity newEntity)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(oldEntity);
ArgumentNullException.ThrowIfNull(newEntity);
if (message.Entities != null)
{
message.Entities.Remove(oldEntity);
}
else
{
message.Entities = [];
}
message.Entities.Add(newEntity);
return message;
}
///
/// Adds a quoted message reference and appends a placeholder to the message text.
///
/// The message activity.
/// The ID of the message being quoted.
/// Optional text to append after the quote placeholder.
/// The message activity for chaining.
[Experimental("ExperimentalTeamsQuotedReplies")]
public static MessageActivity AddQuote(this MessageActivity message, string messageId, string? text = null)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentException.ThrowIfNullOrWhiteSpace(messageId);
QuotedReplyEntityExtensions.AddToActivity(message, messageId, text);
return message;
}
///
/// Prepends a quoted message placeholder before existing text.
///
/// The message activity.
/// The ID of the message being quoted.
/// The message activity for chaining.
[Experimental("ExperimentalTeamsQuotedReplies")]
public static MessageActivity PrependQuote(this MessageActivity message, string messageId)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentException.ThrowIfNullOrWhiteSpace(messageId);
message.Entities ??= [];
message.Entities.Insert(0, new QuotedReplyEntity { QuotedReply = new QuotedReplyData { MessageId = messageId } });
var placeholder = QuotedReplyEntityExtensions.QuotedPlaceholder(messageId);
var text = message.Text?.Trim() ?? "";
message.Text = string.IsNullOrEmpty(text) ? placeholder : $"{placeholder} {text}";
return message;
}
///
/// Adds targeted message info entity for prompt preview and strips quote placeholders.
///
[Experimental("ExperimentalTeamsTargeted")]
public static MessageActivity AddTargetedMessageInfo(this MessageActivity message, string messageId)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentException.ThrowIfNullOrWhiteSpace(messageId);
TargetedMessageInfoEntityExtensions.AddToActivity(message, messageId);
return message;
}
///
/// Adds a mention (@mention) entity and optionally prepends mention text.
///
/// The message activity.
/// The account being mentioned.
/// Optional mention text. If null, uses account name.
/// Whether mention text should be prepended to message text.
/// The message activity for chaining.
public static MessageActivity AddMention(this MessageActivity message, ConversationAccount account, string? text = null, bool addText = true)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(account);
MentionEntityExtensions.AddToActivity(message, account, text, addText);
return message;
}
///
/// Marks the message as a final streaming message by adding a
/// with .
///
/// The message activity.
/// The message activity for chaining.
public static MessageActivity AddStreamFinal(this MessageActivity message)
{
ArgumentNullException.ThrowIfNull(message);
StreamInfoEntityExtensions.AddToActivity(message, StreamType.Final);
return message;
}
///
/// Gets the mention entity for a specific account id.
///
/// The message activity.
/// The account id to match.
/// The matching mention entity, or null if not found.
public static MentionEntity? GetAccountMention(this MessageActivity message, string accountId)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentException.ThrowIfNullOrWhiteSpace(accountId);
return (MentionEntity?)(message.Entities ?? []).FirstOrDefault(e => e is MentionEntity mention && mention.Mentioned?.Id == accountId);
}
///
/// Adds the AI-generated content label to the root message entity.
///
public static OMessageEntity AddAIGenerated(this MessageActivity message)
{
ArgumentNullException.ThrowIfNull(message);
return OMessageEntityExtensions.AddAIGeneratedContent(message);
}
///
/// Adds a content sensitivity label to the message.
///
public static MessageActivity AddSensitivityLabel(this MessageActivity message, string name, string? description = null, DefinedTerm? pattern = null)
{
ArgumentNullException.ThrowIfNull(message);
SensitiveUsageEntityExtensions.AddToActivity(message, name, description, pattern);
return message;
}
///
/// Enables/disables feedback loop on the message.
///
public static MessageActivity AddFeedback(this MessageActivity message, bool value = true)
{
ArgumentNullException.ThrowIfNull(message);
message.ChannelData ??= new TeamsChannelData();
message.ChannelData.FeedbackLoopEnabled = value;
return message;
}
///
/// Configures feedback loop mode on the message.
///
/// The message activity.
/// The feedback loop type. See for known values.
/// The message activity for chaining.
public static MessageActivity AddFeedback(this MessageActivity message, string mode)
{
ArgumentNullException.ThrowIfNull(message);
message.ChannelData ??= new TeamsChannelData();
message.ChannelData.FeedbackLoop = new FeedbackLoop(mode);
message.ChannelData.FeedbackLoopEnabled = null;
return message;
}
///
/// Adds a citation claim to the message.
///
public static CitationEntity AddCitation(this MessageActivity message, int position, CitationAppearance appearance)
{
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(appearance);
message.Entities ??= [];
return CitationEntityExtensions.AddToActivity(message, position, appearance);
}
}