From 30fcf8727b94b09d501a32cab67a635a6279ad03 Mon Sep 17 00:00:00 2001 From: Matias Roldan Date: Tue, 18 Jun 2019 14:03:06 -0300 Subject: [PATCH] Slack Adapter polish (#106) * Remove unused formatter, remove duplicate response code set, remove unnecessary casts * Add Slack login correctly done * Fix id assignation in SendActivitiesAsync method (#107) * Add some minor stylecop corrections LoginWithSlack method temporarily waited to test * Fix minor stylecop warnings, fix response sending, remove unused inclusion, start partial implementation of previously unimplemented ChannelData message assignment, add property to NewSlackMessage class * Remove ChatPostEphemeralMessageResult and ChatPostMessageResult classes * Fix message.ThreadTS assignment and cast. Complete message ChannelData assignent. Some stylecop corrections * Add stylecop corrections, not taking into account parameter documentations * Change the return type of ActivityToSlack * Remove references to Middleware * Remove Middleware files * Fix thread_ts assignation * Add PR changes * Remove commented out lines * Add PR changes: commented out code removed, some dynamic casts removed * Remove all dynamics cast to slackEvent * Add more PR changes --- .../Controllers/BotController.cs | 15 +- .../SimpleSlackAdapterOptions.cs | 24 +- Botkit/Microsoft.Bot.Sample.Slack/Startup.cs | 14 +- .../AdapterError.cs | 7 +- .../ChatPostEphemeralMessageResult.cs | 15 - .../ChatPostMessageResult.cs | 16 - .../DialogData.cs | 19 +- .../DialogElement.cs | 16 +- .../ISlackAdapterOptions.cs | 4 +- .../NewSlackMessage.cs | 6 + .../SlackAdapter.cs | 292 ++++++++---------- .../SlackBotWorker.cs | 151 ++++----- .../SlackDialog.cs | 102 +++--- .../SlackEventMiddleware.cs | 8 +- .../SlackMessageTypeMiddleware.cs | 27 +- .../Microsoft.BotKit.Adapters.Slack/Ware.cs | 18 -- Botkit/Microsoft.BotKit/Core/Botkit.cs | 4 +- Botkit/Microsoft.BotKit/Core/IBotkitPlugin.cs | 3 +- Botkit/Microsoft.BotKit/Core/IMiddleware.cs | 9 - Botkit/Microsoft.BotKit/Core/Middleware.cs | 13 - 20 files changed, 339 insertions(+), 424 deletions(-) delete mode 100644 Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostEphemeralMessageResult.cs delete mode 100644 Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostMessageResult.cs delete mode 100644 Botkit/Microsoft.BotKit.Adapters.Slack/Ware.cs delete mode 100644 Botkit/Microsoft.BotKit/Core/IMiddleware.cs delete mode 100644 Botkit/Microsoft.BotKit/Core/Middleware.cs diff --git a/Botkit/Microsoft.Bot.Sample.Slack/Controllers/BotController.cs b/Botkit/Microsoft.Bot.Sample.Slack/Controllers/BotController.cs index cc18861a72..13580c854c 100644 --- a/Botkit/Microsoft.Bot.Sample.Slack/Controllers/BotController.cs +++ b/Botkit/Microsoft.Bot.Sample.Slack/Controllers/BotController.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // // Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.3.0 @@ -18,24 +18,23 @@ namespace Microsoft.Bot.Sample.Slack.Controllers [ApiController] public class BotController : ControllerBase { - private readonly SlackAdapter Adapter; - private readonly IBot Bot; + private readonly SlackAdapter adapter; + private readonly IBot bot; public BotController(SlackAdapter adapter, IBot bot) { - Adapter = adapter; - Bot = bot; + this.adapter = adapter; + this.bot = bot; - Adapter.Use(new SlackEventMiddleware()); + this.adapter.Use(new SlackEventMiddleware()); } - [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); + await this.adapter.ProcessAsync(this.Request, this.Response, this.bot); } } } diff --git a/Botkit/Microsoft.Bot.Sample.Slack/SimpleSlackAdapterOptions.cs b/Botkit/Microsoft.Bot.Sample.Slack/SimpleSlackAdapterOptions.cs index 047d420ae0..f6b6004a61 100644 --- a/Botkit/Microsoft.Bot.Sample.Slack/SimpleSlackAdapterOptions.cs +++ b/Botkit/Microsoft.Bot.Sample.Slack/SimpleSlackAdapterOptions.cs @@ -1,4 +1,4 @@ -using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Connector.Authentication; using Microsoft.BotKit.Adapters.Slack; using System.Threading.Tasks; @@ -12,16 +12,22 @@ public SimpleSlackAdapterOptions() public SimpleSlackAdapterOptions(string verificationToken, string botToken) { - VerificationToken = verificationToken; - BotToken = botToken; + this.VerificationToken = verificationToken; + this.BotToken = botToken; } - public string VerificationToken { get; set; } - public string ClientSigningSecret { get; set; } - public string BotToken { get; set; } - public string ClientId { get; set; } - public string ClientSecret { get; set; } - public string[] Scopes { get; set; } + public string VerificationToken { get; set; } + + public string ClientSigningSecret { get; set; } + + public string BotToken { get; set; } + + public string ClientId { get; set; } + + public string ClientSecret { get; set; } + + public string[] Scopes { get; set; } + public string RedirectUri { get; set; } public Task GetBotUserByTeam(string TeamId) diff --git a/Botkit/Microsoft.Bot.Sample.Slack/Startup.cs b/Botkit/Microsoft.Bot.Sample.Slack/Startup.cs index 891e15cb8d..efbb4f5f10 100644 --- a/Botkit/Microsoft.Bot.Sample.Slack/Startup.cs +++ b/Botkit/Microsoft.Bot.Sample.Slack/Startup.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // // Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.3.0 @@ -9,11 +9,10 @@ using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Bot.Connector.Authentication; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - using Microsoft.Bot.Sample.Slack.Bots; using Microsoft.BotKit.Adapters.Slack; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Bot.Sample.Slack { @@ -21,7 +20,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -36,7 +35,8 @@ public void ConfigureServices(IServiceCollection services) // Create the options for the SlackAdapter services.AddSingleton(); - //services.AddSingleton(); + + // services.AddSingleton(); // Create the Bot Framework Adapter. services.AddSingleton(); @@ -60,7 +60,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseDefaultFiles(); app.UseStaticFiles(); - //app.UseHttpsRedirection(); + // app.UseHttpsRedirection(); app.UseMvc(); } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/AdapterError.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/AdapterError.cs index 82cb594063..0e2d8ef482 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/AdapterError.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/AdapterError.cs @@ -1,16 +1,15 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. namespace Microsoft.BotKit.Adapters.Slack { public class AdapterError { - string Name; - string Error; + public string Name; + public string Error; public AdapterError() { - } } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostEphemeralMessageResult.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostEphemeralMessageResult.cs deleted file mode 100644 index e16fd9e64e..0000000000 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostEphemeralMessageResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. -// Licensed under the MIT License. -using SlackAPI; -using SlackAPI.RPCMessages; - -namespace Microsoft.BotKit.Adapters.Slack -{ - /// - /// Abstract class to cast result of web api calls - /// - public class ChatPostEphemeralMessageResult : PostEphemeralResponse - { - public string Id { get; } - } -} diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostMessageResult.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostMessageResult.cs deleted file mode 100644 index 5ede13ccf7..0000000000 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/ChatPostMessageResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. -// Licensed under the MIT License. -using SlackAPI; - -namespace Microsoft.BotKit.Adapters.Slack -{ - /// - /// Abstract class to cast result of web api calls - /// - public class ChatPostMessageResult : PostMessageResponse - { - public string Id { get; } - public string Channel { get; } - public string Ts { get; } - } -} diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/DialogData.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/DialogData.cs index de2481a306..12559d9930 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/DialogData.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/DialogData.cs @@ -1,4 +1,4 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. using System.Collections.Generic; @@ -8,18 +8,23 @@ namespace Microsoft.BotKit.Adapters.Slack public class DialogData { public string Title { get; set; } + public string CallbackId { get; set; } + public string SubmitLabel { get; set; } - public List Elements { get; set; } - public string State { get; set; } + + public List Elements { get; set; } + + public string State { get; set; } + public bool NotifyOnCancel { get; set; } public DialogData(string title, string callback, string submit, List elements) { - Title = title; - CallbackId = callback; - SubmitLabel = submit; - Elements = elements; + this.Title = title; + this.CallbackId = callback; + this.SubmitLabel = submit; + this.Elements = elements; } } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/DialogElement.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/DialogElement.cs index f1703a5599..df3b0572ea 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/DialogElement.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/DialogElement.cs @@ -1,4 +1,4 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. using System.Collections.Generic; @@ -6,13 +6,19 @@ namespace Microsoft.BotKit.Adapters.Slack { public class DialogElement - { + { public string Label { get; set; } + public string Name { get; set; } - public string Value { get; set; } - public Dictionary OptionList { get; set; } - public ISlackAdapterOptions Options { get; set; } + + public string Value { get; set; } + + public Dictionary OptionList { get; set; } + + public ISlackAdapterOptions Options { get; set; } + public string Type { get; set; } + public string Subtype { get; set; } } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/ISlackAdapterOptions.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/ISlackAdapterOptions.cs index e0b2240444..3e4961873a 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/ISlackAdapterOptions.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/ISlackAdapterOptions.cs @@ -1,4 +1,4 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. using System.Threading.Tasks; @@ -45,7 +45,7 @@ public interface ISlackAdapterOptions /// /// A method that receives a Slack team id and returns the bot token associated with that team. Required for multi-team apps. /// - /// Team ID + /// Team ID. /// Task GetTokenForTeam(string TeamId); diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/NewSlackMessage.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/NewSlackMessage.cs index 4a3e2bf92a..705f03c7ec 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/NewSlackMessage.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/NewSlackMessage.cs @@ -1,3 +1,4 @@ +using System; using SlackAPI.WebSocketMessages; namespace Microsoft.BotKit.Adapters.Slack @@ -5,8 +6,13 @@ namespace Microsoft.BotKit.Adapters.Slack public class NewSlackMessage : NewMessage { public string Ephemeral { get; set; } + public bool AsUser { get; set; } + public string IconUrl { get; set; } + public string IconEmoji { get; set; } + + public string ThreadTS { get; set; } } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackAdapter.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackAdapter.cs index a452f98b74..bbf288f5fd 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackAdapter.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackAdapter.cs @@ -1,43 +1,43 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. - -using Microsoft.Rest.Serialization; -using Microsoft.AspNetCore.Http; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Schema; -using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Dynamic; -using System.Net.Http; -using System.Net.Http.Formatting; +using System.IO; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -using System.Net; using System.Text; using SlackAPI; -using SlackAPI.WebSocketMessages; -using System.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Schema; +using Newtonsoft.Json; namespace Microsoft.BotKit.Adapters.Slack { public class SlackAdapter : BotAdapter, IBotFrameworkHttpAdapter { - private readonly ISlackAdapterOptions options; - private readonly SlackTaskClient Slack; - private readonly string Identity; - private readonly string SlackOAuthURL = "https://slack.com/oauth/authorize?client_id="; - public Dictionary Middlewares; + /// + /// Name used by Botkit plugin loader. + /// public readonly string NAME = "Slack Adapter"; - public SlackBotWorker botkitWorker; + + /// + /// A customized BotWorker object that exposes additional utility methods. + /// + public SlackBotWorker BotkitWorker; + private readonly ISlackAdapterOptions options; + private readonly SlackTaskClient slack; + private readonly string slackOAuthURL = "https://slack.com/oauth/authorize?client_id="; + private string identity; /// /// Create a Slack adapter. /// - /// An object containing API credentials, a webhook verification token and other options - public SlackAdapter(ISlackAdapterOptions options) : base() + /// An object containing API credentials, a webhook verification token and other options. + public SlackAdapter(ISlackAdapterOptions options) + : base() { this.options = options; @@ -57,43 +57,25 @@ public SlackAdapter(ISlackAdapterOptions options) : base() throw new Exception(warning + Environment.NewLine + "Required: include a verificationToken or clientSigningSecret to verify incoming Events API webhooks"); } + this.slack = new SlackTaskClient(this.options.BotToken); + this.LoginWithSlack().Wait(); + } + + private async Task LoginWithSlack() + { if (this.options.BotToken != null) { - Slack = new SlackTaskClient(this.options.BotToken); - Identity = Slack.MySelf?.id; + AuthTestResponse response = await this.slack.TestAuthAsync(); + this.identity = response.user_id; } else { - if (string.IsNullOrEmpty(options.ClientId) || string.IsNullOrEmpty(options.ClientSecret) || - string.IsNullOrEmpty(options.RedirectUri) || options.Scopes.Length > 0) + if (string.IsNullOrEmpty(this.options.ClientId) || string.IsNullOrEmpty(this.options.ClientSecret) || + string.IsNullOrEmpty(this.options.RedirectUri) || this.options.Scopes.Length > 0) { throw new Exception("Missing Slack API credentials! Provide clientId, clientSecret, scopes and redirectUri as part of the SlackAdapter options."); } } - - Ware ware = new Ware(); - ware.Name = "spawn"; - ware.Middlewares = new List>(); - ware.Middlewares.Add - ( - async (Bot, Next) => - { - try - { - // make the Slack API available to all bot instances. - (Bot as dynamic).api = await GetAPIAsync(Bot.config.Activity); - } - catch (Exception ex) - { - throw ex; - } - - Next(); - } - ); - - Middlewares = new Dictionary(); - Middlewares.Add(ware.Name, ware); } /// @@ -104,9 +86,9 @@ public SlackAdapter(ISlackAdapterOptions options) : base() /// public async Task GetAPIAsync(Activity activity) { - if (Slack != null) + if (this.slack != null) { - return Slack; + return this.slack; } if (activity.Conversation.Properties["team"] == null) @@ -114,7 +96,7 @@ public async Task GetAPIAsync(Activity activity) throw new Exception($"Unable to create API based on activity:{activity}"); } - var token = await options.GetTokenForTeam(activity.Conversation.Properties["team"].ToString()); + var token = await this.options.GetTokenForTeam(activity.Conversation.Properties["team"].ToString()); return string.IsNullOrEmpty(token) ? new SlackTaskClient(token) : throw new Exception("Missing credentials for team."); } @@ -127,9 +109,9 @@ public async Task GetAPIAsync(Activity activity) /// public async Task GetBotUserByTeamAsync(Activity activity) { - if (!string.IsNullOrEmpty(Identity)) + if (!string.IsNullOrEmpty(this.identity)) { - return Identity; + return this.identity; } if (activity.Conversation.Properties["team"] == null) @@ -137,7 +119,8 @@ public async Task GetBotUserByTeamAsync(Activity activity) return null; } - var userID = await options.GetBotUserByTeam(activity.Conversation.Properties["team"].ToString()); + // multi-team mode + var userID = await this.options.GetBotUserByTeam(activity.Conversation.Properties["team"].ToString()); return !string.IsNullOrEmpty(userID) ? userID : throw new Exception("Missing credentials for team."); } @@ -147,8 +130,8 @@ public async Task GetBotUserByTeamAsync(Activity activity) /// A url pointing to the first step in Slack's oauth flow. public string GetInstallLink() { - return (!string.IsNullOrEmpty(options.ClientId) && options.Scopes.Length > 0) - ? SlackOAuthURL + options.ClientId + "&scope=" + string.Join(",", options.Scopes) + return (!string.IsNullOrEmpty(this.options.ClientId) && this.options.Scopes.Length > 0) + ? this.slackOAuthURL + this.options.ClientId + "&scope=" + string.Join(",", this.options.Scopes) : throw new Exception("getInstallLink() cannot be called without clientId and scopes in adapter options."); } @@ -160,16 +143,16 @@ public string GetInstallLink() public async Task ValidateOauthCodeAsync(string code) { var helpers = new SlackClientHelpers(); - var results = await helpers.GetAccessTokenAsync(options.ClientId, options.ClientSecret, options.RedirectUri, code); + var results = await helpers.GetAccessTokenAsync(this.options.ClientId, this.options.ClientSecret, this.options.RedirectUri, code); return results.ok ? results : throw new Exception(results.error); } /// /// Formats a BotBuilder activity into an outgoing Slack message. /// - /// A BotBuilder Activity object + /// A BotBuilder Activity object. /// A Slack message object with {text, attachments, channel, thread ts} as well as any fields found in activity.channelData - public object ActivityToSlack(Activity activity) + public NewSlackMessage ActivityToSlack(Activity activity) { NewSlackMessage message = new NewSlackMessage(); @@ -177,6 +160,7 @@ public object ActivityToSlack(Activity activity) { message.ts = activity.Timestamp.Value.DateTime; } + message.text = activity.Text; foreach (Microsoft.Bot.Schema.Attachment att in activity.Attachments) @@ -188,14 +172,34 @@ public object ActivityToSlack(Activity activity) }; message.attachments.Add(newAttachment); } - + message.channel = activity.Conversation.Id; - //message.thread_ts = JsonConvert.DeserializeObject(activity.Conversation.Properties["thread_ts"].ToString()); + + if (activity.Conversation.Properties["thread_ts"].ToString() != string.Empty) + { + message.ThreadTS = activity.Conversation.Properties["thread_ts"].ToString(); + } // if channelData is specified, overwrite any fields in message object if (activity.ChannelData != null) { - //message = activity.ChannelData; + try + { + // Try a straight up cast + message = activity.ChannelData as NewSlackMessage; + } + catch (InvalidCastException ex) + { + foreach (var property in message.GetType().GetFields()) + { + string name = property.Name; + var value = (activity.ChannelData as dynamic)[name]; + if (value != null) + { + message.GetType().GetField(name).SetValue(message, value); + } + } + } } // should this message be sent as an ephemeral message @@ -215,7 +219,7 @@ public object ActivityToSlack(Activity activity) /// /// Standard BotBuilder adapter method to send a message from the bot to the messaging API. /// - /// A TurnContext representing the current incoming message and environment. + /// A TurnContext representing the current incoming message and environment. /// An array of outgoing activities to be sent back to the messaging API. public override async Task SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken) { @@ -225,7 +229,7 @@ public override async Task SendActivitiesAsync(ITurnContext Activity activity = activities[i]; if (activity.Type == ActivityTypes.Message) { - NewSlackMessage message = ActivityToSlack(activity) as NewSlackMessage; + NewSlackMessage message = this.ActivityToSlack(activity); try { @@ -234,35 +238,27 @@ public override async Task SendActivitiesAsync(ITurnContext if (message.Ephemeral != null) { - result = await slack.PostEphemeralMessageAsync(message.channel, message.text, message.user) as ChatPostEphemeralMessageResult; + result = await slack.PostEphemeralMessageAsync(message.channel, message.text, message.user); } else { - result = await slack.PostMessageAsync(message.channel, message.text) as ChatPostMessageResult; + result = await slack.PostMessageAsync(message.channel, message.text); } if (result.ok) { - ResourceResponse response = new ResourceResponse() //{ id = result.Id, activityId = result.Ts, conversation = new { Id = result.Channel } }; + ResourceResponse response = new ResourceResponse() // { id = result.ts, activityId = result.ts, conversation = new { Id = result.Channel } }; { - Id = (result as dynamic).Id + Id = (result as dynamic).ts, }; responses.Add(response as ResourceResponse); } - else - { - throw new Exception($"Error sending activity to API:{result}"); - } } catch (Exception ex) { throw ex; } } - else - { - throw new Exception("Unknown message type encountered in sendActivities:${activity.Type}"); - } } return responses.ToArray(); @@ -271,14 +267,14 @@ public override async Task SendActivitiesAsync(ITurnContext /// /// Standard BotBuilder adapter method to update a previous message with new content. /// - /// A TurnContext representing the current incoming message and environment. + /// A TurnContext representing the current incoming message and environment. /// The updated activity in the form `{id: , ...}` public override async Task UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken) { if (activity.Id != null && activity.Conversation != null) { - NewSlackMessage message = ActivityToSlack(activity) as NewSlackMessage; - SlackTaskClient slack = await GetAPIAsync(activity); + NewSlackMessage message = this.ActivityToSlack(activity); + SlackTaskClient slack = await this.GetAPIAsync(activity); var results = await slack.UpdateAsync(activity.Timestamp.ToString(), activity.ChannelId, message.text); if (!results.ok) { @@ -292,20 +288,20 @@ public override async Task UpdateActivityAsync(ITurnContext tu return new ResourceResponse() { - Id = activity.Id + Id = activity.Id, }; } /// /// Standard BotBuilder adapter method to delete a previous message. /// - /// A TurnContext representing the current incoming message and environment. + /// A TurnContext representing the current incoming message and environment. /// An object in the form `{activityId: , conversation: { id: }}` public override async Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken) { if (reference.ActivityId != null && reference.Conversation != null) { - SlackTaskClient slack = await GetAPIAsync(turnContext.Activity); + SlackTaskClient slack = await this.GetAPIAsync(turnContext.Activity); var results = await slack.DeleteMessageAsync(reference.ChannelId, turnContext.Activity.Timestamp.Value.DateTime); } else @@ -325,7 +321,7 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot using (TurnContext context = new TurnContext(this, request)) { - await RunPipelineAsync(context, logic, default(CancellationToken)); + await this.RunPipelineAsync(context, logic, default(CancellationToken)); } } @@ -334,7 +330,7 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot /// /// A request object from Restify or Express /// A response object from Restify or Express - /// A bot logic function in the form `async(context) => { ... }` + /// A bot with logic function in the form `async(context) => { ... }`. public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot bot, CancellationToken cancellationToken = default(CancellationToken)) { try @@ -344,19 +340,7 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot StreamReader sr = new StreamReader(request.Body); dynamic slackEvent = JsonConvert.DeserializeObject(sr.ReadToEnd()); - MediaTypeFormatter[] formatters = new MediaTypeFormatter[] - { - new JsonMediaTypeFormatter - { - SupportedMediaTypes = - { - new System.Net.Http.Headers.MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }, - new System.Net.Http.Headers.MediaTypeHeaderValue("text/json") { CharSet = "utf-8" }, - }, - }, - }; - - if ((slackEvent as dynamic).type == "url_verification") + if (slackEvent.type == "url_verification") { response.StatusCode = 200; response.ContentType = "text/plain"; @@ -364,13 +348,13 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot await response.WriteAsync(text); } - if (VerifySignature(request, response)) + if (this.VerifySignature(request, response)) { if (slackEvent.payload != null) { // handle interactive_message callbacks and block_actions slackEvent = JsonConvert.ToString(slackEvent.payload); - if (options.VerificationToken != null && slackEvent.token != options.VerificationToken) + if (this.options.VerificationToken != null && slackEvent.token != this.options.VerificationToken) { response.StatusCode = 403; } @@ -378,54 +362,50 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot { Activity activity = new Activity() { - Timestamp = new DateTime(), + Timestamp = default(DateTime), ChannelId = "slack", Conversation = new ConversationAccount() { - Id = slackEvent.Channel.Id + Id = slackEvent.Channel.Id, }, From = new ChannelAccount() { - Id = (slackEvent.BotId != null) ? slackEvent.BotId : slackEvent.User.Id + Id = (slackEvent.BotId != null) ? slackEvent.BotId : slackEvent.User.Id, }, Recipient = new ChannelAccount() { - Id = null + Id = null, }, ChannelData = slackEvent, - Type = ActivityTypes.Event + Type = ActivityTypes.Event, }; // Extra fields that do not belong to activity - activity.Conversation.Properties["thread_ts"] = slackEvent.ThreadTs; + activity.Conversation.Properties["thread_ts"] = slackEvent["event"].thread_ts; activity.Conversation.Properties["team"] = slackEvent.Team.Id; // this complains because of extra fields in conversation - activity.Recipient.Id = await GetBotUserByTeamAsync(activity); + activity.Recipient.Id = await this.GetBotUserByTeamAsync(activity); // create a conversation reference using (var context = new TurnContext(this, activity)) { context.TurnState.Add("httpStatus", "200"); - await RunPipelineAsync(context, bot.OnTurnAsync, default(CancellationToken)); + await this.RunPipelineAsync(context, bot.OnTurnAsync, default(CancellationToken)); // send http response back response.StatusCode = Convert.ToInt32(context.TurnState.Get("httpStatus")); - if (context.TurnState.Get("httpBody") != null) - { - response.StatusCode = 200; - response.ContentType = "text/plain"; - string text = context.TurnState.Get("httpBody"); - await response.WriteAsync(text); - } + response.ContentType = "text/plain"; + string text = (context.TurnState.Get("httpBody") != null) ? context.TurnState.Get("httpBody").ToString() : string.Empty; + await response.WriteAsync(text); } } } - else if ((slackEvent as dynamic).type == "event_callback") + else if (slackEvent.type == "event_callback") { // this is an event api post - if (options.VerificationToken != null && (slackEvent as dynamic).token != options.VerificationToken) + if (this.options.VerificationToken != null && slackEvent.token != this.options.VerificationToken) { response.StatusCode = 403; response.ContentType = "text/plain"; @@ -434,34 +414,33 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot } else { - Activity activity = new Activity() { - Id = ((dynamic)slackEvent)["event"].ts, - Timestamp = new DateTime(), + Id = slackEvent["event"].ts, + Timestamp = default(DateTime), ChannelId = "slack", Conversation = new ConversationAccount() { - Id = (slackEvent as dynamic)["event"].channel //id + Id = slackEvent["event"].channel, }, From = new ChannelAccount() { - Id = (((dynamic)slackEvent)["event"].bot_id != null)? ((dynamic)slackEvent)["event"].bot_id : ((dynamic)slackEvent)["event"].user + Id = (slackEvent["event"].bot_id != null) ? slackEvent["event"].bot_id : slackEvent["event"].user, }, Recipient = new ChannelAccount() { - Id = null + Id = null, }, - ChannelData = ((dynamic)slackEvent)["event"], + ChannelData = slackEvent["event"], Text = null, - Type = ActivityTypes.Event + Type = ActivityTypes.Event, }; // Extra field that doesn't belong to activity - activity.Conversation.Properties["thread_ts"] = slackEvent.thread_ts; + activity.Conversation.Properties["thread_ts"] = slackEvent["event"].thread_ts; // this complains because of extra fields in conversation - activity.Recipient.Id = await GetBotUserByTeamAsync(activity); + activity.Recipient.Id = await this.GetBotUserByTeamAsync(activity); // Normalize the location of the team id (activity.ChannelData as dynamic).team = slackEvent.team_id; @@ -470,10 +449,10 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot activity.Conversation.Properties["team"] = (activity.ChannelData as dynamic).team; // If this is conclusively a message originating from a user, we'll mark it as such - if (((dynamic)slackEvent)["event"].type == "message" && ((dynamic)slackEvent)["event"].subtype == null) + if (slackEvent["event"].type == "message" && slackEvent["event"].subtype == null) { activity.Type = ActivityTypes.Message; - activity.Text = ((dynamic)slackEvent)["event"].text; + activity.Text = slackEvent["event"].text; } // create a conversation reference @@ -481,23 +460,19 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot { context.TurnState.Add("httpStatus", "200"); - await RunPipelineAsync(context, bot.OnTurnAsync, default(CancellationToken)); + await this.RunPipelineAsync(context, bot.OnTurnAsync, default(CancellationToken)); // send http response back response.StatusCode = Convert.ToInt32(context.TurnState.Get("httpStatus")); - if (context.TurnState.Get("httpBody") != null) - { - response.StatusCode = 200; - response.ContentType = "text/plain"; - string text = context.TurnState.Get("httpBody").ToString(); - await response.WriteAsync(text); - } + response.ContentType = "text/plain"; + string text = (context.TurnState.Get("httpBody") != null) ? context.TurnState.Get("httpBody").ToString() : string.Empty; + await response.WriteAsync(text); } } } else if (slackEvent.Command != null) { - if (options.VerificationToken != null && slackEvent.Token != options.VerificationToken) + if (this.options.VerificationToken != null && slackEvent.Token != this.options.VerificationToken) { response.StatusCode = 403; } @@ -507,26 +482,26 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot Activity activity = new Activity() { Id = slackEvent.TriggerId, - Timestamp = new DateTime(), + Timestamp = default(DateTime), ChannelId = "slack", Conversation = new ConversationAccount() { - Id = slackEvent.ChannelId + Id = slackEvent.ChannelId, }, From = new ChannelAccount() { - Id = slackEvent.UserId + Id = slackEvent.UserId, }, Recipient = new ChannelAccount() { - Id = null + Id = null, }, ChannelData = slackEvent, Text = slackEvent.text, - Type = ActivityTypes.Event + Type = ActivityTypes.Event, }; - activity.Recipient.Id = await GetBotUserByTeamAsync(activity); + activity.Recipient.Id = await this.GetBotUserByTeamAsync(activity); // Normalize the location of the team id (activity.ChannelData as dynamic).team = slackEvent.TeamId; @@ -541,24 +516,13 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot { context.TurnState.Add("httpStatus", "200"); - await RunPipelineAsync(context, bot.OnTurnAsync, default(CancellationToken)); + await this.RunPipelineAsync(context, bot.OnTurnAsync, default(CancellationToken)); // send http response back response.StatusCode = Convert.ToInt32(context.TurnState.Get("httpStatus")); - if (context.TurnState.Get("httpBody") != null) - { - response.StatusCode = 200; - response.ContentType = "text/plain"; - string text = context.TurnState.Get("httpBody").ToString(); - await response.WriteAsync(text); - } - else - { - response.StatusCode = 200; - response.ContentType = "text/plain"; - string text = string.Empty; - await response.WriteAsync(text); - } + response.ContentType = "text/plain"; + string text = (context.TurnState.Get("httpBody") != null) ? context.TurnState.Get("httpBody").ToString() : string.Empty; + await response.WriteAsync(text); } } } @@ -576,23 +540,23 @@ public async Task ContinueConversationAsync(ConversationReference reference, Bot /// If signature is valid, returns true. Otherwise, sends a 401 error status via http response and then returns false. private bool VerifySignature(HttpRequest request, HttpResponse response) { - if (options.ClientSigningSecret != null && request.Body != null) + if (this.options.ClientSigningSecret != null && request.Body != null) { var timestamp = request.Headers; var body = request.Body; object[] signature = { "v0", timestamp.ToString(), body.ToString() }; - string baseString = String.Join(":", signature); + string baseString = string.Join(":", signature); - HMACSHA256 myHMAC = new HMACSHA256(Encoding.UTF8.GetBytes(options.ClientSigningSecret)); + HMACSHA256 myHMAC = new HMACSHA256(Encoding.UTF8.GetBytes(this.options.ClientSigningSecret)); var hash = "v0=" + myHMAC.ComputeHash(Encoding.UTF8.GetBytes(baseString)); var retrievedSignature = request.Headers["X-Slack-Signature"]; // Compare the hash of the computed signature with the retrieved signature with a secure hmac compare function - bool signatureIsValid = String.Equals(hash, retrievedSignature); + bool signatureIsValid = string.Equals(hash, retrievedSignature); // replace direct compare with the hmac result if (!signatureIsValid) diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackBotWorker.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackBotWorker.cs index f1de8bc8d3..941b78c036 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackBotWorker.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackBotWorker.cs @@ -1,35 +1,37 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.BotKit.Core; +using System; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using SlackAPI; +using SlackAPI.RPCMessages; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; -using System; +using Microsoft.BotKit.Core; using Newtonsoft.Json; -using Microsoft.Bot.Builder.Dialogs; -using System.Threading; -using System.Net.Http; -using Microsoft.Bot.Builder; using Dialog = SlackAPI.Dialog; -using SlackAPI.RPCMessages; namespace Microsoft.BotKit.Adapters.Slack { public class SlackBotWorker : BotWorker { - //public SlackClient api; - public SlackTaskClient apiClient; + // public SlackClient api; + public SlackTaskClient ApiClient; /// + /// Initializes a new instance of the class. /// Reserved for use internally by Botkit's `controller.spawn()`, this class is used to create a BotWorker instance that can send messages, replies, and make other API calls. /// When used with the SlackAdapter's multi-tenancy mode, it is possible to spawn a bot instance by passing in the Slack workspace ID of a team that has installed the app. /// Use this in concert with [startPrivateConversation()](#startPrivateConversation) and [changeContext()](core.md#changecontext) to start conversations /// or send proactive alerts to users on a schedule or in response to external events. /// - /// The Botkit controller object responsible for spawning this bot worker + /// The Botkit controller object responsible for spawning this bot worker. /// Normally, a DialogContext object. Can also be the id of a team. - public SlackBotWorker(Botkit botkit, BotWorkerConfiguration config) : base(botkit, config) + public SlackBotWorker(Botkit botkit, BotWorkerConfiguration config) + : base(botkit, config) { // TODO make overload to allow a teamid (string) to be passed in } @@ -38,23 +40,23 @@ public SlackBotWorker(Botkit botkit, BotWorkerConfiguration config) : base(botki /// Switch a bot's context to a 1:1 private message channel with a specific user. /// After calling this method, messages sent with `bot.say` and any dialogs started with `bot.beginDialog` will occur in this new context. /// - /// A Slack user id, like one found in `message.user` or in a `<@mention>` + /// A Slack user id, like one found in `message.user` or in a `.<@mention>` public async Task StartPrivateConversation(string userId) { - var channel = await apiClient.JoinDirectMessageChannelAsync(userId); + var channel = await this.ApiClient.JoinDirectMessageChannelAsync(userId); if (channel.ok) { var convRef = new ConversationReference(); - var activity = config.Activity; + var activity = this.config.Activity; convRef.Conversation.Id = channel.channel.id; - (convRef.Conversation as dynamic).Team = (activity.Conversation as dynamic).Team; //TO-DO: replace 'as dynamic' + (convRef.Conversation as dynamic).Team = (activity.Conversation as dynamic).Team; // TO-DO: replace 'as dynamic' convRef.User.Id = userId; convRef.User.Name = null; convRef.ChannelId = "slack"; - return ChangeContextAsync(convRef); + return this.ChangeContextAsync(convRef); } else { @@ -66,154 +68,153 @@ public async Task StartPrivateConversation(string userId) /// Switch a bot's context into a different channel. /// After calling this method, messages sent with `bot.say` and any dialogs started with `bot.beginDialog` will occur in this new context. /// - /// A Slack channel id, like one found in `message.channel` - /// A Slack user id, like one found in `message.user` or in a `<@mention>` + /// A Slack channel id, like one found in `message.channel`. + /// A Slack user id, like one found in `message.user` or in a `.<@mention>` public async Task StartConversationInChannel(string channelId, string userId) { var convRef = new ConversationReference(); - var activity = config.Activity; + var activity = this.config.Activity; convRef.Conversation.Id = channelId; - (convRef.Conversation as dynamic).Team = (activity.Conversation as dynamic).Team; //TO-DO: replace 'as dynamic' + (convRef.Conversation as dynamic).Team = (activity.Conversation as dynamic).Team; // TO-DO: replace 'as dynamic' convRef.User.Id = userId; convRef.User.Name = null; convRef.ChannelId = "slack"; - return await ChangeContextAsync(convRef); + return await this.ChangeContextAsync(convRef); } /// /// Switch a bot's context into a specific sub-thread within a channel. /// After calling this method, messages sent with `bot.say` and any dialogs started with `bot.beginDialog` will occur in this new context. /// - /// A Slack channel id, like one found in `message.channel` - /// A Slack user id, like one found in `message.user` or in a `<@mention>` + /// A Slack channel id, like one found in `message.channel`. + /// A Slack user id, like one found in `message.user` or in a `.<@mention>` /// A thread_ts value found in the `message.thread_ts` or `message.ts` field. public async Task StartConversationInThread(string channelId, string userId, string threadTs) { var convRef = new ConversationReference(); - var activity = config.Activity; + var activity = this.config.Activity; convRef.Conversation.Id = channelId; - (convRef.Conversation as dynamic).Team = (activity.Conversation as dynamic).Team; //TO-DO: replace 'as dynamic' - (convRef.Conversation as dynamic).ThreadTS = threadTs; //TO-DO: replace 'as dynamic' + (convRef.Conversation as dynamic).Team = (activity.Conversation as dynamic).Team; // TO-DO: replace 'as dynamic' + (convRef.Conversation as dynamic).ThreadTS = threadTs; // TO-DO: replace 'as dynamic' convRef.User.Id = userId; convRef.User.Name = null; convRef.ChannelId = "slack"; - return await ChangeContextAsync(convRef); + return await this.ChangeContextAsync(convRef); } /// /// Like bot.reply, but as a threaded response to the incoming message rather than a new message in the main channel. /// - /// An incoming message object - /// An outgoing message object (or part of one or just reply text) + /// An incoming message object. + /// An outgoing message object (or part of one or just reply text). public async Task ReplyInThread(BotkitSlackMessage source, BotkitSlackMessage resp) { // make sure the threadTs setting is set // this will be included in the conversation reference - var threadTs = (source.IncomingMessage.ChannelData as dynamic).ThreadTs; //TO-DO: replace 'as dynamic' + var threadTs = (source.IncomingMessage.ChannelData as dynamic).ThreadTs; // TO-DO: replace 'as dynamic' if (threadTs is null) { - threadTs = (source.IncomingMessage.ChannelData as dynamic).Ts; //TO-DO: replace 'as dynamic' + threadTs = (source.IncomingMessage.ChannelData as dynamic).Ts; // TO-DO: replace 'as dynamic' } - return await ReplyAsync(source, resp); + return await this.ReplyAsync(source, resp); } /// /// Like bot.reply, but sent as an "ephemeral" message meaning only the recipient can see it. - /// Uses chat.postEphemeral + /// Uses chat.postEphemeral. /// - /// An incoming message object - /// An outgoing message object (or part of one or just reply text) + /// An incoming message object. + /// An outgoing message object (or part of one or just reply text). public async Task ReplyEphemeral(BotkitSlackMessage source, BotkitSlackMessage resp) { // make sure resp is in an object format. - var activity = EnsureMessageFormat(resp); + var activity = this.EnsureMessageFormat(resp); // make sure ephemeral is set // fields set in channelData will end up in the final message to slack - (activity.ChannelData as dynamic).ephemeral = true; //TO-DO: replace 'as dynamic' + (activity.ChannelData as dynamic).ephemeral = true; // TO-DO: replace 'as dynamic' resp.IncomingMessage = activity; - return await ReplyAsync(source, resp); + return await this.ReplyAsync(source, resp); } /// /// Like bot.reply, but used to send an immediate public reply to a /slash command. /// The message in `resp` will be displayed to everyone in the channel. /// - /// An incoming message object of type `slash_command` - /// An outgoing message object (or part of one or just reply text) + /// An incoming message object of type `slash_command`. + /// An outgoing message object (or part of one or just reply text). public async Task ReplyPublic(BotkitSlackMessage source, BotkitSlackMessage resp) { - var activity = EnsureMessageFormat(resp); + var activity = this.EnsureMessageFormat(resp); - (activity.ChannelData as dynamic).ResponseType = "in_channel"; //TO-DO: replace 'as dynamic' + (activity.ChannelData as dynamic).ResponseType = "in_channel"; // TO-DO: replace 'as dynamic' resp.IncomingMessage = activity; - return await ReplyInteractive(source, resp); + return await this.ReplyInteractive(source, resp); } /// /// Like bot.reply, but used to send an immediate private reply to a /slash command. /// The message in `resp` will be displayed only to the person who executed the slash command. /// - /// An incoming message object of type `slash_command` - /// An outgoing message object (or part of one or just reply text) + /// An incoming message object of type `slash_command`. + /// An outgoing message object (or part of one or just reply text). public async Task ReplyPrivate(BotkitSlackMessage source, BotkitSlackMessage resp) { - var activity = EnsureMessageFormat(resp); + var activity = this.EnsureMessageFormat(resp); - (activity.ChannelData as dynamic).ResponseType = "ephemeral"; //TO-DO: replace 'as dynamic' - (activity.ChannelData as dynamic).To = source.User; //TO-DO: replace 'as dynamic' + (activity.ChannelData as dynamic).ResponseType = "ephemeral"; // TO-DO: replace 'as dynamic' + (activity.ChannelData as dynamic).To = source.User; // TO-DO: replace 'as dynamic' resp.IncomingMessage = activity; - return await ReplyInteractive(source, resp); + return await this.ReplyInteractive(source, resp); } /// /// Like bot.reply, but used to respond to an `interactive_message` event and cause the original message to be replaced with a new one. - /// An incoming message object of type `interactive_message` + /// An incoming message object of type `interactive_message`. /// - /// An incoming message object of type `interactive_message` - /// A new or modified message that will replace the original one + /// An incoming message object of type `interactive_message`. + /// A new or modified message that will replace the original one. public async Task ReplyInteractive(BotkitSlackMessage source, BotkitSlackMessage resp) { - if((source.IncomingMessage.ChannelData as dynamic).ResponseUrl is null) //TO-DO: replace 'as dynamic' + if ((source.IncomingMessage.ChannelData as dynamic).ResponseUrl is null) // TO-DO: replace 'as dynamic' { throw new Exception("No responseUrl found in incoming message"); } - var activity = EnsureMessageFormat(resp); + var activity = this.EnsureMessageFormat(resp); activity.Conversation.Id = source.Channel; - (activity.ChannelData as dynamic).To = source.User; //TO-DO: replace 'as dynamic' + (activity.ChannelData as dynamic).To = source.User; // TO-DO: replace 'as dynamic' - if ((source.IncomingMessage.ChannelData as dynamic).ThreadTs != null) //TO-DO: replace 'as dynamic' + if ((source.IncomingMessage.ChannelData as dynamic).ThreadTs != null) // TO-DO: replace 'as dynamic' { - (activity.Conversation as dynamic).ThreadTs = (source.IncomingMessage.ChannelData as dynamic).ThreadTs; //TO-DO: replace 'as dynamic' + (activity.Conversation as dynamic).ThreadTs = (source.IncomingMessage.ChannelData as dynamic).ThreadTs; // TO-DO: replace 'as dynamic' } - var adapter = (SlackAdapter)Controller.Adapter; + var adapter = (SlackAdapter)this.Controller.Adapter; - activity = (Activity)adapter.ActivityToSlack(activity); + var slackMessage = adapter.ActivityToSlack(activity); var requestOptions = new { - uri = (source.IncomingMessage.ChannelData as dynamic).ResponseUrl, //TO-DO: replace 'as dynamic' + uri = (source.IncomingMessage.ChannelData as dynamic).ResponseUrl, // TO-DO: replace 'as dynamic' method = "POST", - json = activity, + json = slackMessage, }; - return await RequestUrl(requestOptions); - + return await this.RequestUrl(requestOptions); } public async Task RequestUrl(dynamic options) @@ -231,12 +232,12 @@ public async Task RequestUrl(dynamic options) /// Return 1 or more error to a `dialog_submission` event that will be displayed as form validation errors. /// Each error must be mapped to the name of an input in the dialog. /// - /// 1 or more objects in form {name: string, error: string} + /// 1 or more objects in form {name: string, error: string}. public void DialogError(AdapterError error) { if (error != null) { - HTTPBody(JsonConvert.ToString(error)); + this.HTTPBody(JsonConvert.ToString(error)); } } @@ -244,21 +245,21 @@ public void DialogError(AdapterError[] errors) { if (errors != null) { - HTTPBody(JsonConvert.ToString(errors)); + this.HTTPBody(JsonConvert.ToString(errors)); } } /// /// Reply to a button click with a request to open a dialog. /// - /// An incoming `interactive_callback` event containing a `trigger_id` field + /// An incoming `interactive_callback` event containing a `trigger_id` field. /// A dialog, as created using [SlackDialog](#SlackDialog) or [authored to this spec](https://api.slack.com/dialogs). public async Task ReplyWithDialog(object source, object dialogObj) { var triggerId = (source as dynamic).TriggerId; - var dialog = (Dialog) dialogObj; + var dialog = (Dialog)dialogObj; - return await apiClient.DialogOpenAsync(triggerId, dialog); + return await this.ApiClient.DialogOpenAsync(triggerId, dialog); } /// @@ -267,8 +268,8 @@ public async Task ReplyWithDialog(object source, object dial /// An object in the form `{id: , conversation: { id: }, text: , card: }` public async Task UpdateMessage(IBotkitMessage update) { - SlackAdapter adapter = (SlackAdapter)Controller.Adapter; - TurnContext context = config.TurnContext; + SlackAdapter adapter = (SlackAdapter)this.Controller.Adapter; + TurnContext context = this.config.TurnContext; CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; return await adapter.UpdateActivityAsync(context, update.IncomingMessage, token); @@ -277,11 +278,11 @@ public async Task UpdateMessage(IBotkitMessage update) /// /// Delete an existing message. /// - /// An object in the form of `{id: , conversation: { id: }}` + /// An object in the form of {id:, conversation: { id: }} public async Task DeleteMessage(IBotkitMessage update) { - var adapter = (BotFrameworkAdapter)Controller.Adapter; - var context = config.TurnContext; + var adapter = (BotFrameworkAdapter)this.Controller.Adapter; + var context = this.config.TurnContext; CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; await adapter.DeleteActivityAsync(context, update.Reference, token); diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackDialog.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackDialog.cs index ed5a658c83..99ec796014 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackDialog.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackDialog.cs @@ -1,103 +1,103 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Newtonsoft.Json; using System.Collections; using System.Collections.Generic; +using Newtonsoft.Json; namespace Microsoft.BotKit.Adapters.Slack { public class SlackDialog { - private DialogData data; private readonly List elements; + private DialogData data; /// - /// Create a Slack Dialog object for use with [replyWithDialog()](#replyWithDialog) + /// Create a Slack Dialog object for use with [replyWithDialog()](#replyWithDialog). /// /// The title of the dialog. /// Callback id of dialog. /// Label for the submit button. - /// An array of dialog elements. + /// An array of dialog elements. public SlackDialog(string title, string callbackId, string submitLabel, List elements) { - data = new DialogData(title, callbackId, submitLabel, elements); + this.data = new DialogData(title, callbackId, submitLabel, elements); } /// - /// Set the State property of the dialog + /// Set the State property of the dialog. /// /// Value for state. public void SetState(string value) { - data.State = value; + this.data.State = value; } - + /// - /// Set the NotifyOnCancel property of the dialog + /// Set the NotifyOnCancel property of the dialog. /// /// Set true to have Slack notify you with a `dialog_cancellation` event if a user cancels the dialog without submitting. public void SetNotifyOnCancel(bool set) { - data.NotifyOnCancel = set; + this.data.NotifyOnCancel = set; } /// - /// Set the title of the dialog + /// Set the title of the dialog. /// /// Value for title. public void SetTitle(string value) { - data.Title = value; + this.data.Title = value; } /// - /// Set the dialog's callback_id + /// Set the dialog's callback_id. /// /// Value for the callback_id. public void SetCallbackId(string value) { - data.CallbackId = value; + this.data.CallbackId = value; } /// - /// Set the button text for the submit button on the dialog + /// Set the button text for the submit button on the dialog. /// /// Value for the button label. public void SetSubmitLabel(string value) { - data.SubmitLabel = value; + this.data.SubmitLabel = value; } /// - /// Add a text element to the dialog + /// Add a text element to the dialog. /// /// Label of the element. /// Name of the element. /// Value of the element. - /// . + /// . /// Subtype of the element. public void AddText(DialogElement label, string name, string value, object options, string subtype = default(string)) { DialogElement element; element = label; - + if (options.GetType() == typeof(DialogElement)) { element = (DialogElement)options; } - data.Elements.Add(element); + this.data.Elements.Add(element); } /// - /// Add a text element to the dialog + /// Add a text element to the dialog. /// /// Label of the element. /// Name of the element. /// Value of the element. - /// . + /// . /// Subtype of the element. public void AddText(string label, string name, string value, object options, string subtype = default(string)) { @@ -109,7 +109,7 @@ public void SetSubmitLabel(string value) Name = name, Value = value, Type = "text", - Subtype = subtype + Subtype = subtype, }; if (options.GetType() == typeof(DialogElement)) @@ -117,64 +117,64 @@ public void SetSubmitLabel(string value) element = (DialogElement)options; } - data.Elements.Add(element); + this.data.Elements.Add(element); } /// - /// Add an email input to the dialog + /// Add an email input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. - /// . + /// . public void AddEmail(string label, string name, string value, object options = default(object)) { - AddText(label, name, value, options, "email"); + this.AddText(label, name, value, options, "email"); } /// - /// Add a number input to the dialog + /// Add a number input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. - /// . + /// . public void AddNumber(string label, string name, string value, object options = default(object)) { - AddText(label, name, value, options, "number"); + this.AddText(label, name, value, options, "number"); } /// - /// Add a telephone number input to the dialog + /// Add a telephone number input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. - /// . + /// . public void AddTel(string label, string name, string value, object options = default(object)) { - AddText(label, name, value, options, "tel"); + this.AddText(label, name, value, options, "tel"); } /// - /// Add a URL input to the dialog + /// Add a URL input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. - /// . + /// . public void AddUrl(string label, string name, string value, object options = default(object)) { - AddText(label, name, value, options, "url"); + this.AddText(label, name, value, options, "url"); } /// - /// Add a text area input to the dialog + /// Add a text area input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. - /// . + /// . /// Subtype of the input. public void AddTextArea(DialogElement label, string name, string value, object options, string subtype) { @@ -187,16 +187,16 @@ public void AddTextArea(DialogElement label, string name, string value, object o element = (DialogElement)options; } - data.Elements.Add(element); + this.data.Elements.Add(element); } /// - /// Add a text area input to the dialog + /// Add a text area input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. - /// . + /// . /// Subtype of the input. public void AddTextArea(string label, string name, string value, object options, string subtype) { @@ -208,7 +208,7 @@ public void AddTextArea(string label, string name, string value, object options, Name = name, Value = value, Type = "textArea", - Subtype = subtype + Subtype = subtype, }; if (options.GetType() == typeof(DialogElement)) @@ -216,17 +216,17 @@ public void AddTextArea(string label, string name, string value, object options, element = (DialogElement)options; } - data.Elements.Add(element); + this.data.Elements.Add(element); } /// - /// Add a dropdown select input to the dialog + /// Add a dropdown select input to the dialog. /// /// Label of the input. /// Name of the input. /// Value of the input. /// List of options of the input. - /// . + /// . public void AddSelect(string label, string name, string value, Dictionary optionList, object options) { DialogElement element; @@ -245,23 +245,23 @@ public void AddSelect(string label, string name, string value, Dictionary - /// Get the dialog object as a JSON encoded string. + /// Get the dialog object as a JSON encoded string. /// public string AsString() { - return JsonConvert.ToString(data.ToString()); + return JsonConvert.ToString(this.data.ToString()); } /// - /// Get the dialog object for use with bot.replyWithDialog() + /// Get the dialog object for use with bot.replyWithDialog(). /// public DialogData AsObject() { - return data; + return this.data; } } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackEventMiddleware.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackEventMiddleware.cs index aa8c524c59..1e2245e93f 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackEventMiddleware.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackEventMiddleware.cs @@ -1,13 +1,13 @@ -// Copyright(c) Microsoft Corporation. All rights reserved. +// Copyright(c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Bot.Builder; -using Microsoft.Bot.Schema; using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; namespace Microsoft.BotKit.Adapters.Slack { @@ -20,7 +20,7 @@ public class SlackEventMiddleware : IMiddleware /// public async Task OnTurnAsync(ITurnContext context, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)) { - if ((context.Activity.Type == ActivityTypes.Event)) + if (context.Activity.Type == ActivityTypes.Event) { // Handle message sub-types if ((context.Activity.ChannelData as dynamic)?.subtype != null) diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackMessageTypeMiddleware.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackMessageTypeMiddleware.cs index e7a18b6711..cd3f23fa4e 100644 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/SlackMessageTypeMiddleware.cs +++ b/Botkit/Microsoft.BotKit.Adapters.Slack/SlackMessageTypeMiddleware.cs @@ -1,8 +1,6 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. -using Microsoft.Bot.Builder; -using Microsoft.Bot.Schema; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +8,8 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; namespace Microsoft.BotKit.Adapters.Slack { @@ -25,10 +25,10 @@ namespace Microsoft.BotKit.Adapters.Slack public class SlackMessageTypeMiddleware : MiddlewareSet { /// - /// Not for direct use - implements the MiddlewareSet's required onTurn function used to process the event + /// Not for direct use - implements the MiddlewareSet's required onTurn function used to process the event. /// /// - /// + /// public async void OnTurn(TurnContext context, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken)) { if (context.Activity.Type == "message" && context.Activity.ChannelData != null) @@ -49,10 +49,10 @@ public class SlackMessageTypeMiddleware : MiddlewareSet Regex.Replace( Regex.Replace( Regex.Replace( - Regex.Replace(context.Activity.Text, directMention.ToString(), ""), - @"/ ^\s +/", ""), - @"/ ^:\s +/", ""), - @"/ ^\s +/", ""); + Regex.Replace(context.Activity.Text, directMention.ToString(), string.Empty), + @"/ ^\s +/", string.Empty), + @"/ ^:\s +/", string.Empty), + @"/ ^\s +/", string.Empty); } else if (!string.IsNullOrEmpty(botUserId) && !string.IsNullOrEmpty(context.Activity.Text) && context.Activity.Text.Equals(directMention)) { @@ -62,10 +62,10 @@ public class SlackMessageTypeMiddleware : MiddlewareSet Regex.Replace( Regex.Replace( Regex.Replace( - Regex.Replace(context.Activity.Text, directMention.ToString(), ""), - @"/ ^\s +/", ""), - @"/ ^:\s +/", ""), - @"/ ^\s +/", ""); + Regex.Replace(context.Activity.Text, directMention.ToString(), string.Empty), + @"/ ^\s +/", string.Empty), + @"/ ^:\s +/", string.Empty), + @"/ ^\s +/", string.Empty); } else if (!string.IsNullOrEmpty(botUserId) && string.IsNullOrEmpty(context.Activity.Text) && context.Activity.Text.Equals(mention)) { @@ -83,6 +83,7 @@ public class SlackMessageTypeMiddleware : MiddlewareSet context.Activity.Type = ActivityTypes.Event; } } + await next(cancellationToken).ConfigureAwait(false); } } diff --git a/Botkit/Microsoft.BotKit.Adapters.Slack/Ware.cs b/Botkit/Microsoft.BotKit.Adapters.Slack/Ware.cs deleted file mode 100644 index d3336e6209..0000000000 --- a/Botkit/Microsoft.BotKit.Adapters.Slack/Ware.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.BotKit.Adapters.Slack -{ - public class Ware - { - /// - /// Valid names: spawn, ingest, send, receive - /// - public string Name; - public List> Middlewares; - } -} diff --git a/Botkit/Microsoft.BotKit/Core/Botkit.cs b/Botkit/Microsoft.BotKit/Core/Botkit.cs index 571cda3edf..a16182bdc5 100644 --- a/Botkit/Microsoft.BotKit/Core/Botkit.cs +++ b/Botkit/Microsoft.BotKit/Core/Botkit.cs @@ -1,4 +1,4 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. using Microsoft.Bot.Builder; @@ -17,7 +17,7 @@ public class Botkit /// Middleware endpoints available for plugins and features to extend Botkit. /// Endpoints available are: spawn, ingest, receive, send. /// - public Middleware Middleware { get; set; } + // Not yet implemented /// /// A BotBuilder storage driver - defaults to MemoryStorage diff --git a/Botkit/Microsoft.BotKit/Core/IBotkitPlugin.cs b/Botkit/Microsoft.BotKit/Core/IBotkitPlugin.cs index 936079a69d..8b061d0c3b 100644 --- a/Botkit/Microsoft.BotKit/Core/IBotkitPlugin.cs +++ b/Botkit/Microsoft.BotKit/Core/IBotkitPlugin.cs @@ -1,4 +1,4 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. +// Copyright(c) Microsoft Corporation.All rights reserved. // Licensed under the MIT License. using System; @@ -10,6 +10,5 @@ public interface IBotkitPlugin { string Name { get; set; } Task Init(Botkit botkit); - Tuple Middlewares { get; set; } } } diff --git a/Botkit/Microsoft.BotKit/Core/IMiddleware.cs b/Botkit/Microsoft.BotKit/Core/IMiddleware.cs deleted file mode 100644 index b873a11c6f..0000000000 --- a/Botkit/Microsoft.BotKit/Core/IMiddleware.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.BotKit.Core -{ - public interface IMiddleware - { - } -} diff --git a/Botkit/Microsoft.BotKit/Core/Middleware.cs b/Botkit/Microsoft.BotKit/Core/Middleware.cs deleted file mode 100644 index be808e632d..0000000000 --- a/Botkit/Microsoft.BotKit/Core/Middleware.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright(c) Microsoft Corporation.All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.BotKit.Core -{ - public class Middleware - { - public IMiddleware Spawn { get; set; } - public IMiddleware Ingest { get; set; } - public IMiddleware Send { get; set; } - public IMiddleware Receive { get; set; } - } -}