From 1e0c58247f6160f550fc759f879d7b58b792df09 Mon Sep 17 00:00:00 2001 From: Bobby Burden III Date: Tue, 10 May 2022 13:10:13 -0400 Subject: [PATCH 01/11] add in SlackClient and basics --- .../Extensions/IServiceCollectionExtension.cs | 2 + src/SlackDotNet/ISlackClient.cs | 37 ++++++ src/SlackDotNet/Models/Messages/ChatAction.cs | 22 ++++ .../Models/Messages/ChatAttachment.cs | 20 +++ .../Models/Messages/ChatMessage.cs | 124 ++++++++++++++++++ src/SlackDotNet/Models/Profile.cs | 56 ++++++++ src/SlackDotNet/Models/Response.cs | 13 ++ .../Models/Responses/UserListResponse.cs | 14 ++ src/SlackDotNet/Models/User.cs | 64 +++++++++ src/SlackDotNet/SlackClient.cs | 84 ++++++++++++ src/SlackDotNet/SlackOptions.cs | 2 + 11 files changed, 438 insertions(+) create mode 100644 src/SlackDotNet/ISlackClient.cs create mode 100644 src/SlackDotNet/Models/Messages/ChatAction.cs create mode 100644 src/SlackDotNet/Models/Messages/ChatAttachment.cs create mode 100644 src/SlackDotNet/Models/Messages/ChatMessage.cs create mode 100644 src/SlackDotNet/Models/Profile.cs create mode 100644 src/SlackDotNet/Models/Response.cs create mode 100644 src/SlackDotNet/Models/Responses/UserListResponse.cs create mode 100644 src/SlackDotNet/Models/User.cs create mode 100644 src/SlackDotNet/SlackClient.cs diff --git a/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs b/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs index 5deb362..d274bf7 100644 --- a/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs +++ b/src/SlackDotNet/Extensions/IServiceCollectionExtension.cs @@ -25,6 +25,8 @@ public static IServiceCollection AddSlackDotNet(this IServiceCollection services services.TryAddSingleton(); services.AddSingleton(); + + services.AddSingleton(); return services; } } diff --git a/src/SlackDotNet/ISlackClient.cs b/src/SlackDotNet/ISlackClient.cs new file mode 100644 index 0000000..7f32cee --- /dev/null +++ b/src/SlackDotNet/ISlackClient.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using SlackDotNet.Models; +using SlackDotNet.Models.Messages; + +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); + + /// + /// Get's the user's ID from their username + /// + /// + /// + Task GetUserId(string username); + } +} diff --git a/src/SlackDotNet/Models/Messages/ChatAction.cs b/src/SlackDotNet/Models/Messages/ChatAction.cs new file mode 100644 index 0000000..d2c8aef --- /dev/null +++ b/src/SlackDotNet/Models/Messages/ChatAction.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace SlackDotNet.Models.Messages +{ + public class ChatAction + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("text")] + public string Text { get; set; } + + [JsonProperty("style")] + public string Style { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + } +} diff --git a/src/SlackDotNet/Models/Messages/ChatAttachment.cs b/src/SlackDotNet/Models/Messages/ChatAttachment.cs new file mode 100644 index 0000000..c7bb1cf --- /dev/null +++ b/src/SlackDotNet/Models/Messages/ChatAttachment.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SlackDotNet.Models.Messages +{ + public class ChatAttachment + { + [JsonProperty("text")] + public string Text { get; set; } + + [JsonProperty("callback_id")] + public string CallbackId { get; set; } + + [JsonProperty("image_url")] + public string ImageUrl { get; set; } + + [JsonProperty("actions")] + public List Actions { get; set; } + } +} diff --git a/src/SlackDotNet/Models/Messages/ChatMessage.cs b/src/SlackDotNet/Models/Messages/ChatMessage.cs new file mode 100644 index 0000000..a8e242c --- /dev/null +++ b/src/SlackDotNet/Models/Messages/ChatMessage.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SlackDotNet.Models.Messages +{ + public class ChatMessage + { + [JsonProperty("token")] + public string Token { get; set; } + + /// + /// Channel, private group, or IM channel to send message to. Can be an encoded ID, or a name. + /// + /// + [JsonProperty("channel")] + public string Channel { get; set; } + + /// + /// The usage of the text field changes depending on whether you're using blocks. + /// If you are using blocks, this is used as a fallback string to display in notifications. + /// If you aren't, this is the main body text of the message. + /// It can be formatted as plain text, or with markdwn. + /// + /// + [JsonProperty("text")] + public string Text { get; set; } + + /// + /// Pass true to post the message as the authed user, instead of as a bot. Defaults to false. + /// + /// + [JsonProperty("as_user")] + public bool? AsUser { get; set; } = null; + + /// + /// Emoji to use as the icon for this message. Overrides IconUrl. + /// Must be used in conjunction with AsUser set to `false`, otherwise ignored. + /// + /// + [JsonProperty("icon_emoji")] + public string IconEmoji { get; set; } + + /// + /// URL to an image to use as the icon for this message. + /// Must be used in conjunction with AsUser set to `false`, otherwise ignored. + /// + /// + [JsonProperty("icon_url")] + public Uri IconUrl { get; set; } = null; + + /// + /// Find and link channel names and usernames. + /// + /// + [JsonProperty("link_names")] + public bool? LinkNames { get; set; } = null; + + /// + /// Disable Slack markup parsing by setting to `false`. Enabled by default. + /// + /// + [JsonProperty("mrkdwn")] + public bool? FormatMarkdown { get; set; } = null; + + /// + /// Change how messages are treated. + /// + /// + [JsonProperty("parse")] + public string Parse { get; set; } + + /// + /// Used in conjunction with ThreadTimestamp and indicates whether reply should be made visible to everyone in + /// the channel or conversation. + /// + /// + [JsonProperty("reply_broadcast")] + public bool? ReplyBroadcast { get; set; } = null; + + /// + /// Provide another message's Timestamp value to make this message a reply. + /// Avoid using a reply's `Timestamp` value; use its parent instead. + /// + /// + [JsonProperty("thread_ts")] + public string ThreadTimestamp { get; set; } + + /// + /// Pass `true` to enable unfurling of primarily text-based content. + /// + /// + [JsonProperty("unfurl_links")] + public bool? UnfurlLinks { get; set; } = null; + + /// + /// Pass `false` to disable unfurling of media content. + /// + /// + [JsonProperty("unfurl_media")] + public bool? UnfurlMedia { get; set; } = null; + + /// + /// Set your bot's user name. Must be used in conjunction with AsUser set to false, otherwise ignored. + /// + /// + [JsonProperty("username")] + public string Username { get; set; } + + /// + /// Message attachments + /// + /// + [JsonProperty("attachments")] + public List Attachments { get; set; } + + /// + /// User ID to receive the message (used for ephemeral messages) + /// + /// + [JsonProperty("user")] + public string User { get; set; } + } +} diff --git a/src/SlackDotNet/Models/Profile.cs b/src/SlackDotNet/Models/Profile.cs new file mode 100644 index 0000000..64c5e76 --- /dev/null +++ b/src/SlackDotNet/Models/Profile.cs @@ -0,0 +1,56 @@ +using System; +using Newtonsoft.Json; + +namespace SlackDotNet.Models +{ + public class Profile + { + [JsonProperty("avatar_hash")] + public string AvatarHash { get; set; } + + [JsonProperty("status_text")] + public string StatusText { get; set; } + + [JsonProperty("status_emoji")] + public string StatusEmoji { get; set; } + + [JsonProperty("real_name")] + public string RealName { get; set; } + + [JsonProperty("display_name")] + public string DisplayName { get; set; } + + [JsonProperty("real_name_normalized")] + public string RealNameNormalized { get; set; } + + [JsonProperty("display_name_normalized")] + public string DisplayNameNormalized { get; set; } + + [JsonProperty("email")] + public string Email { get; set; } + + [JsonProperty("image_original")] + public Uri ImageOriginal { get; set; } + + [JsonProperty("image_24")] + public Uri Image24 { get; set; } + + [JsonProperty("image_32")] + public Uri Image32 { get; set; } + + [JsonProperty("image_48")] + public Uri Image48 { get; set; } + + [JsonProperty("image_72")] + public Uri Image72 { get; set; } + + [JsonProperty("image_192")] + public Uri Image192 { get; set; } + + [JsonProperty("image_512")] + public Uri Image512 { get; set; } + + [JsonProperty("team")] + public string Team { get; set; } + } +} diff --git a/src/SlackDotNet/Models/Response.cs b/src/SlackDotNet/Models/Response.cs new file mode 100644 index 0000000..b41ca76 --- /dev/null +++ b/src/SlackDotNet/Models/Response.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace SlackDotNet.Models +{ + public class Response + { + [JsonProperty("ok")] + public bool Ok { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + } +} diff --git a/src/SlackDotNet/Models/Responses/UserListResponse.cs b/src/SlackDotNet/Models/Responses/UserListResponse.cs new file mode 100644 index 0000000..beb34bd --- /dev/null +++ b/src/SlackDotNet/Models/Responses/UserListResponse.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SlackDotNet.Models.Responses +{ + public class UsersListResponse + { + [JsonProperty("ok")] + public bool Ok { get; set; } + + [JsonProperty("members")] + public List Members { get; set; } + } +} diff --git a/src/SlackDotNet/Models/User.cs b/src/SlackDotNet/Models/User.cs new file mode 100644 index 0000000..7ea25ac --- /dev/null +++ b/src/SlackDotNet/Models/User.cs @@ -0,0 +1,64 @@ +using Newtonsoft.Json; + +namespace SlackDotNet.Models +{ + public class User + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("team_id")] + public string TeamId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("deleted")] + public bool Deleted { get; set; } + + [JsonProperty("color")] + public string Color { get; set; } + + [JsonProperty("real_name")] + public string RealName { get; set; } + + [JsonProperty("tz")] + public string Tz { get; set; } + + [JsonProperty("tz_label")] + public string TzLabel { get; set; } + + [JsonProperty("tz_offset")] + public long TzOffset { get; set; } + + [JsonProperty("profile")] + public Profile Profile { get; set; } + + [JsonProperty("is_admin")] + public bool IsAdmin { get; set; } + + [JsonProperty("is_owner")] + public bool IsOwner { get; set; } + + [JsonProperty("is_primary_owner")] + public bool IsPrimaryOwner { get; set; } + + [JsonProperty("is_restricted")] + public bool IsRestricted { get; set; } + + [JsonProperty("is_ultra_restricted")] + public bool IsUltraRestricted { get; set; } + + [JsonProperty("is_bot")] + public bool IsBot { get; set; } + + [JsonProperty("updated")] + public long Updated { get; set; } + + [JsonProperty("is_app_user")] + public bool IsAppUser { get; set; } + + [JsonProperty("has_2fa")] + public bool Has2Fa { get; set; } + } +} diff --git a/src/SlackDotNet/SlackClient.cs b/src/SlackDotNet/SlackClient.cs new file mode 100644 index 0000000..93bca52 --- /dev/null +++ b/src/SlackDotNet/SlackClient.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Flurl; +using Flurl.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SlackDotNet.Models; +using SlackDotNet.Models.Messages; +using SlackDotNet.Models.Responses; + +namespace SlackDotNet +{ + public class SlackClient : ISlackClient + { + private SlackOptions Options { get; set; } + + private ILogger Logger { get; set; } + + private List Users { get; set; } = null; + + public SlackClient(IOptions options, ILogger logger) + { + Options = options.Value; + Logger = logger; + } + + public async Task GetUser(string userId) + { + var response = await "https://slack.com/api/users.info" + .SetQueryParam("token", Options.OauthToken) + .SetQueryParam("user", userId) + .GetJsonAsync(); + + return response.User; + } + + public async Task PostMessage(ChatMessage message, bool ephemeral = false) + { + var endpoint = ephemeral ? "postEphemeral" : "postMessage"; + var response = await $"https://slack.com/api/chat.{endpoint}" + .WithHeader("Authorization", "Bearer " + Options.OauthToken) + .PostJsonAsync(message); + + return true; + } + + public async Task DeleteResponse(string responseUrl) + { + var response = await responseUrl + .WithHeader("Authorization", "Bearer " + Options.OauthToken) + .PostJsonAsync(new + { + delete_original = true + }); + + return true; + } + + public async Task GetUserId(string username) + { + await RefreshUserList(); + return Users.Find(u => u.Profile.DisplayName == username).Id; + } + + /// + /// Refreshes the user list. + /// + /// This is rate limited to 50/minute so should not be called often. + /// + /// + private async Task RefreshUserList() + { + if (Users == null) + { + // TODO: This should be cached in some way and invalidated automatically without having to be manually called. + var response = await "https://slack.com/api/users.list" + .WithHeader("Authorization", "Bearer " + Options.OauthToken) + .GetJsonAsync(); + + Users = response.Members; + } + } + } +} diff --git a/src/SlackDotNet/SlackOptions.cs b/src/SlackDotNet/SlackOptions.cs index 759fce6..229b4cd 100644 --- a/src/SlackDotNet/SlackOptions.cs +++ b/src/SlackDotNet/SlackOptions.cs @@ -3,5 +3,7 @@ namespace SlackDotNet public class SlackOptions { public string AppLevelToken { get; set; } + + public string OauthToken { get; set; } } } From cc8bdc7d7ccadb43a737bd3176ba688446799987 Mon Sep 17 00:00:00 2001 From: Bobby Burden III Date: Tue, 10 May 2022 16:31:40 -0400 Subject: [PATCH 02/11] add method to grab emoji lists --- src/SlackDotNet/ISlackClient.cs | 7 +++++++ src/SlackDotNet/Models/Responses/Emojis.cs | 15 +++++++++++++++ src/SlackDotNet/SlackClient.cs | 9 +++++++++ 3 files changed, 31 insertions(+) create mode 100644 src/SlackDotNet/Models/Responses/Emojis.cs diff --git a/src/SlackDotNet/ISlackClient.cs b/src/SlackDotNet/ISlackClient.cs index 7f32cee..caf679f 100644 --- a/src/SlackDotNet/ISlackClient.cs +++ b/src/SlackDotNet/ISlackClient.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using SlackDotNet.Models; using SlackDotNet.Models.Messages; +using SlackDotNet.Models.Responses; namespace SlackDotNet { @@ -33,5 +34,11 @@ public interface ISlackClient /// /// Task GetUserId(string username); + + /// + /// Get's all the emojis in the workspace as a dictionary of name => URL + /// + /// + Task GetEmojisAndUrls(); } } diff --git a/src/SlackDotNet/Models/Responses/Emojis.cs b/src/SlackDotNet/Models/Responses/Emojis.cs new file mode 100644 index 0000000..25eb0a1 --- /dev/null +++ b/src/SlackDotNet/Models/Responses/Emojis.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SlackDotNet.Models.Responses +{ + public class Emojis + { + [JsonProperty("ok")] + public bool Ok { get; set; } + + [JsonProperty("emoji")] + public Dictionary EmojiDictionary { get; set; } + } +} diff --git a/src/SlackDotNet/SlackClient.cs b/src/SlackDotNet/SlackClient.cs index 93bca52..b22a780 100644 --- a/src/SlackDotNet/SlackClient.cs +++ b/src/SlackDotNet/SlackClient.cs @@ -62,6 +62,15 @@ public async Task GetUserId(string username) return Users.Find(u => u.Profile.DisplayName == username).Id; } + public async Task GetEmojisAndUrls() + { + var response = await $"https://slack.com/api/emoji.list" + .WithHeader("Authorization", "Bearer " + Options.OauthToken) + .GetJsonAsync(); + + return response; + } + /// /// Refreshes the user list. /// From 5b6bad6fc65f42d9e88d49757cb0aa8227db67f2 Mon Sep 17 00:00:00 2001 From: Bobby Burden III Date: Tue, 10 May 2022 19:50:31 -0400 Subject: [PATCH 03/11] add support for Views and View Publishing --- src/SlackDotNet/ISlackClient.cs | 7 + src/SlackDotNet/Models/Block.cs | 151 ++++++++++++++++++ src/SlackDotNet/Models/BlockElement.cs | 104 ++++++++++++ src/SlackDotNet/Models/ConfirmDialog.cs | 22 +++ .../Models/DispatchConfiguration.cs | 11 ++ src/SlackDotNet/Models/Filter.cs | 17 ++ src/SlackDotNet/Models/Option.cs | 19 +++ src/SlackDotNet/Models/OptionGroup.cs | 14 ++ src/SlackDotNet/Models/PublishViewRequest.cs | 13 ++ .../Models/Responses/PublishViewResponse.cs | 19 +++ src/SlackDotNet/Models/TextBlock.cs | 35 ++++ src/SlackDotNet/Models/View.cs | 82 ++++++++++ src/SlackDotNet/SlackClient.cs | 10 ++ 13 files changed, 504 insertions(+) create mode 100644 src/SlackDotNet/Models/Block.cs create mode 100644 src/SlackDotNet/Models/BlockElement.cs create mode 100644 src/SlackDotNet/Models/ConfirmDialog.cs create mode 100644 src/SlackDotNet/Models/DispatchConfiguration.cs create mode 100644 src/SlackDotNet/Models/Filter.cs create mode 100644 src/SlackDotNet/Models/Option.cs create mode 100644 src/SlackDotNet/Models/OptionGroup.cs create mode 100644 src/SlackDotNet/Models/PublishViewRequest.cs create mode 100644 src/SlackDotNet/Models/Responses/PublishViewResponse.cs create mode 100644 src/SlackDotNet/Models/TextBlock.cs create mode 100644 src/SlackDotNet/Models/View.cs diff --git a/src/SlackDotNet/ISlackClient.cs b/src/SlackDotNet/ISlackClient.cs index caf679f..ec03048 100644 --- a/src/SlackDotNet/ISlackClient.cs +++ b/src/SlackDotNet/ISlackClient.cs @@ -40,5 +40,12 @@ public interface ISlackClient /// /// Task GetEmojisAndUrls(); + + /// + /// Publish a view. + /// + /// + /// + Task PublishView(PublishViewRequest request); } } diff --git a/src/SlackDotNet/Models/Block.cs b/src/SlackDotNet/Models/Block.cs new file mode 100644 index 0000000..8f45c25 --- /dev/null +++ b/src/SlackDotNet/Models/Block.cs @@ -0,0 +1,151 @@ +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` + /// + 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..d10753d --- /dev/null +++ b/src/SlackDotNet/Models/BlockElement.cs @@ -0,0 +1,104 @@ +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