diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload.sln b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload.sln new file mode 100644 index 0000000000..660c6086bb --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InlineImageDownload", "InlineImageDownload\InlineImageDownload.csproj", "{388685A8-7D39-4875-9B14-D139CC9D3633}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {388685A8-7D39-4875-9B14-D139CC9D3633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {388685A8-7D39-4875-9B14-D139CC9D3633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {388685A8-7D39-4875-9B14-D139CC9D3633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {388685A8-7D39-4875-9B14-D139CC9D3633}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {43061AB5-7B67-48C5-8B7A-7C980222B3E9} + EndGlobalSection +EndGlobal diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/AdapterWithErrorHandler.cs b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..0fb67821eb --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/AdapterWithErrorHandler.cs @@ -0,0 +1,30 @@ +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace InlineImageDownload +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + // NOTE: In production environment, you should consider logging this to + // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how + // to add telemetry capture to your bot. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + await turnContext.SendActivityAsync("The bot encountered 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 turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Bot/ImageDownloadBot.cs b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Bot/ImageDownloadBot.cs new file mode 100644 index 0000000000..99cbe7af64 --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Bot/ImageDownloadBot.cs @@ -0,0 +1,81 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace InlineImageDownload.Bot +{ + public class ImageDownloadBot : ActivityHandler + { + private readonly IConfiguration configuration; + + public ImageDownloadBot(IConfiguration configuration) + { + this.configuration = configuration; + } + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + try + { + var message = turnContext.Activity; + if (message.Attachments!=null) + { + var attachment = message.Attachments[0]; + using (HttpClient httpClient = new HttpClient()) + { + // MS Teams attachment URLs are secured by a JwtToken, so we need to pass the token from our bot. + var token = await new MicrosoftAppCredentials(this.configuration["MicrosoftAppId"], this.configuration["MicrosoftAppPassword"]).GetTokenAsync(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + var responseMessage = await httpClient.GetAsync(attachment.ContentUrl); + var contentLenghtBytes = responseMessage.Content.Headers.ContentLength; + + // You could not use this response message to fetch the image for further processing. + if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK) + { + Stream attachmentStream = await responseMessage.Content.ReadAsStreamAsync(); + attachmentStream.Position = 0; + var image = Image.FromStream(attachmentStream); + // Saves the image in solution folder + image.Save(@"ImageFromUser.png"); + await turnContext.SendActivityAsync($"Attachment of {attachment.ContentType} type and size of {contentLenghtBytes} bytes received."); + } + else + { + await turnContext.SendActivityAsync($"Resoponse: {responseMessage.ToString()} \n\nContentUrl: {attachment.ContentUrl}\n\nBearerToken: {token}"); + + } + } + } + else + { + await turnContext.SendActivityAsync($"No image attachment received"); + } + } + catch (Exception ex) + { + await turnContext.SendActivityAsync(ex.ToString()); + } + + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + var welcomeText = "Hello and welcome!"; + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken); + } + } + } + } +} diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Controllers/BotController.cs b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Controllers/BotController.cs new file mode 100644 index 0000000000..7694a47f5c --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Controllers/BotController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using System.Threading.Tasks; + +namespace InlineImageDownload.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, HttpGet] + 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); + } + } +} diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/ImageFromUser.png b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/ImageFromUser.png new file mode 100644 index 0000000000..769300b0c1 Binary files /dev/null and b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/ImageFromUser.png differ diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/InlineImageDownload.csproj b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/InlineImageDownload.csproj new file mode 100644 index 0000000000..cc6ccc85ed --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/InlineImageDownload.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + + + + + + + + + diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Program.cs b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Program.cs new file mode 100644 index 0000000000..623023eb51 --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace InlineImageDownload +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Properties/launchSettings.json b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Properties/launchSettings.json new file mode 100644 index 0000000000..e6da805d1f --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:60884/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "InlineImageDownload": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Startup.cs b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Startup.cs new file mode 100644 index 0000000000..6913b25528 --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/Startup.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using InlineImageDownload.Bot; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace InlineImageDownload +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + + app.UseDefaultFiles() + .UseStaticFiles() + .UseWebSockets() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/appsettings.json b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/appsettings.json new file mode 100644 index 0000000000..97dff81a1b --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/InlineImageDownload/appsettings.json @@ -0,0 +1,4 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "" +} diff --git a/experimental/83.teams-inline-image-download-main-C# dot-net-core/README.md b/experimental/83.teams-inline-image-download-main-C# dot-net-core/README.md new file mode 100644 index 0000000000..a546cde398 --- /dev/null +++ b/experimental/83.teams-inline-image-download-main-C# dot-net-core/README.md @@ -0,0 +1,66 @@ + +# Teams Conversation Bot + +Bot Framework v4 Conversation Bot sample for Teams. + +This bot has been created using [Bot Framework](https://dev.botframework.com). This sample shows +how to send an inline image to Bot + +## Prerequisites + +- Microsoft Teams is installed and you have an account +- [.NET Core SDK](https://dotnet.microsoft.com/download) version 3.1 +- [ngrok](https://ngrok.com/) or equivalent tunnelling solution + +## To try this sample + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-samples.git + ``` + +1) If you are using Visual Studio + - Launch Visual Studio + - File -> Open -> Project/Solution + - Navigate to `samples/csharp_dotnetcore/57.teams-conversation-bot` folder + - Select `TeamsConversationBot.csproj` file + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http -host-header=rewrite 3978 + ``` + +1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure + - Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample + - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + +1) Update the `appsettings.json` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + +1) __*This step is specific to Teams.*__ + - **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip` + - **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app") + +1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder. + +## Interacting with the bot + + + +### Avoiding Permission-Related Errors + +You may encounter permission-related errors when sending a proactive message. This can often be mitigated by using `MicrosoftAppCredentials.TrustServiceUrl()`. See [the documentation](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp#avoiding-401-unauthorized-errors) for more information. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [How Microsoft Teams bots work](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=javascript) diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/AdapterWithErrorHandler.cs b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..d81e5bae8f --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/AdapterWithErrorHandler.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace SubscribeToBotEvents +{ + using Microsoft.Bot.Builder.Integration.AspNet.Core; + using Microsoft.Bot.Builder.TraceExtensions; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; + + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + // NOTE: In production environment, you should consider logging this to + // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how + // to add telemetry capture to your bot. + logger.LogError($"Exception caught : {exception.Message}"); + + // Send a catch-all apology to the user. + await turnContext.SendActivityAsync("Sorry, it looks like something went wrong."); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Bots/SubscribeToBotEvents.cs b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Bots/SubscribeToBotEvents.cs new file mode 100644 index 0000000000..8e2ef8d6d5 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Bots/SubscribeToBotEvents.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace SubscribeToBotEvents.Bots +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Teams; + using Microsoft.Bot.Schema; + using Microsoft.Bot.Schema.Teams; + + /// + /// Represents a bot that processes incoming activities. + /// For each user interaction, an instance of this class is created and the OnTurnAsync method is called. + /// This is a Transient lifetime service. Transient lifetime services are created each time they're requested. + /// For each Activity received, a new instance of this class is created. + /// Objects that are expensive to construct, or have a lifetime beyond the single turn, should be carefully managed. + /// + public class SubscribeToBotEvents : TeamsActivityHandler + { + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + await turnContext.SendActivityAsync(MessageFactory.Text("You said:" + turnContext.Activity.Text), cancellationToken); + } + + protected override async Task OnTeamsChannelCreatedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) + { + var heroCard = new HeroCard(text: $"{channelInfo.Name} is the Channel created"); + await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken); + } + + protected override async Task OnTeamsChannelRenamedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) + { + var heroCard = new HeroCard(text: $"{channelInfo.Name} is the new Channel name"); + await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken); + } + + protected override async Task OnTeamsChannelDeletedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) + { + var heroCard = new HeroCard(text: $"{channelInfo.Name} is the Channel deleted"); + await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken); + } + + protected override async Task OnTeamsMembersAddedAsync(IList membersAdded, TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var teamMember in membersAdded) + { + if (teamMember.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text($"Welcome to the team {teamMember.GivenName} {teamMember.Surname}."), cancellationToken); + } + } + } + + protected override async Task OnTeamsMembersRemovedAsync(IList membersRemoved, TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (TeamsChannelAccount member in membersRemoved) + { + if (member.Id == turnContext.Activity.Recipient.Id) + { + // The bot was removed + // You should clear any cached data you have for this team + } + else + { + var heroCard = new HeroCard(text: $"{member.Name} was removed from {teamInfo.Name}"); + await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken); + } + } + } + + protected override async Task OnTeamsTeamRenamedAsync(TeamInfo teamInfo, ITurnContext turnContext, CancellationToken cancellationToken) + { + var heroCard = new HeroCard(text: $"{teamInfo.Name} is the new Team name"); + await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken); + } + + protected override async Task OnReactionsAddedAsync(IList messageReactions, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var reaction in messageReactions) + { + var newReaction = $"You reacted with '{reaction.Type}' to the following message: '{turnContext.Activity.ReplyToId}'"; + var replyActivity = MessageFactory.Text(newReaction); + await turnContext.SendActivityAsync(replyActivity, cancellationToken); + } + } + + protected override async Task OnReactionsRemovedAsync(IList messageReactions, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var reaction in messageReactions) + { + var newReaction = $"You removed the reaction '{reaction.Type}' from the following message: '{turnContext.Activity.ReplyToId}'"; + var replyActivity = MessageFactory.Text(newReaction); + await turnContext.SendActivityAsync(replyActivity, cancellationToken); + } + } + } +} diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Controllers/BotController.cs b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Controllers/BotController.cs new file mode 100644 index 0000000000..2f967b66f5 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Controllers/BotController.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace SubscribeToBotEvents.Controllers +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Integration.AspNet.Core; + + // 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); + } + } +} diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Program.cs b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Program.cs new file mode 100644 index 0000000000..eb81df02e0 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Program.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace SubscribeToBotEvents +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.ConfigureLogging((logging) => + { + logging.AddDebug(); + logging.AddConsole(); + }); + webBuilder.UseStartup(); + }); + } +} diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Properties/launchSettings.json b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Properties/launchSettings.json new file mode 100644 index 0000000000..91a6bd24fd --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:44386", + "sslPort": 0 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EchoBot": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:3979;http://localhost:3978" + } + } +} \ No newline at end of file diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/README.md b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/README.md new file mode 100644 index 0000000000..930bda3e17 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/README.md @@ -0,0 +1,70 @@ + +# Teams Conversation Bot + +Bot Framework v4 Subcribe to bot events sample for Teams. + +This bot has been created using [Bot Framework](https://dev.botframework.com). This sample shows +all the conversational events for a Teams Bot. + +## Prerequisites + +- Microsoft Teams is installed and you have an account +- [.NET Core SDK](https://dotnet.microsoft.com/download) version 3.1 +- [ngrok](https://ngrok.com/) or equivalent tunnelling solution + +## To try this sample + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-samples.git + ``` + +1) If you are using Visual Studio + - Launch Visual Studio + - File -> Open -> Project/Solution + - Navigate to `samples/csharp_dotnetcore/57.teams-conversation-bot` folder + - Select `TeamsConversationBot.csproj` file + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http -host-header=rewrite 3978 + ``` + +1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure + - Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample + - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + +1) Update the `appsettings.json` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + +1) __*This step is specific to Teams.*__ + - **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip` + - **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app") + +1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder. + +## Interacting with the bot + + - Bot will send a Welcome message with the logged in User name upon adding the app. + - Post respective messages in the General channel of a Team when any channel is created, renamed, deleted or in case of Team rename as well. + - A message will be posted in the General channel, welcoming the members being added, removed. + - Reply message would be posted upon the reaction addition/removal. + +### Avoiding Permission-Related Errors + +You may encounter permission-related errors when sending a proactive message. This can often be mitigated by using `MicrosoftAppCredentials.TrustServiceUrl()`. See [the documentation](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp#avoiding-401-unauthorized-errors) for more information. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [How Microsoft Teams bots work](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=javascript) + diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Startup.cs b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Startup.cs new file mode 100644 index 0000000000..bdf801c971 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/Startup.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace SubscribeToBotEvents +{ + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Integration.AspNet.Core; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + // app.UseHttpsRedirection(); + } + } +} diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/SubscribeToBotEvents.csproj b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/SubscribeToBotEvents.csproj new file mode 100644 index 0000000000..d3ea5487a1 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/SubscribeToBotEvents.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + latest + + + + + + + + + + Always + + + True + True + + + + diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/SubscribeToBotEvents.sln b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/SubscribeToBotEvents.sln new file mode 100644 index 0000000000..2a76d31559 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/SubscribeToBotEvents.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubscribeToBotEvents", "SubscribeToBotEvents.csproj", "{41564FAD-2225-48F5-B474-DA158F9730E9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41564FAD-2225-48F5-B474-DA158F9730E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41564FAD-2225-48F5-B474-DA158F9730E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41564FAD-2225-48F5-B474-DA158F9730E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41564FAD-2225-48F5-B474-DA158F9730E9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7AE137BD-333D-4373-AEA1-2A74E3250067} + EndGlobalSection +EndGlobal diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/icon-color.png b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/icon-color.png new file mode 100644 index 0000000000..48a2de1330 Binary files /dev/null and b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/icon-color.png differ diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/icon-outline.png b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/icon-outline.png new file mode 100644 index 0000000000..dbfa927729 Binary files /dev/null and b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/icon-outline.png differ diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/manifest.json b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/manifest.json new file mode 100644 index 0000000000..eed2405d85 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/TeamsAppManifest/manifest.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", + "manifestVersion": "1.5", + "version": "1.0.0", + "id": "<>", + "packageName": "com.teams.sample.teamsconversationbot", + "developer": { + "name": "teamsConversationBot", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.teams.com/privacy", + "termsOfUseUrl": "https://www.teams.com/termsofuser" + }, + "icons": { + "outline": "icon-outline.png", + "color": "icon-color.png" + }, + "name": { + "short": "SubcribeToBotEvents", + "full": "Welcome to SubcribeToBotEvents" + }, + "description": { + "short": "SubcribeToBotEvents", + "full": "SubcribeToBotEvents" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ + "personal", + "groupchat", + "team" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/appsettings.json b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/appsettings.json new file mode 100644 index 0000000000..900773a488 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/appsettings.json @@ -0,0 +1,4 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "" +} \ No newline at end of file diff --git a/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/wwwroot/default.html b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/wwwroot/default.html new file mode 100644 index 0000000000..1bca4a8026 --- /dev/null +++ b/experimental/84.teams-subscribe-to-conversation-events-master-C# dot-net-core/wwwroot/default.html @@ -0,0 +1,292 @@ + + + + + + + Subscribe To BotEvents + + + + + +
+
+
+
Subscribe To BotEvents
+
+
+
+
+
Your bot is ready!
+
+ You can now test your bot in Teams.
+
+ Visit + Azure + Bot Service + to register your bot and add it to the
+ Teams channel. The bot's endpoint URL typically looks + like this: +
+
https://your_bots_hostname/api/messages
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation.sln b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation.sln new file mode 100644 index 0000000000..9d6231701f --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30330.147 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JustInTimeInstallation", "JustInTimeInstallation\JustInTimeInstallation.csproj", "{C8BA2AFA-0A24-4C19-97CD-3F446FA1CCF0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C8BA2AFA-0A24-4C19-97CD-3F446FA1CCF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8BA2AFA-0A24-4C19-97CD-3F446FA1CCF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8BA2AFA-0A24-4C19-97CD-3F446FA1CCF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8BA2AFA-0A24-4C19-97CD-3F446FA1CCF0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {259558D3-FFE3-4FBA-AF5F-C6ADEE730CE9} + EndGlobalSection +EndGlobal diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/AdapterWithErrorHandler.cs b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..6d906946d3 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/AdapterWithErrorHandler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace JustInTimeInstallation +{ + using Microsoft.Bot.Builder.Integration.AspNet.Core; + using Microsoft.Bot.Builder.TraceExtensions; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; + + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger) + : base(configuration, logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + // NOTE: In production environment, you should consider logging this to + // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how + // to add telemetry capture to your bot. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + await turnContext.SendActivityAsync("The bot encountered 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 turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Bots/JustInTimeInstallationBot.cs b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Bots/JustInTimeInstallationBot.cs new file mode 100644 index 0000000000..9d6f2d0d74 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Bots/JustInTimeInstallationBot.cs @@ -0,0 +1,127 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace JustInTimeInstallation.Bots +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Teams; + using Microsoft.Bot.Schema; + using Microsoft.Bot.Schema.Teams; + using Newtonsoft.Json; + + /// + /// Represents a bot that processes incoming activities. + /// For each user interaction, an instance of this class is created and the OnTurnAsync method is called. + /// This is a Transient lifetime service. Transient lifetime services are created each time they're requested. + /// For each Activity received, a new instance of this class is created. + /// Objects that are expensive to construct, or have a lifetime beyond the single turn, should be carefully managed. + /// + public class JustInTimeInstallationBot : TeamsActivityHandler + { + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + var replyText = $"Echo: {turnContext.Activity.Text}"; + await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken); + } + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + var welcomeText = "Hello and welcome!"; + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken); + } + } + } + + protected override async Task OnTeamsMessagingExtensionFetchTaskAsync(ITurnContext turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) + { + // we are handling two cases within try/catch block + //if the bot is installed it will create adaptive card attachment and show card with input fields + try + { + await TeamsInfo.GetPagedMembersAsync(turnContext, 100, null, cancellationToken); + return new MessagingExtensionActionResponse + { + Task = new TaskModuleContinueResponse + { + Value = new TaskModuleTaskInfo + { + Card = CreateAdaptiveCardAttachment(), + Height = 200, + Width = 400, + Title = "Adaptive Card: Inputs", + }, + }, + }; + } + catch (Exception ex) + { + if (ex.Message.Contains("403")) + { + // else it will show installation card in Task module for the Bot so user can install the app + return new MessagingExtensionActionResponse + { + Task = new TaskModuleContinueResponse + { + Value = new TaskModuleTaskInfo + { + Card = GetJustInTimeInstallationCard(), + Height = 200, + Width = 400, + Title = "Adaptive Card: Inputs", + }, + }, + }; + + } + return null; + + } + } + + protected override Task OnTeamsMessagingExtensionSubmitActionAsync(ITurnContext 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()); + } + + private static Attachment CreateAdaptiveCardAttachment() + { + // combine path for cross platform support + string[] paths = { ".", "Resources", "adaptiveCard.json" }; + var adaptiveCardJson = File.ReadAllText(Path.Combine(paths)); + + var adaptiveCardAttachment = new Attachment() + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = JsonConvert.DeserializeObject(adaptiveCardJson), + }; + return adaptiveCardAttachment; + } + + private static Attachment GetJustInTimeInstallationCard() + { + + // combine path for cross platform support + //Reading the card json and sending it as an attachment to Task module response + string[] paths = { ".", "Resources", "justintimeinstallation.json" }; + var adaptiveCardJson = File.ReadAllText(Path.Combine(paths)); + + var adaptiveCardAttachment = new Attachment() + { + ContentType = "application/vnd.microsoft.card.adaptive", + Content = JsonConvert.DeserializeObject(adaptiveCardJson), + }; + return adaptiveCardAttachment; + } + } +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Controllers/BotController.cs b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Controllers/BotController.cs new file mode 100644 index 0000000000..be7db24192 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Controllers/BotController.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace JustInTimeInstallation.Controllers +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Integration.AspNet.Core; + + /// + /// 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, HttpGet] + 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); + } + } +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/JustInTimeInstallation.csproj b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/JustInTimeInstallation.csproj new file mode 100644 index 0000000000..c05705adde --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/JustInTimeInstallation.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp3.1 + + + + + + + + + + diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Program.cs b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Program.cs new file mode 100644 index 0000000000..eb059b4294 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Program.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace JustInTimeInstallation +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.ConfigureLogging((logging) => + { + logging.AddDebug(); + logging.AddConsole(); + }); + webBuilder.UseStartup(); + }); + } +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Properties/launchSettings.json b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Properties/launchSettings.json new file mode 100644 index 0000000000..49908eb0b3 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4412", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "JustInTimeInstallation": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Resources/adaptiveCard.json b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Resources/adaptiveCard.json new file mode 100644 index 0000000000..0c100bc05e --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Resources/adaptiveCard.json @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0", + "type": "AdaptiveCard", + "body": [ + { + "type": "TextBlock", + "text": "This app is installed in this conversation. You can now use it to do some great stuff!!", + "isSubtle": false, + "wrap": true + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "Close" + } + ] +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Resources/justintimeinstallation.json b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Resources/justintimeinstallation.json new file mode 100644 index 0000000000..df6010f34b --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Resources/justintimeinstallation.json @@ -0,0 +1,22 @@ +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "TextBlock", + "text": "Looks like you haven't used This app in this team chat. Please click on Continue to install this app", + "wrap": true + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "Continue", + "data": { + "msteams": { + "justInTimeInstall": true + } + } + } + ], + "version": "1.0" +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Startup.cs b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Startup.cs new file mode 100644 index 0000000000..39561e3391 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/Startup.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. +// + +namespace JustInTimeInstallation +{ + using JustInTimeInstallation.Bots; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Integration.AspNet.Core; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddMvc(); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseWebSockets() + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + // app.UseHttpsRedirection(); + } + } +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/icon-color.png b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/icon-color.png new file mode 100644 index 0000000000..48a2de1330 Binary files /dev/null and b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/icon-color.png differ diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/icon-outline.png b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/icon-outline.png new file mode 100644 index 0000000000..dbfa927729 Binary files /dev/null and b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/icon-outline.png differ diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/manifest.json b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/manifest.json new file mode 100644 index 0000000000..2171f99bc9 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/TeamsAppManifest/manifest.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", + "manifestVersion": "1.5", + "version": "1.1", + "id": "<>", + "packageName": "com.microsoft.teams.samples", + "developer": { + "name": "Microsoft", + "websiteUrl": "https://dev.botframework.com", + "privacyUrl": "https://privacy.microsoft.com", + "termsOfUseUrl": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx" + }, + "name": { + "short": "Just In Time Installation App", + "full": "Just In Time Installation App for Microsoft Teams" + }, + "description": { + "short": "Just In Time Installation App for Microsoft Teams", + "full": "This sample app provides a very simple app for Microsoft Teams. You can extend this to add more content and capabilities." + }, + "icons": { + "outline": "icon-outline.png", + "color": "icon-color.png" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "needsChannelSelector": false, + "isNotificationOnly": false, + "scopes": [ + "team", + "personal", + "groupchat" + ] + } + ], + "composeExtensions": [ + { + "botId": "<>", + "commands": [ + { + "id": "FetchRoster", + "description": "Fetch the conversation roster", + "title": "FetchRoster", + "type": "action", + "fetchTask": true, + "parameters": [ + { + "name": "Name", + "title": "Title" + } + ] + } + ] + } + ], + "permissions": [ + "identity" + ], + "validDomains": [ "*.ngrok.io" ] +} diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/appsettings.json b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/appsettings.json new file mode 100644 index 0000000000..900773a488 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/appsettings.json @@ -0,0 +1,4 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "" +} \ No newline at end of file diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/wwwroot/default.html b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/wwwroot/default.html new file mode 100644 index 0000000000..37fe5d10f1 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/JustInTimeInstallation/wwwroot/default.html @@ -0,0 +1,426 @@ + + + + + + + JustInTimeInstallation + + + + + +
+
+
+
JustInTimeInstallation
+
+
+
+
+
Your bot is ready!
+
+ You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages. +
+ +
+ Visit + Azure + Bot Service + to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this: +
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/README.md b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/README.md new file mode 100644 index 0000000000..f312f46188 --- /dev/null +++ b/experimental/89.teams-just-in-time-installation-master-C# dot-net-core/README.md @@ -0,0 +1,66 @@ + +# Teams Just In Time Installation Bot + +Bot Framework v4 Just In Time Installation Bot sample for Teams. + +This bot has been created using [Bot Framework](https://dev.botframework.com). This sample shows +how to do a just in time installation of your app in a conversation. the sample code check if the Bot is installed in current context (by fetching the roaster) or not if Bot is installed in current context it sends an adaptive card inputs else it throws a Just in time installation dialog to the user so they can install the app. Please check [Request to Install your conversational Bots](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/messaging-extension-v3/create-extensions?tabs=typescript#request-to-install-your-conversational-bot) for more information about just in time installation + +## Prerequisites + +- Microsoft Teams is installed and you have an account +- [.NET Core SDK](https://dotnet.microsoft.com/download) version 3.1 +- [ngrok](https://ngrok.com/) or equivalent tunnelling solution + +## To try this sample + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-samples.git + ``` + +1) If you are using Visual Studio + - Launch Visual Studio + - File -> Open -> Project/Solution + + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http -host-header=rewrite 3978 + ``` + +1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure + - Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample + - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + +1) Update the `appsettings.json` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + +1) __*This step is specific to Teams.*__ + - **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip` + - **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app") + +1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder. + +## Interacting with the bot + +You can test this bot by invoking the Message Extension from a conversation where this app has not been installed. + +### Avoiding Permission-Related Errors + +You may encounter permission-related errors when sending a proactive message. This can often be mitigated by using `MicrosoftAppCredentials.TrustServiceUrl()`. See [the documentation](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp#avoiding-401-unauthorized-errors) for more information. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [How Microsoft Teams bots work](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=javascript) +