Facebook adapter/add handover feature

* Add Facebook handover feature
This commit is contained in:
Matias Roldan 2019-11-04 16:48:26 -03:00 коммит произвёл GitHub
Родитель c20ac4ce0e
Коммит c5d2be4754
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
39 изменённых файлов: 1915 добавлений и 63 удалений

Просмотреть файл

@ -165,6 +165,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Facebook", "libraries\Adapters\Microsoft.Bot.Builder.Adapters.Facebook\Microsoft.Bot.Builder.Adapters.Facebook.csproj", "{C8E31CD2-89D4-4659-9557-43EC9C99D984}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot", "tests\Adapters\Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot\Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot.csproj", "{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
@ -796,6 +798,16 @@ Global
{C8E31CD2-89D4-4659-9557-43EC9C99D984}.Release|Any CPU.Build.0 = Release|Any CPU
{C8E31CD2-89D4-4659-9557-43EC9C99D984}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
{C8E31CD2-89D4-4659-9557-43EC9C99D984}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Debug-Windows|Any CPU.Build.0 = Debug|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Release|Any CPU.Build.0 = Release|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -869,6 +881,7 @@ Global
{9F7B2BDF-973B-4639-B890-357EB967B2ED} = {E8CD434A-306F-41D9-B67D-BFFF3287354D}
{E372D54C-73AA-41DB-A471-81348AC37F36} = {E8CD434A-306F-41D9-B67D-BFFF3287354D}
{C8E31CD2-89D4-4659-9557-43EC9C99D984} = {6230B915-B238-4E57-AAC4-06B4498F540F}
{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8} = {E8CD434A-306F-41D9-B67D-BFFF3287354D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}

Просмотреть файл

@ -20,6 +20,11 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
public class FacebookAdapter : BotAdapter, IBotFrameworkHttpAdapter
{
private const string HubModeSubscribe = "subscribe";
/// <summary>
/// The constant ID representing the page inbox.
/// </summary>
private const string PageInboxId = "263902037430900";
private readonly FacebookClientWrapper _facebookClient;
@ -61,9 +66,9 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
foreach (var activity in activities)
{
if (activity.Type != ActivityTypes.Message)
if (activity.Type != ActivityTypes.Message && activity.Type != ActivityTypes.Event)
{
throw new Exception("Only Activities of type Message are supported for sending.");
continue;
}
var message = FacebookHelper.ActivityToFacebook(activity);
@ -76,6 +81,23 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
var res = await _facebookClient.SendMessageAsync("/me/messages", message, null, cancellationToken).ConfigureAwait(false);
if (activity.Type == ActivityTypes.Event)
{
if (activity.Name.Equals("pass_thread_control", StringComparison.InvariantCulture))
{
var recipient = (string)activity.Value == "inbox" ? PageInboxId : (string)activity.Value;
await _facebookClient.PassThreadControlAsync(recipient, activity.Conversation.Id, "Pass thread control").ConfigureAwait(false);
}
else if (activity.Name.Equals("take_thread_control", StringComparison.InvariantCulture))
{
await _facebookClient.TakeThreadControlAsync(activity.Conversation.Id, "Take thread control from a secondary receiver").ConfigureAwait(false);
}
else if (activity.Name.Equals("request_thread_control", StringComparison.InvariantCulture))
{
await _facebookClient.RequestThreadControlAsync(activity.Conversation.Id, "Request thread control to the primary receiver").ConfigureAwait(false);
}
}
var response = new ResourceResponse()
{
Id = res,
@ -153,31 +175,33 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
return;
}
string stringifyBody;
string stringifiedBody;
using (var sr = new StreamReader(request.Body))
{
stringifyBody = sr.ReadToEnd();
stringifiedBody = sr.ReadToEnd();
}
if (!_facebookClient.VerifySignature(request, stringifyBody))
if (!_facebookClient.VerifySignature(request, stringifiedBody))
{
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;
FacebookResponseEvent facebookResponseEvent = null;
facebookEvent = JsonConvert.DeserializeObject<FacebookResponseEvent>(stringifyBody);
facebookResponseEvent = JsonConvert.DeserializeObject<FacebookResponseEvent>(stringifiedBody);
foreach (var entry in facebookEvent.Entry)
foreach (var entry in facebookResponseEvent.Entry)
{
var payload = new List<FacebookMessage>();
payload = entry.Changes ?? entry.Messaging;
payload = entry.Changes ?? entry.Messaging ?? entry.Standby;
foreach (var message in payload)
{
message.IsStandby = entry.Standby != null;
var activity = FacebookHelper.ProcessSingleMessage(message);
using (var context = new TurnContext(this, activity))
@ -185,24 +209,6 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
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);
}
}
}
}
}
}

Просмотреть файл

@ -159,5 +159,99 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
await FacebookHelper.WriteAsync(response, statusCode, challenge, Encoding.UTF8, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Posts webhook control events to Facebook.
/// </summary>
/// <param name="postType">The REST post type (GET, PUT, POST, etc).</param>
/// <param name="content">The string content to be posted to Facebook.</param>
/// <returns>A bool indicating the success of the operation.</returns>
public virtual async Task<bool> PostToFacebookApiAsync(string postType, string content)
{
if (postType == null)
{
throw new ArgumentNullException(nameof(postType));
}
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
var graphApiBaseUrl = $"https://{_options.FacebookApiHost}/{_options.FacebookApiVersion + postType}?access_token={_options.FacebookAccessToken}";
var requestPath = string.Format(System.Globalization.CultureInfo.InvariantCulture, graphApiBaseUrl, postType, _options.FacebookAccessToken);
var stringContent = new StringContent(content, Encoding.UTF8, "application/json");
using (var requestMessage = new HttpRequestMessage())
{
requestMessage.Method = new HttpMethod("POST");
requestMessage.RequestUri = new Uri(requestPath);
requestMessage.Content = stringContent;
requestMessage.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
using (var client = new HttpClient())
{
var res = await client.SendAsync(requestMessage, CancellationToken.None).ConfigureAwait(false);
return res.IsSuccessStatusCode;
}
}
}
/// <summary>
/// Sends the request_thread_control webhook event to Facebook.
/// </summary>
/// <param name="userId">The sender user Id.</param>
/// <param name="message">An optional message for the metadata paremter.</param>
/// <returns>A bool value indicating the success of the operation.</returns>
public virtual async Task<bool> RequestThreadControlAsync(string userId, string message)
{
if (userId == null)
{
throw new ArgumentNullException(nameof(userId));
}
var content = new { recipient = new { id = userId }, metadata = message };
return await PostToFacebookApiAsync("/me/request_thread_control", JsonConvert.SerializeObject(content)).ConfigureAwait(false);
}
/// <summary>
/// Sends the take_thread_control webhook event to Facebook.
/// </summary>
/// <param name="userId">The sender user Id.</param>
/// <param name="message">An optional message for the metadata paremter.</param>
/// <returns>A bool value indicating the success of the operation.</returns>
public virtual async Task<bool> TakeThreadControlAsync(string userId, string message)
{
if (userId == null)
{
throw new ArgumentNullException(nameof(userId));
}
var content = new { recipient = new { id = userId }, metadata = message };
return await PostToFacebookApiAsync("/me/take_thread_control", JsonConvert.SerializeObject(content)).ConfigureAwait(false);
}
/// <summary>
/// Sends the pass_thread_control webhook event to Facebook.
/// </summary>
/// <param name="targetAppId">The Id of the target app to pass control to.</param>
/// <param name="userId">The sender user Id.</param>
/// <param name="message">An optional message for the metadata paremter.</param>
/// <returns>A bool value indicating the success of the operation.</returns>
public virtual async Task<bool> PassThreadControlAsync(string targetAppId, string userId, string message)
{
if (targetAppId == null)
{
throw new ArgumentNullException(nameof(targetAppId));
}
if (userId == null)
{
throw new ArgumentNullException(nameof(userId));
}
var content = new { recipient = new { id = userId }, target_app_id = targetAppId, metadata = message };
return await PostToFacebookApiAsync("/me/pass_thread_control", JsonConvert.SerializeObject(content)).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -8,7 +8,7 @@ using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents
{
public class MessagePayload
public class AttachmentPayload
{
/// <summary>
/// Gets or sets the url of the attachment.

Просмотреть файл

@ -19,6 +19,6 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents
/// </summary>
/// <value>Payload of the attachment.</value>
[JsonProperty(PropertyName = "payload")]
public MessagePayload Payload { get; set; }
public AttachmentPayload Payload { get; set; }
}
}

Просмотреть файл

@ -20,21 +20,21 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents
public long Time { get; set; }
/// <summary>
/// Gets or sets the messaging list.
/// Gets the messaging list.
/// </summary>
/// <value>List containing one messaging object. Note that even though this is an enumerable, it will only contain one object.</value>
public List<FacebookMessage> Messaging { get; set; }
public List<FacebookMessage> Messaging { get; }
/// <summary>
/// Gets or sets the changes list.
/// Gets the changes list.
/// </summary>
/// <value>List containing the list of changes.</value>
public List<FacebookMessage> Changes { get; set; }
public List<FacebookMessage> Changes { get; }
/// <summary>
/// Gets or sets the standby messages list.
/// Gets the standby messages list.
/// </summary>
/// <value>List containing the messages sent while in standby mode.</value>
public List<FacebookMessage> Standby { get; set; }
public List<FacebookMessage> Standby { get; }
}
}

