发送主动消息时无法获取 Twilio 短信状态回调
Unable to get Twilio sms status callbacks when sending proactive message
我正在尝试跟踪我使用机器人框架发送的消息的短信发送状态。我正在使用 Twilio,并发送主动消息。现在我正在尝试使用 twilio status callbacks
这与 this question 类似,我尝试了这种方法,但无法正常工作。我已经在 TwiML 应用程序上添加了我的 url,但它没有启动。我已经双重和三次检查,我怀疑这个 url 以某种方式被忽略或没有通过我当前的设置。我没有收到关于主动消息的任何回调,也没有收到机器人发送给用户的回复。但是流程运行良好,我可以回复并从机器人那里得到适当的回应。 编辑:称其为“方法 1”
方法 2: 我也试过在 Twilio 适配器上做一些轻微的修改,以便能够在创建消息之前添加我的回调。 (我对其进行了更改,因此它使用自定义的客户端包装器,在创建 twilio 资源时添加了我的回调 url)这确实有效,部分原因是:当我从我的机器人回复消息时,我得到了状态回调。但是由于主动消息是使用默认适配器发送的,因此我没有收到初始消息的回调。
方法 3: 最后,我还尝试在发送主动消息时使用 TwilioAdapter,但出于某种原因,一旦我发送 activity,TurnContext 是已处置,因此我无法保存状态或执行任何后续操作。这让我相信 twilio 适配器不打算以这种方式使用(不能用于主动消息),但如果有必要,我愿意探索这条道路。
这里是修改后的 Twilio 适配器:
public class TwilioAdapterWithErrorHandler : TwilioAdapter
{
private const string TwilioNumberKey = "TwilioNumber";
private const string TwilioAccountSidKey = "TwilioAccountSid";
private const string TwilioAuthTokenKey = "TwilioAuthToken";
private const string TwilioValidationUrlKey = "TwilioValidationUrl";
public TwilioAdapterWithErrorHandler(IConfiguration configuration, ILogger<TwilioAdapter> logger, TwilioAdapterOptions adapterOptions = null)
: base(
new TwilioClientWrapperWithCallback(new TwilioClientWrapperOptions(configuration[TwilioNumberKey], configuration[TwilioAccountSidKey], configuration[TwilioAuthTokenKey], new Uri(configuration[TwilioValidationUrlKey]))), adapterOptions, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
Task[] tasks = {
// Send a message to the user
turnContext.SendActivityAsync("We're sorry but this bot encountered an error when processing your answer."),
// Send a trace activity, which will be displayed in the Bot Framework Emulator
turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError")
};
Task all = Task.WhenAll(tasks); //task with the long running tasks
await Task.WhenAny(all, Task.Delay(5000)); //wait with a timeout
};
}
}
修改后的客户端包装器:
public class TwilioClientWrapperWithCallback : TwilioClientWrapper
{
public TwilioClientWrapperWithCallback(TwilioClientWrapperOptions options) : base(options) { }
public async override Task<string> SendMessageAsync(TwilioMessageOptions messageOptions, CancellationToken cancellationToken)
{
var createMessageOptions = new CreateMessageOptions(messageOptions.To)
{
ApplicationSid = messageOptions.ApplicationSid,
MediaUrl = messageOptions.MediaUrl,
Body = messageOptions.Body,
From = messageOptions.From,
};
createMessageOptions.StatusCallback = new System.Uri("https://myApp.ngrok.io/api/TwilioSms/SmsStatusUpdated");
var messageResource = await MessageResource.CreateAsync(createMessageOptions).ConfigureAwait(false);
return messageResource.Sid;
}
}
最后,这是我发送主动消息的总结代码:
[HttpPost("StartConversationWithSuperBill/{superBillId:long}")]
[HttpPost("StartConversationWithSuperBill/{superBillId:long}/Campaign/{campaignId:int}")]
public async Task<IActionResult> StartConversation(long superBillId, int? campaignId)
{
ConversationReference conversationReference = this.GetConversationReference("+17545517768");
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
{
await turnContext.SendActivityAsync("proactive message 1");
//this code was edited for brevity. Here I would start a new dialog that would cascade into another, but the end result is the same, as soon as a message is sent, the turn context is disposed.
await turnContext.SendActivityAsync("proactive message 2"); //throws ObjectDisposedException
}, default(CancellationToken));
var result = new { status = "Initialized fine!" };
return new JsonResult(result);
}
private ConversationReference GetConversationReference(string targetNumber)
{
string fromNumber = "+18632704234";
return new ConversationReference
{
User = new ChannelAccount { Id = targetNumber, Role = "user" },
Bot = new ChannelAccount { Id = fromNumber, Role = "bot" },
Conversation = new ConversationAccount { Id = targetNumber },
//ChannelId = "sms",
ChannelId = "twilio-sms", //appparently when using twilio adapter we need to set this. if using TwiML app and not using Twilio Adapter, use the above. Otherwise the frameworks interprets answers from SMS as new conversations instead.
ServiceUrl = "https://sms.botframework.com/",
};
}
(我可以看到我可以只调用创建对话引用并执行两个回调,一个用于每条消息,但在我的实际代码中,我正在创建一个对话框来发送一条消息,然后调用另一个对话框来启动另一条消息留言)
编辑 2:
一些说明:
在 方法 2 上,我使用了两个适配器,正如 code sample and documentation on using twilio adapter. The controller that starts the proactive message uses an instance of a default adapter (similar to this one 所建议的那样,TwilioController(获取 twilio 传入消息的那个)使用 TwilioAdapterWithErrorHandler .
在方法 3 上,我排除了默认适配器,并且两个控制器都使用 TwilioAdapterWithErrorHandler。
编辑 3:
Here's 一个有问题的小仓库。
我通过更改用于 ContinueConversation 的重载,围绕方法 3 找到了解决此问题的方法。替换为:
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
有了这个:
//Start a new conversation.
var twilioAdapter = (TwilioAdapterWithErrorHandler)_adapter;
await twilioAdapter.ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
这样,上下文不会被释放,我可以使用 twilio 适配器获取主动消息,并对所有消息进行状态回调。
我正在尝试跟踪我使用机器人框架发送的消息的短信发送状态。我正在使用 Twilio,并发送主动消息。现在我正在尝试使用 twilio status callbacks
这与 this question 类似,我尝试了这种方法,但无法正常工作。我已经在 TwiML 应用程序上添加了我的 url,但它没有启动。我已经双重和三次检查,我怀疑这个 url 以某种方式被忽略或没有通过我当前的设置。我没有收到关于主动消息的任何回调,也没有收到机器人发送给用户的回复。但是流程运行良好,我可以回复并从机器人那里得到适当的回应。 编辑:称其为“方法 1”
方法 2: 我也试过在 Twilio 适配器上做一些轻微的修改,以便能够在创建消息之前添加我的回调。 (我对其进行了更改,因此它使用自定义的客户端包装器,在创建 twilio 资源时添加了我的回调 url)这确实有效,部分原因是:当我从我的机器人回复消息时,我得到了状态回调。但是由于主动消息是使用默认适配器发送的,因此我没有收到初始消息的回调。
方法 3: 最后,我还尝试在发送主动消息时使用 TwilioAdapter,但出于某种原因,一旦我发送 activity,TurnContext 是已处置,因此我无法保存状态或执行任何后续操作。这让我相信 twilio 适配器不打算以这种方式使用(不能用于主动消息),但如果有必要,我愿意探索这条道路。
这里是修改后的 Twilio 适配器:
public class TwilioAdapterWithErrorHandler : TwilioAdapter
{
private const string TwilioNumberKey = "TwilioNumber";
private const string TwilioAccountSidKey = "TwilioAccountSid";
private const string TwilioAuthTokenKey = "TwilioAuthToken";
private const string TwilioValidationUrlKey = "TwilioValidationUrl";
public TwilioAdapterWithErrorHandler(IConfiguration configuration, ILogger<TwilioAdapter> logger, TwilioAdapterOptions adapterOptions = null)
: base(
new TwilioClientWrapperWithCallback(new TwilioClientWrapperOptions(configuration[TwilioNumberKey], configuration[TwilioAccountSidKey], configuration[TwilioAuthTokenKey], new Uri(configuration[TwilioValidationUrlKey]))), adapterOptions, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
Task[] tasks = {
// Send a message to the user
turnContext.SendActivityAsync("We're sorry but this bot encountered an error when processing your answer."),
// Send a trace activity, which will be displayed in the Bot Framework Emulator
turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError")
};
Task all = Task.WhenAll(tasks); //task with the long running tasks
await Task.WhenAny(all, Task.Delay(5000)); //wait with a timeout
};
}
}
修改后的客户端包装器:
public class TwilioClientWrapperWithCallback : TwilioClientWrapper
{
public TwilioClientWrapperWithCallback(TwilioClientWrapperOptions options) : base(options) { }
public async override Task<string> SendMessageAsync(TwilioMessageOptions messageOptions, CancellationToken cancellationToken)
{
var createMessageOptions = new CreateMessageOptions(messageOptions.To)
{
ApplicationSid = messageOptions.ApplicationSid,
MediaUrl = messageOptions.MediaUrl,
Body = messageOptions.Body,
From = messageOptions.From,
};
createMessageOptions.StatusCallback = new System.Uri("https://myApp.ngrok.io/api/TwilioSms/SmsStatusUpdated");
var messageResource = await MessageResource.CreateAsync(createMessageOptions).ConfigureAwait(false);
return messageResource.Sid;
}
}
最后,这是我发送主动消息的总结代码:
[HttpPost("StartConversationWithSuperBill/{superBillId:long}")]
[HttpPost("StartConversationWithSuperBill/{superBillId:long}/Campaign/{campaignId:int}")]
public async Task<IActionResult> StartConversation(long superBillId, int? campaignId)
{
ConversationReference conversationReference = this.GetConversationReference("+17545517768");
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
{
await turnContext.SendActivityAsync("proactive message 1");
//this code was edited for brevity. Here I would start a new dialog that would cascade into another, but the end result is the same, as soon as a message is sent, the turn context is disposed.
await turnContext.SendActivityAsync("proactive message 2"); //throws ObjectDisposedException
}, default(CancellationToken));
var result = new { status = "Initialized fine!" };
return new JsonResult(result);
}
private ConversationReference GetConversationReference(string targetNumber)
{
string fromNumber = "+18632704234";
return new ConversationReference
{
User = new ChannelAccount { Id = targetNumber, Role = "user" },
Bot = new ChannelAccount { Id = fromNumber, Role = "bot" },
Conversation = new ConversationAccount { Id = targetNumber },
//ChannelId = "sms",
ChannelId = "twilio-sms", //appparently when using twilio adapter we need to set this. if using TwiML app and not using Twilio Adapter, use the above. Otherwise the frameworks interprets answers from SMS as new conversations instead.
ServiceUrl = "https://sms.botframework.com/",
};
}
(我可以看到我可以只调用创建对话引用并执行两个回调,一个用于每条消息,但在我的实际代码中,我正在创建一个对话框来发送一条消息,然后调用另一个对话框来启动另一条消息留言)
编辑 2:
一些说明:
在 方法 2 上,我使用了两个适配器,正如 code sample and documentation on using twilio adapter. The controller that starts the proactive message uses an instance of a default adapter (similar to this one 所建议的那样,TwilioController(获取 twilio 传入消息的那个)使用 TwilioAdapterWithErrorHandler .
在方法 3 上,我排除了默认适配器,并且两个控制器都使用 TwilioAdapterWithErrorHandler。
编辑 3:
Here's 一个有问题的小仓库。
我通过更改用于 ContinueConversation 的重载,围绕方法 3 找到了解决此问题的方法。替换为:
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
有了这个:
//Start a new conversation.
var twilioAdapter = (TwilioAdapterWithErrorHandler)_adapter;
await twilioAdapter.ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
这样,上下文不会被释放,我可以使用 twilio 适配器获取主动消息,并对所有消息进行状态回调。