diff --git a/src/SlackDotNet/Enums/InteractiveMessageType.cs b/src/SlackDotNet/Enums/InteractiveMessageType.cs new file mode 100644 index 0000000..7adef05 --- /dev/null +++ b/src/SlackDotNet/Enums/InteractiveMessageType.cs @@ -0,0 +1,19 @@ +using SlackDotNet.Models.Messages.Payloads.EventsAPI; +using SlackDotNet.Models.Messages.Payloads.Interactive; + +namespace SlackDotNet.Enums +{ + public enum InteractiveMessageType + { + block_actions, + + shortcut, + + message_actions, + + [Type(typeof(ViewSubmission))] + view_submission, + + view_closed + } +} diff --git a/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs b/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs index 5deb362..91c3b5c 100644 --- a/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs +++ b/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs @@ -23,8 +23,11 @@ public static IServiceCollection AddSlackDotNet(this IServiceCollection services services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.AddSingleton(); + + services.AddSingleton(); return services; } } diff --git a/src/SlackDotNet/Handlers/IInteractiveHandler.cs b/src/SlackDotNet/Handlers/IInteractiveHandler.cs new file mode 100644 index 0000000..9914983 --- /dev/null +++ b/src/SlackDotNet/Handlers/IInteractiveHandler.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using SlackDotNet.Models; +using SlackDotNet.Models.Messages; + +namespace SlackDotNet.Handlers +{ + /// + /// Handler for WebSocket "interactive" messages. + /// + public interface IInteractiveHandler + { + Task Handle(InteractiveMessage interactiveMessage); + } +} diff --git a/src/SlackDotNet/Handlers/InteractiveHandler.cs b/src/SlackDotNet/Handlers/InteractiveHandler.cs new file mode 100644 index 0000000..8ef5719 --- /dev/null +++ b/src/SlackDotNet/Handlers/InteractiveHandler.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SlackDotNet.Models; +using SlackDotNet.Models.Messages; + +namespace SlackDotNet.Handlers +{ + public class InteractiveHandler : IInteractiveHandler + { + public ILogger Logger { get; } + + public InteractiveHandler(ILogger logger) + { + Logger = logger; + } + + public async Task Handle(InteractiveMessage interactiveMessage) + { + await Task.Run(() => + Logger.LogWarning($"Received unhandled `{interactiveMessage.Type}` message from WebSocket.") + ); + + return new SlackWebSocketMessageResponse(interactiveMessage); + } + } +} diff --git a/src/SlackDotNet/ISlackClient.cs b/src/SlackDotNet/ISlackClient.cs new file mode 100644 index 0000000..e55154e --- /dev/null +++ b/src/SlackDotNet/ISlackClient.cs @@ -0,0 +1,65 @@ +using System.Threading.Tasks; +using SlackDotNet.Models; +using SlackDotNet.Models.Messages; +using SlackDotNet.Models.Responses; + +namespace SlackDotNet +{ + public interface ISlackClient + { + /// + /// Get's a slack user's information + /// + /// + /// + Task GetUser(string userId); + + /// + /// Posts a message to a channel + /// + /// + /// + Task PostMessage(ChatMessage message, bool ephemeral = false); + + /// + /// Deletes a message in response to an interactive command + /// + /// + /// + Task DeleteResponse(string responseUrl); + + /// + /// Gets the user's ID from their display name + /// + /// + /// + Task GetUserId(string username); + + /// + /// Gets the user's display name from their username + /// + /// + /// + Task GetUserName(string userId); + + /// + /// Get's all the emojis in the workspace as a dictionary of name => URL + /// + /// + Task GetEmojisAndUrls(); + + /// + /// Publish a view. + /// + /// + /// + Task PublishView(PublishViewRequest request); + + /// + /// Open a view. + /// + /// + /// + Task OpenView(OpenViewRequest request); + } +} diff --git a/src/SlackDotNet/Models/Block.cs b/src/SlackDotNet/Models/Block.cs new file mode 100644 index 0000000..cd33f17 --- /dev/null +++ b/src/SlackDotNet/Models/Block.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SlackDotNet.Models +{ + public class Block + { + /// + /// The type of the block: + /// `actions`, `context`, `divider`, `file`, `header`, `image`, `input`, `section` + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// An array of interactive elemnts. + /// Max of 25. + /// + /// Valid for: `actions`, `context` + /// + [JsonProperty("elements")] + public List Elements { get; set; } + + /// + /// A string acting as a unique identifier for a block. + /// If not specified, a block_id will be generated. + /// You can use this block_id when you receive an interaction payload to identify the source of the action. + /// Maximum length for this field is 255 characters. + /// block_id should be unique for each message and each iteration of a message. + /// If a message is updated, use a new block_id. + /// + /// Valid for: `actions`, `context`, `divider`, `file`, `header`, `image`, `input`, `section` + /// + [JsonProperty("block_id")] + public string BlockId { get; set; } + + /// + /// The external unique ID for this file. + /// + /// Valid for: `file` + /// + [JsonProperty("external_id")] + public string ExternalId { get; set; } + + /// + /// At the moment, source should always be `remote` for a remote file. + /// + /// Valid for: `file` + /// + [JsonProperty("source")] + public string Source { get; set; } + + /// + /// The text for the block, in the form of a `plain_text` text object. + /// Maximum length for the text in this field is 150 characters. + /// + /// Valid for: `header`, `section` + /// + [JsonProperty("text")] + public TextBlock Text { get; set; } + + /// + /// The URL of the image to be displayed. Maximum length for this field is 3000 characters. + /// + /// Valid for: `image` + /// + [JsonProperty("image_url")] + public string ImageUrl { get; set; } + + /// + /// A plain-text summary of the image. This should not contain any markup. + /// Maximum length for this field is 2000 characters + /// + /// Valid for: `image` + /// + [JsonProperty("alt_text")] + public string AltText { get; set; } + + /// + /// An optional title for the image in the form of a text object that can only be of type: plain_text. + /// Maximum length for the text in this field is 2000 characters. + /// + /// Valid for: `image` + /// + [JsonProperty("title")] + public TextBlock Title { get; set; } + + /// + /// A label that appears above an input element in the form of a text object that must have type of plain_text. + /// Maximum length for the text in this field is 2000 characters. + /// + /// Valid for: `input` + /// + [JsonProperty("label")] + public TextBlock Label { get; set; } + + /// + /// A plain-text input element, a checkbox element, a radio button element, a select menu element, + /// a multi-select menu element, or a datepicker. + /// + /// Valid for: `input` + /// + [JsonProperty("element")] + public BlockElement Element { get; set; } + + /// + /// A boolean that indicates whether or not the use of elements in this block should dispatch a block_actions payload. + /// Defaults to false. + /// + /// Valid for: `input` + /// + [JsonProperty("dispatch_action")] + public bool? DispatchAction { get; set; } + + /// + /// An optional hint that appears below an input element in a lighter grey. + /// It must be a text object with a type of plain_text. + /// Maximum length for the text in this field is 2000 characters. + /// + /// Valid for: `input` + /// + [JsonProperty("hint")] + public TextBlock Hint { get; set; } + + /// + /// A boolean that indicates whether the input element may be empty when a user submits the modal. + /// Defaults to false. + /// + /// Valid for: `input` + /// + [JsonProperty("optional")] + public bool? Optional { get; set; } + + /// + /// Required if no text is provided. A List of text objects. + /// Any text objects included with fields will be rendered in a compact format that allows for 2 columns of side-by-side text. + /// Maximum number of items is 10. Maximum length for the text in each item is 2000 characters. + /// + /// Valid for: `section` + /// + [JsonProperty("fields")] + public List Fields { get; set; } + + /// + /// One of the available element objects. + /// + /// Valid for: `section` + /// + [JsonProperty("accessory")] + public BlockElement Accessory { get; set; } + } +} diff --git a/src/SlackDotNet/Models/BlockElement.cs b/src/SlackDotNet/Models/BlockElement.cs new file mode 100644 index 0000000..c9a6adc --- /dev/null +++ b/src/SlackDotNet/Models/BlockElement.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SlackDotNet.Models +{ + public class BlockElement + { + /// + /// The type of the element. + /// + /// Possible values: + /// `button`, `checkboxes`, `datepicker`, `image`, `multi_static_select`, `multi_external_select`, + /// `multi_users_select`, `multi_conversations_list`, `multi_channels_select`, `overflow`, + /// `plain_text_input`, `radio_buttons`, `static_select`, `external_select`, `users_select`, + /// `conversations_select`, `channels_select`, `timepicker` + /// + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("text")] + public TextBlock Text { get; set; } + + [JsonProperty("action_id")] + public string ActionId { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("style")] + public string Style { get; set; } + + [JsonProperty("confirm")] + public ConfirmDialog Confirm { get; set; } + + [JsonProperty("accessibility_label")] + public string AccessibilityLabel { get; set; } + + [JsonProperty("options")] + public List