Просмотреть файл

@ -11,6 +11,6 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents
[JsonProperty(PropertyName = "object")]
public string ResponseObject { get; set; }
public List<FacebookEntry> Entry { get; set; }
public List<FacebookEntry> Entry { get; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover
{
public class FacebookPassThreadControl : FacebookThreadControl
{
/// <summary>
/// Gets or Sets the app id of the new owner.
/// </summary>
/// <remarks>
/// 263902037430900 for the page inbox.
/// </remarks>
/// <value>
/// The app id of the new owner.
/// </value>
[JsonProperty("new_owner_app_id")]
public string NewOwnerAppId { get; set; }
[JsonProperty("previous_owner_app_id")]
public string PreviousOwnerAppId { get; set; }
[JsonProperty("requested_owner_app_id")]
public string RequestedOwnerAppId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover
{
/// <summary>
/// A Facebook thread control message, including appid of requested thread owner and an optional message to send with the request
/// <see cref="Metadata"/>.
/// </summary>
public class FacebookRequestThreadControl : FacebookThreadControl
{
/// <summary>
/// Gets or Sets the app id of the requested owner.
/// </summary>
/// <remarks>
/// 263902037430900 for the page inbox.
/// </remarks>
/// <value>
/// the app id of the requested owner.
/// </value>
[JsonProperty("requested_owner_app_id")]
public string RequestedOwnerAppId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover
{
public class FacebookTakeThreadControl : FacebookThreadControl
{
/// <summary>
/// Gets or Sets the app id of the previous owner.
/// </summary>
/// <remarks>
/// 263902037430900 for the page inbox.
/// </remarks>
/// <value>
/// The app id of the previous owner.
/// </value>
[JsonProperty("previous_owner_app_id")]
public string PreviousOwnerAppId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover
{
public class FacebookThreadControl
{
/// <summary>
/// Gets or Sets the message sent from the requester.
/// </summary>
/// <remarks>
/// Example: "All yours!".
/// </remarks>
/// <value>
/// Message sent from the requester.
/// </value>
[JsonProperty("metadata")]
public string Metadata { get; set; }
}
}

Просмотреть файл

@ -32,7 +32,6 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
facebookMessage.Message.Text = activity.Text;
// map these fields to their appropriate place
if (activity.ChannelData != null)
{
facebookMessage = activity.GetChannelData<FacebookMessage>();
@ -52,7 +51,7 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
if (activity.Attachments != null && activity.Attachments.Count > 0)
{
var payload = JsonConvert.DeserializeObject<MessagePayload>(JsonConvert.SerializeObject(
var payload = JsonConvert.DeserializeObject<AttachmentPayload>(JsonConvert.SerializeObject(
activity.Attachments[0].Content,
Formatting.None,
new JsonSerializerSettings
@ -60,13 +59,13 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
NullValueHandling = NullValueHandling.Ignore,
}));
var attach = new FacebookAttachment
var facebookAttachment = new FacebookAttachment
{
Type = activity.Attachments[0].ContentType,
Payload = payload,
};
facebookMessage.Message.Attachment = attach;
facebookMessage.Message.Attachment = facebookAttachment;
}
return facebookMessage;
@ -112,18 +111,24 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
Text = null,
};
if (message.PassThreadControl != null)
{
activity.Value = message.PassThreadControl;
}
else if (message.RequestThreadControl != null)
{
activity.Value = message.RequestThreadControl;
}
else if (message.TakeThreadControl != null)
{
activity.Value = message.TakeThreadControl;
}
if (message.Message != null)
{
activity.Type = ActivityTypes.Message;
activity.Text = message.Message.Text;
if (activity.GetChannelData<FacebookMessage>().Message.IsEcho)
{
activity.Type = ActivityTypes.Event;
}
// copy all fields (like attachments, sticker, quick_reply, nlp, etc.)
activity.ChannelData = message.Message;
activity.Type = activity.GetChannelData<FacebookMessage>().Message.IsEcho ? ActivityTypes.Event : ActivityTypes.Message;
if (message.Message.Attachments != null && message.Message.Attachments.Count > 0)
{
activity.Attachments = HandleMessageAttachments(message.Message);
@ -138,6 +143,11 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
return activity;
}
/// <summary>
/// Extracts attachments from the facebook message.
/// </summary>
/// <param name="message">The <see cref="Message"/>used for input.</param>
/// <returns>A List of <see cref="Attachment"/>.</returns>
public static List<Attachment> HandleMessageAttachments(Message message)
{
var attachmentsList = new List<Attachment>();

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents;
using Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook
@ -79,16 +80,53 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
[JsonProperty(PropertyName = "sender_action")]
public string SenderAction { get; set; }
/// <summary>
/// Gets or sets the timestamp.
/// </summary>
/// <value>Timestamp.</value>
[JsonProperty(PropertyName = "timestamp")]
public long TimeStamp { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the message was received while in Standby mode.
/// </summary>
/// <value>Value indicating whether the message was received while in Standby mode.</value>
[JsonProperty(PropertyName = "standby")]
public bool Standby { get; set; }
public bool IsStandby { get; set; }
/// <summary>
/// Gets or sets the value of the postback property.
/// </summary>
/// <value>The postback payload. Postbacks occur when a postback button, Get Started button, or persistent menu item is tapped.</value>
[JsonProperty(PropertyName = "postback")]
public FacebookPostBack PostBack { get; set; }
/// <summary>
/// Gets or sets the value of the optin property.
/// </summary>
/// <value>The optin field. See https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_optins. </value>
[JsonProperty(PropertyName = "optin")]
public FacebookRecipient OptIn { get; set; }
/// <summary>
/// Gets or sets the contents of the pass_thread_control property.
/// </summary>
/// <value>A <see cref="FacebookPassThreadControl"> holding the contents of the pass_thread_control property.</value>.
[JsonProperty(PropertyName = "pass_thread_control")]
public FacebookPassThreadControl PassThreadControl { get; set; }
/// <summary>
/// Gets or sets the contents of the take_thread_control property.
/// </summary>
/// <value>A <see cref="FacebookTakeThreadControl"> holding the contents of the pass_thread_control property.</value>.
[JsonProperty(PropertyName = "take_thread_control")]
public FacebookTakeThreadControl TakeThreadControl { get; set; }
/// <summary>
/// Gets or sets the contents of the request_thread_control property.
/// </summary>
/// <value>A <see cref="FacebookRequestThreadControl"> holding the contents of the pass_thread_control property.</value>.
[JsonProperty(PropertyName = "request_thread_control")]
public FacebookRequestThreadControl RequestThreadControl { get; set; }
}
}

Просмотреть файл

@ -26,14 +26,14 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
/// <summary>
/// Gets or sets a list of attachments.
/// </summary>
/// <value>Attachments.</value>
/// <value>Attachments that could come with a Facebook message.</value>
[JsonProperty(PropertyName = "attachments")]
public List<FacebookAttachment> Attachments { get; set; }
/// <summary>
/// Gets or sets the attachment.
/// </summary>
/// <value>Attachment.</value>
/// <value>Single attachment that will be sent back to Facebook.</value>
[JsonProperty(PropertyName = "attachment")]
public FacebookAttachment Attachment { get; set; }
@ -52,9 +52,20 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook
[JsonProperty(PropertyName = "quick_replies")]
public List<FacebookQuickReply> QuickReplies { get; set; } = new List<FacebookQuickReply>();
/// <summary>
/// Gets or sets a value indicating whether the message was sent from the page itself.
/// </summary>
/// <value>A value indicating whether the message was sent from the page itself.</value>
[JsonProperty(PropertyName = "is_echo")]
public bool IsEcho { get; set; }
/// <summary>
/// Gets or sets the Mid.
/// </summary>
/// <value>Message ID.</value>
[JsonProperty(PropertyName = "mid")]
public string Mid { get; set; }
public bool ShouldSerializeQuickReplies()
{
return QuickReplies.Count > 0;

Просмотреть файл

@ -0,0 +1,110 @@
// 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
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot.Bots
{
public class EchoBot : ActivityHandler
{
/// <summary>
/// This option passes thread control from the secondary receiver to a primary receiver.
/// </summary>
private const string OptionPassPrimaryBot = "Pass to primary";
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var activity = MessageFactory.Text("Hello and Welcome!");
await turnContext.SendActivityAsync(activity, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.GetChannelData<FacebookMessage>().IsStandby)
{
if ((turnContext.Activity as Activity)?.Text == "OtherBot")
{
var activity = new Activity();
activity.Type = ActivityTypes.Event;
//Action
(activity as IEventActivity).Name = "request_thread_control";
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
else if (turnContext.Activity.Attachments != null)
{
foreach (var attachment in turnContext.Activity.Attachments)
{
var activity = MessageFactory.Text($" I got {turnContext.Activity.Attachments.Count} attachments");
var image = new Attachment(
attachment.ContentType,
content: attachment.Content);
activity.Attachments.Add(image);
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
else
{
IActivity activity;
switch (turnContext.Activity.Text)
{
case OptionPassPrimaryBot:
activity = MessageFactory.Text("Redirecting to the primary bot...");
activity.Type = ActivityTypes.Event;
(activity as IEventActivity).Name = "pass_thread_control";
(activity as IEventActivity).Value = "<PRIMARY RECEIVER APP ID>";
break;
case "Redirected to the bot":
activity = MessageFactory.Text("Hello Human, I'm the secondary bot to help you!");
break;
case "Little":
activity = MessageFactory.Text($"You have spoken the forbidden word!");
break;
default:
activity = MessageFactory.Text($"Echo Secondary: {turnContext.Activity.Text}");
break;
}
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Value != null)
{
var metadata = (turnContext.Activity.Value as FacebookThreadControl).Metadata;
if (metadata.Equals("Pass thread control"))
{
var activity = MessageFactory.Text("Hello Human, I'm the secondary bot to help you!");
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
}
private static Attachment CreateTemplateAttachment(string filePath)
{
var templateAttachmentJson = File.ReadAllText(filePath);
var templateAttachment = new Attachment()
{
ContentType = "template",
Content = JsonConvert.DeserializeObject(templateAttachmentJson),
};
return templateAttachment;
}
}
}

Просмотреть файл

@ -0,0 +1,45 @@
// 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
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
namespace Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot.Controllers
{
// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot
// implementation at runtime. Multiple different IBot implementations running at different endpoints can be
// achieved by specifying a more specific type for the bot constructor argument.
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly IBot _bot;
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
_adapter = adapter;
_bot = bot;
}
[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);
}
[HttpGet]
public async Task GetAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
}
}

Просмотреть файл

@ -0,0 +1,183 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"groupLocation": {
"type": "string",
"metadata": {
"description": "Specifies the location of the Resource Group."
}
},
"groupName": {
"type": "string",
"metadata": {
"description": "Specifies the name of the Resource Group."
}
},
"appId": {
"type": "string",
"metadata": {
"description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings."
}
},
"botId": {
"type": "string",
"metadata": {
"description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
}
},
"botSku": {
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"type": "string",
"metadata": {
"description": "The name of the App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"newAppServicePlanLocation": {
"type": "string",
"metadata": {
"description": "The location of the App Service Plan. Defaults to \"westus\"."
}
},
"newWebAppName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
}
}
},
"variables": {
"appServicePlanName": "[parameters('newAppServicePlanName')]",
"resourcesLocation": "[parameters('newAppServicePlanLocation')]",
"webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
"siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
"botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
},
"resources": [
{
"name": "[parameters('groupName')]",
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('groupLocation')]",
"properties": {
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "storageDeployment",
"resourceGroup": "[parameters('groupName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"comments": "Create a new App Service Plan",
"type": "Microsoft.Web/serverfarms",
"name": "[variables('appServicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('resourcesLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"properties": {
"name": "[variables('appServicePlanName')]"
}
},
{
"comments": "Create a Web App using the new App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"location": "[variables('resourcesLocation')]",
"kind": "app",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
],
"name": "[variables('webAppName')]",
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[variables('appServicePlanName')]",
"siteConfig": {
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"name": "MicrosoftAppId",
"value": "[parameters('appId')]"
},
{
"name": "MicrosoftAppPassword",
"value": "[parameters('appSecret')]"
}
],
"cors": {
"allowedOrigins": [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net/"
]
}
}
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.BotService/botServices",
"name": "[parameters('botId')]",
"location": "global",
"kind": "bot",
"sku": {
"name": "[parameters('botSku')]"
},
"properties": {
"name": "[parameters('botId')]",
"displayName": "[parameters('botId')]",
"endpoint": "[variables('botEndpoint')]",
"msaAppId": "[parameters('appId')]",
"developerAppInsightsApplicationId": null,
"developerAppInsightKey": null,
"publishingCredentials": null,
"storageResourceId": null
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
]
}
],
"outputs": {}
}
}
}
]
}

Просмотреть файл

@ -0,0 +1,154 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appId": {
"type": "string",
"metadata": {
"description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"."
}
},
"botId": {
"type": "string",
"metadata": {
"description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
}
},
"botSku": {
"defaultValue": "F0",
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The name of the new App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"appServicePlanLocation": {
"type": "string",
"metadata": {
"description": "The location of the App Service Plan."
}
},
"existingAppServicePlan": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "Name of the existing App Service Plan used to create the Web App for the bot."
}
},
"newWebAppName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
}
}
},
"variables": {
"defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]",
"useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]",
"servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]",
"resourcesLocation": "[parameters('appServicePlanLocation')]",
"webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
"siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
"botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
},
"resources": [
{
"comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.",
"type": "Microsoft.Web/serverfarms",
"condition": "[not(variables('useExistingAppServicePlan'))]",
"name": "[variables('servicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('resourcesLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"properties": {
"name": "[variables('servicePlanName')]"
}
},
{
"comments": "Create a Web App using an App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"location": "[variables('resourcesLocation')]",
"kind": "app",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]"
],
"name": "[variables('webAppName')]",
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[variables('servicePlanName')]",
"siteConfig": {
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
},
{
"name": "MicrosoftAppId",
"value": "[parameters('appId')]"
},
{
"name": "MicrosoftAppPassword",
"value": "[parameters('appSecret')]"
}
],
"cors": {
"allowedOrigins": [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net/"
]
}
}
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.BotService/botServices",
"name": "[parameters('botId')]",
"location": "global",
"kind": "bot",
"sku": {
"name": "[parameters('botSku')]"
},
"properties": {
"name": "[parameters('botId')]",
"displayName": "[parameters('botId')]",
"endpoint": "[variables('botEndpoint')]",
"msaAppId": "[parameters('appId')]",
"developerAppInsightsApplicationId": null,
"developerAppInsightKey": null,
"publishingCredentials": null,
"storageResourceId": null
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
]
}
]
}

