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.

236 lines
9.7 KiB

10 years ago
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2010-2018 Akretion France
  3. # @author: Alexis de Lattre <alexis.delattre@akretion.com>
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  5. from odoo import models, fields, api, _
  6. from odoo.exceptions import UserError, ValidationError
  7. from pprint import pformat
  8. import logging
  9. _logger = logging.getLogger(__name__)
  10. try:
  11. # pip install py-Asterisk
  12. from Asterisk import Manager
  13. except ImportError:
  14. _logger.debug('Cannot import Asterisk')
  15. Manager = None
  16. class AsteriskServer(models.Model):
  17. '''Asterisk server object, stores the parameters of the Asterisk IPBXs'''
  18. _name = "asterisk.server"
  19. _description = "Asterisk Servers"
  20. name = fields.Char(string='Asterisk Server Name', required=True)
  21. active = fields.Boolean(string='Active', default=True)
  22. ip_address = fields.Char(
  23. string='Asterisk IP address or DNS', required=True)
  24. port = fields.Integer(
  25. string='Port', required=True, default=5038,
  26. help="TCP port on which the Asterisk Manager Interface listens. "
  27. "Defined in /etc/asterisk/manager.conf on Asterisk.")
  28. out_prefix = fields.Char(
  29. string='Out Prefix', size=4, help="Prefix to dial to make outgoing "
  30. "calls. If you don't use a prefix to make outgoing calls, "
  31. "leave empty.")
  32. login = fields.Char(
  33. string='AMI Login', required=True,
  34. help="Login that Odoo will use to communicate with the "
  35. "Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
  36. "on your Asterisk server.")
  37. password = fields.Char(
  38. string='AMI Password', required=True,
  39. help="Password that Odoo will use to communicate with the "
  40. "Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
  41. "on your Asterisk server.")
  42. context = fields.Char(
  43. string='Dialplan Context', required=True,
  44. help="Asterisk dialplan context from which the calls will be "
  45. "made. Refer to /etc/asterisk/extensions.conf on your Asterisk "
  46. "server.")
  47. wait_time = fields.Integer(
  48. string='Wait Time', required=True, default=15,
  49. help="Amount of time (in seconds) Asterisk will try to reach "
  50. "the user's phone before hanging up.")
  51. extension_priority = fields.Integer(
  52. string='Extension Priority', required=True, default=1,
  53. help="Priority of the extension in the Asterisk dialplan. Refer "
  54. "to /etc/asterisk/extensions.conf on your Asterisk server.")
  55. alert_info = fields.Char(
  56. string='Alert-Info SIP Header',
  57. help="Set Alert-Info header in SIP request to user's IP Phone "
  58. "for the click2dial feature. If empty, the Alert-Info header "
  59. "will not be added. You can use it to have a special ring tone "
  60. "for click2dial (a silent one !) or to activate auto-answer "
  61. "for example.")
  62. company_id = fields.Many2one(
  63. 'res.company', string='Company',
  64. default=lambda self: self.env['res.company']._company_default_get(
  65. 'asterisk.server'),
  66. help="Company who uses the Asterisk server.")
  67. @api.constrains(
  68. 'out_prefix', 'wait_time', 'extension_priority', 'port',
  69. 'context', 'alert_info', 'login', 'password')
  70. def _check_validity(self):
  71. for server in self:
  72. out_prefix = ('Out prefix', server.out_prefix)
  73. dialplan_context = ('Dialplan context', server.context)
  74. alert_info = ('Alert-Info SIP header', server.alert_info)
  75. login = ('AMI login', server.login)
  76. password = ('AMI password', server.password)
  77. if out_prefix[1] and not out_prefix[1].isdigit():
  78. raise ValidationError(_(
  79. "Only use digits for the '%s' on the Asterisk server "
  80. "'%s'" % (out_prefix[0], server.name)))
  81. if server.wait_time < 1 or server.wait_time > 120:
  82. raise ValidationError(_(
  83. "You should set a 'Wait time' value between 1 and 120 "
  84. "seconds for the Asterisk server '%s'" % server.name))
  85. if server.extension_priority < 1:
  86. raise ValidationError(_(
  87. "The 'extension priority' must be a positive value for "
  88. "the Asterisk server '%s'" % server.name))
  89. if server.port > 65535 or server.port < 1:
  90. raise ValidationError(_(
  91. "You should set a TCP port between 1 and 65535 for the "
  92. "Asterisk server '%s'" % server.name))
  93. for check_str in [dialplan_context, alert_info, login, password]:
  94. if check_str[1]:
  95. try:
  96. check_str[1].encode('ascii')
  97. except UnicodeEncodeError:
  98. raise ValidationError(_(
  99. "The '%s' should only have ASCII caracters for "
  100. "the Asterisk server '%s'"
  101. % (check_str[0], server.name)))
  102. @api.model
  103. def _connect_to_asterisk(self):
  104. '''
  105. Open the connection to the Asterisk Manager
  106. Returns an instance of the Asterisk Manager
  107. '''
  108. user = self.env.user
  109. ast_server = user.get_asterisk_server_from_user()
  110. # We check if the current user has a chan type
  111. if not user.asterisk_chan_type:
  112. raise UserError(_(
  113. 'No channel type configured for the current user.'))
  114. # We check if the current user has an internal number
  115. if not user.resource:
  116. raise UserError(_(
  117. 'No resource name configured for the current user'))
  118. _logger.debug(
  119. "User's phone: %s/%s", user.asterisk_chan_type, user.resource)
  120. _logger.debug(
  121. "Asterisk server: %s:%d", ast_server.ip_address, ast_server.port)
  122. # Connect to the Asterisk Manager Interface
  123. try:
  124. ast_manager = Manager.Manager(
  125. (ast_server.ip_address, ast_server.port),
  126. ast_server.login, ast_server.password)
  127. except Exception as e:
  128. _logger.error(
  129. "Error in the request to the Asterisk Manager Interface %s",
  130. ast_server.ip_address)
  131. _logger.error("Here is the error message: %s", e)
  132. raise UserError(_(
  133. "Problem in the request from Odoo to Asterisk. "
  134. "Here is the error message: %s" % e))
  135. return (user, ast_server, ast_manager)
  136. def test_ami_connection(self):
  137. self.ensure_one()
  138. ast_manager = False
  139. try:
  140. ast_manager = Manager.Manager(
  141. (self.ip_address, self.port),
  142. self.login,
  143. self.password)
  144. except Exception as e:
  145. raise UserError(_(
  146. "Connection Test Failed! The error message is: %s" % e))
  147. finally:
  148. if ast_manager:
  149. ast_manager.Logoff()
  150. raise UserError(_(
  151. "Connection Test Successfull! Odoo can successfully login to "
  152. "the Asterisk Manager Interface."))
  153. @api.model
  154. def _get_calling_number_from_channel(self, chan, user):
  155. '''Method designed to be inherited to work with
  156. very old or very new versions of Asterisk'''
  157. sip_account = user.asterisk_chan_type + '/' + user.resource
  158. internal_number = user.internal_number
  159. # 4 = Ring
  160. # 6 = Up
  161. if (
  162. chan.get('ChannelState') in ('4', '6') and (
  163. chan.get('ConnectedLineNum') == internal_number or
  164. chan.get('EffectiveConnectedLineNum') == internal_number or
  165. sip_account in chan.get('BridgedChannel', ''))):
  166. _logger.debug(
  167. "Found a matching Event with channelstate = %s",
  168. chan.get('ChannelState'))
  169. return chan.get('CallerIDNum')
  170. # Compatibility with Asterisk 1.4
  171. if (
  172. chan.get('State') == 'Up' and
  173. sip_account in chan.get('Link', '')):
  174. _logger.debug("Found a matching Event in 'Up' state")
  175. return chan.get('CallerIDNum')
  176. return False
  177. @api.model
  178. def _get_calling_number(self):
  179. user, ast_server, ast_manager = self._connect_to_asterisk()
  180. calling_party_number = False
  181. try:
  182. list_chan = ast_manager.Status()
  183. # from pprint import pprint
  184. # pprint(list_chan)
  185. _logger.debug("Result of Status AMI request:")
  186. _logger.debug(pformat(list_chan))
  187. for chan in list_chan.values():
  188. calling_party_number = self._get_calling_number_from_channel(
  189. chan, user)
  190. if calling_party_number:
  191. break
  192. except Exception as e:
  193. _logger.error(
  194. "Error in the Status request to Asterisk server %s",
  195. ast_server.ip_address)
  196. _logger.error(
  197. "Here are the details of the error: '%s'", str(e))
  198. raise UserError(_(
  199. "Can't get calling number from Asterisk.\nHere is the "
  200. "error: '%s'" % str(e)))
  201. finally:
  202. ast_manager.Logoff()
  203. _logger.debug("Calling party number: '%s'", calling_party_number)
  204. return calling_party_number
  205. @api.model
  206. def get_record_from_my_channel(self):
  207. calling_number = self.env['asterisk.server']._get_calling_number()
  208. # calling_number = "0641981246"
  209. if calling_number:
  210. record = self.env['phone.common'].get_record_from_phone_number(
  211. calling_number)
  212. if record:
  213. return record
  214. else:
  215. return calling_number
  216. else:
  217. return False