diff --git a/callautomation-appointment-reminder/CallConfiguration.py b/callautomation-appointment-reminder/CallConfiguration.py
deleted file mode 100644
index 51a8567..0000000
--- a/callautomation-appointment-reminder/CallConfiguration.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from EventHandler.EventAuthHandler import EventAuthHandler
-
-
-class CallConfiguration:
-
- def __init__(self, connection_string, source_identity, source_phone_number, app_base_url, audio_file_name):
- self.connection_string: str = str(connection_string)
- self.source_identity: str = str(source_identity)
- self.source_phone_number: str = str(source_phone_number)
- self.app_base_url: str = str(app_base_url)
- self.audio_file_name: str = str(audio_file_name)
- eventhandler = EventAuthHandler()
- self.app_callback_url: str = app_base_url + \
- "/api/outboundcall/callback?" + eventhandler.get_secret_querystring()
- self.audio_file_url: str = app_base_url + "/audio/" + audio_file_name
diff --git a/callautomation-appointment-reminder/CommunicationIdentifierKind.py b/callautomation-appointment-reminder/CommunicationIdentifierKind.py
deleted file mode 100644
index 571b625..0000000
--- a/callautomation-appointment-reminder/CommunicationIdentifierKind.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import enum
-
-
-class CommunicationIdentifierKind(enum.Enum):
-
- USER_IDENTITY = 1
- PHONE_IDENTITY = 2
- UNKNOWN_IDENTITY = 3
diff --git a/callautomation-appointment-reminder/ConfigurationManager.py b/callautomation-appointment-reminder/ConfigurationManager.py
index 4a3483b..7b3e39a 100644
--- a/callautomation-appointment-reminder/ConfigurationManager.py
+++ b/callautomation-appointment-reminder/ConfigurationManager.py
@@ -1,23 +1,38 @@
import configparser
-
-
class ConfigurationManager:
__configuration = None
- __instance = None
+ __instance = None
def __init__(self):
- if(self.__configuration == None):
+ if(self.__configuration == None):
self.__configuration = configparser.ConfigParser()
self.__configuration.read('config.ini')
@staticmethod
def get_instance():
+
if(ConfigurationManager.__instance == None):
ConfigurationManager.__instance = ConfigurationManager()
-
return ConfigurationManager.__instance
def get_app_settings(self, key):
if (key != None):
- return self.__configuration.get('default', key)
+ return self.__configuration.get("DEFAULT", key)
return None
+
+class CallConfiguration:
+
+ def __init__(self, connection_string,source_phone_number, app_base_url, audio_file_name,event_callback_route,appointment_confirmed_audio,
+ appointment_cancelled_audio,timed_out_audio,invalid_input_audio):
+ self.connection_string: str = str(connection_string)
+ #self.source_identity: str = str(source_identity)
+ self.source_phone_number: str = str(source_phone_number)
+ self.app_base_url: str = str(app_base_url)
+ self.audio_file_name: str = str(audio_file_name)
+ self.appointment_confirmed_audio: str = str(appointment_confirmed_audio)
+ self.appointment_cancelled_audio: str = str(appointment_cancelled_audio)
+ self.timed_out_audio: str = str(timed_out_audio)
+ self.invalid_input_audio: str = str(invalid_input_audio)
+ self.event_callback_route:str = str(event_callback_route)
+ self.app_callback_url: str = app_base_url + event_callback_route
+
\ No newline at end of file
diff --git a/callautomation-appointment-reminder/Controller/OutboundCallController.py b/callautomation-appointment-reminder/Controller/OutboundCallController.py
deleted file mode 100644
index 9bb1514..0000000
--- a/callautomation-appointment-reminder/Controller/OutboundCallController.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from aiohttp import web
-from EventHandler.EventAuthHandler import EventAuthHandler
-from EventHandler.EventDispatcher import EventDispatcher
-
-
-class OutboundCallController():
-
- app = web.Application()
-
- def __init__(self):
- self.app.add_routes(
- [web.post('/api/outboundcall/callback', self.on_incoming_request_async)])
- self.app.add_routes([web.get("/audio/{file_name}", self.load_file)])
- web.run_app(self.app, port=9007)
-
- async def on_incoming_request_async(self, request):
- param = request.rel_url.query
- content = await request.content.read()
-
- eventhandler = EventAuthHandler()
- if (param.get('secret') and eventhandler.authorize(param['secret'])):
- eventDispatcher: EventDispatcher = EventDispatcher.get_instance()
- eventDispatcher.process_notification(str(content.decode('UTF-8')))
-
- return "OK"
-
- async def load_file(self, request):
- file_name = request.match_info.get('file_name', "Anonymous")
- resp = web.FileResponse(f'audio/{file_name}')
- return resp
diff --git a/callautomation-appointment-reminder/Controller/__init__.py b/callautomation-appointment-reminder/Controller/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/callautomation-appointment-reminder/EventHandler/EventAuthHandler.py b/callautomation-appointment-reminder/EventHandler/EventAuthHandler.py
deleted file mode 100644
index 61d00d4..0000000
--- a/callautomation-appointment-reminder/EventHandler/EventAuthHandler.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from ConfigurationManager import ConfigurationManager
-from Logger import Logger
-
-
-class EventAuthHandler:
-
- secret_value = None
-
- def __init__(self):
- configuration = ConfigurationManager.get_instance()
- self.secret_value = configuration.get_app_settings("SecretPlaceholder")
-
- if (self.secret_value == None or self.secret_value == ''):
- Logger.log_message(Logger.INFORMATION, "SecretPlaceholder is null")
- self.secret_value = "h3llowW0rld"
-
- def authorize(self, requestSecretValue):
- return ((requestSecretValue != None) and (requestSecretValue == self.secret_value))
-
- def get_secret_querystring(self):
- secretKey = "secret"
- return (secretKey + "=" + self.secret_value)
diff --git a/callautomation-appointment-reminder/EventHandler/EventDispatcher.py b/callautomation-appointment-reminder/EventHandler/EventDispatcher.py
deleted file mode 100644
index d555bb6..0000000
--- a/callautomation-appointment-reminder/EventHandler/EventDispatcher.py
+++ /dev/null
@@ -1,101 +0,0 @@
-from Logger import Logger
-from threading import Lock
-import threading
-import json
-from azure.core.messaging import CloudEvent
-from azure.communication.callingserver import CallingServerEventType, \
- CallConnectionStateChangedEvent, ToneReceivedEvent, \
- PlayAudioResultEvent, AddParticipantResultEvent
-
-
-class EventDispatcher:
- __instance = None
- notification_callbacks: dict = None
- subscription_lock = None
-
- def __init__(self):
- self.notification_callbacks = dict()
- self.subscription_lock = Lock()
-
- @staticmethod
- def get_instance():
- if EventDispatcher.__instance is None:
- EventDispatcher.__instance = EventDispatcher()
-
- return EventDispatcher.__instance
-
- def subscribe(self, event_type: str, event_key: str, notification_callback):
- self.subscription_lock.acquire
- event_id: str = self.build_event_key(event_type, event_key)
- self.notification_callbacks[event_id] = notification_callback
- self.subscription_lock.release
-
- def unsubscribe(self, event_type: str, event_key: str):
- self.subscription_lock.acquire
- event_id: str = self.build_event_key(event_type, event_key)
- del self.notification_callbacks[event_id]
- self.subscription_lock.release
-
- def build_event_key(self, event_type: str, event_key: str):
- return event_type + "-" + event_key
-
- def process_notification(self, request: str):
- call_event = self.extract_event(request)
- if call_event is not None:
- self.subscription_lock.acquire
- notification_callback = self.notification_callbacks.get(
- self.get_event_key(call_event))
- if (notification_callback != None):
- threading.Thread(target=notification_callback,
- args=(call_event,)).start()
-
- def get_event_key(self, call_event_base):
- if type(call_event_base) == CallConnectionStateChangedEvent:
- call_leg_id = call_event_base.call_connection_id
- key = self.build_event_key(
- CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT, call_leg_id)
- return key
- elif type(call_event_base) == ToneReceivedEvent:
- call_leg_id = call_event_base.call_connection_id
- key = self.build_event_key(
- CallingServerEventType.TONE_RECEIVED_EVENT, call_leg_id)
- return key
- elif type(call_event_base) == PlayAudioResultEvent:
- operation_context = call_event_base.operation_context
- key = self.build_event_key(
- CallingServerEventType.PLAY_AUDIO_RESULT_EVENT, operation_context)
- return key
- elif type(call_event_base) == AddParticipantResultEvent:
- operation_context = call_event_base.operation_context
- key = self.build_event_key(
- CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT, operation_context)
- return key
- return None
-
- def extract_event(self, content: str):
- try:
- event = CloudEvent.from_dict(json.loads(content)[0])
- if event.type == CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT:
- call_connection_state_changed_event = CallConnectionStateChangedEvent.deserialize(
- event.data)
- return call_connection_state_changed_event
-
- if event.type == CallingServerEventType.PLAY_AUDIO_RESULT_EVENT:
- play_audio_result_event = PlayAudioResultEvent.deserialize(
- event.data)
- return play_audio_result_event
-
- if event.type == CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT:
- add_participant_result_event = AddParticipantResultEvent.deserialize(
- event.data)
- return add_participant_result_event
-
- if event.type == CallingServerEventType.TONE_RECEIVED_EVENT:
- tone_received_event = ToneReceivedEvent.deserialize(event.data)
- return tone_received_event
-
- except Exception as ex:
- Logger.log_message(
- Logger.ERROR, "Failed to parse request content Exception: " + str(ex))
-
- return None
diff --git a/callautomation-appointment-reminder/EventHandler/__init__.py b/callautomation-appointment-reminder/EventHandler/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/callautomation-appointment-reminder/Ngrok/NgrokConnector.py b/callautomation-appointment-reminder/Ngrok/NgrokConnector.py
deleted file mode 100644
index 7e1ba15..0000000
--- a/callautomation-appointment-reminder/Ngrok/NgrokConnector.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import requests
-import json
-
-
-class NgrokConnector:
-
- def __init__(self):
- self.ngrok_tunnel_url = "http://127.0.0.1:4040/api/tunnels"
-
- def get_all_tunnels(self):
- tunnel_url = requests.get(self.ngrok_tunnel_url).text
- tunnel_list = json.loads(tunnel_url)
- return tunnel_list
diff --git a/callautomation-appointment-reminder/Ngrok/NgrokService.py b/callautomation-appointment-reminder/Ngrok/NgrokService.py
deleted file mode 100644
index f08761b..0000000
--- a/callautomation-appointment-reminder/Ngrok/NgrokService.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from time import sleep
-from Ngrok.NgrokConnector import NgrokConnector
-import os
-import subprocess
-import psutil
-import signal
-from pathlib import Path
-from subprocess import CREATE_NEW_CONSOLE
-
-
-class NgrokService:
- # The NGROK process
- __ngrokProcess = None # Process
- # NgrokConnector connector;
- __connector = None # NgrokConnector
-
- def __init__(self, ngrokPath, authToken):
- self.__connector = NgrokConnector()
- self.ensure_ngrok_not_running()
- self.create_ngrok_process(ngrokPath, authToken)
-
- #
- # Ensures that NGROK is not running.
- #
-
- def ensure_ngrok_not_running(self):
- process_name = "ngrok.exe"
-
- for proc in psutil.process_iter():
- try:
- # Check if process name contains the given name string.
- if process_name.lower() in proc.name().lower():
- raise(
- "Looks like NGROK is still running. Please kill it before running the provider again.")
-
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
- pass
-
- #
- # Kill ngrok.exe process
- #
-
- def dispose(self):
- out, err = self.__ngrokProcess.communicate()
- for line in out.splitlines():
- if 'ngrok.exe' in line:
- pid = int(line.split(None, 1)[0])
- os.kill(pid, signal.SIGKILL)
-
- #
- # Creates the NGROK process.
- #
-
- def create_ngrok_process(self, ngrokPath, authToken):
- auth_token_args = ""
- if (authToken and len(authToken)):
- auth_token_args = " --authtoken " + authToken
-
- executable = str(Path(ngrokPath, "ngrok.exe"))
- os.chmod(executable, 0o777)
- self.__ngrokProcess = subprocess.Popen([executable, "http", "http://localhost:9007/", "-host-header", "localhost:9007"],creationflags=CREATE_NEW_CONSOLE)
-
- #
- # Get Ngrok URL
- #
-
- def get_ngrok_url(self):
- try:
- totalAttempts = 4
- while(totalAttempts > 0):
- # Wait for fetching the ngrok url as ngrok process might not be started yet.
- sleep(2)
- tunnels = self.__connector.get_all_tunnels()
- if (tunnels and len(tunnels['tunnels'])):
- # Do the parsing of the get
- ngrok_url = tunnels['tunnels'][0]['public_url']
- return ngrok_url
-
- totalAttempts = totalAttempts - 1
-
- except Exception as ex:
- raise Exception("Failed to retrieve ngrok url --> " + str(ex))
diff --git a/callautomation-appointment-reminder/OutboundCallReminder.py b/callautomation-appointment-reminder/OutboundCallReminder.py
deleted file mode 100644
index aab7169..0000000
--- a/callautomation-appointment-reminder/OutboundCallReminder.py
+++ /dev/null
@@ -1,344 +0,0 @@
-from asyncio.tasks import FIRST_COMPLETED
-import re
-import traceback
-import uuid
-import asyncio
-from azure.communication.identity._shared.models import CommunicationIdentifier
-from CallConfiguration import CallConfiguration
-from ConfigurationManager import ConfigurationManager
-from Logger import Logger
-from CommunicationIdentifierKind import CommunicationIdentifierKind
-from EventHandler.EventDispatcher import EventDispatcher
-from azure.communication.identity import CommunicationUserIdentifier
-from azure.communication.chat import PhoneNumberIdentifier
-from azure.communication.callingserver import CallingServerClient, \
- CallConnection, CallConnectionStateChangedEvent, ToneReceivedEvent, \
- ToneInfo, PlayAudioResultEvent, AddParticipantResultEvent, CallMediaType, \
- CallingEventSubscriptionType, CreateCallOptions, CallConnectionState, \
- CallingOperationStatus, ToneValue, PlayAudioOptions, CallingServerEventType, \
- PlayAudioResult, AddParticipantResult
-
-PLAY_AUDIO_AWAIT_TIMER = 30
-ADD_PARTICIPANT_AWAIT_TIMER = 60
-
-
-class OutboundCallReminder:
- call_configuration: CallConfiguration = None
- calling_server_client: CallingServerClient = None
- call_connection: CallConnection = None
-
- call_connected_task: asyncio.Future = None
- play_audio_completed_task: asyncio.Future = None
- call_terminated_task: asyncio.Future = None
- tone_received_complete_task: asyncio.Future = None
- add_participant_complete_task: asyncio.Future = None
- max_retry_attempt_count: int = None
-
- user_identity_regex: str = "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}"
- phone_identity_regex: str = "^\\+\\d{10,14}$"
-
- def __init__(self, call_configuration):
-
- self.call_configuration = call_configuration
- self.calling_server_client = CallingServerClient.from_connection_string(
- self.call_configuration.connection_string)
- self.max_retry_attempt_count: int = int(
- ConfigurationManager.get_instance().get_app_settings("MaxRetryCount"))
-
- async def report(self, target_phone_number, participant):
-
- try:
- await self.create_call_async(target_phone_number)
-
- self.register_to_dtmf_result_event(
- self.call_connection.call_connection_id)
-
- await self.play_audio_async()
- play_audio_completed = await self.play_audio_completed_task
-
- if (not play_audio_completed):
- self.hang_up_async()
- else:
- try:
- tone_received_complete = await asyncio.wait_for(self.tone_received_complete_task, timeout=PLAY_AUDIO_AWAIT_TIMER)
- except asyncio.TimeoutError as ex:
- Logger.log_message(
- Logger.INFORMATION, "No response from user in 30 sec, initiating hangup")
- else:
- if (tone_received_complete):
- Logger.log_message(Logger.INFORMATION, "Initiating add participant from number --> " +
- target_phone_number + " and participant identifier is -- > " + participant)
-
- self.add_participant(participant)
- add_participant_completed = None
- try:
- add_participant_completed = await asyncio.wait_for(self.add_participant_complete_task, timeout=ADD_PARTICIPANT_AWAIT_TIMER)
- except asyncio.TimeoutError as ex:
- Logger.log_message(
- Logger.INFORMATION, "Add participant failed with timeout -- > " + str(ex))
- finally:
- if (not add_participant_completed):
- await asyncio.create_task(self.retry_add_participant_async(participant))
-
- self.hang_up_async()
-
- # Wait for the call to terminate
- await self.call_terminated_task
-
- except Exception as ex:
- Logger.log_message(
- Logger.ERROR, "Call ended unexpectedly, reason -- > " + str(ex))
- print(traceback.format_exc())
-
- async def create_call_async(self, target_phone_number):
- try:
- source = CommunicationUserIdentifier(
- self.call_configuration.source_identity)
- targets = [PhoneNumberIdentifier(target_phone_number)]
-
- call_modality = [CallMediaType.AUDIO]
- event_subscription_type = [
- CallingEventSubscriptionType.PARTICIPANTS_UPDATED, CallingEventSubscriptionType.TONE_RECEIVED]
-
- options: CreateCallOptions = CreateCallOptions(
- callback_uri=self.call_configuration.app_callback_url, requested_media_types=call_modality, requested_call_events=event_subscription_type)
- options.alternate_Caller_Id = PhoneNumberIdentifier(
- self.call_configuration.source_phone_number)
- options.subject = None
-
- Logger.log_message(Logger.INFORMATION,
- "Performing CreateCall operation")
-
- self.call_connection = self.calling_server_client.create_call_connection(
- source, targets, options)
-
- Logger.log_message(
- Logger.INFORMATION, "Call initiated with Call Leg id -- >" + self.call_connection.call_connection_id)
-
- self.register_to_callstate_change_event(
- self.call_connection.call_connection_id)
-
- await self.call_connected_task
-
- except Exception as ex:
- Logger.log_message(
- Logger.ERROR, "Failure occured while creating/establishing the call. Exception -- >" + str(ex))
-
- def register_to_callstate_change_event(self, call_leg_id):
- self.call_terminated_task = asyncio.Future()
- self.call_connected_task = asyncio.Future()
-
- # Set the callback method
- def call_state_change_notificaiton(call_event):
- try:
- call_state_changes: CallConnectionStateChangedEvent = call_event
- Logger.log_message(
- Logger.INFORMATION, "Call State changed to -- > " + call_state_changes.call_connection_state)
-
- if (call_state_changes.call_connection_state == CallConnectionState.CONNECTED):
- Logger.log_message(Logger.INFORMATION,
- "Call State successfully connected")
- self.call_connected_task.set_result(True)
-
- elif (call_state_changes.call_connection_state == CallConnectionState.DISCONNECTED):
- EventDispatcher.get_instance().unsubscribe(
- CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT, call_leg_id)
- self.call_terminated_task.set_result(True)
-
- except asyncio.InvalidStateError:
- pass
-
- # Subscribe to the event
- EventDispatcher.get_instance().subscribe(CallingServerEventType.CALL_CONNECTION_STATE_CHANGED_EVENT,
- call_leg_id, call_state_change_notificaiton)
-
- def register_to_dtmf_result_event(self, call_leg_id):
- self.tone_received_complete_task = asyncio.Future()
-
- def dtmf_received_event(call_event):
- tone_received_event: ToneReceivedEvent = call_event
- tone_info: ToneInfo = tone_received_event.tone_info
-
- Logger.log_message(Logger.INFORMATION,
- "Tone received -- > : " + str(tone_info.tone))
-
- if (tone_info.tone == ToneValue.TONE1):
- self.tone_received_complete_task.set_result(True)
- else:
- self.tone_received_complete_task.set_result(False)
-
- EventDispatcher.get_instance().unsubscribe(
- CallingServerEventType.TONE_RECEIVED_EVENT, call_leg_id)
- # cancel playing audio
- self.cancel_media_processing()
-
- # Subscribe to event
- EventDispatcher.get_instance().subscribe(
- CallingServerEventType.TONE_RECEIVED_EVENT, call_leg_id, dtmf_received_event)
-
- def cancel_media_processing(self):
- Logger.log_message(
- Logger.INFORMATION, "Performing cancel media processing operation to stop playing audio")
-
- self.call_connection.cancel_all_media_operations()
-
- async def play_audio_async(self):
- try:
- self.play_audio_completed_task = asyncio.Future()
- # Preparing data for request
- loop = True
- audio_file_uri = self.call_configuration.audio_file_url
- operation_context = str(uuid.uuid4())
- audio_file_id = str(uuid.uuid4())
- callbackuri = self.call_configuration.app_callback_url
-
- play_audio_options: PlayAudioOptions = PlayAudioOptions(loop=loop,
- operation_context=operation_context,
- audio_file_id=audio_file_id,
- callback_uri=callbackuri)
-
- Logger.log_message(Logger.INFORMATION,
- "Performing PlayAudio operation")
- play_audio_response: PlayAudioResult = self.call_connection.play_audio(audio_file_uri=audio_file_uri,
- play_audio_options=play_audio_options)
-
- Logger.log_message(Logger.INFORMATION, "playAudioWithResponse -- > " + str(play_audio_response) +
- ", Id: " + play_audio_response.operation_id + ", OperationContext: " + play_audio_response.operation_context + ", OperationStatus: " +
- play_audio_response.status)
-
- if (play_audio_response.status == CallingOperationStatus.RUNNING):
- Logger.log_message(
- Logger.INFORMATION, "Play Audio state -- > " + str(CallingOperationStatus.RUNNING))
-
- # listen to play audio events
- self.register_to_play_audio_result_event(
- play_audio_response.operation_context)
-
- tasks = []
- tasks.append(self.play_audio_completed_task)
- tasks.append(asyncio.create_task(
- asyncio.sleep(PLAY_AUDIO_AWAIT_TIMER)))
-
- await asyncio.wait(tasks, return_when=FIRST_COMPLETED)
-
- if(not self.play_audio_completed_task.done()):
- Logger.log_message(
- Logger.INFORMATION, "No response from user in 30 sec, initiating hangup")
- self.play_audio_completed_task.set_result(False)
- self.tone_received_complete_task.set_result(False)
-
- except Exception as ex:
- if (self.play_audio_completed_task.cancelled()):
- Logger.log_message(Logger.INFORMATION,
- "Play audio operation cancelled")
- else:
- Logger.log_message(
- Logger.INFORMATION, "Failure occured while playing audio on the call. Exception: " + str(ex))
-
- def hang_up_async(self):
- Logger.log_message(Logger.INFORMATION, "Performing Hangup operation")
-
- self.call_connection.hang_up()
-
- def register_to_play_audio_result_event(self, operation_context):
- def play_prompt_response_notification(call_event):
- play_audio_result_event: PlayAudioResultEvent = call_event
- Logger.log_message(
- Logger.INFORMATION, "Play audio status -- > " + str(play_audio_result_event.status))
-
- if (play_audio_result_event.status == CallingOperationStatus.COMPLETED):
- EventDispatcher.get_instance().unsubscribe(
- CallingServerEventType.PLAY_AUDIO_RESULT_EVENT, operation_context)
- self.play_audio_completed_task.set_result(True)
- elif (play_audio_result_event.status == CallingOperationStatus.FAILED):
- self.play_audio_completed_task.set_result(False)
-
- # Subscribe to event
- EventDispatcher.get_instance().subscribe(CallingServerEventType.PLAY_AUDIO_RESULT_EVENT,
- operation_context, play_prompt_response_notification)
-
- async def retry_add_participant_async(self, addedParticipant):
- retry_attempt_count: int = 1
- while (retry_attempt_count <= self.max_retry_attempt_count):
- Logger.log_message(Logger.INFORMATION, "Retrying add participant attempt -- > " +
- str(retry_attempt_count) + " is in progress")
- self.add_participant(addedParticipant)
-
- add_participant_result = None
- try:
- add_participant_result = await asyncio.wait_for(self.add_participant_complete_task, timeout=ADD_PARTICIPANT_AWAIT_TIMER)
- except asyncio.TimeoutError as ex:
- Logger.log_message(
- Logger.INFORMATION, "Retry add participant failed with timeout -- > " + str(retry_attempt_count) + str(ex))
- finally:
- if (add_participant_result):
- return
- else:
- Logger.log_message(
- Logger.INFORMATION, "Retry add participant attempt -- > " + str(retry_attempt_count) + " has failed")
- retry_attempt_count = retry_attempt_count + 1
-
- def add_participant(self, addedParticipant):
- identifier_kind: CommunicationIdentifierKind = self.get_identifier_kind(
- addedParticipant)
-
- if (identifier_kind == CommunicationIdentifierKind.UNKNOWN_IDENTITY):
- Logger.log_message(
- Logger.INFORMATION, "Unknown identity provided. Enter valid phone number or communication user id")
- return True
- else:
- participant: CommunicationIdentifier = None
- operation_context = str(uuid.uuid4())
-
- self.register_to_add_participants_result_event(operation_context)
-
- if (identifier_kind == CommunicationIdentifierKind.USER_IDENTITY):
- participant = CommunicationUserIdentifier(id=addedParticipant)
-
- elif (identifier_kind == CommunicationIdentifierKind.PHONE_IDENTITY):
- participant = PhoneNumberIdentifier(value=addedParticipant)
-
- alternate_caller_id = PhoneNumberIdentifier(
- value=str(ConfigurationManager.get_instance().get_app_settings("SourcePhone")))
-
- add_participant_response: AddParticipantResult = self.call_connection.add_participant(participant=participant,
- alternate_caller_id=alternate_caller_id, operation_context=operation_context)
- Logger.log_message(
- Logger.INFORMATION, "addParticipantWithResponse -- > " + add_participant_response.additional_properties.get("operationId"))
-
- Logger.log_message(
- Logger.INFORMATION, "addParticipantWithResponse -- > " + add_participant_response.additional_properties.get("status"))
-
- def register_to_add_participants_result_event(self, operation_context):
- if(self.add_participant_complete_task):
- self.add_participant_complete_task = None
-
- self.add_participant_complete_task = asyncio.Future()
-
- def add_participant_received_event(call_event):
- add_participants_updated_event: AddParticipantResultEvent = call_event
- operation_status: CallingOperationStatus = add_participants_updated_event.status
- if (operation_status == CallingOperationStatus.COMPLETED):
- Logger.log_message(
- Logger.INFORMATION, "Add participant status -- > " + operation_status)
- self.add_participant_complete_task.set_result(True)
- elif(operation_status == CallingOperationStatus.FAILED):
- Logger.log_message(
- Logger.INFORMATION, "Add participant status -- > " + operation_status)
- self.add_participant_complete_task.set_result(False)
-
- EventDispatcher.get_instance().unsubscribe(
- CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT, operation_context)
-
- # Subscribe to event
- EventDispatcher.get_instance().subscribe(CallingServerEventType.ADD_PARTICIPANT_RESULT_EVENT,
- operation_context, add_participant_received_event)
-
- def get_identifier_kind(self, participantnumber: str):
- # checks the identity type returns as string
- if(re.search(self.user_identity_regex, participantnumber)):
- return CommunicationIdentifierKind.USER_IDENTITY
- elif(re.search(self.phone_identity_regex, participantnumber)):
- return CommunicationIdentifierKind.PHONE_IDENTITY
- else:
- return CommunicationIdentifierKind.UNKNOWN_IDENTITY
diff --git a/callautomation-appointment-reminder/audio/AppointmentCancelledAudio.wav b/callautomation-appointment-reminder/audio/AppointmentCancelledAudio.wav
new file mode 100644
index 0000000..cc9e1c7
Binary files /dev/null and b/callautomation-appointment-reminder/audio/AppointmentCancelledAudio.wav differ
diff --git a/callautomation-appointment-reminder/audio/AppointmentConfirmedAudio.wav b/callautomation-appointment-reminder/audio/AppointmentConfirmedAudio.wav
new file mode 100644
index 0000000..13b92ec
Binary files /dev/null and b/callautomation-appointment-reminder/audio/AppointmentConfirmedAudio.wav differ
diff --git a/callautomation-appointment-reminder/audio/AppointmentReminderMenu.wav b/callautomation-appointment-reminder/audio/AppointmentReminderMenu.wav
new file mode 100644
index 0000000..f119e03
Binary files /dev/null and b/callautomation-appointment-reminder/audio/AppointmentReminderMenu.wav differ
diff --git a/callautomation-appointment-reminder/audio/InvalidInputAudio.wav b/callautomation-appointment-reminder/audio/InvalidInputAudio.wav
new file mode 100644
index 0000000..3dfa656
Binary files /dev/null and b/callautomation-appointment-reminder/audio/InvalidInputAudio.wav differ
diff --git a/callautomation-appointment-reminder/audio/TimedoutAudio.wav b/callautomation-appointment-reminder/audio/TimedoutAudio.wav
new file mode 100644
index 0000000..fb76eeb
Binary files /dev/null and b/callautomation-appointment-reminder/audio/TimedoutAudio.wav differ
diff --git a/callautomation-appointment-reminder/audio/custom-message.wav b/callautomation-appointment-reminder/audio/custom-message.wav
deleted file mode 100644
index 98c2726..0000000
Binary files a/callautomation-appointment-reminder/audio/custom-message.wav and /dev/null differ
diff --git a/callautomation-appointment-reminder/audio/sample-message.wav b/callautomation-appointment-reminder/audio/sample-message.wav
deleted file mode 100644
index 98c2726..0000000
Binary files a/callautomation-appointment-reminder/audio/sample-message.wav and /dev/null differ
diff --git a/callautomation-appointment-reminder/config.ini b/callautomation-appointment-reminder/config.ini
index 605cc17..e569fae 100644
--- a/callautomation-appointment-reminder/config.ini
+++ b/callautomation-appointment-reminder/config.ini
@@ -1,21 +1,20 @@
# app settings
-[default]
-#1. Configurations related to Communication Service resource
-Connectionstring=%Connectionstring%
+[DEFAULT]
+
+connectionstring=%Connection string%
#
-SourcePhone=%SourcePhone%
-#
-DestinationIdentities=%DestinationIdentities%
-MaxRetryCount=3
+sourcephone=%Source Phone%
+targetIdentity=%Target Identity%
+eventcallbackroute= /api/callbacks
+app_base_uri= %App Base Uri%
+appointmentremindermenuaudio= /audio/AppointmentReminderMenu.wav
+appointmentconfirmedaudio= /audio/AppointmentConfirmedAudio.wav
+appointmentcancelledaudio= /audio/AppointmentCancelledAudio.wav
+agentaudio= /audio/AgentAudio.wav
+invalidinputaudio= /audio/InvalidInputAudio.wav
+timedoutaudio = /audio/TimedoutAudio.wav
+
+
+
-# 2. Configurations related to environment
-NgrokExePath=%NgrokExePath%
-SecretPlaceholder=h3llowW0rld
-#
-CognitiveServiceKey=
-#
-CognitiveServiceRegion=
-#
-CustomMessage=Hello this is a reminder call. If you would like to speak with a representative Press 1 or 2 if you want to hang up.
diff --git a/callautomation-appointment-reminder/program.py b/callautomation-appointment-reminder/program.py
index 86c6af3..f036f49 100644
--- a/callautomation-appointment-reminder/program.py
+++ b/callautomation-appointment-reminder/program.py
@@ -1,175 +1,158 @@
-import asyncio
-import nest_asyncio
-from azure.communication.identity._shared.models import CommunicationIdentifier, CommunicationUserIdentifier
-from Controller.OutboundCallController import OutboundCallController
+import re
+from azure.communication.identity._shared.models import CommunicationIdentifier,PhoneNumberIdentifier,\
+ CommunicationUserIdentifier,CommunicationIdentifierKind,identifier_from_raw_id
+import json
+from aiohttp import web
from Logger import Logger
-from ConfigurationManager import ConfigurationManager
-from CallConfiguration import CallConfiguration
-from Ngrok.NgrokService import NgrokService
-from azure.communication.identity import CommunicationIdentityClient
-from azure.cognitiveservices.speech import AudioDataStream, SpeechConfig, SpeechSynthesizer, SpeechSynthesisOutputFormat
-from callautomation import OutboundCallReminder
-
-
-class Program():
-
+from urllib.parse import urlencode
+from ConfigurationManager import ConfigurationManager,CallConfiguration
+from azure.communication.callautomation import CallAutomationClient,CallInvite,\
+CallAutomationEventParser,CallConnected,CallMediaRecognizeDtmfOptions,\
+CallConnectionClient,CallDisconnected,PlaySource,FileSource,ParticipantsUpdated,DtmfTone,\
+RecognizeCanceled,RecognizeCompleted,RecognizeFailed,PlayCompleted,PlayFailed
+
+class Program():
+ target_number = None
+ ngrok_url = None
+ call_configuration: CallConfiguration = None
+ calling_automation_client: CallAutomationClient = None
+ call_connection: CallConnectionClient = None
configuration_manager = None
- __ngrok_service = None
- url = "http://localhost:9007"
+ user_identity_regex: str = '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}'
+ phone_identity_regex: str = '^\\+\\d{10,14}$'
+
+ def get_identifier_kind(self, participantnumber: str):
+ # checks the identity type returns as string
+ if(re.search(self.user_identity_regex, participantnumber)):
+ return CommunicationIdentifierKind.COMMUNICATION_USER
+ elif(re.search(self.phone_identity_regex, participantnumber)):
+ return CommunicationIdentifierKind.PHONE_NUMBER
+ else:
+ return CommunicationIdentifierKind.UNKNOWN
+
+ configuration_manager = ConfigurationManager.get_instance()
+ calling_automation_client = CallAutomationClient.from_connection_string(configuration_manager.get_app_settings('Connectionstring'))
+ ngrok_url =configuration_manager.get_app_settings('app_base_uri')
+ targets_identifiers= {};
def __init__(self):
- Logger.log_message(Logger.INFORMATION, "Starting ACS Sample App ")
- # Get configuration properties
- self.configuration_manager = ConfigurationManager.get_instance()
-
- async def program(self):
- # Start Ngrok service
- ngrok_url = self.start_ngrok_service()
-
+ Logger.log_message(Logger.INFORMATION, 'Starting ACS Sample App ')
+ # Get configuration properties
+ self.app = web.Application()
+ self.app.add_routes([web.post('/api/call',self.run_sample)])
+ self.app.add_routes([web.get('/audio/{file_name}', self.load_file)])
+ self.app.add_routes([web.post('/api/callbacks',self.start_callBack)])
+ web.run_app(self.app, port=8080)
+
+ async def run_sample(self,request):
+ self.call_configuration =self.initiate_configuration(self.ngrok_url)
try:
- if (ngrok_url and len(ngrok_url)):
- Logger.log_message(Logger.INFORMATION,
- "Server started at -- > " + self.url)
-
- run_sample = asyncio.create_task(self.run_sample(ngrok_url))
-
- loop = asyncio.get_event_loop()
- loop.run_until_complete(OutboundCallController())
- await run_sample
-
- else:
- Logger.log_message(Logger.INFORMATION,
- "Failed to start Ngrok service")
-
+ target_ids = self.configuration_manager.get_app_settings('TargetIdentity')
+ Target_identities= target_ids.split(';')
+ for target_id in Target_identities :
+ if(target_id and len(target_id)):
+ target_Identity = self.get_identifier_kind(target_id)
+ if target_Identity == CommunicationIdentifierKind.COMMUNICATION_USER :
+ Callinvite=CallInvite(CommunicationUserIdentifier(target_id))
+ if target_Identity == CommunicationIdentifierKind.PHONE_NUMBER :
+ Callinvite=CallInvite(PhoneNumberIdentifier(target_id),sourceCallIdNumber=PhoneNumberIdentifier(self.call_configuration.source_phone_number))
+ call_back_url= self.call_configuration.app_callback_url
+ Logger.log_message(Logger.INFORMATION,'Performing CreateCall operation')
+ self.call_connection_response = CallAutomationClient.create_call(self.calling_automation_client ,Callinvite,callback_uri=call_back_url)
+ self.targets_identifiers[self.call_connection_response.call_connection.call_connection_id] = target_id;
+ Logger.log_message(
+ Logger.INFORMATION, 'Call initiated with Call Leg id -- >' + self.call_connection_response.call_connection.call_connection_id)
except Exception as ex:
Logger.log_message(
- Logger.ERROR, "Failed to start Ngrok service --> "+str(ex))
-
- Logger.log_message(Logger.INFORMATION,
- "Press 'Ctrl + C' to exit the sample")
- self.__ngrok_service.dispose()
-
- def start_ngrok_service(self):
- try:
- ngrokPath = self.configuration_manager.get_app_settings(
- "NgrokExePath")
-
- if (not(len(ngrokPath))):
- Logger.log_message(Logger.INFORMATION,
- "Ngrok path not provided")
- return None
-
- Logger.log_message(Logger.INFORMATION, "Starting Ngrok")
- self.__ngrok_service = NgrokService(ngrokPath, None)
-
- Logger.log_message(Logger.INFORMATION, "Fetching Ngrok Url")
- ngrok_url = self.__ngrok_service.get_ngrok_url()
-
- Logger.log_message(Logger.INFORMATION,
- "Ngrok Started with url -- > " + ngrok_url)
- return ngrok_url
-
- except Exception as ex:
- Logger.log_message(Logger.INFORMATION,
- "Ngrok service got failed -- > " + str(ex))
- return None
-
- async def run_sample(self, app_base_url):
- call_configuration = self.initiate_configuration(app_base_url)
- try:
- outbound_call_pairs = self.configuration_manager.get_app_settings(
- "DestinationIdentities")
-
- if (outbound_call_pairs and len(outbound_call_pairs)):
- identities = outbound_call_pairs.split(";")
- tasks = []
- for identity in identities:
- pair = identity.split(",")
- task = asyncio.ensure_future(OutboundCallReminder(
- call_configuration).report(pair[0].strip(), pair[1].strip()))
- tasks.append(task)
-
- _ = await asyncio.gather(*tasks)
-
+ Logger.ERROR, 'Failure occured while creating/establishing the call. Exception -- > ' + str(ex))
+
+ async def start_callBack(self,request):
+ try:
+ _content = await request.content.read()
+ event = CallAutomationEventParser.parse(_content)
+ call_connection = self.calling_automation_client.get_call_connection(event.call_connection_id)
+ call_connection_media =call_connection.get_call_media()
+ if event.__class__ == CallConnected:
+ Logger.log_message(Logger.INFORMATION,'CallConnected event received for call connection id --> '
+ + event.call_connection_id)
+ recognize_options = CallMediaRecognizeDtmfOptions(identifier_from_raw_id(self.targets_identifiers[event.call_connection_id]),max_tones_to_collect=1)
+ recognize_options.interrupt_prompt = True
+ recognize_options.inter_tone_timeout = 10
+ recognize_options.initial_silence_timeout=5
+ File_source=FileSource(uri=(self.call_configuration.app_base_url + self.call_configuration.audio_file_name))
+ File_source.play_source_id= 'AppointmentReminderMenu'
+ recognize_options.play_prompt = File_source
+ recognize_options.operation_context= 'AppointmentReminderMenu'
+ call_connection_media.start_recognizing(recognize_options)
+ if event.__class__ == RecognizeCompleted and event.operation_context == 'AppointmentReminderMenu' :
+ Logger.log_message(Logger.INFORMATION,'RecognizeCompleted event received for call connection id --> '+ event.call_connection_id
+ +'Correlation id:'+event.correlation_id)
+ toneDetected=event.collect_tones_result.tones[0]
+ if toneDetected == DtmfTone.ONE:
+ play_Source = FileSource(uri=(self.call_configuration.app_base_url+self.call_configuration.appointment_confirmed_audio))
+ call_connection_media.play_to_all(play_Source,content='ResponseToDtmf')
+ elif toneDetected == DtmfTone.TWO :
+ play_Source = FileSource(uri=(self.call_configuration.app_base_url+self.call_configuration.appointment_cancelled_audio))
+ call_connection_media.play_to_all(play_Source,content='ResponseToDtmf')
+ else:
+ play_Source = FileSource(uri=(self.call_configuration.app_base_url+self.call_configuration.invalid_input_audio))
+ call_connection_media.play_to_all(play_Source)
+
+ if event.__class__ == RecognizeFailed and event.operation_context == 'AppointmentReminderMenu' :
+ Logger.log_message(Logger.INFORMATION,'Recognition timed out for call connection id --> '+ event.call_connection_id
+ +'Correlation id:'+event.correlation_id)
+ play_Source = FileSource(uri=(self.call_configuration.app_base_url+self.call_configuration.timed_out_audio))
+ call_connection_media.play_to_all(play_Source)
+ if event.__class__ == PlayCompleted:
+ Logger.log_message(Logger.INFORMATION,'PlayCompleted event received for call connection id --> '+ event.call_connection_id
+ +'Call Connection Properties :'+event.correlation_id)
+ call_connection.hang_up(True)
+ if event.__class__ == PlayFailed:
+ Logger.log_message(Logger.INFORMATION,'PlayFailed event received for call connection id --> '+ event.call_connection_id
+ +'Call Connection Properties :'+event.correlation_id)
+ call_connection.hang_up(True)
+ if event.__class__ == ParticipantsUpdated :
+ Logger.log_message(Logger.INFORMATION,'Participants Updated --> ')
+ if event.__class__ == CallDisconnected :
+ Logger.log_message(Logger.INFORMATION,'Call Disconnected event received for call connection id --> '
+ + event.call_connection_id)
+
except Exception as ex:
Logger.log_message(
- Logger.ERROR, "Failed to initiate the outbound call Exception -- > " + str(ex))
-
- self.delete_user(call_configuration.connection_string,
- call_configuration.source_identity)
-
- #
+ Logger.ERROR, 'Failed to get recognize Options . --> ' + str(ex))
+
+
+ #
# Fetch configurations from App Settings and create source identity
#
- # The base url of the app.
+ # The base url of the app.
# The
def initiate_configuration(self, app_base_url):
- connection_string = self.configuration_manager.get_app_settings(
- "Connectionstring")
- source_phone_number = self.configuration_manager.get_app_settings(
- "SourcePhone")
-
- source_identity = self.create_user(connection_string)
- audio_file_name = self.generate_custom_audio_message()
-
- return CallConfiguration(connection_string, source_identity, source_phone_number, app_base_url, audio_file_name)
-
- #
- # Get .wav Audio file
- #
-
- def generate_custom_audio_message(self):
- configuration_manager = ConfigurationManager()
- key = configuration_manager.get_app_settings("CognitiveServiceKey")
- region = configuration_manager.get_app_settings(
- "CognitiveServiceRegion")
- custom_message = configuration_manager.get_app_settings(
- "CustomMessage")
-
try:
- if (key and len(key) and region and len(region) and custom_message and len(custom_message)):
-
- config = SpeechConfig(subscription=key, region=region)
- config.set_speech_synthesis_output_format(
- SpeechSynthesisOutputFormat["Riff24Khz16BitMonoPcm"])
-
- synthesizer = SpeechSynthesizer(SpeechSynthesizer=config)
-
- result = synthesizer.speak_text_async(custom_message).get()
- stream = AudioDataStream(result)
- stream.save_to_wav_file("/audio/custom-message.wav")
-
- return "custom-message.wav"
-
- return "sample-message.wav"
+ connection_string = self.configuration_manager.get_app_settings('connectionstring')
+ source_phone_number = self.configuration_manager.get_app_settings('Sourcephone')
+ event_callback_route=self.configuration_manager.get_app_settings('eventcallbackroute')
+ audio_file_name = self.configuration_manager.get_app_settings('appointmentremindermenuaudio')
+ appointment_confirmed_audio = self.configuration_manager.get_app_settings('appointmentconfirmedaudio')
+ appointment_cancelled_audio = self.configuration_manager.get_app_settings('appointmentcancelledaudio')
+ timed_out_audio = self.configuration_manager.get_app_settings('timedoutaudio')
+ invalid_input_audio = self.configuration_manager.get_app_settings('invalidinputaudio')
+
+ return CallConfiguration(connection_string, source_phone_number, app_base_url,
+ audio_file_name,event_callback_route,appointment_confirmed_audio,
+ appointment_cancelled_audio,timed_out_audio,invalid_input_audio)
except Exception as ex:
Logger.log_message(
- Logger.ERROR, "Exception while generating text to speech, falling back to sample audio. Exception -- > " + str(ex))
- return "sample-message.wav"
-
+ Logger.ERROR, 'Failed to CallConfiguration. Exception -- > ' + str(ex))
+
#
- # Create new user
- #
-
- def create_user(self, connection_string):
- client = CommunicationIdentityClient.from_connection_string(
- connection_string)
- user: CommunicationIdentifier = client.create_user()
- return user.properties.get('id')
-
- #
- # Delete the user
+ # Get .wav Audio file
#
-
- def delete_user(self, connection_string, source):
- client = CommunicationIdentityClient.from_connection_string(
- connection_string)
- user = CommunicationUserIdentifier(source)
- client.delete_user(user)
-
-
-if __name__ == "__main__":
- nest_asyncio.apply()
- obj = Program()
- asyncio.run(obj.program())
+ async def load_file(self, request):
+ file_name = request.match_info.get('file_name', 'Anonymous')
+ resp = web.FileResponse(f'Audio/{file_name}')
+ return resp
+
+if __name__ == '__main__':
+ Program()
\ No newline at end of file
diff --git a/callautomation-appointment-reminder/readme.md b/callautomation-appointment-reminder/readme.md
index 286ccf5..9a677fd 100644
--- a/callautomation-appointment-reminder/readme.md
+++ b/callautomation-appointment-reminder/readme.md
@@ -4,13 +4,13 @@ languages:
- python
products:
- azure
-- azure-communication-services
+- azure-communication-CallAutomation
---
-# Outbound Reminder Call Sample
+# Appointment Reminder Call Sample
-This sample application shows how the Azure Communication Services Server, Calling package can be used to build IVR related solutions. This sample makes an outbound call to a phone number or a communication identifier and plays an audio message. If the callee presses 1 (tone1), to reschedule an appointment, then the application invites a new participant and then leaves the call. If the callee presses any other key then the application ends the call. This sample application is also capable of making multiple concurrent outbound calls.
-The application is a console based application build using Python 3.9.
+This sample application shows how the Azure Communication CallAutomation, Calling package can be used to build IVR related solutions. This sample makes an outbound call to a phone number or a communication identifier and plays an audio message. If the callee presses 1 (tone1), to reschedule an appointment, then leaves the call.If the callee presses 2(tone2) cancell an appointment,then leave the call. If the callee presses any other key then the application ends the call.This sample application is also capable of making multiple concurrent outbound calls.
+The application is a console based application build using Python 3.9 and above.
## Getting started
@@ -20,33 +20,32 @@ The application is a console based application build using Python 3.9.
- [Python](https://www.python.org/downloads/) 3.9 and above
- Create an Azure Communication Services resource. For details, see [Create an Azure Communication Resource](https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource). You'll need to record your resource **connection string** for this sample.
- Get a phone number for your new Azure Communication Services resource. For details, see [Get a phone number](https://docs.microsoft.com/azure/communication-services/quickstarts/telephony-sms/get-phone-number?pivots=platform-azp)
+- Download and install [VS Code](https://code.visualstudio.com/download) or [Visual Studio (2022 v17.4.0 and above)](https://visualstudio.microsoft.com/vs/)
+-[Python311](https://www.python.org/downloads/) (Make sure to install version that corresponds with your visual studio instance, 32 vs 64 bit)
- Download and install [Ngrok](https://www.ngrok.com/download). As the sample is run locally, Ngrok will enable the receiving of all the events.
-- Download and install [Visual C++](https://support.microsoft.com/en-us/topic/the-latest-supported-visual-c-downloads-2647da03-1eea-4433-9aff-95f26a218cc0)
-- (Optional) Create Azure Speech resource for generating custom message to be played by application. Follow [here](https://docs.microsoft.com/azure/cognitive-services/speech-service/overview#try-the-speech-service-for-free) to create the resource.
+- Generate Ngrok Url by using below steps.
+ - Open command prompt or powershell window on the machine using to run the sample.
+ - Navigate to directory path where Ngrok.exe file is located. Then, run:
+ - ngrok http {portNumber}(For e.g. ngrok http 8080)
+ - Get Ngrok Url generated. Ngrok Url will be in the form of e.g. "https://95b6-43-230-212-228.ngrok-free.app"
-> Note: the samples make use of the Microsoft Cognitive Services Speech SDK. By downloading the Microsoft Cognitive Services Speech SDK, you acknowledge its license, see [Speech SDK license agreement](https://aka.ms/csspeech/license201809).
+### Before running the sample for the first time
+
+ -Open an instance of PowerShell, Windows Terminal, Command Prompt or equivalent and navigate to the directory that you would like to clone the sample to.
+ -git clone "https://github.com/moirf/communication-services-python-quickstarts".
+ Navigate to call-automation-appointment-reminder folder.
### Configuring application
- Open the config.ini file to configure the following settings
-
- - Connection String: Azure Communication Service resource's connection string.
- - Source Phone: Phone number associated with the Azure Communication Service resource.
- - DestinationIdentities: Multiple sets of outbound target and Transfer target. These sets are seperated by a semi-colon, and outbound target and Transfer target in a each set are seperated by a coma.
-
- Format: "OutboundTarget1(PhoneNumber),TransferTarget1(PhoneNumber/MRI);OutboundTarget2(PhoneNumber),TransferTarget2(PhoneNumber/MRI);OutboundTarget3(PhoneNumber),TransferTarget3(PhoneNumber/MRI)".
-
- For e.g. "+1425XXXAAAA,8:acs:ab12b0ea-85ea-4f83-b0b6-84d90209c7c4_00000009-bce0-da09-54b7-xxxxxxxxxxxx;+1425XXXBBBB,+1425XXXCCCC"
-
- - NgrokExePath: Folder path where ngrok.exe is insalled/saved.
- - SecretPlaceholder: Secret/Password that would be part of callback and will be use to validate incoming requests.
- - CognitiveServiceKey: (Optional) Cognitive service key used for generating custom message
- - CognitiveServiceRegion: (Optional) Region associated with cognitive service
- - CustomMessage: (Optional) Text for the custom message to be converted to speech.
+- Connection String: Azure Communication Service resource's connection string.
+- Source Phone: Phone number associated with the Azure Communication Service resource.
+- target Identity: Multiple sets of outbound target and Transfer target. These sets are seperated by a semi-colon, and outbound target and Transfer target in a each set are seperated by a coma.
+-App_base_uri: Base url of the app. (For local development replace the Ngrok url.For e.g. "https://95b6-43-230-212-228.ngrok-free.app")
### Run the Application
-- Add azure communication callingserver's wheel file path in requirement.txt
+- Add azure communication callautomation's wheel file path in requirement.txt
- Navigate to the directory containing the requirements.txt file and use the following commands for installing all the dependencies and for running the application respectively:
- - pip install -r requirements.txt
- - python program.py
+ - pip install -r requirements.txt
+ - python program.py
diff --git a/callautomation-appointment-reminder/requirements.txt b/callautomation-appointment-reminder/requirements.txt
index 98a246c..95eae5c 100644
--- a/callautomation-appointment-reminder/requirements.txt
+++ b/callautomation-appointment-reminder/requirements.txt
@@ -1,31 +1,3 @@
aiohttp==3.7.4.post0
-async-timeout==3.0.1
-attrs==21.2.0
-azure-cognitiveservices-speech==1.27.0
-azure-common==1.1.27
-azure-communication-callautomation @file:///C:/Users/v-moirf/pyenv311/azure_communication_callautomation-1.0.0a20230413001-py3-none-any.whl
-azure-communication-chat==1.1.0
-azure-communication-identity==1.3.1
-azure-core==1.26.4
-azure-nspkg==3.0.2
-azure-storage==0.36.0
-certifi==2021.5.30
-cffi==1.15.1
-chardet==4.0.0
-charset-normalizer==2.0.4
-cryptography==3.4.8
-idna==3.2
-isodate==0.6.0
-msrest==0.7.1
-multidict==6.0.4
-nest-asyncio==1.5.1
-oauthlib==3.1.1
-psutil==5.8.0
-pycparser==2.20
-python-dateutil==2.8.2
-requests==2.26.0
-requests-oauthlib==1.3.0
-six==1.16.0
-typing-extensions==4.3.0
-urllib3==1.26.6
-yarl==1.8.2
\ No newline at end of file
+azure-communication-callautomation @file:///C:/pyenv2_311/azure_communication_callautomation-1.0.0a20230413001-py3-none-any.whl
+azure-communication-identity==1.3.1
\ No newline at end of file