diff --git a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder.csproj b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder.csproj index bfbbffd6..c79d756b 100644 --- a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder.csproj +++ b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder.csproj @@ -7,14 +7,10 @@ + - - - - - @@ -23,4 +19,10 @@ + + + + + + diff --git a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallConfiguration.cs b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallConfiguration.cs index 2b7b25b0..8953ba0c 100644 --- a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallConfiguration.cs +++ b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/CallConfiguration.cs @@ -28,6 +28,11 @@ public CallConfiguration() /// public string SourcePhoneNumber { get; set; } + /// + /// The phone number to add Participant to the call. + /// + public string AddParticipantNumber { get; set; } + /// /// The base url of the applicaiton. /// @@ -58,6 +63,21 @@ public CallConfiguration() /// public string AppointmentCancelledAudio { get; set; } + /// + /// Appointment to add AgentAudio audio file route + /// + public string AgentAudio { get; set; } + + /// + /// Appointment to AddParticipant audio file route + /// + public string AddParticipant { get; set; } + + /// + /// Appointment to remove RemoveParticipant audio file route + /// + public string RemoveParticipant { get; set; } + /// /// Invalid input audio file route /// @@ -67,5 +87,10 @@ public CallConfiguration() /// Time out audio file route /// public string TimedoutAudio { get; set; } + + /// + /// Scenario for hanging up the call + /// + public int HangUpScenarios { get; set; } } } diff --git a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Program.cs b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Program.cs index f7f7d3f1..91367ff0 100644 --- a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Program.cs +++ b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Program.cs @@ -1,3 +1,4 @@ +using Azure; using Azure.Communication; using Azure.Communication.CallAutomation; using Azure.Messaging; @@ -5,6 +6,8 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); @@ -16,26 +19,77 @@ builder.Services.AddSingleton(new CallAutomationClient(callConfigurationSection["ConnectionString"])); var app = builder.Build(); - +var TargetIdentity = ""; var sourceIdentity = await app.ProvisionAzureCommunicationServicesIdentity(callConfigurationSection["ConnectionString"]); +var target = callConfigurationSection["AddParticipantNumber"]; +var addedParticipants = target.Split(';'); +int addedParticipantsCount = 0; +int declineParticipantsCount = 0; +var SourceId = ""; + +CommunicationIdentifierKind GetIdentifierKind(string participantnumber) +{ + //checks the identity type returns as string + return Regex.Match(participantnumber, Constants.userIdentityRegex, RegexOptions.IgnoreCase).Success ? CommunicationIdentifierKind.UserIdentity : + Regex.Match(participantnumber, Constants.phoneIdentityRegex, RegexOptions.IgnoreCase).Success ? CommunicationIdentifierKind.PhoneIdentity : + CommunicationIdentifierKind.UnknownIdentity; +} // Api to initiate out bound call -app.MapPost("/api/call", async (CallAutomationClient callAutomationClient, IOptions callConfiguration, ILogger logger) => +app.MapPost("/api/call", async ([Required] string targetNo, CallAutomationClient callAutomationClient, IOptions callConfiguration, ILogger logger) => { - var source = new CallSource(new CommunicationUserIdentifier(sourceIdentity)) + + var acsAcquiredNumber = new PhoneNumberIdentifier(callConfiguration.Value.SourcePhoneNumber); + if (!string.IsNullOrEmpty(targetNo)) { - CallerId = new PhoneNumberIdentifier(callConfiguration.Value.SourcePhoneNumber) - }; - var target = new PhoneNumberIdentifier(callConfiguration.Value.TargetPhoneNumber); + var identities = targetNo.Split(';'); + foreach (var target in identities) + { + if (!string.IsNullOrEmpty(target)) + { + TargetIdentity = target; + CallInvite? callInvite = null; + var identifierKind = GetIdentifierKind(target); - var createCallOption = new CreateCallOptions(source, - new List() { target }, - new Uri(callConfiguration.Value.CallbackEventUri)); + if (identifierKind == CommunicationIdentifierKind.PhoneIdentity) + { + callInvite = new CallInvite(new PhoneNumberIdentifier(target), acsAcquiredNumber); + } + else if (identifierKind == CommunicationIdentifierKind.UserIdentity) + { + callInvite = new CallInvite(new CommunicationUserIdentifier(target)); + } - var response = await callAutomationClient.CreateCallAsync(createCallOption).ConfigureAwait(false); + var createCallOption = new CreateCallOptions(callInvite, new Uri(callConfiguration.Value.CallbackEventUri)); + var response = await callAutomationClient.CreateCallAsync(createCallOption).ConfigureAwait(false); + logger.LogInformation($"Reponse from create call: {response.GetRawResponse()}" + + $"CallConnection Id : {response.Value.CallConnection.CallConnectionId}"); + SourceId = response.Value.CallConnectionProperties.Source.RawId; + } + } + } + else + { + TargetIdentity = callConfiguration.Value.TargetPhoneNumber; + if (!string.IsNullOrEmpty(TargetIdentity)) + { + var identifierKind = GetIdentifierKind(TargetIdentity); + CallInvite? callInvite = null; + if (identifierKind == CommunicationIdentifierKind.PhoneIdentity) + { + callInvite = new CallInvite(new PhoneNumberIdentifier(TargetIdentity), acsAcquiredNumber); + } - logger.LogInformation($"Reponse from create call: {response.GetRawResponse()}" + - $"CallConnection Id : {response.Value.CallConnection.CallConnectionId}"); + else if (identifierKind == CommunicationIdentifierKind.UserIdentity) + { + callInvite = new CallInvite(new CommunicationUserIdentifier(TargetIdentity)); + } + var createCallOption = new CreateCallOptions(callInvite, new Uri(callConfiguration.Value.CallbackEventUri)); + var response = await callAutomationClient.CreateCallAsync(createCallOption).ConfigureAwait(false); + logger.LogInformation($"Reponse from create call: {response.GetRawResponse()}" + + $"CallConnection Id : {response.Value.CallConnection.CallConnectionId}"); + } + } }); //api to handle call back events @@ -50,10 +104,22 @@ var callConnectionMedia = callConnection.GetCallMedia(); if (@event is CallConnected) { + addedParticipantsCount = 0; + declineParticipantsCount = 0; //Initiate recognition as call connected event is received - logger.LogInformation($"CallConnected event received for call connection id: {@event.CallConnectionId}"); + logger.LogInformation($"CallConnected event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); + + var properties = callConnection.GetCallConnectionProperties(); + logger.LogInformation($"call connection properties -------> SourceIdentity : {properties.Value.Source.RawId}," + + $"CallConnection State : {properties.Value.CallConnectionState}"); + logger.LogInformation($"targets ------->"); + foreach (var target in properties.Value.Targets) + { + logger.LogInformation($"{target.RawId}"); + } + var recognizeOptions = - new CallMediaRecognizeDtmfOptions(CommunicationIdentifier.FromRawId(callConfiguration.Value.TargetPhoneNumber), maxTonesToCollect: 1) + new CallMediaRecognizeDtmfOptions(CommunicationIdentifier.FromRawId(properties.Value.Targets[0].RawId), maxTonesToCollect: 1) { InterruptPrompt = true, InterToneTimeout = TimeSpan.FromSeconds(10), @@ -68,43 +134,200 @@ if (@event is RecognizeCompleted { OperationContext: "AppointmentReminderMenu" }) { // Play audio once recognition is completed sucessfully - logger.LogInformation($"RecognizeCompleted event received for call connection id: {@event.CallConnectionId}"); + logger.LogInformation($"RecognizeCompleted event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); var recognizeCompletedEvent = (RecognizeCompleted)@event; - var toneDetected = recognizeCompletedEvent.CollectTonesResult.Tones[0]; - var playSource = Utils.GetAudioForTone(toneDetected, callConfiguration); + DtmfTone toneDetected = ((DtmfResult)recognizeCompletedEvent.RecognizeResult).Tones[0]; + if (toneDetected == DtmfTone.Three) + { + var playSource = Utils.GetAudioForTone(toneDetected, callConfiguration) ; + // Play audio for dtmf response + await callConnectionMedia.PlayToAllAsync(new PlayToAllOptions(playSource) { OperationContext = "AgentConnect", Loop = false }); + + } + else + { + var playSource = Utils.GetAudioForTone(toneDetected, callConfiguration) ; + // Play audio for dtmf response + await callConnectionMedia.PlayToAllAsync(new PlayToAllOptions(playSource) { OperationContext = "ResponseToDtmf", Loop = false }); + } + } + if (@event is PlayCompleted { OperationContext: "AgentConnect" }) + { + foreach (var Participantindentity in addedParticipants) + { + CallInvite? callInvite = null; + if (!string.IsNullOrEmpty(Participantindentity)) + { + var identifierKind = GetIdentifierKind(Participantindentity.Trim()); + if (identifierKind == CommunicationIdentifierKind.PhoneIdentity) + { + callInvite = new CallInvite(new PhoneNumberIdentifier(Participantindentity.Trim()), new PhoneNumberIdentifier(callConfiguration.Value.SourcePhoneNumber)); + } + + else if (identifierKind == CommunicationIdentifierKind.UserIdentity) + { + callInvite = new CallInvite(new CommunicationUserIdentifier(Participantindentity.Trim())); + } + var addParticipantOptions = new AddParticipantOptions(callInvite); + var response = await callConnection.AddParticipantAsync(addParticipantOptions); + var playSource = new FileSource(new Uri(callConfiguration.Value.AppBaseUri + callConfiguration.Value.AddParticipant)); + await callConnectionMedia.PlayToAllAsync(new PlayToAllOptions(playSource) { OperationContext = "addParticipant", Loop = false }); + await Task.Delay(TimeSpan.FromSeconds(10)); - // Play audio for dtmf response - await callConnectionMedia.PlayToAllAsync(playSource, new PlayOptions { OperationContext = "ResponseToDtmf", Loop = false }); + logger.LogInformation($"Add participant call : {response.Value.Participant}" + $" Status of call :{response.GetRawResponse().Status}" + + $" participant ID: {response.Value.Participant.Identifier}" + $" participat is muted : {response.Value.Participant.IsMuted}"); + } + } } + if (@event is RecognizeFailed { OperationContext: "AppointmentReminderMenu" }) { - logger.LogInformation($"RecognizeFailed event received for call connection id: {@event.CallConnectionId}"); + logger.LogInformation($"RecognizeFailed event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); var recognizeFailedEvent = (RecognizeFailed)@event; // Check for time out, and then play audio message - if (recognizeFailedEvent.ReasonCode.Equals(ReasonCode.RecognizeInitialSilenceTimedOut)) + if (recognizeFailedEvent.ReasonCode.Equals(MediaEventReasonCode.RecognizeInitialSilenceTimedOut)) { - logger.LogInformation($"Recognition timed out for call connection id: {@event.CallConnectionId}"); - var playSource = new FileSource(new Uri(callConfiguration.Value.AppBaseUri + callConfiguration.Value.TimedoutAudio)); - + logger.LogInformation($"Recognition timed out for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); + var playSource = new FileSource(new Uri(callConfiguration.Value.AppBaseUri + callConfiguration.Value.TimedoutAudio)) ; + //Play audio for time out - await callConnectionMedia.PlayToAllAsync(playSource, new PlayOptions { OperationContext = "ResponseToDtmf", Loop = false }); + await callConnectionMedia.PlayToAllAsync(new PlayToAllOptions(playSource) { OperationContext = "ResponseToDtmf", Loop = false }); } } + if (@event is PlayCompleted { OperationContext: "ResponseToDtmf" }) { - logger.LogInformation($"PlayCompleted event received for call connection id: {@event.CallConnectionId}"); + logger.LogInformation($"PlayCompleted event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); await callConnection.HangUpAsync(forEveryone: true); } if (@event is PlayFailed { OperationContext: "ResponseToDtmf" }) { - logger.LogInformation($"PlayFailed event received for call connection id: {@event.CallConnectionId}"); + logger.LogInformation($"PlayFailed event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); await callConnection.HangUpAsync(forEveryone: true); } + if (@event is AddParticipantSucceeded addParticipantSucceeded) + { + addedParticipantsCount++; + logger.LogInformation($"participant added ---> {addParticipantSucceeded.Participant.RawId}"); + if ((addedParticipantsCount + declineParticipantsCount) == addedParticipants.Length) + { + await PerformHangUp(callConnection); + } + } + if (@event is AddParticipantFailed failedParticipant) + { + declineParticipantsCount++; + logger.LogInformation($"Failed participant Reason -------> {failedParticipant.ResultInformation?.Message}"); + if ((addedParticipantsCount + declineParticipantsCount) == addedParticipants.Length) + { + await PerformHangUp(callConnection); + } + } + if (@event is RemoveParticipantSucceeded) + { + RemoveParticipantSucceeded RemoveParticipantSucceeded = (RemoveParticipantSucceeded)@event; + logger.LogInformation($"Remove Participant Succeeded RawId : {RemoveParticipantSucceeded.Participant.RawId}"); + } + if (@event is RemoveParticipantFailed) + { + RemoveParticipantFailed removeParticipantFailed = (RemoveParticipantFailed)@event; + logger.LogInformation($"Remove participant failed RawId:{removeParticipantFailed.Participant.RawId}"); + } + if (@event is ParticipantsUpdated updatedParticipantEvent) + { + logger.LogInformation($"Participant Updated Event Recieved"); + logger.LogInformation($"------- Updated Participant : {updatedParticipantEvent.Participants.Count} -------- "); + foreach (var participant in updatedParticipantEvent.Participants) + { + logger.LogInformation($"Participant Raw ID : {participant.Identifier.RawId}, IsMuted : {participant.IsMuted}"); + } + } + } + + //Perform HangUp + async Task PerformHangUp(CallConnection callConnection) + { + await Task.Delay(TimeSpan.FromSeconds(10)); + + var participantlistResponse = await callConnection.GetParticipantsAsync(); + logger.LogInformation($"-------Participant List : {participantlistResponse.Value.Count} ----- "); + foreach (var participant in participantlistResponse.Value) + { + try + { + logger.LogInformation($"{participant.Identifier.RawId}"); + var response = callConnection.GetParticipant(participant.Identifier); + logger.LogInformation($"-------get participnat response : {response} ----- "); + } + catch(Exception ex) + { + logger.LogInformation($"------Error In GetParticipant() for participnat : {participant.Identifier.RawId} " + + $"-----> {ex.Message}"); + } + + } + + int hangupScenario = callConfiguration.Value.HangUpScenarios; + if (hangupScenario == 1) + { + logger.LogInformation($"CA hanging up the call for everyone." + $"Information of Call:{callConnection.GetCallConnectionProperties()}"); + var response = await callConnection.HangUpAsync(true); + logger.LogInformation($"Hang up response : {response}"); + } + else if (hangupScenario == 2) + { + logger.LogInformation($"CA hang up the call." + $"Information of Call:{callConnection.GetCallConnectionProperties()}"); + var response = await callConnection.HangUpAsync(false); + logger.LogInformation($"Hang up response : {response}"); + } + else if (hangupScenario == 3 || hangupScenario == 4) + { + if (addedParticipantsCount == 0 && hangupScenario == 3) + { + logger.LogInformation($"No participants got addedd to remove"); + } + else + { + logger.LogInformation($"Going to remove added partipants."); + List participantsToRemoveAll = (await callConnection.GetParticipantsAsync()).Value.ToList(); + CommunicationIdentifier targetParticipant = null; + foreach (CallParticipant participantToRemove in participantsToRemoveAll) + { + if (!string.IsNullOrEmpty(participantToRemove.Identifier.ToString())) + { + if (participantToRemove.Identifier.RawId.Contains(TargetIdentity)) + { + targetParticipant = participantToRemove.Identifier; + } + else if (participantToRemove.Identifier.RawId.Contains(SourceId)) + { + SourceId = participantToRemove.Identifier.RawId; + } + else + { + var RemoveParticipant = new RemoveParticipantOptions(participantToRemove.Identifier); + logger.LogInformation($"going to remove participant : {participantToRemove.Identifier.RawId}"); + var removeParticipantResponse = await callConnection.RemoveParticipantAsync(RemoveParticipant); + logger.LogInformation($"Removing participant Response : {removeParticipantResponse.GetRawResponse()}"); + } + } + } + if (hangupScenario == 4 && targetParticipant != null) + { + logger.LogInformation($"going to remove target participant : {targetParticipant.RawId}"); + var removeParticipantResponse = await callConnection.RemoveParticipantAsync(targetParticipant); + logger.LogInformation($"Removing participant Response : {removeParticipantResponse.GetRawResponse()}"); + } + } + } } + return Results.Ok(); }).Produces(StatusCodes.Status200OK); + + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment() || app.Environment.IsProduction()) { @@ -121,3 +344,16 @@ app.UseHttpsRedirection(); app.Run(); +public enum CommunicationIdentifierKind +{ + PhoneIdentity, + UserIdentity, + UnknownIdentity + +} +public class Constants +{ + public const string userIdentityRegex = @"8:acs:[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}_[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}"; + public const string phoneIdentityRegex = @"^\+\d{10,14}$"; + +} diff --git a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Utils.cs b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Utils.cs index 7c712591..b27b4512 100644 --- a/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Utils.cs +++ b/CallAutomation_AppointmentReminder/CallAutomation_AppointmentReminder/Utils.cs @@ -17,6 +17,10 @@ public static PlaySource GetAudioForTone(DtmfTone toneDetected, IOptions + - + diff --git a/CallAutomation_SimpleIvr/Program.cs b/CallAutomation_SimpleIvr/Program.cs index 79ae779c..2a796b88 100644 --- a/CallAutomation_SimpleIvr/Program.cs +++ b/CallAutomation_SimpleIvr/Program.cs @@ -1,3 +1,4 @@ +using Azure; using Azure.Communication; using Azure.Communication.CallAutomation; using Azure.Messaging; @@ -8,6 +9,7 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using System.Text.Json.Nodes; +using System.Text.RegularExpressions; var builder = WebApplication.CreateBuilder(args); @@ -21,8 +23,20 @@ { baseUri = builder.Configuration["BaseUri"]; } - +CommunicationIdentifierKind GetIdentifierKind(string participantnumber) +{ +    //checks the identity type returns as string +    return Regex.Match(participantnumber, Constants.userIdentityRegex, RegexOptions.IgnoreCase).Success ? CommunicationIdentifierKind.UserIdentity : + Regex.Match(participantnumber, Constants.phoneIdentityRegex, RegexOptions.IgnoreCase).Success ? CommunicationIdentifierKind.PhoneIdentity : + CommunicationIdentifierKind.UnknownIdentity; +} +int addedParticipantsCount = 0; +int declineParticipantsCount = 0; +var target = builder.Configuration["ParticipantToAdd"]; +string sourceCallerID = null; +var Participants = target.Split(';'); var app = builder.Build(); +string caSourceId = null; app.MapPost("/api/incomingCall", async ( [FromBody] EventGridEvent[] eventGridEvents, ILogger logger) => @@ -44,11 +58,35 @@ } } var jsonObject = JsonNode.Parse(eventGridEvent.Data).AsObject(); - var callerId = (string)(jsonObject["from"]["rawId"]); - var incomingCallContext = (string)jsonObject["incomingCallContext"]; - var callbackUri = new Uri(baseUri + $"/api/calls/{Guid.NewGuid()}?callerId={callerId}"); + var targetId = (string)(jsonObject["to"]["rawId"]); + if(caSourceId == null) + { + caSourceId = builder.Configuration["TargetId"]; + } + var rejectcall = Convert.ToBoolean(builder.Configuration["declinecall"]); - AnswerCallResult answerCallResult = await client.AnswerCallAsync(incomingCallContext, callbackUri); + if (caSourceId.Contains(targetId) ) + { + sourceCallerID = (string)(jsonObject["from"]["rawId"]); + var incomingCallContext = (string)jsonObject["incomingCallContext"]; + var callbackUri = new Uri(baseUri + $"/api/calls/{Guid.NewGuid()}?callerId={sourceCallerID}"); + + if (rejectcall) + { + var response = client.RejectCallAsync(incomingCallContext); + logger.LogInformation($"{response.Result}"); + } + else + { + AnswerCallResult answerCallResult = await client.AnswerCallAsync(incomingCallContext, callbackUri); + logger.LogInformation($"answerCall Response -------> source callerId {answerCallResult.CallConnectionProperties.SourceCallerIdNumber.RawId}"); + logger.LogInformation($"targets ------->"); + foreach (var target in answerCallResult.CallConnectionProperties.Targets) + { + logger.LogInformation($"{target.RawId}"); + } + } + } } return Results.Ok(); }); @@ -59,11 +97,23 @@ [Required] string callerId, ILogger logger) => { - var audioPlayOptions = new PlayOptions() { OperationContext = "SimpleIVR", Loop = false }; + //var audioPlayOptions = new PlayToAllOptions() { OperationContext = "SimpleIVR", Loop = false }; + + if (cloudEvents == null) + { + logger.LogWarning("cloudEvents parameter is null."); + return Results.BadRequest("cloudEvents parameter is null."); + } foreach (var cloudEvent in cloudEvents) { + logger.LogInformation($"Event received: {JsonConvert.SerializeObject(cloudEvent)}"); CallAutomationEventBase @event = CallAutomationEventParser.Parse(cloudEvent); + if (@event == null) + { + logger.LogWarning("cloudEvents param is null"); + continue; + } logger.LogInformation($"Event received: {JsonConvert.SerializeObject(@event)}"); var callConnection = client.GetCallConnection(@event.CallConnectionId); @@ -76,9 +126,23 @@ if (@event is CallConnected) { + addedParticipantsCount = 0; + declineParticipantsCount = 0; + + logger.LogInformation($"CallConnected event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); + + var properties = callConnection.GetCallConnectionProperties(); + logger.LogInformation($"call connection properties -------> SourceIdentity : {properties.Value.SourceCallerIdNumber.RawId}," + + $"CallConnection State : {properties.Value.CallConnectionState}"); + logger.LogInformation($"targets ------->"); + foreach (var target in properties.Value.Targets) + { + logger.LogInformation($"{target.RawId}"); + } + // Start recognize prompt - play audio and recognize 1-digit DTMF input var recognizeOptions = - new CallMediaRecognizeDtmfOptions(CommunicationIdentifier.FromRawId(callerId), maxTonesToCollect: 1) + new CallMediaRecognizeDtmfOptions(CommunicationIdentifier.FromRawId(sourceCallerID), maxTonesToCollect: 1) { InterruptPrompt = true, InterToneTimeout = TimeSpan.FromSeconds(10), @@ -91,66 +155,254 @@ if (@event is RecognizeCompleted { OperationContext: "MainMenu" }) { var recognizeCompleted = (RecognizeCompleted)@event; + DtmfResult collectedTones = (DtmfResult)recognizeCompleted.RecognizeResult; - if (recognizeCompleted.CollectTonesResult.Tones[0] == DtmfTone.One) + if (collectedTones.Tones[0] == DtmfTone.One) { - PlaySource salesAudio = new FileSource(new Uri(baseUri + builder.Configuration["SalesAudio"])); - await callMedia.PlayToAllAsync(salesAudio, audioPlayOptions); + var salesAudio = new FileSource(new Uri(baseUri + builder.Configuration["SalesAudio"])); + var audioPlayOptions = new PlayToAllOptions(salesAudio) { OperationContext = "SimpleIVR", Loop = false }; + await callMedia.PlayToAllAsync(audioPlayOptions); } - else if (recognizeCompleted.CollectTonesResult.Tones[0] == DtmfTone.Two) + else if (collectedTones.Tones[0] == DtmfTone.Two) { - PlaySource marketingAudio = new FileSource(new Uri(baseUri + builder.Configuration["MarketingAudio"])); - await callMedia.PlayToAllAsync(marketingAudio, audioPlayOptions); + var marketingAudio = new FileSource(new Uri(baseUri + builder.Configuration["MarketingAudio"])) ; + await callMedia.PlayToAllAsync(new PlayToAllOptions(marketingAudio) + { OperationContext = "SimpleIVR", Loop = false }); } - else if (recognizeCompleted.CollectTonesResult.Tones[0] == DtmfTone.Three) + else if (collectedTones.Tones[0] == DtmfTone.Three) { - PlaySource customerCareAudio = new FileSource(new Uri(baseUri + builder.Configuration["CustomerCareAudio"])); - await callMedia.PlayToAllAsync(customerCareAudio, audioPlayOptions); + var customerCareAudio = new FileSource(new Uri(baseUri + builder.Configuration["CustomerCareAudio"])); + await callMedia.PlayToAllAsync(new PlayToAllOptions(customerCareAudio) + { OperationContext = "CustomerCare", Loop = false }); } - else if (recognizeCompleted.CollectTonesResult.Tones[0] == DtmfTone.Four) + else if (collectedTones.Tones[0] == DtmfTone.Four) { - PlaySource agentAudio = new FileSource(new Uri(baseUri + builder.Configuration["AgentAudio"])); - audioPlayOptions.OperationContext = "AgentConnect"; - await callMedia.PlayToAllAsync(agentAudio, audioPlayOptions); + var agentAudio = new FileSource(new Uri(baseUri + builder.Configuration["AgentAudio"])) ; + await callMedia.PlayToAllAsync(new PlayToAllOptions(agentAudio ) + { OperationContext = "AgentConnect", Loop = false }); + } - else if (recognizeCompleted.CollectTonesResult.Tones[0] == DtmfTone.Five) + else if (collectedTones.Tones[0] == DtmfTone.Five) { // Hangup for everyone await callConnection.HangUpAsync(true); + logger.LogInformation($"Call disconnected event received call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); } else { - PlaySource invalidAudio = new FileSource(new Uri(baseUri + builder.Configuration["InvalidAudio"])); - await callMedia.PlayToAllAsync(invalidAudio, audioPlayOptions); + var invalidAudio = new FileSource(new Uri(baseUri + builder.Configuration["InvalidAudio"])) ; + await callMedia.PlayToAllAsync(new PlayToAllOptions(invalidAudio) + { OperationContext = "SimpleIVR", Loop = false }); } } if (@event is RecognizeFailed { OperationContext: "MainMenu" }) { // play invalid audio - await callMedia.PlayToAllAsync(new FileSource(new Uri(baseUri + builder.Configuration["InvalidAudio"])), audioPlayOptions); + var invalidAudio = new FileSource(new Uri(baseUri + builder.Configuration["InvalidAudio"])) ; + await callMedia.PlayToAllAsync(new PlayToAllOptions(invalidAudio) + { OperationContext = "SimpleIVR", Loop = false }); } if (@event is PlayCompleted) { if (@event.OperationContext == "AgentConnect") { - var addParticipantOptions = new AddParticipantsOptions(new List() + foreach (var Participantindentity in Participants) { - new PhoneNumberIdentifier(builder.Configuration["ParticipantToAdd"]) - }); + var identifierKind = GetIdentifierKind(Participantindentity); + CallInvite? callInvite = null; + if (!string.IsNullOrEmpty(Participantindentity)) + { + if (identifierKind == CommunicationIdentifierKind.PhoneIdentity) + { + callInvite = new CallInvite(new PhoneNumberIdentifier(Participantindentity), new PhoneNumberIdentifier(builder.Configuration["ACSAlternatePhoneNumber"])); + } + if (identifierKind == CommunicationIdentifierKind.UserIdentity) + { + callInvite = new CallInvite(new CommunicationUserIdentifier(Participantindentity)); + } + } - addParticipantOptions.SourceCallerId = new PhoneNumberIdentifier(builder.Configuration["ACSAlternatePhoneNumber"]); - await callConnection.AddParticipantsAsync(addParticipantOptions); + var addParticipantOptions = new AddParticipantOptions(callInvite); + var response = await callConnection.AddParticipantAsync(addParticipantOptions); + + // Add the FileSource to the list + var fileSource = new FileSource(new Uri(baseUri + builder.Configuration["AddParticipant"])); + + // Pass the list of PlaySources to PlayToAllAsync + await callMedia.PlayToAllAsync(new PlayToAllOptions(fileSource) { OperationContext = "addParticipant", Loop = false }); + + TimeSpan InterToneTimeout = TimeSpan.FromSeconds(20); + TimeSpan InitialSilenceTimeout = TimeSpan.FromSeconds(10); + logger.LogInformation($"AddParticipant event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}"); + logger.LogInformation($"Addparticipant call: {response.Value.Participant}" + $" Addparticipant ID: {Participantindentity}" + + $" get response fron participant : {response.GetRawResponse()}" + $" call reason : {response.GetRawResponse().ReasonPhrase}"); + } } - if (@event.OperationContext == "SimpleIVR") + else if (@event.OperationContext == "CustomerCare") { - await callConnection.HangUpAsync(true); + var customerCareIdentity = builder.Configuration["customerCareIdentity"]; + var identifierKind = GetIdentifierKind(customerCareIdentity); + CommunicationIdentifier? callInvite = null; + if (!string.IsNullOrEmpty(customerCareIdentity)) + { + if (identifierKind == CommunicationIdentifierKind.PhoneIdentity) + { + callInvite = new PhoneNumberIdentifier(customerCareIdentity); + } + if (identifierKind == CommunicationIdentifierKind.UserIdentity) + { + callInvite = new CommunicationUserIdentifier(customerCareIdentity); + } + } + var transferResponse = await callConnection.TransferCallToParticipantAsync(callInvite); + logger.LogInformation($"Call Transfered to : {customerCareIdentity}"); + logger.LogInformation($"Transfer call result : {transferResponse.GetRawResponse()}"); } } + if (@event is AddParticipantSucceeded addedParticipant) + { + addedParticipantsCount++; + logger.LogInformation($"participant added ---> {addedParticipant.Participant.RawId}"); + + if ((addedParticipantsCount + declineParticipantsCount) == Participants.Length) + { + await PerformHangUp(callConnection); + } + } + if (@event is AddParticipantFailed failedParticipant) + { + declineParticipantsCount++; + AddParticipantFailed addParticipantFailed = (AddParticipantFailed)@event; + logger.LogInformation($"Failed participant Reason -------> {failedParticipant.ResultInformation?.Message}"); + if ((addedParticipantsCount + declineParticipantsCount) == Participants.Length) + { + await PerformHangUp(callConnection); + } + } + if (@event is RemoveParticipantSucceeded) + { + RemoveParticipantSucceeded RemoveParticipantSucceeded = (RemoveParticipantSucceeded)@event; + logger.LogInformation($"Remove Participant Succeeded RawId : {RemoveParticipantSucceeded.Participant.RawId}"); + } + if (@event is RemoveParticipantFailed) + { + RemoveParticipantFailed removeParticipantFailed = (RemoveParticipantFailed)@event; + logger.LogInformation($"Remove participant failed RawId:{removeParticipantFailed.Participant.RawId}"); + } + if (@event.OperationContext == "SimpleIVR") + { + await callConnection.HangUpAsync(true); + } if (@event is PlayFailed) { logger.LogInformation($"PlayFailed Event: {JsonConvert.SerializeObject(@event)}"); await callConnection.HangUpAsync(true); } + if (@event is ParticipantsUpdated updatedParticipantEvent) + { + logger.LogInformation($"Participant Updated Event Recieved"); + logger.LogInformation("-------Updated Participant List----- "); + if(updatedParticipantEvent.Participants.Count == 2) + { + if (updatedParticipantEvent.Participants[0].Identifier.RawId == sourceCallerID) + { + caSourceId = updatedParticipantEvent.Participants[1].Identifier.RawId.Trim(); + } + else if (updatedParticipantEvent.Participants[1].Identifier.RawId == sourceCallerID) + { + caSourceId = updatedParticipantEvent.Participants[0].Identifier.RawId.Trim(); + } + } + foreach (var participant in updatedParticipantEvent.Participants) + { + logger.LogInformation($"Participant Raw ID : {participant.Identifier.RawId}, IsMuted : {participant.IsMuted}"); + } + } + if (@event is CallTransferAccepted callTransferAccepted) + { + logger.LogInformation($"Transfer call accepted"); + } + if (@event is CallTransferFailed callTransferFailed) + { + logger.LogInformation($"Transfer call Failed ----> {callTransferFailed.ResultInformation.Message}"); + } + + async Task PerformHangUp(CallConnection callConnection) + { + await Task.Delay(TimeSpan.FromSeconds(10)); + var participantlistResponse = await callConnection.GetParticipantsAsync(); + logger.LogInformation("-------Participant List----- "); + foreach (var participant in participantlistResponse.Value) + { + try + { + logger.LogInformation($"{participant.Identifier.RawId}"); + var response = callConnection.GetParticipant(participant.Identifier); + logger.LogInformation($"-------get participnat response : {response} ----- "); + } + catch (Exception ex) + { + logger.LogInformation($"------Error In GetParticipant() for participnat : {participant.Identifier.RawId} " + + $"-----> {ex.Message}"); + } + } + + logger.LogInformation($"Number of Participants : {participantlistResponse.Value.Count}"); + + int hangupScenario = Convert.ToInt32(builder.Configuration["HangUpScenarios"]); + if (hangupScenario == 1) + { + logger.LogInformation($"CA hanging up the call for everyone." + $"Information of Call:{callConnection.GetCallConnectionProperties()}"); + var response = await callConnection.HangUpAsync(true); + logger.LogInformation($"Hang up response : {response}"); + } + else if (hangupScenario == 2) + { + logger.LogInformation($"CA hang up the call." + $"Information of Call:{callConnection.GetCallConnectionProperties()}"); + var response = await callConnection.HangUpAsync(false); + logger.LogInformation($"Hang up response : {response}"); + } + else if (hangupScenario == 3 || hangupScenario == 4) + { + if (addedParticipantsCount == 0 && hangupScenario == 3) + { + logger.LogInformation($"No participants got addedd to remove"); + } + else + { + logger.LogInformation($"Going to remove added partipants."); + List participantsToRemoveAll = (await callConnection.GetParticipantsAsync()).Value.ToList(); + CommunicationIdentifier sourceParticipant = null; + foreach (CallParticipant participantToRemove in participantsToRemoveAll) + { + if (!string.IsNullOrEmpty(participantToRemove.Identifier.ToString())) + { + if (participantToRemove.Identifier.RawId.Contains(sourceCallerID) ) + { + sourceParticipant = participantToRemove.Identifier; + } + else if (participantToRemove.Identifier.RawId.Contains(caSourceId)) + { + caSourceId = participantToRemove.Identifier.RawId; + } + else + { + var RemoveParticipant = new RemoveParticipantOptions(participantToRemove.Identifier); + logger.LogInformation($"going to remove participant : {participantToRemove.Identifier.RawId}"); + var removeParticipantResponse = await callConnection.RemoveParticipantAsync(RemoveParticipant); + logger.LogInformation($"Removing participant Response : {removeParticipantResponse.Value.ToString}"); + } + } + } + if(hangupScenario == 4 && sourceParticipant != null) + { + logger.LogInformation($"going to remove participant : {sourceParticipant.RawId}"); + var removeParticipantResponse = await callConnection.RemoveParticipantAsync(sourceParticipant); + logger.LogInformation($"Removing participant Response : {removeParticipantResponse.Value.ToString}"); + } + } + } + } } return Results.Ok(); }).Produces(StatusCodes.Status200OK); @@ -175,3 +427,15 @@ app.MapControllers(); app.Run(); + +public enum CommunicationIdentifierKind +{ + PhoneIdentity, + UserIdentity, + UnknownIdentity +} +public class Constants +{ + public const string userIdentityRegex = @"8:acs:[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}_[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}"; + public const string phoneIdentityRegex = @"^\+\d{10,14}$"; +} diff --git a/CallAutomation_SimpleIvr/appsettings.json b/CallAutomation_SimpleIvr/appsettings.json index dc405d74..805cea30 100644 --- a/CallAutomation_SimpleIvr/appsettings.json +++ b/CallAutomation_SimpleIvr/appsettings.json @@ -1,19 +1,30 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "ConnectionString": "%ConnectionString%", - "ACSAlternatePhoneNumber": "%ACSAlternatePhoneNumber%", // get it from acs resouce - "ParticipantToAdd": "%ParticipantToAdd%", - "BaseUri": "%BaseUri%", - "MainMenuAudio": "/audio/mainmenu.wav", - "SalesAudio": "/audio/sales.wav", - "MarketingAudio": "/audio/marketing.wav", - "CustomerCareAudio": "/audio/customercare.wav", - "AgentAudio": "/audio/agent.wav", - "InvalidAudio": "/audio/invalid.wav", - "AllowedHosts": "*" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionString": "%ACS Connection String%", + "ACSAlternatePhoneNumber": "%ACS Alternate Phone Number%", // get it from acs resouce + "TargetId": "% CA ID", + "ParticipantToAdd": "%Participant To Add%", + "customerCareIdentity": "%Identity on which we want to transfer the call%", + "BaseUri": "%Base Uri%", + "declinecall": false, + "MainMenuAudio": "/audio/mainmenu.wav", + "SalesAudio": "/audio/sales.wav", + "MarketingAudio": "/audio/marketing.wav", + "CustomerCareAudio": "/audio/customercare.wav", + "AddParticipant": "/audio/AddParticipant.wav", + "RemoveParticipant": "/audio/RemoveParticipant.wav", + "AgentAudio": "/audio/agent.wav", + "InvalidAudio": "/audio/invalid.wav", + // 1: Hangup for eveyone after adding participant + // 2: Hangup CA after adding participant + // 3: Remove addedd participants after adding them + // 4: Terminate al the participants in te call + "HangUpScenarios": 3, + + "AllowedHosts": "*" } diff --git a/CallAutomation_SimpleIvr/audio/AddParticipant.wav b/CallAutomation_SimpleIvr/audio/AddParticipant.wav new file mode 100644 index 00000000..7491fbd4 Binary files /dev/null and b/CallAutomation_SimpleIvr/audio/AddParticipant.wav differ diff --git a/CallAutomation_SimpleIvr/audio/InvalidInputAudio.wav b/CallAutomation_SimpleIvr/audio/InvalidInputAudio.wav new file mode 100644 index 00000000..3dfa6563 Binary files /dev/null and b/CallAutomation_SimpleIvr/audio/InvalidInputAudio.wav differ diff --git a/CallAutomation_SimpleIvr/audio/RemoveParticipant.wav b/CallAutomation_SimpleIvr/audio/RemoveParticipant.wav new file mode 100644 index 00000000..9b6f577d Binary files /dev/null and b/CallAutomation_SimpleIvr/audio/RemoveParticipant.wav differ diff --git a/CallAutomation_SimpleIvr/audio/TimedoutAudio.wav b/CallAutomation_SimpleIvr/audio/TimedoutAudio.wav new file mode 100644 index 00000000..fb76eebc Binary files /dev/null and b/CallAutomation_SimpleIvr/audio/TimedoutAudio.wav differ