Forráskód Böngészése

FIX: Prevent WhatsApp infinite loop and correct self-message attribution

M22 Bot 2 hete
szülő
commit
cdf6e5fd2e
3 módosított fájl, 39 hozzáadás és 205 törlés
  1. 1 1
      models/__init__.py
  2. 38 11
      models/whatsapp_account.py
  3. 0 193
      models/whatsapp_patch.py

+ 1 - 1
models/__init__.py

@@ -1,7 +1,7 @@
 from . import whatsapp_account
 from . import whatsapp_message
 from . import whatsapp_composer
-from . import whatsapp_patch
+
 from . import mail_message
 from . import discuss_channel
 from . import res_partner

+ 38 - 11
models/whatsapp_account.py

@@ -3,6 +3,7 @@ import requests
 import json
 import mimetypes
 import base64
+import traceback
 from markupsafe import Markup
 
 from odoo import fields, models, _
@@ -21,6 +22,15 @@ class WhatsAppAccount(models.Model):
     whatsapp_web_login = fields.Char(string="Login", readonly=False, copy=False)
     whatsapp_web_api_key = fields.Char(string="API Key", readonly=False, copy=False)
 
+    def _send_message(self, message_type, message_values, message_id):
+        """Trap to debug who is sending messages"""
+        _logger.warning(
+            "TRAP: _send_message called! Type: %s. Stack: \n%s",
+            message_type,
+            "".join(traceback.format_stack()),
+        )
+        return super()._send_message(message_type, message_values, message_id)
+
     def get_groups(self):
         """
         Obtiene los grupos de WhatsApp Web para la cuenta desde la base de datos de la plataforma.
@@ -150,11 +160,6 @@ class WhatsAppAccount(models.Model):
         y rutearlos al chat correcto.
         Refactorizado para soportar grupos vía metadata y creación Lazy.
         """
-        # Log del payload recibido para debug
-        _logger.info(
-            "DEBUG - WhatsApp Webhook Value: %s",
-            json.dumps(value, indent=4, default=str),
-        )
 
         if "messages" not in value and value.get("whatsapp_business_api_data", {}).get(
             "messages"
@@ -166,6 +171,9 @@ class WhatsAppAccount(models.Model):
         # 1. Identificar Remitente (Sender)
         contacts_data = value.get("contacts", [])
         sender_partner = self._find_or_create_partner_from_payload(contacts_data)
+        original_sender_partner = (
+            sender_partner  # Preserve original sender (Me) for attribution
+        )
 
         # Fallback Name if partner creation failed (rare)
         sender_name = sender_partner.name if sender_partner else "Unknown"
@@ -229,6 +237,22 @@ class WhatsAppAccount(models.Model):
                     sender_mobile,
                     messages["from"],
                 )
+
+                # Fix: Update sender_partner to be the RECIPIENT partner for correct channel naming
+                if len(sender_mobile) >= 10:
+                    sender_partner = (
+                        self.env["res.partner"]
+                        .sudo()
+                        .search(
+                            [("mobile", "like", f"%{sender_mobile[-10:]}")], limit=1
+                        )
+                    )
+                    if sender_partner:
+                        _logger.info(
+                            "Partner destinatario encontrado para self-message: %s",
+                            sender_partner.name,
+                        )
+
             # --- RECONCILIATION LOGIC ---
             # Si viene un job_id en metadata, reconciliar el ID antes de chequear duplicados.
             # Esto maneja el caso donde el "Echo" del mensaje trae el ID real y confirma el envío del worker.
@@ -363,7 +387,15 @@ class WhatsAppAccount(models.Model):
 
             # Determinar autor (Author ID)
             # Preferimos usar el partner identificado del payload
