Merge pull request #2 from dotnetcore/master

Build-in Tencent Wechat channel.
This commit is contained in:
geffzhang 2018-11-05 12:14:09 +08:00 committed by GitHub
commit 621c8e2ef5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1237 additions and 13 deletions

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="ViewModels\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
</ItemGroup>
</Project>

View file

@ -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<AgentIntegration>().FirstOrDefault(x => x.AgentId == agentId && x.Platform == "Facebook Messenger");
return config.VerifyToken == token ? Ok(challenge) : Ok(agentId);
}
return BadRequest();
}
[HttpPost("{agentId}")]
public async Task<ActionResult> 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<WebhookEvent>(json);
}
body.Entry.ForEach(entry =>
{
entry.Messaging.ForEach(msg =>
{
// received text message
if (msg.Message.ContainsKey("text"))
{
OnTextMessaged(agentId, new WebhookMessage<WebhookTextMessage>
{
Sender = msg.Sender,
Recipient = msg.Recipient,
Timestamp = msg.Timestamp,
Message = msg.Message.ToObject<WebhookTextMessage>()
});
}
});
});
return Ok();
}
private void OnTextMessaged(string agentId, WebhookMessage<WebhookTextMessage> 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<AgentIntegration>().FirstOrDefault(x => x.AgentId == agentId && x.Platform == "Facebook Messenger");
SendTextMessage(config.AccessToken, new WebhookMessage<WebhookTextMessage>
{
Recipient = message.Sender.ToObject<WebhookMessageRecipient>(),
Message = new WebhookTextMessage
{
Text = String.IsNullOrEmpty(aiResponse.Result.Fulfillment.Speech) ? aiResponse.Result.Action : aiResponse.Result.Fulfillment.Speech
}
});*/
}
private void SendTextMessage(string accessToken, WebhookMessage<WebhookTextMessage> 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);
}
}
}

View file

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Channel.FacebookMessenger
{
public interface IWebhookMessageBody
{
}
}

View file

@ -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<WebhookEventEntry> Entry { get; set; }
}
}

View file

@ -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<WebhookMessage> Messaging { get; set; }
}
}

View file

