| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- from odoo import models, fields, api
- from odoo.tools import groupby
- from odoo.exceptions import ValidationError
- import logging
- import markupsafe
- import requests
- import json
- import time
- import random
- import re
- import html
- import base64
- _logger = logging.getLogger(__name__)
- class WhatsAppMessage(models.Model):
- _inherit = 'whatsapp.message'
- # Campos para soporte de grupos
- recipient_type = fields.Selection([
- ('phone', 'Phone Number'),
- ('group', 'WhatsApp Group')
- ], string='Recipient Type', default='phone', help="Type of recipient: phone number or WhatsApp group")
-
- whatsapp_group_id = fields.Many2one('ww.group', string='WhatsApp Group',
- help="WhatsApp group to send message to (if recipient_type is group)",
- ondelete='set null')
-
- @api.depends('recipient_type', 'mobile_number', 'whatsapp_group_id')
- def _compute_final_recipient(self):
- """Compute the final recipient based on type"""
- for record in self:
- if record.recipient_type == 'group' and record.whatsapp_group_id:
- record.final_recipient = record.whatsapp_group_id.whatsapp_web_id
- else:
- record.final_recipient = record.mobile_number
-
- final_recipient = fields.Char('Final Recipient', compute='_compute_final_recipient',
- help="Final recipient (phone or group ID)")
-
- @api.depends('mobile_number', 'recipient_type')
- def _compute_mobile_number_formatted(self):
- """Override SOLO para casos específicos de grupos con WhatsApp Web"""
- for message in self:
- # SOLO intervenir si es grupo CON WhatsApp Web configurado
- if (hasattr(message, 'recipient_type') and message.recipient_type == 'group' and
- message.wa_account_id and message.wa_account_id.whatsapp_web_url and
- message.mobile_number and message.mobile_number.endswith('@g.us')):
-
- message.mobile_number_formatted = message.mobile_number
- else:
- # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
- super(WhatsAppMessage, message)._compute_mobile_number_formatted()
-
- @api.constrains('recipient_type', 'mobile_number', 'whatsapp_group_id')
- def _check_recipient_configuration(self):
- """Validar configuración de destinatario"""
- for record in self:
- if record.recipient_type == 'group':
- if not record.whatsapp_group_id and not (record.mobile_number and record.mobile_number.endswith('@g.us')):
- raise ValidationError("Para mensajes a grupos, debe seleccionar un grupo o proporcionar un ID de grupo válido (@g.us)")
- elif record.recipient_type == 'phone':
- if not record.mobile_number or record.mobile_number.endswith('@g.us'):
- raise ValidationError("Para mensajes a teléfonos, debe proporcionar un número telefónico válido")
-
- @api.onchange('recipient_type')
- def _onchange_recipient_type(self):
- """Limpiar campos al cambiar tipo de destinatario"""
- if self.recipient_type == 'group':
- self.mobile_number = False
- else:
- self.whatsapp_group_id = False
-
- def _whatsapp_phone_format(self, fpath=None, number=None, raise_on_format_error=False):
- """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
- self.ensure_one()
-
- # SOLO intervenir en casos muy específicos de grupos
- # Si es un mensaje a grupo Y tiene WhatsApp Web configurado
- if (hasattr(self, 'recipient_type') and self.recipient_type == 'group' and
- self.wa_account_id and self.wa_account_id.whatsapp_web_url):
-
- if self.whatsapp_group_id:
- return self.whatsapp_group_id.whatsapp_web_id
- # Si el número es un ID de grupo (termina en @g.us), retornarlo sin validar
- elif number and number.endswith('@g.us'):
- return number
- elif self.mobile_number and self.mobile_number.endswith('@g.us'):
- return self.mobile_number
-
- # TODOS LOS DEMÁS CASOS: usar validación original sin modificar
- return super()._whatsapp_phone_format(fpath, number, raise_on_format_error)
-
- def _get_final_destination(self):
- """Método mejorado para obtener destino final (grupo o teléfono)"""
- self.ensure_one()
-
- # 1. Si es tipo grupo y hay grupo seleccionado
- if self.recipient_type == 'group' and self.whatsapp_group_id:
- try:
- return self.whatsapp_group_id.whatsapp_web_id
- except Exception:
- # Si el modelo ww.group no existe, usar mobile_number
- pass
-
- # 2. Si el mobile_number es un ID de grupo
- if self.mobile_number and self.mobile_number.endswith('@g.us'):
- return self.mobile_number
-
- return False
-
- def _send_message(self, with_commit=False):
- url = ''
- if self.wa_account_id and self.wa_account_id.whatsapp_web_url:
- url = self.wa_account_id.whatsapp_web_url
- _logger.info('WHATSAPP WEB SEND MESSAGE' + url)
- group = ''
-
- if not url:
- super()._send_message(with_commit)
-
- for whatsapp_message in self:
- # Determinar destinatario final usando solo la nueva lógica
- final_destination = whatsapp_message._get_final_destination()
-
- if final_destination:
- group = final_destination
-
- attachment = False
- if whatsapp_message.wa_template_id:
- record = self.env[whatsapp_message.wa_template_id.model].browse(whatsapp_message.mail_message_id.res_id)
- #codigo con base a whatsapp.message y whatsapp.template para generacion de adjuntos
- RecordModel = self.env[whatsapp_message.mail_message_id.model].with_user(whatsapp_message.create_uid)
- from_record = RecordModel.browse(whatsapp_message.mail_message_id.res_id)
- # if retrying message then we need to unlink previous attachment
- # in case of header with report in order to generate it again
- 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:
- whatsapp_message.mail_message_id.attachment_ids.unlink()
- if not attachment and whatsapp_message.wa_template_id.report_id:
- attachment = whatsapp_message.wa_template_id._generate_attachment_from_report(record)
- if not attachment and whatsapp_message.wa_template_id.header_attachment_ids:
- attachment = whatsapp_message.wa_template_id.header_attachment_ids[0]
-
- if attachment and attachment not in whatsapp_message.mail_message_id.attachment_ids:
- whatsapp_message.mail_message_id.attachment_ids = [(4, attachment.id)]
- # no template
- elif whatsapp_message.mail_message_id.attachment_ids:
- attachment = whatsapp_message.mail_message_id.attachment_ids[0]
- #codigo para limpiar body y numero
- body = whatsapp_message.body
-
- # Asegurar que body sea string y limpiar HTML
- if body:
- if isinstance(body, markupsafe.Markup):
- text = html.unescape(str(body))
- else:
- text = str(body)
-
- # Reemplazamos las etiquetas BR y P
- text = re.sub(r'<br\s*/?>|<BR\s*/?>', '\n', text)
- text = re.sub(r'<p>|<P>', '\n\n', text)
- text = re.sub(r'</p>|</P>', '', text)
-
- # Eliminamos el resto de etiquetas HTML
- text = re.sub(r'<[^>]+>', '', text)
-
- # Limpiamos múltiples saltos de línea
- text = re.sub(r'\n\s*\n\s*\n', '\n\n', text)
-
- # Limpiamos espacios en blanco al inicio y final
- body = text.strip()
-
- # Asegurar que no esté vacío
- if not body:
- body = "Mensaje de WhatsApp"
- else:
- body = "Mensaje de WhatsApp"
- # Determinar número/destinatario final
- if group:
- # Si ya hay un grupo determinado, usarlo
- number = group
- else:
- # Formatear número según el tipo de destinatario
- if whatsapp_message.recipient_type == 'group':
- if whatsapp_message.whatsapp_group_id:
- number = whatsapp_message.whatsapp_group_id.whatsapp_web_id
- elif whatsapp_message.mobile_number and whatsapp_message.mobile_number.endswith('@g.us'):
- number = whatsapp_message.mobile_number
- else:
- _logger.error("Mensaje configurado como grupo pero sin destinatario válido")
- continue
- else:
- # Lógica original para números de teléfono
- number = whatsapp_message.mobile_number
- if number:
- number = number.replace(' ', '').replace('+','').replace('-','')
- if number.startswith("52") and len(number) == 12:
- number = "521" + number[2:]
-
- if len(number) == 10:
- number = "521" + number
-
- number = number + '@c.us'
- # ENVIO DE MENSAJE
- # Headers de la petición, si es necesario
- headers = {
- "Content-Type": "application/json"
- }
-
- #$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]);
- parent_message_id = ''
- if whatsapp_message.mail_message_id and whatsapp_message.mail_message_id.parent_id:
- parent_id = whatsapp_message.mail_message_id.parent_id.wa_message_ids
- if parent_id:
- parent_message_id = parent_id[0].msg_uid
- # Validar que tenemos un destinatario válido
- if not number:
- _logger.error("No se pudo determinar el destinatario para el mensaje")
- continue
-
- # Validar que tenemos un cuerpo de mensaje válido
- if not body or not isinstance(body, str):
- _logger.error("Cuerpo del mensaje inválido: %s", body)
- body = "Mensaje de WhatsApp"
-
- # Log del payload para debugging
- _logger.info("Enviando mensaje a %s: %s", number, body[:50] + "..." if len(body) > 50 else body)
-
- if attachment:
- payload = {
- "method": "sendMessage",
- "args": [number, {'type': 'MessageMedia', 'args': [attachment.mimetype, base64.b64encode(attachment.raw).decode('utf-8'), attachment.name, attachment.file_size]}, {'caption': body}]
- }
- else:
- payload = {
- "method": "sendMessage",
- "args": [number, body, {}]
- }
- if parent_message_id:
- payload['args'][2]['quotedMessageId'] = parent_message_id
- # Realizando la petición POST
- response = requests.post(url, data=json.dumps(payload), headers=headers)
- # Verificando si la respuesta contiene data->id
- if response.status_code == 200:
- try:
- response_json = response.json()
- except ValueError:
- _logger.error("La respuesta no es JSON válido: %s", response.text)
- response_json = {}
- if "_data" in response_json and "id" in response_json["_data"]:
- _logger.info(f"Petición exitosa. ID: {response_json['_data']['id']['id']}")
- whatsapp_message.write({
- 'state': 'sent',
- 'msg_uid': response_json['_data']['id']['_serialized']
- })
- self._cr.commit()
- else:
- _logger.info("La respuesta no contiene 'data->id'.")
- else:
- _logger.info(f"Error en la petición. Código de estado: {response.status_code}")
- time.sleep(random.randint(3, 7))
|