File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/phonenumbers/bw_operational.py
from __future__ import print_function
import traceback
import logging
from dateutil import parser
import pytz
from datetime import datetime
from xml.dom.minidom import parseString
from flask import (
Blueprint,
request,
redirect,
jsonify,
make_response,
current_app as app,
url_for
)
import redis
from sqlalchemy import and_
from sqlalchemy.orm import load_only
from buyercall.blueprints.filters import format_phone_number
from buyercall.extensions import csrf, db
from buyercall.lib.util_twilio import (
CallStorage,
InboundCallState as State
)
import buyercall.lib.bandwidth as bandwidth
from buyercall.lib.util_bandwidth import bw_client
from buyercall.lib.bandwidth import (
BandwidthException
)
from buyercall.lib.bandwidth_bxml import create_xml_response, Response
from buyercall.lib.util_webhooks import WebhookUtil
from .models import (
Phone, Audio
)
from ..leads.models import Lead
from ..block_numbers.models import Block
from ..partnership.models import PartnershipAccount, Partnership
from ..mobile.models import Endpoint
from ..agents.models import Agent
from ..user.models import User
from .routing import (
get_routing_agents,
get_agent_number
)
log = logging.getLogger(__name__)
webhooker = WebhookUtil()
bw_operational = Blueprint(
'bw_operational', __name__, template_folder='templates'
)
TRANSFER_CALL_TIMEOUT = "22"
""" The call timeout period in seconds for transfer """
# Testing Bandwidth Migration
@bw_operational.route('/bw/voice', methods=['GET', 'POST'])
@csrf.exempt
def voice_call():
""" Entry point for the incoming phone call. This is specifically for Bandwidth API V2. A Location
exist with 2 application attach to it. One is for SMS/MMS and the other for Voice. This call-back url
is specified on the Voice application.
"""
from buyercall.lib.util_bandwidth import authenticate_bw_request
authenticated = authenticate_bw_request(request.headers)
if authenticated:
# Fetch the request data. This includes message data for incoming sms/mms
args = request.json or request.args
log.info('The initiated callback args: {}'.format(args))
return handle_incoming_call(args)
else:
print('Authentication failed')
status_code = 401
message = jsonify(message='Authentication failed.')
response = make_response(message, status_code)
return response
def handle_incoming_call(args):
"""
Handle incoming voice call from Bandwidth API V2
:param args:
:return:
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare BXML response
bxml = Response()
# Determine some request data
event_type = args.get('eventType')
tag = args.get('tag', '')
call_id = args.get('callId', '')
caller_number = args.get('from', '')
bc_number = args.get('to', '')
cause = args.get('cause', '')
req_start_time = args.get('startTime', '')
if req_start_time:
start_time = req_start_time
else:
start_time = args.get('enqueuedTime', '')
# Find the phone number/inbound id being called in the BuyerCall database
phone_number = Phone.query.filter(and_(Phone.phonenumber == bc_number,
Phone.is_deactivated.is_(False))).first()
# start save inbound-id to resource field of request-log data
try:
partnership_account_id = phone_number.partnership_account_id
partnership_account = PartnershipAccount.query.get(partnership_account_id) if partnership_account_id else None
partnership_id = partnership_account.partnership_id
partnership = Partnership.query.get(partnership_id) if partnership_id else None
from buyercall.blueprints.sysadmin.models import RequestLog
request_id = request.environ.get('HTTP_X_REQUEST_ID')
# updating the request log with the inbound id
update_data = {
"partnership_id": partnership_id if partnership_id else None,
'partnership_name': partnership.name if partnership else None,
"partnership_account_id": partnership_account_id if partnership_account_id else None,
'partnership_account_name': partnership_account.name if partnership_account else None,
"resource": phone_number.id if phone_number else None,
'user_id': None
}
RequestLog().update_record(request_id, update_data)
# RequestLog().update_record(
# request_id, {"resource": phone_number.id if phone_number else None})
except Exception as e:
print(f"The exception in saving inbound-id in handle_incoming_call is {e}")
# end save inbound-id to resource field of request-log data
if not phone_number:
bxml.hangup()
return create_xml_response(bxml)
# Create lead and contact
from .bw_operational_tasks import bw_operational_lead_create
lead_id = bw_operational_lead_create(
phone_number.id, call_id, caller_number,
start_time=start_time)
# Send the first webhook saying that an incoming call started
webhooker.trigger_generic_webhook('operational_start_call', lead_id)
# Activate and set redis storage and add lead
storage = CallStorage(redis_db, lead_id)
# Set the storage retry value to false. If it's a retry it will be set to true based on routing value
storage.is_call_retry = False
# Check if the incoming call is labeled as a blocked number
is_number_blocked = Block.blocked(phone_number.id, caller_number)
if is_number_blocked:
storage.state = State.BLOCKED
storage.cause_description = 'Incoming phone number on blocked list.'
bxml.hangup()
return create_xml_response(bxml)
# Set a variable containing the phone number full routing not just default agent routing info
routing_config = phone_number.routing_config
# Check what type of routing is saved for the Number. If its default its normal sequence and simultaneously
# calling Whereas if its digits it means its IVR or phone tree routing where the user decide who to talk
# to based on a digit menu
routing_type = routing_config.get('routingType', '')
log.info('THE ROUTING TYPE IS: {}'.format(routing_type))
if routing_type == 'digits':
transfer_call = lead_operational_call_digits_routing(lead_id)
return transfer_call
else:
# If the routing is not digits (phone tree/IVR) then the routing is default and just normal
# routing occurs
transfer_call = lead_operational_call_default_routing(phone_number.id, routing_config, lead_id)
return transfer_call
@bw_operational.route('/bw/voice/status', methods=['GET', 'POST'])
@csrf.exempt
def voice_status_callback(*args):
""" Entry point for the incoming voice status callback. This is specifically for Bandwidth API
V2. A Location exist with 2 application attach to it. One is for SMS/MMS and the other for Voice. This call-back url
is specified on the Voice application.
"""
from buyercall.lib.util_bandwidth import authenticate_bw_request
authenticated = authenticate_bw_request(request.headers)
if authenticated:
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Fetch the request data. This includes message data for incoming sms/mms
args = request.json or request.args
# Fetch args values
call_id = args.get('callId', '')
event_type = args.get('eventType', '')
req_start_time = parser.parse(args.get('startTime', ''))
if req_start_time:
start_time = req_start_time
else:
start_time = parser.parse(args.get('enqueuedTime', ''))
try:
end_time = parser.parse(args.get('endTime', ''))
except Exception as e:
log.error(e)
end_time = args.get('endTime', '')
cause = args.get('cause', '')
tag = args.get('tag', '')
error_message = args.get('errorMessage', '')
# Look up lead in db based on the provider call sid
call_lead = Lead.query.filter(Lead.call_sid == call_id).first()
from ..partnership.models import PartnershipAccount,Partnership
partnership_account_id = call_lead.partnership_account_id
partnership_account = PartnershipAccount.query.get(partnership_account_id) if partnership_account_id else None
partnership_id = partnership_account.partnership_id
# start save inbound-id to resource field of request-log data
try:
partnership = Partnership.query.get(partnership_id) if partnership_id else None
from buyercall.blueprints.sysadmin.models import RequestLog
request_id = request.environ.get('HTTP_X_REQUEST_ID')
update_data = {
"partnership_id": partnership_id if partnership_id else None,
'partnership_name': partnership.name if partnership else None,
"partnership_account_id": partnership_account_id if partnership_account_id else None,
'partnership_account_name': partnership_account.name if partnership_account else None,
"resource": call_lead.inbound_id if call_lead else None,
'user_id': None
}
RequestLog().update_record(request_id, update_data)
# updating the request log with the inbound id
# RequestLog().update_record(
# request_id, {"resource": call_lead.inbound_id if call_lead else None})
except Exception as e:
print(f"The exception in saving inbound-id in handle_incoming_call is {e}")
# end save inbound-id to resource field of request-log data
log.info('The status callback args: {}'.format(args))
try:
# Fetch storage from Redis based on call lead id
storage = CallStorage(redis_db, call_lead.id)
cause_description = storage.cause_description
if error_message:
storage.state = State.ERROR
storage.cause_description = error_message
log.info('Storage state is: {}'.format(storage.state))
log.info('The tag is: {}'.format(tag))
if storage.state not in ('ANSWERED', 'CAPTURED'):
if event_type == 'disconnect' and storage.state == 'NEW':
storage.state = 'MISSED'
cause_description = 'Call ended by caller before transfer can occurred.'
if storage.state == 'MISSED' and tag == 'transfer-complete':
cause_description = 'Call ended by callee.'
duration, response_time_seconds = 0, 0
if storage.connect_time:
connect_time = parser.parse(storage.connect_time)
response_time_seconds = (connect_time - start_time).total_seconds()
duration = (end_time - start_time).total_seconds()
# Label call as complete
lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
cause, cause_description, tag)
else:
if tag == 'transfer-initiated':
call_lead.cause_description = 'Call ended by caller.'
db.session.commit()
elif tag == 'transfer-complete':
call_lead.cause_description = 'Call ended by callee.'
db.session.commit()
return ''
except Exception as e:
log.error('Unable to find call id; {} in the db. error: {}'.format(call_id, e))
return ''
else:
print('Authentication failed')
status_code = 401
message = jsonify(message='Authentication failed.')
response = make_response(message, status_code)
return response
@bw_operational.route('/bw/voice/fallback', methods=['GET', 'POST'])
@csrf.exempt
def voice_fallback_status_callback():
""" Entry point for the incoming voice fallback callback. This is specifically for Bandwidth API
V2. A Location exist with 2 application attach to it. One is for SMS/MMS and the other for Voice. This call-back url
is specified on the Voice application. This function will call the status callback endpoint.
"""
from buyercall.lib.util_bandwidth import authenticate_bw_request
authenticated = authenticate_bw_request(request.headers)
if authenticated:
# Fetch the request data. This includes message data for incoming sms/mms
args = request.json or request.args
return voice_status_callback(args)
else:
print('Authentication failed')
status_code = 401
message = jsonify(message='Authentication failed.')
response = make_response(message, status_code)
return response
def lead_operational_call(args):
""" Entry point for a operational bw phone number on BW Voice V2.
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Import hang up calls code
from .bw_tasks import bw_hangup_calls
# Determine some request data
event_type = args.get('eventType')
tag = args.get('tag', '')
call_id = args.get('callId', '')
caller_number = args.get('from', '')
bc_number = args.get('to', '')
cause = args.get('cause', '')
req_start_time = args.get('startTime', '')
if req_start_time:
start_time = req_start_time
else:
start_time = args.get('enqueuedTime', '')
# Find the phone number/inbound id being called in the BuyerCall database
phone_number = Phone.query.filter(and_(Phone.phonenumber == bc_number,
Phone.is_deactivated.is_(False))).first()
if not phone_number:
bw_hangup_calls.delay(call_id)
log.info('Unable to find the phone number; {} in BuyerCall DB'.format(bc_number))
return ''
is_number_blocked = Block.blocked(phone_number.id, caller_number)
if is_number_blocked:
bw_hangup_calls.delay(call_id)
log.info('This call was not processed because phone number: {} was blocked'.format(caller_number))
return ''
if event_type == 'answer':
if phone_number:
# Retrieve the Partnership Account details for the phone number
partnership_account = PartnershipAccount.query \
.filter(PartnershipAccount.id == phone_number.partnership_account_id).first()
# Set a variable containing the phone number full routing not just default agent routing info
routing_config = phone_number.routing_config
else:
log.error('A BuyerCall phone number was not found in the db for inbound id: {}'.format(phone_number.id))
return
# Create lead and contact
from .bw_operational_tasks import bw_operational_lead_create
lead_id = bw_operational_lead_create(
phone_number.id, call_id, caller_number,
start_time=start_time)
# Send the first webhook saying that a incoming call started
webhooker.trigger_generic_webhook('operational_start_call', lead_id)
# Set the storage retry value to false. If it's a retry it will be set to true below
storage = CallStorage(redis_db, lead_id)
storage.is_call_retry = False
# Check what type of routing is saved for the Number. If its default its normal sequence and simultaneously
# calling Whereas if its digits it means its IVR or phone tree routing where the user decide who to talk
# to based on a digit menu
routing_type = phone_number.routing_config.get('routingType', '')
log.info('THE ROUTING TYPE IS: {}'.format(routing_type))
if routing_type == 'digits':
log.info('An incoming call for phone number id: {}, has been received'.format(phone_number.id))
transfer_call = lead_operational_call_digits_routing(lead_id)
return transfer_call
else:
# If the routing is not digits (phone tree/IVR) then the routing is default and just normal
# routing occurs
log.info('An incoming call for phone number id: {}, has been received'.format(phone_number.id))
transfer_call = lead_operational_call_default_routing(phone_number.id, routing_config, lead_id)
return transfer_call
if event_type == 'transferComplete':
log.info('The transfer event is complete, meaning the call has ended after a successful answer by agent')
call_lead = Lead.query.filter(Lead.call_sid == tag).first()
storage = CallStorage(redis_db, call_lead.id)
storage.state = State.CAPTURED
start_time = datetime.strptime(
storage.start_time, '%Y-%m-%dT%H:%M:%SZ'
)
duration, response_time_seconds, end_time = None, None, None
if storage.connect_time:
connect_time = datetime.strptime(
storage.connect_time, '%Y-%m-%dT%H:%M:%SZ'
)
response_time_seconds = (connect_time - start_time).total_seconds()
end_time = datetime.strptime(
args.get('time'), '%Y-%m-%dT%H:%M:%SZ'
)
duration = (end_time - start_time).total_seconds()
lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time)
if event_type == 'hangup' and cause != 'NORMAL_CLEARING':
call_lead = Lead.query.filter(Lead.call_sid == call_id).first()
storage = CallStorage(redis_db, call_lead.id)
storage.state = State.MISSED
log.info('The call lead is: {}'.format(call_lead.id))
lead_mark_finished(call_lead.id, storage.state, cause=cause)
return ''
def lead_operational_call_default_routing(inbound_id, routing_config, lead_id):
""" This function is used when an incoming call comes in and the routing on the phone number is set to default.
The function will then determine how to route the calls for default routing
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
default_routing = routing_config['defaultRouting']
# Get the list of available agents
agents = get_routing_agents(default_routing, expand_groups=True)
# Get the call order from the phone number routing_config
call_order = default_routing.get('callOrder', 'sequence')
# Set the storage db model in redis
storage = CallStorage(redis_db, lead_id)
if call_order == 'shuffle':
import random
random.shuffle(agents)
call_order = 'sequence'
storage.call_order = call_order
storage.routing = default_routing
if call_order == 'sequence':
agent_ids = [agent.id for agent in agents]
log.info('the agent ids {}'.format(agent_ids))
storage.set_agents_to_call(agent_ids)
# We set a storage variable to save the first agent id. This is used to play greeting messages to the
# Caller if its the first agent in line being called. We don't want to play the greeting message again
# if we route the call to the second agent.
if agent_ids:
storage.first_call_agent_id = agent_ids[0]
else:
storage.cause_description = 'No available agents.'
return missed_call(storage, lead_id, inbound_id)
return lead_operational_call_sequence_continue(inbound_id, lead_id)
if call_order == 'simultaneous':
agent_ids = [agent.id for agent in agents]
log.info('the agent ids {}'.format(agent_ids))
if agent_ids:
agent_ids = [agent_ids[0]]
storage.set_agents_to_call(agent_ids)
else:
storage.cause_description = 'No available agents.'
return missed_call(storage, lead_id, inbound_id)
return lead_operational_call_simultaneous_continue(inbound_id, lead_id)
@bw_operational.route('/api/bw/operational/sequence/continue/<int:inbound_id>/<int:lead_id>',
methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_call_sequence_continue(inbound_id, lead_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
lead = Lead.query.join(Lead.inbound).filter(Lead.id == lead_id).first()
agent_id = storage.next_agent_to_call()
log.info('The next agent id to call: {}'.format(agent_id))
if not agent_id:
from .bw_operational_tasks import retry_once_more
retry_agents = retry_once_more(lead_id)
if retry_agents:
# Set this storage value to True because a retry is happening
storage.is_call_retry = True
if storage.routing_config.get('routingType') == 'digits':
return lead_operational_call_digits_routing(lead_id)
else:
return lead_operational_call_default_routing(inbound_id, storage.routing_config, lead_id)
else:
storage.cause_description = 'No available agents.'
return missed_call(storage, lead_id, inbound_id)
return sequence_calling(inbound_id, lead, agent_id)
def sequence_calling(inbound_id, lead, agent_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead.id)
# Return all variables from the Routing saved in Redis Storage
greeting_enabled = storage.routing_config.get('greetingMessage', '')
greeting_type = storage.routing_config.get('whisperMessageType', '')
greeting = storage.routing_config.get('whisperMessage', '')
language = storage.routing_config.get('language', '')
client_voicemail = storage.routing_config.get('clientVoicemail', '')
caller_id_info = storage.routing_config.get('callerIdPn', False)
if not agent_id:
agent_id = storage.agent_id
agent = Agent.query.filter(Agent.id == agent_id).first()
if agent.is_group:
agents = agent.agents
storage.is_group = True
agent_numbers = []
for agent in agents:
agent_number, extension = get_agent_number(agent, storage.routing)
agent_numbers.append({"agent_id": agent.id, "agent_number": agent_number})
else:
agent_number, extension = get_agent_number(agent, storage.routing)
agent_numbers = [{"agent_id": agent.id, "agent_number": agent_number}]
# Set the list of agent id and numbers that is being called in redis storage for later use
storage.agent_list = agent_numbers
# This is the web-hook that will be called if a transfer is answered
transfer_answer_webhook_url = url_for(
'bw_operational.lead_operational_transfer_answer_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
# This is the fallback web-hook that will be called if a transfer is answered
transfer_answer_fallback_webhook_url = url_for(
'bw_operational.lead_operational_transfer_answer_fallback_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
# This is the web-hook that will be called if a transfer is disconnected
transfer_disconnect_webhook_url = url_for(
'bw_operational.lead_operational_transfer_disconnect_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
log.info('THE INBOUND ID IS: {}'.format(inbound_id))
# This is the web-hook that will be called if a transfer is completed
transfer_complete_webhook_url = url_for(
'bw_operational.lead_operational_transfer_complete_bw_webhook', inbound_id=inbound_id, lead_id=lead.id,
_external=True, _scheme='https')
# This is the fallback web-hook that will be called if a transfer is completed
transfer_complete_fallback_webhook_url = url_for(
'bw_operational.lead_operational_transfer_complete_fallback_bw_webhook', inbound_id=inbound_id, lead_id=lead.id,
_external=True, _scheme='https')
# This is the first sequence call/transfer. If the agent doesn't pick up the call
# will redirect to the next agent
redirect_sequence_url = url_for(
'bw_operational.lead_operational_call_sequence_continue', inbound_id=inbound_id, lead_id=lead.id,
_external=True, _scheme='https')
# Declare the bandwidth response xml tag. This will be used for the xml responses
bxml = Response()
try:
# If greeting is enabled and its the very first agent in sequence and not a retry call
# we play a greeting message to the caller
if greeting_enabled and storage.first_call_agent_id == agent_id and not storage.is_call_retry:
if greeting_type == 'audio':
greeting_audio = Audio.query \
.filter(and_(Audio.whisper_message_type == 'whisperMessage',
Audio.inbound_id == inbound_id)).first()
if greeting_audio:
greeting_audio_link = greeting_audio.audio_url
bxml.tag('pre-transfer')
bxml.play_audio(greeting_audio_link)
else:
log.error('There is no whisperMessage audio link for inbound id: {}'.format(inbound_id))
else:
if language == 'es':
bxml.tag('pre-transfer')
bxml.say(greeting, 'female', 'es_MX', 'esperanza')
else:
bxml.tag('pre-transfer')
bxml.say(greeting)
if storage.is_call_retry:
bxml.custom_pause('4')
if client_voicemail:
call_time_out = "50"
else:
call_time_out = TRANSFER_CALL_TIMEOUT
log.info(f"storage call sid id before saving : {storage.call_sid} {type(storage.call_sid)}")
# Check if the caller id needs to be the tracking number or the caller's number
if caller_id_info:
caller_id = lead.my_phone
else:
caller_id = lead.phonenumber
# Perform the transfer to the agent number
transfer = bxml.transfer(caller_id, call_time_out, transfer_complete_webhook_url,
transfer_complete_fallback_webhook_url, tag='transfer-initiated')
for number in agent_numbers:
# Lookup to see if the agent number is associated with a buyercall sip endpoint
buyercall_sip_lookup = Phone.mobile_sip_uri(number["agent_number"])
if buyercall_sip_lookup:
agent_call_number = buyercall_sip_lookup
transfer.sip_uri(agent_call_number, transfer_answer_webhook_url,
transfer_answer_fallback_webhook_url,
transfer_disconnect_webhook_url)
else:
agent_call_number = format_phone_number(number["agent_number"])
transfer.phone_number(agent_call_number, transfer_answer_webhook_url,
transfer_answer_fallback_webhook_url,
transfer_disconnect_webhook_url)
log.info('THE AGENT THAT TOOK THE CALL: {}'.format(storage.agent_id))
storage.push_agent_call(storage.agent_id, storage.call_sid)
return create_xml_response(bxml)
except BandwidthException:
log.error(traceback.format_exc())
log.error('Error calling agent {}...'.format(agent.id))
return ''
@bw_operational.route('/api/bw/operational/simultaneous/continue/<int:inbound_id>/<int:lead_id>',
methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_call_simultaneous_continue(inbound_id, lead_id):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
agents = get_routing_agents(storage.routing, expand_groups=True)
lead = Lead.query.join(Lead.inbound).filter(Lead.id == lead_id).first()
agent_id = storage.next_agent_to_call()
log.info('The next agent id to call: {}'.format(agent_id))
if not agent_id:
from buyercall.blueprints.phonenumbers.bw_operational_tasks import retry_once_more
retry_agents = retry_once_more(lead_id)
if retry_agents:
# Set this storage value to True because a retry is happening
storage.is_call_retry = True
if storage.routing_config.get('routingType') == 'digits':
return lead_operational_call_digits_routing(lead_id)
else:
return lead_operational_call_default_routing(inbound_id, storage.routing_config, lead_id)
else:
storage.cause_description = 'No available agents.'
return missed_call(storage, lead_id, inbound_id)
return simultaneous_calling(inbound_id, lead, agents)
def simultaneous_calling(inbound_id, lead, agents):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead.id)
# Return all variables from the Routing saved in Redis Storage
greeting_enabled = storage.routing_config.get('greetingMessage', '')
greeting_type = storage.routing_config.get('whisperMessageType', '')
greeting = storage.routing_config.get('whisperMessage', '')
language = storage.routing_config.get('language')
record_call = storage.routing_config.get('recordCalls', '')
client_voicemail = storage.routing_config.get('clientVoicemail', '')
transcribe_call = storage.routing_config.get('transcribeAnsweredCall', '')
hidden_information = storage.routing_config.get('hiddenInformation', '')
caller_id_info = storage.routing_config.get('callerIdPn', False)
agent_numbers = []
for agent in agents:
agent_number, extension = get_agent_number(agent, storage.routing)
agent_numbers.append({"agent_id": agent.id, "agent_number": agent_number})
# Set the list of agent id and numbers that is being called in redis storage for later use
storage.agent_list = agent_numbers
# This is the web-hook that will be called if a transfer is answered
transfer_answer_webhook_url = url_for(
'bw_operational.lead_operational_transfer_answer_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
# This is the fallback web-hook that will be called if a transfer is answered
transfer_answer_fallback_webhook_url = url_for(
'bw_operational.lead_operational_transfer_answer_fallback_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
# This is the web-hook that will be called if a transfer is disconnected
transfer_disconnect_webhook_url = url_for(
'bw_operational.lead_operational_transfer_disconnect_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
# This is the web-hook that will be called if a transfer is completed
transfer_complete_webhook_url = url_for(
'bw_operational.lead_operational_transfer_complete_bw_webhook', inbound_id=inbound_id, lead_id=lead.id,
_external=True, _scheme='https')
# This is the fallback web-hook that will be called if a transfer is completed
transfer_complete_fallback_webhook_url = url_for(
'bw_operational.lead_operational_transfer_complete_fallback_bw_webhook', inbound_id=inbound_id, lead_id=lead.id,
_external=True, _scheme='https')
# Declare the bandwidth response xml tag. This will be used for the xml responses
bxml = Response()
try:
# If greeting is enabled and not a retry call we play a greeting message to the caller
if greeting_enabled and not storage.is_call_retry:
if greeting_type == 'audio':
greeting_audio = Audio.query \
.filter(and_(Audio.whisper_message_type == 'whisperMessage',
Audio.inbound_id == inbound_id)).first()
if greeting_audio:
greeting_audio_link = greeting_audio.audio_url
bxml.tag('pre-transfer')
bxml.play_audio(greeting_audio_link)
else:
log.error('There is no whisperMessage audio link for inbound id: {}'.format(inbound_id))
else:
if language == 'es':
bxml.tag('pre-transfer')
bxml.say(greeting, 'female', 'es_MX', 'esperanza')
else:
bxml.tag('pre-transfer')
bxml.say(greeting)
if storage.is_call_retry:
bxml.custom_pause('4')
log.info(f"storage call sid id before saving 2: {storage.call_sid} {type(storage.call_sid)}")
if client_voicemail:
call_time_out = "50"
else:
call_time_out = TRANSFER_CALL_TIMEOUT
#transfer = xml_response.transfer(request_url=transfer_call_webhook_url,
# callTimeout=call_time_out, tag=storage.call_sid)
if caller_id_info:
caller_id = lead.my_phone
else:
caller_id = lead.phonenumber
# Perform the transfer to the agent number
transfer = bxml.transfer(caller_id, call_time_out, transfer_complete_webhook_url,
transfer_complete_fallback_webhook_url, tag='transfer-initiated')
for number in agent_numbers:
# Lookup to see if the agent number is associated with a buyercall sip endpoint
buyercall_sip_lookup = Phone.mobile_sip_uri(number["agent_number"])
if buyercall_sip_lookup:
agent_call_number = buyercall_sip_lookup
transfer.sip_uri(agent_call_number, transfer_answer_webhook_url,
transfer_answer_fallback_webhook_url, transfer_disconnect_webhook_url)
else:
agent_call_number = format_phone_number(number["agent_number"])
transfer.phone_number(agent_call_number, transfer_answer_webhook_url,
transfer_answer_fallback_webhook_url, transfer_disconnect_webhook_url)
agent_ids = [agent.id for agent in agents]
agent_id = agent_ids[0]
storage.push_agent_call(agent_id, storage.call_sid)
# Return and send the BXML code transferring the call to Bandwidth
return create_xml_response(bxml)
except BandwidthException:
log.error(traceback.format_exc())
log.error('Error calling agent simultaneously for lead id: {}'.format(lead.id))
return ''
def lead_operational_call_digits_routing(lead_id):
""" This function is used when a incoming call comes in and the routing on the phone number is set to digits.
The function will then determine how to route the calls for digits where a phone tree/IVR is used. This means
The user is presented with a phone menu and routed to agents based on the digits entered in phone. for example
Press 1 to speak to A and Press 2 to speak to B
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
# Declare the bandwidth response xml tag. This will be used for the xml responses
bxml = Response()
digit_routing = storage.routing_config.get('digitRoutings', '')
digit_greeting_type = storage.routing_config.get('digitWhisperMessageType', '')
log.info('The digit greeting type is: {}'.format(digit_greeting_type))
log.info('The digit routing is: {}'.format(digit_routing))
if not storage.is_call_retry:
# This is the gathering digit call web-hook. Every time there's a new event with the gather verb
# this endpoint will be hit with some call-back information
digits_webhook_url = url_for(
'bw_operational.lead_operational_phone_call_digits_bw_webhook', lead_id=lead_id,
_external=True, _scheme='https')
digits_fallback_webhook_url = url_for(
'bw_operational.lead_operational_phone_call_fallback_digits_bw_webhook', lead_id=lead_id,
_external=True, _scheme='https')
digit_gather = bxml.gather(digits_webhook_url, digits_fallback_webhook_url, tag='gather-digits',
first_digit_timeout='7', max_digits='1', repeat_count='3')
if digit_greeting_type == 'audio':
lead = Lead.query.filter(Lead.id == lead_id).first()
greeting_audio = Audio.query.filter(and_(Audio.whisper_message_type == 'digitWhisperMessage',
Audio.inbound_id == lead.inbound_id)).first()
if greeting_audio:
greeting_url = greeting_audio.audio_url
digit_gather.play_audio(greeting_url)
else:
digit_greeting = storage.routing_config.get('digitWhisperMessage', '')
digit_gather.say(digit_greeting)
return create_xml_response(bxml)
return lead_operational_phone_call_digits_bw_webhook(lead_id)
@bw_operational.route('/api/bw/operational_phone_call_digits/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_phone_call_digits_bw_webhook(lead_id, *args):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
# Get the params of the Request
args = request.json or request.args
log.info('The operation call gather digits call-back/web-hook response: {}'.format(args))
if storage.is_call_retry:
pressed_digits = storage.pressed_digit
else:
pressed_digits = args.get('digits', '')
storage.pressed_digit = pressed_digits
# Return the digit routings from the phone number routing config
digit_routings = storage.routing_config.get('digitRoutings', '')
if digit_routings and pressed_digits:
for routing in digit_routings:
dial_digit = routing.get('dialDigit', '')
if dial_digit == pressed_digits:
# Get the lead information to get the inbound id
lead = Lead.query.filter(Lead.id == lead_id).first()
agents = get_routing_agents(routing, expand_groups=False)
# Get the call order from the phone number routing_config
call_order = routing.get('callOrder', 'sequence')
# Set the storage db model in redis
storage = CallStorage(redis_db, lead_id)
if call_order == 'shuffle':
import random
log.debug('Shuffling agent list...')
random.shuffle(agents)
call_order = 'sequence'
storage.call_order = call_order
storage.routing = routing
if call_order == 'sequence':
agent_ids = [agent.id for agent in agents]
log.info('the agent ids {}'.format(agent_ids))
storage.set_agents_to_call(agent_ids)
# We set a storage variable to save the first agent id. This is used to play greeting messages to
# the Caller if its the first agent in line being called. We don't want to play the greeting
# message again. if we route the call to the second agent.
if agent_ids:
storage.first_call_agent_id = agent_ids[0]
else:
log.info('No agent available for inbound id: {} at this time'.format(lead.inbound_id))
missed_call(storage, lead_id, lead.inbound_id)
return lead_operational_call_sequence_continue(lead.inbound_id, lead_id)
if call_order == 'simultaneous':
# Get the list of available agents
agents = get_routing_agents(routing, expand_groups=True)
agent_ids = [agent.id for agent in agents]
log.info('the agent ids {}'.format(agent_ids))
if agent_ids:
agent_ids = [agent_ids[0]]
storage.set_agents_to_call(agent_ids)
else:
log.info('No agent available for inbound id: {} at this time'.format(lead.inbound_id))
missed_call(storage, lead_id, lead.inbound_id)
return lead_operational_call_simultaneous_continue(lead.inbound_id, lead_id)
elif digit_routings and not pressed_digits:
# Declare the bandwidth response xml tag. This will be used for the xml responses
bxml = Response()
# This is the gathering digit call web-hook. Every time there's a new event with the gather verb
# this endpoint will be hit with some call-back information
digits_webhook_url = url_for(
'bw_operational.lead_operational_phone_call_digits_bw_webhook', lead_id=lead_id,
_external=True, _scheme='https')
digits_fallback_webhook_url = url_for(
'bw_operational.lead_operational_phone_call_fallback_digits_bw_webhook', lead_id=lead_id,
_external=True, _scheme='https')
digit_gather = bxml.gather(digits_webhook_url, digits_fallback_webhook_url, tag='gather-digits',
first_digit_timeout='7', max_digits='1', repeat_count='3')
no_digit_whisper_message = storage.routing_config.get('noDigitWhisperMessageType', '')
if no_digit_whisper_message == 'audio':
lead = Lead.query.filter(Lead.id == lead_id).first()
no_digit_audio = Audio.query.filter(and_(Audio.whisper_message_type == 'noDigitWhisperMessage',
Audio.inbound_id == lead.inbound_id)).first()
if no_digit_audio:
greeting_url = no_digit_audio.audio_url
digit_gather.play_audio(greeting_url)
else:
no_digit_text_whisper = storage.routing_config.get('noDigitWhisperMessage', '')
digit_gather.say(no_digit_text_whisper)
return create_xml_response(bxml)
else:
log.error('No digit routing was found for lead id: {}'.format(lead_id))
return ''
@bw_operational.route('/api/bw/operational_phone_call_fallback_digits/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_phone_call_fallback_digits_bw_webhook(lead_id):
""" This is the fallback callback url for the gather digits callback event
https://dev.bandwidth.com/voice/bxml/callbacks/gather.html
"""
# Get the params of the Request
args = request.json or request.args
log.info('The call gather digits fallback callback response: {}'.format(args))
return lead_operational_phone_call_digits_bw_webhook(lead_id, args)
@bw_operational.route('/api/bw/operational_transfer_answer/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_transfer_answer_bw_webhook(lead_id, *args):
""" This endpoint should be hit when a transfer is answered sending over an event. Additional bXML can be
invoked when this endpoint is hit. The only event to be sent in this
call back is: TransferAnswered
https://dev.bandwidth.com/voice/bxml/callbacks/transferAnswer.html
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
record_call = storage.routing_config.get('recordCalls', '')
transcribe_call = storage.routing_config.get('transcribeAnsweredCall', '')
# Get the params of the Request
args = request.json or request.args
log.info('The transfer answer callback response: {}'.format(args))
log.info('THE AGENT CALL ID IS: {}'.format(storage.agent_call_id))
transfer_call_id = args.get('callId')
connect_time = args.get('answerTime', '')
to_number = str(args.get('to', ''))
from .bw_operational_tasks import bw_operational_update_lead
bw_operational_update_lead.delay(lead_id, to_number=to_number, transfer_call_id=transfer_call_id,
connect_time=connect_time)
# Declare the bandwidth response xml tag. This will be used for the xml responses
bxml = Response()
# Set the answer tag
bxml.tag('transfer-answer')
# Play a custom message to the agent before connecting with the lead
hidden_information = storage.routing_config.get('hiddenInformation', '')
if hidden_information:
bxml.say('.....')
bxml.say(hidden_information)
bxml.say('....')
bxml.play_audio(app.config['BEEP_SOUND'])
if record_call:
record_call_back_url = url_for(
'bw_operational.operational_call_record_bw_webhook', lead_id=lead_id,
_external=True, _scheme='https')
if transcribe_call:
transcribe = "true"
transcribe_call_back_url = url_for(
'bw_operational.operational_call_transcribe_bw_webhook', lead_id=lead_id,
_external=True, _scheme='https')
else:
transcribe = "false"
transcribe_call_back_url = ""
bxml.start_record(recording_available_url=record_call_back_url, transcribe=transcribe,
transcribe_available_url=transcribe_call_back_url, tag='start-recording')
return create_xml_response(bxml)
@bw_operational.route('/api/bw/operational_transfer_answer_fallback/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_transfer_answer_fallback_bw_webhook(lead_id):
""" This is the fallback callback url for the transfer answer callback event
https://dev.bandwidth.com/voice/bxml/callbacks/transferAnswer.html
"""
# Get the params of the Request
args = request.json or request.args
log.info('The transfer answer fallback callback response: {}'.format(args))
return lead_operational_transfer_answer_bw_webhook(lead_id, args)
@bw_operational.route('/api/bw/operational_transfer_disconnect/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_transfer_disconnect_bw_webhook(lead_id, *args):
""" This endpoint should be hit when a transfer is disconnected sending over an event. Additional bXML can not be
invoked when this endpoint is hit. The only event to be sent in this
call back is: TransferDisconnect
https://dev.bandwidth.com/voice/bxml/callbacks/transferDisconnect.html
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
# Get the params of the Request
args = request.json or request.args
call_id = args.get('callId', '')
req_start_time = parser.parse(args.get('startTime', ''))
if req_start_time:
start_time = req_start_time
else:
start_time = parser.parse(args.get('enqueuedTime', ''))
end_time = parser.parse(args.get('endTime', ''))
cause = args.get('cause', '')
error_message = args.get('errorMessage', '')
tag = args.get('tag', '')
# Lookup the Call information from the DB
call_lead = Lead.query.filter(Lead.id == lead_id).first()
duration, response_time_seconds, cause_description = None, None, None
if tag == 'transfer-answer' or tag == 'start-recording':
storage.state = State.CAPTURED
if storage.connect_time:
connect_time = parser.parse(storage.connect_time)
response_time_seconds = (connect_time - start_time).total_seconds()
duration = (end_time - start_time).total_seconds()
# Label call as complete
lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
cause, cause_description, tag)
# This following lines need to replace the else statement. This is a bug with BW not reporting the cancel
# cause properly. Once fix the cancel cause and error message should be included in the callback.
# elif cause == 'cancel':
# storage.state = State.MISSED
# storage.cause_description = error_message
else:
storage.state = State.MISSED
storage.cause_description = 'Call ended by caller before it was answered by callee.'
if cause in ['error', 'node-capacity-exceeded', 'unknown', 'callback-error',
'invalid-bxml', 'application-error', 'account-limit']:
storage.state = State.ERROR
cause_description = error_message
# Label call as complete
lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
cause, cause_description, tag)
log.info('The transfer disconnect callback response: {}'.format(args))
return ''
@bw_operational.route('/api/bw/operational_transfer_complete/webhook/<int:inbound_id>/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_transfer_complete_bw_webhook(inbound_id, lead_id, *args):
""" This endpoint should be hit when a transfer is complete sending over an event. Additional bXML can be
invoked when this endpoint is hit after a transfer hangs-up. The only event to be sent in this
call back is: TransferComplete
https://dev.bandwidth.com/voice/bxml/callbacks/transferComplete.html
"""
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
# Declare the redis db model
storage = CallStorage(redis_db, lead_id)
# BXML Response
bxml = Response()
# Get the params of the Request
args = request.json or request.args
log.info('The transfer complete callback response: {}'.format(args))
cause = args.get('cause', '')
error_message = args.get('errorMessage', '')
if cause in ['timeout']:
# This is the first sequence call/transfer. If the agent doesn't pick up the call
if storage.call_order == 'simultaneous':
# will redirect to the next agent
redirect_sequence_url = url_for(
'bw_operational.lead_operational_call_simultaneous_continue', inbound_id=inbound_id, lead_id=lead_id,
_external=True, _scheme='https')
else:
# will redirect to the next agent
redirect_sequence_url = url_for(
'bw_operational.lead_operational_call_sequence_continue', inbound_id=inbound_id, lead_id=lead_id,
_external=True, _scheme='https')
bxml.redirect(redirect_sequence_url, redirect_sequence_url, tag='transfer-redirect')
else:
bxml.tag('transfer-complete')
return create_xml_response(bxml)
@bw_operational.route('/api/bw/operational_transfer_complete_fallback/webhook/<int:inbound_id>/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_transfer_complete_fallback_bw_webhook(inbound_id, lead_id):
""" This is the fallback callback url for the transfer complete callback event
https://dev.bandwidth.com/voice/bxml/callbacks/transferComplete.html
"""
# Get the params of the Request
args = request.json or request.args
log.info('The transfer complete fallback callback response: {}'.format(args))
return lead_operational_transfer_complete_bw_webhook(lead_id, inbound_id, args)
@bw_operational.route('/api/bw/operational_phone_call/call_recording/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def operational_call_record_bw_webhook(lead_id):
# This function is a call back url for operational incoming phone calls.
# Whenever a call is complete and record is enabled this call back url will be called.
# Get the params of the Request
args = request.json or request.args
call_id = args.get('callId', '')
recording_id = args.get('recordingId', '')
lead = Lead.query.filter(Lead.id == lead_id).first()
lead.recording_id = recording_id
db.session.commit()
from .bw_operational_tasks import bw_upload_recording
bw_upload_recording.delay(lead_id, recording_id=recording_id, call_id=call_id)
inbound = Phone.query.filter(Phone.id == lead.inbound_id).first()
if inbound:
# Send push notification to sip mobile user
if inbound.type == 'mobile' and lead.status == 'missed':
from buyercall.blueprints.mobile.tasks import send_vm_push_notification
send_vm_push_notification.delay(lead.id)
return ''
@bw_operational.route('/api/bw/operational_phone_call/call_transcribe/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def operational_call_transcribe_bw_webhook(lead_id):
# This function is a call back url for operational phone calls.
# Get the params of the Request
args = request.json or request.args
recording_id = args.get('recordingId', '')
call_id = args.get('callId', '')
lead = Lead.query.filter(Lead.recording_id == recording_id).first()
partner_account = PartnershipAccount.query.options(load_only('id', 'partnership_id')) \
.filter(PartnershipAccount.id == lead.partnership_account_id).first()
inbound = Phone.query.filter(Phone.id == lead.inbound_id).first()
from buyercall.lib.util_bandwidth import bw_client
if inbound.type == 'mobile':
client = bw_client(partner_account.partnership_id, request_type='voice', tn_type='mobile')
else:
client = bw_client(partner_account.partnership_id, request_type='voice')
# Retrieve the transcription text from BW
transcript = client.call.transcription(call_id, recording_id)
try:
transcript_channel_1 = transcript['transcripts'][0].get('text', '')
transcript_channel_1_confidence = transcript['transcripts'][0].get('confidence', '')
# Save the transcription text and confidence to the database
lead.transcription_text = transcript_channel_1
lead.transcription_1_confidence = transcript_channel_1_confidence
if len(transcript['transcripts']) > 1:
transcript_channel_2 = transcript['transcripts'][1].get('text', '')
transcript_channel_2_confidence = transcript['transcripts'][1].get('confidence', '')
lead.transcription_text_2 = transcript_channel_2
lead.transcription_2_confidence = transcript_channel_2_confidence
db.session.commit()
except IndexError:
log.info('No transcription information was returned for recording id: {}'.format(recording_id))
# If this call back is hit it means there's a transcription and we only send
# web hook after receiving this call back
# Delay the webhook because we need to wait for the recording to be upload with celery too
from .bw_operational_tasks import delay_webhook_trigger
if inbound.type == 'mobile':
delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
else:
delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
return ''
def lead_mark_finished(lead_id, new_state, response_time_seconds=None, duration=None,
end_time=None, cause=None, cause_description=None, tag=None):
# Declare redis config url
redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
storage = CallStorage(redis_db, lead_id)
lead = Lead.query.options(load_only("id", "status")).filter(
Lead.id == lead_id
).first()
if storage.cause_description:
cause_description = storage.cause_description
if new_state == State.MISSED:
lead.status = 'missed'
lead.duration = 60
lead.endtime = end_time
lead.missed_call_cause = cause
lead.cause_description = cause_description
elif new_state == State.CAPTURED:
lead.status = 'completed'
lead.response_time_seconds = response_time_seconds
lead.duration = duration
lead.endtime = end_time
lead.missed_call_cause = 'completed'
if cause_description:
lead.cause_description = cause_description
elif new_state == State.BLOCKED:
lead.status = 'blocked'
lead.response_time_seconds = response_time_seconds
lead.duration = duration
lead.endtime = end_time
lead.missed_call_cause = cause
lead.cause_description = cause_description
elif new_state == State.ERROR:
lead.status = 'error'
lead.response_time_seconds = response_time_seconds
lead.duration = duration
lead.endtime = end_time
lead.missed_call_cause = cause
lead.cause_description = cause_description
lead.call_count += 1
db.session.commit()
# Check to see if recording is turned on. If so then we will send the web hook if the recording call back
recording = storage.routing_config.get('recordCalls', '')
voicemail = storage.routing_config.get('voicemail', '')
log.info('THE TAG IS: {}'.format(tag))
log.info('THE LEAD STATUS IS: {}'.format(lead.status))
from .bw_operational_tasks import delay_webhook_trigger
if lead.status == 'completed' and not recording:
print('a')
delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
elif lead.status == 'completed' and tag not in ['start-recording']:
print('ab')
delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
elif lead.status == 'missed' and not voicemail:
print('abc')
delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
elif lead.status == 'missed' and tag in ['pre-record', 'transfer-initiated', 'pre-transfer']:
print('abcd')
delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
elif lead.status in ['error', 'blocked']:
print('abcde')
delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
agent_list = storage.agent_list
log.info('The agent list is: {}'.format(agent_list))
# Send notifications after call
from .bw_operational_tasks import send_notifications
send_notifications.delay(lead_id)
def missed_call(storage, lead_id, inbound_id):
# Declare the bandwidth response xml tag. This will be used for the xml responses
bxml = Response()
storage.state = State.MISSED
lead = Lead.query.join(Lead.inbound).filter(Lead.id == lead_id).first()
lead.status = 'missed'
db.session.commit()
if storage.routing_config['voicemail']:
if storage.routing_config['voicemailMessageType'] == 'audio':
vm_audio = Audio.query \
.filter(and_(Audio.whisper_message_type == 'voicemailMessage',
Audio.inbound_id == inbound_id))\
.order_by(Audio.id.desc()).first()
if vm_audio:
vm_audio_link = vm_audio.audio_url
bxml.tag('pre-record')
bxml.play_audio(vm_audio_link)
bxml.custom_pause('1')
else:
vm_greet = storage.routing_config['voicemailMessage']
if storage.routing_config['voicemail'] == 'es':
bxml.tag('pre-record')
bxml.custom_pause('1')
bxml.say(vm_greet, 'female', 'es_MX', 'esperanza')
bxml.custom_pause('1')
else:
bxml.tag('pre-record')
bxml.custom_pause('2')
bxml.say(vm_greet)
bxml.custom_pause('1')
record_call_back_url = url_for(
'bw_operational.operational_call_record_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
if storage.routing_config['transcribeVoiceMail']:
transcribe = "true"
transcribe_call_back_url = url_for(
'bw_operational.operational_call_transcribe_bw_webhook', lead_id=lead.id,
_external=True, _scheme='https')
else:
transcribe = "false"
transcribe_call_back_url = ""
bxml.play_audio(app.config['BEEP_SOUND'])
bxml.tag('post-record')
bxml.record(recording_available_url=record_call_back_url, transcribe=transcribe,
transcribe_available_url=transcribe_call_back_url, tag='voicemail')
else:
bxml.hangup()
return create_xml_response(bxml)
def to_response(r):
""" Transform a Twiml Response to a Flask response object. """
xml = parseString(str(r))
result = xml.toprettyxml()
return result