diff --git a/CHANGELOG.md b/CHANGELOG.md index 7573d86..2ea73de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [4.1.6](https://github.com/plivo/plivo-node/releases/tag/v4.1.6)(2019-11-14) +- Fix list APIs to return meta in response. + +## [4.1.5](https://github.com/plivo/plivo-node/releases/tag/v4.1.5)(2019-11-13) +- Add GetInput XML support + +## [4.1.4](https://github.com/plivo/plivo-node/releases/tag/v4.1.4)(2019-11-06) +- Add SSML support + ## [4.1.3](https://github.com/plivo/plivo-node/releases/tag/v4.1.3)(2019-07-30) - Add proxy-support for Signature Validation - Add HTTP status codes in responses diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..b2874e8 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,7 @@ +#!groovy + +@Library('plivo_standard_libs@sdks') _ + +sdksPipeline ([ + buildContainer: 'plivo/jenkins-ci/node-sdk:latest' +]) diff --git a/ci/config.yml b/ci/config.yml new file mode 100644 index 0000000..71c3e3a --- /dev/null +++ b/ci/config.yml @@ -0,0 +1,7 @@ +--- +parent: central +serviceName: plivo-node +language: node-sdk +build: + command: | + npm install diff --git a/examples/speak.js b/examples/speak.js new file mode 100644 index 0000000..62f6526 --- /dev/null +++ b/examples/speak.js @@ -0,0 +1,59 @@ +var Plivo = require('../dist/rest/client.js'); +var response = new Plivo.Response(); + +const speak = response.addSpeak('hello', { + voice: "Polly.Aditi", + language: 'en-IN', +}); + +speak.addLang('आप लोग कैसे हो', { + 'xml:lang': 'hi-IN', +}); + +const emphasis = speak.addEmphasis('emphasis', { + 'level': 'x-strong', +}) +emphasis.addBreak(); +emphasis.addLang('language!!!'); + +// Mixed-content without tag +speak.addText('extra text'); + +speak.addLang('how are you people?', { + 'xml:lang': 'en-IN', +}); + +speak.addSayAs('30101996', { + 'interpret-as': 'date', + 'format': 'dmy' +}); + +speak.addLang('well well well').addBreak(); + +speak.addLang('no no no'); + +speak.addProsody('कैसे हो'); + +speak.addSayAs('S A Y A S', { + 'interpret-as': 'characters', +}); + +speak.addP('this is p tag'); + +speak.addPhoneme('this is Phoneme', { + 'alphabet': 'ipa', +}); + +speak.addS('this is s tag'); + +speak.addSub('Hg', { + alias: 'Mercury', +}); + +speak.addText('Past of read is') + +speak.addW('read', { + 'role': 'amazon:VBD', +}); + +console.log(response.toXML()); \ No newline at end of file diff --git a/lib/base.js b/lib/base.js index aa7701b..4597b8e 100644 --- a/lib/base.js +++ b/lib/base.js @@ -113,7 +113,7 @@ export class PlivoResourceInterface { client('GET', action, params) .then(response => { let objects = []; - Object.defineProperty(objects, 'meta', { value: response.body.meta }); + Object.defineProperty(objects, 'meta', { value: response.body.meta, enumerable: true }); response.body.objects.forEach(item => { objects.push(new Klass(client, item)); }); diff --git a/lib/rest/utils.js b/lib/rest/utils.js index 27a9bf7..cf3793b 100644 --- a/lib/rest/utils.js +++ b/lib/rest/utils.js @@ -3,6 +3,7 @@ import _snakeCase from 'lodash/snakeCase'; import _mapKeys from 'lodash/mapKeys'; import _mapValues from 'lodash/mapValues'; import _map from 'lodash/map'; +import { parseString } from 'xml2js'; function recursivelyRenameObject(object, renameFunc) { if (!(object instanceof Object)) { @@ -54,3 +55,20 @@ export function camelCaseRequestWrapper(requestFunc) { } } +export function validateSpeakAttributes(content, voice) { + + if (!voice || ['MAN', 'WOMAN'].indexOf(voice) != -1) { + return { success: true }; + } + + var voiceParts = voice.split('.'); + if (voiceParts.length != 2 || voiceParts[0] != 'Polly') { + return { + success: false, msg: "Invalid voice " + voice + '.' + }; + }; + return { + success: true, + } +} + diff --git a/lib/utils/exceptions.js b/lib/utils/exceptions.js index 0900363..04805f2 100644 --- a/lib/utils/exceptions.js +++ b/lib/utils/exceptions.js @@ -1,6 +1,7 @@ -export class PlivoRestError extends Error {} -export class ResourceNotFoundError extends PlivoRestError {} -export class ServerError extends PlivoRestError {} -export class InvalidRequestError extends PlivoRestError {} -export class PlivoXMLError extends PlivoRestError {} -export class AuthenticationError extends PlivoRestError {} +export class PlivoRestError extends Error { } +export class ResourceNotFoundError extends PlivoRestError { } +export class ServerError extends PlivoRestError { } +export class InvalidRequestError extends PlivoRestError { } +export class PlivoXMLError extends PlivoRestError { } +export class PlivoXMLValidationError extends PlivoRestError { } +export class AuthenticationError extends PlivoRestError { } diff --git a/lib/utils/plivoxml.js b/lib/utils/plivoxml.js index 94f5266..79ec786 100644 --- a/lib/utils/plivoxml.js +++ b/lib/utils/plivoxml.js @@ -1,6 +1,8 @@ var qs = require('querystring'); var xmlBuilder = require('xmlbuilder'); var util = require('util'); +var plivoUtils = require('./../rest/utils'); +import * as Exceptions from './exceptions'; var jsonStringifier = require('./jsonStrinfigier'); export class PlivoXMLError extends Error { } @@ -11,7 +13,7 @@ export class PlivoXMLError extends Error { } */ export function Response() { this.element = 'Response'; - this.nestables = ['Speak', 'Play', 'GetDigits', 'Record', 'Dial', 'Message', + this.nestables = ['Speak', 'Play', 'GetDigits', 'GetInput', 'Record', 'Dial', 'Message', 'Redirect', 'Wait', 'Hangup', 'PreAnswer', 'Conference', 'DTMF']; this.valid_attributes = []; this.elem = xmlBuilder.begin().ele(this.element); @@ -162,6 +164,31 @@ Response.prototype = { return this.add(new GetDigits(Response), '', attributes); }, + /** + * Add a GetInput element + * @method + * @param {object} attributes + * @param {string} [attributes.action] + * @param {string} [attributes.method] + * @param {string} [attributes.inputType] + * @param {number} [attributes.executionTimeout] + * @param {number} [attributes.digitEndTimeout] + * @param {number} [attributes.speechEndTimeout] + * @param {string} [attributes.finishOnKey] + * @param {number} [attributes.numDigits] + * @param {string} [attributes.speechModel] + * @param {string} [attributes.hints] + * @param {string} [attributes.language] + * @param {string} [attributes.interimSpeechResultsCallback] + * @param {string} [attributes.interimSpeechResultsCallbackMethod] + * @param {boolean} [attributes.log] + * @param {boolean} [attributes.redirect] + * @param {string} [attributes.profanityFilter] + */ + addGetInput: function (attributes) { + return this.add(new GetInput(Response), '', attributes); + }, + /** * Add a Hangup element * @method @@ -252,22 +279,141 @@ Response.prototype = { * @param {number} [attributes.loop] */ addSpeak: function (body, attributes) { - //Convert accented characters to numerical references first - let accentedCharsList = { - "Á": "A", "Ă": "A", "Ắ": "A", "Ặ": "A", "Ằ": "A", "Ẳ": "A", "Ẵ": "A", "Ǎ": "A", "Â": "A", "Ấ": "A", "Ậ": "A", "Ầ": "A", "Ẩ": "A", "Ẫ": "A", "Ä": "A", "Ǟ": "A", "Ȧ": "A", "Ǡ": "A", "Ạ": "A", "Ȁ": "A", "À": "A", "Ả": "A", "Ȃ": "A", "Ā": "A", "Ą": "A", "Å": "A", "Ǻ": "A", "Ḁ": "A", "Ⱥ": "A", "Ã": "A", "Ꜳ": "AA", "Æ": "AE", "Ǽ": "AE", "Ǣ": "AE", "Ꜵ": "AO", "Ꜷ": "AU", "Ꜹ": "AV", "Ꜻ": "AV", "Ꜽ": "AY", "Ḃ": "B", "Ḅ": "B", "Ɓ": "B", "Ḇ": "B", "Ƀ": "B", "Ƃ": "B", "Ć": "C", "Č": "C", "Ç": "C", "Ḉ": "C", "Ĉ": "C", "Ċ": "C", "Ƈ": "C", "Ȼ": "C", "Ď": "D", "Ḑ": "D", "Ḓ": "D", "Ḋ": "D", "Ḍ": "D", "Ɗ": "D", "Ḏ": "D", "Dz": "D", "Dž": "D", "Đ": "D", "Ƌ": "D", "DZ": "DZ", "DŽ": "DZ", "É": "E", "Ĕ": "E", "Ě": "E", "Ȩ": "E", "Ḝ": "E", "Ê": "E", "Ế": "E", "Ệ": "E", "Ề": "E", "Ể": "E", "Ễ": "E", "Ḙ": "E", "Ë": "E", "Ė": "E", "Ẹ": "E", "Ȅ": "E", "È": "E", "Ẻ": "E", "Ȇ": "E", "Ē": "E", "Ḗ": "E", "Ḕ": "E", "Ę": "E", "Ɇ": "E", "Ẽ": "E", "Ḛ": "E", "Ꝫ": "ET", "Ḟ": "F", "Ƒ": "F", "Ǵ": "G", "Ğ": "G", "Ǧ": "G", "Ģ": "G", "Ĝ": "G", "Ġ": "G", "Ɠ": "G", "Ḡ": "G", "Ǥ": "G", "Ḫ": "H", "Ȟ": "H", "Ḩ": "H", "Ĥ": "H", "Ⱨ": "H", "Ḧ": "H", "Ḣ": "H", "Ḥ": "H", "Ħ": "H", "Í": "I", "Ĭ": "I", "Ǐ": "I", "Î": "I", "Ï": "I", "Ḯ": "I", "İ": "I", "Ị": "I", "Ȉ": "I", "Ì": "I", "Ỉ": "I", "Ȋ": "I", "Ī": "I", "Į": "I", "Ɨ": "I", "Ĩ": "I", "Ḭ": "I", "Ꝺ": "D", "Ꝼ": "F", "Ᵹ": "G", "Ꞃ": "R", "Ꞅ": "S", "Ꞇ": "T", "Ꝭ": "IS", "Ĵ": "J", "Ɉ": "J", "Ḱ": "K", "Ǩ": "K", "Ķ": "K", "Ⱪ": "K", "Ꝃ": "K", "Ḳ": "K", "Ƙ": "K", "Ḵ": "K", "Ꝁ": "K", "Ꝅ": "K", "Ĺ": "L", "Ƚ": "L", "Ľ": "L", "Ļ": "L", "Ḽ": "L", "Ḷ": "L", "Ḹ": "L", "Ⱡ": "L", "Ꝉ": "L", "Ḻ": "L", "Ŀ": "L", "Ɫ": "L", "Lj": "L", "Ł": "L", "LJ": "LJ", "Ḿ": "M", "Ṁ": "M", "Ṃ": "M", "Ɱ": "M", "Ń": "N", "Ň": "N", "Ņ": "N", "Ṋ": "N", "Ṅ": "N", "Ṇ": "N", "Ǹ": "N", "Ɲ": "N", "Ṉ": "N", "Ƞ": "N", "Nj": "N", "Ñ": "N", "NJ": "NJ", "Ó": "O", "Ŏ": "O", "Ǒ": "O", "Ô": "O", "Ố": "O", "Ộ": "O", "Ồ": "O", "Ổ": "O", "Ỗ": "O", "Ö": "O", "Ȫ": "O", "Ȯ": "O", "Ȱ": "O", "Ọ": "O", "Ő": "O", "Ȍ": "O", "Ò": "O", "Ỏ": "O", "Ơ": "O", "Ớ": "O", "Ợ": "O", "Ờ": "O", "Ở": "O", "Ỡ": "O", "Ȏ": "O", "Ꝋ": "O", "Ꝍ": "O", "Ō": "O", "Ṓ": "O", "Ṑ": "O", "Ɵ": "O", "Ǫ": "O", "Ǭ": "O", "Ø": "O", "Ǿ": "O", "Õ": "O", "Ṍ": "O", "Ṏ": "O", "Ȭ": "O", "Ƣ": "OI", "Ꝏ": "OO", "Ɛ": "E", "Ɔ": "O", "Ȣ": "OU", "Ṕ": "P", "Ṗ": "P", "Ꝓ": "P", "Ƥ": "P", "Ꝕ": "P", "Ᵽ": "P", "Ꝑ": "P", "Ꝙ": "Q", "Ꝗ": "Q", "Ŕ": "R", "Ř": "R", "Ŗ": "R", "Ṙ": "R", "Ṛ": "R", "Ṝ": "R", "Ȑ": "R", "Ȓ": "R", "Ṟ": "R", "Ɍ": "R", "Ɽ": "R", "Ꜿ": "C", "Ǝ": "E", "Ś": "S", "Ṥ": "S", "Š": "S", "Ṧ": "S", "Ş": "S", "Ŝ": "S", "Ș": "S", "Ṡ": "S", "Ṣ": "S", "Ṩ": "S", "Ť": "T", "Ţ": "T", "Ṱ": "T", "Ț": "T", "Ⱦ": "T", "Ṫ": "T", "Ṭ": "T", "Ƭ": "T", "Ṯ": "T", "Ʈ": "T", "Ŧ": "T", "Ɐ": "A", "Ꞁ": "L", "Ɯ": "M", "Ʌ": "V", "Ꜩ": "TZ", "Ú": "U", "Ŭ": "U", "Ǔ": "U", "Û": "U", "Ṷ": "U", "Ü": "U", "Ǘ": "U", "Ǚ": "U", "Ǜ": "U", "Ǖ": "U", "Ṳ": "U", "Ụ": "U", "Ű": "U", "Ȕ": "U", "Ù": "U", "Ủ": "U", "Ư": "U", "Ứ": "U", "Ự": "U", "Ừ": "U", "Ử": "U", "Ữ": "U", "Ȗ": "U", "Ū": "U", "Ṻ": "U", "Ų": "U", "Ů": "U", "Ũ": "U", "Ṹ": "U", "Ṵ": "U", "Ꝟ": "V", "Ṿ": "V", "Ʋ": "V", "Ṽ": "V", "Ꝡ": "VY", "Ẃ": "W", "Ŵ": "W", "Ẅ": "W", "Ẇ": "W", "Ẉ": "W", "Ẁ": "W", "Ⱳ": "W", "Ẍ": "X", "Ẋ": "X", "Ý": "Y", "Ŷ": "Y", "Ÿ": "Y", "Ẏ": "Y", "Ỵ": "Y", "Ỳ": "Y", "Ƴ": "Y", "Ỷ": "Y", "Ỿ": "Y", "Ȳ": "Y", "Ɏ": "Y", "Ỹ": "Y", "Ź": "Z", "Ž": "Z", "Ẑ": "Z", "Ⱬ": "Z", "Ż": "Z", "Ẓ": "Z", "Ȥ": "Z", "Ẕ": "Z", "Ƶ": "Z", "IJ": "IJ", "Œ": "OE", "ᴀ": "A", "ᴁ": "AE", "ʙ": "B", "ᴃ": "B", "ᴄ": "C", "ᴅ": "D", "ᴇ": "E", "ꜰ": "F", "ɢ": "G", "ʛ": "G", "ʜ": "H", "ɪ": "I", "ʁ": "R", "ᴊ": "J", "ᴋ": "K", "ʟ": "L", "ᴌ": "L", "ᴍ": "M", "ɴ": "N", "ᴏ": "O", "ɶ": "OE", "ᴐ": "O", "ᴕ": "OU", "ᴘ": "P", "ʀ": "R", "ᴎ": "N", "ᴙ": "R", "ꜱ": "S", "ᴛ": "T", "ⱻ": "E", "ᴚ": "R", "ᴜ": "U", "ᴠ": "V", "ᴡ": "W", "ʏ": "Y", "ᴢ": "Z", "á": "a", "ă": "a", "ắ": "a", "ặ": "a", "ằ": "a", "ẳ": "a", "ẵ": "a", "ǎ": "a", "â": "a", "ấ": "a", "ậ": "a", "ầ": "a", "ẩ": "a", "ẫ": "a", "ä": "a", "ǟ": "a", "ȧ": "a", "ǡ": "a", "ạ": "a", "ȁ": "a", "à": "a", "ả": "a", "ȃ": "a", "ā": "a", "ą": "a", "ᶏ": "a", "ẚ": "a", "å": "a", "ǻ": "a", "ḁ": "a", "ⱥ": "a", "ã": "a", "ꜳ": "aa", "æ": "ae", "ǽ": "ae", "ǣ": "ae", "ꜵ": "ao", "ꜷ": "au", "ꜹ": "av", "ꜻ": "av", "ꜽ": "ay", "ḃ": "b", "ḅ": "b", "ɓ": "b", "ḇ": "b", "ᵬ": "b", "ᶀ": "b", "ƀ": "b", "ƃ": "b", "ɵ": "o", "ć": "c", "č": "c", "ç": "c", "ḉ": "c", "ĉ": "c", "ɕ": "c", "ċ": "c", "ƈ": "c", "ȼ": "c", "ď": "d", "ḑ": "d", "ḓ": "d", "ȡ": "d", "ḋ": "d", "ḍ": "d", "ɗ": "d", "ᶑ": "d", "ḏ": "d", "ᵭ": "d", "ᶁ": "d", "đ": "d", "ɖ": "d", "ƌ": "d", "ı": "i", "ȷ": "j", "ɟ": "j", "ʄ": "j", "dz": "dz", "dž": "dz", "é": "e", "ĕ": "e", "ě": "e", "ȩ": "e", "ḝ": "e", "ê": "e", "ế": "e", "ệ": "e", "ề": "e", "ể": "e", "ễ": "e", "ḙ": "e", "ë": "e", "ė": "e", "ẹ": "e", "ȅ": "e", "è": "e", "ẻ": "e", "ȇ": "e", "ē": "e", "ḗ": "e", "ḕ": "e", "ⱸ": "e", "ę": "e", "ᶒ": "e", "ɇ": "e", "ẽ": "e", "ḛ": "e", "ꝫ": "et", "ḟ": "f", "ƒ": "f", "ᵮ": "f", "ᶂ": "f", "ǵ": "g", "ğ": "g", "ǧ": "g", "ģ": "g", "ĝ": "g", "ġ": "g", "ɠ": "g", "ḡ": "g", "ᶃ": "g", "ǥ": "g", "ḫ": "h", "ȟ": "h", "ḩ": "h", "ĥ": "h", "ⱨ": "h", "ḧ": "h", "ḣ": "h", "ḥ": "h", "ɦ": "h", "ẖ": "h", "ħ": "h", "ƕ": "hv", "í": "i", "ĭ": "i", "ǐ": "i", "î": "i", "ï": "i", "ḯ": "i", "ị": "i", "ȉ": "i", "ì": "i", "ỉ": "i", "ȋ": "i", "ī": "i", "į": "i", "ᶖ": "i", "ɨ": "i", "ĩ": "i", "ḭ": "i", "ꝺ": "d", "ꝼ": "f", "ᵹ": "g", "ꞃ": "r", "ꞅ": "s", "ꞇ": "t", "ꝭ": "is", "ǰ": "j", "ĵ": "j", "ʝ": "j", "ɉ": "j", "ḱ": "k", "ǩ": "k", "ķ": "k", "ⱪ": "k", "ꝃ": "k", "ḳ": "k", "ƙ": "k", "ḵ": "k", "ᶄ": "k", "ꝁ": "k", "ꝅ": "k", "ĺ": "l", "ƚ": "l", "ɬ": "l", "ľ": "l", "ļ": "l", "ḽ": "l", "ȴ": "l", "ḷ": "l", "ḹ": "l", "ⱡ": "l", "ꝉ": "l", "ḻ": "l", "ŀ": "l", "ɫ": "l", "ᶅ": "l", "ɭ": "l", "ł": "l", "lj": "lj", "ſ": "s", "ẜ": "s", "ẛ": "s", "ẝ": "s", "ḿ": "m", "ṁ": "m", "ṃ": "m", "ɱ": "m", "ᵯ": "m", "ᶆ": "m", "ń": "n", "ň": "n", "ņ": "n", "ṋ": "n", "ȵ": "n", "ṅ": "n", "ṇ": "n", "ǹ": "n", "ɲ": "n", "ṉ": "n", "ƞ": "n", "ᵰ": "n", "ᶇ": "n", "ɳ": "n", "ñ": "n", "nj": "nj", "ó": "o", "ŏ": "o", "ǒ": "o", "ô": "o", "ố": "o", "ộ": "o", "ồ": "o", "ổ": "o", "ỗ": "o", "ö": "o", "ȫ": "o", "ȯ": "o", "ȱ": "o", "ọ": "o", "ő": "o", "ȍ": "o", "ò": "o", "ỏ": "o", "ơ": "o", "ớ": "o", "ợ": "o", "ờ": "o", "ở": "o", "ỡ": "o", "ȏ": "o", "ꝋ": "o", "ꝍ": "o", "ⱺ": "o", "ō": "o", "ṓ": "o", "ṑ": "o", "ǫ": "o", "ǭ": "o", "ø": "o", "ǿ": "o", "õ": "o", "ṍ": "o", "ṏ": "o", "ȭ": "o", "ƣ": "oi", "ꝏ": "oo", "ɛ": "e", "ᶓ": "e", "ɔ": "o", "ᶗ": "o", "ȣ": "ou", "ṕ": "p", "ṗ": "p", "ꝓ": "p", "ƥ": "p", "ᵱ": "p", "ᶈ": "p", "ꝕ": "p", "ᵽ": "p", "ꝑ": "p", "ꝙ": "q", "ʠ": "q", "ɋ": "q", "ꝗ": "q", "ŕ": "r", "ř": "r", "ŗ": "r", "ṙ": "r", "ṛ": "r", "ṝ": "r", "ȑ": "r", "ɾ": "r", "ᵳ": "r", "ȓ": "r", "ṟ": "r", "ɼ": "r", "ᵲ": "r", "ᶉ": "r", "ɍ": "r", "ɽ": "r", "ↄ": "c", "ꜿ": "c", "ɘ": "e", "ɿ": "r", "ś": "s", "ṥ": "s", "š": "s", "ṧ": "s", "ş": "s", "ŝ": "s", "ș": "s", "ṡ": "s", "ṣ": "s", "ṩ": "s", "ʂ": "s", "ᵴ": "s", "ᶊ": "s", "ȿ": "s", "ɡ": "g", "ᴑ": "o", "ᴓ": "o", "ᴝ": "u", "ť": "t", "ţ": "t", "ṱ": "t", "ț": "t", "ȶ": "t", "ẗ": "t", "ⱦ": "t", "ṫ": "t", "ṭ": "t", "ƭ": "t", "ṯ": "t", "ᵵ": "t", "ƫ": "t", "ʈ": "t", "ŧ": "t", "ᵺ": "th", "ɐ": "a", "ᴂ": "ae", "ǝ": "e", "ᵷ": "g", "ɥ": "h", "ʮ": "h", "ʯ": "h", "ᴉ": "i", "ʞ": "k", "ꞁ": "l", "ɯ": "m", "ɰ": "m", "ᴔ": "oe", "ɹ": "r", "ɻ": "r", "ɺ": "r", "ⱹ": "r", "ʇ": "t", "ʌ": "v", "ʍ": "w", "ʎ": "y", "ꜩ": "tz", "ú": "u", "ŭ": "u", "ǔ": "u", "û": "u", "ṷ": "u", "ü": "u", "ǘ": "u", "ǚ": "u", "ǜ": "u", "ǖ": "u", "ṳ": "u", "ụ": "u", "ű": "u", "ȕ": "u", "ù": "u", "ủ": "u", "ư": "u", "ứ": "u", "ự": "u", "ừ": "u", "ử": "u", "ữ": "u", "ȗ": "u", "ū": "u", "ṻ": "u", "ų": "u", "ᶙ": "u", "ů": "u", "ũ": "u", "ṹ": "u", "ṵ": "u", "ᵫ": "ue", "ꝸ": "um", "ⱴ": "v", "ꝟ": "v", "ṿ": "v", "ʋ": "v", "ᶌ": "v", "ⱱ": "v", "ṽ": "v", "ꝡ": "vy", "ẃ": "w", "ŵ": "w", "ẅ": "w", "ẇ": "w", "ẉ": "w", "ẁ": "w", "ⱳ": "w", "ẘ": "w", "ẍ": "x", "ẋ": "x", "ᶍ": "x", "ý": "y", "ŷ": "y", "ÿ": "y", "ẏ": "y", "ỵ": "y", "ỳ": "y", "ƴ": "y", "ỷ": "y", "ỿ": "y", "ȳ": "y", "ẙ": "y", "ɏ": "y", "ỹ": "y", "ź": "z", "ž": "z", "ẑ": "z", "ʑ": "z", "ⱬ": "z", "ż": "z", "ẓ": "z", "ȥ": "z", "ẕ": "z", "ᵶ": "z", "ᶎ": "z", "ʐ": "z", "ƶ": "z", "ɀ": "z", "ff": "ff", "ffi": "ffi", "ffl": "ffl", "fi": "fi", "fl": "fl", "ij": "ij", "œ": "oe", "st": "st", "ₐ": "a", "ₑ": "e", "ᵢ": "i", "ⱼ": "j", "ₒ": "o", "ᵣ": "r", "ᵤ": "u", "ᵥ": "v", "ₓ": "x" - }; - let newBody = ''; - for (var i = 0; i < body.length; i++) { - if (accentedCharsList[body[i]]) { - newBody += '&#' + body.charCodeAt(i) + ';'; - } else { - newBody += body[i]; - } + let validation; + if (attributes && attributes.voice) { + validation = plivoUtils.validateSpeakAttributes(body, attributes.voice); + } else { + validation = plivoUtils.validateSpeakAttributes(body); + } + var item = this; + if (validation.success == true) { + var result = item.add(new Speak(Response), body, attributes); + return result; + } else { + throw new Exceptions.PlivoXMLValidationError(validation.msg); } - return this.add(new Speak(Response), newBody, attributes); }, + /** + * Add a Break element + * @method + * @param {object} attributes + * @param {string} [attributes.strength] + * @param {string} [attributes.time] + */ + addBreak: function (attributes) { + return this.add(new Break(Response), '', attributes); + }, + + + /** + * Add a Emphasis element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.level] + */ + addEmphasis: function (body, attributes) { + return this.add(new Emphasis(Response), body, attributes); + }, + + /** + * Add a Lang element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.xml:lang] + */ + addLang: function (body, attributes) { + return this.add(new Lang(Response), body, attributes); + }, + + /** + * Add a P element + * @method + * @param {string} body + */ + addP: function (body) { + return this.add(new P(Response), body, {}); + }, + + /** + * Add a Phoneme element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.alphabet] + * @param {string} [attributes.ph] + */ + addPhoneme: function (body, attributes) { + return this.add(new Phoneme(Response), body, attributes); + }, + + /** + * Add a Prosody element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.pitch] + * @param {string} [attributes.rate] + * @param {string} [attributes.volume] + */ + addProsody: function (body, attributes) { + return this.add(new Prosody(Response), body, attributes); + }, + + /** + * Add a S element + * @method + * @param {string} body + */ + addS: function (body) { + return this.add(new S(Response), body, {}); + }, + + /** + * Add a SayAs element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.interpret-as] + * @param {string} [attributes.format] + */ + addSayAs: function (body, attributes) { + return this.add(new SayAs(Response), body, attributes); + }, + + /** + * Add a Sub element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.alias] + */ + addSub: function (body, attributes) { + return this.add(new Sub(Response), body, attributes); + }, + + /** + * Add a W element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.role] + */ + addW: function (body, attributes) { + return this.add(new W(Response), body, attributes); + }, + + /** + * Add a body to the element + * @method + * @param {string} body + */ + addText: function (body) { + return this.elem.txt(body); + }, /** * Add a Wait element @@ -366,6 +512,20 @@ function GetDigits(Response) { } util.inherits(GetDigits, Response); +/** + * GetInput element + * @constructor + */ +function GetInput(Response) { + this.element = 'GetInput'; + this.valid_attributes = ['action', 'method', 'inputType', 'executionTimeout', + 'digitEndTimeout', 'speechEndTimeout', 'finishOnKey', 'numDigits', + 'speechModel', 'hints','language', 'interimSpeechResultsCallback', + 'interimSpeechResultsCallbackMethod', 'log', 'redirect', 'profanityFilter']; + this.nestables = ['Speak', 'Play', 'Wait']; +} +util.inherits(GetInput, Response); + /** * Hangup element * @constructor @@ -445,10 +605,120 @@ util.inherits(Redirect, Response); function Speak(Response) { this.element = 'Speak'; this.valid_attributes = ['voice', 'language', 'loop']; - this.nestables = []; + this.nestables = ['break', 'emphasis', 'lang', 'p', 'phoneme', 'prosody', 's', 'say-as', 'sub', 'w']; } util.inherits(Speak, Response); +/** + * Break element + * @constructor + */ +function Break(Response) { + this.element = 'break'; + this.valid_attributes = ['strength', 'time']; + this.nestables = []; +} +util.inherits(Break, Response); + +/** + * Emphasis element + * @constructor + */ +function Emphasis(Response) { + this.element = 'emphasis'; + this.valid_attributes = ['level']; + this.nestables = ['break', 'emphasis', 'lang', 'phoneme', 'prosody', 'say-as', 'sub', 'w']; +} +util.inherits(Emphasis, Response); + +/** + * Lang element + * @constructor + */ +function Lang(Response) { + this.element = 'lang'; + this.valid_attributes = ['xml:lang']; + this.nestables = ['break', 'emphasis', 'lang', 'p', 'phoneme', 'prosody', 's', 'say-as', 'sub', 'w']; +} +util.inherits(Lang, Response); + +/** + * P element + * @constructor + */ +function P(Response) { + this.element = 'p'; + this.valid_attributes = []; + this.nestables = ['break', 'emphasis', 'lang', 'prosody', 's', 'say-as', 'sub', 'w']; +} +util.inherits(P, Response); + +/** + * SayAs element + * @constructor + */ +function Phoneme(Response) { + this.element = 'phoneme'; + this.valid_attributes = ['alphabet', 'ph']; + this.nestables = []; +} +util.inherits(Phoneme, Response); + +/** + * Prosody element + * @constructor + */ +function Prosody(Response) { + this.element = 'prosody'; + this.valid_attributes = ['pitch', 'rate', 'volume']; + this.nestables = ['break', 'emphasis', 'lang', 'p', 'phoneme', 'prosody', 's', 'say-as', 'sub', 'w']; +} +util.inherits(Prosody, Response); + +/** + * S element + * @constructor + */ +function S(Response) { + this.element = 's'; + this.valid_attributes = []; + this.nestables = ['break', 'emphasis', 'lang', 'phoneme', 'prosody', 'say-as', 'sub', 'w']; +} +util.inherits(S, Response); + +/** + * SayAs element + * @constructor + */ +function SayAs(Response) { + this.element = 'say-as'; + this.valid_attributes = ['interpret-as', 'format']; + this.nestables = []; +} +util.inherits(SayAs, Response); + +/** + * Sub element + * @constructor + */ +function Sub(Response) { + this.element = 'sub'; + this.valid_attributes = ['alias']; + this.nestables = []; +} +util.inherits(Sub, Response); + +/** + * W element + * @constructor + */ +function W(Response) { + this.element = 'w'; + this.valid_attributes = ['role']; + this.nestables = ['break', 'emphasis', 'phoneme', 'prosody', 'say-as', 'sub']; +} +util.inherits(W, Response); + /** * Wait element * @constructor @@ -471,4 +741,3 @@ function DTMF(Response) { } util.inherits(DTMF, Response); - diff --git a/package-lock.json b/package-lock.json index bfdb65a..347cb92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "plivo", - "version": "4.0.5", + "version": "4.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4669,6 +4669,11 @@ "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", @@ -5565,6 +5570,15 @@ "mkdirp": "^0.5.1" } }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, "xmlbuilder": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", diff --git a/package.json b/package.json index 5d5c8c5..93ffbdf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plivo", - "version": "4.1.3", + "version": "4.1.6", "description": "A Node.js SDK to make voice calls and send SMS using Plivo and to generate Plivo XML", "homepage": "https://github.com/plivo/plivo-node", "files": [ @@ -64,6 +64,7 @@ "request": "^2.81.0", "uri-parser": "^1.0.0", "utf8": "^2.1.2", + "xml2js": "^0.4.19", "xmlbuilder": "^9.0.1" } -} \ No newline at end of file +} diff --git a/test/ssml.js b/test/ssml.js new file mode 100644 index 0000000..ee68a06 --- /dev/null +++ b/test/ssml.js @@ -0,0 +1,97 @@ +import assert from 'assert'; +import { Response, Client } from '../lib/rest/client'; +import { PlivoGenericResponse } from '../lib/base.js'; +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('SsmlInterface', function () { + + it('Ssml - Invalid SSML XML Structure', function (done) { + + let response = new Response(); + + // Invalid speak body + let speak_body = ' Here is a number read \ + as a cardinal number: \ + read. \ + Here is a word spelled out: \ + hello.'; + + // response.addSpeak(speak_body, { language: 'Spanish-Castilian', voice: 'Polly.*' }); + response.addSpeak(speak_body, { language: 'Spanish-Castilian', voice: 'Polly.Conchita' }).then((result) => { + done(new Error("Invalid xml should be rejected and should throw error.")); + }).catch((err) => { + assert.equal('Invalid SSML xml structure. Content must be a valid xml.', err.message); + done(); + }); + + }); + + it('Ssml - Invalid SSML Tags', function (done) { + + let response = new Response(); + + // Invalid speak body + let speak_body = ' Here is a number read \ + as a cardinal number: \ + read. \ + Here is a word spelled out: \ + hello.'; + + // response.addSpeak(speak_body, { language: 'Spanish-Castilian', voice: 'Polly.*' }); + response.addSpeak(speak_body, { language: 'Spanish-Castilian', voice: 'Polly.Conchita' }).then((result) => { + done(new Error("Invalid xml tags should be rejected and should throw error.")); + }).catch((err) => { + assert.equal('Ssml tag is not supported.', err.message); + done(); + }); + + }); + + it('Ssml - Invalid Language Validation', function (done) { + + let response = new Response(); + + // Invalid speak body + let speak_body = ' Here is a number'; + + response.addSpeak(speak_body, { language: 'Spanish-Castilian1', voice: 'Polly.Conchita' }).then((result) => { + done(new Error("Unsupported language `Spanish-Castilian1` should be rejected and should throw error.")); + }).catch((err) => { + assert.equal('Invalid language. Language `Spanish-Castilian1` is not supported.', err.message); + done(); + }); + + }); + + it('Ssml - Invalid Language-Voice Combination', function (done) { + + let response = new Response(); + + // Invalid speak body + let speak_body = 'Here is a number'; + + response.addSpeak(speak_body, { language: 'Spanish-Castilian', voice: 'Polly.Maxim' }).then((result) => { + done(new Error("Invalid language voice combination should be rejected")); + }).catch((err) => { + assert.equal(' voice ‘Polly.Maxim’ is not valid. Refer for list of supported voices.', err.message); + done(); + }); + + }); + + it('Ssml - Valid Language-Voice Combination', function (done) { + + let response = new Response(); + + // Invalid speak body + let speak_body = 'Here is a number'; + + response.addSpeak(speak_body, { language: 'Spanish-Castilian', voice: 'Polly.Conchita' }).then((result) => { + done(); + }).catch((err) => { + done('Validate Language Voice combination should be accepted.'); + }); + + + }); +}); diff --git a/test/xml.js b/test/xml.js index 9b935fd..efb2b60 100644 --- a/test/xml.js +++ b/test/xml.js @@ -1,27 +1,32 @@ import assert from 'assert'; import sinon from 'sinon'; -import {Response} from '../lib/utils/plivoxml'; +import { Response } from '../lib/utils/plivoxml'; describe('PlivoXML', function () { - it('should work', function () { + it('should work', function (done) { const response = new Response(); response.addPreAnswer(); response.addRecord(); response.addHangup(); - response.addSpeak('text'); - response.addWait(); - response.addDTMF('123'); - response.addConference('test'); - response.addRedirect('url'); - response.addGetDigits(); - response.addPlay('url'); - const dial = response.addDial(); - dial.addNumber('123'); - dial.addUser('sip:test@sip.plivo.com'); - response.addMessage('∫test', { - src: '123', - dst: '456', + response.addSpeak('text').then(function (result) { + response.addWait(); + response.addDTMF('123'); + response.addConference('test'); + response.addRedirect('url'); + response.addGetDigits(); + response.addPlay('url'); + const dial = response.addDial(); + dial.addNumber('123'); + + dial.addUser('sip:test@sip.plivo.com'); + response.addMessage('∫test', { + src: '123', + dst: '456', + }); + assert.equal('text123testurlurl123sip:test@sip.plivo.com∫test', response.toXML()); + done(); + }).catch(function (err) { + done("Failed to test Plivo Xml due to unknown error"); }); - assert.equal('text123testurlurl123sip:test@sip.plivo.com∫test', response.toXML()); }); });