Implement WhatsApp linking
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from urllib.parse import quote_plus
|
||||
@@ -49,6 +50,8 @@ class WhatsAppClient(ClientBase):
|
||||
self.database_url = str(
|
||||
getattr(settings, "WHATSAPP_DATABASE_URL", "")
|
||||
).strip()
|
||||
safe_name = re.sub(r"[^a-zA-Z0-9_.-]+", "_", self.client_name) or "gia_whatsapp"
|
||||
self.session_db = self.database_url or f"/tmp/{safe_name}.db"
|
||||
|
||||
transport.register_runtime_client(self.service, self)
|
||||
self._publish_state(
|
||||
@@ -60,6 +63,7 @@ class WhatsAppClient(ClientBase):
|
||||
),
|
||||
accounts=[],
|
||||
last_event="init",
|
||||
session_db=self.session_db,
|
||||
)
|
||||
|
||||
def _publish_state(self, **updates):
|
||||
@@ -78,6 +82,7 @@ class WhatsAppClient(ClientBase):
|
||||
|
||||
async def _run(self):
|
||||
try:
|
||||
import neonize.aioze.client as wa_client_mod
|
||||
from neonize.aioze.client import NewAClient
|
||||
from neonize.aioze import events as wa_events
|
||||
try:
|
||||
@@ -100,9 +105,41 @@ class WhatsAppClient(ClientBase):
|
||||
self.log.warning("whatsapp neonize import failed: %s", exc)
|
||||
return
|
||||
|
||||
# Neonize async module ships with its own global event loop object.
|
||||
# In this runtime we already have a live asyncio loop; bind Neonize's
|
||||
# globals to it so QR/pair callbacks and connect tasks actually execute.
|
||||
try:
|
||||
wa_events.event_global_loop = self.loop
|
||||
wa_client_mod.event_global_loop = self.loop
|
||||
self._publish_state(
|
||||
neonize_loop_bound=True,
|
||||
neonize_loop_type=str(type(self.loop).__name__),
|
||||
last_event="neonize_loop_bound",
|
||||
)
|
||||
except Exception as exc:
|
||||
self._publish_state(
|
||||
neonize_loop_bound=False,
|
||||
last_event="neonize_loop_bind_failed",
|
||||
last_error=str(exc),
|
||||
)
|
||||
self.log.warning("failed binding neonize loop: %s", exc)
|
||||
|
||||
self._build_jid = wa_build_jid
|
||||
self._chat_presence = ChatPresence
|
||||
self._chat_presence_media = ChatPresenceMedia
|
||||
try:
|
||||
db_dir = os.path.dirname(self.session_db)
|
||||
if db_dir:
|
||||
os.makedirs(db_dir, exist_ok=True)
|
||||
except Exception as exc:
|
||||
self._publish_state(
|
||||
connected=False,
|
||||
warning=f"WhatsApp DB path setup failed: {exc}",
|
||||
last_event="db_path_setup_failed",
|
||||
last_error=str(exc),
|
||||
)
|
||||
self.log.warning("whatsapp db path setup failed: %s", exc)
|
||||
return
|
||||
self._client = self._build_client(NewAClient)
|
||||
if self._client is None:
|
||||
self._publish_state(
|
||||
@@ -216,8 +253,13 @@ class WhatsAppClient(ClientBase):
|
||||
return
|
||||
now_ts = int(time.time())
|
||||
try:
|
||||
if hasattr(self._client, "is_connected"):
|
||||
connected_value = await self._maybe_await(self._client.is_connected())
|
||||
check_connected = getattr(self._client, "is_connected", None)
|
||||
if check_connected is not None:
|
||||
connected_value = (
|
||||
await self._maybe_await(check_connected())
|
||||
if callable(check_connected)
|
||||
else await self._maybe_await(check_connected)
|
||||
)
|
||||
if connected_value:
|
||||
self._connected = True
|
||||
self._publish_state(
|
||||
@@ -322,6 +364,7 @@ class WhatsAppClient(ClientBase):
|
||||
last_event="qr_handler",
|
||||
pair_status="qr_ready",
|
||||
qr_received_at=int(time.time()),
|
||||
qr_probe_result="event",
|
||||
last_error="",
|
||||
)
|
||||
|
||||
@@ -359,23 +402,16 @@ class WhatsAppClient(ClientBase):
|
||||
return str(raw_payload).strip()
|
||||
|
||||
def _build_client(self, cls):
|
||||
candidates = []
|
||||
if self.database_url:
|
||||
candidates.append((self.client_name, self.database_url))
|
||||
candidates.append((self.client_name,))
|
||||
for args in candidates:
|
||||
# NewAClient first arg is the SQLite filename / DB string.
|
||||
try:
|
||||
return cls(*args)
|
||||
except TypeError:
|
||||
continue
|
||||
return cls(self.session_db)
|
||||
except Exception as exc:
|
||||
self.log.warning("whatsapp client init failed for args %s: %s", args, exc)
|
||||
try:
|
||||
if self.database_url:
|
||||
return cls(name=self.client_name, database=self.database_url)
|
||||
return cls(name=self.client_name)
|
||||
except Exception as exc:
|
||||
self.log.warning("whatsapp client init failed: %s", exc)
|
||||
self.log.warning("whatsapp client init failed (%s): %s", self.session_db, exc)
|
||||
self._publish_state(
|
||||
last_event="client_init_exception",
|
||||
last_error=str(exc),
|
||||
session_db=self.session_db,
|
||||
)
|
||||
return None
|
||||
|
||||
def _register_event_handlers(self, wa_events):
|
||||
@@ -459,6 +495,7 @@ class WhatsAppClient(ClientBase):
|
||||
last_event="pair_status_qr",
|
||||
pair_status="qr_ready",
|
||||
qr_received_at=int(time.time()),
|
||||
qr_probe_result="event",
|
||||
last_error="",
|
||||
)
|
||||
status_raw = self._pluck(event, "Status")
|
||||
@@ -501,6 +538,7 @@ class WhatsAppClient(ClientBase):
|
||||
last_event="qr_event",
|
||||
pair_status="qr_ready",
|
||||
qr_received_at=int(time.time()),
|
||||
qr_probe_result="event",
|
||||
last_error="",
|
||||
)
|
||||
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
{% if object.warning %}
|
||||
<p class="is-size-7" style="margin-top: 0.6rem;">{{ object.warning }}</p>
|
||||
{% endif %}
|
||||
{% if object.debug_lines %}
|
||||
<article class="notification is-light" style="margin-top: 0.6rem; margin-bottom: 0;">
|
||||
<p><strong>Runtime Debug</strong></p>
|
||||
<pre class="is-size-7" style="white-space: pre-wrap; margin: 0.4rem 0 0;">{% for line in object.debug_lines %}{{ line }}
|
||||
{% endfor %}</pre>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<article class="notification is-warning is-light" style="margin-bottom: 0;">
|
||||
<p><strong>WhatsApp QR Not Ready.</strong></p>
|
||||
@@ -34,11 +27,13 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if object.debug_lines %}
|
||||
<article class="notification is-light" style="margin-top: 0.6rem; margin-bottom: 0;">
|
||||
<p><strong>Runtime Debug</strong></p>
|
||||
<pre class="is-size-7" style="white-space: pre-wrap; margin: 0.4rem 0 0;">{% for line in object.debug_lines %}{{ line }}
|
||||
<details style="margin-top: 0.6rem;">
|
||||
<summary><strong>Runtime Debug</strong></summary>
|
||||
<article class="notification is-light" style="margin-top: 0.5rem; margin-bottom: 0;">
|
||||
<pre class="is-size-7" style="white-space: pre-wrap; margin: 0;">{% for line in object.debug_lines %}{{ line }}
|
||||
{% endfor %}</pre>
|
||||
</article>
|
||||
</details>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
@@ -105,23 +105,17 @@ class WhatsAppAccountAdd(SuperUserRequiredMixin, ObjectRead):
|
||||
qr_value = str(state.get("pair_qr") or "")
|
||||
return [
|
||||
f"connected={bool(state.get('connected'))}",
|
||||
f"runtime_updated={_age('updated_at')}",
|
||||
f"runtime_seen={_age('runtime_seen_at')}",
|
||||
f"pair_requested={_age('pair_requested_at')}",
|
||||
f"qr_received={_age('qr_received_at')}",
|
||||
f"last_qr_probe={_age('last_qr_probe_at')}",
|
||||
f"pair_status={state.get('pair_status') or '-'}",
|
||||
f"pair_request_source={state.get('pair_request_source') or '-'}",
|
||||
f"qr_probe_result={state.get('qr_probe_result') or '-'}",
|
||||
f"qr_handler_supported={state.get('qr_handler_supported')}",
|
||||
f"qr_handler_registered={state.get('qr_handler_registered')}",
|
||||
f"event_hook_callable={state.get('event_hook_callable')}",
|
||||
f"event_support={state.get('event_support') or {}}",
|
||||
f"last_event={state.get('last_event') or '-'}",
|
||||
f"last_error={state.get('last_error') or '-'}",
|
||||
f"pair_qr_present={bool(qr_value)} len={len(qr_value)}",
|
||||
f"accounts={state.get('accounts') or []}",
|
||||
f"warning={state.get('warning') or '-'}",
|
||||
f"pair_qr_present={bool(qr_value)}",
|
||||
f"session_db={state.get('session_db') or '-'}",
|
||||
]
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user