You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
9.8 KiB

  1. #!/usr/bin/python
  2. # -*- encoding: utf-8 -*-
  3. """
  4. Name lookup in OpenERP for incoming and outgoing calls with an
  5. FreeSWITCH system
  6. This script is designed to be used as an WSGI script on the same server
  7. as OpenERP/Odoo.
  8. Relevant documentation"
  9. https://wiki.freeswitch.org/wiki/Mod_cidlookup
  10. https://freeswitch.org/confluence/display/FREESWITCH/mod_cidlookup
  11. Apache Configuration:
  12. Listen 8080
  13. <VirtualHost *:8080>
  14. WSGIScriptAlias /wscgi-bin/ /var/www/wscgi-bin/
  15. ServerAdmin webmaster@localhost
  16. # ...
  17. </VirtualHost>
  18. FreeSWITCH mod_cidlookup configuration:
  19. <configuration name="cidlookup.conf" description="cidlookup Configuration">
  20. <settings>
  21. <param name="url" value="https://openerp.localdomain/wscgi-bin/get_caller_name.py?name=${caller_id_name}&number=${caller_id_number}&notify=1004,1007"/>
  22. <param name="cache" value="false"/>
  23. </settings>
  24. </configuration>
  25. If you want geoloc, add &geoloc=true to the end (it is going to be slow).
  26. Notify should be the internal number of the called parties. This should be
  27. comma (,) delimited, not :_: delimited. It is up to you to format the
  28. extensions list appropriately. The persons who are at extensions in the
  29. notify list will receive a poppup if so configured and if they are logged in.
  30. The notify list actually shouldn't be in the cidlookup.conf, but should be
  31. used when doing notify (in an on answer hook for example).
  32. From the dialplan, do something like this <action application="set"
  33. data="effective_caller_id_name=${cidlookup(${caller_id_number})}"/>.
  34. If you are not using FreeTDM modules for the incoming line, doing
  35. <action application="pre_answer"/> before the cidlookup is a VERY good idea.
  36. If you are wishing to set the callee name, <action application="export"
  37. data="callee_id_name=${cidlookup($1)}" />
  38. Of course, you should adapt this example to the FreeSWITCH server you are
  39. using. This is especially true of the options variable in the application
  40. function. The user (by number id, not name) that is used to connect to
  41. OpenERP/Odoo must have "Phone CallerID" access rights. That may also require
  42. "Technical Features" rights.
  43. """
  44. __author__ = "Trever Adams <trever.adams@gmail.com>"
  45. __date__ = "May 2016"
  46. __version__ = "0.5"
  47. # Copyright (C) 2014-2015 Trever L. Adams <trever.adams@gmail.com>
  48. # Copyright (C) 2010-2014 Alexis de Lattre <alexis.delattre@akretion.com>
  49. #
  50. # This program is free software: you can redistribute it and/or modify
  51. # it under the terms of the GNU Affero General Public License as
  52. # published by the Free Software Foundation, either version 3 of the
  53. # License, or (at your option) any later version.
  54. #
  55. # This program is distributed in the hope that it will be useful,
  56. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  57. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  58. # GNU Affero General Public License for more details.
  59. #
  60. # You should have received a copy of the GNU Affero General Public License
  61. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  62. import sys
  63. sys.path.append('.')
  64. import xmlrpclib
  65. from cgi import parse_qs, escape
  66. import unicodedata
  67. # Name that will be displayed if there is no match
  68. # and no geolocalisation
  69. not_found_name = "Not in OpenERP"
  70. # Name used if name and number are both empty
  71. unknown_name = "unknown"
  72. # Set to 1 for debugging output
  73. verbose = 0
  74. def stdout_write(string):
  75. '''Wrapper on sys.stdout.write'''
  76. if verbose == 1:
  77. sys.stdout.write(string)
  78. sys.stdout.flush()
  79. return True
  80. def stderr_write(string):
  81. '''Wrapper on sys.stderr.write'''
  82. if verbose == 1:
  83. sys.stderr.write(string)
  84. sys.stderr.flush()
  85. return True
  86. def geolocate_phone_number(number, my_country_code, lang):
  87. import phonenumbers
  88. from phonenumbers import geocoder
  89. res = ''
  90. phonenum = phonenumbers.parse(number, my_country_code.upper())
  91. city = phonenumbers.geocoder.description_for_number(phonenum, lang.lower())
  92. country_code = phonenumbers.region_code_for_number(phonenum)
  93. # We don't display the country name when it's my own country
  94. if country_code == my_country_code.upper():
  95. if city:
  96. res = city
  97. else:
  98. # Convert country code to country name
  99. country = phonenumbers.geocoder._region_display_name(
  100. country_code, lang.lower())
  101. if country and city:
  102. res = country + ' ' + city
  103. elif country and not city:
  104. res = country
  105. return res
  106. def convert_to_ascii(my_unicode):
  107. '''Convert to ascii, with clever management of accents (é -> e, è -> e)'''
  108. if isinstance(my_unicode, unicode):
  109. my_unicode_with_ascii_chars_only = ''.join((
  110. char for char in unicodedata.normalize('NFD', my_unicode)
  111. if unicodedata.category(char) != 'Mn'))
  112. return str(my_unicode_with_ascii_chars_only)
  113. # If the argument is already of string type, return it with the same value
  114. elif isinstance(my_unicode, str):
  115. return my_unicode
  116. else:
  117. return False
  118. def main(name, phone_number, options):
  119. # If we already have a "True" caller ID name
  120. # i.e. not just digits, but a real name, then we don't try to
  121. # connect to OpenERP or geoloc, we just keep it
  122. if (
  123. name and
  124. not name.isdigit() and
  125. name.lower()
  126. not in ['freeswitch', 'unknown', 'anonymous', 'unavailable']):
  127. stdout_write('Incoming CallerID name is %s\n' % name)
  128. stdout_write('As it is a real name, we do not change it\n')
  129. return name
  130. if not isinstance(phone_number, str):
  131. stdout_write('Phone number is empty\n')
  132. exit(0)
  133. # Match for particular cases and anonymous phone calls
  134. # To test anonymous call in France, dial 3651 + number
  135. if not phone_number.isdigit():
  136. stdout_write(
  137. 'Phone number (%s) is not a digit\n' % phone_number)
  138. exit(0)
  139. stdout_write('Phone number = %s\n' % phone_number)
  140. res = name
  141. # Yes, this script can be used without "-s openerp_server" !
  142. if options["server"]:
  143. if options["ssl"]:
  144. stdout_write(
  145. 'Starting XML-RPC secure request on OpenERP %s:%s\n'
  146. % (options["server"], str(options["port"])))
  147. protocol = 'https'
  148. else:
  149. stdout_write(
  150. 'Starting clear XML-RPC request on OpenERP %s:%s\n'
  151. % (options["server"], str(options["port"])))
  152. protocol = 'http'
  153. sock = xmlrpclib.ServerProxy(
  154. '%s://%s:%s/xmlrpc/object'
  155. % (protocol, options["server"], str(options["port"])))
  156. try:
  157. if options["notify"]:
  158. res = sock.execute(
  159. options["database"], options["user"], options["password"],
  160. 'phone.common', 'incall_notify_by_extension',
  161. phone_number, options["notify"])
  162. stdout_write('Calling incall_notify_by_extension\n')
  163. else:
  164. res = sock.execute(
  165. options["database"], options["user"], options["password"],
  166. 'phone.common', 'get_name_from_phone_number',
  167. phone_number)
  168. stdout_write('Calling get_name_from_phone_number\n')
  169. stdout_write('End of XML-RPC request on OpenERP\n')
  170. if not res:
  171. stdout_write('Phone number not found in OpenERP\n')
  172. except:
  173. stdout_write('Could not connect to OpenERP %s\n'
  174. % options["database"])
  175. res = False
  176. # To simulate a long execution of the XML-RPC request
  177. # import time
  178. # time.sleep(5)
  179. # Function to limit the size of the name
  180. if res:
  181. if len(res) > options["max_size"]:
  182. res = res[0:options["max_size"]]
  183. elif options["geoloc"]:
  184. # if the number is not found in OpenERP, we try to geolocate
  185. stdout_write(
  186. 'Trying to geolocate with country %s and lang %s\n'
  187. % (options["country"], options["lang"]))
  188. res = geolocate_phone_number(
  189. phone_number, options["country"], options["lang"])
  190. else:
  191. # if the number is not found in OpenERP and geoloc is off,
  192. # we put 'not_found_name' as Name
  193. res = not_found_name
  194. # All SIP phones should support UTF-8...
  195. # but in case you have analog phones over TDM
  196. # or buggy phones, you should use the command line option --ascii
  197. if options["ascii"]:
  198. res = convert_to_ascii(res)
  199. stdout_write('Name = %s\n' % res)
  200. return res
  201. def application(environ, start_response):
  202. output = ""
  203. name = False
  204. options = {}
  205. options["server"] = "127.0.0.1"
  206. options["port"] = 8069
  207. options["database"] = "test"
  208. options["user"] = 1
  209. options["password"] = "admin"
  210. options["geoloc"] = True
  211. options["country"] = "US"
  212. options["lang"] = "en"
  213. options["ssl"] = False
  214. options["ascii"] = True
  215. options["max_size"] = 40
  216. parameters = parse_qs(environ.get('QUERY_STRING', ''))
  217. if 'number' in parameters:
  218. number = escape(parameters['number'][0])
  219. if 'name' in parameters:
  220. name = escape(parameters['name'][0])
  221. else:
  222. name = unknown_name
  223. if 'notify' in parameters:
  224. options["notify"] = []
  225. for item in parameters['notify'][0].split(','):
  226. options["notify"].append(escape(item))
  227. stdout_write(
  228. 'Trying to notify %s\n' % options["notify"])
  229. else:
  230. options["notify"] = False
  231. if 'geoloc' in parameters:
  232. options["geoloc"] = True
  233. else:
  234. options["geoloc"] = False
  235. try:
  236. output = main(name if name else False, number, options)
  237. except:
  238. output = name
  239. status = '200 OK'
  240. response_headers = [('Content-type', 'text/plain'),
  241. ('Content-Length', str(len(output)))]
  242. start_response(status, response_headers)
  243. return [output]