Continue implementing WhatsApp

This commit is contained in:
2026-02-16 00:39:16 +00:00
parent b1a53034d5
commit 3d32834ccf
12 changed files with 796 additions and 120 deletions

View File

@@ -3,6 +3,7 @@ import base64
import io
import secrets
import time
from urllib.parse import quote_plus
from typing import Any
import aiohttp
@@ -99,11 +100,94 @@ def list_accounts(service: str):
state = get_runtime_state(service_key)
accounts = state.get("accounts") or []
if service_key == "whatsapp" and not accounts:
contacts = state.get("contacts") or []
recovered = []
seen = set()
for row in contacts:
if not isinstance(row, dict):
continue
candidate = str(row.get("identifier") or row.get("jid") or "").strip()
if not candidate or candidate in seen:
continue
seen.add(candidate)
recovered.append(candidate)
if recovered:
accounts = recovered
update_runtime_state(service_key, accounts=recovered)
if isinstance(accounts, list):
return accounts
return []
def _account_key(value: str) -> str:
raw = str(value or "").strip().lower()
if "@" in raw:
raw = raw.split("@", 1)[0]
return raw
def unlink_account(service: str, account: str) -> bool:
service_key = _service_key(service)
account_value = str(account or "").strip()
if not account_value:
return False
if service_key == "signal":
import requests
base = str(getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080")).rstrip("/")
target = quote_plus(account_value)
for path in (f"/v1/accounts/{target}", f"/v1/account/{target}"):
try:
response = requests.delete(f"{base}{path}", timeout=20)
if response.ok:
return True
except Exception:
continue
return False
if service_key in {"whatsapp", "instagram"}:
state = get_runtime_state(service_key)
key = _account_key(account_value)
raw_accounts = state.get("accounts") or []
accounts = []
for row in raw_accounts:
value = str(row or "").strip()
if not value:
continue
if _account_key(value) == key:
continue
accounts.append(value)
raw_contacts = state.get("contacts") or []
contacts = []
for row in raw_contacts:
if not isinstance(row, dict):
continue
identifier = str(row.get("identifier") or "").strip()
jid = str(row.get("jid") or "").strip()
if _account_key(identifier) == key or _account_key(jid) == key:
continue
contacts.append(row)
update_runtime_state(
service_key,
accounts=accounts,
contacts=contacts,
connected=bool(accounts),
pair_status=("connected" if accounts else ""),
pair_qr="",
warning=("" if accounts else "Account unlinked. Add account to link again."),
last_event="account_unlinked",
last_error="",
)
return True
return False
def get_service_warning(service: str) -> str:
service_key = _service_key(service)
if service_key == "signal":
@@ -131,6 +215,18 @@ def request_pairing(service: str, device_name: str = ""):
service_key = _service_key(service)
if service_key not in {"whatsapp", "instagram"}:
return
state = get_runtime_state(service_key)
existing_accounts = state.get("accounts") or []
is_connected = bool(state.get("connected"))
pair_status = str(state.get("pair_status") or "").strip().lower()
if existing_accounts and (is_connected or pair_status == "connected"):
update_runtime_state(
service_key,
warning="Account already linked.",
pair_status="connected",
pair_qr="",
)
return
device = str(device_name or "GIA Device").strip() or "GIA Device"
update_runtime_state(
service_key,
@@ -454,6 +550,17 @@ def get_link_qr(service: str, device_name: str):
return cached
if service_key == "whatsapp":
state = get_runtime_state(service_key)
existing_accounts = state.get("accounts") or []
pair_status = str(state.get("pair_status") or "").strip().lower()
if existing_accounts and (
bool(state.get("connected")) or pair_status == "connected"
):
raise RuntimeError(
"WhatsApp account already linked in this runtime. "
"Only one active linked device is supported. "
"Unlink the current account first, then add a new one."
)
raise RuntimeError(
"Neonize has not provided a pairing QR yet. "
"Ensure UR is running with WHATSAPP_ENABLED=true and retry."