// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Teams; using Microsoft.Bot.Schema; using Microsoft.Bot.Schema.Teams; using Microsoft.Teams.Apps.BotBuilder; using Microsoft.Teams.Core; using Microsoft.Teams.Core.Schema; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace CompatBot; public class ConversationData { public int MessageCount { get; set; } = 0; } internal class EchoBot(BotApplication teamsBotApp, ConversationState conversationState, ILogger logger) : TeamsActivityHandler { public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { await base.OnTurnAsync(turnContext, cancellationToken); await conversationState.SaveChangesAsync(turnContext, false, cancellationToken); } protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { logger.LogInformation("OnMessage"); IStatePropertyAccessor conversationStateAccessors = conversationState.CreateProperty(nameof(ConversationData)); ConversationData conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData(), cancellationToken); TeamsChannelAccount mm = await TeamsApiClient.GetMemberAsync(turnContext, turnContext.Activity.From.Id, cancellationToken); string replyText = $"Echo {mm.Name} from BF Compat [{conversationData.MessageCount++}]: {turnContext.Activity.Text}"; // Targeted Messaging via BF compat layer: setting isTargeted on the BF Microsoft.Bot.Schema.ChannelAccount // causes the compat layer to set CoreActivity.Recipient.IsTargeted, which appends // ?isTargetedActivity=true to the URL making the message visible only to that user. Activity act = MessageFactory.Text(replyText, replyText); act.Recipient = new Microsoft.Bot.Schema.ChannelAccount(); act.Recipient.Properties.Add("isTargeted", true); await turnContext.SendActivityAsync(act, cancellationToken); if (turnContext.Activity.Conversation.IsGroup == true) { TeamDetails teamDetails = await TeamsApiClient.GetTeamDetailsAsync(turnContext, null, cancellationToken); await turnContext.SendActivityAsync(JsonConvert.SerializeObject(teamDetails, Formatting.Indented), cancellationToken: cancellationToken); TeamsPagedMembersResult pagedMembersResult; List members = []; string continuationToken = null!; do { pagedMembersResult = await TeamsApiClient.GetPagedMembersAsync( turnContext, 5, continuationToken, cancellationToken ); continuationToken = pagedMembersResult.ContinuationToken; members.AddRange(pagedMembersResult.Members); } while (continuationToken != null); await turnContext.SendActivityAsync(JsonConvert.SerializeObject(members.Select(m => m.Name).ToList(), Formatting.Indented), cancellationToken: cancellationToken); } // Targeted Messaging via Core SDK (preferred): sends directly through ConversationClient // to bypass the BF compat layer's ApplyConversationReference which would overwrite the Recipient. CoreActivity incomingCoreActivity = ((Activity)turnContext.Activity).FromBotFrameworkActivity(); Microsoft.Teams.Core.Schema.ChannelAccount? incomingFrom = incomingCoreActivity.From; Microsoft.Teams.Core.Schema.ChannelAccount? incomingRecipient = incomingCoreActivity.Recipient; #pragma warning disable ExperimentalTeamsTargeted incomingFrom!.IsTargeted = true; #pragma warning restore ExperimentalTeamsTargeted CoreActivity tm = CoreActivity.CreateBuilder() .WithConversation(incomingCoreActivity.Conversation!) .WithProperty("text", "Hello TM !") .WithRecipient(incomingFrom) .WithFrom(incomingRecipient) //.WithServiceUrl(activity.ServiceUrl!) .WithServiceUrl("https://pilot1.botapi.skype.com/amer/9a9b49fd-1dc5-4217-88b3-ecf855e91b0e/") .Build(); await teamsBotApp.ConversationClient.SendActivityAsync(tm, cancellationToken: cancellationToken); ResourceResponse res = await turnContext.SendActivityAsync( MessageFactory.Text("I'm going to add and remove reactions to this message."), cancellationToken); await Task.Delay(500, cancellationToken); await teamsBotApp.ConversationClient.AddReactionAsync( turnContext.Activity.Conversation.Id, res.Id, "laugh", new Uri("https://pilot1.botapi.skype.com/amer/9a9b49fd-1dc5-4217-88b3-ecf855e91b0e/"), //incomingCoreActivity.ServiceUrl!, AgenticIdentity.FromAccount(incomingRecipient), null, cancellationToken); await Task.Delay(500, cancellationToken); await teamsBotApp.ConversationClient.AddReactionAsync( turnContext.Activity.Conversation.Id, res.Id, "sad", incomingCoreActivity.ServiceUrl!, AgenticIdentity.FromAccount(incomingRecipient), null, cancellationToken); await Task.Delay(500, cancellationToken); await teamsBotApp.ConversationClient.DeleteReactionAsync( turnContext.Activity.Conversation.Id, res.Id, "laugh", //new Uri("https://pilot1.botapi.skype.com/amer/9a9b49fd-1dc5-4217-88b3-ecf855e91b0e/"), incomingCoreActivity.ServiceUrl!, AgenticIdentity.FromAccount(incomingRecipient), null, cancellationToken); // Card submission triggers OnInvokeActivityAsync below. Attachment attachment = new() { ContentType = "application/vnd.microsoft.card.adaptive", Content = Cards.FeedbackCardObj }; IMessageActivity attachmentReply = MessageFactory.Attachment(attachment); await turnContext.SendActivityAsync(attachmentReply, cancellationToken); } protected override async Task OnMessageReactionActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { await turnContext.SendActivityAsync(MessageFactory.Text("Message reaction received."), cancellationToken); } protected override async Task OnInstallationUpdateActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { await turnContext.SendActivityAsync(MessageFactory.Text("Installation update received."), cancellationToken); await turnContext.SendActivityAsync(MessageFactory.Text($"Send a proactive messages to `/api/notify/{turnContext.Activity.Conversation.Id}`"), cancellationToken); } protected override async Task OnInstallationUpdateAddAsync(ITurnContext turnContext, CancellationToken cancellationToken) { await turnContext.SendActivityAsync(MessageFactory.Text("Installation update Add received."), cancellationToken); await turnContext.SendActivityAsync(MessageFactory.Text($"Send a proactive messages to `/api/notify/{turnContext.Activity.Conversation.Id}`"), cancellationToken); } protected override async Task OnInvokeActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { logger.LogInformation("Invoke Activity received: {Name}", turnContext.Activity.Name); JObject actionValue = JObject.FromObject(turnContext.Activity.Value); JObject? action = actionValue["action"] as JObject; JObject? actionData = action?["data"] as JObject; string? userInput = actionData?["feedback"]?.ToString(); //var userInput = actionValue["userInput"]?.ToString(); logger.LogInformation("Action: {Action}, User Input: {UserInput}", action, userInput); Attachment attachment = new() { ContentType = "application/vnd.microsoft.card.adaptive", Content = Cards.ResponseCard(userInput) }; IMessageActivity card = MessageFactory.Attachment(attachment); await turnContext.SendActivityAsync(card, cancellationToken); return new Microsoft.Bot.Builder.InvokeResponse { Status = 200, Body = new { value = "invokes from compat bot" } }; } protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) { await turnContext.SendActivityAsync(MessageFactory.Text("Welcome."), cancellationToken); await turnContext.SendActivityAsync(MessageFactory.Text($"Send a proactive messages to `/api/notify/{turnContext.Activity.Conversation.Id}`"), cancellationToken); } protected override Task OnMembersRemovedAsync(IList membersRemoved, ITurnContext turnContext, CancellationToken cancellationToken) { return turnContext.SendActivityAsync(MessageFactory.Text("Bye."), cancellationToken); } protected override async Task OnTeamsMeetingStartAsync(MeetingStartEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) { await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to meeting: "), cancellationToken); await turnContext.SendActivityAsync(MessageFactory.Text($"{meeting.Title} {meeting.MeetingType}"), cancellationToken); } private static async Task SendUpdateDeleteActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { ConversationReference cr = turnContext.Activity.GetConversationReference(); Activity reply = (Activity)Activity.CreateMessageActivity(); reply.ApplyConversationReference(cr, isIncoming: false); reply.Text = "This is a proactive message sent using the Conversations API."; ResourceResponse[] res = await turnContext.Adapter.SendActivitiesAsync(turnContext, [reply], cancellationToken); await Task.Delay(2000, cancellationToken); Activity updatedActivity = (Activity)Activity.CreateMessageActivity(); updatedActivity.ApplyConversationReference(cr, isIncoming: false); updatedActivity.Id = res[0].Id; updatedActivity.Text = "This message has been updated."; await turnContext.Adapter.UpdateActivityAsync(turnContext, updatedActivity, cancellationToken); await Task.Delay(2000, cancellationToken); ConversationReference deleteReference = cr; deleteReference.ActivityId = res[0].Id; await turnContext.Adapter.DeleteActivityAsync(turnContext, deleteReference, cancellationToken); await turnContext.SendActivityAsync(MessageFactory.Text("Proactive message sent and deleted."), cancellationToken); } }