Implement business plans
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
@@ -14,9 +15,14 @@ from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from core.clients import ClientBase, transport
|
||||
from core.messaging import history, media_bridge
|
||||
from core.messaging import history, media_bridge, reply_sync
|
||||
from core.models import Message, PersonIdentifier, PlatformChatLink
|
||||
|
||||
try:
|
||||
from google.protobuf.json_format import MessageToDict
|
||||
except Exception: # pragma: no cover
|
||||
MessageToDict = None
|
||||
|
||||
|
||||
class WhatsAppClient(ClientBase):
|
||||
"""
|
||||
@@ -45,6 +51,9 @@ class WhatsAppClient(ClientBase):
|
||||
self._qr_handler_supported = False
|
||||
self._event_hook_callable = False
|
||||
self._last_send_error = ""
|
||||
self.reply_debug_chat = str(
|
||||
getattr(settings, "WHATSAPP_REPLY_DEBUG_CHAT", "120363402761690215")
|
||||
).strip()
|
||||
|
||||
self.enabled = bool(
|
||||
str(getattr(settings, "WHATSAPP_ENABLED", "false")).lower()
|
||||
@@ -1464,6 +1473,72 @@ class WhatsAppClient(ClientBase):
|
||||
return sorted(str(key) for key in vars(obj).keys())
|
||||
return []
|
||||
|
||||
def _proto_to_dict(self, obj):
|
||||
if obj is None:
|
||||
return {}
|
||||
if isinstance(obj, dict):
|
||||
return obj
|
||||
# Neonize emits protobuf objects for inbound events. Convert them to a
|
||||
# plain dict so nested contextInfo reply fields are addressable.
|
||||
if MessageToDict is not None and hasattr(obj, "DESCRIPTOR"):
|
||||
try:
|
||||
return MessageToDict(
|
||||
obj,
|
||||
preserving_proto_field_name=True,
|
||||
use_integers_for_enums=True,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def _chat_matches_reply_debug(self, chat: str) -> bool:
|
||||
target = str(self.reply_debug_chat or "").strip()
|
||||
value = str(chat or "").strip()
|
||||
if not target or not value:
|
||||
return False
|
||||
value_local = value.split("@", 1)[0]
|
||||
return value == target or value_local == target
|
||||
|
||||
def _extract_reply_hints(self, obj, max_depth: int = 6):
|
||||
hints = []
|
||||
|
||||
def walk(value, path="", depth=0):
|
||||
if depth > max_depth or value is None:
|
||||
return
|
||||
if isinstance(value, dict):
|
||||
for key, nested in value.items():
|
||||
key_str = str(key)
|
||||
next_path = f"{path}.{key_str}" if path else key_str
|
||||
lowered = key_str.lower()
|
||||
if any(
|
||||
token in lowered
|
||||
for token in ("stanza", "quoted", "reply", "context")
|
||||
):
|
||||
if isinstance(nested, (str, int, float, bool)):
|
||||
hints.append(
|
||||
{
|
||||
"path": next_path,
|
||||
"value": str(nested)[:180],
|
||||
}
|
||||
)
|
||||
walk(nested, next_path, depth + 1)
|
||||
return
|
||||
if isinstance(value, list):
|
||||
for idx, nested in enumerate(value):
|
||||
walk(nested, f"{path}[{idx}]", depth + 1)
|
||||
|
||||
walk(obj, "", 0)
|
||||
# Deduplicate by path/value for compact diagnostics.
|
||||
unique = []
|
||||
seen = set()
|
||||
for row in hints:
|
||||
key = (str(row.get("path") or ""), str(row.get("value") or ""))
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
unique.append(row)
|
||||
return unique[:40]
|
||||
|
||||
def _normalize_timestamp(self, raw_value):
|
||||
if raw_value is None:
|
||||
return int(time.time() * 1000)
|
||||
@@ -2361,19 +2436,22 @@ class WhatsAppClient(ClientBase):
|
||||
]
|
||||
|
||||
async def _handle_message_event(self, event):
|
||||
msg_obj = self._pluck(event, "message") or self._pluck(event, "Message")
|
||||
text = self._message_text(msg_obj, event)
|
||||
event_obj = self._proto_to_dict(event) or event
|
||||
msg_obj = self._pluck(event_obj, "message") or self._pluck(event_obj, "Message")
|
||||
text = self._message_text(msg_obj, event_obj)
|
||||
if not text:
|
||||
self.log.debug(
|
||||
"whatsapp empty-text event shape: msg_keys=%s event_keys=%s type=%s",
|
||||
self._shape_keys(msg_obj),
|
||||
self._shape_keys(event),
|
||||
self._shape_keys(event_obj),
|
||||
str(type(event).__name__),
|
||||
)
|
||||
source = (
|
||||
self._pluck(event, "Info", "MessageSource")
|
||||
or self._pluck(event, "info", "message_source")
|
||||
or self._pluck(event, "info", "messageSource")
|
||||
self._pluck(event_obj, "Info", "MessageSource")
|
||||
or self._pluck(event_obj, "info", "message_source")
|
||||
or self._pluck(event_obj, "info", "messageSource")
|
||||
or self._pluck(event_obj, "info", "message_source")
|
||||
or self._pluck(event_obj, "info", "messageSource")
|
||||
)
|
||||
is_from_me = bool(
|
||||
self._pluck(source, "IsFromMe") or self._pluck(source, "isFromMe")
|
||||
@@ -2389,17 +2467,17 @@ class WhatsAppClient(ClientBase):
|
||||
self._pluck(source, "Chat") or self._pluck(source, "chat")
|
||||
)
|
||||
raw_ts = (
|
||||
self._pluck(event, "Info", "Timestamp")
|
||||
or self._pluck(event, "info", "timestamp")
|
||||
or self._pluck(event, "info", "message_timestamp")
|
||||
or self._pluck(event, "Timestamp")
|
||||
or self._pluck(event, "timestamp")
|
||||
self._pluck(event_obj, "Info", "Timestamp")
|
||||
or self._pluck(event_obj, "info", "timestamp")
|
||||
or self._pluck(event_obj, "info", "message_timestamp")
|
||||
or self._pluck(event_obj, "Timestamp")
|
||||
or self._pluck(event_obj, "timestamp")
|
||||
)
|
||||
msg_id = str(
|
||||
self._pluck(event, "Info", "ID")
|
||||
or self._pluck(event, "info", "id")
|
||||
or self._pluck(event, "ID")
|
||||
or self._pluck(event, "id")
|
||||
self._pluck(event_obj, "Info", "ID")
|
||||
or self._pluck(event_obj, "info", "id")
|
||||
or self._pluck(event_obj, "ID")
|
||||
or self._pluck(event_obj, "id")
|
||||
or ""
|
||||
).strip()
|
||||
ts = self._normalize_timestamp(raw_ts)
|
||||
@@ -2529,12 +2607,196 @@ class WhatsAppClient(ClientBase):
|
||||
)()
|
||||
if duplicate_exists:
|
||||
continue
|
||||
await history.store_message(
|
||||
reply_ref = reply_sync.extract_reply_ref(
|
||||
self.service,
|
||||
{
|
||||
"contextInfo": self._pluck(msg_obj, "contextInfo")
|
||||
or self._pluck(msg_obj, "ContextInfo")
|
||||
or self._pluck(msg_obj, "extendedTextMessage", "contextInfo")
|
||||
or self._pluck(msg_obj, "ExtendedTextMessage", "ContextInfo")
|
||||
or self._pluck(msg_obj, "imageMessage", "contextInfo")
|
||||
or self._pluck(msg_obj, "ImageMessage", "ContextInfo")
|
||||
or self._pluck(msg_obj, "videoMessage", "contextInfo")
|
||||
or self._pluck(msg_obj, "VideoMessage", "ContextInfo")
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"ephemeralMessage",
|
||||
"message",
|
||||
"extendedTextMessage",
|
||||
"contextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"EphemeralMessage",
|
||||
"Message",
|
||||
"ExtendedTextMessage",
|
||||
"ContextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"viewOnceMessage",
|
||||
"message",
|
||||
"extendedTextMessage",
|
||||
"contextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"ViewOnceMessage",
|
||||
"Message",
|
||||
"ExtendedTextMessage",
|
||||
"ContextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"viewOnceMessageV2",
|
||||
"message",
|
||||
"extendedTextMessage",
|
||||
"contextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"ViewOnceMessageV2",
|
||||
"Message",
|
||||
"ExtendedTextMessage",
|
||||
"ContextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"viewOnceMessageV2Extension",
|
||||
"message",
|
||||
"extendedTextMessage",
|
||||
"contextInfo",
|
||||
)
|
||||
or self._pluck(
|
||||
msg_obj,
|
||||
"ViewOnceMessageV2Extension",
|
||||
"Message",
|
||||
"ExtendedTextMessage",
|
||||
"ContextInfo",
|
||||
)
|
||||
or {},
|
||||
"messageContextInfo": self._pluck(msg_obj, "messageContextInfo")
|
||||
or self._pluck(msg_obj, "MessageContextInfo")
|
||||
or {},
|
||||
"message": {
|
||||
"extendedTextMessage": self._pluck(msg_obj, "extendedTextMessage")
|
||||
or self._pluck(msg_obj, "ExtendedTextMessage")
|
||||
or {},
|
||||
"imageMessage": self._pluck(msg_obj, "imageMessage") or {},
|
||||
"ImageMessage": self._pluck(msg_obj, "ImageMessage") or {},
|
||||
"videoMessage": self._pluck(msg_obj, "videoMessage") or {},
|
||||
"VideoMessage": self._pluck(msg_obj, "VideoMessage") or {},
|
||||
"documentMessage": self._pluck(msg_obj, "documentMessage")
|
||||
or {},
|
||||
"DocumentMessage": self._pluck(msg_obj, "DocumentMessage")
|
||||
or {},
|
||||
"ephemeralMessage": self._pluck(msg_obj, "ephemeralMessage")
|
||||
or {},
|
||||
"EphemeralMessage": self._pluck(msg_obj, "EphemeralMessage")
|
||||
or {},
|
||||
"viewOnceMessage": self._pluck(msg_obj, "viewOnceMessage")
|
||||
or {},
|
||||
"ViewOnceMessage": self._pluck(msg_obj, "ViewOnceMessage")
|
||||
or {},
|
||||
"viewOnceMessageV2": self._pluck(msg_obj, "viewOnceMessageV2")
|
||||
or {},
|
||||
"ViewOnceMessageV2": self._pluck(msg_obj, "ViewOnceMessageV2")
|
||||
or {},
|
||||
"viewOnceMessageV2Extension": self._pluck(
|
||||
msg_obj, "viewOnceMessageV2Extension"
|
||||
)
|
||||
or {},
|
||||
"ViewOnceMessageV2Extension": self._pluck(
|
||||
msg_obj, "ViewOnceMessageV2Extension"
|
||||
)
|
||||
or {},
|
||||
},
|
||||
},
|
||||
)
|
||||
reply_debug = {}
|
||||
if self._chat_matches_reply_debug(chat):
|
||||
reply_debug = reply_sync.extract_whatsapp_reply_debug(
|
||||
{
|
||||
"contextInfo": self._pluck(msg_obj, "contextInfo") or {},
|
||||
"messageContextInfo": self._pluck(msg_obj, "messageContextInfo")
|
||||
or {},
|
||||
"message": {
|
||||
"extendedTextMessage": self._pluck(
|
||||
msg_obj, "extendedTextMessage"
|
||||
)
|
||||
or {},
|
||||
"imageMessage": self._pluck(msg_obj, "imageMessage") or {},
|
||||
"videoMessage": self._pluck(msg_obj, "videoMessage") or {},
|
||||
"documentMessage": self._pluck(msg_obj, "documentMessage")
|
||||
or {},
|
||||
"ephemeralMessage": self._pluck(msg_obj, "ephemeralMessage")
|
||||
or {},
|
||||
"viewOnceMessage": self._pluck(msg_obj, "viewOnceMessage")
|
||||
or {},
|
||||
"viewOnceMessageV2": self._pluck(msg_obj, "viewOnceMessageV2")
|
||||
or {},
|
||||
"viewOnceMessageV2Extension": self._pluck(
|
||||
msg_obj, "viewOnceMessageV2Extension"
|
||||
)
|
||||
or {},
|
||||
},
|
||||
}
|
||||
)
|
||||
self.log.warning(
|
||||
"wa-reply-debug chat=%s msg_id=%s reply_ref=%s debug=%s",
|
||||
str(chat or ""),
|
||||
str(msg_id or ""),
|
||||
json.dumps(reply_ref, ensure_ascii=True),
|
||||
json.dumps(reply_debug, ensure_ascii=True),
|
||||
)
|
||||
reply_target = await reply_sync.resolve_reply_target(
|
||||
identifier.user,
|
||||
session,
|
||||
reply_ref,
|
||||
)
|
||||
message_meta = reply_sync.apply_sync_origin(
|
||||
{},
|
||||
reply_sync.extract_origin_tag(payload),
|
||||
)
|
||||
if self._chat_matches_reply_debug(chat):
|
||||
info_obj = self._proto_to_dict(self._pluck(event_obj, "Info")) or self._pluck(
|
||||
event_obj, "Info"
|
||||
)
|
||||
raw_obj = self._proto_to_dict(self._pluck(event_obj, "Raw")) or self._pluck(
|
||||
event_obj, "Raw"
|
||||
)
|
||||
message_meta["wa_reply_debug"] = {
|
||||
"reply_ref": reply_ref,
|
||||
"reply_target_id": str(getattr(reply_target, "id", "") or ""),
|
||||
"msg_id": str(msg_id or ""),
|
||||
"chat": str(chat or ""),
|
||||
"sender": str(sender or ""),
|
||||
"msg_obj_keys": self._shape_keys(msg_obj),
|
||||
"event_keys": self._shape_keys(event_obj),
|
||||
"info_keys": self._shape_keys(info_obj),
|
||||
"raw_keys": self._shape_keys(raw_obj),
|
||||
"event_type": str(type(event).__name__),
|
||||
"reply_hints_event": self._extract_reply_hints(event_obj),
|
||||
"reply_hints_message": self._extract_reply_hints(msg_obj),
|
||||
"reply_hints_info": self._extract_reply_hints(info_obj),
|
||||
"reply_hints_raw": self._extract_reply_hints(raw_obj),
|
||||
"debug": reply_debug,
|
||||
}
|
||||
local_message = await history.store_message(
|
||||
session=session,
|
||||
sender=str(sender or chat or ""),
|
||||
text=display_text,
|
||||
ts=ts,
|
||||
outgoing=is_from_me,
|
||||
source_service=self.service,
|
||||
source_message_id=str(msg_id or ""),
|
||||
source_chat_id=str(chat or sender or ""),
|
||||
reply_to=reply_target,
|
||||
reply_source_service=str(reply_ref.get("reply_source_service") or ""),
|
||||
reply_source_message_id=str(
|
||||
reply_ref.get("reply_source_message_id") or ""
|
||||
),
|
||||
message_meta=message_meta,
|
||||
)
|
||||
await self.ur.message_received(
|
||||
self.service,
|
||||
@@ -2542,6 +2804,7 @@ class WhatsAppClient(ClientBase):
|
||||
text=display_text,
|
||||
ts=ts,
|
||||
payload=payload,
|
||||
local_message=local_message,
|
||||
)
|
||||
|
||||
async def _handle_receipt_event(self, event):
|
||||
@@ -2679,6 +2942,11 @@ class WhatsAppClient(ClientBase):
|
||||
return ""
|
||||
if "@" in raw:
|
||||
return raw
|
||||
# Group chats often arrive as bare numeric ids in compose/runtime
|
||||
# payloads; prefer known group mappings before defaulting to person JIDs.
|
||||
group_jid = self._resolve_group_jid(raw)
|
||||
if group_jid:
|
||||
return group_jid
|
||||
digits = re.sub(r"[^0-9]", "", raw)
|
||||
if digits:
|
||||
# Prefer direct JID formatting for phone numbers; Neonize build_jid
|
||||
@@ -2691,6 +2959,66 @@ class WhatsAppClient(ClientBase):
|
||||
pass
|
||||
return raw
|
||||
|
||||
def _resolve_group_jid(self, value: str) -> str:
|
||||
local = str(value or "").strip().split("@", 1)[0].strip()
|
||||
if not local:
|
||||
return ""
|
||||
|
||||
# Runtime state is the cheapest source of truth for currently joined groups.
|
||||
state = transport.get_runtime_state(self.service) or {}
|
||||
for row in list(state.get("groups") or []):
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
candidates = (
|
||||
row.get("identifier"),
|
||||
row.get("chat_identifier"),
|
||||
row.get("chat"),
|
||||
row.get("jid"),
|
||||
row.get("chat_jid"),
|
||||
)
|
||||
matched = False
|
||||
for candidate in candidates:
|
||||
candidate_local = str(self._jid_to_identifier(candidate) or "").split(
|
||||
"@", 1
|
||||
)[0].strip()
|
||||
if candidate_local and candidate_local == local:
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
continue
|
||||
jid = str(
|
||||
self._jid_to_identifier(row.get("jid") or row.get("chat_jid") or "")
|
||||
).strip()
|
||||
if jid and "@g.us" in jid:
|
||||
return jid
|
||||
return f"{local}@g.us"
|
||||
|
||||
# DB fallback for compose pages that resolved from PlatformChatLink.
|
||||
try:
|
||||
link = (
|
||||
PlatformChatLink.objects.filter(
|
||||
service="whatsapp",
|
||||
chat_identifier=local,
|
||||
is_group=True,
|
||||
)
|
||||
.order_by("-updated_at", "-id")
|
||||
.first()
|
||||
)
|
||||
except Exception:
|
||||
link = None
|
||||
if link is not None:
|
||||
jid = str(self._jid_to_identifier(link.chat_jid or "")).strip()
|
||||
if jid and "@g.us" in jid:
|
||||
return jid
|
||||
return f"{local}@g.us"
|
||||
|
||||
# WhatsApp group ids are numeric and usually very long (commonly start
|
||||
# with 120...). Treat those as groups when no explicit mapping exists.
|
||||
digits = re.sub(r"[^0-9]", "", local)
|
||||
if digits and digits == local and len(digits) >= 15 and digits.startswith("120"):
|
||||
return f"{digits}@g.us"
|
||||
return ""
|
||||
|
||||
def _blob_key_to_compose_url(self, blob_key):
|
||||
key = str(blob_key or "").strip()
|
||||
if not key:
|
||||
@@ -2806,8 +3134,31 @@ class WhatsAppClient(ClientBase):
|
||||
metadata = dict(metadata or {})
|
||||
xmpp_source_id = str(metadata.get("xmpp_source_id") or "").strip()
|
||||
legacy_message_id = str(metadata.get("legacy_message_id") or "").strip()
|
||||
reply_to_upstream_message_id = str(
|
||||
metadata.get("reply_to_upstream_message_id") or ""
|
||||
).strip()
|
||||
reply_to_participant = str(metadata.get("reply_to_participant") or "").strip()
|
||||
reply_to_remote_jid = str(metadata.get("reply_to_remote_jid") or "").strip()
|
||||
person_identifier = None
|
||||
if xmpp_source_id:
|
||||
if legacy_message_id:
|
||||
person_identifier = await sync_to_async(
|
||||
lambda: (
|
||||
Message.objects.filter(id=legacy_message_id)
|
||||
.select_related("session__identifier__user", "session__identifier__person")
|
||||
.first()
|
||||
)
|
||||
)()
|
||||
if person_identifier is not None:
|
||||
person_identifier = getattr(
|
||||
getattr(person_identifier, "session", None), "identifier", None
|
||||
)
|
||||
if (
|
||||
person_identifier is not None
|
||||
and str(getattr(person_identifier, "service", "") or "").strip().lower()
|
||||
!= "whatsapp"
|
||||
):
|
||||
person_identifier = None
|
||||
if person_identifier is None and (xmpp_source_id or legacy_message_id):
|
||||
candidates = list(self._normalize_identifier_candidates(recipient, jid_str))
|
||||
if candidates:
|
||||
person_identifier = await sync_to_async(
|
||||
@@ -2828,8 +3179,25 @@ class WhatsAppClient(ClientBase):
|
||||
or ""
|
||||
).strip()
|
||||
|
||||
def _record_bridge(response, ts_value, body_hint=""):
|
||||
if not xmpp_source_id or person_identifier is None:
|
||||
async def _record_bridge(response, ts_value, body_hint=""):
|
||||
if person_identifier is None:
|
||||
return
|
||||
upstream_message_id = _extract_response_message_id(response)
|
||||
if legacy_message_id:
|
||||
try:
|
||||
await history.save_bridge_ref(
|
||||
person_identifier.user,
|
||||
person_identifier,
|
||||
source_service="whatsapp",
|
||||
local_message_id=legacy_message_id,
|
||||
local_ts=int(ts_value or int(time.time() * 1000)),
|
||||
upstream_message_id=upstream_message_id,
|
||||
upstream_author=str(recipient or ""),
|
||||
upstream_ts=int(ts_value or 0),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if not xmpp_source_id:
|
||||
return
|
||||
transport.record_bridge_mapping(
|
||||
user_id=person_identifier.user_id,
|
||||
@@ -2837,7 +3205,7 @@ class WhatsAppClient(ClientBase):
|
||||
service="whatsapp",
|
||||
xmpp_message_id=xmpp_source_id,
|
||||
xmpp_ts=int(metadata.get("xmpp_source_ts") or 0),
|
||||
upstream_message_id=_extract_response_message_id(response),
|
||||
upstream_message_id=upstream_message_id,
|
||||
upstream_ts=int(ts_value or 0),
|
||||
text_preview=str(body_hint or metadata.get("xmpp_body") or ""),
|
||||
local_message_id=legacy_message_id,
|
||||
@@ -2899,7 +3267,7 @@ class WhatsAppClient(ClientBase):
|
||||
sent_ts,
|
||||
self._normalize_timestamp(self._pluck(response, "Timestamp") or 0),
|
||||
)
|
||||
_record_bridge(response, sent_ts, body_hint=filename)
|
||||
await _record_bridge(response, sent_ts, body_hint=filename)
|
||||
sent_any = True
|
||||
if getattr(settings, "WHATSAPP_DEBUG", False):
|
||||
self.log.debug(
|
||||
@@ -2916,6 +3284,35 @@ class WhatsAppClient(ClientBase):
|
||||
if text:
|
||||
response = None
|
||||
last_error = None
|
||||
quoted_text_message = text
|
||||
if reply_to_upstream_message_id:
|
||||
try:
|
||||
from neonize.proto.waE2E.WAWebProtobufsE2E_pb2 import (
|
||||
ContextInfo,
|
||||
ExtendedTextMessage,
|
||||
Message as WAProtoMessage,
|
||||
)
|
||||
|
||||
context = ContextInfo(
|
||||
stanzaID=reply_to_upstream_message_id,
|
||||
)
|
||||
participant_jid = self._to_jid(reply_to_participant)
|
||||
remote_jid = self._to_jid(reply_to_remote_jid) or jid_str
|
||||
if participant_jid:
|
||||
context.participant = participant_jid
|
||||
if remote_jid:
|
||||
context.remoteJID = remote_jid
|
||||
quoted_text_message = WAProtoMessage(
|
||||
extendedTextMessage=ExtendedTextMessage(
|
||||
text=str(text or ""),
|
||||
contextInfo=context,
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
self.log.warning(
|
||||
"whatsapp quoted-reply payload build failed: %s", exc
|
||||
)
|
||||
quoted_text_message = text
|
||||
# Prepare cancel key (if caller provided command_id)
|
||||
cancel_key = None
|
||||
try:
|
||||
@@ -2945,7 +3342,7 @@ class WhatsAppClient(ClientBase):
|
||||
response = await self._call_client_method(
|
||||
getattr(self._client, "send_message", None),
|
||||
send_target,
|
||||
text,
|
||||
quoted_text_message,
|
||||
timeout=9.0,
|
||||
)
|
||||
sent_any = True
|
||||
@@ -3030,7 +3427,7 @@ class WhatsAppClient(ClientBase):
|
||||
sent_ts,
|
||||
self._normalize_timestamp(self._pluck(response, "Timestamp") or 0),
|
||||
)
|
||||
_record_bridge(response, sent_ts, body_hint=str(text or ""))
|
||||
await _record_bridge(response, sent_ts, body_hint=str(text or ""))
|
||||
|
||||
if not sent_any:
|
||||
self._last_send_error = "no_payload_sent"
|
||||
|
||||
Reference in New Issue
Block a user