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_forms/buyercall/buyercall/blueprints/mobile/tasks.py
import logging as log
from datetime import datetime
import sys
import json
import redis
from io import BytesIO
import requests
from buyercall.app import create_celery_app
from buyercall.lib.util_twilio import (
    split_name,
    CallStorage,
    InboundCallState as State
)
from buyercall.lib.util_bandwidth import bw_client
from buyercall.lib.flask_mailplus import _try_renderer_template
from buyercall.lib.util_ses_email import send_ses_email
import qrcode
import boto
import uuid
from boto.s3.key import Key
from buyercall.extensions import db
from buyercall.blueprints.leads.models import Lead

from buyercall.blueprints.phonenumbers.models import (
    Phone,
)
from buyercall.blueprints.mobile.models import Endpoint
from buyercall.blueprints.sms.models import Message
from buyercall.blueprints.filters import format_phone_number, format_phone_number_bracket
from buyercall.lib.util_webhooks import WebhookUtil
import os.path as path
from flask import current_app as app
from buyercall.blueprints.contacts.models import Contact
from sqlalchemy.sql import text
from ..partnership.models import Partnership, PartnershipAccount
from sqlalchemy.orm import load_only
from sqlalchemy import and_

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

# TODO Parse Redis URL
redis_db = redis.StrictRedis(host='redis')
celery = create_celery_app(app)
webhooker = WebhookUtil()


@celery.task
def mobile_lead_create(inbound_id, call_id, phone_number, sip_outbound, caller_name=None, **kwargs):

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

    sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == inbound.id).first()

    # import partnership information to get partnership id
    from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == inbound.partnership_account_id).first()

    first_name, last_name = split_name(caller_name)
    email = ''
    progress_status = ''

    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()

    previous_operational_lead = Lead.query.options(load_only('phonenumber')).filter(
        phone_number == Lead.my_phone,
        inbound.partnership_account_id == Lead.partnership_account_id
    ).order_by(Lead.created_on.desc()).first()

    operational_number = Phone.query \
        .filter(and_(Phone.phonenumber == phone_number,
                     Phone.partnership_account_id == inbound.partnership_account_id)).first()

    if previous_operational_lead and operational_number:
        originating_call_number = previous_operational_lead.phonenumber
    else:
        originating_call_number = phone_number

    log.info('The previous lead call number is: {}'.format(originating_call_number))

    if previous_lead is not None:
        # Query contacts to see if lead already exist in the contacts table
        contact = Contact.query.filter(Contact.id == previous_lead.contact_id) \
            .filter(Contact.partnership_account_id == inbound.partnership_account_id).first()
        contact_id = contact.id
        first_name = contact.firstname
        last_name = contact.lastname
        email = contact.email
        progress_status = contact.status
        caller_id = contact.caller_id
    else:
        # Return Bandwidth call for CNAM value for lead
        client = bw_client(partner_account.partnership_id, tn_type='mobile')
        if celery.conf.get('DEBUG', False):
            caller_id = client.cnam_lookup(phone_number, test=True)
        else:
            caller_id = client.cnam_lookup(phone_number)
        log.info('The mobile call cnam lookup returned: {}'.format(caller_id))
        if not caller_id:
            caller_id = ''

        new_contact = Contact(
            firstname=first_name,
            lastname=last_name,
            caller_id=caller_id,
            phonenumber_1=phone_number,
            agent_id=sip_endpoint.agent_id,
            email='',
            partnership_account_id=inbound.partnership_account_id,
        )
        db.session.add(new_contact)
        db.session.commit()
        log.info('The contact has been added')
        contact_id = new_contact.id

    if sip_outbound:
        direction = 'outbound'
        originating_call_number = inbound.phonenumber
    else:
        direction = 'inbound'

    lead = Lead(
        partnership_account_id=inbound.partnership_account_id,
        firstname=first_name,
        caller_id=caller_id,
        lastname=last_name,
        phonenumber=phone_number,
        email=email,
        starttime=datetime.utcnow(),
        progress_status=progress_status,
        agent_id=sip_endpoint.agent_id,
        call_sid=call_id,
        call_type=direction,
        my_phone=inbound.phonenumber,
        call_source=inbound.friendly_name,
        inbound_id=inbound_id,
        status='ringing',
        contact_id=contact_id,
        originating_number=originating_call_number
    )
    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()
    log.info('latest lead: {}'.format(latest_lead))

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

    return latest_lead.id


