From 4b5ae302352f85f1c34aafea7e7c187840510390 Mon Sep 17 00:00:00 2001 From: soxtoby Date: Tue, 2 Jan 2018 13:49:26 +1000 Subject: [PATCH] Adding Dialogs and other missing API methods --- SlackNet.AspNetCore/AspNetCoreExtensions.cs | 3 + .../NullDialogSubmissionHandler.cs | 12 + SlackNet.AspNetCore/ResolvedActionHandler.cs | 10 +- .../ResolvedDialogSubmissionHandler.cs | 24 + SlackNet.AspNetCore/ResolvedOptionProvider.cs | 1 + SlackNet.AspNetCore/SlackActionsService.cs | 1 + SlackNet.AspNetCore/SlackEventsMiddleware.cs | 55 ++- SlackNet.AspNetCore/SlackOptionsService.cs | 1 + .../SlackServiceConfiguration.cs | 8 + SlackNet.EventsExample/ColorSelector.cs | 15 +- SlackNet.EventsExample/Counter.cs | 9 +- SlackNet.EventsExample/DialogDemo.cs | 115 +++++ SlackNet.EventsExample/MessageHandler.cs | 8 + .../Properties/launchSettings.json | 25 + SlackNet.EventsExample/Startup.cs | 5 +- SlackNet.Tests/ApiLintTest.cs | 182 ++++++++ SlackNet.Tests/SerializationTests.cs | 19 +- SlackNet.Tests/SlackBotTests.cs | 4 +- SlackNet/Default.cs | 1 + SlackNet/EnumNameConverter.cs | 29 +- SlackNet/Interaction/ActionElement.cs | 13 + .../AttachmentUpdateResponse.cs | 5 +- SlackNet/Interaction/Button.cs | 10 + SlackNet/Interaction/Dialog.cs | 12 + SlackNet/Interaction/DialogElement.cs | 14 + SlackNet/Interaction/DialogElementType.cs | 11 + SlackNet/Interaction/DialogError.cs | 8 + SlackNet/Interaction/DialogErrorResponse.cs | 9 + SlackNet/Interaction/DialogSubmission.cs | 9 + .../IActionHandler.cs | 2 +- .../Interaction/IDialogSubmissionHandler.cs | 10 + SlackNet/Interaction/IOptionProvider.cs | 9 + SlackNet/Interaction/InputElementType.cs | 10 + SlackNet/Interaction/InteractionRequest.cs | 13 + .../InteractiveMessage.cs | 17 +- .../Action.cs => Interaction/Menu.cs} | 17 +- .../MessageResponse.cs | 2 +- .../MessageUpdateResponse.cs | 3 +- .../OptionsRequest.cs | 2 +- SlackNet/Interaction/OptionsResponse.cs | 10 + .../{WebApi => Interaction}/ResponseType.cs | 2 +- SlackNet/Interaction/SelectElement.cs | 11 + SlackNet/Interaction/SelectOption.cs | 8 + .../{Actions => Interaction}/SlackActions.cs | 2 +- .../{Actions => Interaction}/SlackOptions.cs | 13 +- SlackNet/Interaction/TextArea.cs | 7 + SlackNet/Interaction/TextElement.cs | 7 + SlackNet/Interaction/TextElementBase.cs | 11 + SlackNet/Objects/Attachment.cs | 5 +- SlackNet/Objects/Conversation.cs | 147 ++++++ SlackNet/Objects/Purpose.cs | 9 + SlackNet/SlackApiClient.cs | 12 +- SlackNet/SlackException.cs | 13 +- SlackNet/SlackNet.csproj | 4 + SlackNet/SlackNet.csproj.DotSettings | 3 +- SlackNet/Utils.cs | 2 +- SlackNet/WebApi/AuthApi.cs | 2 +- SlackNet/WebApi/ChatApi.cs | 36 +- SlackNet/WebApi/ConversationType.cs | 10 + SlackNet/WebApi/ConversationsApi.cs | 426 ++++++++++++++++++ SlackNet/WebApi/DialogApi.cs | 37 ++ SlackNet/WebApi/DndApi.cs | 2 +- SlackNet/WebApi/FileCommentsApi.cs | 4 +- SlackNet/WebApi/GroupsApi.cs | 10 +- SlackNet/WebApi/Message.cs | 9 +- SlackNet/WebApi/MigrationApi.cs | 37 ++ SlackNet/WebApi/RemindersApi.cs | 2 +- .../Responses/ConversationHistoryResponse.cs | 8 + .../Responses/ConversationJoinResponse.cs | 9 + .../ConversationJoinResponseMetadata.cs | 9 + .../Responses/ConversationListResponse.cs | 10 + .../Responses/ConversationMembersResponse.cs | 10 + .../Responses/ConversationMessagesResponse.cs | 12 + .../Responses/ConversationOpenResponse.cs | 9 + .../WebApi/Responses/ConversationResponse.cs | 7 + SlackNet/WebApi/Responses/ErrorResponse.cs | 8 + .../WebApi/Responses/ErrorResponseMetadata.cs | 9 + .../WebApi/Responses/MigrationResponse.cs | 12 + .../WebApi/Responses/PermalinkResponse.cs | 8 + .../WebApi/Responses/PostEphemeralResponse.cs | 7 + ...esponseMetadata.cs => ResponseMetadata.cs} | 2 +- SlackNet/WebApi/Responses/UserListResponse.cs | 2 +- SlackNet/WebApi/UsersApi.cs | 19 +- SlackNet/WebApi/WebApiResponse.cs | 1 - 84 files changed, 1568 insertions(+), 118 deletions(-) create mode 100644 SlackNet.AspNetCore/NullDialogSubmissionHandler.cs create mode 100644 SlackNet.AspNetCore/ResolvedDialogSubmissionHandler.cs create mode 100644 SlackNet.EventsExample/DialogDemo.cs create mode 100644 SlackNet.EventsExample/Properties/launchSettings.json create mode 100644 SlackNet.Tests/ApiLintTest.cs create mode 100644 SlackNet/Interaction/ActionElement.cs rename SlackNet/{Actions => Interaction}/AttachmentUpdateResponse.cs (93%) create mode 100644 SlackNet/Interaction/Button.cs create mode 100644 SlackNet/Interaction/Dialog.cs create mode 100644 SlackNet/Interaction/DialogElement.cs create mode 100644 SlackNet/Interaction/DialogElementType.cs create mode 100644 SlackNet/Interaction/DialogError.cs create mode 100644 SlackNet/Interaction/DialogErrorResponse.cs create mode 100644 SlackNet/Interaction/DialogSubmission.cs rename SlackNet/{Actions => Interaction}/IActionHandler.cs (82%) create mode 100644 SlackNet/Interaction/IDialogSubmissionHandler.cs create mode 100644 SlackNet/Interaction/IOptionProvider.cs create mode 100644 SlackNet/Interaction/InputElementType.cs create mode 100644 SlackNet/Interaction/InteractionRequest.cs rename SlackNet/{Objects => Interaction}/InteractiveMessage.cs (54%) rename SlackNet/{Objects/Action.cs => Interaction/Menu.cs} (66%) rename SlackNet/{Actions => Interaction}/MessageResponse.cs (89%) rename SlackNet/{Actions => Interaction}/MessageUpdateResponse.cs (97%) rename SlackNet/{Actions => Interaction}/OptionsRequest.cs (93%) create mode 100644 SlackNet/Interaction/OptionsResponse.cs rename SlackNet/{WebApi => Interaction}/ResponseType.cs (70%) create mode 100644 SlackNet/Interaction/SelectElement.cs create mode 100644 SlackNet/Interaction/SelectOption.cs rename SlackNet/{Actions => Interaction}/SlackActions.cs (96%) rename SlackNet/{Actions => Interaction}/SlackOptions.cs (72%) create mode 100644 SlackNet/Interaction/TextArea.cs create mode 100644 SlackNet/Interaction/TextElement.cs create mode 100644 SlackNet/Interaction/TextElementBase.cs create mode 100644 SlackNet/Objects/Conversation.cs create mode 100644 SlackNet/Objects/Purpose.cs create mode 100644 SlackNet/WebApi/ConversationType.cs create mode 100644 SlackNet/WebApi/ConversationsApi.cs create mode 100644 SlackNet/WebApi/DialogApi.cs create mode 100644 SlackNet/WebApi/MigrationApi.cs create mode 100644 SlackNet/WebApi/Responses/ConversationHistoryResponse.cs create mode 100644 SlackNet/WebApi/Responses/ConversationJoinResponse.cs create mode 100644 SlackNet/WebApi/Responses/ConversationJoinResponseMetadata.cs create mode 100644 SlackNet/WebApi/Responses/ConversationListResponse.cs create mode 100644 SlackNet/WebApi/Responses/ConversationMembersResponse.cs create mode 100644 SlackNet/WebApi/Responses/ConversationMessagesResponse.cs create mode 100644 SlackNet/WebApi/Responses/ConversationOpenResponse.cs create mode 100644 SlackNet/WebApi/Responses/ConversationResponse.cs create mode 100644 SlackNet/WebApi/Responses/ErrorResponse.cs create mode 100644 SlackNet/WebApi/Responses/ErrorResponseMetadata.cs create mode 100644 SlackNet/WebApi/Responses/MigrationResponse.cs create mode 100644 SlackNet/WebApi/Responses/PermalinkResponse.cs create mode 100644 SlackNet/WebApi/Responses/PostEphemeralResponse.cs rename SlackNet/WebApi/Responses/{UserListResponseMetadata.cs => ResponseMetadata.cs} (68%) diff --git a/SlackNet.AspNetCore/AspNetCoreExtensions.cs b/SlackNet.AspNetCore/AspNetCoreExtensions.cs index cb6c5c8..34df98d 100644 --- a/SlackNet.AspNetCore/AspNetCoreExtensions.cs +++ b/SlackNet.AspNetCore/AspNetCoreExtensions.cs @@ -1,6 +1,8 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { @@ -14,6 +16,7 @@ public static IServiceCollection AddSlackNet(this IServiceCollection serviceColl serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.TryAddSingleton(); serviceCollection.AddTransient(c => new SlackApiClient(c.GetService(), c.GetService(), c.GetService(), configuration.ApiToken)); return serviceCollection; diff --git a/SlackNet.AspNetCore/NullDialogSubmissionHandler.cs b/SlackNet.AspNetCore/NullDialogSubmissionHandler.cs new file mode 100644 index 0000000..655b8f4 --- /dev/null +++ b/SlackNet.AspNetCore/NullDialogSubmissionHandler.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using SlackNet.Interaction; + +namespace SlackNet.AspNetCore +{ + public class NullDialogSubmissionHandler : IDialogSubmissionHandler + { + public Task> Handle(DialogSubmission dialog) => Task.FromResult(Enumerable.Empty()); + } +} \ No newline at end of file diff --git a/SlackNet.AspNetCore/ResolvedActionHandler.cs b/SlackNet.AspNetCore/ResolvedActionHandler.cs index d5f9fe8..926ad76 100644 --- a/SlackNet.AspNetCore/ResolvedActionHandler.cs +++ b/SlackNet.AspNetCore/ResolvedActionHandler.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { @@ -24,10 +25,13 @@ public ResolvedActionHandler(IServiceProvider serviceProvider, string actionName _serviceProvider = serviceProvider; } - public override Task Handle(InteractiveMessage message) + public override async Task Handle(InteractiveMessage message) { - var handler = _serviceProvider.GetRequiredService(); - return handler.Handle(message); + using (var scope = _serviceProvider.CreateScope()) + { + var handler = scope.ServiceProvider.GetRequiredService(); + return await handler.Handle(message).ConfigureAwait(false); + } } } } \ No newline at end of file diff --git a/SlackNet.AspNetCore/ResolvedDialogSubmissionHandler.cs b/SlackNet.AspNetCore/ResolvedDialogSubmissionHandler.cs new file mode 100644 index 0000000..cf6443e --- /dev/null +++ b/SlackNet.AspNetCore/ResolvedDialogSubmissionHandler.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using SlackNet.Interaction; + +namespace SlackNet.AspNetCore +{ + public class ResolvedDialogSubmissionHandler : IDialogSubmissionHandler + where T : IDialogSubmissionHandler + { + private readonly IServiceProvider _serviceProvider; + public ResolvedDialogSubmissionHandler(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + public async Task> Handle(DialogSubmission dialog) + { + using (var scope = _serviceProvider.CreateScope()) + { + var handler = scope.ServiceProvider.GetRequiredService(); + return await handler.Handle(dialog).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/SlackNet.AspNetCore/ResolvedOptionProvider.cs b/SlackNet.AspNetCore/ResolvedOptionProvider.cs index 59558af..5c91d52 100644 --- a/SlackNet.AspNetCore/ResolvedOptionProvider.cs +++ b/SlackNet.AspNetCore/ResolvedOptionProvider.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { diff --git a/SlackNet.AspNetCore/SlackActionsService.cs b/SlackNet.AspNetCore/SlackActionsService.cs index b79535d..1a509c7 100644 --- a/SlackNet.AspNetCore/SlackActionsService.cs +++ b/SlackNet.AspNetCore/SlackActionsService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { diff --git a/SlackNet.AspNetCore/SlackEventsMiddleware.cs b/SlackNet.AspNetCore/SlackEventsMiddleware.cs index 247770b..22ddbd6 100644 --- a/SlackNet.AspNetCore/SlackEventsMiddleware.cs +++ b/SlackNet.AspNetCore/SlackEventsMiddleware.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -5,6 +6,7 @@ using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using SlackNet.Events; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { @@ -15,14 +17,16 @@ class SlackEventsMiddleware private readonly ISlackEvents _slackEvents; private readonly ISlackActions _slackActions; private readonly ISlackOptions _slackOptions; + private readonly IDialogSubmissionHandler _dialogSubmissionHandler; private readonly SlackJsonSettings _jsonSettings; public SlackEventsMiddleware( - RequestDelegate next, - SlackEndpointConfiguration configuration, - ISlackEvents slackEvents, - ISlackActions slackActions, + RequestDelegate next, + SlackEndpointConfiguration configuration, + ISlackEvents slackEvents, + ISlackActions slackActions, ISlackOptions slackOptions, + IDialogSubmissionHandler dialogSubmissionHandler, SlackJsonSettings jsonSettings) { _next = next; @@ -30,6 +34,7 @@ public SlackEventsMiddleware( _slackEvents = slackEvents; _slackActions = slackActions; _slackOptions = slackOptions; + _dialogSubmissionHandler = dialogSubmissionHandler; _jsonSettings = jsonSettings; } @@ -60,8 +65,9 @@ private async Task HandleSlackEvent(HttpContext context) if (body is EventCallback eventCallback && IsValidToken(eventCallback.Token)) { + var response = context.Respond(HttpStatusCode.OK).ConfigureAwait(false); _slackEvents.Handle(eventCallback); - return await context.Respond(HttpStatusCode.OK).ConfigureAwait(false); + return await response; } return await context.Respond(HttpStatusCode.BadRequest, body: "Invalid token or unrecognized content").ConfigureAwait(false); @@ -72,22 +78,43 @@ private async Task HandleSlackAction(HttpContext context) if (context.Request.Method != "POST") return await context.Respond(HttpStatusCode.MethodNotAllowed).ConfigureAwait(false); - var interactiveMessage = await DeserializePayload(context).ConfigureAwait(false); + var interactionRequest = await DeserializePayload(context).ConfigureAwait(false); - if (interactiveMessage != null && IsValidToken(interactiveMessage.Token)) + if (interactionRequest != null && IsValidToken(interactionRequest.Token)) { - var response = await _slackActions.Handle(interactiveMessage).ConfigureAwait(false); - - var responseJson = response == null ? null - : interactiveMessage.IsAppUnfurl ? Serialize(new AttachmentUpdateResponse(response)) - : Serialize(new MessageUpdateResponse(response)); - - return await context.Respond(HttpStatusCode.OK, "application/json", responseJson).ConfigureAwait(false); + switch (interactionRequest) + { + case InteractiveMessage interactiveMessage: + return await HandleInteractiveMessage(context, interactiveMessage).ConfigureAwait(false); + case DialogSubmission dialogSubmission: + return await HandleDialogSubmission(context, dialogSubmission).ConfigureAwait(false); + } } return await context.Respond(HttpStatusCode.BadRequest, body: "Invalid token or unrecognized content").ConfigureAwait(false); } + private async Task HandleInteractiveMessage(HttpContext context, InteractiveMessage interactiveMessage) + { + var response = await _slackActions.Handle(interactiveMessage).ConfigureAwait(false); + + var responseJson = response == null ? null + : interactiveMessage.IsAppUnfurl ? Serialize(new AttachmentUpdateResponse(response)) + : Serialize(new MessageUpdateResponse(response)); + + return await context.Respond(HttpStatusCode.OK, "application/json", responseJson).ConfigureAwait(false); + } + + private async Task HandleDialogSubmission(HttpContext context, DialogSubmission dialog) + { + var errors = (await _dialogSubmissionHandler.Handle(dialog).ConfigureAwait(false))?.ToList() + ?? new List(); + + return errors.Any() + ? await context.Respond(HttpStatusCode.OK, "application/json", Serialize(new DialogErrorResponse { Errors = errors })).ConfigureAwait(false) + : await context.Respond(HttpStatusCode.OK).ConfigureAwait(false); + } + private async Task HandleSlackOptions(HttpContext context) { if (context.Request.Method != "POST") diff --git a/SlackNet.AspNetCore/SlackOptionsService.cs b/SlackNet.AspNetCore/SlackOptionsService.cs index 55fa5c9..d44b8b1 100644 --- a/SlackNet.AspNetCore/SlackOptionsService.cs +++ b/SlackNet.AspNetCore/SlackOptionsService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { diff --git a/SlackNet.AspNetCore/SlackServiceConfiguration.cs b/SlackNet.AspNetCore/SlackServiceConfiguration.cs index 1c52c72..91a5d67 100644 --- a/SlackNet.AspNetCore/SlackServiceConfiguration.cs +++ b/SlackNet.AspNetCore/SlackServiceConfiguration.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using SlackNet.Events; +using SlackNet.Interaction; namespace SlackNet.AspNetCore { @@ -43,6 +44,13 @@ public SlackServiceConfiguration RegisterOptionProvider(string action return this; } + public SlackServiceConfiguration RegisterDialogSubmissionHandler() + where THandler : IDialogSubmissionHandler + { + _serviceCollection.AddSingleton(c => new ResolvedDialogSubmissionHandler(c)); + return this; + } + public string ApiToken { get; private set; } } } \ No newline at end of file diff --git a/SlackNet.EventsExample/ColorSelector.cs b/SlackNet.EventsExample/ColorSelector.cs index c00df94..e8e9cee 100644 --- a/SlackNet.EventsExample/ColorSelector.cs +++ b/SlackNet.EventsExample/ColorSelector.cs @@ -2,6 +2,7 @@ using System.Drawing; using System.Linq; using System.Threading.Tasks; +using SlackNet.Interaction; namespace SlackNet.EventsExample { @@ -11,12 +12,13 @@ public class ColorSelector : IActionHandler, IOptionProvider public async Task Handle(InteractiveMessage message) { - message.OriginalAttachment.Color = message.Action.SelectedValue; - message.OriginalAttachment.Actions[0].SelectedOptions = new List