HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/buyercall/buyercall/blueprints/phonenumbers/bw_operational_tasks.py
import logging as log
import traceback
import os
import pytz
from datetime import datetime
import redis
from flask import current_app, url_for
from buyercall.app import create_celery_app
from buyercall.lib.util_twilio import (
    CallStorage,
    InboundCallState as State,
    split_name,
)
from buyercall.lib.util_webhooks import WebhookUtil
from ..filters import format_phone_number
from buyercall.extensions import db
from buyercall.lib.flask_mailplus import _try_renderer_template
from buyercall.lib.util_ses_email import send_ses_email
from ..leads.models import Lead
from ..phonenumbers.models import (
    Phone,
    HoldMusic,
    Audio,
)
from tempfile import TemporaryFile
import uuid
import boto3

from sqlalchemy.orm import load_only
from ..contacts.models import Contact
from ..agents.models import Agent
from ..mobile.models import Endpoint
from ..partnership.models import PartnershipAccount, Partnership

HOURS = 3600
DAYS = 86400  # The length of a day in seconds
AGENT_TIMEOUT = 30

# TODO Parse Redis URL
celery = create_celery_app(current_app)
webhooker = WebhookUtil()


@celery.task
def bw_operational_lead_create(inbound_id, call_id, phone_number, caller_name=None, start_time=None, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    inbound = Phone.query.filter(Phone.id == inbound_id).first()

    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == inbound.partnership_account_id).first()

    from buyercall.lib.util_bandwidth import bw_client
    client = bw_client(partner_account.partnership_id)

    first_name, last_name = split_name(caller_name)

    if (not first_name and not last_name) or (first_name == 'Unknown'):
        first_name, last_name = Lead.get_last_known_name(
            inbound.partnership_account_id, phone_number
        )

    previous_lead = Lead.query.filter(
        phone_number == Lead.phonenumber,
        inbound.partnership_account_id == Lead.partnership_account_id
    ).order_by(Lead.created_on.desc()).first()

    if previous_lead is not None:
        contact_id = previous_lead.contact_id
        progress_status = previous_lead.progress_status
        caller_display_id = previous_lead.caller_id
    else:
        contact_id = None
        progress_status = 'no status'
        # Return Bandwidth call for CNAM value for lead
        if celery.conf.get('DEBUG', False):
            info = client.cnam_lookup(phone_number, test=True)
        else:
            info = client.cnam_lookup(phone_number)
        if not info:
            caller_display_id = ''
        else:
            caller_display_id = info

    lead = Lead(
        partnership_account_id=inbound.partnership_account_id,
        firstname=first_name,
        lastname=last_name,
        phonenumber=phone_number,
        email='',
        starttime=datetime.utcnow(),
        call_sid=call_id,
        caller_id=caller_display_id,
        call_type='inbound',
        my_phone=inbound.phonenumber,
        call_source=inbound.friendly_name,
        inbound_id=inbound_id,
        status='ringing',
        contact_id=contact_id,
        originating_number=phone_number,
        progress_status=progress_status
    )
    db.session.add(lead)
    db.session.commit()

    # Query the leads table to retrieve the latest lead that was added
    latest_lead = Lead.query\
        .filter(Lead.phonenumber == phone_number)\
        .order_by(Lead.created_on.desc())\
        .first()

    # Query contacts to see if lead already exist in the contacts table
    contact = Contact.query\
        .filter(Contact.phonenumber_1 == latest_lead.phonenumber)\
        .filter(Contact.partnership_account_id == inbound.partnership_account_id)\
        .first()

    if contact:
        contact.updated_on = latest_lead.updated_on
        contact.caller_id = caller_display_id
        db.session.commit()
    else:
        contact = Contact(
            firstname=first_name,
            lastname=last_name,
            caller_id=caller_display_id,
            phonenumber_1=phone_number,
            email='',
            partnership_account_id=inbound.partnership_account_id,
        )
        db.session.add(contact)
        db.session.commit()

    new_contact = Contact.query\
        .filter(Contact.phonenumber_1 == latest_lead.phonenumber)\
        .filter(Contact.partnership_account_id == inbound.partnership_account_id)\
        .first()
    if new_contact:
        latest_lead.contact_id = new_contact.id
        db.session.commit()
    else:
        log.info('no lead contact exist')

    # Associate the lead ID with the Bandwidth call ID
    key = 'CALL:{}'.format(call_id)
    redis_db.setex(key, 2*HOURS, lead.id)

    storage = CallStorage(redis_db, lead.id)
    storage.init()
    storage.call_sid = call_id
    storage.routing_config = inbound.routing_config
    storage.clear_retry_cnt()
    storage.clear_callback_cnt()
    storage.caller_number = phone_number
    storage.start_time = start_time

    # TODO: Bandwidth user ID
    storage.subaccount_sid = None

    storage.state = State.NEW
    storage.inbound = inbound.phonenumber

    if previous_lead is not None:
        storage.caller_name = caller_name
    return lead.id