-            author_id = sender_partner.id if sender_partner else False
+            # Si es self-message, usar original_sender_partner (Me)
+            if is_self_message:
+                author_id = (
+                    original_sender_partner.id
+                    if original_sender_partner
+                    else self.env.ref("base.partner_root").id
+                )
+            else:
+                author_id = sender_partner.id if sender_partner else False
 
             # If no sender partner, try channel partner if target is channel
             if (
@@ -372,10 +404,6 @@ class WhatsAppAccount(models.Model):
             ):
                 author_id = target_record.whatsapp_partner_id.id
 
-            if is_self_message:
-                # Si es mensaje propio, usar el partner de la compañía o OdooBot
-                author_id = self.env.ref("base.partner_root").id
-
             kwargs = {
                 "message_type": "whatsapp_message",
                 "author_id": author_id,
@@ -484,7 +512,6 @@ class WhatsAppAccount(models.Model):
                 _logger.warning("Unsupported whatsapp message type: %s", messages)
                 continue
 
-            # Fix: Only pass whatsapp_inbound_msg_uid if valid for this channel type
             # Standard channels (like groups) do not support this param and will crash
             if getattr(target_record, "_name", "") == "discuss.channel":
                 if (

+ 0 - 193
models/whatsapp_patch.py

@@ -1,193 +0,0 @@
-import logging
-import base64
-from odoo.addons.whatsapp.tools.whatsapp_api import WhatsAppApi
-
-_logger = logging.getLogger(__name__)
-
-# Guarda una referencia al método original
-original_get_whatsapp_document = WhatsAppApi._get_whatsapp_document
-
-
-def custom_get_whatsapp_document(self, document_id):
-    _logger.info("Ejecutando versión modificada de _get_whatsapp_document")
-
-    if self.wa_account_id.whatsapp_web_url:
-        _logger.info(
-            "Ejecutando versión modificada de _get_whatsapp_document con whatsapp web"
-        )
-        try:
-            result = base64.b64decode(document_id)
-        except Exception:
-            # Si falla la decodificación (ej. es un ID y no base64), devolvemos vacío o el ID raw
-            # para evitar crash, aunque el archivo estará corrupto.
-            # TODO: Implementar fetch real a WPPConnect
-            _logger.warning(
-                "No se pudo decodificar base64 en _get_whatsapp_document, retornando vacío"
-            )
-            result = b""
-    else:
-        result = original_get_whatsapp_document(self, document_id)
-
-    # Aquí puedes modificar 'result' si es necesario antes de devolverlo
-    return result
-
-
-# Sobrescribir el método en tiempo de ejecución
-WhatsAppApi._get_whatsapp_document = custom_get_whatsapp_document
-
-# Parche para el método _post_whatsapp_reaction para evitar errores de constraint
-try:
-    from odoo.addons.whatsapp.models.mail_message import MailMessage
-
-    # Guardar referencia al método original
-    original_post_whatsapp_reaction = MailMessage._post_whatsapp_reaction
-
-    def custom_post_whatsapp_reaction(self, reaction_content, partner_id):
-        """Parche para evitar error de constraint cuando partner_id es None"""
-        self.ensure_one()
-
-        # Si no hay partner_id, no procesar la reacción
-        if not partner_id:
-            _logger.warning(
-                "Reacción de WhatsApp recibida sin partner_id para mensaje %s - ignorando",
-                self.id,
-            )
-            return
-
-        # Llamar al método original si hay partner_id
-        return original_post_whatsapp_reaction(self, reaction_content, partner_id)
-
-    # Aplicar el parche
-    MailMessage._post_whatsapp_reaction = custom_post_whatsapp_reaction
-    _logger.info("Parche aplicado exitosamente para _post_whatsapp_reaction")
-
-except ImportError as e:
-    _logger.warning("No se pudo aplicar el parche para _post_whatsapp_reaction: %s", e)
-
-# Parche para el método wa_phone_format de phone_validation para evitar AttributeError
-try:
-    from odoo.addons.whatsapp.tools import phone_validation
-
-    # Guardar referencia al método original
-    original_wa_phone_format = phone_validation.wa_phone_format
-
-    def custom_wa_phone_format(
-        record,
-        fname=False,
-        number=False,
-        country=None,
-        force_format="INTERNATIONAL",
-        raise_exception=True,
-    ):
-        """Parche para evitar AttributeError: 'bool' object has no attribute 'italian_leading_zero'"""
-
-        # Ejecutar lógica original, pero capturando errores
-        try:
-            # Reimplementar la parte final del método original que falla
-            # Primero llamamos al método original, si funciona, perfecto
-            return original_wa_phone_format(
-                record, fname, number, country, force_format, raise_exception
-            )
-        except AttributeError as e:
-            if "italian_leading_zero" in str(e):
-                _logger.warning(
-                    "Capturado AttributeError en wa_phone_format, intentando recuperación segura: %s",
-                    e,
-                )
-
-                # Intentar replicar la lógica segura aquí si es necesario
-                # Por ahora, simplemente devolvemos el número formateado si es posible obtenerlo
-                # o relanzamos si no podemos manejarlo
-
-                # Obtener el número base
-                if not number and record and fname:
-                    record.ensure_one()
-                    number = record[fname]
-
-                if not number:
-                    return False
-
-                # Si llegamos aquí es porque falló el acceso a atributos de parsed
-                # Devolvemos el número original o intentamos un formateo básico
-                return number
-            raise e
-
-    # Una mejor aproximación: Monkey Patch directo a la función interna si es posible,
-    # o redefinir completamente la función si el error está dentro de ella y no podemos envolverla fácilmente.
-    # Dado que el error ocurre DENTRO de la función original al acceder a parsed.italian_leading_zero,
-    # necesitamos redefinir la función completa para corregir el acceso al atributo.
-
-    def safe_wa_phone_format(
-        record,
-        fname=False,
-        number=False,
-        country=None,
-        force_format="INTERNATIONAL",
-        raise_exception=True,
-    ):
-        """Versión segura de wa_phone_format que maneja correctamente los atributos de parsed"""
-
-        # Importar dependencias necesarias
-        from odoo.addons.phone_validation.tools import phone_validation as pv_tools
-
-        if not number and record and fname:
-            record.ensure_one()
-            number = record[fname]
-        if not number:
-            return False
-
-        if not country and record:
-            country = record._phone_get_country().get(record.id)
-        if not country:
-            country = record.env.company.country_id
-
-        try:
-            formatted = pv_tools.phone_format(
-                number,
-                country.code,
-                country.phone_code,
-                force_format=force_format if force_format != "WHATSAPP" else "E164",
-                raise_exception=True,
-            )
-        except Exception:
-            if raise_exception:
-                raise
-            formatted = False
-
-        if formatted and force_format == "WHATSAPP":
-            try:
-                parsed = pv_tools.phone_parse(formatted, country.code)
-            except Exception:
-                if raise_exception:
-                    raise
-                return False
-
-            zeros = ""
-            # USO SEGURO DE ATRIBUTOS (Corrección del bug original)
-            if getattr(parsed, "italian_leading_zero", False):
-                zeros = "0"
-                if getattr(parsed, "number_of_leading_zeros", False):
-                    zeros = "0" * parsed.number_of_leading_zeros
-
-            # Verificación adicional para country_code y national_number
-            country_code = getattr(parsed, "country_code", "")
-            national_number = getattr(parsed, "national_number", "")
-
-            if not country_code or not national_number:
-                # Si no se pueden obtener estos datos, intentar usar el formatted original o un fallback
-                if formatted:
-                    return formatted
-                return number
-
-            return f"{country_code}" + zeros + f"{national_number}"
-
-        return formatted
-
-    # Aplicar el parche reemplazando la función en el módulo
-    phone_validation.wa_phone_format = safe_wa_phone_format
-    _logger.info("Parche aplicado exitosamente para phone_validation.wa_phone_format")
-
-except ImportError as e:
-    _logger.warning("No se pudo aplicar el parche para phone_validation: %s", e)
-except Exception as e:
-    _logger.warning("Error al aplicar parche para phone_validation: %s", e)