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