有没有办法在 ConversationUpdate 事件上启动表单流对话框?
Is there a way to start formflow dialog on ConversationUpdate event?
最终目标是:
botduilder sdk 女士,Slack 的聊天机器人。
当用户被添加到与 bot 的群聊时(ActivityTypes.ConversationUpdate 和 activity.MembersAdded.Count != 0),我想立即开始通过私人消息使用 formflow 对话框从他那里收集数据。但是从现在开始我找不到办法做到这一点,看来您已经需要从用户那里收到一些消息了。
这是否正确,唯一的解决方法是要求用户先输入一些文本(或按钮 "lets start")?
我还通过主动示例解析对话框堆栈尝试了此解决方案:
else if (activity.Type == ActivityTypes.ConversationUpdate)
{
if (activity.MembersAdded.Count != 0)
{
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
//This is our dialog stack
var stack = scope.Resolve<IDialogStack>();
//interrupt the stack. This means that we're stopping whatever conversation that is currently happening with the user
//Then adding this stack to run and once it's finished, we will be back to the original conversation
var questions = new WelcomePoll();
var myform = new FormDialog<WelcomePoll>(questions, WelcomePoll.BuildForm, FormOptions.PromptInStart, null);
stack.Call(myform, Resume); //GOT "STACK IS EMPTY" EXCEPTION
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
}
}
但是 System.InvalidOperationException: 'Stack is empty' 在 stack.Call 行。也许我需要先创建堆栈,但我找不到正确的方法。
谢谢你。
免责声明:我的回答比你的问题更笼统。请参阅答案末尾从 ConversationUpdate 启动对话
在 Microsoft Bot Framework 中可以发送“真正的”主动消息,但并非在每个渠道中都可以。
重要的一点:当我说“真正的主动消息”时,它与 Microsoft 在 documentation 中调用的 proactive message
有所区别(它从机器人发送消息但过去已经与之交谈过的人):这里我说的是向以前从未与机器人交谈过的客户发送消息。
发送主动消息
发送主动消息的主要困难是知道您将设置的 2 ChannelAccount(= 用户,即您的机器人和最终用户)的 ID在对话对象中。
那些 ChannelAccount 对象获得了一个 ID,该 ID 取决于您当前使用的频道。例如,根据我之前在 Bot Framework 上的工作分析:
- 对于 Skype,bot ID 是“28:”及其 Microsoft AppId 的串联,但您需要知道用户 ID 并且它们之间的对话必须已经存在 => 您不能真正主动发送消息
- 对于 Facebook messager,bot ID 是 FB 页面 ID,但用户 ID 是您不能轻易请求的特定 messenger ID => 复杂,必须熟悉 Facebook APIs
- 对于 Slack,机器人 ID 是其 Slack 的 Bot_id、一个“:”和 Slack 的 Team_id 的串联。用户 ID 是其 Slack 的 ID、“:”和 Slack 的 Team_id => interesting
的串联
- 对于 Twilio SMS,机器人 ID 和用户 ID 是它们的 phone 号码 => 简单
- 对于电子邮件渠道,这是他们的电子邮件
- ...
如果您想知道在获得这些 ID 后如何开始对话,请跳到最后的部分。
Slack ID 焦点
Slack 的优点是可以通过仅了解一些信息并请求他们 API 来获取开始对话所需的所有参数。例如,您可以通过只知道以下内容来开始对话:
- Bot Slack 的用户名
- 用户电子邮件地址或 Slack 的用户名
然后你可以使用 Slack 的 API 来获得机器人真正需要的东西。
如何获取Slack数据?
1 种调用服务的方法,1 种使用它获取正确数据的方法:
public class SlackService
{
internal static async Task<SlackMembersRootObject> GetSlackUsersList(string slackApiToken, CancellationToken ct)
{
using (var client = new HttpClient())
{
using (var response = await client.GetAsync($"https://slack.com/api/users.list?token=" + $"{slackApiToken}", ct).ConfigureAwait(false))
{
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var rootObject = Newtonsoft.Json.JsonConvert.DeserializeObject<SlackMembersRootObject>(jsonString);
return rootObject;
}
}
}
}
public static async Task StartSlackDirectMessage(string msAppId, string msAppPassword, string slackApiToken, string message, string botSlackUsername, string botName, string destEmailAddress, string destName, string lang, CancellationToken ct)
{
// Getting Slack user (and bot) list
var slackUsersList = await SlackService.GetSlackUsersList(slackApiToken, CancellationToken.None);
// Get Bot SlackId by searching its "username", you can also search by other criteria
var slackBotUser = slackUsersList.Members.FirstOrDefault(x => x.Name == botSlackUsername);
if (slackBotUser == null)
{
throw new Exception($"Slack API : no bot found for name '{botSlackUsername}'");
}
// Get User SlackId by email address
var slackTargetedUser = slackUsersList.Members.FirstOrDefault(x => x.Profile.Email == destEmailAddress);
if (slackTargetedUser != null)
{
// if found, starting the conversation by passing the right IDs
await StartDirectMessage(msAppId, msAppPassword, "https://slack.botframework.com/", "slack", $"{slackBotUser.Profile.Bot_id}:{slackBotUser.Team_id}", botName, $"{slackTargetedUser.Id}:{slackTargetedUser.Team_id}", destName, message, lang, ct);
}
else
{
throw new Exception($"Slack API : no user found for email '{destEmailAddress}'");
}
}
如何开始对话?
一旦您了解了有关您的机器人和用户的相关信息(再次取决于渠道),您就可以开始对话了。
- 创建对话
- 创建消息
- 向会话发送消息
示例方法:
private static async Task StartDirectMessage(string msAppId, string msAppPassword, string connectorClientUrl, string channelId, string fromId, string fromName, string recipientId, string recipientName, string message, string locale, CancellationToken token)
{
// Init connector
MicrosoftAppCredentials.TrustServiceUrl(connectorClientUrl, DateTime.Now.AddDays(7));
var account = new MicrosoftAppCredentials(msAppId, msAppPassword);
var connector = new ConnectorClient(new Uri(connectorClientUrl), account);
// Init conversation members
ChannelAccount channelFrom = new ChannelAccount(fromId, fromName);
ChannelAccount channelTo = new ChannelAccount(recipientId, recipientName);
// Create Conversation
var conversation = await connector.Conversations.CreateDirectConversationAsync(channelFrom, channelTo);
// Create message Activity and send it to Conversation
IMessageActivity newMessage = Activity.CreateMessageActivity();
newMessage.Type = ActivityTypes.Message;
newMessage.From = channelFrom;
newMessage.Recipient = channelTo;
newMessage.Locale = (locale ?? "en-US");
newMessage.ChannelId = channelId;
newMessage.Conversation = new ConversationAccount(id: conversation.Id);
newMessage.Text = message;
await connector.Conversations.SendToConversationAsync((Activity)newMessage);
}
所以回到你的上下文:
- 您在群组对话中获得了一个 ConversationUpdate
(ActivityTypes.ConversationUpdate and activity.MembersAdded.Count != 0)
:在此 ConversationUpdate 中,您可以获得 userId 和您的 botId。然后你开始一个新的对话(使用上面的代码),然后创建一个你不发送的假消息并从中获取 IDialogTask:你将能够启动你的 Dialog
示例:
if (activity.MembersAdded.Count != 0)
{
// We have to create a new conversation between Bot and AddedUser
#region Conversation creation
// Connector init
MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl, DateTime.Now.AddDays(7));
var account = new MicrosoftAppCredentials("yourMsAppId", "yourMsAppPassword");
var connector = new ConnectorClient(new Uri(activity.ServiceUrl), account);
// From the conversationUpdate message, Recipient = bot and From = User
ChannelAccount botChannelAccount = new ChannelAccount(activity.Recipient.Id, activity.Recipient.Name);
ChannelAccount userChannelAccount = new ChannelAccount(activity.From.Id, activity.From.Name);
// Create Conversation
var conversation = await connector.Conversations.CreateDirectConversationAsync(botChannelAccount, userChannelAccount);
#endregion
// Then we prepare a fake message from bot to user, mandatory to get the working IDialogTask. This message MUST be from User to Bot, if you want the following Dialog to be from Bot to User
IMessageActivity fakeMessage = Activity.CreateMessageActivity();
fakeMessage.From = userChannelAccount;
fakeMessage.Recipient = botChannelAccount;
fakeMessage.ChannelId = activity.ChannelId;
fakeMessage.Conversation = new ConversationAccount(id: conversation.Id);
fakeMessage.ServiceUrl = activity.ServiceUrl;
fakeMessage.Id = Guid.NewGuid().ToString();
// Then use this fake message to launch the new dialog
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, fakeMessage))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
//This is our dialog stack
var task = scope.Resolve<IDialogTask>();
//interrupt the stack. This means that we're stopping whatever conversation that is currently happening with the user
//Then adding this stack to run and once it's finished, we will be back to the original conversation
var dialog = new DemoDialog();
task.Call(dialog.Void<object, IMessageActivity>(), null);
await task.PollAsync(CancellationToken.None);
//flush dialog stack
await botData.FlushAsync(CancellationToken.None);
}
}
最终目标是:
botduilder sdk 女士,Slack 的聊天机器人。
当用户被添加到与 bot 的群聊时(ActivityTypes.ConversationUpdate 和 activity.MembersAdded.Count != 0),我想立即开始通过私人消息使用 formflow 对话框从他那里收集数据。但是从现在开始我找不到办法做到这一点,看来您已经需要从用户那里收到一些消息了。
这是否正确,唯一的解决方法是要求用户先输入一些文本(或按钮 "lets start")?
我还通过主动示例解析对话框堆栈尝试了此解决方案:
else if (activity.Type == ActivityTypes.ConversationUpdate)
{
if (activity.MembersAdded.Count != 0)
{
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
//This is our dialog stack
var stack = scope.Resolve<IDialogStack>();
//interrupt the stack. This means that we're stopping whatever conversation that is currently happening with the user
//Then adding this stack to run and once it's finished, we will be back to the original conversation
var questions = new WelcomePoll();
var myform = new FormDialog<WelcomePoll>(questions, WelcomePoll.BuildForm, FormOptions.PromptInStart, null);
stack.Call(myform, Resume); //GOT "STACK IS EMPTY" EXCEPTION
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
}
}
但是 System.InvalidOperationException: 'Stack is empty' 在 stack.Call 行。也许我需要先创建堆栈,但我找不到正确的方法。 谢谢你。
免责声明:我的回答比你的问题更笼统。请参阅答案末尾从 ConversationUpdate 启动对话
在 Microsoft Bot Framework 中可以发送“真正的”主动消息,但并非在每个渠道中都可以。
重要的一点:当我说“真正的主动消息”时,它与 Microsoft 在 documentation 中调用的 proactive message
有所区别(它从机器人发送消息但过去已经与之交谈过的人):这里我说的是向以前从未与机器人交谈过的客户发送消息。
发送主动消息
发送主动消息的主要困难是知道您将设置的 2 ChannelAccount(= 用户,即您的机器人和最终用户)的 ID在对话对象中。
那些 ChannelAccount 对象获得了一个 ID,该 ID 取决于您当前使用的频道。例如,根据我之前在 Bot Framework 上的工作分析:
- 对于 Skype,bot ID 是“28:”及其 Microsoft AppId 的串联,但您需要知道用户 ID 并且它们之间的对话必须已经存在 => 您不能真正主动发送消息
- 对于 Facebook messager,bot ID 是 FB 页面 ID,但用户 ID 是您不能轻易请求的特定 messenger ID => 复杂,必须熟悉 Facebook APIs
- 对于 Slack,机器人 ID 是其 Slack 的 Bot_id、一个“:”和 Slack 的 Team_id 的串联。用户 ID 是其 Slack 的 ID、“:”和 Slack 的 Team_id => interesting 的串联
- 对于 Twilio SMS,机器人 ID 和用户 ID 是它们的 phone 号码 => 简单
- 对于电子邮件渠道,这是他们的电子邮件
- ...
如果您想知道在获得这些 ID 后如何开始对话,请跳到最后的部分。
Slack ID 焦点
Slack 的优点是可以通过仅了解一些信息并请求他们 API 来获取开始对话所需的所有参数。例如,您可以通过只知道以下内容来开始对话:
- Bot Slack 的用户名
- 用户电子邮件地址或 Slack 的用户名
然后你可以使用 Slack 的 API 来获得机器人真正需要的东西。
如何获取Slack数据?
1 种调用服务的方法,1 种使用它获取正确数据的方法:
public class SlackService
{
internal static async Task<SlackMembersRootObject> GetSlackUsersList(string slackApiToken, CancellationToken ct)
{
using (var client = new HttpClient())
{
using (var response = await client.GetAsync($"https://slack.com/api/users.list?token=" + $"{slackApiToken}", ct).ConfigureAwait(false))
{
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var rootObject = Newtonsoft.Json.JsonConvert.DeserializeObject<SlackMembersRootObject>(jsonString);
return rootObject;
}
}
}
}
public static async Task StartSlackDirectMessage(string msAppId, string msAppPassword, string slackApiToken, string message, string botSlackUsername, string botName, string destEmailAddress, string destName, string lang, CancellationToken ct)
{
// Getting Slack user (and bot) list
var slackUsersList = await SlackService.GetSlackUsersList(slackApiToken, CancellationToken.None);
// Get Bot SlackId by searching its "username", you can also search by other criteria
var slackBotUser = slackUsersList.Members.FirstOrDefault(x => x.Name == botSlackUsername);
if (slackBotUser == null)
{
throw new Exception($"Slack API : no bot found for name '{botSlackUsername}'");
}
// Get User SlackId by email address
var slackTargetedUser = slackUsersList.Members.FirstOrDefault(x => x.Profile.Email == destEmailAddress);
if (slackTargetedUser != null)
{
// if found, starting the conversation by passing the right IDs
await StartDirectMessage(msAppId, msAppPassword, "https://slack.botframework.com/", "slack", $"{slackBotUser.Profile.Bot_id}:{slackBotUser.Team_id}", botName, $"{slackTargetedUser.Id}:{slackTargetedUser.Team_id}", destName, message, lang, ct);
}
else
{
throw new Exception($"Slack API : no user found for email '{destEmailAddress}'");
}
}
如何开始对话? 一旦您了解了有关您的机器人和用户的相关信息(再次取决于渠道),您就可以开始对话了。
- 创建对话
- 创建消息
- 向会话发送消息
示例方法:
private static async Task StartDirectMessage(string msAppId, string msAppPassword, string connectorClientUrl, string channelId, string fromId, string fromName, string recipientId, string recipientName, string message, string locale, CancellationToken token)
{
// Init connector
MicrosoftAppCredentials.TrustServiceUrl(connectorClientUrl, DateTime.Now.AddDays(7));
var account = new MicrosoftAppCredentials(msAppId, msAppPassword);
var connector = new ConnectorClient(new Uri(connectorClientUrl), account);
// Init conversation members
ChannelAccount channelFrom = new ChannelAccount(fromId, fromName);
ChannelAccount channelTo = new ChannelAccount(recipientId, recipientName);
// Create Conversation
var conversation = await connector.Conversations.CreateDirectConversationAsync(channelFrom, channelTo);
// Create message Activity and send it to Conversation
IMessageActivity newMessage = Activity.CreateMessageActivity();
newMessage.Type = ActivityTypes.Message;
newMessage.From = channelFrom;
newMessage.Recipient = channelTo;
newMessage.Locale = (locale ?? "en-US");
newMessage.ChannelId = channelId;
newMessage.Conversation = new ConversationAccount(id: conversation.Id);
newMessage.Text = message;
await connector.Conversations.SendToConversationAsync((Activity)newMessage);
}
所以回到你的上下文:
- 您在群组对话中获得了一个 ConversationUpdate
(ActivityTypes.ConversationUpdate and activity.MembersAdded.Count != 0)
:在此 ConversationUpdate 中,您可以获得 userId 和您的 botId。然后你开始一个新的对话(使用上面的代码),然后创建一个你不发送的假消息并从中获取 IDialogTask:你将能够启动你的 Dialog
示例:
if (activity.MembersAdded.Count != 0)
{
// We have to create a new conversation between Bot and AddedUser
#region Conversation creation
// Connector init
MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl, DateTime.Now.AddDays(7));
var account = new MicrosoftAppCredentials("yourMsAppId", "yourMsAppPassword");
var connector = new ConnectorClient(new Uri(activity.ServiceUrl), account);
// From the conversationUpdate message, Recipient = bot and From = User
ChannelAccount botChannelAccount = new ChannelAccount(activity.Recipient.Id, activity.Recipient.Name);
ChannelAccount userChannelAccount = new ChannelAccount(activity.From.Id, activity.From.Name);
// Create Conversation
var conversation = await connector.Conversations.CreateDirectConversationAsync(botChannelAccount, userChannelAccount);
#endregion
// Then we prepare a fake message from bot to user, mandatory to get the working IDialogTask. This message MUST be from User to Bot, if you want the following Dialog to be from Bot to User
IMessageActivity fakeMessage = Activity.CreateMessageActivity();
fakeMessage.From = userChannelAccount;
fakeMessage.Recipient = botChannelAccount;
fakeMessage.ChannelId = activity.ChannelId;
fakeMessage.Conversation = new ConversationAccount(id: conversation.Id);
fakeMessage.ServiceUrl = activity.ServiceUrl;
fakeMessage.Id = Guid.NewGuid().ToString();
// Then use this fake message to launch the new dialog
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, fakeMessage))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
//This is our dialog stack
var task = scope.Resolve<IDialogTask>();
//interrupt the stack. This means that we're stopping whatever conversation that is currently happening with the user
//Then adding this stack to run and once it's finished, we will be back to the original conversation
var dialog = new DemoDialog();
task.Call(dialog.Void<object, IMessageActivity>(), null);
await task.PollAsync(CancellationToken.None);
//flush dialog stack
await botData.FlushAsync(CancellationToken.None);
}
}