@celery.task
def bw_operational_update_lead(lead_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])
    storage = CallStorage(redis_db, lead_id)

    # Declare kwargs parameters
    to_number = kwargs.get('to_number', '')
    log.info('THE to number IS: {}'.format(to_number))
    transfer_call_id = kwargs.get('transfer_call_id', '')
    connect_time = kwargs.get('connect_time', '')

    # Get lead data from db
    lead = Lead.query.filter(Lead.id == lead_id).first()
    if lead:
        # Get the agent id that answered the phone call
        answer_agent_id = ''
        if to_number.startswith('sip'):
            answer_agent_id = Endpoint.sip_agent_id(to_number)
        else:
            log.info('The first storage agent list is: {}'.format(storage.agent_list))
            agent_list = storage.agent_list
            log.info('The storage agent list is: {}'.format(agent_list))
            for agent in agent_list:
                answer_agent_id = agent.get("agent_id", 0)
        log.info('THE AGENT ID IS: {}'.format(answer_agent_id))
        lead.agent_id = int(answer_agent_id)
        lead.transfer_call_id = transfer_call_id
        lead.status = 'in-progress'
        storage.state = State.ANSWERED
        storage.connect_time = connect_time
        db.session.commit()

        # Send a webhook because the call has been answered and we know which agent picked up
        webhooker.trigger_generic_webhook('operational_agent_call', lead_id)
        if answer_agent_id and lead:
            contact = Contact.query.filter(Contact.id == lead.contact_id).first()
            agent = Agent.query.filter(Agent.id == answer_agent_id).first()
            if contact and agent and contact.agent_id != agent.id:
                contact.agent_id = answer_agent_id
                contact.agent_assigned = agent.full_name
                db.session.commit()
                from buyercall.blueprints.mobile.utils import send_agent_push_notification
                send_agent_push_notification(contact)
    else:
        log.error('No lead was found for lead id: {}'.format(lead_id))
    return ''


@celery.task
def retry_once_more(lead_id, **kwargs):
    """ Try calling the agents once more, up to the maximum number of retries.
        Return False if the maximum number of retries exceeded, otherwise True.
    """
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])
    storage = CallStorage(redis_db, lead_id)
    routing_config = storage.routing_config
    routing = ''
    if routing_config.get('routingType', '') == 'digits':
        digit_routing = routing_config['digitRoutings']
        for route in digit_routing:
            if route['dialDigit'] == storage.pressed_digit:
                routing = route
    else:
        routing = routing_config['defaultRouting']
    log.info('The routing looks like: {}'.format(routing))
    cnt = storage.inc_retry_cnt()
    log.info('The retry count is: {}'.format(cnt))
    if cnt <= int(routing['retryRouting']):
        log.debug('Retrying once more for lead {}...'.format(lead_id))
        return True
    return False


