From f77d86d593ff1f4970e19a7550b9789a55f475ac Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 29 Dec 2010 02:08:39 +0100 Subject: [PATCH] Add AGI script and its wrapper to manage timeout. --- asterisk_click2dial/asterisk_click2dial.py | 10 +- asterisk_click2dial/scripts/get_cid_name.py | 167 ++++++++++++++++++ .../scripts/get_cid_name_timeout.sh | 18 ++ 3 files changed, 190 insertions(+), 5 deletions(-) create mode 100755 asterisk_click2dial/scripts/get_cid_name.py create mode 100755 asterisk_click2dial/scripts/get_cid_name_timeout.sh diff --git a/asterisk_click2dial/asterisk_click2dial.py b/asterisk_click2dial/asterisk_click2dial.py index 8f8358a..4f75dbb 100644 --- a/asterisk_click2dial/asterisk_click2dial.py +++ b/asterisk_click2dial/asterisk_click2dial.py @@ -298,11 +298,11 @@ class res_partner_address(osv.osv): def get_name_from_phone_number(self, cr, uid, number, context=None): '''Function to get name from phone number. Usefull for use from Asterisk - to add CallerID name to incoming calls - To use this function from a python console/script : - import xmlrpclib - sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/object') - sock.execute("openerp_database", user_id_num, "user_passwd", 'res.partner.address', 'get_name_from_phone_number', '141983212') + to add CallerID name to incoming calls. + The "scripts/" subdirectory of this module has an AGI script that you can + install on your Asterisk IPBX : the script will be called from the Asterisk + dialplan via the AGI() function and it will use this function via an XML-RPC + request. ''' res = {} logger = netsvc.Logger() diff --git a/asterisk_click2dial/scripts/get_cid_name.py b/asterisk_click2dial/scripts/get_cid_name.py new file mode 100755 index 0000000..306923f --- /dev/null +++ b/asterisk_click2dial/scripts/get_cid_name.py @@ -0,0 +1,167 @@ +#! /usr/bin/python +# -*- encoding: utf-8 -*- +""" + CallerID name lookup in OpenERP for Asterisk IPBX + + When executed from the dialplan on an incoming phone call, it will lookup in + OpenERP's partner addresses, and, if it finds the phone number, it will get the + corresponding name of the person and use this name as CallerID name for the incoming call. + + Requires the "asterisk_click2dial" module (available in the extra-addons) + on OpenERP version >= 5 + + This script is designed to be used as an AGI on an Asterisk IPBX... + BUT I advise you to use a wrapper around this script to control the + execution time. Why ? Because if the script takes too much time to + execute or get stucks (in the XML-RPC request for example), then the + incoming phone call will also get stucks and you will miss a call ! + The most simple solution I found is to use the "timeout" shell command to + call this script, for example : + + # timeout 1s get_cid_name.py + + See my sample wrapper "get_cid_name_timeout.sh" + + Asterisk dialplan example : + + [from-extern] + exten => _0141981242,1,AGI(/usr/local/bin/get_cid_name_timeout.sh) + exten => _0141981242,n,Dial(SIP/10, 30) + exten => _0141981242,n,Answer() + exten => _0141981242,n,Voicemail(10@default,u) + exten => _0141981242,n,Hangup() + +""" + +__author__ = "Alexis de Lattre " +__date__ = "December 2010" +__version__ = "0.1" + +# Copyright (C) 2010 Alexis de Lattre +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import xmlrpclib +import sys +from optparse import OptionParser + + +# CID Name that will be displayed if there is no match in res.partner.address +default_cid_name = "Not in OpenERP" + +# Define command line options +option_server = {'names': ('-s', '--server'), 'dest': 'server', 'type': 'string', 'help': 'DNS or IP address of the OpenERP server', 'action': 'store', 'default':'localhost'} +option_port = {'names': ('-p', '--port'), 'dest': 'port', 'type': 'int', 'help': "Port of OpenERP's XML-RPC interface", 'action': 'store', 'default': 8069} +option_database = {'names': ('-d', '--database'), 'dest': 'database', 'type': 'string', 'help': "OpenERP database name", 'action': 'store', 'default': 'openerp'} +option_user = {'names': ('-u', '--user-id'), 'dest': 'user', 'type': 'int', 'help': "OpenERP user ID to use when connecting to OpenERP", 'action': 'store', 'default': 1} +option_password = {'names': ('-w', '--password'), 'dest': 'password', 'type': 'string', 'help': "Password of the OpenERP user", 'action': 'store', 'default': 'admin'} + +options = [option_server, option_port, option_database, option_user, option_password] + + +def reformat_phone_number_before_query_openerp(number): + '''We match only on the end of the phone number''' + if len(number) >= 9: + return number[-9:len(number)] # Take 9 last numbers + else: + return number + +def convert_to_ascii(my_unicode): + '''Convert to ascii, with clever management of accents (é -> e, è -> e)''' + import unicodedata + if isinstance(my_unicode, unicode): + my_unicode_with_ascii_chars_only = ''.join((char for char in unicodedata.normalize('NFD', my_unicode) if unicodedata.category(char) != 'Mn')) + return str(my_unicode_with_ascii_chars_only) + # If the argument is already of string type, we return it with the same value + elif isinstance(my_unicode, str): + return my_unicode + else: + return False + +def main(options, arguments): + #print 'options = %s' % options + #print 'arguments = %s' % arguments + + # AGI passes parameters to the script on standard input + stdinput = {} + while 1: + input_line = sys.stdin.readline().strip() + if input_line == '': + break + variable, value = input_line.split(':') # TODO à protéger ! + if variable[:4] != 'agi_': # All AGI parameters start with 'agi_' + sys.stderr.write("Bad stdin variable : %s\n" % variable) + continue + variable = variable.strip() + value = value.strip() + if variable != '': + stdinput[variable] = value + sys.stderr.write("Full AGI environnement :\n") + for variable in stdinput.keys(): + sys.stderr.write("%s = %s\n" % (variable, stdinput[variable])) + + input_cid_number = stdinput.get('agi_callerid', False) + + if not isinstance(input_cid_number, str): + exit(0) + # Match for particular cases and anonymous phone calls + # To test anonymous call in France, dial 3651 + number + if not input_cid_number.isdigit(): + sys.stdout.write('VERBOSE "CallerID number (%s) is not a digit"\n' % input_cid_number) + sys.stdout.flush() + exit(0) + + sys.stdout.write('VERBOSE "CallerID number = %s"\n' % input_cid_number) + query_number = reformat_phone_number_before_query_openerp(input_cid_number) + sys.stderr.write("phone number sent to OpenERP = %s\n" % query_number) + + sys.stdout.write('VERBOSE "Starting XML-RPC request on OpenERP %s:%s"\n' % (options.server, str(options.port))) + sys.stdout.flush() + + sock = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/object' % (options.server, str(options.port))) + + res = sock.execute(options.database, options.user, options.password, 'res.partner.address', 'get_name_from_phone_number', query_number) + # To simulate a long execution of the XML-RPC request + #import time + #time.sleep(5) + + sys.stdout.write('VERBOSE "End of XML-RPC request on OpenERP"\n') + sys.stdout.flush() + + # Function to limit the size of the CID name to 40 chars + if res: + if len(res) > 40: + res = res[0:40] + else: + # if the number is not found in OpenERP, we put 'default_cid_name' as CID Name + res = default_cid_name + + # I am not sure how SIP and IP phones manage non-ASCII caracters, so I prefer + # to replace all non-ASCII caracters in the name + res_ascii = convert_to_ascii(res) + + sys.stdout.write('VERBOSE "CallerID Name = %s"\n' % res_ascii) + sys.stdout.flush() + sys.stdout.write('SET CALLERID "%s" <%s>\n' % (res_ascii, input_cid_number)) + sys.stdout.flush() + +if __name__ == '__main__': + parser = OptionParser() + for option in options: + param = option['names'] + del option['names'] + parser.add_option(*param, **option) + options, arguments = parser.parse_args() + sys.argv[:] = arguments + main(options, arguments) diff --git a/asterisk_click2dial/scripts/get_cid_name_timeout.sh b/asterisk_click2dial/scripts/get_cid_name_timeout.sh new file mode 100755 index 0000000..35f89ad --- /dev/null +++ b/asterisk_click2dial/scripts/get_cid_name_timeout.sh @@ -0,0 +1,18 @@ +#! /bin/sh +# Written by Alexis de Lattre + +# Example of wrapper for get_cid_name.py which makes sure that the +# script doesn't take too much time to execute + +# Limiting the execution time of get_cid_name.py is important because +# the script is designed to be executed at the beginning of each +# incoming phone call... and if the script get stucks, the phone call +# will also get stucks and you will miss a call ! + +# For Debian Lenny, you need to install the package "timeout" +# For Ubuntu and Debian >= Squeeze, the "timeout" command is shipped in +# the "coreutils" package + +# The first argument of the "timeout" command is the maximum execution time +# In this example, we chose 1 second +timeout 1s /usr/local/bin/get_cid_name.py -s openerp.mycompany.com -d erp_prod -u 12 -w "mypasswd"