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.

269 lines
9.1 KiB

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