Просмотреть файл

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\libraries\Adapters\Microsoft.Bot.Builder.Adapters.Facebook\Microsoft.Bot.Builder.Adapters.Facebook.csproj" />
<ProjectReference Include="..\..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
<ProjectReference Include="..\..\..\libraries\Microsoft.Bot.Builder\Microsoft.Bot.Builder.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Resources\adaptive_card.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot", "Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot.csproj", "{29FEF930-5333-41D4-8035-D052F056BD65}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{29FEF930-5333-41D4-8035-D052F056BD65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29FEF930-5333-41D4-8035-D052F056BD65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29FEF930-5333-41D4-8035-D052F056BD65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29FEF930-5333-41D4-8035-D052F056BD65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC808DD8-B0F5-48FC-AE87-05163C7BA6C4}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,22 @@
// 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
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}

Просмотреть файл

@ -0,0 +1,106 @@
# EchoBot using Facebook Adapter
Bot Framework v4 echo bot using facebook Adapter sample.
This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple echo bot that connects with Facebook to respond to messages.
## Prerequisites
- [.NET Core SDK](https://dotnet.microsoft.com/download) version 2.1
```bash
# determine dotnet version
dotnet --version
```
## To try this sample
1 - Clone the repository
```bash
git clone https://github.com/Microsoft/botbuilder-dotnet.git
```
2 - In a terminal, navigate to `test/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.TestBot`
3 - Connect the bot with Facebook by following the instructions below.
4 - Run the bot from a terminal or from Visual Studio, choose option A or B.
A) From a terminal
```bash
# run the bot
dotnet run
```
B) Or from Visual Studio
- Launch Visual Studio
- File -> Open -> Project/Solution
- Navigate to `test/Adapters/Microsoft.Bot.Builder.Adapters.Facebook.TestBot` folder
- Select `Microsoft.Bot.Builder.Adapters.Facebook.TestBot.csproj` file
- Press <kbd>F5</kbd> to run the project
### Connect the bot with Facebook
1 - Create a Facebook Account for Developers (https://developers.facebook.com/).
2 - Create a new App. Give a name to the app and click Create App ID button.
3 - In the Dashboard go to Add a Product and select Messenger by clicking on Set Up button
A) In the Access Tokens section, select a Facebook Page or create a new one. This is the page where the bot will be tested later.
B) After selecting the page, the permissions should be edited, click on the button "Add or Remove Pages", select the page just created as option and continue.
C) A Page Access Token is generated. Copy it, it will be needed to connect the adapter.
4 - Get the app credentials. Go to Settings, Basic and copy the App Secret.
5 - Set the tokens in appsettings.json file:
- VerifyToken (create one. It will be used to validate received messages)
- AppSecret (the one obtained in step 4)
- AccessToken (the one obtained in step 3.c)
6 - Using a tunneling tool like [Ngrok](https://ngrok.com/download), expose the bot's endpoint.
7 - Go back to the Facebook for Developers page and click on Messenger, Settings.
In the Webhooks section, click on Subscribe To Events button.
A) Complete the Callback URL with the ngrok https URL adding '/api/messages'.
Fill in the Verify Token with the one setted on your bot.
Subscribe to the following events: messages, messaging_postbacks, messaging_optins, messaging_deliveries
B) Click Verify and Save button.
8 - Subscribe the webhook to the Page.
9 - Go to the Page and click Add a Button.
A) Select a Send Message button.
B) Select Messenger.
C) And click Finish button.
10 - Finally, click on the button created and test your bot!
## Deploy the bot to Azure
To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions.
## Further reading
- [Bot Framework Documentation](https://docs.botframework.com)
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0)
- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0)
- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x)
- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest)
- [Azure Portal](https://portal.azure.com)
- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/)
- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)