@ -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<TWebhookMessage> where TWebhookMessage : IWebhookMessageBody
{
public WebhookMessageSender Sender { get; set; }
public WebhookMessageRecipient Recipient { get; set; }
public long Timestamp { get; set; }
public TWebhookMessage Message { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Channel.FacebookMessenger.Models
{
public class WebhookMessageRecipient
{
/// <summary>
/// PAGE_ID
/// </summary>
public String Id { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Channel.FacebookMessenger.Models
{
public class WebhookMessageSender
{
/// <summary>
/// PSID
/// </summary>
public String Id { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -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> senparcSetting, IOptions<SenparcWeixinSetting> senparcWeixinSetting*/)
{
}
}
}

View file

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Haiping Chen</Authors>
<RepositoryUrl>https://github.com/Oceania2018/botsharp-channel-weixin</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/Oceania2018/botsharp-channel-weixin</PackageProjectUrl>
<Copyright>Apache 2.0</Copyright>
<PackageTags>botsharp, wechat, wexin, chatbot</PackageTags>
<AssemblyVersion>0.1.1.0</AssemblyVersion>
<FileVersion>0.1.1.0</FileVersion>
<Version>0.1.1</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.3" />
<PackageReference Include="Senparc.Weixin.MP.MVC" Version="7.1.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,142 @@
using BotSharp.Channel.Weixin.Models;
using BotSharp.Platform.Abstraction;
using BotSharp.Platform.Dialogflow;
using BotSharp.Platform.Dialogflow.Models;
using BotSharp.Platform.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Senparc.CO2NET;
using Senparc.CO2NET.HttpUtility;
using Senparc.Weixin.MP;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MvcExtension;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Channel.Weixin.Controllers
{
/// <summary>
/// 此Controller为异步ControllerAction使用异步线程处理并发请求。
/// 为了方便演示此Controller中没有加入多余的日志记录等示例保持了最简单的Controller写法。日志等其他操作可以参考WeixinController.cs。
/// 提示异步Controller并不是在任何情况下都能提升效率响应时间当请求量非常小的时候反而会增加一定的开销。
/// </summary>
[Route("weixin")]
public class WeixinAsyncController : ControllerBase
{
readonly Func<string> _getRandomFileName = () => DateTime.Now.ToString("yyyyMMdd-HHmmss") + "_Async_" + Guid.NewGuid().ToString("n").Substring(0, 6);
readonly IConfiguration config;
private IPlatformBuilder<AgentModel> builder;
public WeixinAsyncController(IPlatformBuilder<AgentModel> platform, IConfiguration configuration)
{
config = configuration;
builder = platform;
}
[HttpGet]
public Task<ActionResult> Get(string signature, string timestamp, string nonce, string echostr)
{
var token = config.GetValue<string>("weixinChannel:token");
return Task.Factory.StartNew(() =>
{
if (CheckSignature.Check(signature, timestamp, nonce, token))
{
return echostr; //返回随机字符串则表示验证通过
}
else
{
return "failed:" + signature + "," + Senparc.Weixin.MP.CheckSignature.GetSignature(timestamp, nonce, token) + "。" +
"如果你在浏览器中看到这句话说明此地址可以被作为微信公众账号后台的Url请注意保持Token一致。";
}
}).ContinueWith<ActionResult>(task => Content(task.Result));
}
public CustomMessageHandler MessageHandler = null;//开放出MessageHandler是为了做单元测试实际使用过程中不需要
/// <summary>
/// 最简化的处理流程
/// </summary>
[HttpPost]
public async Task<ActionResult> Post(PostModel postModel)
{
var token = config.GetValue<string>("weixinChannel:token");
if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, token))
{
return new WeixinResult("参数错误!");
}
postModel.Token = token;
postModel.EncodingAESKey = config.GetValue<string>("weixinChannel:encodingAESKey");
postModel.AppId = config.GetValue<string>("weixinChannel:appId");
var messageHandler = new CustomMessageHandler(builder, config, Request.GetRequestMemoryStream(), postModel, 10);
messageHandler.DefaultMessageHandlerAsyncEvent = Senparc.NeuChar.MessageHandlers.DefaultMessageHandlerAsyncEvent.SelfSynicMethod;//没有重写的异步方法将默认尝试调用同步方法中的代码(为了偷懒)
#region
/* OmitRepeatedMessage功能SDK会自动处理
* 2-5RequestMessage*/
messageHandler.OmitRepeatedMessage = true;//默认已经开启此处仅作为演示也可以设置为false在本次请求中停用此功能
#endregion
#region Request
var logPath = Server.GetMapPath(string.Format("~/App_Data/MP/{0}/", DateTime.Now.ToString("yyyy-MM-dd")));
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
//测试时可开启此记录帮助跟踪数据使用前请确保App_Data文件夹存在且有读写权限。
messageHandler.RequestDocument.Save(Path.Combine(logPath, string.Format("{0}_Request_{1}_{2}.txt", _getRandomFileName(),
messageHandler.RequestMessage.FromUserName,
messageHandler.RequestMessage.MsgType)));
if (messageHandler.UsingEcryptMessage)
{
messageHandler.EcryptRequestDocument.Save(Path.Combine(logPath, string.Format("{0}_Request_Ecrypt_{1}_{2}.txt", _getRandomFileName(),
messageHandler.RequestMessage.FromUserName,
messageHandler.RequestMessage.MsgType)));
}
#endregion
await messageHandler.ExecuteAsync(); //执行微信处理过程
#region Response
//测试时可开启,帮助跟踪数据
//if (messageHandler.ResponseDocument == null)
//{
// throw new Exception(messageHandler.RequestDocument.ToString());
//}
if (messageHandler.ResponseDocument != null)
{
messageHandler.ResponseDocument.Save(Path.Combine(logPath, string.Format("{0}_Response_{1}_{2}.txt", _getRandomFileName(),
messageHandler.ResponseMessage.ToUserName,
messageHandler.ResponseMessage.MsgType)));
}
if (messageHandler.UsingEcryptMessage && messageHandler.FinalResponseDocument != null)
{
//记录加密后的响应信息
messageHandler.FinalResponseDocument.Save(Path.Combine(logPath, string.Format("{0}_Response_Final_{1}_{2}.txt", _getRandomFileName(),
messageHandler.ResponseMessage.ToUserName,
messageHandler.ResponseMessage.MsgType)));
}
#endregion
MessageHandler = messageHandler;//开放出MessageHandler是为了做单元测试实际使用过程中不需要
return new FixWeixinBugWeixinResult(messageHandler);
}
}
}

View file

@ -0,0 +1,94 @@
/*----------------------------------------------------------------
Copyright (C) 2018 Senparc
LocationService.cs
Senparc - 20150312
----------------------------------------------------------------*/
using System.Collections.Generic;
using Senparc.Weixin.MP.Entities;
using Senparc.CO2NET.Helpers.BaiduMap;
using Senparc.CO2NET.Helpers.GoogleMap;
using Senparc.Weixin.MP.Helpers;
using Senparc.CO2NET.Helpers;
using Senparc.NeuChar.Entities;
namespace BotSharp.Channel.Weixin
{
public class LocationService
{
public ResponseMessageNews GetResponseMessage(RequestMessageLocation requestMessage)
{
var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageNews>(requestMessage);
#region
{
var markersList = new List<BaiduMarkers>();
markersList.Add(new BaiduMarkers()
{
Longitude = requestMessage.Location_X,
Latitude = requestMessage.Location_Y,
Color = "red",
Label = "S",
Size = BaiduMarkerSize.m
});
var mapUrl = BaiduMapHelper.GetBaiduStaticMap(requestMessage.Location_X, requestMessage.Location_Y, 1, 6, markersList);
responseMessage.Articles.Add(new Article()
{
Description = string.Format("【来自百度地图】您刚才发送了地理位置信息。Location_X{0}Location_Y{1}Scale{2},标签:{3}",
requestMessage.Location_X, requestMessage.Location_Y,
requestMessage.Scale, requestMessage.Label),
PicUrl = mapUrl,
Title = "定位地点周边地图",
Url = mapUrl
});
}
#endregion
#region GoogleMap
{
var markersList = new List<GoogleMapMarkers>();
markersList.Add(new GoogleMapMarkers()
{
X = requestMessage.Location_X,
Y = requestMessage.Location_Y,
Color = "red",
Label = "S",
Size = GoogleMapMarkerSize.Default,
});
var mapSize = "480x600";
var mapUrl = GoogleMapHelper.GetGoogleStaticMap(19 /*requestMessage.Scale*//*微信和GoogleMap的Scale不一致这里建议使用固定值*/,
markersList, mapSize);
responseMessage.Articles.Add(new Article()
{
Description = string.Format("【来自GoogleMap】您刚才发送了地理位置信息。Location_X{0}Location_Y{1}Scale{2},标签:{3}",
requestMessage.Location_X, requestMessage.Location_Y,
requestMessage.Scale, requestMessage.Label),
PicUrl = mapUrl,
Title = "定位地点周边地图",
Url = mapUrl
});
}
#endregion
responseMessage.Articles.Add(new Article()
{
Title = "微信公众平台SDK 官网链接",
Description = "Senparc.Weixin.MK SDK地址",
PicUrl = "http://sdk.weixin.senparc.com/images/logo.jpg",
Url = "http://sdk.weixin.senparc.com"
});
return responseMessage;
}
}
}

View file

@ -0,0 +1,51 @@
/*----------------------------------------------------------------
Copyright (C) 2018 Senparc
CustomMessageContext.cs
Senparc - 20150312
----------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Senparc.NeuChar.Context;
using Senparc.NeuChar.Entities;
namespace BotSharp.Channel.Weixin.Models
{
public class CustomMessageContext : MessageContext<IRequestMessageBase, IResponseMessageBase>
{
public CustomMessageContext()
{
base.MessageContextRemoved += CustomMessageContext_MessageContextRemoved;
}
/// <summary>
/// 当上下文过期,被移除时触发的时间
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void CustomMessageContext_MessageContextRemoved(object sender, Senparc.NeuChar.Context.WeixinContextRemovedEventArgs<IRequestMessageBase, IResponseMessageBase> e)
{
/* 线
* WeixinContext中的算法
*/
var messageContext = e.MessageContext as CustomMessageContext;
if (messageContext == null)
{
return;//如果是正常的调用messageContext不会为null
}
//TODO:这里根据需要执行消息过期时候的逻辑,下面的代码仅供参考
//Log.InfoFormat("{0}的消息上下文已过期",e.OpenId);
//api.SendMessage(e.OpenId, "由于长时间未搭理客服,您的客服状态已退出!");
}
}
}

