diff --git a/CHANGELOG.md b/CHANGELOG.md index 927f26a8..3e5dbff4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## [v4.24.0](https://github.com/plivo/plivo-node/tree/v4.24.0) (2021-11-30) +**Features - Voice: Multiparty calls** +- The [Add Multiparty Call API](https://www.plivo.com/docs/voice/api/multiparty-call/participants#add-a-participant) allows for greater functionality by accepting options like `start recording audio`, `stop recording audio`, and their HTTP methods. +- [Multiparty Calls](https://www.plivo.com/docs/voice/api/multiparty-call/) now has new APIs to `stop` and `play` audio. + ## [v4.23.1](https://github.com/plivo/plivo-node/tree/v4.23.1) (2021-10-13) **Bug Fix** - LiveCallInterface. diff --git a/lib/resources/multiPartyCall.js b/lib/resources/multiPartyCall.js index 3fee3f86..2c7fe6f1 100644 --- a/lib/resources/multiPartyCall.js +++ b/lib/resources/multiPartyCall.js @@ -15,6 +15,7 @@ const clientKey = Symbol(); const action = 'MultiPartyCall/'; const idField = 'mpcUuid'; const secondaryAction = 'Participant/'; +const secondaryMemberAction = 'Member/'; const secondaryIdField = 'participantUuid'; export class MPCError extends Error { } @@ -306,6 +307,29 @@ export class MultiPartyCall extends PlivoResource{ else { params.exitSoundMethod = 'GET' } + + if(params.startRecordingAudio){ + validUrl('startRecordingAudio', params.startRecordingAudio, false) + } + + if(params.startRecordingAudioMethod){ + validParam('startRecordingAudioMethod', params.startRecordingAudioMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.startRecordingAudioMethod = 'GET' + } + + if(params.stopRecordingAudio){ + validUrl('stopRecordingAudio', params.stopRecordingAudio, false) + } + + if(params.stopRecordingAudioMethod){ + validParam('stopRecordingAudioMethod', params.stopRecordingAudioMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.stopRecordingAudioMethod = 'GET' + } + if(params.to && (String(params.ringTimeout).split('<').length > params.to.split('<').length)){ throw new MPCError("RingTimeout:number of ring_timeout(s) should be same as number of destination(s)") } @@ -449,6 +473,32 @@ export class MultiPartyCallParticipant extends PlivoSecondaryResource{ } +export class MultiPartyCallMember extends PlivoSecondaryResource{ + constructor(client, data= {}) { + super(action, MultiPartyCall, idField, secondaryMemberAction, MultiPartyCallMember, secondaryIdField, client); + + if (idField in data) { + this.id = data[idField]; + } + + if(secondaryIdField in data){ + this.secondaryId = data[secondaryIdField]; + } + + extend(this, data); + this[clientKey] = client; + } + + startPlayAudio(params){ + params.isVoiceRequest = 'true'; + return super.executeAction(this.id, this.secondaryId + '/Play', 'POST', params) + } + + stopPlayAudio(){ + return super.executeAction(this.id, this.secondaryId +'/Play', 'DELETE',{'isVoiceRequest' : 'true'}) + } +} + export class MultiPartyCallInterface extends PlivoResourceInterface{ constructor(client, data = {}) { super(action, MultiPartyCall, idField, client); @@ -737,4 +787,33 @@ export class MultiPartyCallInterface extends PlivoResourceInterface{ return new MultiPartyCallParticipant(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).getParticipant() } + startPlayAudio(participantId, url, params = {}){ + validParam('participantId', participantId, [String, Number], true) + validUrl('url', url, true) + if(params.uuid){ + validParam('uuid', params.uuid, [String], false) + } + if(params.friendlyName){ + validParam('friendlyName', params.friendlyName, [String], false) + } + let mpcId = this.makeMpcId(params.uuid, params.friendlyName) + delete params.uuid + delete params.friendlyName + params.url = url + return new MultiPartyCallMember(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).startPlayAudio(params) + } + + stopPlayAudio(participantId, params = {}){ + validParam('participantId', participantId, [String, Number], true) + if(params.uuid){ + validParam('uuid', params.uuid, [String], false) + } + if(params.friendlyName){ + validParam('friendlyName', params.friendlyName, [String], false) + } + let mpcId = this.makeMpcId(params.uuid, params.friendlyName) + delete params.uuid + delete params.friendlyName + return new MultiPartyCallMember(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).stopPlayAudio(params) + } } diff --git a/lib/rest/request-test.js b/lib/rest/request-test.js index 3f0abe0a..fc1df286 100644 --- a/lib/rest/request-test.js +++ b/lib/rest/request-test.js @@ -883,6 +883,60 @@ export function Request(config) { }); } + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/'){ + resolve({ + response: {}, + body: { + "api_id": "036c80f3-3721-11ec-a678-0242ac110002", + "message": "MPC: TestMPC participant record started", + "recording_id": "24670db8-c723-4ba2-8521-f10ec41ddf8b", + "recording_url": "https://media-qa.voice.plivodev.com/v1/Account/MAXXXXXXXXXXXX/Recording/XXXXX-XXXX-XXXX-XXXXX.mp3" + } + }); + } + + else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/'){ + resolve({ + response: {}, + body: {} + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/Pause/'){ + resolve({ + response: {}, + body: {} + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/Resume/'){ + resolve({ + response: {}, + body: {} + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Member/10/Play/'){ + resolve({ + response: {}, + body: { + "api_id": "c07db813-3721-11ec-8bcd-0242ac110008", + "message": "play queued into MPC", + "mpcMemberId": [ + "1003" + ], + "mpcName": "TestMPC" + } + }); + } + + else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Member/10/Play/'){ + resolve({ + response: {}, + body: {} + }); + } + // ============= Numbers =================== else if (method == 'GET' && action == 'Number/+919999999990/') { resolve({ diff --git a/lib/utils/plivoxml.js b/lib/utils/plivoxml.js index f6a30b39..d1009e76 100644 --- a/lib/utils/plivoxml.js +++ b/lib/utils/plivoxml.js @@ -473,6 +473,10 @@ Response.prototype = { * @param {string} [attributes.recordingCallbackUrl] * @param {string} [attributes.statusCallbackUrl] * @param {string} [attributes.customerHoldMusicUrl] + * @param {string} [attributes.startRecordingAudio] + * @param {string} [attributes.stopRecordingAudio] + * @param {string} [attributes.startRecordingAudioMethod] + * @param {string} [attributes.stopRecordingAudioMethod] */ addMultiPartyCall: function (body, attributes){ const VALID_ROLE_VALUES = ['agent', 'supervisor', 'customer'] @@ -664,6 +668,28 @@ Response.prototype = { if(attributes.customerHoldMusicUrl && !plivoUtils.validUrl('customerHoldMusicUrl', attributes.customerHoldMusicUrl, false)){ throw new PlivoXMLError('Invalid attribute value ' + attributes.customerHoldMusicUrl + ' for customerHoldMusicUrl') } + + if(attributes.startRecordingAudio && !plivoUtils.validUrl('startRecordingAudio', attributes.startRecordingAudio, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.startRecordingAudio + ' for startRecordingAudio') + } + + if(attributes.stopRecordingAudio && !plivoUtils.validUrl('stopRecordingAudio', attributes.stopRecordingAudio, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.stopRecordingAudio + ' for stopRecordingAudio') + } + + if(attributes.startRecordingAudioMethod && VALID_METHOD_VALUES.indexOf(attributes.startRecordingAudioMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.startRecordingAudioMethod + ' for startRecordingAudioMethod') + } + else if (!attributes.startRecordingAudioMethod){ + attributes.startRecordingAudioMethod = 'GET' + } + + if(attributes.stopRecordingAudioMethod && VALID_METHOD_VALUES.indexOf(attributes.stopRecordingAudioMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.stopRecordingAudioMethod + ' for stopRecordingAudioMethod') + } + else if (!attributes.stopRecordingAudioMethod){ + attributes.stopRecordingAudioMethod = 'GET' + } return this.add(new MultiPartyCall(Response), body, attributes); }, @@ -984,6 +1010,7 @@ function MultiPartyCall(Response){ 'statusCallbackEvents', 'statusCallbackUrl', 'statusCallbackMethod', 'stayAlone', 'coachMode', 'mute', 'hold', 'startMpcOnEnter', 'endMpcOnExit', 'enterSound', 'enterSoundMethod', 'exitSound', 'exitSoundMethod', - 'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs']; + 'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs', + 'startRecordingAudio', 'startRecordingAudioMethod', 'stopRecordingAudio', 'stopRecordingAudioMethod']; } util.inherits(MultiPartyCall, Response); diff --git a/package.json b/package.json index 38d9b64e..771a4e4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plivo", - "version": "4.23.1", + "version": "4.24.0", "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": [ diff --git a/test/multiPartyCalls.js b/test/multiPartyCalls.js index 0e848ec7..73cfe30b 100644 --- a/test/multiPartyCalls.js +++ b/test/multiPartyCalls.js @@ -92,4 +92,41 @@ describe('multiPartyCalls', function (){ assert.equal(response.resourceUri, '/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/') }) }); + + it('should start MPC Participant Recording', function (){ + return client.multiPartyCalls.startParticipantRecording(10, {friendlyName: 'TestMPC'}).then(function (response){ + assert(response.message, "MPC: TestMPC participant record started") + }) + }); + + it('should stop MPC Participant Recording', function (){ + return client.multiPartyCalls.stopParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should pause MPC Participant Recording', function (){ + return client.multiPartyCalls.pauseParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should resume MPC Participant Recording', function (){ + return client.multiPartyCalls.resumeParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should start MPC Play Audio Member', function (){ + return client.multiPartyCalls.startPlayAudio(10,'https://s3.amazonaws.com/XXX/XXX.mp3', + {friendlyName: 'TestMPC'}).then(function (response){ + assert(response.message, "play queued into MPC") + }) + }); + + it('should stop MPC Play Audio Member', function (){ + return client.multiPartyCalls.stopPlayAudio(10,{friendlyName: 'TestMPC'}).then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); }) diff --git a/test/xml.js b/test/xml.js index 061ac5ee..cfc587ed 100644 --- a/test/xml.js +++ b/test/xml.js @@ -35,7 +35,7 @@ describe('PlivoXML', function () { maxDuration: 1000, statusCallbackEvents: 'participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes' }); - assert.equal('Nairobi',mpcResponse.toXML()); + assert.equal('Nairobi',mpcResponse.toXML()); done(); }); });