// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; using Microsoft.Bot.Schema; using Microsoft.Bot.Schema.Teams; using Microsoft.Teams.Apps.BotBuilder; namespace PABot.Bots { /// /// This bot is derived from the TeamsActivityHandler class and handles Teams-specific activities. /// /// The type of the dialog. public class TeamsBot : DialogBot where T : Dialog { /// /// Initializes a new instance of the class. /// /// The conversation state. /// The user state. /// The dialog. /// The logger. public TeamsBot(ConversationState conversationState, UserState userState, T dialog, ILogger> logger) : base(conversationState, userState, dialog, logger) { } /// /// Handles the event when members are added to the conversation. /// /// The list of members added. /// The turn context. /// The cancellation token. /// A task that represents the work queued to execute. protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) { foreach (ChannelAccount member in membersAdded) { if (member.Id != turnContext.Activity.Recipient.Id) { string welcomeMessage = "Welcome to AuthenticationBot. Type anything to get logged in. Type 'logout' to sign-out.\n" + "Try these commands:\n" + "- /help - Show detailed help and bot capabilities\n" + "- /member-info - Get your member details\n" + "- /team-info - Get team details (in team context)\n" + "- /create-conversation - Create 1:1 chat (from group chat)"; await turnContext.SendActivityAsync(MessageFactory.Text(welcomeMessage), cancellationToken); // Use TeamsApiClient.GetMemberAsync to get detailed member information try { TeamsChannelAccount memberDetails = await TeamsApiClient.GetMemberAsync(turnContext, member.Id, cancellationToken); _logger.LogInformation("Member added: {Name} ({Email}), AAD Object ID: {AadObjectId}", memberDetails.Name, memberDetails.Email, memberDetails.AadObjectId); } catch (Exception ex) { _logger.LogWarning(ex, "Could not retrieve member details for {MemberId}", member.Id); } } } } /// /// Handles the Teams sign-in verification state. /// /// The turn context. /// The cancellation token. /// A task that represents the work queued to execute. protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { string text = System.Text.RegularExpressions.Regex.Replace( turnContext.Activity.Text ?? string.Empty, @"[^<]*<\/at>", string.Empty).Trim(); if (text.Equals("/help", StringComparison.OrdinalIgnoreCase)) { string helpMessage = "**PABot - Personal Assistant Bot**\n\n" + "I'm a Teams bot that demonstrates authentication and Teams integration capabilities.\n\n" + "**Authentication Features:**\n" + "- Dual OAuth authentication (graph and graph-2 connections)\n" + "- Sequential authentication flow - authenticate both connections one after another\n" + "- Retrieve and display user profile information from Microsoft Graph\n" + "- Show user profile photos\n" + "- Display OAuth tokens for both connections\n\n" + "**Available Commands:**\n" + "- **/help** - Show this help message\n" + "- **/member-info** - Get detailed information about your Teams member account\n" + " - Displays: Name, ID, AAD Object ID, User Principal Name, Email\n" + "- **/team-info** - Get details about the current team (only works in team context)\n" + " - Displays: Team Name, ID, AAD Group ID\n" + "- **/create-conversation** - Create a 1:1 conversation from a group chat\n" + " - Only available in group chat contexts\n" + "- **logout** - Sign out from authenticated connections\n\n" + "**How to use:**\n" + "1. Send any message to start the authentication flow\n" + "2. Authenticate with the first connection (graph)\n" + "3. Authenticate with the second connection (graph-2)\n" + "4. View your profile information automatically\n" + "5. Use commands to explore Teams integration features\n\n" + "**Technical Features:**\n" + "- Uses TeamsApiClient for Teams member and team information\n" + "- Demonstrates Microsoft Graph integration\n" + "- Shows OAuth connection management\n" + "- Logs detailed member information when users join"; await turnContext.SendActivityAsync(MessageFactory.Text(helpMessage), cancellationToken); return; } if (text.Equals("/create-conversation", StringComparison.OrdinalIgnoreCase)) { if (turnContext.Activity.Conversation.IsGroup != true) { await turnContext.SendActivityAsync(MessageFactory.Text("This command can only be used in a group chat."), cancellationToken); return; } TeamsChannelData channelData = turnContext.Activity.GetChannelData(); ChannelAccount userChannel = turnContext.Activity.From; ConversationParameters conversationParameters = new() { IsGroup = false, Bot = new ChannelAccount { Id = turnContext.Activity.Recipient.Id }, Members = [userChannel], TenantId = channelData.Tenant.Id, }; _logger.LogInformation("Creating 1:1 conversation with user {UserId} in tenant {TenantId}", userChannel.Id, conversationParameters.TenantId); IConnectorClient connectorClient = turnContext.TurnState.Get(); ConversationResourceResponse conv = await connectorClient.Conversations.CreateConversationAsync(conversationParameters, cancellationToken); _logger.LogInformation("Created conversation {ConversationId}", conv.Id); Activity message = MessageFactory.Text("Hello! I've started a 1:1 conversation with you from the group chat."); message.ServiceUrl = turnContext.Activity.ServiceUrl; await connectorClient.Conversations.SendToConversationAsync(conv.Id, message, cancellationToken); await turnContext.SendActivityAsync(MessageFactory.Text("Done! Check your personal chat."), cancellationToken); return; } if (text.Equals("/member-info", StringComparison.OrdinalIgnoreCase)) { // Get member details using TeamsApiClient.GetMemberAsync string userId = turnContext.Activity.From.Id; TeamsChannelAccount member = await TeamsApiClient.GetMemberAsync(turnContext, userId, cancellationToken); string memberInfo = $"Member Details:\n" + $"- Name: {member.Name}\n" + $"- ID: {member.Id}\n" + $"- AAD Object ID: {member.AadObjectId}\n" + $"- User Principal Name: {member.UserPrincipalName}\n" + $"- Email: {member.Email}"; await turnContext.SendActivityAsync(MessageFactory.Text(memberInfo), cancellationToken); return; } if (text.Equals("/team-info", StringComparison.OrdinalIgnoreCase)) { // Get team details using TeamsApiClient.GetTeamDetailsAsync TeamInfo? teamInfo = turnContext.Activity.TeamsGetTeamInfo(); if (teamInfo?.Id == null) { await turnContext.SendActivityAsync(MessageFactory.Text("This command can only be used in a team context."), cancellationToken); return; } TeamDetails teamDetails = await TeamsApiClient.GetTeamDetailsAsync(turnContext, teamInfo.Id, cancellationToken); string teamDetailsInfo = $"Team Details:\n" + $"- Name: {teamDetails.Name}\n" + $"- ID: {teamDetails.Id}\n" + $"- AAD Group ID: {teamDetails.AadGroupId}"; await turnContext.SendActivityAsync(MessageFactory.Text(teamDetailsInfo), cancellationToken); return; } await base.OnMessageActivityAsync(turnContext, cancellationToken); } protected override async Task OnTeamsSigninVerifyStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) { _logger.LogInformation("Running dialog with sign-in/verify state from an Invoke Activity."); // The OAuth Prompt needs to see the Invoke Activity in order to complete the login process. // Run the Dialog with the new Invoke Activity. await _dialog.RunAsync(turnContext, _conversationState.CreateProperty(nameof(DialogState)), cancellationToken); } /// /// Handles invoke activities, including signin/failure events. /// /// The turn context. /// The cancellation token. /// An invoke response. protected override async Task OnInvokeActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { if (turnContext.Activity.Name == "signin/failure") { _logger.LogWarning("Sign-in failure detected. Activity Name: {ActivityName}", turnContext.Activity.Name); _logger.LogWarning("Sign-in failure details - ConversationId: {ConversationId}, From: {FromId}, Value: {Value}", turnContext.Activity.Conversation?.Id, turnContext.Activity.From?.Id, Newtonsoft.Json.JsonConvert.SerializeObject(turnContext.Activity)); } return await base.OnInvokeActivityAsync(turnContext, cancellationToken); } } }