View file

@ -0,0 +1,380 @@
/*----------------------------------------------------------------
Copyright (C) 2018 Senparc
CustomMessageHandler.cs
MessageHandler
Senparc - 20150312
Senparc - 20171027
v14.8.3 OnUnknownTypeRequest()Demo
----------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Senparc.Weixin.MP.Agent;
using Senparc.NeuChar.Context;
using Senparc.Weixin.Exceptions;
using Senparc.Weixin.Helpers;
using Senparc.Weixin.MP.Entities;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MessageHandlers;
using Senparc.Weixin.MP.Helpers;
using System.Xml.Linq;
using Senparc.Weixin.MP.AdvancedAPIs;
using System.Threading.Tasks;
using Senparc.NeuChar.Entities.Request;
using Senparc.CO2NET.Helpers;
using Senparc.NeuChar.Helpers;
using Senparc.NeuChar.Entities;
using Senparc.Weixin;
using Senparc.Weixin.MP;
using BotSharp.Platform.Models.AiRequest;
using BotSharp.Platform.Abstraction;
using BotSharp.Platform.Dialogflow.Models;
using Microsoft.Extensions.Configuration;
#if NET45
using System.Web;
using System.Configuration;
using System.Web.Configuration;
using Senparc.Weixin.MP.Sample.CommonService.Utilities;
#else
using Microsoft.AspNetCore.Http;
#endif
namespace BotSharp.Channel.Weixin.Models
{
/// <summary>
/// 自定义MessageHandler
/// 把MessageHandler作为基类重写对应请求的处理方法
/// </summary>
public partial class CustomMessageHandler : MessageHandler<CustomMessageContext>
{
/*
* v1.5MessageHandler提供了一个DefaultResponseMessage的抽象方法
* DefaultResponseMessage必须在子类中重写
* OnXX的抽象方法已经都改为虚方法DefaultResponseMessage方法中的结果
*/
#if !DEBUG || NETSTANDARD1_6 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1
string agentUrl = "http://localhost:12222/App/Weixin/4";
string agentToken = "27C455F496044A87";
string wiweihiKey = "CNadjJuWzyX5bz5Gn+/XoyqiqMa5DjXQ";
#else
//下面的Url和Token可以用其他平台的消息或者到www.weiweihi.com注册微信用户将自动在“微信营销工具”下得到
private string agentUrl = Config.SenparcWeixinSetting.AgentUrl;//这里使用了www.weiweihi.com微信自动托管平台
private string agentToken = Config.SenparcWeixinSetting.AgentToken;//Token
private string wiweihiKey = Config.SenparcWeixinSetting.SenparcWechatAgentKey;//WeiweihiKey专门用于对接www.Weiweihi.com平台获取方式见http://www.weiweihi.com/ApiDocuments/Item/25#51
#endif
#if NET45
private string appId = Config.SenparcWeixinSetting.WeixinAppId;
private string appSecret = Config.SenparcWeixinSetting.WeixinAppSecret;
#else
private string appId = "appId";
private string appSecret = "appSecret";
#endif
/// <summary>
/// 模板消息集合KeycheckCodeValueOpenId
/// </summary>
public static Dictionary<string, string> TemplateMessageCollection = new Dictionary<string, string>();
private IPlatformBuilder<AgentModel> nluPlatform = null;
private readonly IConfiguration config;
public CustomMessageHandler(IPlatformBuilder<AgentModel> nluPlatform, IConfiguration configuration, Stream inputStream, PostModel postModel, int maxRecordCount = 0)
: base(inputStream, postModel, maxRecordCount)
{
this.nluPlatform = nluPlatform;
this.config = configuration;
//这里设置仅用于测试,实际开发可以在外部更全局的地方设置,
//比如MessageHandler<MessageContext>.GlobalGlobalMessageContext.ExpireMinutes = 3。
GlobalMessageContext.ExpireMinutes = 3;
if (!string.IsNullOrEmpty(postModel.AppId))
{
appId = postModel.AppId;//通过第三方开放平台发送过来的请求
}
//在指定条件下,不使用消息去重
base.OmitRepeatedMessageFunc = requestMessage =>
{
var textRequestMessage = requestMessage as RequestMessageText;
if (textRequestMessage != null && textRequestMessage.Content == "容错")
{
return false;
}
return true;
};
}
public override void OnExecuting()
{
//测试MessageContext.StorageData
if (CurrentMessageContext.StorageData == null)
{
CurrentMessageContext.StorageData = 0;
}
base.OnExecuting();
}
public override void OnExecuted()
{
base.OnExecuted();
CurrentMessageContext.StorageData = ((int)CurrentMessageContext.StorageData) + 1;
}
/// <summary>
/// 处理文字请求
/// </summary>
/// <returns></returns>
public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
{
var defaultResponseMessage = base.CreateResponseMessage<ResponseMessageText>();
var requestHandler =
requestMessage.StartHandler()
.Keyword("BotSharp",() =>
{
var result = new StringBuilder();
result.AppendFormat("{0}", "你好欢迎使用BotSharp NLU服务BotSharp通过强大的自然语言理解技术让你更轻松的处理智能回复。");
defaultResponseMessage.Content = result.ToString();
return defaultResponseMessage;
})
.Keyword("再见", () =>
{
var result = new StringBuilder();
result.AppendFormat("{0}", "谢谢使用,再见!");
defaultResponseMessage.Content = result.ToString();
return defaultResponseMessage;
})
.Default(() =>
{
var result = new StringBuilder();
// send text to BotSharp platform emulator
var aIResponse = nluPlatform.TextRequest<AIResponseResult>(new AiRequest
{
Text = requestMessage.Content,
AgentId = config.GetValue<string>("weixinChannel:agentId"),
SessionId = requestMessage.FromUserName
});
result.AppendFormat("{0}", aIResponse.Result.Fulfillment.Speech);
defaultResponseMessage.Content = result.ToString();
return defaultResponseMessage;
});
return requestHandler.GetResponseMessage() as IResponseMessageBase;
}
/// <summary>
/// 处理位置请求
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnLocationRequest(RequestMessageLocation requestMessage)
{
var locationService = new LocationService();
var responseMessage = locationService.GetResponseMessage(requestMessage as RequestMessageLocation);
return responseMessage;
}
public override IResponseMessageBase OnShortVideoRequest(RequestMessageShortVideo requestMessage)
{
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "您刚才发送的是小视频";
return responseMessage;
}
/// <summary>
/// 处理图片请求
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnImageRequest(RequestMessageImage requestMessage)
{
//一隔一返回News或Image格式
if (base.GlobalMessageContext.GetMessageContext(requestMessage).RequestMessages.Count() % 2 == 0)
{
var responseMessage = CreateResponseMessage<ResponseMessageNews>();
responseMessage.Articles.Add(new Article()
{
Title = "您刚才发送了图片信息",
Description = "您发送的图片将会显示在边上",
PicUrl = requestMessage.PicUrl,
Url = "http://sdk.weixin.senparc.com"
});
responseMessage.Articles.Add(new Article()
{
Title = "第二条",
Description = "第二条带连接的内容",
PicUrl = requestMessage.PicUrl,
Url = "http://sdk.weixin.senparc.com"
});
return responseMessage;
}
else
{
var responseMessage = CreateResponseMessage<ResponseMessageImage>();
responseMessage.Image.MediaId = requestMessage.MediaId;
return responseMessage;
}
}
/// <summary>
/// 处理语音请求
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnVoiceRequest(RequestMessageVoice requestMessage)
{
var responseMessage = CreateResponseMessage<ResponseMessageMusic>();
//上传缩略图
//var accessToken = Containers.AccessTokenContainer.TryGetAccessToken(appId, appSecret);
var uploadResult = Senparc.Weixin.MP.AdvancedAPIs.MediaApi.UploadTemporaryMedia(appId, UploadMediaFileType.image,
Server.GetMapPath("~/Images/Logo.jpg"));
//设置音乐信息
responseMessage.Music.Title = "天籁之音";
responseMessage.Music.Description = "播放您上传的语音";
responseMessage.Music.MusicUrl = "http://sdk.weixin.senparc.com/Media/GetVoice?mediaId=" + requestMessage.MediaId;
responseMessage.Music.HQMusicUrl = "http://sdk.weixin.senparc.com/Media/GetVoice?mediaId=" + requestMessage.MediaId;
responseMessage.Music.ThumbMediaId = uploadResult.media_id;
//推送一条客服消息
try
{
CustomApi.SendText(appId, WeixinOpenId, "本次上传的音频MediaId" + requestMessage.MediaId);
}
catch
{
}
return responseMessage;
}
/// <summary>
/// 处理视频请求
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnVideoRequest(RequestMessageVideo requestMessage)
{
var responseMessage = CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "您发送了一条视频信息ID" + requestMessage.MediaId;
#region
Task.Factory.StartNew(async () =>
{
//上传素材
var dir = Server.GetMapPath("~/App_Data/TempVideo/");
var file = await MediaApi.GetAsync(appId, requestMessage.MediaId, dir);
var uploadResult = await MediaApi.UploadTemporaryMediaAsync(appId, UploadMediaFileType.video, file, 50000);
await CustomApi.SendVideoAsync(appId, base.WeixinOpenId, uploadResult.media_id, "这是您刚才发送的视频", "这是一条视频消息");
}).ContinueWith(async task =>
{
if (task.Exception != null)
{
WeixinTrace.Log("OnVideoRequest()储存Video过程发生错误", task.Exception.Message);
var msg = string.Format("上传素材出错:{0}\r\n{1}",
task.Exception.Message,
task.Exception.InnerException != null
? task.Exception.InnerException.Message
: null);
await CustomApi.SendTextAsync(appId, base.WeixinOpenId, msg);
}
});
#endregion
return responseMessage;
}
/// <summary>
/// 处理链接消息请求
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnLinkRequest(RequestMessageLink requestMessage)
{
var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage);
responseMessage.Content = string.Format(@"您发送了一条连接信息:
Title{0}
Description:{1}
Url:{2}", requestMessage.Title, requestMessage.Description, requestMessage.Url);
return responseMessage;
}
public override IResponseMessageBase OnFileRequest(RequestMessageFile requestMessage)
{
var responseMessage = requestMessage.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = string.Format(@"您发送了一个文件:
{0}
:{1}
{2}
MD5:{3}", requestMessage.Title, requestMessage.Description, requestMessage.FileTotalLen, requestMessage.FileMd5);
return responseMessage;
}
/// <summary>
/// 处理事件请求这个方法一般不用重写这里仅作为示例出现。除非需要在判断具体Event类型以外对Event信息进行统一操作
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEventRequest(IRequestMessageEventBase requestMessage)
{
var eventResponseMessage = base.OnEventRequest(requestMessage);//对于Event下属分类的重写方法CustomerMessageHandler_Events.cs
//TODO: 对Event信息进行统一操作
return eventResponseMessage;
}
public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
{
/*
* 使
*
* var responseMessage = MessageAgent.RequestResponseMessage(agentUrl, agentToken, RequestDocument.ToString());
* return responseMessage;
*/
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "这条消息来自DefaultResponseMessage。";
return responseMessage;
}
public override IResponseMessageBase OnUnknownTypeRequest(RequestMessageUnknownType requestMessage)
{
/*
* SDK没有提供的消息类型
* XML可以通过requestMessage.RequestDocumentthis.RequestDocument
* v14.8.3
*/
var msgType = Senparc.NeuChar.Helpers.MsgTypeHelper.GetRequestMsgTypeString(requestMessage.RequestDocument);
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "未知消息类型:" + msgType;
WeixinTrace.SendCustomLog("未知请求消息类型", requestMessage.RequestDocument.ToString());//记录到日志中
return responseMessage;
}
}
}