Просмотреть файл

@ -0,0 +1,21 @@
{
"template_type": "button",
"text": "What do you want to do next?",
"buttons": [
{
"type": "web_url",
"url": "https://www.messenger.com",
"title": "Visit Messenger"
},
{
"type": "postback",
"title": "Say Hello",
"payload": "Hello button"
},
{
"type": "postback",
"title": "Say Goodbye",
"payload": "Goodbye button"
}
]
}

Просмотреть файл

@ -0,0 +1,27 @@
{
"template_type": "generic",
"elements": [
{
"title": "Welcome to BotFramework!",
"image_url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU",
"subtitle": "Know more about Bots.",
"default_action": {
"type": "web_url",
"url": "https://dev.botframework.com/",
"webview_height_ratio": "compact"
},
"buttons": [
{
"type": "web_url",
"url": "https://dev.botframework.com/",
"title": "View Website"
},
{
"type": "postback",
"title": "Start Chatting",
"payload": "Chatting"
}
]
}
]
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"template_type": "button",
"text": "You can contact with a live agent.",
"buttons": [
{
"type": "postback",
"title": "Contact",
"payload": "Handover"
}
]
}

Просмотреть файл

@ -0,0 +1,9 @@
{
"template_type": "media",
"elements": [
{
"media_type": "video",
"url": "https://www.facebook.com/MicrosoftLatam/videos/724316691373804/"
}
]
}

Просмотреть файл

@ -0,0 +1,56 @@
// 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
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot.Bots;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Create the Bot Framework Facebook Adapter.
services.AddSingleton<IBotFrameworkHttpAdapter, FacebookAdapter>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, EchoBot>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
// app.UseHttpsRedirection();
app.UseMvc();
}
}
}

Просмотреть файл

@ -0,0 +1,5 @@
{
"FacebookVerifyToken": "",
"FacebookAppSecret": "",
"FacebookAccessToken": ""
}

Просмотреть файл

