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)