Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Teams] dotnet search auth config sample #1846

Merged
merged 7 commits into from
Oct 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;


namespace Microsoft.BotBuilderSamples
{
public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
{
public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger)
: base(configuration, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

// Send a message to the user
await turnContext.SendActivityAsync("The bot encounted an error or bug.");
await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code.");

// Send a trace activity, which will be displayed in the Bot Framework Emulator
await SendTraceActivityAsync(turnContext, exception);
};
}

private static async Task SendTraceActivityAsync(ITurnContext turnContext, Exception exception)
{
// Only send a trace activity if we're talking to the Bot Framework Emulator
if (turnContext.Activity.ChannelId == Channels.Emulator)
{
Activity traceActivity = new Activity(ActivityTypes.Trace)
{
Label = "TurnError",
Name = "OnTurnError Trace",
Value = exception.Message,
ValueType = "https://www.botframework.com/schemas/error",
};

// Send a trace activity
await turnContext.SendActivityAsync(traceActivity);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AdaptiveCards;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Teams;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Schema.Teams;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;

namespace Microsoft.BotBuilderSamples.Bots
{
public class TeamsMessagingExtensionsSearchAuthConfigBot : TeamsActivityHandler
{
readonly string _connectionName;
readonly string _siteUrl;
readonly UserState _userState;
readonly IStatePropertyAccessor<string> _userConfigProperty;

public TeamsMessagingExtensionsSearchAuthConfigBot(IConfiguration configuration, UserState userState)
{
_connectionName = configuration["ConnectionName"] ?? throw new NullReferenceException("ConnectionName");
_siteUrl = configuration["SiteUrl"] ?? throw new NullReferenceException("SiteUrl");
_userState = userState ?? throw new NullReferenceException(nameof(userState));
_userConfigProperty = userState.CreateProperty<string>("UserConfiguration");
}

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
await base.OnTurnAsync(turnContext, cancellationToken);

// After the turn is complete, persist any UserState changes.
await _userState.SaveChangesAsync(turnContext);
}

protected override async Task<MessagingExtensionResponse> OnTeamsMessagingExtensionConfigurationQuerySettingUrlAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
// The user has requested the Messaging Extension Configuration page.
var escapedSettings = string.Empty;
var userConfigSettings = await _userConfigProperty.GetAsync(turnContext, () => string.Empty);

if (!string.IsNullOrEmpty(userConfigSettings))
{
escapedSettings = Uri.EscapeDataString(userConfigSettings);
}

return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "config",
SuggestedActions = new MessagingExtensionSuggestedAction
{
Actions = new List<CardAction>
{
new CardAction
{
Type = ActionTypes.OpenUrl,
Value = $"{_siteUrl}/searchSettings.html?settings={escapedSettings}",
},
},
},
},
};
}

protected override async Task OnTeamsMessagingExtensionConfigurationSettingAsync(ITurnContext<IInvokeActivity> turnContext, JObject settings, CancellationToken cancellationToken)
{
// When the user submits the settings page, this event is fired.
var state = settings["state"];
if (state != null)
{
var userConfigSettings = state.ToString();
await _userConfigProperty.SetAsync(turnContext, userConfigSettings, cancellationToken);
}
}

protected override async Task<MessagingExtensionResponse> OnTeamsMessagingExtensionQueryAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery action, CancellationToken cancellationToken)
{
var text = action?.Parameters?[0]?.Value as string ?? string.Empty;

var attachments = new List<MessagingExtensionAttachment>();
var userConfigSettings = await _userConfigProperty.GetAsync(turnContext, () => string.Empty);
if (userConfigSettings.ToUpper().Contains("EMAIL"))
{
// When the Bot Service Auth flow completes, the action.State will contain a magic code used for verification.
var magicCode = string.Empty;
var state = action.State;
if (!string.IsNullOrEmpty(state))
{
int parsed = 0;
if (int.TryParse(state, out parsed))
{
magicCode = parsed.ToString();
}
}

var tokenResponse = await (turnContext.Adapter as IUserTokenProvider).GetUserTokenAsync(turnContext, _connectionName, magicCode, cancellationToken: cancellationToken);
if (tokenResponse == null || string.IsNullOrEmpty(tokenResponse.Token))
{
// There is no token, so the user has not signed in yet.

// Retrieve the OAuth Sign in Link to use in the MessagingExtensionResult Suggested Actions
var signInLink = await (turnContext.Adapter as IUserTokenProvider).GetOauthSignInLinkAsync(turnContext, _connectionName, cancellationToken);

return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "auth",
SuggestedActions = new MessagingExtensionSuggestedAction
{
Actions = new List<CardAction>
{
new CardAction
{
Type = ActionTypes.OpenUrl,
Value = signInLink,
Title = "Bot Service OAuth",
},
},
},
},
};
}

var client = new SimpleGraphClient(tokenResponse.Token);

var messages = await client.SearchMailInboxAsync(text);

