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.

421 lines
18 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
  3. # © 2014-2016 Trever L. Adams
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  5. from openerp import models, fields, api, _
  6. from openerp.exceptions import UserError, ValidationError
  7. import logging
  8. try:
  9. from freeswitchESL import ESL
  10. except ImportError:
  11. import ESL
  12. import StringIO
  13. import re
  14. import json
  15. _logger = logging.getLogger(__name__)
  16. class FreeSWITCHServer(models.Model):
  17. '''FreeSWITCH server object, stores the parameters of the FreeSWITCH
  18. Servers'''
  19. _name = "freeswitch.server"
  20. _description = "FreeSWITCH Servers"
  21. name = fields.Char(string='FreeSWITCH Server Name', required=True)
  22. active = fields.Boolean(
  23. string='Active', help="The active field allows you to hide the "
  24. "FreeSWITCH server without deleting it.", default=True)
  25. ip_address = fields.Char(
  26. string='FreeSWITCH IP address or DNS', required=True,
  27. help="IP address or DNS name of the FreeSWITCH server.")
  28. port = fields.Integer(
  29. string='Port', required=True, default=8021,
  30. help="TCP port on which the FreeSWITCH Event Socket listens. "
  31. "Defined in "
  32. "/etc/freeswitch/autoload_configs/event_socket.conf.xml on "
  33. "FreeSWITCH.")
  34. out_prefix = fields.Char(
  35. string='Out Prefix', size=4, help="Prefix to dial to make outgoing "
  36. "calls. If you don't use a prefix to make outgoing calls, "
  37. "leave empty.")
  38. password = fields.Char(
  39. string='Event Socket Password', required=True,
  40. help="Password that OpenERP/Odoo will use to communicate with the "
  41. "FreeSWITCH Event Socket. Refer to "
  42. "/etc/freeswitch/autoload_configs/event_socket.conf.xml "
  43. "on your FreeSWITCH server.")
  44. context = fields.Char(
  45. string='Dialplan Context', required=True, default="XML default",
  46. help="FreeSWITCH dialplan context from which the calls will be "
  47. "made; e.g. 'XML default'. Refer to /etc/freeswitch/dialplan/* "
  48. "on your FreeSWITCH server.")
  49. wait_time = fields.Integer(
  50. string='Wait Time (sec)', required=True,
  51. help="Amount of time (in seconds) FreeSWITCH will try to reach "
  52. "the user's phone before hanging up.", default=60)
  53. alert_info = fields.Char(
  54. string='Alert-Info SIP Header',
  55. help="Set Alert-Info header in SIP request to user's IP Phone "
  56. "for the click2dial feature. If empty, the Alert-Info header "
  57. "will not be added. You can use it to have a special ring tone "
  58. "for click2dial (a silent one !) or to activate auto-answer "
  59. "for example.")
  60. company_id = fields.Many2one(
  61. 'res.company', string='Company',
  62. default=lambda self: self.env['res.company']._company_default_get(
  63. 'freeswitch.server'),
  64. help="Company who uses the FreeSWITCH server.")
  65. @api.multi
  66. @api.constrains(
  67. 'out_prefix', 'wait_time', 'port', 'context', 'alert_info', 'password')
  68. def _check_validity(self):
  69. for server in self:
  70. out_prefix = (_('Out prefix'), server.out_prefix)
  71. dialplan_context = (_('Dialplan context'), server.context)
  72. alert_info = (_('Alert-Info SIP header'), server.alert_info)
  73. password = (_('Event Socket password'), server.password)
  74. if out_prefix[1] and not out_prefix[1].isdigit():
  75. raise ValidationError(
  76. _("Only use digits for the '%s' on the FreeSWITCH server "
  77. "'%s'" % (out_prefix[0], server.name)))
  78. if server.wait_time < 1 or server.wait_time > 120:
  79. raise ValidationError(
  80. _("You should set a 'Wait time' value between 1 and 120 "
  81. "seconds for the FreeSWITCH server '%s'"
  82. % server.name))
  83. if server.port > 65535 or server.port < 1:
  84. raise ValidationError(
  85. _("You should set a TCP port between 1 and 65535 for the "
  86. "FreeSWITCH server '%s'" % server.name))
  87. for check_str in [dialplan_context, alert_info, password]:
  88. if check_str[1]:
  89. try:
  90. check_str[1].encode('ascii')
  91. except UnicodeEncodeError:
  92. raise ValidationError(
  93. _("The '%s' should only have ASCII characters for "
  94. "the FreeSWITCH server '%s'"
  95. % (check_str[0], server.name)))
  96. @api.model
  97. def _connect_to_freeswitch(self):
  98. '''
  99. Open the connection to the FreeSWITCH Event Socket
  100. Returns an instance of the FreeSWITCH Event Socket
  101. '''
  102. user = self.env.user
  103. fs_server = user.get_freeswitch_server_from_user()
  104. # We check if the current user has a chan type
  105. if not user.freeswitch_chan_type:
  106. raise UserError(
  107. _('No channel type configured for the current user.'))
  108. # We check if the current user has an internal number
  109. if not user.resource:
  110. raise UserError(
  111. _('No resource name configured for the current user.'))
  112. _logger.debug(
  113. "User's phone: %s/%s", user.freeswitch_chan_type, user.resource)
  114. _logger.debug(
  115. "FreeSWITCH server: %s:%d", fs_server.ip_address, fs_server.port)
  116. # Connect to the FreeSWITCH Event Socket
  117. try:
  118. fs_manager = ESL.ESLconnection(
  119. str(fs_server.ip_address),
  120. str(fs_server.port), str(fs_server.password))
  121. except Exception as e:
  122. _logger.error(
  123. "Error in the request to the FreeSWITCH Event Socket %s",
  124. fs_server.ip_address)
  125. _logger.error("Here is the error message: %s", e)
  126. raise UserError(
  127. _("Problem in the request from Odoo to FreeSWITCH. "
  128. "Here is the error message: %s" % e))
  129. return (user, fs_server, fs_manager)
  130. @api.multi
  131. def test_es_connection(self):
  132. self.ensure_one()
  133. fs_manager = False
  134. try:
  135. fs_manager = ESL.ESLconnection(
  136. str(self.ip_address),
  137. str(self.port), str(self.password))
  138. except Exception as e:
  139. raise UserError(
  140. _("Connection Test Failed! The error message is: %s" % e))
  141. finally:
  142. try:
  143. if fs_manager.connected() is not 1:
  144. raise UserError(
  145. _("Connection Test Failed! Check Host, Port and "
  146. "Password."))
  147. else:
  148. fs_manager.disconnect()
  149. except Exception as e:
  150. pass
  151. raise UserError(_(
  152. "Connection Test Successfull! Odoo can successfully login to "
  153. "the FreeSWITCH Event Socket."))
  154. @api.model
  155. def _get_calling_number(self):
  156. user, fs_server, fs_manager = self._connect_to_freeswitch()
  157. calling_party_number = False
  158. try:
  159. is_fq_res = user.resource.rfind('@')
  160. if is_fq_res > 0:
  161. <<<<<<< d8cf9efb5fd18748d1901978041bb8f34ee1717b
  162. resource = user.resource[0:is_fq_res]
  163. =======
  164. resource = user.resource[0:is_fq_res+1]
  165. _logger.error("is_fq_res: %d, resource is %s\n", is_fq_res, resource)
  166. >>>>>>> Thank you to Alexandr Usov and Stanislav Sinyagin for testing and ideas.
  167. else:
  168. resource = user.resource
  169. request = "channels like /" + re.sub(r'/', r':', resource) + \
  170. ("/" if user.freeswitch_chan_type == "FreeTDM" else "@") + \
  171. " as json"
  172. ret = fs_manager.api('show', str(request))
  173. f = json.load(StringIO.StringIO(ret.getBody()))
  174. if int(f['row_count']) > 0:
  175. for x in range(0, int(f['row_count'])):
  176. if (is_fq_res > 0 and f['rows'][x]['presence_id'] !=
  177. user.resource):
  178. continue
  179. if (f['rows'][x]['cid_num'] == user.internal_number or
  180. len(f['rows'][x]['cid_num']) < 3):
  181. calling_party_number = f['rows'][x]['dest']
  182. else:
  183. calling_party_number = f['rows'][x]['cid_num']
  184. except Exception as e:
  185. _logger.error(
  186. "Error in the Status request to FreeSWITCH server %s",
  187. fs_server.ip_address)
  188. _logger.error(
  189. "Here are the details of the error: '%s'" % unicode(e))
  190. raise UserError(
  191. _("Can't get calling number from FreeSWITCH.\nHere is the "
  192. "error: '%s'" % unicode(e)))
  193. finally:
  194. fs_manager.disconnect()
  195. _logger.debug("Calling party number: '%s'", calling_party_number)
  196. if calling_party_number and calling_party_number.isdigit():
  197. return calling_party_number
  198. else:
  199. return False
  200. @api.model
  201. def get_record_from_my_channel(self):
  202. calling_number = self.env['freeswitch.server']._get_calling_number()
  203. # calling_number = "0641981246"
  204. if calling_number:
  205. record = self.env['phone.common'].get_record_from_phone_number(
  206. calling_number)
  207. if record:
  208. return record
  209. else:
  210. return calling_number
  211. else:
  212. return False
  213. class ResUsers(models.Model):
  214. _inherit = "res.users"
  215. internal_number = fields.Char(
  216. string='Internal Number',
  217. help="User's internal phone number.")
  218. dial_suffix = fields.Char(
  219. string='User-specific Dial Suffix',
  220. help="User-specific dial suffix such as aa=2wb for SCCP "
  221. "auto answer.")
  222. callerid = fields.Char(
  223. string='Caller ID', copy=False,
  224. help="Caller ID used for the calls initiated by this user. "
  225. "This must be in the form of 'Name <NUMBER>'.")
  226. cdraccount = fields.Char(
  227. string='CDR Account',
  228. help="Call Detail Record (CDR) account used for billing this "
  229. "user.")
  230. freeswitch_chan_type = fields.Selection([
  231. ('user', 'SIP'),
  232. ('FreeTDM', 'FreeTDM'),
  233. ('verto.rtc', 'Verto'),
  234. ('skinny', 'Skinny'),
  235. ('h323', 'H323'),
  236. ('dingaling', 'XMPP/JINGLE'),
  237. ('gsmopen', 'GSM SMS/Voice'),
  238. ('skypeopen', 'SkypeOpen'),
  239. ('Khomp', 'Khomp'),
  240. ('opal', 'Opal Multi-protocol'),
  241. ('portaudio', 'Portaudio'),
  242. ], string='FreeSWITCH Channel Type', default='user',
  243. help="FreeSWITCH channel type, as used in the FreeSWITCH "
  244. "dialplan. If the user has a regular IP phone, the channel type "
  245. "is 'SIP'. Use Verto for verto.rtc connections only if you "
  246. "haven't added '${verto_contact ${dialed_user}@${dialed_domain}}' "
  247. "to the default dial string. Otherwise, use SIP. (This better "
  248. "allows for changes to the user directory and changes in type of "
  249. "phone without the need for further changes in OpenERP/Odoo.)")
  250. resource = fields.Char(
  251. string='Resource Name',
  252. help="Resource name for the channel type selected. For example, "
  253. "if you use 'user/phone1' in your FreeSWITCH dialplan to ring "
  254. "the SIP phone of this user, then the resource name for this user "
  255. "is 'phone1'. For a SIP phone, the phone number is often used as "
  256. "resource name, but not always. FreeTDM will be the span followed "
  257. "by the port (i.e. 1/5).")
  258. alert_info = fields.Char(
  259. string='User-specific Alert-Info SIP Header',
  260. help="Set a user-specific Alert-Info header in SIP request to "
  261. "user's IP Phone for the click2dial feature. If empty, the "
  262. "Alert-Info header will not be added. You can use it to have a "
  263. "special ring tone for click2dial (a silent one !) or to "
  264. "activate auto-answer for example.")
  265. variable = fields.Char(
  266. string='User-specific Variable',
  267. help="Set a user-specific 'Variable' field in the FreeSWITCH "
  268. "Event Socket 'originate' request for the click2dial "
  269. "feature. If you want to have several variable headers, separate "
  270. "them with '|'.")
  271. freeswitch_server_id = fields.Many2one(
  272. 'freeswitch.server', string='FreeSWITCH Server',
  273. help="FreeSWITCH server on which the user's phone is connected. "
  274. "If you leave this field empty, it will use the first FreeSWITCH "
  275. "server of the user's company.")
  276. @api.multi
  277. @api.constrains('resource', 'internal_number', 'callerid')
  278. def _check_validity(self):
  279. for user in self:
  280. strings_to_check = [
  281. (_('Resource Name'), user.resource),
  282. (_('Internal Number'), user.internal_number),
  283. (_('Caller ID'), user.callerid),
  284. ]
  285. for check_string in strings_to_check:
  286. if check_string[1]:
  287. try:
  288. check_string[1].encode('ascii')
  289. except UnicodeEncodeError:
  290. raise ValidationError(_(
  291. "The '%s' for the user '%s' should only have "
  292. "ASCII caracters."),
  293. check_string[0], user.name)
  294. @api.multi
  295. def get_freeswitch_server_from_user(self):
  296. '''Returns an freeswitch.server recordset'''
  297. self.ensure_one()
  298. # We check if the user has an FreeSWITCH server configured
  299. if self.freeswitch_server_id.id:
  300. fs_server = self.freeswitch_server_id
  301. else:
  302. freeswitch_servers = self.env['freeswitch.server'].search(
  303. [('company_id', '=', self.company_id.id)])
  304. # If the user doesn't have an FreeSWITCH server,
  305. # we take the first one of the user's company
  306. if not freeswitch_servers:
  307. raise UserError(
  308. _("No FreeSWITCH server configured for the company '%s'.")
  309. % self.company_id.name)
  310. else:
  311. fs_server = freeswitch_servers[0]
  312. return fs_server
  313. class PhoneCommon(models.AbstractModel):
  314. _inherit = 'phone.common'
  315. @api.model
  316. def click2dial(self, erp_number):
  317. res = super(PhoneCommon, self).click2dial(erp_number)
  318. if not erp_number:
  319. raise UserError(_('Missing phone number.'))
  320. user, fs_server, fs_manager = \
  321. self.env['freeswitch.server']._connect_to_freeswitch()
  322. fs_number = self.convert_to_dial_number(erp_number)
  323. # Add 'out prefix'
  324. if fs_server.out_prefix:
  325. _logger.debug('Out prefix = %s', fs_server.out_prefix)
  326. fs_number = '%s%s' % (fs_server.out_prefix, fs_number)
  327. _logger.debug('Number to be sent to FreeSWITCH = %s', fs_number)
  328. # The user should have a CallerID
  329. if not user.callerid:
  330. raise UserError(_('No callerID configured for the current user.'))
  331. variable = ""
  332. if user.freeswitch_chan_type == 'user':
  333. # We can only have one alert-info header in a SIP request
  334. if user.alert_info:
  335. variable += 'alert_info=' + user.alert_info
  336. elif fs_server.alert_info:
  337. variable += 'alert_info=' + fs_server.alert_info
  338. if user.variable:
  339. for user_variable in user.variable.split('|'):
  340. if len(variable) and len(user_variable):
  341. variable += ','
  342. variable += user_variable.strip()
  343. if user.callerid:
  344. caller_name = re.search(r'([^<]*).*',
  345. user.callerid).group(1).strip()
  346. caller_number = re.search(r'.*<(.*)>.*',
  347. user.callerid).group(1).strip()
  348. if caller_name:
  349. if len(variable):
  350. variable += ','
  351. caller_name = caller_name.replace(",", r"\,")
  352. variable += 'effective_caller_id_name=\'' + caller_name + '\''
  353. if caller_number:
  354. if len(variable):
  355. variable += ','
  356. variable += 'effective_caller_id_number=\'' + \
  357. caller_number + '\''
  358. if fs_server.wait_time != 60:
  359. if len(variable):
  360. variable += ','
  361. variable += 'ignore_early_media=true' + ','
  362. variable += 'originate_timeout=' + str(fs_server.wait_time)
  363. if len(variable):
  364. variable += ','
  365. variable += 'odoo_connector=true'
  366. channel = '%s/%s' % (user.freeswitch_chan_type, user.resource)
  367. if user.dial_suffix:
  368. channel += '/%s' % user.dial_suffix
  369. try:
  370. # originate <csv global vars>user/2005 1005 DP_TYPE DP_NAME
  371. # 'Caller ID name showed to aleg' 90125
  372. dial_string = (('<' + variable + '>') if variable else '') + \
  373. channel + ' ' + fs_number + ' ' + fs_server.context + ' ' + \
  374. '\'' + self.get_name_from_phone_number(fs_number) + '\' ' + \
  375. fs_number
  376. fs_manager.api('originate', dial_string.encode('utf-8'))
  377. except Exception as e:
  378. _logger.error(
  379. "Error in the Originate request to FreeSWITCH server %s",
  380. fs_server.ip_address)
  381. _logger.error(
  382. "Here are the details of the error: '%s'", unicode(e))
  383. raise UserError(
  384. _("Click to dial with FreeSWITCH failed.\nHere is the error: "
  385. "'%s'")
  386. % unicode(e))
  387. finally:
  388. fs_manager.disconnect()
  389. res['dialed_number'] = fs_number
  390. return res