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_new/buyercall/buyercall/blueprints/sms/bw_sms_inbound.py
"""
Governs the logic of the incoming lead text messages.
"""
from __future__ import print_function
import logging

from flask import (
    Blueprint,
    request,
    jsonify,
    make_response,
    current_app as app,
)
import json
from buyercall.blueprints.block_numbers.models import Block
from buyercall.extensions import csrf, db
from sqlalchemy import and_
from sqlalchemy.orm import load_only

log = logging.getLogger(__name__)

bw_sms_inbound = Blueprint(
    'bw_sms_inbound', __name__, template_folder='templates'
)

provider = "bandwidth"


# Testing Bandwidth Migration
@bw_sms_inbound.route('/bw/sms', methods=['GET', 'POST'])
@csrf.exempt
def inbound_sms():
    """ Entry point for the incoming SMS or MMS messages. 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 SMS.MMS 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
        lead_inbound_message(args)
        status_code = 201
        message = jsonify(message='Authentication passed.')
        response = make_response(message, status_code)
        return response
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response


def lead_inbound_message(args):
    # Declare the args parameters to get sms/mms message data
    message = args[0].get('message', '')
    from_ = message.get('from', '')
    to_ = args[0].get('to', '')

    if to_:
        # Determine the BuyerCall to establish the inbound id and partnership account
        from ..phonenumbers.models import Phone
        from ..partnership.models import PartnershipAccount,Partnership
        inbound = Phone.query.filter(Phone.phonenumber == to_).first()
        partnership_account_id = inbound.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:
            from buyercall.blueprints.sysadmin.models import RequestLog
            request_id = request.environ.get('HTTP_X_REQUEST_ID')
            partnership = Partnership.query.get(partnership_id) if partnership_id else None
            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": inbound.id if inbound 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": inbound.id if inbound else None})
        except Exception as e:
            print(f"The exception in saving inbound-id in lead_inbound_message is {e}")
        # end save inbound-id to resource field of request-log data

        if inbound:
            partnership_account_id = inbound.partnership_account_id
        else:
            log.info('Unable to match a BC number to to_ field number. args: {}'.format(args))
            return ''
        delivery_description = args[0].get('description', '')
        text = message.get('text', '')
        lowercase_text_body = text.lower()
        # Determine if there's media attached to the message making it a MMS instead of a sms
        media = message.get('media', '')
        if media:
            message_type = 'mms'
        else:
            message_type = 'sms'
    else:
        log.info('Something went wrong with receiving a sms/mms message. args: {}'.format(args))
        return ''

    # First check if the phone number sending the message is not blocked
    is_number_blocked = Block.blocked(inbound.id, from_)
    if is_number_blocked:
        log.info('This phone number: {} was blocked and the message was not received'.format(from_))
        return ''

    # Add message to message table
    from .bw_sms_tasks import add_bw_message_lead
    added_message = add_bw_message_lead(inbound.id, inbound.phonenumber, message_type, message, delivery_description,
                                        partnership_account_id, provider)

    if not added_message:
        return log.error('Something went wrong with adding the inbound message to the database. '
                         'Most likely a field length issue')
    
    from ..contacts.models import Contact
    contact = Contact.query\
        .filter(and_(Contact.id == added_message.contact_id,
                     Contact.partnership_account_id == partnership_account_id)).first()

    # A contact can send a HELP command that needs to return help information to the contact
    help_keyword_list = [' help', 'help ', 'help']

    if lowercase_text_body in help_keyword_list:
        from ..sms.bw_sms_tasks import bw_send_sms
        from ..partnership.models import PartnershipAccount
        account = PartnershipAccount.query\
            .filter(PartnershipAccount.id == partnership_account_id).options(load_only('name')).first()
        log.info(account.name)
        sender = from_
        from ..filters import format_phone_number_bracket
        return_text = account.name + ': Help at ' + format_phone_number_bracket(to_) + '. ' + 'Msg&data rates may apply. Reply STOP to unsubscribe.'
        bw_send_sms(inbound.id, sender, return_text, agent_list=None)
        return ''

    # A contact can subscribed again to receive messages. First we need to look for the command
    subscribe_keyword_list = ['unstop', ' unstop', 'unstop ', 'start', ' start', 'start ', 'nonarret']

    # Check to see if the body text is one of the the subscribed keywords
    if lowercase_text_body in subscribe_keyword_list:
        if contact:
            contact.is_unsubscribe = False
            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(from_, inbound.id))
        return ''

    if contact.is_unsubscribe:
        sender = from_
        from ..sms.bw_sms_tasks import bw_send_sms
        return_text = 'You replied with the word "STOP" which blocks all texts sent from this number. ' \
                      'Text back "UNSTOP" to receive messages again.'
        bw_send_sms(inbound.id, sender, return_text, agent_list=None)
        return ''

    # 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
    unsubscribed_keyword_list = ['stop', ' stop', 'stop ', 'unsubscribe', ' unsubscribe',
                                 'unsubscribe ', 'un-subscribe', 'unsubcribed', 'arret']

    # Check to see the text body text is one of the unsubscribe/stop keywords
    if lowercase_text_body in unsubscribed_keyword_list:
        if contact:
            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(from_, inbound.id))
        log.info('Message not sent due to unsubscribe keyword in received message text.')
        return ''

    # Forward SMS/MMS msg using function in bw celery task to assigned agents
    from ..sms.bw_sms_tasks import bw_forward_sms
    bw_forward_sms.apply_async(args=[inbound.id, added_message.id], countdown=1)

    # This checks the phone number SMS settings first and then perform actions based on the sms configuration
    if inbound.routing_config.get('configSMSSetup') and inbound.routing_config.get('SMSAutoReply'):
        from ..sms.views import send_text_message
        text_body = inbound.routing_config.get('SMSAutoReplyText') + ' Reply STOP to unsubscribe.'
        media_url = inbound.routing_config.get('SMSAutoReplyImageUrl', '')
        if media_url:
            media_key = (media_url.split('/', 3)[-1]).split('?')[0].replace('%20', ' ')
            bucket = app.config['MMS_BUCKET']
            from buyercall.lib.util_boto3_s3 import generate_presigned_aws_url
            media_url = generate_presigned_aws_url(media_key, bucket)
        send_text_message(inbound.id, from_, text_body, media_url)

    # This checks the phone number SMS settings first and then perform keyword actions based on the sms config
    if inbound.routing_config.get('configSMSSetup') and inbound.routing_config.get('SMSRuleOne'):
        from ..sms.views import send_text_message
        text_body = lowercase_text_body
        lead = from_
        # call_agent = inbound.routing_config.get('SMSRuleOneCallAgent')
        keyword = (inbound.routing_config.get('SMSRuleOneKeyword')).lower()
        action = inbound.routing_config.get('SMSRuleOneAction')
        recipient = inbound.routing_config.get('SMSRuleOneRecipientType')
        rule_sms_body = inbound.routing_config.get('SMSRuleOneSMSBody')
        rule_sms_agent = inbound.routing_config.get('SMSRuleOneRecipientAgent')
        rule_sms_custom = inbound.routing_config.get('SMSRuleOneRecipientCustom')
        # rule_sms_email_body = inbound.routing_config.get('SMSRuleOneEmailBody')
        # custom_email_addresses = inbound.routing_config.get('SMSRuleOneEmails')
        # email_agent = inbound.routing_config.get('SMSRuleOneEmailAgent')
        if keyword:
            if keyword in text_body:
                log.info('the keyword: {} is in the message'.format(keyword))
                if action == 'sms' and recipient == 'lead':
                    if lead:
                        send_text_message(inbound.id, lead, rule_sms_body, media=None)
                    else:
                        log.warning('There is no lead phone number set to receive the sms rule base sms.'
                                    ' Inbound id: {}'.format(inbound.id))
                if action == 'sms' and recipient == 'agent':
                    from ..agents.models import Agent
                    if rule_sms_agent:
                        agent_no = Agent.query.filter(Agent.id == rule_sms_agent).first()
                        send_text_message(inbound.id, agent_no.phonenumber, rule_sms_body, media=None)
                    else:
                        log.warning('There is no agent set to receive the sms rule base sms. '
                                    'Inbound id: {}'.format(inbound.id))
                if action == 'sms' and recipient == 'custom':
                    if rule_sms_custom:
                        send_text_message(inbound.id, rule_sms_custom, rule_sms_body, media=None)
                    else:
                        log.warning('There is no custom phone number set to receive the sms rule base sms. '
                                    'Inbound id: {}'.format(inbound.id))
                if action == 'unsubscribe' and recipient == 'lead':
                    from ..contacts.models import Contact
                    contact_lead = Contact.query.filter(Contact.phonenumber_1 == lead)\
                        .filter(Contact.partnership_account_id == partnership_account_id).first()
                    contact_lead.is_unsubscribe = True
                    db.session.add(contact_lead)
                    db.session.commit()
    else:
        log.info('The SMS Rule is turned off for this phone number: {}'.format(inbound.phonenumber))

    return ''


@bw_sms_inbound.route('/bw/sms/status/callback', methods=['GET', 'POST'])
@csrf.exempt
def sms_status_callback():
    """ Entry point for the incoming SMS or MMS messages 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 SMS.MMS 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
        print(args)
        status_code = 201

        # start save inbound-id to resource field of request-log data
        from buyercall.blueprints.phonenumbers import Phone
        try:
            to_ = args[0].get('to', '')
            inbound = Phone.query.filter(Phone.phonenumber == to_).first()
            # start save inbound-id to resource field of request-log data
            from ..partnership.models import PartnershipAccount,Partnership

            partnership_account_id = inbound.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

            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": inbound.id if inbound 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": inbound.id if inbound else None})
        except Exception as e:
            print(f"The Exception in sms_status_callback is {e}")
        # end save inbound-id to resource field of request-log data
        message = args[0].get('message', '')

        msg_id = message.get('id', '')
        delivery_code = args[0].get('errorCode', 201)
        delivery_description = args[0].get('description', '')
        delivery_type = args[0].get('type', '')
        
        from ..sms.bw_sms_tasks import update_msg_record
        update_msg_record.apply_async(args=[msg_id, delivery_code, delivery_type, delivery_description], countdown=1)
        message = jsonify(message='Authentication passed.')
        response = make_response(message, status_code)

        return response
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response