@ -0,0 +1,420 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Microsoft.Bot.Builder.Facebook.Sample</title>
<style>
body {
margin: 0px;
padding: 0px;
font-family: Segoe UI;
}
html,
body {
height: 100%;
}
header {
background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 4638.9 651.6' style='enable-background:new 0 0 4638.9 651.6;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bfill:%2355A0E0;%7D .st1%7Bfill:none;%7D .st2%7Bfill:%230058A8;%7D .st3%7Bfill:%23328BD8;%7D .st4%7Bfill:%23B6DCF1;%7D .st5%7Bopacity:0.2;fill:url(%23SVGID_1_);enable-background:new ;%7D%0A%3C/style%3E%3Crect y='1.1' class='st0' width='4640' height='646.3'/%3E%3Cpath class='st1' d='M3987.8,323.6L4310.3,1.1h-65.6l-460.1,460.1c-17.5,17.5-46.1,17.5-63.6,0L3260.9,1.1H0v646.3h3660.3 L3889,418.7c17.5-17.5,46.1-17.5,63.6,0l228.7,228.7h66.6l-260.2-260.2C3970.3,369.8,3970.3,341.1,3987.8,323.6z'/%3E%3Cpath class='st2' d='M3784.6,461.2L4244.7,1.1h-983.9l460.1,460.1C3738.4,478.7,3767.1,478.7,3784.6,461.2z'/%3E%3Cpath class='st3' d='M4640,1.1h-329.8l-322.5,322.5c-17.5,17.5-17.5,46.1,0,63.6l260.2,260.2H4640L4640,1.1L4640,1.1z'/%3E%3Cpath class='st4' d='M3889,418.8l-228.7,228.7h521.1l-228.7-228.7C3935.2,401.3,3906.5,401.3,3889,418.8z'/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='3713.7576' y1='438.1175' x2='3911.4084' y2='14.2535' gradientTransform='matrix(1 0 0 -1 0 641.3969)'%3E%3Cstop offset='0' style='stop-color:%23FFFFFF;stop-opacity:0.5'/%3E%3Cstop offset='1' style='stop-color:%23FFFFFF'/%3E%3C/linearGradient%3E%3Cpath class='st5' d='M3952.7,124.5c-17.5-17.5-46.1-17.5-63.6,0l-523,523h1109.6L3952.7,124.5z'/%3E%3C/svg%3E%0A");
background-repeat: no-repeat;
background-size: 100%;
background-position: right;
background-color: #55A0E0;
width: 100%;
font-size: 44px;
height: 120px;
color: white;
padding: 30px 0 40px 0px;
display: inline-block;
}
.header-icon {
background-image: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%09%20viewBox%3D%220%200%20150.2%20125%22%20style%3D%22enable-background%3Anew%200%200%20150.2%20125%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text/css%22%3E%0A%09.st0%7Bfill%3Anone%3B%7D%0A%09.st1%7Bfill%3A%23FFFFFF%3B%7D%0A%3C/style%3E%0A%3Crect%20x%3D%220.5%22%20class%3D%22st0%22%20width%3D%22149.7%22%20height%3D%22125%22/%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M59%2C102.9L21.8%2C66c-3.5-3.5-3.5-9.1%2C0-12.5l37-36.5l2.9%2C3l-37%2C36.4c-1.8%2C1.8-1.8%2C4.7%2C0%2C6.6l37.2%2C37L59%2C102.9z%22%0A%09%09/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M92.5%2C102.9l-3-3l37.2-37c0.9-0.9%2C1.4-2%2C1.4-3.3c0-1.2-0.5-2.4-1.4-3.3L89.5%2C20l2.9-3l37.2%2C36.4%0A%09%09c1.7%2C1.7%2C2.6%2C3.9%2C2.6%2C6.3s-0.9%2C4.6-2.6%2C6.3L92.5%2C102.9z%22/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M90.1%2C68.4c-4.5%2C0-8-3.5-8-8.1c0-4.5%2C3.5-8.1%2C8-8.1c4.4%2C0%2C8%2C3.7%2C8%2C8.1C98.1%2C64.7%2C94.4%2C68.4%2C90.1%2C68.4z%0A%09%09%20M90.1%2C56.5c-2.2%2C0-3.8%2C1.7-3.8%2C3.9c0%2C2.2%2C1.7%2C3.9%2C3.8%2C3.9c1.9%2C0%2C3.8-1.6%2C3.8-3.9S91.9%2C56.5%2C90.1%2C56.5z%22/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M61.4%2C68.4c-4.5%2C0-8-3.5-8-8.1c0-4.5%2C3.5-8.1%2C8-8.1c4.4%2C0%2C8%2C3.7%2C8%2C8.1C69.5%2C64.7%2C65.8%2C68.4%2C61.4%2C68.4z%0A%09%09%20M61.4%2C56.5c-2.2%2C0-3.8%2C1.7-3.8%2C3.9c0%2C2.2%2C1.7%2C3.9%2C3.8%2C3.9c1.9%2C0%2C3.8-1.6%2C3.8-3.9S63.3%2C56.5%2C61.4%2C56.5z%22/%3E%0A%3C/g%3E%0A%3C/svg%3E%0A");
background-repeat: no-repeat;
float: left;
height: 140px;
width: 140px;
display: inline-block;
vertical-align: middle;
}
.header-text {
padding-left: 1%;
color: #FFFFFF;
font-family: "Segoe UI";
font-size: 72px;
font-weight: 300;
letter-spacing: 0.35px;
line-height: 96px;
display: inline-block;
vertical-align: middle;
}
.header-inner-container {
min-width: 480px;
max-width: 1366px;
margin-left: auto;
margin-right: auto;
vertical-align: middle;
}
.header-inner-container::after {
content: "";
clear: both;
display: table;
}
.main-content-area {
padding-left: 30px;
}
.content-title {
color: #000000;
font-family: "Segoe UI";
font-size: 46px;
font-weight: 300;
line-height: 62px;
}
.main-text {
color: #808080;
font-size: 24px;
font-family: "Segoe UI";
font-size: 24px;
font-weight: 200;
line-height: 32px;
}
.main-text-p1 {
padding-top: 48px;
padding-bottom: 28px;
}
.endpoint {
height: 32px;
width: 571px;
color: #808080;
font-family: "Segoe UI";
font-size: 24px;
font-weight: 200;
line-height: 32px;
padding-top: 28px;
}
.how-to-build-section {
padding-top: 20px;
padding-left: 30px;
}
.how-to-build-section>h3 {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.35px;
line-height: 22px;
margin: 0 0 24px 0;
text-transform: uppercase;
}
.step-container {
display: flex;
align-items: stretch;
position: relative;
}
.step-container dl {
border-left: 1px solid #A0A0A0;
display: block;
padding: 0 24px;
margin: 0;
}
.step-container dl>dt::before {
background-color: white;
border: 1px solid #A0A0A0;
border-radius: 100%;
content: '';
left: 47px;
height: 11px;
position: absolute;
width: 11px;
}
.step-container dl>.test-bullet::before {
background-color: blue;
}
.step-container dl>dt {
display: block;
font-size: inherit;
font-weight: bold;
line-height: 20px;
}
.step-container dl>dd {
font-size: inherit;
line-height: 20px;
margin-left: 0;
padding-bottom: 32px;
}
.step-container:last-child dl {
border-left: 1px solid transparent;
}
.ctaLink {
background-color: transparent;
border: 1px solid transparent;
color: #006AB1;
cursor: pointer;
font-weight: 600;
padding: 0;
white-space: normal;
}
.ctaLink:focus {
outline: 1px solid #00bcf2;
}
.ctaLink:hover {
text-decoration: underline;
}
.step-icon {
display: flex;
height: 38px;
margin-right: 15px;
width: 38px;
}
.step-icon>div {
height: 30px;
width: 30px;
background-repeat: no-repeat;
}
.ms-logo-container {
min-width: 580px;
max-width: 980px;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
transition: bottom 400ms;
}
.ms-logo {
float: right;
background-image: url("data:image/svg+xml;utf8,%0A%3Csvg%20version%3D%221.1%22%20id%3D%22MS-symbol%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%09%20viewBox%3D%220%200%20400%20120%22%20style%3D%22enable-background%3Anew%200%200%20400%20120%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text/css%22%3E%0A%09.st0%7Bfill%3Anone%3B%7D%0A%09.st1%7Bfill%3A%23737474%3B%7D%0A%09.st2%7Bfill%3A%23D63F26%3B%7D%0A%09.st3%7Bfill%3A%23167D3E%3B%7D%0A%09.st4%7Bfill%3A%232E76BC%3B%7D%0A%09.st5%7Bfill%3A%23FDB813%3B%7D%0A%3C/style%3E%0A%3Crect%20x%3D%220.6%22%20class%3D%22st0%22%20width%3D%22398.7%22%20height%3D%22119%22/%3E%0A%3Cpath%20class%3D%22st1%22%20d%3D%22M171.3%2C38.4v43.2h-7.5V47.7h-0.1l-13.4%2C33.9h-5l-13.7-33.9h-0.1v33.9h-6.9V38.4h10.8l12.4%2C32h0.2l13.1-32H171.3%0A%09z%20M177.6%2C41.7c0-1.2%2C0.4-2.2%2C1.3-3c0.9-0.8%2C1.9-1.2%2C3.1-1.2c1.3%2C0%2C2.4%2C0.4%2C3.2%2C1.3c0.8%2C0.8%2C1.3%2C1.8%2C1.3%2C3c0%2C1.2-0.4%2C2.2-1.3%2C3%0A%09c-0.9%2C0.8-1.9%2C1.2-3.2%2C1.2s-2.3-0.4-3.1-1.2C178%2C43.8%2C177.6%2C42.8%2C177.6%2C41.7z%20M185.7%2C50.6v31h-7.3v-31H185.7z%20M207.8%2C76.3%0A%09c1.1%2C0%2C2.3-0.3%2C3.6-0.8c1.3-0.5%2C2.5-1.2%2C3.6-2v6.8c-1.2%2C0.7-2.5%2C1.2-4%2C1.5c-1.5%2C0.3-3.1%2C0.5-4.9%2C0.5c-4.6%2C0-8.3-1.4-11.1-4.3%0A%09c-2.9-2.9-4.3-6.6-4.3-11c0-5%2C1.5-9.1%2C4.4-12.3c2.9-3.2%2C7-4.8%2C12.4-4.8c1.4%2C0%2C2.7%2C0.2%2C4.1%2C0.5c1.4%2C0.4%2C2.5%2C0.8%2C3.3%2C1.2v7%0A%09c-1.1-0.8-2.3-1.5-3.4-1.9c-1.2-0.5-2.4-0.7-3.6-0.7c-2.9%2C0-5.2%2C0.9-7%2C2.8c-1.8%2C1.9-2.7%2C4.4-2.7%2C7.6c0%2C3.1%2C0.8%2C5.6%2C2.5%2C7.3%0A%09C202.6%2C75.4%2C204.9%2C76.3%2C207.8%2C76.3z%20M235.7%2C50.1c0.6%2C0%2C1.1%2C0%2C1.6%2C0.1s0.9%2C0.2%2C1.2%2C0.3v7.4c-0.4-0.3-0.9-0.5-1.7-0.8%0A%09c-0.7-0.3-1.6-0.4-2.7-0.4c-1.8%2C0-3.3%2C0.8-4.5%2C2.3c-1.2%2C1.5-1.9%2C3.8-1.9%2C7v15.6h-7.3v-31h7.3v4.9h0.1c0.7-1.7%2C1.7-3%2C3-4%0A%09C232.2%2C50.6%2C233.8%2C50.1%2C235.7%2C50.1z%20M238.9%2C66.6c0-5.1%2C1.4-9.2%2C4.3-12.2c2.9-3%2C6.9-4.5%2C12.1-4.5c4.8%2C0%2C8.6%2C1.4%2C11.3%2C4.3%0A%09c2.7%2C2.9%2C4.1%2C6.8%2C4.1%2C11.7c0%2C5-1.4%2C9-4.3%2C12c-2.9%2C3-6.8%2C4.5-11.8%2C4.5c-4.8%2C0-8.6-1.4-11.4-4.2C240.3%2C75.3%2C238.9%2C71.4%2C238.9%2C66.6z%0A%09%20M246.5%2C66.3c0%2C3.2%2C0.7%2C5.7%2C2.2%2C7.4c1.5%2C1.7%2C3.6%2C2.6%2C6.3%2C2.6c2.7%2C0%2C4.7-0.9%2C6.1-2.6c1.4-1.7%2C2.1-4.2%2C2.1-7.6c0-3.3-0.7-5.8-2.2-7.5%0A%09c-1.4-1.7-3.4-2.5-6-2.5c-2.7%2C0-4.7%2C0.9-6.2%2C2.7C247.2%2C60.5%2C246.5%2C63%2C246.5%2C66.3z%20M281.5%2C58.8c0%2C1%2C0.3%2C1.9%2C1%2C2.5%0A%09c0.7%2C0.6%2C2.1%2C1.3%2C4.4%2C2.2c2.9%2C1.2%2C5%2C2.5%2C6.1%2C3.9c1.2%2C1.5%2C1.8%2C3.2%2C1.8%2C5.3c0%2C2.9-1.1%2C5.3-3.4%2C7c-2.2%2C1.8-5.3%2C2.7-9.1%2C2.7%0A%09c-1.3%2C0-2.7-0.2-4.3-0.5c-1.6-0.3-2.9-0.7-4-1.2v-7.2c1.3%2C0.9%2C2.8%2C1.7%2C4.3%2C2.2c1.5%2C0.5%2C2.9%2C0.8%2C4.2%2C0.8c1.6%2C0%2C2.9-0.2%2C3.6-0.7%0A%09c0.8-0.5%2C1.2-1.2%2C1.2-2.3c0-1-0.4-1.9-1.2-2.5c-0.8-0.7-2.4-1.5-4.6-2.4c-2.7-1.1-4.6-2.4-5.7-3.8c-1.1-1.4-1.7-3.2-1.7-5.4%0A%09c0-2.8%2C1.1-5.1%2C3.3-6.9c2.2-1.8%2C5.1-2.7%2C8.6-2.7c1.1%2C0%2C2.3%2C0.1%2C3.6%2C0.4c1.3%2C0.2%2C2.5%2C0.6%2C3.4%2C0.9v6.9c-1-0.6-2.1-1.2-3.4-1.7%0A%09c-1.3-0.5-2.6-0.7-3.8-0.7c-1.4%2C0-2.5%2C0.3-3.2%2C0.8C281.9%2C57.1%2C281.5%2C57.8%2C281.5%2C58.8z%20M297.9%2C66.6c0-5.1%2C1.4-9.2%2C4.3-12.2%0A%09c2.9-3%2C6.9-4.5%2C12.1-4.5c4.8%2C0%2C8.6%2C1.4%2C11.3%2C4.3c2.7%2C2.9%2C4.1%2C6.8%2C4.1%2C11.7c0%2C5-1.4%2C9-4.3%2C12c-2.9%2C3-6.8%2C4.5-11.8%2C4.5%0A%09c-4.8%2C0-8.6-1.4-11.4-4.2C299.4%2C75.3%2C297.9%2C71.4%2C297.9%2C66.6z%20M305.5%2C66.3c0%2C3.2%2C0.7%2C5.7%2C2.2%2C7.4c1.5%2C1.7%2C3.6%2C2.6%2C6.3%2C2.6%0A%09c2.7%2C0%2C4.7-0.9%2C6.1-2.6c1.4-1.7%2C2.1-4.2%2C2.1-7.6c0-3.3-0.7-5.8-2.2-7.5c-1.4-1.7-3.4-2.5-6-2.5c-2.7%2C0-4.7%2C0.9-6.2%2C2.7%0A%09C306.3%2C60.5%2C305.5%2C63%2C305.5%2C66.3z%20M353.9%2C56.6h-10.9v25h-7.4v-25h-5.2v-6h5.2v-4.3c0-3.3%2C1.1-5.9%2C3.2-8c2.1-2.1%2C4.8-3.1%2C8.1-3.1%0A%09c0.9%2C0%2C1.7%2C0%2C2.4%2C0.1c0.7%2C0.1%2C1.3%2C0.2%2C1.8%2C0.4V42c-0.2-0.1-0.7-0.3-1.3-0.5c-0.6-0.2-1.3-0.3-2.1-0.3c-1.5%2C0-2.7%2C0.5-3.5%2C1.4%0A%09s-1.2%2C2.4-1.2%2C4.2v3.7h10.9v-7l7.3-2.2v9.2h7.4v6h-7.4v14.5c0%2C1.9%2C0.3%2C3.3%2C1%2C4c0.7%2C0.8%2C1.8%2C1.2%2C3.3%2C1.2c0.4%2C0%2C0.9-0.1%2C1.5-0.3%0A%09c0.6-0.2%2C1.1-0.4%2C1.6-0.7v6c-0.5%2C0.3-1.2%2C0.5-2.3%2C0.7c-1.1%2C0.2-2.1%2C0.3-3.2%2C0.3c-3.1%2C0-5.4-0.8-6.9-2.5c-1.5-1.6-2.3-4.1-2.3-7.4%0A%09V56.6z%22/%3E%0A%3Cg%3E%0A%09%3Crect%20x%3D%2231%22%20y%3D%2224%22%20class%3D%22st2%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2268.8%22%20y%3D%2224%22%20class%3D%22st3%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2231%22%20y%3D%2261.8%22%20class%3D%22st4%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2268.8%22%20y%3D%2261.8%22%20class%3D%22st5%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%3C/g%3E%0A%3C/svg%3E%0A");
}
.ms-logo-container>div {
min-height: 60px;
width: 150px;
background-repeat: no-repeat;
}
.row {
padding: 90px 0px 0 20px;
min-width: 480px;
max-width: 1366px;
margin-left: auto;
margin-right: auto;
}
.column {
float: left;
width: 45%;
padding-right: 20px;
}
.row:after {
content: "";
display: table;
clear: both;
}
a {
text-decoration: none;
}
.download-the-emulator {
height: 20px;
color: #0063B1;
font-size: 15px;
line-height: 20px;
padding-bottom: 70px;
}
.how-to-iframe {
max-width: 700px !important;
min-width: 650px !important;
height: 700px !important;
}
.remove-frame-height {
height: 10px;
}
@media only screen and (max-width: 1300px) {
.ms-logo {
padding-top: 30px;
}
.header-text {
font-size: 40x;
}
.column {
float: none;
padding-top: 30px;
width: 100%;
}
.ms-logo-container {
padding-top: 30px;
min-width: 480px;
max-width: 650px;
margin-left: auto;
margin-right: auto;
}
.row {
padding: 20px 0px 0 20px;
min-width: 480px;
max-width: 650px;
margin-left: auto;
margin-right: auto;
}
}
@media only screen and (max-width: 1370px) {
header {
background-color: #55A0E0;
background-size: auto 200px;
}
}
@media only screen and (max-width: 1230px) {
header {
background-color: #55A0E0;
background-size: auto 200px;
}
.header-text {
font-size: 44px;
}
.header-icon {
height: 120px;
width: 120px;
}
}
@media only screen and (max-width: 1000px) {
header {
background-color: #55A0E0;
background-image: none;
}
}
@media only screen and (max-width: 632px) {
.header-text {
font-size: 32px;
}
.row {
padding: 10px 0px 0 10px;
max-width: 490px !important;
min-width: 410px !important;
}
.endpoint {
font-size: 25px;
}
.main-text {
font-size: 20px;
}
.step-container dl>dd {
font-size: 14px;
}
.column {
padding-right: 5px;
}
.header-icon {
height: 110px;
width: 110px;
}
.how-to-iframe {
max-width: 480px !important;
min-width: 400px !important;
height: 650px !important;
overflow: hidden;
}
}
.remove-frame-height {
max-height: 10px;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
loadFrame();
});
var loadFrame = function () {
var iframe = document.createElement('iframe');
iframe.setAttribute("id", "iframe");
var offLineHTMLContent = "";
var frameElement = document.getElementById("how-to-iframe");
if (window.navigator.onLine) {
iframe.src = 'https://docs.botframework.com/static/abs/pages/f5.htm';
iframe.setAttribute("scrolling", "no");
iframe.setAttribute("frameborder", "0");
iframe.setAttribute("width", "100%");
iframe.setAttribute("height", "100%");
var frameDiv = document.getElementById("how-to-iframe");
frameDiv.appendChild(iframe);
} else {
frameElement.classList.add("remove-frame-height");
}
};
</script>
</head>
<body>
<header class="header">
<div class="header-inner-container">
<div class="header-icon" style="display: inline-block"></div>
<div class="header-text" style="display: inline-block">Microsoft.Bot.Builder.Facebook.Sample Bot</div>
</div>
</header>
<div class="row">
<div class="column" class="main-content-area">
<div class="content-title">Your bot is ready!</div>
<div class="main-text main-text-p1">You can test your bot in the Bot Framework Emulator<br />
by connecting to http://localhost:3978/api/messages.</div>
<div class="main-text download-the-emulator"><a class="ctaLink"
href="https://aka.ms/bot-framework-F5-download-emulator" target="_blank">Download the Emulator</a>
</div>
<div class="main-text">Visit <a class="ctaLink" href="https://aka.ms/bot-framework-F5-abs-home"
target="_blank">Azure
Bot Service</a> to register your bot and add it to<br />
various channels. The bot's endpoint URL typically looks
like this:</div>
<div class="endpoint">https://<i>your_bots_hostname</i>/api/messages</div>
</div>
<div class="column how-to-iframe" id="how-to-iframe"></div>
</div>
<div class="ms-logo-container">
<div class="ms-logo"></div>
</div>
</body>
</html>

