/// <summary> /// Standard BotBuilder adapter method to send a message from the bot to the messaging API. /// </summary> /// <param name="turnContext">A TurnContext representing the current incoming message and environment.</param> /// <param name="activities">An array of outgoing activities to be sent back to the messaging API.</param> /// <param name="cancellationToken">A cancellation token for the task.</param> /// <returns>An array of <see cref="ResourceResponse"/> objects containing the IDs that Slack assigned to the sent messages.</returns> public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken) { if (turnContext == null) { throw new ArgumentNullException(nameof(turnContext)); } if (activities == null) { throw new ArgumentNullException(nameof(activities)); } var responses = new List <ResourceResponse>(); foreach (var activity in activities) { if (activity.Type != ActivityTypes.Message) { _logger.LogTrace($"Unsupported Activity Type: '{activity.Type}'. Only Activities of type 'Message' are supported."); } else { var message = SlackHelper.ActivityToSlack(activity); var slackResponse = await _slackClient.PostMessageAsync(message, cancellationToken) .ConfigureAwait(false); if (slackResponse != null && slackResponse.Ok) { var resourceResponse = new ActivityResourceResponse() { Id = slackResponse.Ts, ActivityId = slackResponse.Ts, Conversation = new ConversationAccount() { Id = slackResponse.Channel, }, }; responses.Add(resourceResponse); } } } return(responses.ToArray()); }
/// <summary> /// Standard BotBuilder adapter method to update a previous message with new content. /// </summary> /// <param name="turnContext">A TurnContext representing the current incoming message and environment.</param> /// <param name="activity">The updated activity in the form '{id: `id of activity to update`, ...}'.</param> /// <param name="cancellationToken">A cancellation token for the task.</param> /// <returns>A resource response with the Id of the updated activity.</returns> public override async Task <ResourceResponse> UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken) { if (turnContext == null) { throw new ArgumentNullException(nameof(turnContext)); } if (activity == null) { throw new ArgumentNullException(nameof(activity)); } if (activity.Id == null) { throw new ArgumentException(nameof(activity.Timestamp)); } if (activity.Conversation == null) { throw new ArgumentException(nameof(activity.ChannelId)); } var message = SlackHelper.ActivityToSlack(activity); var results = await _slackClient.UpdateAsync(message.Ts, message.Channel, message.Text, cancellationToken : cancellationToken).ConfigureAwait(false); if (!results.Ok) { throw new InvalidOperationException($"Error updating activity on Slack:{results}"); } return(new ResourceResponse() { Id = activity.Id, }); }
/// <summary> /// Accept an incoming webhook request and convert it into a TurnContext which can be processed by the bot's logic. /// </summary> /// <param name="request">The incoming HTTP request.</param> /// <param name="response">When this method completes, the HTTP response to send.</param> /// <param name="bot">The bot that will handle the incoming activity.</param> /// <param name="cancellationToken">A cancellation token for the task.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot bot, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (bot == null) { throw new ArgumentNullException(nameof(bot)); } string body; Activity activity = null; using (var sr = new StreamReader(request.Body)) { body = await sr.ReadToEndAsync().ConfigureAwait(false); } if (_options.VerifyIncomingRequests && !_slackClient.VerifySignature(request, body)) { const string text = "Rejected due to mismatched header signature"; await SlackHelper.WriteAsync(response, HttpStatusCode.Unauthorized, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new AuthenticationException(text); } var requestContentType = request.Headers["Content-Type"].ToString(); if (requestContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { var postValues = SlackHelper.QueryStringToDictionary(body); if (postValues.ContainsKey("payload")) { var payload = JsonConvert.DeserializeObject <InteractionPayload>(postValues["payload"]); activity = SlackHelper.PayloadToActivity(payload); } else if (postValues.ContainsKey("command")) { var serializedPayload = JsonConvert.SerializeObject(postValues); var payload = JsonConvert.DeserializeObject <CommandPayload>(serializedPayload); activity = SlackHelper.CommandToActivity(payload, _slackClient); } } else if (requestContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) { var bodyObject = JObject.Parse(body); if (bodyObject["type"]?.ToString() == "url_verification") { var verificationEvent = bodyObject.ToObject <UrlVerificationEvent>(); await SlackHelper.WriteAsync(response, HttpStatusCode.OK, verificationEvent.Challenge, Encoding.UTF8, cancellationToken).ConfigureAwait(false); return; } if (!string.IsNullOrWhiteSpace(_slackClient.Options.SlackVerificationToken) && bodyObject["token"]?.ToString() != _slackClient.Options.SlackVerificationToken) { var text = $"Rejected due to mismatched verificationToken:{bodyObject["token"]}"; await SlackHelper.WriteAsync(response, HttpStatusCode.Forbidden, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new AuthenticationException(text); } if (bodyObject["type"]?.ToString() == "event_callback") { // this is an event api post var eventRequest = bodyObject.ToObject <EventRequest>(); activity = SlackHelper.EventToActivity(eventRequest, _slackClient); } } // As per official Slack API docs, some additional request types may be receieved that can be ignored // but we should respond with a 200 status code // https://api.slack.com/interactivity/slash-commands if (activity == null) { await SlackHelper.WriteAsync(response, HttpStatusCode.OK, "Unable to transform request / payload into Activity. Possible unrecognized request type", Encoding.UTF8, cancellationToken).ConfigureAwait(false); } else { using (var context = new TurnContext(this, activity)) { context.TurnState.Add("httpStatus", ((int)HttpStatusCode.OK).ToString(System.Globalization.CultureInfo.InvariantCulture)); await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); var code = Convert.ToInt32(context.TurnState.Get <string>("httpStatus"), System.Globalization.CultureInfo.InvariantCulture); var statusCode = (HttpStatusCode)code; var text = context.TurnState.Get <object>("httpBody") != null?context.TurnState.Get <object>("httpBody").ToString() : string.Empty; await SlackHelper.WriteAsync(response, statusCode, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); } } }