@celery.task
def mobile_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)
    log.info('Agent id is: {}'.format(storage.agent_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:
        lead.agent_id = int(storage.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()

        if storage.agent_id and lead:
            contact = Contact.query.filter(Contact.id == lead.contact_id).first()
            from buyercall.blueprints.agents.models import Agent
            agent = Agent.query.filter(Agent.id == storage.agent_id).first()
            if contact and agent and contact.agent_id != agent.id:
                contact.agent_id = storage.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 push_notification(sip_username, push_type, message=None, sms_id=None, message_preview=None, badge=None,
                      user_display_name=None, content_type=None, **kwargs):
    log.info('This got hit {}'.format(sip_username))
    log.info('The message is {}'.format(message))
    endpoint = Endpoint.query.filter(Endpoint.sip_username == sip_username).first()
    log.info('The endpoint is {}'.format(endpoint))
    push_type = push_type
    message = message
    headers = {'Content-Type': 'application/json'}
    # headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    url = 'https://pnm.cloudsoftphone.com/pnm2/send'
    try:
        # Include the custom push notification message. This is not for text
        # messages. See below.
        if message:
            payload = {'verb': push_type,
                       'Message': message,
                       'DeviceToken': endpoint.device_token,
                       'Selector': endpoint.selector,
                       'AppId': endpoint.app_id
                       }
            json_data = json.dumps(payload, default=str)
            log.info('The json payload looks like {}'.format(json_data))
        # This is when it's text message. The message field is not
        # required when sending a sms. It's only required for custom
        # push notifications.
        else:
            payload = {'verb': push_type,
                       'DeviceToken': endpoint.device_token,
                       'Selector': endpoint.selector,
                       'AppId': endpoint.app_id,
                       'UserDisplayName': user_display_name,
                       'Message': message_preview,
                       'ContentType': content_type,
                       'Id': sms_id,
                       'Badge': badge,
                       'Sound': 'default'
                       }
            json_data = json.dumps(payload, default=str)
            log.info('The json payload looks like {}'.format(json_data))
    except Exception as e:
        log.debug('The push notification was not sent because: ' + str(sys.exc_info()[0]))

    r = requests.post(url, data=json_data, headers=headers)
    log.info('The status code is: {}'.format(r.status_code))
    log.info('The response is: {}'.format(r.content.decode()))
    return r


@celery.task
def add_mobile_message_lead(inbound_id, message_info, partnership_account_id, provider, **kwargs):
    """
        Add mobile sms lead message to the messages table
    """
    log.info('The message args look like: {}'.format(message_info))
    message_object = message_info[0].get('message', '')

    # THe sender's formatted phone number
    format_phone = format_phone_number(message_object.get('from', ''))
    previous_message = Message.query.options(load_only('from_'))\
        .filter(and_(Message.to == format_phone, Message.partnership_account_id == partnership_account_id))\
        .order_by(Message.created_on.desc()).first()
    operational_number = Phone.query\
        .filter(and_(Phone.phonenumber == format_phone,
                     Phone.partnership_account_id == partnership_account_id)).first()
    if previous_message and operational_number:
        originating_msg_number = previous_message.from_
    else:
        originating_msg_number = format_phone
    log.info('The formatted phone number is {}'.format(format_phone))
    contact = Contact.query\
        .filter(Contact.phonenumber_1 == format_phone)\
        .filter(Contact.partnership_account_id == partnership_account_id)\
        .first()
    if contact:
        log.info('The existing contact info is: {}'.format(contact))
    if not contact:
        contact_entry = Contact(
            phonenumber_1=format_phone,
            partnership_account_id=partnership_account_id
        )
        db.session.add(contact_entry)
        db.session.commit()
        log.info('The message contact has been added')
    new_contact = Contact.query\
        .filter(Contact.phonenumber_1 == format_phone)\
        .filter(Contact.partnership_account_id == partnership_account_id)\
        .first()
    if new_contact:
        new_contact_id = new_contact.id
        if message_object.get('direction', '') == 'in':
            msg_direction = 'inbound'
            msg_status = 'received'
        else:
            msg_direction = ''
            msg_status = 'unknown'
        new_contact.updated_on = message_object.get('time', '')

        if message_object.get('media', ''):
            message_type = 'mms'
            media_args = str(message_object.get('media')).replace("[", "").replace("]", "").replace(" ", "").replace("'", "")
            media_links = media_args.split(",")
            print(media_links)
            media_list = []
            for i in media_links:
                if not i.endswith(".xml"):
                    from ..partnership.models import PartnershipAccount, Partnership
                    partner_account = PartnershipAccount.query \
                        .filter(PartnershipAccount.id == partnership_account_id).first()
                    partner_name = partner_account.name

                    media_file_name = i.rpartition('media/')[2].replace("'", "")

                    client = bw_client(partner_account.partnership_id, request_type='messaging', tn_type='mobile')
                    try:
                        get_media = client.media.get_media(media_name=media_file_name)
                        converted_media_file_name = media_file_name.replace('/', '-')
                        file_path = path.join(celery.conf['UPLOAD_FOLDER'], converted_media_file_name)
                        with open(file_path, 'wb') as f:
                            count = 0
                            for c in get_media.iter_content(chunk_size=1024):
                                count += len(c)
                                f.write(c)
                            log.info("Read {} bytes.".format(count))
                        from buyercall.blueprints.sms.tasks import upload_mms_image
                        media_url = upload_mms_image(
                            partner_name,
                            partner_account.id,
                            media_file_name,
                            file_path
                        )
                        media_list.append(str(media_url))
                    except:
                        log.info('exception is: {}'.format(str(sys.exc_info()[0])))

            from buyercall.lib.util_ses_email import send_ses_email
            email_message = 'The contact id is: ' + str(new_contact.id) + ' and partnership account id: ' \
                            + str(partnership_account_id)
            support_emails = app.config.get('SUPPORT_EMAILS', [])
            send_ses_email(recipients=support_emails,
                           p_id=1,
                           subject='Encryption failed for list value - mobile',
                           text=email_message)
        else:
            media_list = ''
            message_type = 'sms'
        agent_list = []
        sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == inbound_id).first()
        if sip_endpoint and sip_endpoint.agent_id:
            from buyercall.blueprints.agents.models import Agent
            agent = Agent.query.filter(Agent.id == sip_endpoint.agent_id).first()
            if agent:
                agent_list.append(agent.id)
        message = Message(
            type=message_type,
            provider=provider,
            provider_message_id=message_object.get('id', ''),
            provider_message_date=message_object.get('time', ''),
            to=message_info[0].get('to', ''),
            from_=message_object.get('from', ''),
            body_text=message_object.get('text', ''),
            media_url=media_list,
            status=msg_status,
            delivery_code=message_info[0].get('deliveryCode', 201),
            delivery_type=message_info[0].get('type', ''),
            delivery_description=message_info[0].get('description', ''),
            direction=msg_direction,
            inbound_id=inbound_id,
            partnership_account_id=partnership_account_id,
            contact_id=new_contact_id,
            originating_number=originating_msg_number,
            agent_id=agent_list
        )
        db.session.add(message)
        db.session.commit()
        # Perform webhook call-back because a message was received
        if message_object.get('direction', '') == 'in':
            webhooker.trigger_generic_webhook('mobile_receive_message', message.id)
            if agent and new_contact and agent.id != new_contact.agent_id:
                new_contact.agent_id = agent.id
                new_contact.agent_assigned = agent.full_name
                db.session.commit()
                from buyercall.blueprints.mobile.utils import send_agent_push_notification
                send_agent_push_notification(new_contact)
        # Check to see if the message contains either the text stop or unsubscribe
        # If the message contains these keywords we do not forward the message
        # And we update the contact as do_not_contact and unsubscribe
        keyword_list = ['stop', ' stop', 'stop ', 'unsubscribe', ' unsubscribe',
                        'unsubscribe ', 'un-subscribe', 'unsubcribed']
        sms_msg_text = message_object.get('text', '')
        lowercase_text_body = sms_msg_text.lower()
        # Check to see the text body text is one of the unsubscribe/stop keywords
        if lowercase_text_body in keyword_list:
            sms_contact = Contact.query.filter(Contact.id == new_contact_id).first()
            if sms_contact:
                sms_contact.is_unsubscribe = True
                db.session.commit()
            else:
                log.error('A contact id was not found for the message being sent from: {} using phone number {}'
                          'and we were unable to unsubscribe the contact from '
                          'receiving messages'.format(message_object.get('from', ''), inbound_id))
            log.info('Message not sent due to unsubscribe keyword in received message text.')
            return ''
        # After the inbound message has been saved to db send a push notification
        # to the mobile device to fetch the message
        endpoint = Endpoint.query.filter(Endpoint.inbound_id == inbound_id).first()
        if message.type == 'mms':
            content_type = 'application/x-acro-filetransfer+json'
            message_preview = message.media_url
        else:
            content_type = 'text/plain'
            message_preview = message.body_text
        if contact and contact.firstname:
            user_display_name = contact.user_fullname
        elif contact and not contact.firstname:
            user_display_name = contact.phonenumber_1
        elif new_contact:
            user_display_name = new_contact.phonenumber_1
        else:
            user_display_name = ''
        badge = '1'
        sms_id = message.provider_message_id
        push_type = 'NotifyTextMessage'
        push_notification(sip_username=endpoint.sip_username, push_type=push_type, sms_id=sms_id,
                          message_preview=message_preview, badge=badge, user_display_name=user_display_name,
                          content_type=content_type)

        return message.id


@celery.task
def qr_code(sip_username, sip_password, partnership_account_id, partner_account_name, cloud_id, **kwargs):
    """ Upload the qr code to S3 to get data.
        """
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=12,
        border=2,
    )

    endpoint = Endpoint.query.filter(Endpoint.sip_username == sip_username).first()
    if celery.conf['MOBILE_APP_TEST_MODE']:
        data = 'csc:' + sip_username + ':' + sip_password + '@' + cloud_id + '*'
    else:
        data = 'csc:' + sip_username + ':' + sip_password + '@' + cloud_id
    log.info('The content for the qr code is {}'.format(data))
    qr.add_data(data)
    qr.make(fit=True)

    img = qr.make_image(fill_color="black", back_color="white")
    qr_transform = BytesIO()
    img.save(qr_transform, 'PNG')

    qr_image = qr_transform.getvalue()

    try:
        s3 = boto.connect_s3(celery.conf['AMAZON_ACCESS_KEY'], celery.conf['AMAZON_SECRET_KEY'])

        # Get a handle to the S3 bucket
        bucket_name = celery.conf['QR_BUCKET']
        bucket = s3.get_bucket(bucket_name)
        log.info('The bucket is {}'.format(bucket))
        k = Key(bucket)
        log.info('The key is {}'.format(k))

        # Use Boto to upload the file to the S3 bucket
        hex_value = uuid.uuid4().hex
        log.info('the hex value {}'.format(hex_value))
        log.info('the partner account name {}'.format(partner_account_name))
        log.info('the partner account id {}'.format(partnership_account_id))
        log.info('the sip username {}'.format(sip_username))
        try:
            key = '{}_{}/{}_qrcode_{}'.format(
                partner_account_name, partnership_account_id, sip_username, hex_value)
            log.debug('The qr code key is {}'.format(key))
        except Exception as e:
            log.debug('Could not set key for the qr image')

        k.key = key
        k.set_metadata('Content-Type', 'image/png')
        k.set_contents_from_string(qr_image)
        k.set_acl('public-read')
        log.info('Uploaded file {}'.format(k.key))
        return k.generate_url(expires_in=0, query_auth=False)
    except Exception as e:
        print(e.__doc__)
        print(e.message)


@celery.task
def delete_qr_code(qr_url, **kwargs):
    """ DELETE QR code image from S3 bucket
    """
    # Try and split the file name from the url
    old_qr_url = str(qr_url)
    log.info('The url is {}'.format(old_qr_url))
    domain, file_name = old_qr_url.replace("https://", "").split('/', 1)
    formatted_file_name = file_name.replace("%20", " ")
    log.info('the file name is {}'.format(formatted_file_name))

    try:
        s3 = boto.connect_s3(celery.conf['AMAZON_ACCESS_KEY'], celery.conf['AMAZON_SECRET_KEY'])

        # Get a handle to the S3 bucket
        bucket_name = celery.conf['QR_BUCKET']
        bucket = s3.get_bucket(bucket_name)
        log.info('The bucket is {}'.format(bucket))
        k = Key(bucket)
        log.info('The key is {}'.format(k))

        k.key = formatted_file_name
        bucket.delete_key(k)
        log.info('DELETED file {}'.format(k.key))
        return ''
    except Exception as e:
        print(e.__doc__)
        print(e.message)


@celery.task
def send_mobile_app_info_email(sip_id, phonenumber, email, partnership_account_id, **kwargs):

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

    sip_endpoint = Endpoint.query.filter(and_(Endpoint.id == sip_id,
                                              Endpoint.partnership_account_id == partnership_account_id,
                                              Endpoint.is_deactivated.is_(False))).first()

    partnership_name = partner.name
    partnership_logo = partner.logo

    ctx = {'qr_code': sip_endpoint.qr_url, 'phonenumber': phonenumber, 'company': partnership_name,
           'partner_logo': partnership_logo}

    # Render html template for email
    qr_email_template = _try_renderer_template('mail/mobile_app_setup', ext='html', **ctx)
    
    send_ses_email(recipients=[email],
                   p_id=partner.id,
                   subject='BuyerCall Mobile App Setup Notification',
                   html=qr_email_template
                   )


@celery.task
def send_vm_push_notification(lead_id, **kwargs):
    lead = Lead.query.filter(Lead.id == lead_id).first()
    contact = Contact.query.filter(Contact.id == lead.contact_id).first()
    if contact is not None and contact.user_fullname not in ('', ' '):
        contact_detail = contact.user_fullname
    else:
        contact_detail = format_phone_number_bracket(lead.phonenumber)
    sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == lead.inbound_id).first()
    if sip_endpoint:
        vm_push_msg = 'New voicemail from ' + contact_detail
        push_notification(sip_username=sip_endpoint.sip_username,
                          push_type='NotifyGenericTextMessage',
                          message=vm_push_msg)