diff --git a/core/samples/SsoBot/Program.cs b/core/samples/SsoBot/Program.cs index 2672fd5a6..6115fd565 100644 --- a/core/samples/SsoBot/Program.cs +++ b/core/samples/SsoBot/Program.cs @@ -38,15 +38,30 @@ auth.OnSignInComplete(async (context, tokenResponse, ct) => { - await context.SendActivityAsync("You're now signed in! Try `profile` or `calendar`.", ct); + await context.SendActivityAsync(new MessageActivity("You're now signed in! Try `profile` or `calendar`.") + .WithSuggestedActions( + new SuggestedActions() + { + Actions = new List() + { + new SuggestedAction() { Title = "Profile", Type = "imBack", Value = "profile" }, + new SuggestedAction() { Title = "Calendar", Type = "imBack", Value = "calendar" } + } + }), ct); }); auth.OnSignInFailure(async (context, failure, ct) => { string message = failure is not null - ? $"Sign-in failed: {failure.Code} — {failure.Message}" + ? $"User {context.Activity.From?.Name} Sign-in failed: {failure.Code} — {failure.Message}" : "Sign-in failed. Please try again."; - await context.SendActivityAsync(message, ct); + var signInFailureMessage = new MessageActivity(message) + { + TextFormat = TextFormats.Markdown + }; + signInFailureMessage.Recipient = context.Activity.From; + signInFailureMessage.Recipient?.IsTargeted = context.Activity?.Conversation?.ConversationType == ConversationType.GroupChat; // only set IsTargeted for 1:1 chats to avoid issues in group contexts + await context.SendActivityAsync(signInFailureMessage, ct); }); // ==================== MESSAGE HANDLERS ==================== @@ -57,7 +72,13 @@ string? token = await context.SignIn(cancellationToken: ct); if (token is not null) { - await context.SendActivityAsync("You're already signed in.", ct); + var alreadySignedInMessage = new MessageActivity($"You're already signed in, {context.Activity.From?.Name}!") + { + TextFormat = TextFormats.Markdown + }; + alreadySignedInMessage.Recipient = context.Activity.From; + alreadySignedInMessage.Recipient?.IsTargeted = context.Activity?.Conversation?.ConversationType == ConversationType.GroupChat; // only set IsTargeted for 1:1 chats to avoid issues in group contexts + await context.SendActivityAsync(alreadySignedInMessage, ct); } // else: OAuthCard sent, SSO flow in progress -- OnSignInComplete will fire }); @@ -75,11 +96,16 @@ { string json = await http.GetStringAsync("https://graph.microsoft.com/v1.0/me", ct); string indentedJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(json), new JsonSerializerOptions { WriteIndented = true }); - await context.SendActivityAsync(new MessageActivity($" ## Graph Me \n ```json\n{indentedJson}\n```") { TextFormat = TextFormats.Markdown }, ct); + + var msgResponse = new MessageActivity($" ## Graph Me [{context.Activity.From?.Name}] \n ```json\n{indentedJson}\n```") + { TextFormat = TextFormats.Markdown }; + msgResponse.Recipient = context.Activity.From; + msgResponse.Recipient?.IsTargeted = context.Activity?.Conversation?.ConversationType == ConversationType.GroupChat; // only set IsTargeted for 1:1 chats to avoid issues in group contexts + await context.SendActivityAsync(msgResponse, ct); } catch (HttpRequestException ex) { - await context.SendActivityAsync($"Graph call failed: {ex.Message}", ct); + await context.SendActivityAsync($"[{context.Activity.From?.Name}] Graph call failed: {ex.Message}", ct); } }); @@ -96,24 +122,38 @@ string json = await http.GetStringAsync( "https://graph.microsoft.com/v1.0/me/events?$top=3&$select=subject,start,end&$orderby=start/dateTime", ct); string indentedJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(json), new JsonSerializerOptions { WriteIndented = true }); - await context.SendActivityAsync(new MessageActivity($" ## Graph Calendar \n ```json\n{indentedJson}\n```") { TextFormat = TextFormats.Markdown }, ct); + await context.SendActivityAsync(new MessageActivity($" ## Graph Calendar [{context.Activity.From?.Name}] \n ```json\n{indentedJson}\n```") { TextFormat = TextFormats.Markdown }, ct); } catch (HttpRequestException ex) { - await context.SendActivityAsync($"Graph call failed: {ex.Message}", ct); + await context.SendActivityAsync($"[{context.Activity.From?.Name}] Graph call failed: {ex.Message}", ct); } }); bot.OnMessage("(?i)^logout$", async (context, ct) => { await context.SignOut(cancellationToken: ct); - await context.SendActivityAsync("Signed out.", ct); + var signOutMessage = new MessageActivity($"You've been signed out, {context.Activity.From?.Name}.") + { + TextFormat = TextFormats.Markdown + }; + signOutMessage.Recipient = context.Activity.From; + signOutMessage.Recipient?.IsTargeted = context.Activity?.Conversation?.ConversationType == ConversationType.GroupChat; // only set IsTargeted for 1:1 chats to avoid issues in group contexts + await context.SendActivityAsync(signOutMessage, ct); }); bot.OnMessage("(?i)^status$", async (context, ct) => { bool signedIn = await context.IsSignedInAsync(cancellationToken: ct); - await context.SendActivityAsync(signedIn ? "Signed in." : "Not signed in.", ct); + var signInStatusMessage = new MessageActivity(signedIn + ? $"User {context.Activity.From?.Name} is signed in." + : $"User {context.Activity.From?.Name} is not signed in.") + { + TextFormat = TextFormats.Markdown + }; + signInStatusMessage.Recipient = context.Activity.From; + signInStatusMessage.Recipient?.IsTargeted = context.Activity?.Conversation?.ConversationType == ConversationType.GroupChat; // only set IsTargeted for 1:1 chats to avoid issues in group contexts + await context.SendActivityAsync(signInStatusMessage, ct); }); bot.OnMessage("(?i)^help$", async (context, ct) => @@ -131,7 +171,16 @@ """; await context.SendActivityAsync( - new MessageActivity(helpText) { TextFormat = TextFormats.Markdown }, ct); + new MessageActivity(helpText) { TextFormat = TextFormats.Markdown } + .WithSuggestedActions( + new SuggestedActions() { + Actions = new List() + { + new SuggestedAction() { Title = "Login", Type = "imBack", Value = "login" }, + new SuggestedAction() { Title = "Logout", Type = "imBack", Value = "logout" }, + new SuggestedAction() { Title = "Status", Type = "imBack", Value = "status" }, + } + }), ct); }); // ==================== INSTALL HANDLER ==================== diff --git a/core/samples/SsoBot/SsoBot.csproj b/core/samples/SsoBot/SsoBot.csproj index 0f379b438..8421911b7 100644 --- a/core/samples/SsoBot/SsoBot.csproj +++ b/core/samples/SsoBot/SsoBot.csproj @@ -4,6 +4,7 @@ net10.0 enable enable + $(NoWarn);ExperimentalTeamsTargeted diff --git a/core/src/Microsoft.Teams.Apps/OAuth/OAuthFlow.cs b/core/src/Microsoft.Teams.Apps/OAuth/OAuthFlow.cs index d973edd99..7f46cec09 100644 --- a/core/src/Microsoft.Teams.Apps/OAuth/OAuthFlow.cs +++ b/core/src/Microsoft.Teams.Apps/OAuth/OAuthFlow.cs @@ -180,10 +180,10 @@ public OAuthFlow OnSignInFailure(SignInFailureHandler handler) TeamsActivity oauthActivity = TeamsActivity.CreateBuilder() .WithConversationReference(context.Activity) - .WithRecipient(context.Activity.From, false) + .WithRecipient(context.Activity.From, context.Activity?.Conversation?.ConversationType == ConversationType.GroupChat) .WithAttachment(attachment) .Build(); - + await context.SendActivityAsync(oauthActivity, cancellationToken).ConfigureAwait(false); // Track that this user has a pending sign-in for this flow diff --git a/core/src/Microsoft.Teams.Apps/Schema/TeamsConversation.cs b/core/src/Microsoft.Teams.Apps/Schema/TeamsConversation.cs index 6597c1b6a..af4713495 100644 --- a/core/src/Microsoft.Teams.Apps/Schema/TeamsConversation.cs +++ b/core/src/Microsoft.Teams.Apps/Schema/TeamsConversation.cs @@ -72,7 +72,7 @@ public TeamsConversation() [JsonPropertyName("tenantId")] public string? TenantId { get; set; } /// - /// Conversation Type. See for known values. + /// Conversation Type. See for known values. /// [JsonPropertyName("conversationType")] public string? ConversationType { get; set; }