Skip to content

Commit

Permalink
[WIP] Working in the voice flow, to control how incoming calls are ha…
Browse files Browse the repository at this point in the history
…ndled
  • Loading branch information
danimaribeiro committed Dec 1, 2018
1 parent 7167e7e commit 0d316df
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 53 deletions.
2 changes: 1 addition & 1 deletion twilio_bot/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

class TwilioBotController(http.Controller):

@http.route('/trustbot/new-sms', type='http', auth="public",
@http.route('/twilio/bot', type='http', auth="public",
cors="*", csrf=False)
def call_ended(self, **post):
flow = request.env['twilio.bot.flow'].sudo().search([], limit=1)
Expand Down
6 changes: 5 additions & 1 deletion voice_twilio/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
'depends': [
'twilio_base',
'phone_validation',
'mail'
'mail',
'contacts',
],
'data': [
'security/ir.model.access.csv',
'views/twilio_assets.xml',
'views/twilio_voice_call.xml',
'views/voice_flow.xml',
],
'qweb': [
'static/src/xml/dial.xml'
Expand Down
105 changes: 55 additions & 50 deletions voice_twilio/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
_logger = logging.getLogger(__name__)

try:
from twilio import twiml
from twilio.twiml.voice_response import VoiceResponse
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VoiceGrant
except ImportError:
Expand All @@ -18,61 +18,66 @@

class TwilioController(http.Controller):

@http.route('/twilio/call-ended', type='http', auth="public",
cors="*", csrf=False)
def call_ended(self, **post):
request.env['phone.call'].sudo().update_call_status(**post)

@http.route('/twilio/on-hold', type='http', auth="public",
cors="*", csrf=False)
def call_in_hold(self, **post):
request.env['phone.call'].sudo().update_call_status(**post)

response = twiml.Response()
if post["Direction"] == 'inbound' and \
post["CallStatus"] == 'in-progress':
if int(post['QueueTime']) > 60:
response.hangup()
return str(response)
response.play("http://com.twilio.sounds.music.s3.amazonaws.com/" +
"MARKOVICHAMP-Borghestral.mp3")
return str(response)

@http.route('/twilio/call-connected', type='http', auth="public",
cors="*", csrf=False)
def call_connected(self, **post):
resp = twiml.Response()
resp.say(u"Você será atendido agora", voice="alice", language="pt-BR")
# @http.route('/twilio/on-hold', type='http', auth="public",
# cors="*", csrf=False)
# def call_in_hold(self, **post):
# request.env['phone.call'].sudo().update_call_status(**post)
#
# response = twiml.Response()
# if post["Direction"] == 'inbound' and \
# post["CallStatus"] == 'in-progress':
# if int(post['QueueTime']) > 60:
# response.hangup()
# return str(response)
# response.play("http://com.twilio.sounds.music.s3.amazonaws.com/" +
# "MARKOVICHAMP-Borghestral.mp3")
# return str(response)

def new_incoming_call(self, voice_call_id, vals):
resp = VoiceResponse()
resp.say("Você será atendido agora", voice="alice", language="pt-BR")
return str(resp)

@http.route('/twilio/response', type='http', auth="public",
cors="*", csrf=False)
def receive_call(self, **post):
url_base = request.env.user.company_id.url_base
twilio_number = request.env.user.company_id.twilio_number

request.env['phone.call'].sudo().register_new_call(**post)
resp = twiml.Response()
def call_completed(self, voice_call_id, vals):
voice_call_id.action_call_completed(vals)
return 'true'

if "client" not in post["From"]:
resp.say("Estamos transferindo sua chamada, por favor aguarde",
@http.route('/twilio/voice', type='http', auth="public",
cors="*", csrf=False)
def twilio_voice_request(self, **vals):
voice_obj = request.env['twilio.voice.call'].sudo()
voice_call_id = voice_obj.register_new_call(vals)
call_status = vals['CallStatus']
if call_status == 'ringing':
return self.new_incoming_call(voice_call_id, vals)
elif call_status == 'completed':
return self.call_completed(voice_call_id, vals)
else:
resp = VoiceResponse()
resp.say("Desculpe ",
voice="alice", language="pt-BR")
resp.enqueue('trustcode',
waitUrl='http://%s/twilio/on-hold' % url_base)
resp.hangup()
return str(resp)

else:
if post["To"] != 'queue':
with resp.dial(callerId=twilio_number) as dial:
dial.number(post['To'])
return str(resp)
else:
with resp.dial(callerId=twilio_number,
record='record-from-answer') as dial:
dial.queue(
'trustcode',
url="http://%s/twilio/call-connected" % url_base)
return str(resp)
# if "client" not in post["From"]:
# resp.say("Estamos transferindo sua chamada, por favor aguarde",
# voice="alice", language="pt-BR")
# resp.enqueue('trustcode',
# waitUrl='http://%s/twilio/on-hold' % url_base)
# return str(resp)
#
# else:
# if post["To"] != 'queue':
# with resp.dial(callerId=twilio_number) as dial:
# dial.number(post['To'])
# return str(resp)
# else:
# with resp.dial(callerId=twilio_number,
# record='record-from-answer') as dial:
# dial.queue(
# 'trustcode',
# url="http://%s/twilio/call-connected" % url_base)
# return str(resp)

@http.route('/twilio/token', type='json')
def generate_token(self):
Expand Down
6 changes: 5 additions & 1 deletion voice_twilio/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# © 2018 Danimar Ribeiro <danimaibeiro@gmail.com>
# © 2018 Danimar Ribeiro <danimaribeiro@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)


from . import twilio_voice_call
from . import voice_flow
57 changes: 57 additions & 0 deletions voice_twilio/models/twilio_voice_call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# © 2018 Danimar Ribeiro <danimaribeiro@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)

from datetime import datetime
from odoo import fields, models


class TwilioVoiceCall(models.Model):
_name = 'twilio.voice.call'
_description = 'Voice Call from Twilio'

name = fields.Char(string="Name", size=50, required=True)
sid = fields.Char(string="Unique Identifier", size=40, readonly=True)
calling_date = fields.Datetime('Calling Date')
type = fields.Selection([('inbound', 'Inbound'),
('outbound', 'Outbound')])
from_number = fields.Char(string="From")
to_number = fields.Char(string="To")

from_city = fields.Char(string="From City")
to_city = fields.Char(string="To City")
from_state = fields.Char(string="From State")
to_state = fields.Char(string="To State")
from_country = fields.Char(string="From Country")
to_country = fields.Char(string="To Country")
state = fields.Selection([('ringing', 'Ringing'),
('on-hold', 'On Hold'),
('talking', 'Talking'),
('completed', 'Completed')])
call_duration = fields.Integer(string="Duration")
voice_flow_sequence = fields.Integer(default=0)
last_gather_key = fields.Char()
last_gather_value = fields.Char()

def register_new_call(self, vals):
call = self.search([('sid', '=', vals['CallSid'])])
if not call:
return self.create({
'name': 'Call from %s to: %s' % (vals['From'], vals['To']),
'sid': vals['CallSid'],
'type': vals['Direction'],
'calling_date': datetime.now(),
'from_number': vals['From'],
'to_number': vals['To'],
'from_city': vals.get('FromCity'),
'to_city': vals.get('ToCity'),
'from_state': vals.get('FromState'),
'to_state': vals.get('ToState'),
'from_country': vals.get('FromCountry'),
'to_country': vals.get('ToCountry'),
'state': vals['CallStatus'],
})
return call

def action_call_completed(self, vals):
self.write({'state': 'completed',
'call_duration': vals.get('CallDuration', 0)})
43 changes: 43 additions & 0 deletions voice_twilio/models/voice_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# © 2018 Danimar Ribeiro <danimaribeiro@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)


from odoo import fields, models


class TwilioVoiceFlow(models.Model):
_name = 'twilio.voice.flow'
_description = 'Voice Flow Configuration from Twilio'
_order = 'sequence asc'

name = fields.Char(string="Description", size=100, required=True)
sequence = fields.Integer(string="Sequence")

flow_type = fields.Selection(
[('say', 'Say'),
('gather', 'Gather Information'),
('connect', 'Connect Call')],
string="Type Action",
help="Connect Call will ring for all connected users \
unless specified partner or channel")

say_message = fields.Text(string="Message to Play", size=1000)
record_call = fields.Boolean(string="Record Call")

gather_key = fields.Char(string="Gather Key Name", size=20,
help="Identifier to be used in the next flows \
that identify the question asked!")
gather_possible_values = fields.Text(
string="Possible values", size=500,
help="Possible values to the caller answer. Use one answer per line! \
Type just numbers or words, it is going to ask for digits or \
for the caller to say the words. Use ; for use both: e.g 1;sales")

to_partner_id = fields.Many2one(
'res.partner', string="Partner", help="Partner to connect this call")
to_mail_channel_id = fields.Many2one(
'mail.channel', string="Mail Channel",
help="Channel to connect this call")

filter_key = fields.Char(string="Filter Key")
filter_value = fields.Char(string="Filter Value")
3 changes: 3 additions & 0 deletions voice_twilio/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_twilio_voice_call,access_twilio_voice_call,model_twilio_voice_call,base.group_user,1,1,1,1
access_twilio_voice_flow,access_twilio_voice_flow,model_twilio_voice_flow,base.group_user,1,1,1,1
40 changes: 40 additions & 0 deletions voice_twilio/views/twilio_voice_call.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_twilio_voice_call_form" model="ir.ui.view">
<field name="name">view_twilio_voice_call_form</field>
<field name="model">twilio.voice.call</field>
<field name="arch" type="xml">
<form>
<group>
<field name="state"/>
<field name="calling_date"/>
<field name="call_duration"/>
<field name="sid"/>
<field name="from_number" />
<field name="from_city" />
<field name="from_state" />
<field name="from_country" />
<field name="type" />
<field name="to_number" />
<field name="to_city" />
<field name="to_state" />
<field name="to_country" />
</group>
</form>
</field>
</record>

<record id="action_twilio_voice_call" model="ir.actions.act_window">
<field name="name">Voice Calls</field>
<field name="res_model">twilio.voice.call</field>
<field name="view_mode">tree,form</field>
</record>

<menuitem id="menu_twilio_voice_call"
name="Voice Calls"
action="action_twilio_voice_call"
parent="twilio_base.menu_twilio"
sequence="30"/>


</odoo>
55 changes: 55 additions & 0 deletions voice_twilio/views/voice_flow.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_twilio_voice_flow_form" model="ir.ui.view">
<field name="name">view_twilio_voice_flow_form</field>
<field name="model">twilio.voice.flow</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="flow_type"/>
<field name="say_message"/>
<field name="record_call" attrs="{'invisible': [('flow_type', '!=', 'connect')]}" />
<field name="gather_key" attrs="{'invisible': [('flow_type', '!=', 'gather')]}"/>
<field name="gather_possible_values" attrs="{'invisible': [('flow_type', '!=', 'gather')]}" />
<field name="to_partner_id" attrs="{'invisible': [('flow_type', '!=', 'connect')]}" />
<field name="to_mail_channel_id" attrs="{'invisible': [('flow_type', '!=', 'connect')]}" />
</group>
<group string="Filter Flow">
<field name="filter_key"/>
<field name="filter_value"/>
</group>
</sheet>
</form>
</field>
</record>

<record id="view_twilio_voice_flow_tree" model="ir.ui.view">
<field name="name">view_twilio_voice_flow_tree</field>
<field name="model">twilio.voice.flow</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="flow_type"/>
<field name="to_partner_id" />
<field name="to_mail_channel_id" />
</tree>
</field>
</record>

<record id="action_twilio_voice_flow" model="ir.actions.act_window">
<field name="name">Voice Flow</field>
<field name="res_model">twilio.voice.flow</field>
<field name="view_mode">tree,form</field>
</record>

<menuitem id="menu_twilio_voice_flow"
name="Voice Flows"
action="action_twilio_voice_flow"
parent="twilio_base.menu_twilio_configuration"
sequence="25"/>


</odoo>

0 comments on commit 0d316df

Please sign in to comment.