File: //proc/self/root/home/arjun/projects/buyercall/buyercall/blueprints/widgets/endpoints.py
import logging as log
import traceback
from uuid import uuid4
import redis
from flask import (
request, Blueprint, current_app
)
from flask_cors import cross_origin
from flask_login import current_user
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.channels.models import Channel
from buyercall.blueprints.chat.models import Chat
from buyercall.blueprints.contacts.models import Contact, ContactChannelTie
from buyercall.blueprints.notification.utilities import send_notifications
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
from buyercall.blueprints.phonenumbers.models import Phone
from buyercall.blueprints.sms.models import Message
from buyercall.blueprints.user.decorators import api_role_required
from buyercall.blueprints.widgets.rest_api import fix_name
from buyercall.blueprints.widgets.serializers import WidgetOutSelectedResponseSchema
from buyercall.lib.util_rest import api_jsonify
from buyercall.lib.util_twilio import bw_client, subaccount_client
from .models import Widget, AgentAssignment
from .routing import (
BandwidthRouting,
Routing,
add_widget_lead,
phonecall_inprogress,
NoAgentsException
)
from ..email.models import Email
log = log.getLogger(__name__)
widget_api = Blueprint('chat_widget_api', __name__, template_folder='templates', url_prefix='/api/widget')
@cross_origin()
def call_status(lead_sid):
# Declare redis url
redis_db = redis.StrictRedis(
host=current_app.config['REDIS_CONFIG_URL'],
port=current_app.config['REDIS_CONFIG_PORT'],
decode_responses=True
)
try:
connect = redis_db.get(f'CONNECT{str(lead_sid)}')
if connect == '1':
return api_jsonify({"callConnect": True}, 200, 'Success', True)
elif connect == '-1':
return api_jsonify({"callConnect": False, "error": True}, 200, 'Success', True)
except Exception as e:
print('Error : ', e)
log.error('Cannot retrieve lead status - is Redis accessible?')
return api_jsonify({"callConnect": False}, 200, 'Success', True)
@cross_origin()
def call(guid):
""" The endpoint that gets called when the lead presses 'Call Now!' on the
widget.
Receives the widget GUID as a query string parameter, and the user's data
in the JSON body.
"""
from buyercall.blueprints.partnership.models import PartnershipAccount
# Save the lead
json = request.get_json()
widget = Widget.query.outerjoin(
(AgentAssignment, Widget.assignments)
).outerjoin(
(Agent, AgentAssignment.agent)
).join(Widget.partnership_account).join(PartnershipAccount.partnership).filter(Widget.guid == str(guid)).first()
log.info("The call widget json request: {}".format(json))
if widget and 'firstName' in json and json['firstName'] in ['', ' '] \
or 'phoneNumber' in json and json['phoneNumber'] in ['', ' ']:
log.error('No lead fields provided for call widget ' + str(widget.name) + ' - ' + str(widget.guid) + '.')
return api_jsonify([], 422, f'No lead fields provided for call widget {str(widget.name)}-{str(widget.guid)}',
False)
subscription = widget.partnership_account.subscription
if not subscription:
subscription = widget.partnership_account.partnership.subscription
if subscription.usage_over_limit:
log.warning('partnership_account {} has exceeded their quota.'.format(
widget.partnership_account_id
))
return api_jsonify([], 403, f'partnership_account {widget.partnership_account_id} has exceeded their quota',
False)
lead = None
partnership = None
try:
lead_on_call = phonecall_inprogress(widget, **json)
if lead_on_call:
return api_jsonify([], 200, 'Call in progress', True)
else:
lead = add_widget_lead(widget, **json)
# import partnership information to get partnership id
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
partner_account = PartnershipAccount.query \
.filter(PartnershipAccount.id == widget.partnership_account_id).first()
partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()
partnership = partner
# Is it a Bandwidth or a Twilio number?
if widget.inbound.type == 'tracking':
client = bw_client(partner.id)
log.info('Calling Bandwidth number...')
BandwidthRouting(client).call_lead(widget, lead)
else:
log.info('Calling Twilio number...')
subaccount_sid = subscription.twilio_subaccount_sid
client = subaccount_client(subaccount_sid, partner.id)
Routing(client).call_lead(widget, lead)
except NoAgentsException:
# Add task for agents
all_agents_sids = []
for agent in widget.agents:
all_agents_sids.append(agent.sid)
from buyercall.blueprints.partnership.models import PartnershipAccount
from buyercall.blueprints.chat.endpoints import create_task
partnership_account = PartnershipAccount.query.filter(
PartnershipAccount.id == widget.partnership_account_id).first()
task_payload = {
"type": "MISSED_CALL_FOLLOW_UP",
"users": all_agents_sids,
"partnershipId": str(partnership.sid),
'partnershipAccountId': str(partnership_account.sid)
}
task_resp = create_task(task_payload)
# Send notification
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'MISSED_CALL',
'user_related_entities': "You've",
'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
'hyperlink': f'{partnership.partner_url}/settings'
}
from buyercall.blueprints.notification.utilities import send_notifications
es_response = send_notifications(**es_data)
return api_jsonify({'success': False, 'code': 'ERR_NO_AGENTS', 'callId': lead.sid}, 200, 'Success', True)
return api_jsonify({'success': True, 'callId': lead.sid}, 200, 'Success', True)
@cross_origin()
def save_lead(guid):
""" Saves the lead information in the database. Receives the widget GUID as a query string parameter,
and the user's data in the JSON body."""
json = request.get_json()
widget = Widget.query.filter(Widget.guid == str(guid)).first()
log.info("The after hours call widget json request is: {}".format(json))
if widget and 'firstName' in json and json['firstName'] in ['', ' '] or \
'lastName' in json and json['lastName'] in ['', ' '] or \
'phoneNumber' in json and json['phoneNumber'] in ['', ' '] or \
'emailAddress' in json and json['emailAddress'] in ['', ' ']:
log.error('No lead fields provided for widget ' + str(widget.name) + ' - ' + str(widget.guid) + '.')
return api_jsonify([], 422, "Missing parameters for widget", False)
itype = json.get('type')
partnership = Partnership.query.filter(Partnership.id == current_user.partnership_id).first()
if not partnership:
partnership = Partnership.query.get(1)
if itype == "CALL":
lead = add_widget_lead(widget, status='missed', **json)
lead_id = lead.sid
try:
# after_call_events(lead, widget)
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'NEW_LEAD',
'user_related_entities': "You've",
'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
'hyperlink': f'{partnership.partner_url}/settings'
}
# es_response = send_notifications(**es_data)
except Exception as e:
log.error(traceback.format_exc())
lead_id = None
return api_jsonify({'leadId': lead_id}, 200, "Success", True)
elif itype == "CHAT":
lead_id = ''
try:
chat = Chat.create(**json)
# Send notification
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'CHAT',
'user_related_entities': "You've",
'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
'hyperlink': f'{partnership.partner_url}/settings'
}
es_response = send_notifications(**es_data)
except Exception as e:
log.error(traceback.format_exc())
lead_id = None
return api_jsonify({'leadId': lead_id}, 200, "Success", True)
elif itype == "TEXT_MESSAGE":
lead_id = ''
try:
message = Message.create(**json)
# Send notification
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'TEXT_MESSAGE',
'user_related_entities': "You've",
'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
'hyperlink': f'{partnership.partner_url}/settings'
}
es_response = send_notifications(**es_data)
except Exception as e:
log.error(traceback.format_exc())
lead_id = None
return api_jsonify({'leadId': lead_id}, 200, "Success", True)
elif itype == "EMAIL_MESSAGE":
lead_id = ''
try:
# email = Email.create(**json)
# Send notification
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'EMAIL_MESSAGE',
'user_related_entities': "You've",
'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
'hyperlink': f'{partnership.partner_url}/settings'
}
es_response = send_notifications(**es_data)
except Exception as e:
log.error(traceback.format_exc())
lead_id = None
return api_jsonify({'leadId': lead_id}, 200, "Success", True)
else:
pass
@cross_origin()
@api_role_required('admin')
def create_widget():
"""Create omnichannel widget
Args:
name (str): widget name
description (str): widget description
type (str): widget type. Values: widget/adf
enabled (bool): widget enabled or not. Default: True
phonenumber_id (str): Phone number sid related to widget (optional)
options (json): widget configuration
channels (list): channel's sid
Returns (json):
data (json/None): contains widget guid if success else none
message (str): success or failure message
status_code (int): http status code
success (bool): Whether API was success
"""
message = "Widget created successfully"
success = True
status_code = 200
partnership_account = PartnershipAccount.query.filter(
PartnershipAccount.id == current_user.partnership_account_id).first()
if not partnership_account:
partnership_account = PartnershipAccount.query.get(1)
widget_id = None
received = request.get_json()
if received:
widget_name = fix_name(received.get("name", "New widget"))
widget_desc = received.get("description", "")
# widget_type = received.get('type', 'widget')
widget_enabled = received.get('enabled', True)
widget_guid = str(uuid4())
widget_phonenumber = received.get('phonenumber_id', None)
channel_sids = received.get('channels', [])
options = received.get("options", {})
widget_options = {
"enableNotifications": True,
"showPoweredBCIcon": False,
"widgetThemeColor": [72, 90, 255],
# blue[72, 90, 255], red[224, 11, 28],
"options": [
{
"type": 'TEXT_MESSAGE'
},
{
"type": 'CALL',
"leadSubmitButtonName": 'SEND CALL'
},
{
"type": 'EMAIL',
"leadSubmitButtonName": 'SEND EMAIL'
},
{
"type": 'CHAT',
"leadSubmitButtonName": 'START CHAT'
}
]
}
widget_options.update(options)
if partnership_account.name:
company_before_format = partnership_account.name.replace(" ", "")
company_after_format = ''.join(e for e in company_before_format if e.isalnum())
widget_count = Widget.query.filter(
Widget.partnership_account_id == partnership_account.id
).count() + 1
if widget_count > 0:
widget_count_string = str(widget_count)
else:
widget_count_string = "0{}".format(widget_count)
channels = [Channel.get_by_id(sid) for sid in channel_sids]
params = {
'enabled': widget_enabled,
'email': f"{company_after_format}{widget_count_string}@inbound.buyercall.com",
'partnership_account_id': partnership_account.id,
'guid': widget_guid,
'name': widget_name,
'description': widget_desc,
'options': widget_options,
'channels': channels,
'created_by': current_user.id
}
widget, error = Widget.api_create(params, widget_phonenumber)
if widget:
widget_id = widget.guid
# Send notification
partnership = Partnership.query.filter(Partnership.id == current_user.partnership_id).first()
if not partnership:
partnership = Partnership.query.get(1)
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'NEW_WIDGET_CREATED',
'user_related_entities': "You've",
'other_user_related_entities': f'{current_user.firstname} {current_user.lastname}',
'hyperlink': f'{partnership.partner_url}/settings'
}
es_response = send_notifications(**es_data)
else:
message = f"Widget creation failed. Error: {error}"
status_code = 500
success = False
else:
message = "Partnership account id is empty."
status_code = 401
success = False
return api_jsonify({'widget_id': widget_id}, status_code=status_code, message=message, success=success)
@cross_origin()
def get_widget(guid=None):
widget = Widget.query.filter(Widget.guid == str(guid)).first()
return api_jsonify(WidgetOutSelectedResponseSchema().dump(widget), 200, 'Widget fetch successfully', True)
@api_role_required('admin')
def get_all_widgets():
unallocated_type = request.args.get('unallocated_type', None)
if unallocated_type:
widgets = Widget.get_unallocated_type(unallocated_type)
else:
partnership_account_id = current_user.partnership_account_id or 1
widgets = Widget.query.filter(Widget.is_options_v3 == True,
Widget.partnership_account_id == partnership_account_id).all()
response_list = WidgetOutSelectedResponseSchema(many=True).dump(widgets)
return api_jsonify(response_list, 200, 'Widgets fetched successfully', True)
def get_all_widget_v2():
partnership_account_id = current_user.partnership_account_id or 1
widgets = Widget.query.filter(Widget.is_options_v3 == False,
Widget.partnership_account_id == partnership_account_id).all()
response_list = WidgetOutSelectedResponseSchema(many=True).dump(widgets)
return api_jsonify(response_list, 200, 'Widget fetch successfully', True)
@cross_origin()
@api_role_required('admin')
def update_widget(guid):
"""Update widget"""
widget = Widget.query.filter(Widget.guid == str(guid)).first()
received = request.get_json()
if received and widget:
result_name = received.get('name', widget.name)
result_type = received.get('type', None)
description = received.get('description', None)
result_enabled = received.get('enabled', None)
result_phonenumber = received.get('phonenumber_id', None)
result_options = received.get('options', {})
is_active = received.get('isActive', True)
channel_sids = received.get('channels', [])
channel_sids = [csid['value'] for csid in channel_sids]
phonenumber = Phone.get_id_from_sid(result_phonenumber)
widget.name = result_name
widget.enabled = is_active
widget.updated_by = current_user.id
widget.type = result_type if result_type else widget.type
widget.description = description
widget.enabled = result_enabled if result_enabled else widget.enabled
widget.inbound_id = phonenumber if phonenumber else widget.inbound_id
existing_options = widget.options
new_options = dict(existing_options, **result_options)
widget.options = new_options
if channel_sids:
widget.channels = [Channel.get_by_sid(sid) for sid in channel_sids]
else:
widget.channels = []
widget.save()
# Send notification
partnership = current_user.get_partnership()
es_data = {
'user_id': current_user.sid,
'notify_message_type': 'WIDGET_EDITED',
'user_related_entities': ["You've", str(guid)],
'other_user_related_entities': [f'{current_user.firstname} {current_user.lastname}', str(guid)],
'hyperlink': f'{partnership.partner_url}/settings'
}
es_response = send_notifications(**es_data)
return api_jsonify({'widget_guid': widget.guid}, 200, "Success", True)
return api_jsonify({'widget_guid': None}, 200, "Success", True)
@cross_origin()
def send_lead_mail(widget_sid=None):
""" Send email using the data getting from the widget """
if widget_sid:
widget = Widget.query.filter(Widget.guid == str(widget_sid)).first()
received = request.get_json()
if not widget or 'firstName' in received and received['firstName'] in ['', ' '] or \
'lastName' in received and received['lastName'] in ['', ' '] or \
'phoneNumber' in received and received['phoneNumber'] in ['', ' '] or \
'emailAddress' in received and received['emailAddress'] in ['', ' ']:
return api_jsonify({}, 422, "Missing parameters for widget", False)
# Add contact if not exists
contact = Contact.find(received.get('emailAddress'), received.get('phoneNumber'))
if contact:
is_existing_contact = True
if contact.phonenumber_1 != received.get('phoneNumber', None):
contact.phonenumber_2 = received.get('phoneNumber', None)
if not contact.lastname:
contact.lastname = received.get('lastName', '')
contact.status = 'new'
contact.save()
else:
is_existing_contact = False
params = {
"firstname": received['firstName'],
"lastname": received['lastName'],
"phonenumber_1": received['phoneNumber'],
"email": received['emailAddress'],
"partnership_account_id": widget.partnership_account_id,
"status": "new",
"bdc_status": "lead"
}
contact = Contact.create(**params)
from buyercall.blueprints.widgets.utils.interaction_manager import InteractionManager
channel = widget.get_channel_by_type('email')
email_params = {
"first_name": received.get('firstName', ""),
"last_name": received.get('lastName', ""),
"email": received.get('emailAddress'),
"source": channel.source,
"channel": channel.id,
"is_inbound": True,
"is_forward": False,
"contact_id": contact.id,
"partnership_account_id": widget.partnership_account_id,
"meta_data": received
}
email_object = Email.create(**email_params)
# Create ContactChannelTie
cc_params = {
"contact": contact.id,
"channel_id": channel.id,
"email": email_object.id
}
contact_channel = ContactChannelTie.create(**cc_params)
InteractionManager().run(
'email', contact, channel, is_existing_contact, received, widget.partnership_account_id)
# task_payload = {
# "type": "EMAIL_MESSAGE_FOLLOW_UP",
# "users": all_agents_sids,
# "partnershipId": str(partnership.sid),
# "partnershipAccountId": str(partnership_account.sid)
# }
# task_resp = create_task(task_payload)
# from buyercall.blueprints.widgets.tasks import send_email
# subject = f'New Lead from {received["firstName"]}'
# email_data = render_template('widgets/mail/captured_lead.html', context=received)
# send mail
# mail_status = send_email(emails, subject, email_data)
# if mail_status:
return api_jsonify({}, 200, "Mail sent successfully!", True)
return api_jsonify([], 422, "Missing parameters for widget", False)
@cross_origin()
def start_chat(widget_sid):
message = "Chat started successfully!"
status_code = 200
success = True
received = request.get_json()
widget = Widget.query.filter(Widget.guid == str(widget_sid)).first()
if widget:
log.info("The after hours call widget json request is: {}".format(received))
if widget and 'firstName' in received and received['firstName'] in ['', ' '] or \
'lastName' in received and received['lastName'] in ['', ' '] or \
'phoneNumber' in received and received['phoneNumber'] in ['', ' '] or \
'emailAddress' in received and received['emailAddress'] in ['', ' ']:
log.error('No lead fields provided for widget ' + str(widget.name) + ' - ' + str(widget.guid) + '.')
return api_jsonify([], 422, "Missing parameters for widget", False)
# Add contact if not exists
contact = Contact.find(received.get('emailAddress'), received.get('phoneNumber'))
if contact:
is_existing_contact = True
if contact.phonenumber_1 != received.get('phoneNumber', None):
contact.phonenumber_2 = received.get('phoneNumber', None)
if not contact.lastname:
contact.lastname = received.get('lastName', '')
contact.status = 'new'
contact.save()
else:
is_existing_contact = False
params = {
"firstname": received['firstName'],
"lastname": received['lastName'],
"phonenumber_1": received['phoneNumber'],
"email": received['emailAddress'],
"partnership_account_id": widget.partnership_account_id,
"status": "new",
"bdc_status": "lead"
}
contact = Contact.create(**params)
print('is_existing_contact : ', is_existing_contact)
from buyercall.blueprints.widgets.utils.interaction_manager import InteractionManager
channel = widget.get_channel_by_type('chat')
if not is_existing_contact:
chat_params = {
"first_name": received.get('firstName', ""),
"last_name": received.get('lastName', ""),
"email": received['emailAddress'],
"phone_number": received['phoneNumber'],
"source": channel.source,
"channel": channel.id,
"widget_id": widget.id,
"contact_id": contact.id,
"partnership_account_id": widget.partnership_account_id,
}
chat_object = Chat.create(**chat_params)
# Create ContactChannel
cc_params = {
"contact": contact.id,
"channel_id": channel.id,
"chat": chat_object.id
}
contact_channel = ContactChannelTie.create(**cc_params)
assigned_agents_ids = InteractionManager().run(
'chat', contact, channel, is_existing_contact, received, widget.partnership_account_id)
data = {
"leadId": contact.sid,
"name": contact.name,
"email": contact.email,
"phone_number": contact.phonenumber_1 or contact.phonenumber_2,
"assigned_agent_id": assigned_agents_ids
}
return api_jsonify(data=data, message=message)
return api_jsonify([], 422, "Missing parameters for widget", False)