diff --git a/twilio_bot/controllers/main.py b/twilio_bot/controllers/main.py index e1821d6..22389fa 100644 --- a/twilio_bot/controllers/main.py +++ b/twilio_bot/controllers/main.py @@ -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) diff --git a/voice_twilio/__manifest__.py b/voice_twilio/__manifest__.py index f2cbe69..ec527e1 100644 --- a/voice_twilio/__manifest__.py +++ b/voice_twilio/__manifest__.py @@ -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' diff --git a/voice_twilio/controllers/main.py b/voice_twilio/controllers/main.py index 196ad21..ff5457d 100644 --- a/voice_twilio/controllers/main.py +++ b/voice_twilio/controllers/main.py @@ -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: @@ -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): diff --git a/voice_twilio/models/__init__.py b/voice_twilio/models/__init__.py index 168bcee..9b09fe5 100644 --- a/voice_twilio/models/__init__.py +++ b/voice_twilio/models/__init__.py @@ -1,2 +1,6 @@ -# © 2018 Danimar Ribeiro +# © 2018 Danimar Ribeiro # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + + +from . import twilio_voice_call +from . import voice_flow diff --git a/voice_twilio/models/twilio_voice_call.py b/voice_twilio/models/twilio_voice_call.py new file mode 100644 index 0000000..3afc501 --- /dev/null +++ b/voice_twilio/models/twilio_voice_call.py @@ -0,0 +1,57 @@ +# © 2018 Danimar Ribeiro +# 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)}) diff --git a/voice_twilio/models/voice_flow.py b/voice_twilio/models/voice_flow.py new file mode 100644 index 0000000..110ee0a --- /dev/null +++ b/voice_twilio/models/voice_flow.py @@ -0,0 +1,43 @@ +# © 2018 Danimar Ribeiro +# 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") diff --git a/voice_twilio/security/ir.model.access.csv b/voice_twilio/security/ir.model.access.csv new file mode 100644 index 0000000..3de05b6 --- /dev/null +++ b/voice_twilio/security/ir.model.access.csv @@ -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 diff --git a/voice_twilio/views/twilio_voice_call.xml b/voice_twilio/views/twilio_voice_call.xml new file mode 100644 index 0000000..50ce78c --- /dev/null +++ b/voice_twilio/views/twilio_voice_call.xml @@ -0,0 +1,40 @@ + + + + view_twilio_voice_call_form + twilio.voice.call + +
+ + + + + + + + + + + + + + + +
+
+
+ + + Voice Calls + twilio.voice.call + tree,form + + + + + +
diff --git a/voice_twilio/views/voice_flow.xml b/voice_twilio/views/voice_flow.xml new file mode 100644 index 0000000..c0160c3 --- /dev/null +++ b/voice_twilio/views/voice_flow.xml @@ -0,0 +1,55 @@ + + + + view_twilio_voice_flow_form + twilio.voice.flow + +
+ + + + + + + + + + + + + + + + +
+
+
+ + + view_twilio_voice_flow_tree + twilio.voice.flow + + + + + + + + + + + + + Voice Flow + twilio.voice.flow + tree,form + + + + + +