From e5fbb05f7dd66fbcf3d10eeb20bc292b120d849d Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Sat, 19 Mar 2016 08:51:02 -0400 Subject: [PATCH] Implement voice call and TTS --- CHANGELOG.md | 4 + .../Nexmo.Api.Test.Integration.csproj | 1 + Nexmo.Api.Test.Integration/VoiceTest.cs | 75 ++++ Nexmo.Api.Test.Unit/MockedWebTest.cs | 1 + .../Nexmo.Api.Test.Unit.csproj | 1 + Nexmo.Api.Test.Unit/VoiceTest.cs | 135 ++++++ Nexmo.Api.sln | 1 + Nexmo.Api/Voice.cs | 395 +++++++++++++++++- Nexmo.Web.Sample/Content/voiceDemo.xml | 14 + .../Controllers/Api/TTSController.cs | 32 ++ .../Controllers/Api/VoiceController.cs | 32 ++ .../Controllers/HomeController.cs | 33 +- Nexmo.Web.Sample/Models/Actions.cs | 2 + Nexmo.Web.Sample/Nexmo.Web.Sample.csproj | 5 + Nexmo.Web.Sample/Views/Home/Index.cshtml | 18 + README.md | 5 +- 16 files changed, 726 insertions(+), 28 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 Nexmo.Api.Test.Integration/VoiceTest.cs create mode 100644 Nexmo.Api.Test.Unit/VoiceTest.cs create mode 100644 Nexmo.Web.Sample/Content/voiceDemo.xml create mode 100644 Nexmo.Web.Sample/Controllers/Api/TTSController.cs create mode 100644 Nexmo.Web.Sample/Controllers/Api/VoiceController.cs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5ee11aa0e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ + +# 1.0.0 (2016-03-19) + +* Initial release with nuget package \ No newline at end of file diff --git a/Nexmo.Api.Test.Integration/Nexmo.Api.Test.Integration.csproj b/Nexmo.Api.Test.Integration/Nexmo.Api.Test.Integration.csproj index 18fbff931..464aed562 100644 --- a/Nexmo.Api.Test.Integration/Nexmo.Api.Test.Integration.csproj +++ b/Nexmo.Api.Test.Integration/Nexmo.Api.Test.Integration.csproj @@ -45,6 +45,7 @@ + diff --git a/Nexmo.Api.Test.Integration/VoiceTest.cs b/Nexmo.Api.Test.Integration/VoiceTest.cs new file mode 100644 index 000000000..cd888eb4b --- /dev/null +++ b/Nexmo.Api.Test.Integration/VoiceTest.cs @@ -0,0 +1,75 @@ +using NUnit.Framework; + +namespace Nexmo.Api.Test.Integration +{ + [TestFixture] + public class VoiceTest + { + [Test] + public void should_call_voice() + { + var result = Voice.Call(new Voice.CallCommand + { + to = "17775551212", + answer_url = "https://abcdefgh.ngrok.io/content/voiceDemo.xml", + status_url = "https://abcdefgh.ngrok.io/api/voice", + from = "15555551212", + }); + Assert.IsNotEmpty(result.CallId); + Assert.AreEqual("0", result.status); + Assert.AreEqual("17775551212", result.to); + } + + [Test] + public void should_text_to_speech_call() + { + var result = Voice.TextToSpeech(new Voice.TextToSpeechCallCommand + { + to = "17775551212", + from = "15555551212", + text = "1, 2, 3 wait 5 minutes humanoid", + callback = "https://abcdefgh.ngrok.io/api/tts" + }); + + Assert.IsNotEmpty(result.call_id); + Assert.AreEqual("0", result.status); + Assert.AreEqual("17775551212", result.to); + } + + [Test] + public void should_text_to_speech_capture() + { + var result = Voice.TextToSpeechPrompt(new Voice.TextToSpeechPromptCaptureCommand + { + to = "17775551212", + from = "15555551212", + text = "Enter some digits please", + bye_text = "Thanks", + callback = "https://abcdefgh.ngrok.io/api/tts" + }); + + Assert.IsNotEmpty(result.call_id); + Assert.AreEqual("0", result.status); + Assert.AreEqual("17775551212", result.to); + } + + [Test] + public void should_text_to_speech_confirm() + { + var result = Voice.TextToSpeechPrompt(new Voice.TextToSpeechPromptConfirmCommand + { + to = "17775551212", + text = "Please enter your 6 digit pin", + max_digits = "6", + pin_code = "123457", + bye_text = "Correct!", + failed_text = "Failed, ", + callback = "https://abcdefgh.ngrok.io/api/tts" + }); + + Assert.IsNotEmpty(result.call_id); + Assert.AreEqual("0", result.status); + Assert.AreEqual("17775551212", result.to); + } + } +} \ No newline at end of file diff --git a/Nexmo.Api.Test.Unit/MockedWebTest.cs b/Nexmo.Api.Test.Unit/MockedWebTest.cs index 6994f9ff5..b9d3ce639 100644 --- a/Nexmo.Api.Test.Unit/MockedWebTest.cs +++ b/Nexmo.Api.Test.Unit/MockedWebTest.cs @@ -11,6 +11,7 @@ internal class MockedWebTest protected Mock _mock; protected Mock _request; + protected string ApiUrl = ConfigurationManager.AppSettings["Nexmo.Url.Api"]; protected string RestUrl = ConfigurationManager.AppSettings["Nexmo.Url.Rest"]; protected string ApiKey = ConfigurationManager.AppSettings["Nexmo.api_key"]; protected string ApiSecret = ConfigurationManager.AppSettings["Nexmo.api_secret"]; diff --git a/Nexmo.Api.Test.Unit/Nexmo.Api.Test.Unit.csproj b/Nexmo.Api.Test.Unit/Nexmo.Api.Test.Unit.csproj index b5306e8cf..944121699 100644 --- a/Nexmo.Api.Test.Unit/Nexmo.Api.Test.Unit.csproj +++ b/Nexmo.Api.Test.Unit/Nexmo.Api.Test.Unit.csproj @@ -51,6 +51,7 @@ + diff --git a/Nexmo.Api.Test.Unit/VoiceTest.cs b/Nexmo.Api.Test.Unit/VoiceTest.cs new file mode 100644 index 000000000..27361bfa7 --- /dev/null +++ b/Nexmo.Api.Test.Unit/VoiceTest.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Text; +using Moq; +using Nexmo.Api.Request; +using NUnit.Framework; + +namespace Nexmo.Api.Test.Unit +{ + [TestFixture] + internal class VoiceTest : MockedWebTest + { + [Test] + public void should_call_voice() + { + var resp = new Mock(); + resp.Setup(e => e.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("{\"call-id\":\"ffffffff36c53a6c37ba7bcfada6ffff-1\",\"to\":\"17775551212\",\"status\":\"0\"}"))); + var postDataStream = new MemoryStream(); + _request.Setup(e => e.GetRequestStream()).Returns(postDataStream); + _request.Setup(e => e.GetResponse()).Returns(resp.Object); + var result = Voice.Call(new Voice.CallCommand + { + to = "17775551212", + answer_url = "https://test.test.com/content/voiceDemo.xml", + from = "15555551212", + }); + + _mock.Verify(h => h.CreateHttp(new Uri( + string.Format("{0}/call/json", RestUrl))), + Times.Once); + postDataStream.Position = 0; + var sr = new StreamReader(postDataStream); + var postData = sr.ReadToEnd(); + Assert.AreEqual(string.Format("to=17775551212&answer_url=https%3a%2f%2ftest.test.com%2fcontent%2fvoiceDemo.xml&from=15555551212&api_key={0}&api_secret={1}&", ApiKey, ApiSecret), postData); + + Assert.AreEqual("ffffffff36c53a6c37ba7bcfada6ffff-1", result.CallId); + Assert.AreEqual("17775551212", result.to); + Assert.AreEqual("0", result.status); + } + + [Test] + public void should_text_to_speech() + { + var resp = new Mock(); + resp.Setup(e => e.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("{\"call_id\":\"ffffffffc839189f36354f94b878ffff\",\"to\":\"17775551212\",\"status\":\"0\",\"error_text\":\"Success\"}"))); + var postDataStream = new MemoryStream(); + _request.Setup(e => e.GetRequestStream()).Returns(postDataStream); + _request.Setup(e => e.GetResponse()).Returns(resp.Object); + var result = Voice.TextToSpeech(new Voice.TextToSpeechCallCommand + { + to = "17775551212", + from = "15555551212", + text = "1, 2, 3 wait 5 minutes humanoid", + callback = "https://test.test.com/api/tts" + }); + + _mock.Verify(h => h.CreateHttp(new Uri( + string.Format("{0}/tts/json", ApiUrl))), + Times.Once); + postDataStream.Position = 0; + var sr = new StreamReader(postDataStream); + var postData = sr.ReadToEnd(); + Assert.AreEqual(string.Format("to=17775551212&from=15555551212&text=1%2c+2%2c+3+wait+%3cbreak+time%3d%221s%22%2f%3e5+minutes+%3cprosody+rate%3d%22-25%25%22%3ehumanoid%3c%2fprosody%3e&callback=https%3a%2f%2ftest.test.com%2fapi%2ftts&api_key={0}&api_secret={1}&", ApiKey, ApiSecret), postData); + + Assert.AreEqual("ffffffffc839189f36354f94b878ffff", result.call_id); + Assert.AreEqual("17775551212", result.to); + Assert.AreEqual("0", result.status); + Assert.AreEqual("Success", result.error_text); + } + + [Test] + public void should_text_to_speech_capture() + { + var resp = new Mock(); + resp.Setup(e => e.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("{\"call_id\":\"ffffffffc839189f36354f94b878ffff\",\"to\":\"17775551212\",\"status\":\"0\",\"error_text\":\"Success\"}"))); + var postDataStream = new MemoryStream(); + _request.Setup(e => e.GetRequestStream()).Returns(postDataStream); + _request.Setup(e => e.GetResponse()).Returns(resp.Object); + var result = Voice.TextToSpeechPrompt(new Voice.TextToSpeechPromptCaptureCommand + { + to = "17775551212", + from = "15555551212", + text = "Enter some digits please", + bye_text = "Thanks", + callback = "https://test.test.com/api/tts" + }); + + _mock.Verify(h => h.CreateHttp(new Uri( + string.Format("{0}/tts-prompt/json", ApiUrl))), + Times.Once); + postDataStream.Position = 0; + var sr = new StreamReader(postDataStream); + var postData = sr.ReadToEnd(); + Assert.AreEqual(string.Format("from=15555551212&to=17775551212&text=Enter+some+digits+please&callback=https%3a%2f%2ftest.test.com%2fapi%2ftts&bye_text=Thanks&api_key={0}&api_secret={1}&", ApiKey, ApiSecret), postData); + + Assert.AreEqual("ffffffffc839189f36354f94b878ffff", result.call_id); + Assert.AreEqual("17775551212", result.to); + Assert.AreEqual("0", result.status); + Assert.AreEqual("Success", result.error_text); + } + + [Test] + public void should_text_to_speech_confirm() + { + var resp = new Mock(); + resp.Setup(e => e.GetResponseStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("{\"call_id\":\"ffffffffc839189f36354f94b878ffff\",\"to\":\"17775551212\",\"status\":\"0\",\"error_text\":\"Success\"}"))); + var postDataStream = new MemoryStream(); + _request.Setup(e => e.GetRequestStream()).Returns(postDataStream); + _request.Setup(e => e.GetResponse()).Returns(resp.Object); + var result = Voice.TextToSpeechPrompt(new Voice.TextToSpeechPromptConfirmCommand + { + to = "17775551212", + text = "Please enter your 6 digit pin", + max_digits = "6", + pin_code = "123457", + bye_text = "Correct!", + failed_text = "Failed, ", + callback = "https://test.test.com/api/tts" + }); + + _mock.Verify(h => h.CreateHttp(new Uri( + string.Format("{0}/tts-prompt/json", ApiUrl))), + Times.Once); + postDataStream.Position = 0; + var sr = new StreamReader(postDataStream); + var postData = sr.ReadToEnd(); + Assert.AreEqual(string.Format("pin_code=123457&failed_text=Failed%2c+&to=17775551212&text=Please+enter+your+6+digit+pin&callback=https%3a%2f%2ftest.test.com%2fapi%2ftts&max_digits=6&bye_text=Correct!&api_key={0}&api_secret={1}&", ApiKey, ApiSecret), postData); + + Assert.AreEqual("ffffffffc839189f36354f94b878ffff", result.call_id); + Assert.AreEqual("17775551212", result.to); + Assert.AreEqual("0", result.status); + Assert.AreEqual("Success", result.error_text); + } + } +} \ No newline at end of file diff --git a/Nexmo.Api.sln b/Nexmo.Api.sln index b25883333..00d0df1c9 100644 --- a/Nexmo.Api.sln +++ b/Nexmo.Api.sln @@ -9,6 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nexmo.Web.Sample", "Nexmo.W EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{755C0EA2-03BC-4446-B933-550BEB1EFEF5}" ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md LICENSE.md = LICENSE.md README.md = README.md EndProjectSection diff --git a/Nexmo.Api/Voice.cs b/Nexmo.Api/Voice.cs index f9784792d..517b3372a 100644 --- a/Nexmo.Api/Voice.cs +++ b/Nexmo.Api/Voice.cs @@ -1,11 +1,394 @@ -namespace Nexmo.Api +using System; +using System.Net; +using Newtonsoft.Json; +using Nexmo.Api.Request; + +namespace Nexmo.Api { public static class Voice - { - // TODO: call - - // TODO: tts / tts prompt + { + public class CallCommand + { + /// + /// Required. A single phone number in international format, that is E.164. For example, to=447525856424. You can set one recipient only for each request. + /// + public string to { get; set; } + /// + /// Required. A URL pointing to the VoiceXML file on your HTTP server that controls your Call. If the VoiceXML at answer_url triggers a transfer, you receive separate Call Return Parameters for each leg of the call. + /// + public string answer_url { get; set; } + /// + /// Optional. A voice-enabled virtual number associated with your Nexmo account. This number is displayed on your user's handset during this Text-To-Speech or Text-To-Speech Prompt. Check the features and restrictions to see if this is possible in your destination country. + /// + public string from { get; set; } + /// + /// Optional. If the Call is picked up by an answering machine, set to: + /// true - play your message after the beep. If there is no beep the Call is closed without leaving your message. + /// hangup - the Call hangs up immediately. + /// The value of status in the Call Return Parameters is set to machine when we close the Call. + /// + public string machine_detection { get; set; } + /// + /// Optional. The time in milliseconds used to distinguish between human and machine events. Possible values are from 400ms to 10s. The default value is 10s. + /// + public string machine_timeout { get; set; } + /// + /// Optional. The HTTP method used to send a response to your answer_url. Must be GET (default) or POST. + /// + public string answer_method { get; set; } + /// + /// Optional. Send the VoiceXML error message to this URL if there's a problem requesting or executing the VoiceXML referenced in the answer_url + /// + public string error_url { get; set; } + /// + /// Optional. The HTTP method used to send an error message to your error_url. Must be GET (default) or POST. + /// + public string error_method { get; set; } + /// + /// Optional. If you set this parameter, Nexmo sends the Call Return Parameters to this Callback URL in order to notify your App that the Call has ended. + /// + public string status_url { get; set; } + /// + /// Optional. The HTTP method used to send the status message to your status_url. Must be GET (default) or POST. + /// + public string status_method { get; set; } + } + + public class CallRequestResponse + { + /// + /// An alphanumeric unique call identifier of up to 40 characters. + /// + [JsonProperty("call-id")] + public string CallId { get; set; } + /// + /// The phone number the call was made to. + /// + public string to { get; set; } + /// + /// Shows if your request has been sent successfully to Nexmo, or the reason why it could not be processed. See Response codes for more information. + /// + public string status { get; set; } + /// + /// If status is not 0 this message explains the issue encountered. + /// + [JsonProperty("error-text")] + public string ErrorText { get; set; } + } + + public class CallReturn + { + /// + /// The Nexmo ID for this Call. + /// + [JsonProperty("call-id")] + public string CallId { get; set; } + /// + /// The phone number this Call was sent to. + /// + public string to { get; set; } + /// + /// One of the following: + /// ok - Call terminated normally. + /// failed - Call failed. + /// error - an error occurred during the Call. + /// vxml_error - an error occurred running your VoiceXML script. + /// blocked - the Call was blocked. + /// machine - Call was stopped because you set machine_detection to hangup. + /// + public string status { get; set; } + /// + /// Total price charged to your account in EUR. + /// + [JsonProperty("call-price")] + public string CallPrice { get; set; } + /// + /// Price per minute in EUR. + /// + [JsonProperty("call-rate")] + public string CallRate { get; set; } + /// + /// Duration of the Call in seconds. + /// + [JsonProperty("call-duration")] + public string CallDuration { get; set; } + /// + /// The time you sent the Call request. Printed in the following format: YYYY/MM/DD HH:MM:SS. + /// + [JsonProperty("call-request")] + public string CallRequest { get; set; } + /// + /// The time the Call started. Printed in the following format: YYYY/MM/DD HH:MM:SS. + /// + [JsonProperty("call-start")] + public string CallStart { get; set; } + /// + /// The time the Call ended. Printed in the following format: YYYY/MM/DD HH:MM:SS. + /// + [JsonProperty("call-end")] + public string CallEnd { get; set; } + /// + /// The ID of the parent Call if this Call is a transfer. + /// + [JsonProperty("call-parent")] + public string CallParent { get; set; } + /// + /// One of the following: + /// in - an inbound Call to your voice-enabled number. + /// out - an outbound Call. + /// + [JsonProperty("call-direction")] + public string CallDirection { get; set; } + /// + /// The Mobile Country Code Mobile Network Code (MCCMNC) for the carrier network this phone number is registered with. + /// + [JsonProperty("network-code")] + public string NetworkCode { get; set; } + } + + public class TextToSpeechCallCommand + { + /// + /// Required. The single phone number to call for each request. This number must be in international format, that is E.164. For example, when sending to the uk: to=447525856424. + /// + public string to { get; set; } + /// + /// Optional. A voice-enabled virtual number associated with your Nexmo account. This number is displayed on your user's handset during this Text-To-Speech or Text-To-Speech Prompt. Check the features and restrictions to see if this is possible in your destination country. + /// + public string from { get; set; } + /// + /// Required. A UTF-8 and URL encoded message that is sent to your user. This message can be up to 1500 characters). Text-To-Speech Messages explains how to control the pacing of your message. + /// + public string text { get; set; } + /// + /// Optional. The language used to synthesize the message. Combine this parameter with voice to control the language, accent and gender used to deliver this TTS. The default language is en-us. Note: ensure that lg matches the language you have written in text. + /// + public string lg { get; set; } + /// + /// Optional. The gender of the voice used for the TTS. Possible values are female or male. The default value is female. + /// + public string voice { get; set; } + /// + /// Optional. Define how many times you want to repeat the text message. You can set a message to be repeated up to 10 times. The default is 1. + /// + public string repeat { get; set; } + /// + /// Optional. Control the way this TTS is handled if it is picked up by an answering machine. Set to: + /// true - play the message after the beep. If the answer phone does not issue a beep the call is closed. + /// hangup - hang up. The value of the status sent in the Text-To-Speech Return Parameters is set to machine. + /// + public string machine_detection { get; set; } + /// + /// Optional. The time to check if this TTS has been answered by a machine. Possible values range from 400ms to the default value of 10000ms. If you set a number outside this range the machine_timeout is set to the default value. + /// + public string machine_timeout { get; set; } + /// + /// Optional. Nexmo sends the Text-To-Speech Return Parameters to this URL to tell your App how the call was executed. + /// + public string callback { get; set; } + /// + /// Optional. The HTTP method used to send the status message to your callback. Must be GET (default) or POST. + /// + public string callback_method { get; set; } + } + + public class TextToSpeechRequestResponse + { + /// + /// An alphanumeric unique call identifier of up to 40 characters. + /// + public string call_id { get; set; } + /// + /// The phone number the call was made to. + /// + public string to { get; set; } + /// + /// Shows if your request has been sent successfully to Nexmo, or the reason why it could not be processed. See Response codes for more information. + /// + public string status { get; set; } + /// + /// If status is not 0 this message explains the issue encountered. + /// + public string error_text { get; set; } + } + + public class TextToSpeechReturn + { + /// + /// The Nexmo ID for this TTS or TTS Prompt. + /// + public string call_id { get; set; } + /// + /// The phone number this TTS or TTS Prompt was sent to. + /// + public string to { get; set; } + /// + /// One of the following: + /// ok - TTS terminated normally. + /// failed - TTS failed. + /// error - an error occurred during the TTS. + /// blocked - TTS has been blocked. + /// machine - TTS has been stopped because you set machine_detection to hangup. + /// + public string status { get; set; } + /// + /// The expected value is out for a TTS or TTS Prompt. + /// + public string call_direction { get; set; } + /// + /// The total cost charged to your account in EUR. + /// + public string call_price { get; set; } + /// + /// Price-per-minute in EUR. + /// + public string call_rate { get; set; } + /// + /// Duration of the call in seconds. + /// + public string call_duration { get; set; } + /// + /// The time you sent the TTS or TTS Prompt request. Printed in the following format: YYYY/MM/DD HH:MM:SS. + /// + public string call_request { get; set; } + /// + /// The time the TTS or TTS Prompt started. Printed in the following format: YYYY/MM/DD HH:MM:SS. + /// + public string call_start { get; set; } + /// + /// The time the TTS or TTS Prompt ended. Printed in the following format: YYYY/MM/DD HH:MM:SS. + /// + public string call_end { get; set; } + /// + /// An identifier of the carrier network used by the recipient. This value is optional. + /// + public string network_code { get; set; } + /// + /// The values entered by the end user in response to a TTS Prompt. + /// Possible values are: + /// digits - Either: + /// Capture - the digits entered by the user. + /// Confirm - if the user entered the PIN correctly, this is the pin_code you set in the request. + /// nothing - the user did not press any digits. + /// undefined - Either: + /// The user had 3 failed attempts to enter the pin_code you set in the request. + /// There was a DTMF issue during this TTS Prompt. + /// + public string digits { get; set; } + } + + public class TextToSpeechPromptCallCommand + { + /// + /// The single phone number to call for each request. This number must be in international format, that is E.164. For example, when sending to the uk: to=447525856424 + /// + public string to { get; set; } + /// + /// A UTF-8 and URL encoded message for your user. This message can be up to 1500 characters. Text-To-Speech Messages explains how to control the pacing of your message. + /// + public string text { get; set; } + /// + /// The language used to read the message. Combine this parameter with voice to control the language, accent and gender used to deliver this Text-To-Speech Prompt. The default language is en-us. Note: ensure that lg matches the language you have written in text. + /// + public string lg { get; set; } + /// + /// The gender of the voice used for the Text-To-Speech. Possible values are female or male. The default value is female + /// + public string voice { get; set; } + /// + /// Nexmo sends the Text-To-Speech Return Parameters to this URL to tell your App how the call was executed. + /// + public string callback { get; set; } + /// + /// The HTTP method used to send the status message to your callback. Must be GET (default) or POST. + /// + public string callback_method { get; set; } + /// + /// The number of digits in pin_code. The maximum number is 16, the default is 4. The pin_code must have the number of digits you set here. Extra digits entered by the user are ignored. + /// + public string max_digits { get; set; } + /// + /// The message played after your user has: + /// Capture - entered some digits. + /// Confirm - entered pin_code correctly. + /// This string is up to 500 characters of UTF-8 and URL encoded text. For example, write D%c3%a9j%c3%a0+vu for Déjà vu. + /// + public string bye_text { get; set; } + } + + public class TextToSpeechPromptConfirmCommand : TextToSpeechPromptCallCommand + { + /// + /// The digits your user should enter in a Confirm Text-To-Speech Prompt. For example: 1234. The number of digits in this PIN must be the same as max_digits. + /// + public string pin_code { get; set; } + /// + /// The message played if your user fails to enter pin_code correctly. If your user has more attempts to enter pin_code, text is played immediately after failed_text. Leave a pause at the end of this message for a more natural sound. This string is up to 500 characters of UTF-8 and URL encoded text. For example, write D%c3%a9j%c3%a0+vu for Déjà vu. + /// + public string failed_text { get; set; } + } + + public class TextToSpeechPromptCaptureCommand : TextToSpeechPromptCallCommand + { + /// + /// A voice-enabled virtual number associated with your Nexmo account. This number is displayed on your user's handset during this Text-To-Speech or Text-To-Speech Prompt. Check the features and restrictions to see if this is possible in your destination country. + /// + public string from { get; set; } + /// + /// The number of times text can be repeated after your user picks up the Text-To-Speech Prompt. You can set text to be repeated up to 10 times. The default is 1. + /// + public string repeat { get; set; } + /// + /// Control the way this Text-To-Speech Prompt is handled if it is picked up by an answering machine. Set to: + /// true — play text after the beep. If the answerphone does not issue a beep the Text-To-Speech Prompt is closed. + /// hangup — hang up. The value of the status status code send to the callback url is set to machine. + /// + public string machine_detection { get; set; } + /// + /// The time to check if this Text-To-Speech Prompt has been answered by a machine. Possible values range from 400ms to the default value of 10000ms. If you set a number outside this range, machine_timeout is set to the default value. + /// + public string machine_timeout { get; set; } + } + + public static CallRequestResponse Call(CallCommand cmd) + { + var jsonstring = ApiRequest.DoPostRequest(ApiRequest.GetBaseUriFor(typeof(Voice), "/call/json"), cmd); + + return JsonConvert.DeserializeObject(jsonstring); + } - // TODO: sip + public static CallReturn ParseCallReturn(string json) + { + return JsonConvert.DeserializeObject(json); + } + + public static TextToSpeechRequestResponse TextToSpeech(TextToSpeechCallCommand cmd) + { + var jsonstring = ApiRequest.DoPostRequest(ApiRequest.GetBaseUriFor(typeof(NumberVerify), "/tts/json"), cmd); + + return JsonConvert.DeserializeObject(jsonstring); + } + + public static TextToSpeechReturn ParseTextToSpeechReturn(string json) + { + return JsonConvert.DeserializeObject(json); + } + + public static TextToSpeechRequestResponse TextToSpeechPrompt(TextToSpeechPromptCallCommand cmd) + { + var confirm = cmd as TextToSpeechPromptConfirmCommand; + if (confirm != null) + { + var jsonstring = ApiRequest.DoPostRequest(ApiRequest.GetBaseUriFor(typeof(NumberVerify), "/tts-prompt/json"), confirm); + return JsonConvert.DeserializeObject(jsonstring); + } + var capture = cmd as TextToSpeechPromptCaptureCommand; + if (capture != null) + { + var jsonstring = ApiRequest.DoPostRequest(ApiRequest.GetBaseUriFor(typeof(NumberVerify), "/tts-prompt/json"), capture); + return JsonConvert.DeserializeObject(jsonstring); + } + throw new ArgumentException("cmd must be either confirm or capture"); + } } } \ No newline at end of file diff --git a/Nexmo.Web.Sample/Content/voiceDemo.xml b/Nexmo.Web.Sample/Content/voiceDemo.xml new file mode 100644 index 000000000..b12a0a3c1 --- /dev/null +++ b/Nexmo.Web.Sample/Content/voiceDemo.xml @@ -0,0 +1,14 @@ + + +
+ + + Hello. + + +
+
\ No newline at end of file diff --git a/Nexmo.Web.Sample/Controllers/Api/TTSController.cs b/Nexmo.Web.Sample/Controllers/Api/TTSController.cs new file mode 100644 index 000000000..5e1129723 --- /dev/null +++ b/Nexmo.Web.Sample/Controllers/Api/TTSController.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Runtime.Caching; +using System.Web.Http; +using System.Web.Mvc; +using Nexmo.Api; + +namespace Nexmo.Web.Sample.Controllers.Api +{ + public class TTSController : ApiController + { + readonly object _cacheLock = new object(); + + public ActionResult Get([FromUri]Voice.TextToSpeechReturn response) + { + lock (_cacheLock) + { + var callReturns = new List(); + const string cachekey = "tts_call_returns"; + if (MemoryCache.Default.Contains(cachekey)) + { + callReturns = (List)MemoryCache.Default.Get(cachekey); + } + callReturns.Add(response); + MemoryCache.Default.Set(cachekey, callReturns, DateTimeOffset.MaxValue); + } + + return new HttpStatusCodeResult(HttpStatusCode.OK); + } + } +} \ No newline at end of file diff --git a/Nexmo.Web.Sample/Controllers/Api/VoiceController.cs b/Nexmo.Web.Sample/Controllers/Api/VoiceController.cs new file mode 100644 index 000000000..8b3a7777d --- /dev/null +++ b/Nexmo.Web.Sample/Controllers/Api/VoiceController.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Runtime.Caching; +using System.Web.Http; +using System.Web.Mvc; +using Nexmo.Api; + +namespace Nexmo.Web.Sample.Controllers.Api +{ + public class VoiceController : ApiController + { + readonly object _cacheLock = new object(); + + public ActionResult Get([FromUri]Voice.CallReturn response) + { + lock (_cacheLock) + { + var callReturns = new List(); + const string cachekey = "voice_call_returns"; + if (MemoryCache.Default.Contains(cachekey)) + { + callReturns = (List)MemoryCache.Default.Get(cachekey); + } + callReturns.Add(response); + MemoryCache.Default.Set(cachekey, callReturns, DateTimeOffset.MaxValue); + } + + return new HttpStatusCodeResult(HttpStatusCode.OK); + } + } +} \ No newline at end of file diff --git a/Nexmo.Web.Sample/Controllers/HomeController.cs b/Nexmo.Web.Sample/Controllers/HomeController.cs index a3a89ec22..cc2aa90bd 100644 --- a/Nexmo.Web.Sample/Controllers/HomeController.cs +++ b/Nexmo.Web.Sample/Controllers/HomeController.cs @@ -10,28 +10,23 @@ namespace Nexmo.Web.Sample.Controllers { public class HomeController : Controller { - public ActionResult Index() + private static List GetFromCacheAndClear(string key) { - const string receiptKey = "sms_receipts"; - var receipts = new List(); - if (MemoryCache.Default.Contains(receiptKey)) - { - receipts = (List)MemoryCache.Default.Get(receiptKey); - MemoryCache.Default.Remove(receiptKey); - } - - const string inboundsKey = "sms_inbounds"; - var inbounds = new List(); - if (MemoryCache.Default.Contains(inboundsKey)) - { - inbounds = (List)MemoryCache.Default.Get(inboundsKey); - MemoryCache.Default.Remove(inboundsKey); - } - + var values = new List(); + if (!MemoryCache.Default.Contains(key)) return values; + values = (List)MemoryCache.Default.Get(key); + MemoryCache.Default.Remove(key); + return values; + } + + public ActionResult Index() + { return View(new Actions { - Receipts = receipts, - Inbounds = inbounds + Receipts = GetFromCacheAndClear("sms_receipts"), + Inbounds = GetFromCacheAndClear("sms_inbounds"), + VoiceReturns = GetFromCacheAndClear("voice_call_returns"), + TTSReturns = GetFromCacheAndClear("tts_call_returns") }); } diff --git a/Nexmo.Web.Sample/Models/Actions.cs b/Nexmo.Web.Sample/Models/Actions.cs index 54e6a66b5..65aaff9da 100644 --- a/Nexmo.Web.Sample/Models/Actions.cs +++ b/Nexmo.Web.Sample/Models/Actions.cs @@ -9,6 +9,8 @@ public class Actions public SMS.SMSRequest SMS { get; set; } public List Receipts { get; set; } public List Inbounds { get; set; } + public List VoiceReturns { get; set; } + public List TTSReturns { get; set; } public NumberInsight NI { get; set; } public NumberVerify NV_V { get; set; } diff --git a/Nexmo.Web.Sample/Nexmo.Web.Sample.csproj b/Nexmo.Web.Sample/Nexmo.Web.Sample.csproj index 78f1a74d4..fe54de9e1 100644 --- a/Nexmo.Web.Sample/Nexmo.Web.Sample.csproj +++ b/Nexmo.Web.Sample/Nexmo.Web.Sample.csproj @@ -172,6 +172,8 @@ + + @@ -206,6 +208,9 @@
+ + Designer + diff --git a/Nexmo.Web.Sample/Views/Home/Index.cshtml b/Nexmo.Web.Sample/Views/Home/Index.cshtml index f21fb5290..2bfb3e282 100644 --- a/Nexmo.Web.Sample/Views/Home/Index.cshtml +++ b/Nexmo.Web.Sample/Views/Home/Index.cshtml @@ -37,6 +37,24 @@ { [@i.message_timestamp] @i.msisdn => @i.to = @i.text } +

Voice Returns

+@if (!Model.VoiceReturns.Any()) +{ + No call returns +} +@foreach (var i in Model.VoiceReturns) +{ + [@i.to] (@i.status) @i.CallDuration @i.CallPrice +} +

TTS Returns

+@if (!Model.TTSReturns.Any()) +{ + No TTS returns +} +@foreach (var i in Model.TTSReturns) +{ + [@i.to] (@i.status) @i.call_direction @i.call_price [@i.digits] +}

Number Insight

diff --git a/README.md b/README.md index a857436f4..0c93dd28d 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,5 @@ API support status: * [X] Receipt * [X] Inbound * Voice - * [ ] Call - * [ ] TTS/TTS Prompt - * [ ] SIP \ No newline at end of file + * [X] Call + * [X] TTS/TTS Prompt