// Here we construct a ThumbnailCard for every attachment, and provide a HeroCard which will be
// displayed if the selects that item.
attachments = messages.Select(msg => new MessagingExtensionAttachment
{
ContentType = HeroCard.ContentType,
Content = new HeroCard
{
Title = msg.From.EmailAddress.Address,
Subtitle = msg.Subject,
Text = msg.Body.Content,
},
Preview = new ThumbnailCard
{
Title = msg.From.EmailAddress.Address,
Text = $"{msg.Subject}<br />{msg.BodyPreview}",
Images = new List<CardImage>()
{
new CardImage("https://raw.githubusercontent.com/microsoft/botbuilder-samples/master/docs/media/OutlookLogo.jpg", "Outlook Logo"),
},
}.ToAttachment()
}
).ToList();
}
else
{
var packages = await FindPackages(text);
// We take every row of the results and wrap them in cards wrapped in in MessagingExtensionAttachment objects.
// The Preview is optional, if it includes a Tap, that will trigger the OnTeamsMessagingExtensionSelectItemAsync event back on this bot.
attachments = packages.Select(package => {
var previewCard = new ThumbnailCard { Title = package.Item1, Tap = new CardAction { Type = "invoke", Value = package } };
if (!string.IsNullOrEmpty(package.Item5))
{
previewCard.Images = new List<CardImage>() { new CardImage(package.Item5, "Icon") };
}

var attachment = new MessagingExtensionAttachment
{
ContentType = HeroCard.ContentType,
Content = new HeroCard { Title = package.Item1 },
Preview = previewCard.ToAttachment()
};

return attachment;
}).ToList();
}

// The list of MessagingExtensionAttachments must we wrapped in a MessagingExtensionResult wrapped in a MessagingExtensionResponse.
return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "result",
AttachmentLayout = "list",
Attachments = attachments
}
};
}

protected override Task<MessagingExtensionResponse> OnTeamsMessagingExtensionSelectItemAsync(ITurnContext<IInvokeActivity> turnContext, JObject query, CancellationToken cancellationToken)
{
// The Preview card's Tap should have a Value property assigned, this will be returned to the bot in this event.
var (packageId, version, description, projectUrl, iconUrl) = query.ToObject<(string, string, string, string, string)>();

// We take every row of the results and wrap them in cards wrapped in in MessagingExtensionAttachment objects.
// The Preview is optional, if it includes a Tap, that will trigger the OnTeamsMessagingExtensionSelectItemAsync event back on this bot.
var card = new ThumbnailCard
{
Title = $"{packageId}, {version}",
Subtitle = description,
Buttons = new List<CardAction>
{
new CardAction { Type = ActionTypes.OpenUrl, Title = "Nuget Package", Value = $"https://www.nuget.org/packages/{packageId}" },
new CardAction { Type = ActionTypes.OpenUrl, Title = "Project", Value = projectUrl },
},
};

if (!string.IsNullOrEmpty(iconUrl))
{
card.Images = new List<CardImage>() { new CardImage(iconUrl, "Icon") };
}

var attachment = new MessagingExtensionAttachment
{
ContentType = ThumbnailCard.ContentType,
Content = card,
};

return Task.FromResult(new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "result",
AttachmentLayout = "list",
Attachments = new List<MessagingExtensionAttachment> { attachment }
}
});
}

protected override Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionSubmitActionAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction action, CancellationToken cancellationToken)
{
// This method is to handle the 'Close' button on the confirmation Task Module after the user signs out.
return Task.FromResult(new MessagingExtensionActionResponse());
}

protected override async Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionFetchTaskAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
if (query.CommandId.ToUpper() == "SIGNOUTCOMMAND")
{
await (turnContext.Adapter as IUserTokenProvider).SignOutUserAsync(turnContext, _connectionName, turnContext.Activity.From.Id, cancellationToken);

return new MessagingExtensionActionResponse
{
Task = new TaskModuleContinueResponse
{
Value = new TaskModuleTaskInfo
{
Card = new Attachment
{
Content = new AdaptiveCard(new AdaptiveSchemaVersion("1.0"))
{
Body = new List<AdaptiveElement>() { new AdaptiveTextBlock() { Text = "You have been signed out." } },
Actions = new List<AdaptiveAction>() { new AdaptiveSubmitAction() { Title = "Close" } },
},
ContentType = AdaptiveCard.ContentType,
},
Height = 200,
Width = 400,
Title = "Adaptive Card: Inputs",
},
},
};
}
return null;
}

// Generate a set of substrings to illustrate the idea of a set of results coming back from a query.
private async Task<IEnumerable<(string, string, string, string, string)>> FindPackages(string text)
{
var obj = JObject.Parse(await (new HttpClient()).GetStringAsync($"https://azuresearch-usnc.nuget.org/query?q=id:{text}&prerelease=true"));
return obj["data"].Select(item => (item["id"].ToString(), item["version"].ToString(), item["description"].ToString(), item["projectUrl"]?.ToString(), item["iconUrl"]?.ToString()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;

namespace Microsoft.BotBuilderSamples.Controllers
{
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
// achieved by specifying a more specific type for the bot constructor argument.
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter Adapter;
private readonly IBot Bot;

public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
Adapter = adapter;
Bot = bot;
}

[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await Adapter.ProcessAsync(Request, Response, Bot);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
Loading