/// <summary> /// Verifies the verify token from the message. If the token matches the one configured, sends back the challenge. /// </summary> /// <param name="request">Represents the incoming side of an HTTP request.</param> /// <param name="response">Represents the outgoing side of an HTTP request.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <exception cref="ArgumentNullException"><paramref name="request"/> or <paramref name="response"/> is null.</exception> public virtual async Task VerifyWebhookAsync(HttpRequest request, HttpResponse response, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } var challenge = string.Empty; HttpStatusCode statusCode; if (request.Query["hub.verify_token"].Equals(_options.FacebookVerifyToken)) { challenge = request.Query["hub.challenge"]; statusCode = HttpStatusCode.OK; } else { statusCode = HttpStatusCode.Unauthorized; } await FacebookHelper.WriteAsync(response, statusCode, challenge, Encoding.UTF8, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Standard BotBuilder adapter method to send a message from the bot to the messaging API. /// </summary> /// <param name="context">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>A <see cref="Task"/> representing the asynchronous operation.</returns> public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext context, Activity[] activities, CancellationToken cancellationToken) { var responses = new List <ResourceResponse>(); foreach (var activity in activities) { if (activity.Type != ActivityTypes.Message) { throw new Exception("Only Activities of type Message are supported for sending."); } var message = FacebookHelper.ActivityToFacebook(activity); if (message.Message.Attachment != null) { message.Message.Attachments = null; message.Message.Text = null; } var res = await _facebookClient.SendMessageAsync("/me/messages", message, null, cancellationToken).ConfigureAwait(false); var response = new ResourceResponse() { Id = res, }; responses.Add(response); } return(responses.ToArray()); }
/// <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>A <see cref="Task"/> representing the asynchronous operation.</returns> public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken) { var responses = new List <ResourceResponse>(); foreach (var activity in activities) { if (activity.Type != ActivityTypes.Message && activity.Type != ActivityTypes.Event) { _logger.LogTrace($"Unsupported Activity Type: '{activity.Type}'. Only Activities of type 'Message' or 'Event' are supported."); } else { var message = FacebookHelper.ActivityToFacebook(activity); if (message.Message.Attachment != null) { message.Message.Text = null; } var res = await _facebookClient.SendMessageAsync("/me/messages", message, null, cancellationToken) .ConfigureAwait(false); if (activity.Type == ActivityTypes.Event) { if (activity.Name.Equals(HandoverConstants.PassThreadControl, StringComparison.Ordinal)) { var recipient = (string)activity.Value == "inbox" ? HandoverConstants.PageInboxId : (string)activity.Value; await _facebookClient.PassThreadControlAsync(recipient, activity.Conversation.Id, HandoverConstants.MetadataPassThreadControl).ConfigureAwait(false); } else if (activity.Name.Equals(HandoverConstants.TakeThreadControl, StringComparison.Ordinal)) { await _facebookClient.TakeThreadControlAsync(activity.Conversation.Id, HandoverConstants.MetadataTakeThreadControl).ConfigureAwait(false); } else if (activity.Name.Equals(HandoverConstants.RequestThreadControl, StringComparison.Ordinal)) { await _facebookClient.RequestThreadControlAsync(activity.Conversation.Id, HandoverConstants.MetadataRequestThreadControl).ConfigureAwait(false); } } var response = new ResourceResponse() { Id = res, }; responses.Add(response); } } return(responses.ToArray()); }
/// <summary> /// Accepts an incoming webhook request, creates a turn context, /// and runs the middleware pipeline for an incoming TRUSTED activity. /// </summary> /// <param name="httpRequest">Represents the incoming side of an HTTP request.</param> /// <param name="httpResponse">Represents the outgoing side of an HTTP request.</param> /// <param name="bot">The code to run at the end of the adapter's middleware pipeline.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <exception cref="AuthenticationException">The webhook receives message with invalid signature.</exception> public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default) { if (httpRequest.Query["hub.mode"] == HubModeSubscribe && _options.VerifyIncomingRequests) { await _facebookClient.VerifyWebhookAsync(httpRequest, httpResponse, cancellationToken).ConfigureAwait(false); return; } string stringifiedBody; using (var sr = new StreamReader(httpRequest.Body)) { stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false); } if (!_facebookClient.VerifySignature(httpRequest, stringifiedBody) && _options.VerifyIncomingRequests) { await FacebookHelper.WriteAsync(httpResponse, HttpStatusCode.Unauthorized, string.Empty, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new AuthenticationException("Webhook received message with invalid signature. Potential malicious behavior!"); } FacebookResponseEvent facebookResponseEvent = null; facebookResponseEvent = JsonConvert.DeserializeObject <FacebookResponseEvent>(stringifiedBody); foreach (var entry in facebookResponseEvent.Entry) { var payload = entry.Changes.Count > 0 ? entry.Changes : entry.Messaging.Count > 0 ? entry.Messaging : entry.Standby.Count > 0 ? entry.Standby : new List <FacebookMessage>(); foreach (var message in payload) { message.IsStandby = entry.Standby.Count > 0; var activity = FacebookHelper.ProcessSingleMessage(message); using (var context = new TurnContext(this, activity)) { await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); } } } }
/// <summary> /// Factory method to create the <see cref="FacebookMessage"/> instance of the <see cref="Activity"/> to be sent to Facebook. /// </summary> /// <remarks> /// This lets an override add a Facebook-supported message tag to an outgoing message. /// See https://developers.facebook.com/docs/messenger-platform/send-messages/message-tags/. /// </remarks> /// <param name="activity">An <see cref="Activity"/> instance to build the message.</param> /// <returns>A <see cref="FacebookMessage"/> built from the activity instance.</returns> protected virtual FacebookMessage CreateFacebookMessageFromActivity(Activity activity) { return(FacebookHelper.ActivityToFacebook(activity)); }
/// <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">A request object.</param> /// <param name="response">A response object.</param> /// <param name="bot">A bot logic function.</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.Query["hub.mode"] == HubModeSubscribe) { await _facebookClient.VerifyWebhookAsync(request, response, cancellationToken).ConfigureAwait(false); return; } string stringifyBody; using (var sr = new StreamReader(request.Body)) { stringifyBody = sr.ReadToEnd(); } if (!_facebookClient.VerifySignature(request, stringifyBody)) { await FacebookHelper.WriteAsync(response, HttpStatusCode.Unauthorized, string.Empty, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new Exception("WARNING: Webhook received message with invalid signature. Potential malicious behavior!"); } FacebookResponseEvent facebookEvent = null; facebookEvent = JsonConvert.DeserializeObject <FacebookResponseEvent>(stringifyBody); foreach (var entry in facebookEvent.Entry) { var payload = new List <FacebookMessage>(); payload = entry.Changes ?? entry.Messaging; foreach (var message in payload) { var activity = FacebookHelper.ProcessSingleMessage(message); using (var context = new TurnContext(this, activity)) { await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); } } // Handle standby messages (this bot is not the active receiver) if (entry.Standby != null) { payload = entry.Standby; foreach (var message in payload) { // Indicate that this message was received in standby mode rather than normal mode. message.Standby = true; var activity = FacebookHelper.ProcessSingleMessage(message); using (var context = new TurnContext(this, activity)) { await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); } } } } }