The new Twilio 5x libraries have introduced a bit of an odd approach to gathering DTMF digits on phonecalls.
The old 4x code for a gather would have looked something like:
twiml.BeginGathertwiml.BeginGather(new { numDigits = "1", action = "/TwilioCallbacks/InputResponse" });
if(x == 10){
twiml.Say("I am saying a thing because x = 10");
}
else{
twiml.Say("I am saying the other thing");
}
twiml.EndGather();
Now, if you wanted to let the user punch digits on the keypad while your bot was talking to them, that'd work just fine.
However in Twilio 5x, it looks like this:
twiml.Say("I am saying a really long thing where the user must wait until the twiml script reaches the gather phrase");
twiml.Say("press 1 if stack overflow is awesome, press 2 to quit programming forever");
twiml.Gather(
numDigits: 1,
input: new List<InputEnum>() { InputEnum.Dtmf },
timeout: 10,
method: "POST",
action: new System.Uri(Startup.hostAddress + "/TwilioCallbacks/InputResponse")
);
Right after Gather(...) you have a short window to collect the response, if you set a timeout on the response, the twiml won't proceed to the next say until the timeout expires.
How can I Gather digits in such a way that the user can interact with the keypad at any point during the message? The new approach seems to be a step backward.
edit:
Clarified the 4xx use case, such that folks can understand why chaining .Say won't work here.
edit:
Some people below are suggesting chaining the .Say() verb right after .Gather().
This actually doesn't behave as expected either. This is the C# code.
twiml.Gather(
numDigits: 1,
input: new List<InputEnum>() { InputEnum.Dtmf },
timeout: 10,
method: "POST",
action: new System.Uri(Startup.hostAddress + "/TwilioCallBot/InputResponse")
).Say("this is a test");
This is the resulting twiml:
<Gather input="dtmf" action="https://callbot21.ngrok.io/RCHHRATool//TwilioCallBot/InputResponse" method="POST" timeout="10" numDigits="1">
</Gather>
<Say>this is a test</Say>
The say verb needs to be inside the gather tag to get the behavior we're looking for.
Okay I've found the issue. It looks like fourwhey was correct to point at the API docs there. What I didn't catch was that the .Say was being appended to the gather in a specific way.
This:
twiml.Gather(...).Say("say a thing");
doesn't work the same as this:
var gather = new Twilio.TwiML.Voice.Gather(...).Say("say a thing");
best I can work out is that there's actually two gather methods, and that twiml.Gather(...) is actually calling Twilio.TwiML.Gather.
From here we can build our dynamic voice message and nest the Say verb like so:
gather.Say("Say a thing");
gather.Say("Say another thing");
And the twiml will get spit out the way we intended like so:
<gather>
<say>say a thing</say>
<say>say another thing</say>
</gather>
Related
I need to automatically make calls for customers and start a interaction with them through voice. Basically, when the customer pickup the phone, my "robot" will ask: "Hey, it seems you didn't finish your order. Would you like to finish by phone?" Customer will say YES, NO, or another phrase, and I will follow the flow.
My questions:
1) What is the best approach to solve this problem using Twilio?
2) It seems Twilio has this functionality (ASR) to understand only for inbound calls when I use functions. How can I do that with outbound calls?
3) Is Twilio ready to understand another languages except English? I need to use Portuguese, Brazil.
Thank you for your help.
Twilio developer evangelist here.
To automatically make calls you will need to use the Twilio Programmable Voice API. I note you're using C# according to the tags, so you can start with the Twilio C# library. Using the library you can make calls with the API like this:
using System;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
class Example
{
static void Main(string[] args)
{
// Find your Account Sid and Auth Token at twilio.com/console
const string accountSid = "your_account_sid";
const string authToken = "your_auth_token";
TwilioClient.Init(accountSid, authToken);
var to = new PhoneNumber("+14155551212");
var from = new PhoneNumber("+15017122661");
var call = CallResource.Create(to,
from,
url: new Uri("http://demo.twilio.com/docs/voice.xml"));
Console.WriteLine(call.Sid);
}
}
For a bit more detail on what all this means, check out the guide on making outbound phone calls with C#.
You will see in that example that we pass a URL to the method that makes the call. This URL can point anywhere, including at a Twilio Function (which is just a Node.js run in the Twilio infrastructure) or your own server. When the call connects to the user Twilio will make an HTTP request to that URL to find out what to do next. To tell Twilio what to do you will need to return TwiML.
To respond with the message that you want and then gather speech input from your user you will need to use the <Say> and <Gather> elements of TwiML. An example response might look like:
<Response>
<Gather input="speech" hints="yes, no" action="/gatherResponse">
<Say voice="alice">Hey, it seems you didn't finish your order. Would you like to finish by phone?</Say>
</Gather>
</Response>
In this case we start with <Gather> so that we can capture anything the user says while the call is speaking to them. We set the input attribute to "speech" so that we can use speech to text to recognise what they say. Also included is the hints attribute which can give hints to the service for the text you expect to hear. Finally, there is an action attribute which is a URL that will be called with the result of this.
You can change the language that the <Gather> expects to hear using the language attribute. There are a number of languages available including Brazilian Portuguese, which you would set with "pt-BR".
Nested inside the <Gather> is a <Say> which you use to read out your message. You can use the voice attribute to change available voices.
The next thing you need to do is respond to the result of the <Gather>. At this stage it depends on what web application framework you are using. The important thing is that when it has a result, Twilio will make an HTTP request to the URL set as the action attribute. In that request will be two important parameters, SpeechResult and Confidence. The SpeechResult has the text that Twilio believes was said and the Confidence is a score between 0.0 and 1.0 for how sure Twilio is about it. Hopefully your result will have "Yes" or "No" (or the Brazilian Portuguese equivalent). At this point you need to return more TwiML to tell Twilio what to do next with the call depending on whether the answer was positive, negative or missing/incorrect. For more ideas on how to handle calls from here, check out the Twilio voice tutorials in C#.
Let me know if that helps at all.
I'm trying to implement punchout catalogs on our eComm site. Honestly, the documentation for cXML is a mess and all the code examples are in javascript and/or VB.Net (I use C# and would rather not have to try and translate). Does anyone out there have examples or samples of how to receive the PunchOutSetupRequest XML and then send out the PunchOutSetupResponse XML using C#? I've been unable to find anything on the interwebs (I've been looking for two days now)...
I'm hoping I can just do this inside an ActionResult (vs. a 'launch page' as suggested).
I'm a complete noob at punchouts and could really use some help here. The bosses are being pretty pushy, so any assistance would be greatly appreciated. Suggestions as to how to make this work would also be much appreciated.
I apologize to all for the vagueness of the question (request).
This isn't trivial, but this should get you started.
You'll need 3 generic handlers (.ashx): Setup, Start, and Order....
Setup and Order will receive HTTP Post with content-type of "text/xml". Look at HttpRequest.InputStream if needed to get the XML into a string. From there, look at LINQ-to-XML to dig out the data you want. Your HTTP Response to both of these will also be content-type "text/xml" and UTF8 encoded, returning the CXML as documented...use LINQ-to-XML to produce that.
The Setup handler will need to validate credentials and return a URL with a unique QueryString token pointing to the Start handler. Do not expect session persistence between Setup and Start, because they're not from the same caller. This handler will need to create an application object for the token and associated data you extracted from the cXML.
The Start handler will be called as a simple GET, and will need to match the token in the QueryString to the appropriate application object, copy that data to the session, and then do a response.redirect to whatever page in your site you want the buyer to land on.
Once they populate their cart with some things, and are ready to check out, you'll take them to a page that has an embedded form (not to be confused with an ASP.Net form that posts back to your server) and a submit button (again, not an ASP.Net button). From your Setup handler, you captured a URL to point this form's Post, and within the form you'll have a hidden input tag with the UTF8 encoded CXML Punchout Order injected as the value produced with LINQ-to-XML. Highly recommend Base64 encoding that value to avoid ASP.Net messing with the tags it contains during rendering, and naming the hidden input "cxml-base64" per the documentation. The result is the form is client-side POSTed to your customer's server instead of yours, and their server will extract the CXML Punchout Order and that ends your visitor's session.
The Order handler will receive a CXML OrderRequest and just like Setup, you'll dump that to a string and then use LINQ-to-XML to parse it and act upon it. Again you'll get credentials to verify, possibly a credit card to process, and the order items, ship-to, etc. Note that the OrderRequest may not contain all the items that were in the Punchout Order, because the system on your customer's side may remove items or even change item quantities before submitting the final OrderRequest to you. The OrderRequest could come back to you after the Punchout Order is posted to them in a matter of minutes, days, weeks, or never...don't bother storing the cart data in hopes of matching it to the order later.
Last note...the buyer may be experiencing your site in an iframe embedded in their web-based procurement UI, so design accordingly.
If you need more info, reply to this and I'll get back.
Update...Additional considerations:
Discuss with the buyer how they want fault handling to flow, particularly with orders, because you have a choice. 1) exhaustively evaluate everything in the CXML you receive and return response codes other than 200 if anything is wrong, or 2) always return a 200 Success and deal with any issues out of band or by generating a ConfirmationRequest that rejects the order. My experience is that a mix of the two works best. Certainly you should throw a non-200 if the credentials fail, but you may not want (or be able) to run a credit card or validate stock availability inline. Your buyer's system may not be able to cope with dozens of possible faults, and/or may not show your fault messages to the user for them to make corrections. I've seen systems that will flat-out discard any non-200 response code and just blindly retry the submission repeatedly on an interval for hours or days until it gives up on a sanity check, while others will handle response codes within certain ranges differently than others, for example a 4xx invokes a retry, while a 5xx is treated as fatal. Remember that Setup and Order are not coming directly from the user...their procurement system is generating those internally.
Update...answering the comment about how to test things...
You'd use the same method as you will for generating outbound ConfirmationRequest, ShipNoticeRequest, and InvoiceDetailRequest, all of which generally are produced on your side after receiving an OrderRequest from your customer's procurement system.
Start with Linq-To-XML for an example of crafting your outgoing cXML (Creating XML Trees section). Combine that example with this bit of code:
StringBuilder output = new StringBuilder();
XmlWriterSettings objXmlWriterSettings = new XmlWriterSettings();
objXmlWriterSettings.Indent = true;
objXmlWriterSettings.NewLineChars = Environment.NewLine;
objXmlWriterSettings.NewLineHandling = NewLineHandling.Replace;
objXmlWriterSettings.NewLineOnAttributes = false;
objXmlWriterSettings.Encoding = new UTF8Encoding();
using (XmlWriter objXmlWriter = XmlWriter.Create(output, objXmlWriterSettings)) {
XElement root = new XElement("Root",
new XElement("Child", "child content")
);
root.Save(objXmlWriter);
}
Console.WriteLine(output.ToString());
So at this point the StringBuilder (output) has your whole cXML, and you need to POST it someplace. Your Web Application project, started with F5 and a default.aspx page will be listening on localhost and some port (you'll see that in the URL it opens). Separately, perhaps using VS Express for Desktop, you have the above code in a console app that you can run to do the Post using something like this:
Net.HttpWebRequest objRequest = Net.WebRequest.Create("http://localhost:12345/handler.ashx");
objRequest.Method = "POST";
objRequest.UserAgent = "Some User Agent";
objRequest.ContentLength = output.Length;
objRequest.ContentType = "text/xml";
IO.StreamWriter objStreamWriter = new IO.StreamWriter(objRequest.GetRequestStream, System.Text.Encoding.ASCII);
objStreamWriter.Write(output);
objStreamWriter.Flush();
objStreamWriter.Close();
Net.WebResponse objWebResponse = objRequest.GetResponse();
XmlReaderSettings objXmlReaderSettings = new XmlReaderSettings();
objXmlReaderSettings.DtdProcessing = DtdProcessing.Ignore;
XmlReader objXmlReader = XmlReader.Create(objWebResponse.GetResponseStream, objXmlReaderSettings);
// Pipes the stream to a higher level stream reader with the required encoding format.
IO.MemoryStream objMemoryStream2 = new IO.MemoryStream();
XmlWriter objXmlWriter2 = XmlWriter.Create(objMemoryStream2, objXmlWriterSettings);
objXmlWriter2.WriteNode(objXmlReader, true);
objXmlWriter2.Flush();
objXmlWriter2.Close();
objWebResponse.Close();
// Reset current position to the beginning so we can read all below.
objMemoryStream2.Position = 0;
StreamReader objStreamReader = new StreamReader(objMemoryStream2, Encoding.UTF8);
Console.WriteLine(objStreamReader.ReadToEnd());
objStreamReader.Close();
Since your handler should be producing cXML you'll see that spat out in the console. If it pukes, you'll get a big blob of debug mess in the console, which of course will help you fix whatever is broken.
pardon the verbosity in the variable names, done to try to make things clear.
Where I work, we have two modes of authentication:
CAS (http://www.jasig.org/cas)
LDAP
CAS is the primary method, but it is often unreliable at peak traffic times and so we have been using LDAP as a fallback mode for when we notice that CAS is down. Previously, we were using PHP for doing our LDAP fallback and got reasonable performance. There wasn't a noticeable delay during login other than the expected network lag times. A login took probably ~250-500ms to complete using LDAP.
Now, we are making a new system and have chosen ASP.NET MVC4 as the platform rather than PHP and I am tasked with trying to get this fallback working again. I have been pulling my hair out for about 6 hours now trying different things over and over again, getting the same result (perhaps I am insane). I have finally managed to connect to LDAP, authenticate the user, and get their attributes from LDAP. However, the query consistently takes 4.5 seconds to complete no matter what method I try.
This is very surprising to me seeing as the PHP version was able to do nearly the same thing in 1/8th the time and it would seem that the .NET framework has excellent support for LDAP/ActiveDirectory. Am I doing something incredibly horribly wrong?
Here are the guts of my function as it stands now (this one is the latest iteration that manages to do everything in one 4.5 second query):
public Models.CASAttributes Authenticate(string username, string pwd)
{
string uid = string.Format("uid={0},ou=People,o=byu.edu", username);
LdapDirectoryIdentifier identifier = new LdapDirectoryIdentifier("ldap.byu.edu", 636, false, false);
try
{
using (LdapConnection connection = new LdapConnection(identifier))
{
connection.Credential = new NetworkCredential(uid, pwd);
connection.AuthType = AuthType.Basic;
connection.SessionOptions.SecureSocketLayer = true;
connection.SessionOptions.ProtocolVersion = 3;
string filter = "(uid=" + username + ")";
SearchRequest request = new SearchRequest("ou=People,o=byu.edu", filter, SearchScope.Subtree);
Stopwatch sw = Stopwatch.StartNew();
SearchResponse response = connection.SendRequest(request) as SearchResponse;
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
foreach (SearchResultEntry entry in response.Entries)
{
Debug.WriteLine(entry.DistinguishedName);
foreach (System.Collections.DictionaryEntry attribute in entry.Attributes)
{
Debug.WriteLine(attribute.Key + " " + attribute.Value.GetType().ToString());
}
Debug.WriteLine("");
}
}
}
catch
{
Debugger.Break();
}
Debugger.Break();
return null; //debug
}
The PHP version of this follows this sequence:
Bind anonymously and look up the user information using a basedn and cn
Bind again using the username and password of the user to see if they are authentic
It does two binds (connects?) in 1/8th the time it takes the .NET version to do one! Its this sort of thing that makes me thing I am missing something.
I have tried methods based on the following sites:
http://roadha.us/2013/04/ldap-authentication-with-c-sharp/ - Required 2 queries to do what I wanted and was too slow. I went through probably 6 different tries of doing it differently (varying the authentication & connection settings, etc).
http://web.byu.edu/docs/ldap-authentication-0 - One PHP version, but has a small snippet about .NET at the bottom. I needed to get the profile as well and they weren't exactly descriptive.
System.DirectoryServices is slow? - Current version
EDIT:
Using wireshark, I saw that the following requests are made:
bindRequest passing along my uid (delta 0.7ms)
bindResponse success (delta 2ms)
searchRequest "ou=People,o=byu.edu" wholdSubtree (delta 0.2ms)
searchResEntry "uid=my uid,ou=People,o=byu.edu" | searchResDone success 1 result (delta 10.8ms)
unbindRequest (delta 55.7ms)
Clearly, the overhead is coming from .NET and not from the requests. These don't add up to 4.5 seconds in any way, shape, or form.
ldap.byu.edu sure looks like a fully qualified DNS host name. You should change your LdapDirectoryIdentifier constructor to new LdapDirectoryIdentifier("ldap.byu.edu", 636, true, false).
I think you're definitely on the right track using System.DirectoryServices for this, so you may just need to tweak your search request a bit.
You're only looking to get one result back here, correct? Set your size accordingly :
request.SizeLimit = 1;
This is a tricky one, but make also sure you're suppressing referral binds as well. You'll want to set this before you call connection.SendRequest(request) :
//Setting the DomainScope will suppress referral binds from occurring during the search
SearchOptionsControl SuppressReferrals = new SearchOptionsControl(SearchOption.DomainScope);
request.Controls.Add(SuppressReferrals);
Right now I have an app that allows a user to schedule/post a Facebook post and then monitor the likes/comments. One of the problems I foresee is that currently I am pulling every single comment/like whether it's been processed or not. What I would like to do instead is be able to say 'Give me all the NEW comments since XYZdate/XYZcomment.' Is this currently possible?
var accessToken = existingUserNode.Attributes["accessToken"].Value;
var facebookAPIMgr = new FacebookWrapper.FacebookAPIManager();
var msg = new FacebookWrapper.FacebookMessage()
{
AccessToken = accessToken,
FacebookMessageId = facebookPost.FacebookMessageId
};
//Get Facebook Message Comments
// Need to find a way to limit this to only new comments/likes
var comments = facebookAPIMgr.RetrieveComments(msg);
You can do time-based pagination as part of your graph API query. If you keep a unix timestamp of when you polled things last, you can simply do https://graph.facebook.com/{whatever}?since={last run}.
This worked when I was working heavily with the Graph API earlier this year, and is still around on the documentation, but considering how much Facebook loves to change stuff without telling anyone you may still encounter problems. So just a warning, YMMV.
I want to write a program that responds to calls. After a welcome message it must tell the client to: press 1 for enter your account number, or 2 for speak with an operator. If the client presses 1 then tell him or her to enter your account number and after he or she enters the account number the number must saved in the database.
Is this possible in c#? If it is, I want an IVR library for c#. If not, I need a great IVR library for c++.
Microsoft has the Microsoft Speech API (SAPI) however if you want simple IVR it is better not to reinvent the wheel and customize an Asterisk implementation (which i guess falls under the "great IVR library for c++" category (it's c not c++ but if you know c++ you should be able to understand the c).) Using AsteriskNow you may not even need to write any custom code, it may do what you want already.
I don't know of a free IVR library for C#, but I do know of a couple that are fairly inexpensive:
http://www.voiceelements.com/
http://www.componentsource.com/products/velocity/index.html
Ricky from the Twilio crew here.
We have a tutorial on building an IVR with C#, taking a look at this may be helpful in getting an idea in how to build this type of application.
Whenever a phone call comes into our phone number, a request is made to our server where we can respond with some basic instructions, using TwiML, on what to do with the call:
public TwiMLResult Welcome()
{
var response = new TwilioResponse();
response.BeginGather(new {action = Url.Action("Show", "Menu"), numDigits = "1"})
.Play("http://howtodocs.s3.amazonaws.com/et-phone.mp3", new {loop = 3})
.EndGather();
return TwiML(response);
}
Since we're using the verb, when the user presses a digit a new HTTP request will be made to another route on our server where we can take action based on what digit(s) the user pressed:
public TwiMLResult Show(string digits)
{
var selectedOption = digits;
var optionActions = new Dictionary<string, Func<TwiMLResult>>()
{
{"1", ReturnInstructions},
{"2", Planets}
};
return optionActions.ContainsKey(selectedOption) ?
optionActions[selectedOption]() :
RedirectWelcome();
}
Hope that helps!