@celery.task
def bw_upload_recording(lead_id, **kwargs):
    """ After the call, grab the first recording from Bandwidth, if any, and
    upload to S3.
    """
    lead = Lead.query.filter(Lead.id == lead_id).first()
    # Check to see if transcription is turned on. If so then we will send the web hook if the transcription call back
    inbound = Phone.query.filter(Phone.id == lead.inbound_id).first()
    partner_account = PartnershipAccount.query\
        .options(load_only('name', 'partnership_id')).filter(PartnershipAccount.id == lead.partnership_account_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')
    # Get the media url from the request
    recording_id = kwargs.get('recording_id', '')
    call_id = kwargs.get('call_id', '')
    get_recording = client.call.recording(call_id, recording_id)
    file_path = os.path.join(celery.conf['UPLOAD_FOLDER'], f"recording-{recording_id}")
    with open(file_path, 'wb') as f:
        count = 0
        for c in get_recording.iter_content(chunk_size=1024):
            count += len(c)
            f.write(c)
        log.info("Read {} bytes.".format(count))
    hex_value = uuid.uuid4().hex
    key = '{}_{}/{}_{}_{}'.format(
        partner_account.name, lead.partnership_account_id, lead.id, 'recording', hex_value)
    content_type = 'audio/mpeg'
    bucket = celery.conf['RECORDING_BUCKET']
    from buyercall.lib.util_boto3_s3 import upload_file
    upload_file(file_path, bucket, key, content_type)
    os.remove(file_path)
    # Update recording link
    updated_lead = Lead.query.options(
        load_only("id", "recording_url")
    ).filter(Lead.id == lead.id).first()
    updated_lead.recording_url = '{}::{}'.format(celery.conf['RECORDING_BUCKET'], key)
    db.session.commit()

    call_transcribe = inbound.routing_config.get('transcribeAnsweredCall', '')
    vm_transcribe = inbound.routing_config.get('transcribeVoiceMail', '')
    if lead.status == 'missed' and not vm_transcribe:
        if inbound.type == 'mobile':
            webhooker.trigger_generic_webhook('mobile_end_call', lead_id)
        else:
            webhooker.trigger_generic_webhook('operational_end_call', lead_id)
    if lead.status == 'completed' and not call_transcribe:
        if inbound.type == 'mobile':
            webhooker.trigger_generic_webhook('mobile_end_call', lead_id)
        else:
            webhooker.trigger_generic_webhook('operational_end_call', lead_id)


@celery.task
def delay_webhook_trigger(event, lead_id, **kwargs):
    webhooker.trigger_generic_webhook(event, lead_id)
    return ''


@celery.task
def send_notifications(lead_id, **kwargs):
    # Declare redis config url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    from ..widgets.models import split_emails

    lead = Lead.query.filter(Lead.id == lead_id).first()

    storage = CallStorage(redis_db, lead.id)
    # if storage.manual_call:
    #    log.debug('Notification email suppressed.')
    #    return

    inbound = Phone.query.filter(
        Phone.id == lead.inbound_id,
        Phone.partnership_account_id == lead.partnership_account_id
    ).first()
    if not inbound:
        return

    notifications = inbound.notifications
    routings = inbound.routing_config

    if notifications.get('notifyLeads', 'none') == 'none':
        return

    # Check to see if a sms needs to be sent to the lead and send sms if turned on
    if lead.status == 'missed':
        if routings.get('configSMSSetup') and routings.get('MissedCallAutoReply'):
            from ..sms.views import send_text_message
            text = routings.get('MissedCallAutoReplyText') + ' Reply STOP to unsubscribe.'
            media_url = ''
            send_text_message(lead.inbound_id, lead.phonenumber, text, media_url)
        else:
            log.info('The SMS Configuration is turned off for this phone number')

    # Send eventual notifications
    emails = []
    adf_emails = []

    user_email = None
    from ..user.models import User
    admin = User.query.filter(
        User.role == 'admin',
        User.partnership_account_id == lead.partnership_account_id
    ).first()
    if admin:
        user_email = admin.email

    if inbound.routing_config.get('routingType', '') == 'digits':
        agent_ids = []
        for digit in inbound.routing_config['digitRoutings']:
            for a in digit['agents']:
                agent_ids.append(a['id'])
    else:
        agent_ids = [
            a['id'] for a in inbound.routing_config['defaultRouting']['agents']
        ]
    from ..agents.models import Agent
    agent_emails = [
        a.email
        for a in Agent.query.options(load_only('email')).filter(
            Agent.partnership_account_id == lead.partnership_account_id,
            Agent.id.in_(agent_ids)
        ).all()
    ]

    if (notifications.get('notifyLeads', 'none') == 'missed'
       and lead.status == 'missed'):
        emails.extend(split_emails(notifications.get('notifyMissedCustom', '')))
        if notifications.get('notifyMissedAgents'):
            emails.extend(agent_emails)
        if notifications.get('notifyMissedMe') and user_email:
            emails.append(user_email)
    if notifications.get('notifyLeads', 'none') == 'all':
        emails.extend(split_emails(notifications.get('notifyAllCustom', '')))
        if notifications.get('notifyAllAgents'):
            emails.extend(agent_emails)
        if notifications.get('notifyAllMe') and user_email:
            emails.append(user_email)

    if not emails:
        log.warning('Nowhere to send {} lead notification!'.format(
            lead.status
        ))
        return

    partner_account = PartnershipAccount.query.filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    ctx = vars(lead)
    eastern = pytz.timezone('US/Eastern')
    ctx['created_on'] = lead.created_on.astimezone(eastern).strftime('%c')
    ctx['updated_on'] = lead.updated_on.astimezone(eastern).strftime('%c')
    ctx['adf_updated_on'] = lead.interaction_time

    if lead.firstname:
        ctx['caller_name_label'] = 'Caller Name:'
        ctx['caller_name'] = ''.join((lead.firstname, ' ', lead.lastname))

    if lead.caller_id:
        ctx['caller_id_label'] = 'Caller Id:'
        ctx['caller_id'] = lead.caller_id

    if lead.recording_url:
        ctx['vm_div_style'] = 'block'
    else:
        ctx['vm_div_style'] = 'none'

    if lead.source:
        ctx['call_source'] = lead.source

    if lead.inbound:
        ctx['reference'] = lead.inbound.routing_config.get(
            'hiddenInformation', ''
        )

    ctx['partner_logo'] = partner.logo
    ctx['company'] = partner.name

    if partner.partner_url:
        ctx['lead_contact_url'] = partner.partner_url + '/contacts/contact/' + str(lead.contact_id)
    else:
        ctx['lead_contact_url'] = url_for('contacts.contact_lead_page', id=lead.contact_id, _external=True,
                                          _scheme='https')


    try:
        log.debug('The email list are {}'.format(emails))
        if routings['notifyAdf'] is not None and routings['notifyAdf'] is 'True' or routings['notifyAdf']:

            if routings['notifyAdfCustom'] is not None and routings['notifyAdfCustom'] is not '':
                multi_adf_emails = split_emails(routings['notifyAdfCustom'])

                if multi_adf_emails is not None:
                    adf_emails.extend(multi_adf_emails)
                else:
                    adf_emails.append(routings['notifyAdfCustom'])

            ctx['lead_comments'] = 'Call status: ' + lead.status
            ctx['receiver'] = ''
            agent_name = lead.agent_name
            friendly_name = lead.source
            phonenumber = lead.inbound.phonenumber
            dealership = partner_account.name
            if dealership:
                ctx['vendor_name'] = dealership
            else:
                ctx['vendor_name'] = ''
            call_lead_id = lead.id
            if call_lead_id:
                ctx['call_lead_id'] = call_lead_id
            else:
                ctx['call_lead_id'] = ''

            if agent_name is not None and agent_name is not '' and len(agent_name) > 2:
                ctx['lead_comments'] = ctx['lead_comments'] + ', Agent name: ' + agent_name

            if friendly_name is not None and friendly_name is not '':
                ctx['lead_comments'] = ctx['lead_comments'] + ', Call source: ' + friendly_name
                ctx['receiver'] = friendly_name

            if phonenumber is not None and phonenumber is not '':
                ctx['lead_comments'] = ctx['lead_comments'] + ', Phonenumber: ' + phonenumber

            ctx['campaign_name'] = ''
            ctx['campaign_exists'] = False
            from buyercall.blueprints.contacts.models import Contact, Campaigns
            contact = Contact.query.filter(
                    Contact.id == lead.contact_id,
                    Contact.partnership_account_id == lead.partnership_account_id)\
                .first()
            if contact and contact.campaign_id:
                campaign = Campaigns.query.filter(
                    Campaigns.id == contact.campaign_id)\
                    .first()
                if campaign:
                    ctx['campaign_exists'] = True
                    ctx['campaign_name'] = campaign.display_name

            if adf_emails is not None and len(adf_emails) > 0:
                # Render text template for adf email
                adf_lead_email_template = _try_renderer_template('mail/adf_lead', ext='txt', **ctx)
                send_ses_email(recipients=adf_emails,
                               p_id=partner.id,
                               subject=partner.name + ' - ADF lead notification',
                               text=adf_lead_email_template
                               )

        if lead.status == 'missed' and storage.state == State.MISSED:
            # Render html template for email
            missed_lead_email_template = _try_renderer_template('mail/missed_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - Missed lead notification',
                           html=missed_lead_email_template
                           )

        elif lead.status == 'completed' and storage.state == State.CAPTURED:
            # Render html template for email
            completed_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - Answered lead notification',
                           html=completed_lead_email_template
                           )

        else:
            # Render html template for email
            captured_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - New lead notification',
                           html=captured_lead_email_template
                           )

    except:
        log.error(traceback.format_exc())