View file

@ -0,0 +1,32 @@
using BotSharp.Core.Modules;
using BotSharp.Platform.Abstraction;
using BotSharp.Platform.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET;
using Senparc.CO2NET.RegisterServices;
using Senparc.Weixin.RegisterServices;
using System;
namespace BotSharp.Channel.Weixin
{
public class ModuleInjector : IModule
{
public void ConfigureServices(IServiceCollection services, IConfiguration config)
{
// Senparc.CO2NET Senparc.Weixin
services.AddSenparcGlobalServices(config)
.AddSenparcWeixinServices(config);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env/*, IOptions<SenparcSetting> senparcSetting, IOptions<SenparcWeixinSetting> senparcWeixinSetting*/)
{
//https://github.com/Senparc/Senparc.CO2NET/blob/master/Sample/Senparc.CO2NET.Sample.netcore/Startup.cs
/* IRegisterService register = RegisterService.Start(env, senparcSetting.Value)
.UseSenparcGlobal();*/
}
}
}

View file

@ -0,0 +1,47 @@
# botsharp-channel-weixin
A channel module of BotSharp for Tencent Weixin
### How to install through NuGet
```
PM> Install-Package BotSharp.Channel.Weixin
```
### How to run locally
```
git clone https://github.com/dotnetcore/BotSharp
```
Check app.json to use DialogflowAi
```
{
"version": "0.1.0",
"assemblies": "BotSharp.Core",
"platformModuleName": "DialogflowAi"
}
```
Update channels.weixin.json to set the corresponding KEY
```
{
"weixinChannel": {
"token": "botsharp",
"encodingAESKey": "",
"appId": "",
"agentId": "60bee6f9-ba58-4fe8-8b95-94af69d6fd41"
}
}
```
F5 run BotSharp.WebHost
Access http://localhost:3112
Import demo (Spotify.zip) agent located at App_Data
Train agent (id: 60bee6f9-ba58-4fe8-8b95-94af69d6fd41)
Or refer [BotSharp docs](https://botsharp.readthedocs.io) to design your new chatbot.
Setup Wechat webhood from https://mp.weixin.qq.com/.

View file

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
#if NET45
using System.Web;
#else
using Microsoft.AspNetCore.Http;
#endif
namespace BotSharp.Channel.Weixin
{
public static class Server
{
private static string _appDomainAppPath;
public static string AppDomainAppPath
{
get
{
if (_appDomainAppPath == null)
{
#if NET45
_appDomainAppPath = HttpRuntime.AppDomainAppPath;
#else
_appDomainAppPath = AppContext.BaseDirectory; //dll所在目录;
#endif
}
return _appDomainAppPath;
}
set
{
_appDomainAppPath = value;
#if NETSTANDARD1_6 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1
if (!_appDomainAppPath.EndsWith("/"))
{
_appDomainAppPath += "/";
}
#endif
}
}
private static string _webRootPath;
/// <summary>
/// wwwroot文件夹目录专供ASP.NET Core MVC使用
/// </summary>
public static string WebRootPath
{
get
{
if (_webRootPath == null)
{
#if NET45
_webRootPath = AppDomainAppPath;
#else
_webRootPath = AppDomainAppPath + "wwwroot/";//asp.net core的wwwroot文件目录结构不一样
#endif
}
return _webRootPath;
}
set { _webRootPath = value; }
}
public static string GetMapPath(string virtualPath)
{
if (virtualPath == null)
{
return "";
}
else if (virtualPath.StartsWith("~/"))
{
return virtualPath.Replace("~/", AppDomainAppPath);
}
else
{
return Path.Combine(AppDomainAppPath, virtualPath);
}
}
public static HttpContext HttpContext
{
get
{
#if NET45
HttpContext context = HttpContext.Current;
if (context == null)
{
HttpRequest request = new HttpRequest("Default.aspx", "http://sdk.weixin.senparc.com/default.aspx", null);
StringWriter sw = new StringWriter();
HttpResponse response = new HttpResponse(sw);
context = new HttpContext(request, response);
}
#else
HttpContext context = new DefaultHttpContext();
#endif
return context;
}
}
}
}

View file

@ -23,3 +23,7 @@ var aIResponse = nluPlatform.TextRequest<AIResponseResult>(new AiRequest
});
```
### How to run locally
```
git clone https://github.com/dotnetcore/BotSharp
```

View file

@ -84,6 +84,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BotSharp.Channel.FacebookMessenger\BotSharp.Channel.FacebookMessenger.csproj" />
<ProjectReference Include="..\BotSharp.Channel.Weixin\BotSharp.Channel.Weixin.csproj" />
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
</ItemGroup>

View file

@ -1,5 +1,5 @@
{
"version": "0.1.0",
"version": "0.2.0",
"assemblies": "BotSharp.Core",
"platformModuleName": "DialogflowAi",
@ -9,6 +9,14 @@
{
"Name": "DialogflowAi",
"Type": "BotSharp.Platform.Dialogflow"
},
{
"Name": "WeixinChannel",
"Type": "BotSharp.Channel.Weixin"
},
{
"Name": "FacebookMessengerChannel",
"Type": "BotSharp.Channel.FacebookMessenger"
}
]
}

View file

@ -2,6 +2,7 @@
"weixinChannel": {
"token": "botsharp",
"encodingAESKey": "",
"appId": ""
"appId": "",
"agentId": ""
}
}

View file

@ -19,6 +19,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Platform.Models",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Platform.Dialogflow", "BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj", "{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}"
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
@ -179,6 +183,54 @@ Global
{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}.Test|Any CPU.Build.0 = Debug|Any CPU
{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}.Test|x64.ActiveCfg = Debug|Any CPU
{8C82E35D-DC94-45C9-90FD-1FF62E75CD9B}.Test|x64.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|Any CPU.ActiveCfg = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|Any CPU.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|x64.ActiveCfg = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.ARTICULATE|x64.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|x64.ActiveCfg = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Debug|x64.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|Any CPU.ActiveCfg = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|Any CPU.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|x64.ActiveCfg = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.DIALOGFLOW|x64.Build.0 = Debug|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|Any CPU.ActiveCfg = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|Any CPU.Build.0 = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|x64.ActiveCfg = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.RASA|x64.Build.0 = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|Any CPU.Build.0 = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|x64.ActiveCfg = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Release|x64.Build.0 = Release|Any CPU
{D93BD270-10A6-4EFB-938D-508FD2EAC1E0}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{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

View file

@ -59,23 +59,23 @@ Quick Started
You can use docker compose to run BotSharp quickly, make sure you've got `Docker`_ installed.
::
PS D:\> git clone https://github.com/Oceania2018/BotSharp
PS D:\> git clone https://github.com/dotnetcore/BotSharp
PS D:\> cd BotSharp
PS D:\BotSharp\> docker-compose -f dockerfiles/docker-compose-articulateui.yml up
PS D:\BotSharp\> docker-compose -f dockerfiles/docker-compose-core.yml up
Point your web browser at http://localhost:**** and enjoy BotSharp with Articulate-UI.
Point your web browser at http://localhost:3112 and enjoy BotSharp Core.
Extension Libraries
-----------------
BotSharp uses component design, the kernel is kept to a minimum, and business functions are implemented by external components. The modular design also allows contributors to better participate.
* BotSharp platform emulator extension which is compatible with RASA NLU. `botsharp-rasa`_
* BotSharp platform emulator extension which is compatible with Google Dialogflow. `botsharp-dialogflow`_
* BotSharp platform emulator extension which is compatible with Google Dialogflow.
* BotSharp platform emulator extension which is compatible with Articulate AI. `botsharp-articulate`_
* BotSharp platform emulator extension which is compatible with RasaTalk. `botsharp-rasatalk`_
* A channel module of BotSharp for Facebook Messenger. `botsharp-channel-fbmessenger`_
* A channel module of BotSharp for Tencent Wechat. `botsharp-channel-weixin`_
* A channel module of BotSharp for Telegram. `botsharp-channel-telegram`_
* A channel module of BotSharp for Facebook Messenger.
* A channel module of BotSharp for Tencent Wechat.
* A channel module of BotSharp for Telegram.
* Articulate UI customized for BotSharp NLU. `articulate-ui`_
Documents
@ -102,10 +102,6 @@ Scan to join group in Wechat
.. _license: https://raw.githubusercontent.com/Oceania2018/BotSharp/master/LICENSE
.. _botsharpnuget: https://www.nuget.org/packages/BotSharp.Core
.. _botsharp-rasa: https://github.com/Oceania2018/botsharp-rasa
.. _botsharp-dialogflow: https://github.com/Oceania2018/botsharp-dialogflow
.. _botsharp-articulate: https://github.com/Oceania2018/botsharp-articulate
.. _botsharp-rasatalk: https://github.com/Obrain2016/botsharp-rasatalk
.. _botsharp-channel-fbmessenger: https://github.com/Oceania2018/botsharp-channel-fbmessenger
.. _botsharp-channel-weixin: https://github.com/Oceania2018/botsharp-channel-weixin
.. _botsharp-channel-telegram: https://github.com/Oceania2018/botsharp-channel-telegram
.. _articulate-ui: https://github.com/Oceania2018/articulate-ui

View file

@ -0,0 +1,9 @@
version: '3.0'
services:
botsharp:
image: botsharpdocker/botsharp-core:0.1.0
ports: ['0.0.0.0:3112:3112']
networks: ['botsharp-network']
networks: {botsharp-network: {}}