Просмотреть файл

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents.Handover;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
@ -14,6 +15,8 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.TestBot.Bots
{
public class EchoBot : ActivityHandler
{
private const string PageInboxId = "263902037430900";
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var activity = MessageFactory.Text("Hello and Welcome!");
@ -22,7 +25,19 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.TestBot.Bots
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Attachments != null)
if (turnContext.Activity.GetChannelData<FacebookMessage>().IsStandby)
{
if ((turnContext.Activity as Activity)?.Text == "Little")
{
var activity = MessageFactory.Text("Primary Bot taking back control...");
activity.Type = ActivityTypes.Event;
//Action
(activity as IEventActivity).Name = "take_thread_control";
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
else if (turnContext.Activity.Attachments != null)
{
foreach (var attachment in turnContext.Activity.Attachments)
{
@ -60,6 +75,38 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.TestBot.Bots
case "Chatting":
activity = MessageFactory.Text("Hello! How can I help you?");
break;
case "handover template":
activity = MessageFactory.Attachment(CreateTemplateAttachment(Directory.GetCurrentDirectory() + @"/Resources/HandoverTemplatePayload.json"));
break;
case "Handover":
activity = MessageFactory.Text("Redirecting...");
activity.Type = ActivityTypes.Event;
(activity as IEventActivity).Name = "pass_thread_control";
(activity as IEventActivity).Value = "inbox";
break;
case "bot template":
activity = MessageFactory.Attachment(CreateTemplateAttachment(Directory.GetCurrentDirectory() + @"/Resources/HandoverBotsTemplatePayload.json"));
break;
case "SecondaryBot":
activity = MessageFactory.Text("Redirecting to the secondary bot...");
activity.Type = ActivityTypes.Event;
//Action
(activity as IEventActivity).Name = "pass_thread_control";
//AppId
(activity as IEventActivity).Value = "<SECOND RECEIVER APP ID>";
break;
case "TakeControl":
activity = MessageFactory.Text("Primary Bot Taking control...");
activity.Type = ActivityTypes.Event;
//Action
(activity as IEventActivity).Name = "take_thread_control";
break;
case "OtherBot":
activity = MessageFactory.Text($"Secondary bot is requesting me the thread control. Passing thread control!");
break;
default:
activity = MessageFactory.Text($"Echo: {turnContext.Activity.Text}");
break;
@ -73,11 +120,34 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.TestBot.Bots
{
if (turnContext.Activity.Value != null)
{
var inputs = (Dictionary<string, string>)turnContext.Activity.Value;
var name = inputs["Name"];
var metadata = (turnContext.Activity.Value as FacebookThreadControl).Metadata;
var activity = MessageFactory.Text($"How are you doing {name}?");
await turnContext.SendActivityAsync(activity, cancellationToken);
if (metadata == null)
{
var requester = (turnContext.Activity.Value as FacebookRequestThreadControl).RequestedOwnerAppId;
if (requester == PageInboxId)
{
var activity = MessageFactory.Text($"The Inbox is requesting me the thread control. Passing thread control!");
activity.Type = ActivityTypes.Event;
(activity as IEventActivity).Name = "pass_thread_control";
(activity as IEventActivity).Value = "inbox";
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
else if (metadata.Equals("Request thread control to the primary receiver"))
{
var activity = new Activity();
activity.Type = ActivityTypes.Event;
(activity as IEventActivity).Name = "pass_thread_control";
(activity as IEventActivity).Value = "<APP ID>";
await turnContext.SendActivityAsync(activity, cancellationToken);
}
else if (metadata.Equals("Pass thread control") || metadata.Equals("Pass thread control from Page Inbox"))
{
var activity = MessageFactory.Text("Hello Again Human, I'm the bot");
await turnContext.SendActivityAsync(activity, cancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"template_type": "button",
"text": "You can contact with a secondary bot.",
"buttons": [
{
"type": "postback",
"title": "Contact",
"payload": "SecondaryBot"
}
]
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"template_type": "button",
"text": "You can contact with a live agent.",
"buttons": [
{
"type": "postback",
"title": "Contact",
"payload": "Handover"
}
]
}

Просмотреть файл

@ -226,24 +226,101 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.Tests
}
[Fact]
public async void SendActivitiesAsyncShouldFailWithActivityTypeNotMessage()
public async void SendActivitiesAsyncShouldPostToFacebookOnPassThreadControl()
{
var facebookAdapter = new FacebookAdapter(new Mock<FacebookClientWrapper>(_testOptions).Object);
const string testResponse = "Test Response";
var facebookClientWrapper = new Mock<FacebookClientWrapper>(_testOptions);
var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object);
var activity = new Activity
{
Type = ActivityTypes.Event,
Text = "Test text",
Name = "pass_thread_control",
Conversation = new ConversationAccount()
{
Id = "Test id",
},
ChannelData = new FacebookMessage("recipientId", new Message(), "messagingtype"),
};
Activity[] activities = { activity };
ResourceResponse[] responses = null;
facebookClientWrapper.Setup(api => api.SendMessageAsync(It.IsAny<string>(), It.IsAny<FacebookMessage>(), It.IsAny<HttpMethod>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(testResponse));
facebookClientWrapper.Setup(api => api.PassThreadControlAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(true));
using (var turnContext = new TurnContext(facebookAdapter, activity))
{
await Assert.ThrowsAsync<Exception>(async () =>
{
await facebookAdapter.SendActivitiesAsync(turnContext, activities, default);
});
responses = await facebookAdapter.SendActivitiesAsync(turnContext, activities, default);
}
Assert.Equal(testResponse, responses[0].Id);
facebookClientWrapper.Verify(api => api.PassThreadControlAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
[Fact]
public async void SendActivitiesAsyncShouldPostToFacebookOnTakeThreadControl()
{
const string testResponse = "Test Response";
var facebookClientWrapper = new Mock<FacebookClientWrapper>(_testOptions);
var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object);
var activity = new Activity
{
Type = ActivityTypes.Event,
Text = "Test text",
Name = "take_thread_control",
Conversation = new ConversationAccount()
{
Id = "Test id",
},
ChannelData = new FacebookMessage("recipientId", new Message(), "messagingtype"),
};
Activity[] activities = { activity };
ResourceResponse[] responses = null;
facebookClientWrapper.Setup(api => api.SendMessageAsync(It.IsAny<string>(), It.IsAny<FacebookMessage>(), It.IsAny<HttpMethod>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(testResponse));
facebookClientWrapper.Setup(api => api.TakeThreadControlAsync(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(true));
using (var turnContext = new TurnContext(facebookAdapter, activity))
{
responses = await facebookAdapter.SendActivitiesAsync(turnContext, activities, default);
}
Assert.Equal(testResponse, responses[0].Id);
facebookClientWrapper.Verify(api => api.TakeThreadControlAsync(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
[Fact]
public async void SendActivitiesAsyncShouldPostToFacebookOnRequestThreadControl()
{
const string testResponse = "Test Response";
var facebookClientWrapper = new Mock<FacebookClientWrapper>(_testOptions);
var facebookAdapter = new FacebookAdapter(facebookClientWrapper.Object);
var activity = new Activity
{
Type = ActivityTypes.Event,
Text = "Test text",
Name = "request_thread_control",
Conversation = new ConversationAccount()
{
Id = "Test id",
},
ChannelData = new FacebookMessage("recipientId", new Message(), "messagingtype"),
};
Activity[] activities = { activity };
ResourceResponse[] responses = null;
facebookClientWrapper.Setup(api => api.SendMessageAsync(It.IsAny<string>(), It.IsAny<FacebookMessage>(), It.IsAny<HttpMethod>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(testResponse));
facebookClientWrapper.Setup(api => api.RequestThreadControlAsync(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(true));
using (var turnContext = new TurnContext(facebookAdapter, activity))
{
responses = await facebookAdapter.SendActivitiesAsync(turnContext, activities, default);
}
Assert.Equal(testResponse, responses[0].Id);
facebookClientWrapper.Verify(api => api.RequestThreadControlAsync(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
[Fact]
public async Task UpdateActivityAsyncShouldThrowNotImplementedException()
{

Просмотреть файл

@ -9,6 +9,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Builder.Adapters.Facebook.FacebookEvents;
using Microsoft.Bot.Schema;
using Moq;
using Newtonsoft.Json;
@ -102,6 +103,57 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.Tests
Assert.Equal(facebookMessage.PostBack.Payload, activity.Text);
}
[Fact]
public void ProcessSingleMessageShouldReturnActivityWithPassThreadControlRequest()
{
var facebookMessageJson = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/PayloadWithPassThreadControl.json");
var facebookResponse = JsonConvert.DeserializeObject<FacebookResponseEvent>(facebookMessageJson);
var payload = new List<FacebookMessage>();
payload = facebookResponse.Entry[0].Messaging;
var activity = FacebookHelper.ProcessSingleMessage(payload[0]);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Recipient.Id, activity.Recipient.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Sender.Id, activity.Conversation.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Sender.Id, activity.Conversation.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].PassThreadControl, activity.Value);
}
[Fact]
public void ProcessSingleMessageShouldReturnActivityWithRequestThreadControlRequest()
{
var facebookMessageJson = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/PayloadWithRequestThreadControl.json");
var facebookResponse = JsonConvert.DeserializeObject<FacebookResponseEvent>(facebookMessageJson);
var payload = new List<FacebookMessage>();
payload = facebookResponse.Entry[0].Messaging;
var activity = FacebookHelper.ProcessSingleMessage(payload[0]);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Recipient.Id, activity.Recipient.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Sender.Id, activity.Conversation.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Sender.Id, activity.Conversation.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].RequestThreadControl, activity.Value);
}
[Fact]
public void ProcessSingleMessageShouldReturnActivityWithTakeThreadControlRequest()
{
var facebookMessageJson = File.ReadAllText(Directory.GetCurrentDirectory() + @"/Files/PayloadWithTakeThreadControl.json");
var facebookResponse = JsonConvert.DeserializeObject<FacebookResponseEvent>(facebookMessageJson);
var payload = new List<FacebookMessage>();
payload = facebookResponse.Entry[0].Messaging;
var activity = FacebookHelper.ProcessSingleMessage(payload[0]);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Recipient.Id, activity.Recipient.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Sender.Id, activity.Conversation.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].Sender.Id, activity.Conversation.Id);
Assert.Equal(facebookResponse.Entry[0].Messaging[0].TakeThreadControl, activity.Value);
}
[Fact]
public async Task WriteAsyncShouldFailWithNullResponse()
{

Просмотреть файл

@ -160,5 +160,53 @@ namespace Microsoft.Bot.Builder.Adapters.Facebook.Tests
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.VerifyWebhookAsync(httpRequest.Object, null, default); });
}
[Fact]
public async void PassThreadControlAsyncShouldThrowExceptionWithNullTargetAppId()
{
var facebookClientWrapper = new FacebookClientWrapper(_testOptions);
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.PassThreadControlAsync(null, "fakeUserId", "Test Pass Thread Control"); });
}
[Fact]
public async void PassThreadControlAsyncShouldThrowExceptionWithNullUserId()
{
var facebookClientWrapper = new FacebookClientWrapper(_testOptions);
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.PassThreadControlAsync("fakeAppId", null, "Test Pass Thread Control"); });
}
[Fact]
public async void RequestThreadControlAsyncShouldThrowExceptionWithNullUserId()
{
var facebookClientWrapper = new FacebookClientWrapper(_testOptions);
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.RequestThreadControlAsync(null, "Test Pass Thread Control"); });
}
[Fact]
public async void TakeThreadControlAsyncShouldThrowExceptionWithNullUserId()
{
var facebookClientWrapper = new FacebookClientWrapper(_testOptions);
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.TakeThreadControlAsync(null, "Test Pass Thread Control"); });
}
[Fact]
public async void PostToFacebookApiAsyncShouldThrowExceptionWithNullPostType()
{
var facebookClientWrapper = new FacebookClientWrapper(_testOptions);
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.PostToFacebookApiAsync(null, "fakeContent"); });
}
[Fact]
public async void PostToFacebookApiAsyncShouldThrowExceptionWithNullContent()
{
var facebookClientWrapper = new FacebookClientWrapper(_testOptions);
await Assert.ThrowsAsync<ArgumentNullException>(async () => { await facebookClientWrapper.PostToFacebookApiAsync("fakePostType", null); });
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
{
"object": "page",
"entry": [
{
"id": "fakeId",
"time": 1572630578783,
"messaging": [
{
"recipient": {
"id": "fakeId"
},
"timestamp": 1572630578783,
"sender": {
"id": "fakeId"
},
"pass_thread_control": {
"new_owner_app_id": 1234567890,
"metadata": "Pass thread control"
}
}
]
}
]
}

Просмотреть файл

@ -0,0 +1,24 @@
{
"object": "page",
"entry": [
{
"id": "fakeId",
"time": 1572630401588,
"messaging": [
{
"recipient": {
"id": "fakeId"
},
"timestamp": 1572630401588,
"sender": {
"id": "fakeId"
},
"request_thread_control": {
"requested_owner_app_id": 1234567890,
"metadata": null
}
}
]
}
]
}

Просмотреть файл

@ -0,0 +1,24 @@
{
"object": "page",
"entry": [
{
"id": "fakeId",
"time": 1572630386105,
"messaging": [
{
"recipient": {
"id": "fakeId"
},
"timestamp": 1572630386105,
"sender": {
"id": "fakeId"
},
"take_thread_control": {
"previous_owner_app_id": 1234567890,
"metadata": "Take thread control from a secondary receiver"
}
}
]
}
]
}

Просмотреть файл

@ -33,6 +33,15 @@
<None Update="Files\Payload.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\PayloadWithRequestThreadControl.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\PayloadWithTakeThreadControl.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\PayloadWithPassThreadControl.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\RequestResponse.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>