Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CallAutomation_SimpleIvr/CallAutomation_SimpleIvr.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Communication.CallAutomation" Version="1.0.0-alpha.20230426.1" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="4.12.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Azure.Communication.CallAutomation" Version="1.0.0-beta.1" />

</ItemGroup>

<ItemGroup>
Expand Down
239 changes: 221 additions & 18 deletions CallAutomation_SimpleIvr/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Azure;
using Azure.Communication;
using Azure.Communication.CallAutomation;
using Azure.Messaging;
Expand All @@ -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);

Expand All @@ -21,7 +23,19 @@
{
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;
}
var TotalParticipants = false;
int addedParticipantsCount = 0;
int declineParticipantsCount = 0;
var target = builder.Configuration["ParticipantToAdd"];
string sourceCallerID = null;
var Participants = target.Split(';');
var app = builder.Build();
app.MapPost("/api/incomingCall", async (
[FromBody] EventGridEvent[] eventGridEvents,
Expand All @@ -44,11 +58,26 @@
}
}
var jsonObject = JsonNode.Parse(eventGridEvent.Data).AsObject();
var callerId = (string)(jsonObject["from"]["rawId"]);
var targetId = (string)(jsonObject["to"]["rawId"]);
sourceCallerID = (string)(jsonObject["from"]["rawId"]);
var incomingCallContext = (string)jsonObject["incomingCallContext"];
var callbackUri = new Uri(baseUri + $"/api/calls/{Guid.NewGuid()}?callerId={callerId}");
var callbackUri = new Uri(baseUri + $"/api/calls/{Guid.NewGuid()}?callerId={sourceCallerID}");

AnswerCallResult answerCallResult = await client.AnswerCallAsync(incomingCallContext, callbackUri);
string caSourceId = builder.Configuration["TargetId"];
var rejectcall = Convert.ToBoolean(builder.Configuration["declinecall"]);

if (caSourceId.Contains(targetId))
{
if (rejectcall)
{
var response = client.RejectCallAsync(incomingCallContext);
logger.LogInformation($"{response.Result}");
}
else
{
AnswerCallResult answerCallResult = await client.AnswerCallAsync(incomingCallContext, callbackUri);
}
}
}
return Results.Ok();
});
Expand All @@ -61,11 +90,23 @@
{
var audioPlayOptions = new PlayOptions() { 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);
var callMedia = callConnection?.GetCallMedia();

Expand All @@ -76,6 +117,11 @@

if (@event is CallConnected)
{
TotalParticipants = false;
addedParticipantsCount = 0;
declineParticipantsCount = 0;

logger.LogInformation($"CallConnected event received for call connection id: {@event.CallConnectionId}" + $" Correlation id: {@event.CorrelationId}");
// Start recognize prompt - play audio and recognize 1-digit DTMF input
var recognizeOptions =
new CallMediaRecognizeDtmfOptions(CommunicationIdentifier.FromRawId(callerId), maxTonesToCollect: 1)
Expand All @@ -90,33 +136,36 @@
}
if (@event is RecognizeCompleted { OperationContext: "MainMenu" })
{
var recognizeCompleted = (RecognizeCompleted)@event;
var recognizeCompleted = (RecognizeCompleted)@event;
DtmfResult collectedTones = recognizeCompleted.RecognizeResult as DtmfResult;

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);
}
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);
}
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"]));
audioPlayOptions.OperationContext = "CustomerCare";
await callMedia.PlayToAllAsync(customerCareAudio, audioPlayOptions);
}
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);
}
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
{
Expand All @@ -133,24 +182,165 @@
{
if (@event.OperationContext == "AgentConnect")
{
var addParticipantOptions = new AddParticipantsOptions(new List<CommunicationIdentifier>()
var target = builder.Configuration["ParticipantToAdd"];

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);
//var playSource = new FileSource(new Uri(callConfiguration.Value.AppBaseUri + callConfiguration.Value.AddParticipant));
PlaySource agentAudio = new FileSource(new Uri(baseUri + builder.Configuration["AddParticipant"]));
audioPlayOptions.OperationContext = "addParticipant";
await callMedia.PlayToAllAsync(agentAudio, audioPlayOptions);

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);
CallInvite? callInvite = null;
if (!string.IsNullOrEmpty(customerCareIdentity))
{
if (identifierKind == CommunicationIdentifierKind.PhoneIdentity)
{
callInvite = new CallInvite(new PhoneNumberIdentifier(customerCareIdentity), new PhoneNumberIdentifier(builder.Configuration["ACSAlternatePhoneNumber"]));
}
if (identifierKind == CommunicationIdentifierKind.UserIdentity)
{
callInvite = new 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.Count())
{
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----- ");
foreach (var participant in updatedParticipantEvent.Participants)
{
logger.LogInformation($"Participant Raw ID : {participant.Identifier.RawId}");
}
}
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)
{
logger.LogInformation($"Participant Raw ID : {participant.Identifier.RawId}");
}
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)
{
logger.LogInformation($"No participants got addedd to remove");
}
else
{
logger.LogInformation($"Going to remove added partipants.");
List<CallParticipant> participantsToRemoveAll = (await callConnection.GetParticipantsAsync()).Value.ToList();
foreach (CallParticipant participantToRemove in participantsToRemoveAll)
{
var Plist = builder.Configuration["ParticipantToAdd"];
if (!string.IsNullOrEmpty(participantToRemove.Identifier.ToString()) &&
Plist.Contains(participantToRemove.Identifier.ToString()) ||
(hangupScenario == 4 && participantToRemove.Identifier.RawId.Contains(sourceCallerID)))
{
var RemoveParticipant = new RemoveParticipantOptions(participantToRemove.Identifier);
var removeParticipantResponse = await callConnection.RemoveParticipantAsync(RemoveParticipant);
logger.LogInformation($"Removing participant Response : {removeParticipantResponse.Value.ToString}");
}
}
}
}
}
}
return Results.Ok();
}).Produces(StatusCodes.Status200OK);
Expand All @@ -175,3 +365,16 @@
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}$";
}
19 changes: 15 additions & 4 deletions CallAutomation_SimpleIvr/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,26 @@
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionString": "%ConnectionString%",
"ACSAlternatePhoneNumber": "%ACSAlternatePhoneNumber%", // get it from acs resouce
"ParticipantToAdd": "%ParticipantToAdd%",
"BaseUri": "%BaseUri%",
"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": "*"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added CallAutomation_SimpleIvr/audio/TimedoutAudio.wav
Binary file not shown.