From 4958da5c21b5a3e98fafc69cbd9ccafbe8b9e466 Mon Sep 17 00:00:00 2001 From: Oceania2018 Date: Fri, 2 Nov 2018 07:53:27 -0500 Subject: [PATCH] Build-in Facebook Messenger channel. --- .../BotSharp.Channel.FacebookMessenger.csproj | 19 +++ .../FacebookMessengerController.cs | 114 ++++++++++++++++++ .../IWebhookMessageBody.cs | 10 ++ .../Models/WebhookEvent.cs | 14 +++ .../Models/WebhookEventEntry.cs | 13 ++ .../Models/WebhookMessage.cs | 29 +++++ .../Models/WebhookMessageQuickReply.cs | 11 ++ .../Models/WebhookMessageRecipient.cs | 14 +++ .../Models/WebhookMessageSender.cs | 14 +++ .../Models/WebhookTextMessage.cs | 15 +++ .../ModuleInjector.cs | 22 ++++ BotSharp.WebHost/BotSharp.WebHost.csproj | 1 + BotSharp.WebHost/Settings/app.json | 6 +- BotSharp.sln | 26 ++++ 14 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj create mode 100644 BotSharp.Channel.FacebookMessenger/Controllers/FacebookMessengerController.cs create mode 100644 BotSharp.Channel.FacebookMessenger/IWebhookMessageBody.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookEvent.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookEventEntry.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookMessage.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookMessageQuickReply.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookMessageRecipient.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookMessageSender.cs create mode 100644 BotSharp.Channel.FacebookMessenger/Models/WebhookTextMessage.cs create mode 100644 BotSharp.Channel.FacebookMessenger/ModuleInjector.cs diff --git a/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj b/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj new file mode 100644 index 00000000..1add5556 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + diff --git a/BotSharp.Channel.FacebookMessenger/Controllers/FacebookMessengerController.cs b/BotSharp.Channel.FacebookMessenger/Controllers/FacebookMessengerController.cs new file mode 100644 index 00000000..458a84ba --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Controllers/FacebookMessengerController.cs @@ -0,0 +1,114 @@ +using BotSharp.Channel.FacebookMessenger.Models; +using BotSharp.Platform.Models; +using EntityFrameworkCore.BootKit; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using RestSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Channel.FacebookMessenger.Controllers +{ + [Route("v1/[controller]")] + public class FacebookMessengerController : ControllerBase + { + [HttpGet("{agentId}")] + public ActionResult Verify([FromRoute] string agentId) + { + var mode = Request.Query.ContainsKey("hub.mode") ? Request.Query["hub.mode"].ToString() : String.Empty; + var token = Request.Query.ContainsKey("hub.verify_token") ? Request.Query["hub.verify_token"].ToString() : String.Empty; + var challenge = Request.Query.ContainsKey("hub.challenge") ? Request.Query["hub.challenge"].ToString() : String.Empty; + + if (mode == "subscribe") + { + var dc = new DefaultDataContextLoader().GetDefaultDc(); + var config = dc.Table().FirstOrDefault(x => x.AgentId == agentId && x.Platform == "Facebook Messenger"); + + return config.VerifyToken == token ? Ok(challenge) : Ok(agentId); + } + + return BadRequest(); + } + + [HttpPost("{agentId}")] + public async Task CallbackAsync([FromRoute] string agentId) + { + WebhookEvent body; + IWebhookMessageBody response = null; + + using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) + { + var json = await reader.ReadToEndAsync(); + body = JsonConvert.DeserializeObject(json); + } + + body.Entry.ForEach(entry => + { + entry.Messaging.ForEach(msg => + { + // received text message + if (msg.Message.ContainsKey("text")) + { + OnTextMessaged(agentId, new WebhookMessage + { + Sender = msg.Sender, + Recipient = msg.Recipient, + Timestamp = msg.Timestamp, + Message = msg.Message.ToObject() + }); + } + }); + + }); + + return Ok(); + } + + private void OnTextMessaged(string agentId, WebhookMessage message) + { + Console.WriteLine($"OnTextMessaged: {message.Message.Text}"); + + /*var ai = new ApiAi(); + var agent = ai.LoadAgent(agentId); + ai.AiConfig = new AIConfiguration(agent.ClientAccessToken, SupportedLanguage.English) { AgentId = agentId }; + ai.AiConfig.SessionId = message.Sender.Id; + var aiResponse = ai.TextRequest(new AIRequest { Query = new String[] { message.Message.Text } }); + + var dc = new DefaultDataContextLoader().GetDefaultDc(); + var config = dc.Table().FirstOrDefault(x => x.AgentId == agentId && x.Platform == "Facebook Messenger"); + + SendTextMessage(config.AccessToken, new WebhookMessage + { + Recipient = message.Sender.ToObject(), + Message = new WebhookTextMessage + { + Text = String.IsNullOrEmpty(aiResponse.Result.Fulfillment.Speech) ? aiResponse.Result.Action : aiResponse.Result.Fulfillment.Speech + } + });*/ + } + + private void SendTextMessage(string accessToken, WebhookMessage body) + { + var client = new RestClient("https://graph.facebook.com"); + + var rest = new RestRequest("v2.6/me/messages", Method.POST); + string json = JsonConvert.SerializeObject(body, + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + NullValueHandling = NullValueHandling.Ignore + }); + + rest.AddParameter("application/json", json, ParameterType.RequestBody); + rest.AddQueryParameter("access_token", accessToken); + + var response = client.Execute(rest); + } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/IWebhookMessageBody.cs b/BotSharp.Channel.FacebookMessenger/IWebhookMessageBody.cs new file mode 100644 index 00000000..19d72ace --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/IWebhookMessageBody.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger +{ + public interface IWebhookMessageBody + { + } +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookEvent.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookEvent.cs new file mode 100644 index 00000000..d4ba1d72 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookEvent.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookEvent + { + public string Object { get; set; } + public List Entry { get; set; } + } + + +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookEventEntry.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookEventEntry.cs new file mode 100644 index 00000000..4889fb97 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookEventEntry.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookEventEntry + { + public string Id { get; set; } + public long Time { get; set; } + public List Messaging { get; set; } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookMessage.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessage.cs new file mode 100644 index 00000000..e06642f7 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessage.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookMessage + { + public WebhookMessageSender Sender { get; set; } + + public WebhookMessageRecipient Recipient { get; set; } + + public long Timestamp { get; set; } + + public JObject Message { get; set; } + } + + public class WebhookMessage where TWebhookMessage : IWebhookMessageBody + { + public WebhookMessageSender Sender { get; set; } + + public WebhookMessageRecipient Recipient { get; set; } + + public long Timestamp { get; set; } + + public TWebhookMessage Message { get; set; } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageQuickReply.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageQuickReply.cs new file mode 100644 index 00000000..b67f9a23 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageQuickReply.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookMessageQuickReply + { + public String Payload { get; set; } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageRecipient.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageRecipient.cs new file mode 100644 index 00000000..cba0f51a --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageRecipient.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookMessageRecipient + { + /// + /// PAGE_ID + /// + public String Id { get; set; } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageSender.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageSender.cs new file mode 100644 index 00000000..888aadfa --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookMessageSender.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookMessageSender + { + /// + /// PSID + /// + public String Id { get; set; } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/Models/WebhookTextMessage.cs b/BotSharp.Channel.FacebookMessenger/Models/WebhookTextMessage.cs new file mode 100644 index 00000000..16d0fa73 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/Models/WebhookTextMessage.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Channel.FacebookMessenger.Models +{ + public class WebhookTextMessage : IWebhookMessageBody + { + public string Mid { get; set; } + public String Text { get; set; } + [JsonProperty("quick_reply")] + public WebhookMessageQuickReply QuickReply { get; set; } + } +} diff --git a/BotSharp.Channel.FacebookMessenger/ModuleInjector.cs b/BotSharp.Channel.FacebookMessenger/ModuleInjector.cs new file mode 100644 index 00000000..b0d23723 --- /dev/null +++ b/BotSharp.Channel.FacebookMessenger/ModuleInjector.cs @@ -0,0 +1,22 @@ +using BotSharp.Core.Modules; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace BotSharp.Channel.FacebookMessenger +{ + public class ModuleInjector : IModule + { + public void ConfigureServices(IServiceCollection services, IConfiguration config) + { + + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env/*, IOptions senparcSetting, IOptions senparcWeixinSetting*/) + { + + } + } +} diff --git a/BotSharp.WebHost/BotSharp.WebHost.csproj b/BotSharp.WebHost/BotSharp.WebHost.csproj index b4981ca6..9cb8cb19 100644 --- a/BotSharp.WebHost/BotSharp.WebHost.csproj +++ b/BotSharp.WebHost/BotSharp.WebHost.csproj @@ -84,6 +84,7 @@ + diff --git a/BotSharp.WebHost/Settings/app.json b/BotSharp.WebHost/Settings/app.json index 6ae24b88..486cfadc 100644 --- a/BotSharp.WebHost/Settings/app.json +++ b/BotSharp.WebHost/Settings/app.json @@ -1,5 +1,5 @@ { - "version": "0.1.0", + "version": "0.2.0", "assemblies": "BotSharp.Core", "platformModuleName": "DialogflowAi", @@ -13,6 +13,10 @@ { "Name": "WeixinChannel", "Type": "BotSharp.Channel.Weixin" + }, + { + "Name": "FacebookMessengerChannel", + "Type": "BotSharp.Channel.FacebookMessenger" } ] } diff --git a/BotSharp.sln b/BotSharp.sln index 98c3c7d6..c803e238 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Platform.Dialogflo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Channel.Weixin", "BotSharp.Channel.Weixin\BotSharp.Channel.Weixin.csproj", "{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Channel.FacebookMessenger", "BotSharp.Channel.FacebookMessenger\BotSharp.Channel.FacebookMessenger.csproj", "{22C52A04-581B-4186-8C04-0CD359FA568A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution ARTICULATE|Any CPU = ARTICULATE|Any CPU @@ -205,6 +207,30 @@ Global {D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|Any CPU.Build.0 = Debug|Any CPU {D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|x64.ActiveCfg = Debug|Any CPU {D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|x64.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.ARTICULATE|Any CPU.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.ARTICULATE|Any CPU.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.ARTICULATE|x64.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.ARTICULATE|x64.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Debug|x64.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Debug|x64.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.DIALOGFLOW|Any CPU.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.DIALOGFLOW|Any CPU.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.DIALOGFLOW|x64.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.DIALOGFLOW|x64.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.RASA|Any CPU.ActiveCfg = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.RASA|Any CPU.Build.0 = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.RASA|x64.ActiveCfg = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.RASA|x64.Build.0 = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Release|Any CPU.Build.0 = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Release|x64.ActiveCfg = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Release|x64.Build.0 = Release|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Test|Any CPU.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Test|Any CPU.Build.0 = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Test|x64.ActiveCfg = Debug|Any CPU + {22C52A04-581B-4186-8C04-0CD359FA568A}.Test|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE