whatsapp_message.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. from odoo import models, fields, api
  2. from odoo.tools import groupby
  3. from odoo.exceptions import ValidationError
  4. import logging
  5. import markupsafe
  6. import requests
  7. import json
  8. import time
  9. import random
  10. import re
  11. import html
  12. import base64
  13. _logger = logging.getLogger(__name__)
  14. class WhatsAppMessage(models.Model):
  15. _inherit = 'whatsapp.message'
  16. # Campos para soporte de grupos
  17. recipient_type = fields.Selection([
  18. ('phone', 'Phone Number'),
  19. ('group', 'WhatsApp Group')
  20. ], string='Recipient Type', default='phone', help="Type of recipient: phone number or WhatsApp group")
  21. whatsapp_group_id = fields.Many2one('ww.group', string='WhatsApp Group',
  22. help="WhatsApp group to send message to (if recipient_type is group)",
  23. ondelete='set null')
  24. @api.depends('recipient_type', 'mobile_number', 'whatsapp_group_id')
  25. def _compute_final_recipient(self):
  26. """Compute the final recipient based on type"""
  27. for record in self:
  28. if record.recipient_type == 'group' and record.whatsapp_group_id:
  29. record.final_recipient = record.whatsapp_group_id.whatsapp_web_id
  30. else:
  31. record.final_recipient = record.mobile_number
  32. final_recipient = fields.Char('Final Recipient', compute='_compute_final_recipient',
  33. help="Final recipient (phone or group ID)")
  34. @api.depends('mobile_number', 'recipient_type')
  35. def _compute_mobile_number_formatted(self):
  36. """Override SOLO para casos específicos de grupos con WhatsApp Web"""
  37. for message in self:
  38. # SOLO intervenir si es grupo CON WhatsApp Web configurado
  39. if (hasattr(message, 'recipient_type') and message.recipient_type == 'group' and
  40. message.wa_account_id and message.wa_account_id.whatsapp_web_url and
  41. message.mobile_number and message.mobile_number.endswith('@g.us')):
  42. message.mobile_number_formatted = message.mobile_number
  43. else:
  44. # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
  45. super(WhatsAppMessage, message)._compute_mobile_number_formatted()
  46. @api.constrains('recipient_type', 'mobile_number', 'whatsapp_group_id')
  47. def _check_recipient_configuration(self):
  48. """Validar configuración de destinatario"""
  49. for record in self:
  50. if record.recipient_type == 'group':
  51. if not record.whatsapp_group_id and not (record.mobile_number and record.mobile_number.endswith('@g.us')):
  52. raise ValidationError("Para mensajes a grupos, debe seleccionar un grupo o proporcionar un ID de grupo válido (@g.us)")
  53. elif record.recipient_type == 'phone':
  54. if not record.mobile_number or record.mobile_number.endswith('@g.us'):
  55. raise ValidationError("Para mensajes a teléfonos, debe proporcionar un número telefónico válido")
  56. @api.onchange('recipient_type')
  57. def _onchange_recipient_type(self):
  58. """Limpiar campos al cambiar tipo de destinatario"""
  59. if self.recipient_type == 'group':
  60. self.mobile_number = False
  61. else:
  62. self.whatsapp_group_id = False
  63. def _whatsapp_phone_format(self, fpath=None, number=None, raise_on_format_error=False):
  64. """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
  65. self.ensure_one()
  66. # SOLO intervenir en casos muy específicos de grupos
  67. # Si es un mensaje a grupo Y tiene WhatsApp Web configurado
  68. if (hasattr(self, 'recipient_type') and self.recipient_type == 'group' and
  69. self.wa_account_id and self.wa_account_id.whatsapp_web_url):
  70. if self.whatsapp_group_id:
  71. return self.whatsapp_group_id.whatsapp_web_id
  72. # Si el número es un ID de grupo (termina en @g.us), retornarlo sin validar
  73. elif number and number.endswith('@g.us'):
  74. return number
  75. elif self.mobile_number and self.mobile_number.endswith('@g.us'):
  76. return self.mobile_number
  77. # TODOS LOS DEMÁS CASOS: usar validación original sin modificar
  78. return super()._whatsapp_phone_format(fpath, number, raise_on_format_error)
  79. def _get_final_destination(self):
  80. """Método mejorado para obtener destino final (grupo o teléfono)"""
  81. self.ensure_one()
  82. # 1. Si es tipo grupo y hay grupo seleccionado
  83. if self.recipient_type == 'group' and self.whatsapp_group_id:
  84. try:
  85. return self.whatsapp_group_id.whatsapp_web_id
  86. except Exception:
  87. # Si el modelo ww.group no existe, usar mobile_number
  88. pass
  89. # 2. Si el mobile_number es un ID de grupo
  90. if self.mobile_number and self.mobile_number.endswith('@g.us'):
  91. return self.mobile_number
  92. return False
  93. def _send_message(self, with_commit=False):
  94. url = ''
  95. if self.wa_account_id and self.wa_account_id.whatsapp_web_url:
  96. url = self.wa_account_id.whatsapp_web_url
  97. _logger.info('WHATSAPP WEB SEND MESSAGE' + url)
  98. group = ''
  99. if not url:
  100. super()._send_message(with_commit)
  101. for whatsapp_message in self:
  102. # Determinar destinatario final usando solo la nueva lógica
  103. final_destination = whatsapp_message._get_final_destination()
  104. if final_destination:
  105. group = final_destination
  106. attachment = False
  107. if whatsapp_message.wa_template_id:
  108. record = self.env[whatsapp_message.wa_template_id.model].browse(whatsapp_message.mail_message_id.res_id)
  109. #codigo con base a whatsapp.message y whatsapp.template para generacion de adjuntos
  110. RecordModel = self.env[whatsapp_message.mail_message_id.model].with_user(whatsapp_message.create_uid)
  111. from_record = RecordModel.browse(whatsapp_message.mail_message_id.res_id)
  112. # if retrying message then we need to unlink previous attachment
  113. # in case of header with report in order to generate it again
  114. if whatsapp_message.wa_template_id.report_id and whatsapp_message.wa_template_id.header_type == 'document' and whatsapp_message.mail_message_id.attachment_ids:
  115. whatsapp_message.mail_message_id.attachment_ids.unlink()
  116. if not attachment and whatsapp_message.wa_template_id.report_id:
  117. attachment = whatsapp_message.wa_template_id._generate_attachment_from_report(record)
  118. if not attachment and whatsapp_message.wa_template_id.header_attachment_ids:
  119. attachment = whatsapp_message.wa_template_id.header_attachment_ids[0]
  120. if attachment and attachment not in whatsapp_message.mail_message_id.attachment_ids:
  121. whatsapp_message.mail_message_id.attachment_ids = [(4, attachment.id)]
  122. # no template
  123. elif whatsapp_message.mail_message_id.attachment_ids:
  124. attachment = whatsapp_message.mail_message_id.attachment_ids[0]
  125. #codigo para limpiar body y numero
  126. body = whatsapp_message.body
  127. # Asegurar que body sea string y limpiar HTML
  128. if body:
  129. if isinstance(body, markupsafe.Markup):
  130. text = html.unescape(str(body))
  131. else:
  132. text = str(body)
  133. # Reemplazamos las etiquetas BR y P
  134. text = re.sub(r'<br\s*/?>|<BR\s*/?>', '\n', text)
  135. text = re.sub(r'<p>|<P>', '\n\n', text)
  136. text = re.sub(r'</p>|</P>', '', text)
  137. # Eliminamos el resto de etiquetas HTML
  138. text = re.sub(r'<[^>]+>', '', text)
  139. # Limpiamos múltiples saltos de línea
  140. text = re.sub(r'\n\s*\n\s*\n', '\n\n', text)
  141. # Limpiamos espacios en blanco al inicio y final
  142. body = text.strip()
  143. # Asegurar que no esté vacío
  144. if not body:
  145. body = "Mensaje de WhatsApp"
  146. else:
  147. body = "Mensaje de WhatsApp"
  148. # Determinar número/destinatario final
  149. if group:
  150. # Si ya hay un grupo determinado, usarlo
  151. number = group
  152. else:
  153. # Formatear número según el tipo de destinatario
  154. if whatsapp_message.recipient_type == 'group':
  155. if whatsapp_message.whatsapp_group_id:
  156. number = whatsapp_message.whatsapp_group_id.whatsapp_web_id
  157. elif whatsapp_message.mobile_number and whatsapp_message.mobile_number.endswith('@g.us'):
  158. number = whatsapp_message.mobile_number
  159. else:
  160. _logger.error("Mensaje configurado como grupo pero sin destinatario válido")
  161. continue
  162. else:
  163. # Lógica original para números de teléfono
  164. number = whatsapp_message.mobile_number
  165. if number:
  166. number = number.replace(' ', '').replace('+','').replace('-','')
  167. if number.startswith("52") and len(number) == 12:
  168. number = "521" + number[2:]
  169. if len(number) == 10:
  170. number = "521" + number
  171. number = number + '@c.us'
  172. # ENVIO DE MENSAJE
  173. # Headers de la petición, si es necesario
  174. headers = {
  175. "Content-Type": "application/json"
  176. }
  177. #$wa::sendMessage("521{$fields_data[$settings['borax_whatsapp_mobile']]}@c.us", ['type' => 'MessageMedia', 'args' => [mime_content_type($file), base64_encode(file_get_contents($file)), $filename, $filesize]], ['caption' => $borax_whatsapp_mensaje]);
  178. parent_message_id = ''
  179. if whatsapp_message.mail_message_id and whatsapp_message.mail_message_id.parent_id:
  180. parent_id = whatsapp_message.mail_message_id.parent_id.wa_message_ids
  181. if parent_id:
  182. parent_message_id = parent_id[0].msg_uid
  183. # Validar que tenemos un destinatario válido
  184. if not number:
  185. _logger.error("No se pudo determinar el destinatario para el mensaje")
  186. continue
  187. # Validar que tenemos un cuerpo de mensaje válido
  188. if not body or not isinstance(body, str):
  189. _logger.error("Cuerpo del mensaje inválido: %s", body)
  190. body = "Mensaje de WhatsApp"
  191. # Log del payload para debugging
  192. _logger.info("Enviando mensaje a %s: %s", number, body[:50] + "..." if len(body) > 50 else body)
  193. if attachment:
  194. payload = {
  195. "method": "sendMessage",
  196. "args": [number, {'type': 'MessageMedia', 'args': [attachment.mimetype, base64.b64encode(attachment.raw).decode('utf-8'), attachment.name, attachment.file_size]}, {'caption': body}]
  197. }
  198. else:
  199. payload = {
  200. "method": "sendMessage",
  201. "args": [number, body, {}]
  202. }
  203. if parent_message_id:
  204. payload['args'][2]['quotedMessageId'] = parent_message_id
  205. # Realizando la petición POST
  206. response = requests.post(url, data=json.dumps(payload), headers=headers)
  207. # Verificando si la respuesta contiene data->id
  208. if response.status_code == 200:
  209. try:
  210. response_json = response.json()
  211. except ValueError:
  212. _logger.error("La respuesta no es JSON válido: %s", response.text)
  213. response_json = {}
  214. if "_data" in response_json and "id" in response_json["_data"]:
  215. _logger.info(f"Petición exitosa. ID: {response_json['_data']['id']['id']}")
  216. whatsapp_message.write({
  217. 'state': 'sent',
  218. 'msg_uid': response_json['_data']['id']['_serialized']
  219. })
  220. self._cr.commit()
  221. else:
  222. _logger.info("La respuesta no contiene 'data->id'.")
  223. else:
  224. _logger.info(f"Error en la petición. Código de estado: {response.status_code}")
  225. time.sleep(random.randint(3, 7))